1
\$\begingroup\$

So I've been on a team that has been working on a game for quite a few months now and we're hitting some really nasty bloat problems with our character actions.

Question in bold. Accompanying information below it.

For complex character actions with concurrency of other actions, how would I end up refactoring our current system (see below) using a HSM. As what I've done so far poses concurrency issues.

We currently have these 'states/actions' for the characters.

  • Idle
  • Running/Movement
  • Attacking
  • Jumping
  • Falling
  • Attacking While Jumping
  • Attacking While Falling
  • Movement While Jumping
  • Movement While Falling
  • and more...

We started using a FSM to keep track of all the states for the character, but it's become massive and unmanageable. When we create a new action that can only execute based on some pre-requisites and/or causes current states to be changed, we have to make sure we check all the states.

Here is a small piece of code from our movement code that tries to determine if the character should move into an idle state.

    if (CurrentPosition == PreviousPosition &&
            animator != null &&
            mState.CurrentHas(StateFlags.Landed) == false &&
            mState.CurrentHas(StateFlags.InAir) == false && 
            mState.CurrentHas(StateFlags.Attacking) == false &&
            mState.CurrentHas(StateFlags.Stunned) == false &&
            mState.CurrentHas(StateFlags.Hurting) == false &&
            mState.CurrentHas(StateFlags.Falling) == false &&
            mState.CurrentHas(StateFlags.FallingNonJump) == false &&
            mState.CurrentHas(StateFlags.Charging) == false &&
            mState.CurrentHas(StateFlags.Idle) == false)
        {
                mState.Add(StateFlags.Idle);
                animator.AddToQueue(idleAnimation);
        }

Clearly this is smelly, ugly, and I know there are better ways. I've been trying to refactor into a HSM (which I don't believe I am doing correctly).

Here is an excerpt.

public interface UnitState
{
void Update(UnitStateBehavior usb);
void Enter(UnitStateBehavior usb);
void Exit(UnitStateBehavior usb);
}

public class IdleState : UnitState
{

public IdleState()
{

}

public void Update(UnitStateBehavior usb) 
{
   // Can run while idle (move)
   // Can attack while idle
   // Can jump while idle
}

public void Enter(UnitStateBehavior usb)
{

}

public void Exit(UnitStateBehavior usb)
{

}
}

I feel like I'll start to run into concurrency issues with other actions, such as in the above mentioned list. If I am jumping and start to attack, I'll play my attack animation and do other appropriate things that deal with now being in an attack state. However when the attack is over, what then? I could still be 'jumping' or increasing in Y, or 'falling' decreasing in Y. What if I've hit the ground while in the middle of an attack, I'd need to then go into an idle state and play its animation among other things. Nearly any action and be combined with another at any time dependent on user input. From all I've read about HSM I thought it would be the best decision, but I'm lacking in knowledge of proper implementation.

My implementation based off Aludor's Conversation

Instead of thinking of a stack of states as a literal stack, I bent it to be more of a collection of states, but still sticking mostly to a stack implementation. First in, last out.

I have an Actor class which keeps a reference to a StateCollection class. This StateCollection class keeps a list of states, with various methods for manipulating the list. Peek, Pop, Push, Remove, Replace, Clear. In-addition to manipulating the list, it offers methods for updating the states. FixedUpdate, Update, LateUpdate, for the various states of the game loop.

Each state derives from an abstract class UnitState. The UnitState holds methods for FixedUpdate, Update, LateUpdate, Enter, and Exit. This base class also keeps a reference to the Actor which owns it. The Actor also keeps a list of valid states for that particular Actor type.

Now when input is received by the Actor it is passed onto the StateCollection. This input is then traversed 'down' the stack. The input is consumed by the first state that uses it, so conflicting states do not act on the same input.

Each time a state is pushed to or removed from the stack the appropriate Enter/Exit method is called. Overall, this allows me to fully remove states, replace states (such as turning a jump into a fall), and allows states to run concurrently.

\$\endgroup\$
  • \$\begingroup\$ If you are going that way, you may consider implement the actions as separate state machines instead of collections of states. That way you would have one machine for movement (walk, run, jump) and another one for attack (idle, attack). These two machines evolve concurrently and independently, and the organization may be more clear that just dumping all the states in one single machine. Concurrency issues should be solved at resource level ("locking" a resource when it is being used by an action, for instance). \$\endgroup\$ – angarg12 Jul 21 '14 at 8:26
1
\$\begingroup\$

What if implementing the HSM with a stack of states for each entity you have in the game?

public final class StateStack {
    private List<State> states = new List<State>();

    public void PushState(State newState) {
        states.Add(newState);
    }

    public State PopState() {
        State topmostState = null;
        if (states.Count > 0) {
            int lastIndex = states.Count - 1;
            topmostState = states[lastIndex];
            states.RemoveAt(lastIndex);
        }
        return topmostState;
    }
}

I guess the biggest problem you are facing is, the interface lacks certain ways to retrieve previous states of entities, right? Let's say, the character first jumps into the air and then swings his sword before landing, you will have a stack of states in the following order:

  1. Idle
  2. Jump
  3. Attack

Where (1) is the state at the bottom of the stack.

\$\endgroup\$
  • \$\begingroup\$ I had written this out thinking it might help, but I found too many actions seemed to break it. Such as: I start off idle, jump, move forward while in the air, attack while still moving upward in the air and in the direction of movement. I would have a stack like. Idle -> Jump -> Move -> Attack. I would technically be in three states at once. Jumping up and increasing in Y, Moving forward and increasing in X, and attacking (whatever attack that would entail). Some attacks would even counteract other states. Such as the downward kick that can only happen if the player is in the air. \$\endgroup\$ – steve Jul 21 '14 at 4:10
  • \$\begingroup\$ Can states be divided further into more substates? For example, rather than pushing 'Jump' and 'Attack' states on to the stack, if the player perform an attack action in the air, pop 'Jump' first, and push 'JumpAttack' to the stack. So the character can continue his movement along the jumping trajectory and swing his sword at the same time. \$\endgroup\$ – S.C. Jul 21 '14 at 4:19
  • \$\begingroup\$ JumpComboAttack, JumpDownwardKickAttack, JumpProjectileAttack, ... Is that what you're talking about when you mean break it up into sub-states? If so, that might be just as needlessly complex. If I do the previous Idle-> Jump -> Move -> Attack. If I'm no longer pressing the move forward key in the middle of an attack. I'd have to remove the move from the stack, but move is no longer the top most in the stack. I'd also need to consider all the animation timings. \$\endgroup\$ – steve Jul 21 '14 at 4:51
  • \$\begingroup\$ Maybe the M.U.G.E.N. engine can bring you some inspiration. \$\endgroup\$ – S.C. Jul 21 '14 at 5:14
  • \$\begingroup\$ I'd like to state that Aldour is the most correct answer for my situation. I've edited my question with an implementation based off of the comments and his answer. \$\endgroup\$ – steve Jul 21 '14 at 7:07
0
\$\begingroup\$

First off, I think you should clearly define which are the states of your character and what are just actions that can be executed but don't change the state of the player. For instance, running is a state. But I wouldn't consider using a potion a state, just an action that affects the character in some way.

Usually I would consider attacking an action and not an state, but if your gameplay requires you to define it as an state (as in attacking is a different state that the player can be in), then you need to consider its relationship with other states.

What I understand from your question is that you don't know how to transition from "jumping" to "attacking" and then back. In that case you have two conditional transitions out of attack. For instance, in one branch, you transition to jump if the character is not touching the ground, otherwise you transition to idle. I would also consider defining a different state "jump_attack" to use while jumping instead of just a generic attack.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.