Triggering Animations from A Code Design Standpoint

So I was wondering if anyone had some advice on how they tied their animation system into other aspects of the game. Right now, I have a pretty simple Entity-Component design set up, with one of the Components being an AnimatedSpriteComponent. The ASC owns an AnimationManager, which essentially is a Dictionary of Animations with some code built around it to facilitate the timing, playing, and other aspects of animation.

So to get to my question, how do you tie your Animations into your other Systems? Right now, I have a MovementComponent that essentially holds the “physical” aspects of an entity (direction it’s facing, speed, position, states such as idle, walking, running, etc). This component is included on every entity by default, as I use it as a sort of “glue” compenent (i.e. the AnimatedSpriteComponent will get the position from this for drawing). Right now, the AnimatedSpriteComponent looks at it’s owner and decides what animation to play based on it’s current state. Is this a good way to go about this? Or am I walking into unforseen dangers? I’d love to decouple things as much as possible, but it seems like I have a pretty strong coupling here. However, I do think that game programming has a degree of coupling that is unavoidable.

So am I on the right track that the AnimatedSpriteComponent decides what animation to play? Or should other systems be telling it what to play? i.e.:

a) The input handler sees the player wants to swing their sword, so it tells the AnimatedSpriteComponent to play the “Swing Sword” animation

or

b) The input handler sees the player wants to swing their sword, so it updates the MovementComponent to put the player in a “SWING_SWORD” state. The AnimatedSpriteComponent will then later see the Entity is in the “SWING_SWORD” state, and then switch the sprite to the correct animation

Sorry if this is a bit wordy. I didn’t include any code since this is more of a code design quesiton, but I’d be happy to if it would help.

Thanks

I’m not a seasoned game developer, but I go with the second approach and it seems fine so far, my game runs well on mobile.
The code is a bit messy still, but this is how I do it

1 Like

Thanks for this. Seeing the relevant part in a bigger picture helps a bunch.

1 Like

For anyone who finds this, I think what I am ultimately going to do is create a StateMachineComponent/System and that will subscribe to Messages/Events created by other Systems in order to keep state up-to-date. The AnimationSystem will then subscribe to StateChange Events/Messages and update the current animation/frame accordingly.

I’m not 100% sure what the data is going to look like here, but I would think that the AnimationComponent would have a Dictionary of state->animation. Is this perfectly decoupled? No, these systems would still be loosely/silently coupled (they need to be in some agreement on what kind of states there are), but I think it’s the easiest to wrap my head around from a “close to pure as possible” ECS.

Thought I’d maybe add how I’m doing this, just in case.

I’m also doing an Entity-Component framework thing and my approach is closer to your “a” example I think.

My implementation isn’t very fancy, but I feel like it’s pretty pragmatic. I’m happy with it and I like using it, it’s also pretty simple.

The AnimationState class describes an animation that is used by the Animator component.

public class AnimationState
{
    public string Name;
    public int Start;
    public int End;
    public bool Loop = true;
    public float Speed = 10;
    public List<AnimationEvent> Events;
    public bool Reverse;

    public AnimationState(string name, int start, int? end = null, bool loop = true, float speed = 10, List<AnimationEvent> events = null)
    {
        Name = name;
        Start = start;
        End = end ?? start;
        Loop = loop;
        Speed = speed;
        Events = events;
        if (end < start)
        {
            Reverse = true;
        }
    }
}

The Animation Event class describes an arbitrary action to be called during an animation at a specific frame or frames. I would use this, for example, if during a run animation I wanted some poofs of dust to come out of the characters feet at a specific frame.

public class AnimationEvent
{
    public List<int> Frames;
    public Action Action;

    public AnimationEvent() { }
    public AnimationEvent(int frame, Action action)
    {
        Frames = new List<int>() { frame };
        Action = action;
    }
    public AnimationEvent(List<int> frames, Action action)
    {
        Frames = frames;
        Action = action;
    }
}

The Animator component given an AnimationState just animates a value on the Sprite component that decides which tile to show from it’s spritesheet.

public class Animator : Component
{
    // Public 

    public AnimationState State;
    public int Offset = 0;
    public float Frame = 1;

    // Private

    Sprite sprite;
    int min = 1;
    int max;
    float speed = 10;

    AnimationState oldState;
    int? oldFrame;

    // Methods

    public void SetState(AnimationState state)
    {
		sprite = sprite ?? Entity.GetComponent<Sprite>();

		State = state;
        if (State?.Name != oldState?.Name)
        {
            Frame = State.Start;
            speed = State.Speed;
        }

		int flooredFrame = (int)Math.Floor(Frame);
        sprite.TileNumber = flooredFrame + Offset;
    }

    public override void Start()
    {
        base.Start();

        sprite = Entity.GetComponent<Sprite>();

        // Calculate maximum Frame based on our Sprite's spritesheet
        max = (int)MathF.Floor(
            (sprite.Texture.Width / sprite.TileSize.Value)
            * (sprite.Texture.Height / sprite.TileSize.Value)
        );
    }

    public override void Update()
    {
        base.Update();

        if (State == null) return;

        // Check if State is different
        if (State?.Name != oldState?.Name)
        {
            Frame = State.Start;
            speed = State.Speed;
        }

        // Increment Frame
        Frame += (speed * Time.Delta) * (State.Reverse ? -1 : 1);

        // Check if we've passed the end of the spritesheet.
        if (Frame >= max + min) Frame = State.Loop ? State.Start : max;

        // Loop
        if (State.Reverse)
        {
            if (Frame <= State.End) Frame = State.Loop ? State.Start : State.End;
        }
        else
        {
            if (Frame >= State.End + 1) Frame = State.Loop ? State.Start : State.End;
        }

        // Update our Sprite component
        int flooredFrame = (int)Math.Floor(Frame);
        sprite.TileNumber = flooredFrame + Offset;

        // Process events
        if (flooredFrame != oldFrame && State.Events != null && State.Events.Count > 0)
        {
            AnimationEvent animationEvent = State.Events.Find(e => e.Frames.Contains(flooredFrame));
            if (animationEvent != null)
            {
                animationEvent.Action();
            }
        }

        // Remember stuff
        oldFrame = flooredFrame;
        oldState = State;
    }
}

And then I’ll have another component that will update the Animator component based on whatever arbitrary scenerio:

if (input.X != 0)
{
    animator.SetState(new AnimationState("Walk", 3, 6));
}
else
{
    animator.SetState(new AnimationState("Idle", 1, 2));
}   

Hope this helps!