AnimatedSprite Combo System

Hi everyone,

CONCEPT

I’d like to build a Combo System for AnimatedSprite class, so that for example timed button presses in succession lead to a string of Animations (Like in Devil May Cry). I am having difficulty with the exact implementation. Any ideas would be very much appreciated!

The basic idea is to have a Combo be a tree structure comprised of ComboNodes. The designated Button for the root ComboNode leads to the execution of it’s Animation and the opening of a TimeFrame in which another button can be pressed for the next ComboNode to execute.

The Combo class updates the current ComboNode for checking button presses and eventually leading to the next ComboNode. Each ComboNode can set the Sprites Animation by using the “SetAnimation(EAnimation)” method.

The lack of appropriate input inside the given timeframe leads to the Combo being reset to the root ComboNode.

RELVANT CODE

public class Combo
{
#region MemberVariables

    private ComboNode _root;
    private ComboNode _current;
    private AnimatedSprite _sprite;

    #endregion
    #region Properties

    public ComboNode Root => _root;

    public ComboNode Current
    {
        get => _current;
        set => _current = value;
    }

    public AnimatedSprite Sprite => _sprite;

    #endregion
    #region Methods

    public Combo(ComboNode root, AnimatedSprite sprite)
    {
        _root = root;
        _current = _root;
        _sprite = sprite;
    }

    public void Update(GameTime gameTime)
    {
        _current.Update(gameTime);
    }

    /// <summary>
    /// Resets the Combo to root ComboNode.
    /// </summary>
    public void Reset()
    {
        _current = _root;
    }

    public void Add(ComboNode comboNode)
    {
        throw new NotImplementedException();
    }

    public override string ToString()
    {
        throw new NotImplementedException();
    }

    #endregion
}

}

public class ComboNode
{
#region MemberVariables

    /// <summary>
    /// The Combo that this ComboNode is part of.
    /// </summary>
    private Combo _combo;

    /// <summary>
    /// Animation associated to this part of the Combo.
    /// </summary>
    private EAnimation _animation;

    /// <summary>
    /// Part of the Combo that comes after this.
    /// </summary>
    private Dictionary<Keys, ComboNode> _next;

    /// <summary>
    /// PassedTime since last ComboNode finished.
    /// </summary>
    private int _passedTime;

    /// <summary>
    /// Stores if this ComboNode has been executed.
    /// </summary>
    private bool _checkNextInput = false;

    /// <summary>
    /// When this ComboNode has finished, the Intervall describes the TimeFrame in which the corresponding
    /// Key / Button of the next ComboNode has to be pressed for it to execute.
    /// </summary>
    private Intervall _intervall;

    /// <summary>
    /// Key to execute this ComboNode.
    /// </summary>
    private Keys _key;

    /// <summary>
    /// Button to execute this ComboNode.
    /// </summary>
    private Buttons _button;

    #endregion

    /// <summary>
    /// Describes a time intervall in which the Key / Button has to be pressed to continue the Combo.
    /// </summary>
    public struct Intervall
    {
        private int _start;
        private int _end;

        public int Start => _start;
        public int End => _end;

        public Intervall(int start, int end)
        {
            _start = start;
            _end = end;
        }
    }

    public Keys Key => _key;
    public EAnimation Animation => _animation;

    public ComboNode(Combo combo, EAnimation animation, Dictionary<Keys, ComboNode> next, Intervall intervall, 
        Keys key, Buttons button)
    {
        _combo = combo;
        _animation = animation;
        _next = next;
        _intervall = intervall;
        _key = key;
        _button = button;
    }

    public void Update(GameTime gameTime)
    {
        if (!_checkNextInput)
        {
            if (InputManager.OnKeyDown(_key))
            {
                _combo.Sprite.SetAnimation(_animation);
                _checkNextInput = true;
            }
        }
        else
        {
            _passedTime += gameTime.ElapsedGameTime.Milliseconds;

            // No Input inside TimeIntervall.
            if (_passedTime > _intervall.End)
            {
                _combo.Reset();
                _checkNextInput = false;
            }

            // Look if there is / was any input for one of the next ComboNodes in the Time Intervall.
            else if (_passedTime > _intervall.Start && _passedTime < _intervall.End)
                foreach (Keys k in _next.Keys)
                    if (InputManager.OnKeyDown(k))
                        _combo.Current = _next[k];
        }
    }
}

}

///


/// Prepares the AnimatedSprite for playing the given Animation.
///

///
public void SetAnimation(EAnimation name)
{
// Makes sure we won’t start a new annimation unless it differs from our current animation.
if (_currentAnimation != name && _currentDirection.Equals(Direction.none))
{
_playingAnimation = true;
_currentAnimation = name;
_currentFrameIndex = 0;
Fps = _animationFpsValues[name];
OnAnimationStart(name);
}
}

Hi @Dante3085 ,

I haven’t had a deep look at the code (I’m always a bit lost if I can’t step through) but I haven’t seen any problem with it, so I don’t know if you’re asking for general advice or searching for a bug in the code.

If it’s just advice, I’d recommend 3 things:

  1. You could think of using a FSM (Finite State Machine). Each state is an animation of the combo and each transition is activated with a key in the right moment, or a timeout. The good part is that there are tons free implementations of FSM in the web, so you can use them if you don’t want to write it. I used a FSM for a lame tekken-like style game a long time ago and it worked wonders.

  2. If like me you hate to use external code for relative simple tasks, I’d recommend changing the update function into something like "update (float deltaTime, Keys keys) "

I’m not a big fan of tests, but this is definitively a piece of code that can benefit a lot from them.

i.e. something like

simulateKeypress (Forward,D3);
update (0.2f,keys);
simulateKeypress (D1,D2)
update (0.1f,keys)
if (currentState!=“Cougar DDT”) test_error

with relatively few tests you can get a good coverage, or you can go wild and test all the moves of your characters

  1. Whatever you do (with FSM is very easy) log each frame input and output. This way when you’re playing and your character does something weird, you can go back and look at the FSM states and deduce what’s wrong without having to reproduce it again.

I hope it helps :slight_smile:

p.s. remember users want to redefine keys or use gamepads, so the “key state” should be something that you’ll easily be able to use with redefined keys or gamepads. struct KeyState {public bool up,down,left,right,kick,puch, … } would do the trick, and you fill it up before calling update.

Hi @KakCAT,

I very much appreciate you taking the time to answer!

Yeah, happens to me all the time as well :smiley:

I guess it’s more about general advice, since I am failing at completely conceptualizing the system in my head to implement it.

That sounds like a good idea. I think I am actually using some sort of StateMachine or StateStack for changing screens in the game. I guess i could then think of the Combos as Graphs with the Nodes being the Animations and the edges being the conditions for moving to a different Node.
I’d definetly like to implement as much as possible from the fundamentals of the game myself.

I have seen the “float deltaTime” in people’s code a few times. Why couldn’t I just use “GameTime gameTime”. I don’t really understand what that is.

Like the following ?
///


/// Masks GamePadInput for this game.
///

public class GamePadInput
{
#region WorldMovement
public Buttons Left { get; set; }
public Buttons Up { get; set; }
public Buttons Right { get; set; }
public Buttons Down { get; set; }
public Buttons Run { get; set; }
public Buttons Interact { get; set; }
#endregion
#region Combat
public Buttons CursorLeft { get; set; }
public Buttons CursorUp { get; set; }
public Buttons CursorRight { get; set; }
public Buttons CursorDown { get; set; }
public Buttons Confirm { get; set; }
public Buttons Back { get; set; }

    public Buttons Combo { get; set; } 
    #endregion
    #region Methods

    /// <summary>
    /// Returns GamePadInput with default Layout.
    /// </summary>
    /// <returns></returns>
    public static GamePadInput Default()
    {
        return new GamePadInput()
        {
            Left = Buttons.LeftThumbstickLeft,
            Up = Buttons.LeftThumbstickUp,
            Right = Buttons.LeftThumbstickRight,
            Down = Buttons.LeftThumbstickDown,
            Run = Buttons.RightShoulder,
            Interact = Buttons.A,
            Combo = Buttons.X,
        };
    }

    public static GamePadInput None()
    {
        return new GamePadInput()
        {
            Left = 0,
            Up = 0,
            Right = 0,
            Down = 0,
            Run = 0,
            Interact = 0,
            Combo = 0,
        };
    }

    #endregion
}

It does, thank you :thumbsup:

You could, but at some point you’re going to have to convert into a float/double anyways in order to actually use it. GameTime is just a pair of TimeSpan for elapsed and total time.

Instead of constantly converting a TimeSpan into a fraction of seconds over and over and over, most everyone is going to go for doing it just once and passing the fraction of time along.

1 Like

About timeDelta, as AcidFaucent says it’s the same a GameTime.ElapsedTime.TotalSeconds. But most people only use that field of GameTime, so ends up using timeDelta. Also, I don’t know how hard/slow is to construct a new GameTime, but a float is just a float, so if you have to use timeDelta*0.25 it’s trivial if you’re passing a float but not that trivial if you’re passing a GameTime.

there are lots and lots of ways to do this, mine is usually like this:

first, I define a “control state”

struct ControlState { public bool up,down,left,right; public bool actionBtn,jumpBtn;
...

}

and then, at update I call a function which does something like this (obviously pseudocode)

// reset state
state.up=false;
state.down=false;

if (userControl is GamePad)
{
state.Up|=ReadGamePad (GamePad.Up);
state.Down|=ReadGamePad (GamePad.Down);

state.actionBtn|=ReadGamePad (GamePad.Square);
state.jump|=ReadGamePad (GamePad.X);
}

if (userControl is Keyboard)
{
state.Up|=ReadKeyboard (Key.Up);
state.Down|=ReadKeyboard (Key.Down);

state.actionBtn|=ReadKeyboard (Key.Z);
}

if (userControl is Mouse)
{

state.actionBtn|=ReadMouseButton (Btn.left);
}

if (userControl is MindControl)
{
// to do
}

I use the OR assignment |= because this way you can combine inputs from Keyboard and Mouse, in example. If you don’t want to combine inputs, you can either change it for = and return at the end of each block, or just add “else if”

If you require thumbsticks instead of up/down/left/right, I use a Vector2 where X&Y are the values of the thumbsticks, and you can simulate them in keyboard by setting Vector2.X to -1(left) or 1(right). You can even put code to gradually move from 0 to -1 depending on how long the key has been pressed, but this requires more logic.

Same for pressure triggers, use a float instead of a bool.

So at the end, you have a filled ControlState which you can use it in your game, indepedent of what device control the user is playing with.

Finally, if you require complex motions like quarter circle forward or f,n,d,d/f you can attach a timestamp at the ControlState and log them each frame to later scan and detect these motions, but there are better ways to do that.

1 Like