Skip to content

Chapter 4. Advanced Techniques

Antonio Maiorano edited this page Nov 5, 2015 · 19 revisions

Introduction

In the last chapter, we covered the basics of how to author a hierarchical state machine using HSM. In this chapter, we will take a look at some advanced techniques that will aid in making your state machines easier to manage, and more expressive.

StateValue

When working with hierarchical state machines, one common pattern that arises is setting a shared value in OnEnter, and restoring that same value in OnExit. In this section, we'll learn how to make use of a feature of HSM called StateValue to facilitate this common pattern.

Without StateValue

Before we look at how to use the StateValue feature, we'll start with an example that sets/unsets values in states directly. Once again, this code demonstrates a possible state machine for a character controller:

// state_value_without.cpp

#include "hsm/statemachine.h"

using namespace hsm;

class PhysicsComponent
{
public:
	void SetSpeed(float speed) {} // Stub
	void Move() {} // Stub
};

class Character
{
public:
	Character();
	void Update();

	// Public to simplify sample
	bool mInWater;
	bool mMove;
	bool mCrawl;

private:
	friend struct CharacterStates;
	StateMachine mStateMachine;

	PhysicsComponent mPhysicsComponent;
	float mSpeedScale; // [0,1]
};

struct CharacterStates
{
	struct BaseState : StateWithOwner<Character>
	{
	};

	struct Alive : BaseState
	{
		virtual Transition GetTransition()
		{
			return InnerEntryTransition<OnGround>();
		}
	};

	struct OnGround : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mInWater)
				return SiblingTransition<Swim>();

			return InnerEntryTransition<Stand>();
		}
	};

	struct Stand : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mMove)
				return SiblingTransition<Move>();

			return NoTransition();
		}
	};

	struct Move : BaseState
	{
		virtual Transition GetTransition()
		{
			if (!Owner().mMove)
				return SiblingTransition<Stand>();

			return InnerEntryTransition<Move_Walk>();
		}
	};

	struct Move_Walk : BaseState
	{
		float mLastSpeedScale;

		virtual void OnEnter()
		{
			mLastSpeedScale = Owner().mSpeedScale;
			Owner().mSpeedScale = 1.0f; // Full speed when moving normally
		}

		virtual void OnExit()
		{
			Owner().mSpeedScale = mLastSpeedScale;
		}

		virtual Transition GetTransition()
		{
			if (Owner().mCrawl)
				return SiblingTransition<Move_Crawl>();
			
			return NoTransition();
		}
	};

	struct Move_Crawl : BaseState
	{
		float mLastSpeedScale;

		virtual void OnEnter()
		{
			mLastSpeedScale = Owner().mSpeedScale;
			Owner().mSpeedScale = 0.5f; // Half speed when crawling
		}

		virtual void OnExit()
		{
			Owner().mSpeedScale = mLastSpeedScale;
		}

		virtual Transition GetTransition()
		{
			if (!Owner().mCrawl)
				return SiblingTransition<Move_Walk>();

			return NoTransition();
		}
	};

	struct Swim : BaseState
	{
		float mLastSpeedScale;

		virtual void OnEnter()
		{
			mLastSpeedScale = Owner().mSpeedScale;
			Owner().mSpeedScale = 0.3f; // ~1/3 speed when swimming
		}

		virtual void OnExit()
		{
			Owner().mSpeedScale = mLastSpeedScale;
		}

		virtual Transition GetTransition()
		{
			if (!Owner().mInWater)
				return SiblingTransition<OnGround>();

			return NoTransition();
		}
	};
};

Character::Character()
	: mInWater(false)
	, mMove(false)
	, mCrawl(false)
	, mSpeedScale(0.0f) // By default we don't move
{
	mStateMachine.Initialize<CharacterStates::Alive>(this);
	mStateMachine.SetDebugInfo("TestHsm", TraceLevel::Basic);
}

void Character::Update()
{
	// Update state machine
	mStateMachine.ProcessStateTransitions();
	mStateMachine.UpdateStates();

	// Move character
	const float MAX_SPEED = 100.0f;
	float currSpeed = mSpeedScale * MAX_SPEED;
	mPhysicsComponent.SetSpeed(currSpeed);
	mPhysicsComponent.Move();

	printf("Current speed: %f\n", currSpeed);
}

int main()
{
	Character character;
	character.Update();

	character.mMove = true;
	character.Update();

	character.mCrawl = true;
	character.Update();

	character.mInWater = true;
	character.Update();

	character.mInWater = false;
	character.mMove = false;
	character.mCrawl = false;
	character.Update();
}

Before we talk about the code, let's take a look at the plotHsm output for this state machine:

state_value_without

Just from this plot, we can infer a few things about this character controller. We see the character can be either on the ground or swimming. While on ground, the character can stand or move, and while moving can be either walking or crawling.

In the example code, there is a class named PhysicsComponent that would be responsible for moving a physical representation of the character, handling collisions, etc. This class is stubbed out for simplicity. In Character::Update, we can see how the speed is computed and set on this PhysicsComponent before calling Move() on it:

void Character::Update()
{
	// Update state machine
	mStateMachine.ProcessStateTransitions();
	mStateMachine.UpdateStates();

	// Move character
	const float MAX_SPEED = 100.0f;
	float currSpeed = mSpeedScale * MAX_SPEED;
	mPhysicsComponent.SetSpeed(currSpeed);
	mPhysicsComponent.Move();

	printf("Current speed: %f\n", currSpeed);
}

The idea is that the character can move at a max speed of 100 units per second, and the data member mSpeedScale is used to scale the max speed based on the character's current state. By default, mSpeedScale is initialized to 0 in the constructor, so by default, the character does not move. In the three states where the character can move, we find similar code to save/set/restore mSpeedScale:

	struct Move_Walk : BaseState
	{
		float mLastSpeedScale;

		virtual void OnEnter()
		{
			mLastSpeedScale = Owner().mSpeedScale;
			Owner().mSpeedScale = 1.0f; // Full speed when moving normally
		}

		virtual void OnExit()
		{
			Owner().mSpeedScale = mLastSpeedScale;
		}
		<snip>
	};
		
	struct Move_Crawl : BaseState
	{
		float mLastSpeedScale;

		virtual void OnEnter()
		{
			mLastSpeedScale = Owner().mSpeedScale;
			Owner().mSpeedScale = 0.5f; // Half speed when crawling
		}

		virtual void OnExit()
		{
			Owner().mSpeedScale = mLastSpeedScale;
		}
		<snip>
	};
	
	struct Swim : BaseState
	{
		float mLastSpeedScale;

		virtual void OnEnter()
		{
			mLastSpeedScale = Owner().mSpeedScale;
			Owner().mSpeedScale = 0.3f; // ~1/3 speed when swimming
		}

		virtual void OnExit()
		{
			Owner().mSpeedScale = mLastSpeedScale;
		}
		
		<snip>
	};

In all three states, Move_Walk, Move_Crawl, and Swim, the following steps occur:

  1. In OnEnter: save value of Owner().mSpeedScale into state data member mLastSpeedScale.
  2. In OnEnter: set the value of Owner().mSpeedScale to a value that makes sense for that specific state.
  3. In OnExit: as we exit the state, restore the value of Owner().mSpeedScale to what it was before the state was entered.

Now let's look at the output from this program:

HSM_1_TestHsm: Init    : struct CharacterStates::Alive
HSM_1_TestHsm:  Entry   : struct CharacterStates::OnGround
HSM_1_TestHsm:   Entry   : struct CharacterStates::Stand
Current speed: 0.000000
HSM_1_TestHsm:   Sibling : struct CharacterStates::Move
HSM_1_TestHsm:    Entry   : struct CharacterStates::Move_Walk
Current speed: 100.000000
HSM_1_TestHsm:    Sibling : struct CharacterStates::Move_Crawl
Current speed: 50.000000
HSM_1_TestHsm:  Sibling : struct CharacterStates::Swim
Current speed: 30.000002
HSM_1_TestHsm:  Sibling : struct CharacterStates::OnGround
HSM_1_TestHsm:   Entry   : struct CharacterStates::Stand
Current speed: 0.000000

From the output, we can see that the final speed is scaled appropriately depending on what state is on the state stack.

With StateValue

This method of saving/setting/restoring a shared variable in a state works; however, it is such a common paradigm that HSM offers a feature named StateValue to make easier. Let's take a look at the same example, but this time using StateValue:

// state_value_with.cpp

#include "hsm/statemachine.h"

using namespace hsm;

class PhysicsComponent
{
public:
	void SetSpeed(float speed) {} // Stub
	void Move() {} // Stub
};

class Character
{
public:
	Character();
	void Update();

	// Public to simplify sample
	bool mInWater;
	bool mMove;
	bool mCrawl;

private:
	friend struct CharacterStates;
	StateMachine mStateMachine;

	PhysicsComponent mPhysicsComponent;
	hsm::StateValue<float> mSpeedScale; // [0,1]
};

struct CharacterStates
{
	struct BaseState : StateWithOwner<Character>
	{
	};

	struct Alive : BaseState
	{
		virtual Transition GetTransition()
		{
			return InnerEntryTransition<OnGround>();
		}
	};

	struct OnGround : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mInWater)
				return SiblingTransition<Swim>();

			return InnerEntryTransition<Stand>();
		}
	};

	struct Stand : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mMove)
				return SiblingTransition<Move>();

			return NoTransition();
		}
	};

	struct Move : BaseState
	{
		virtual Transition GetTransition()
		{
			if (!Owner().mMove)
				return SiblingTransition<Stand>();

			return InnerEntryTransition<Move_Walk>();
		}
	};

	struct Move_Walk : BaseState
	{
		virtual void OnEnter()
		{
			SetStateValue(Owner().mSpeedScale) = 1.0f; // Full speed when moving normally
		}

		virtual Transition GetTransition()
		{
			if (Owner().mCrawl)
				return SiblingTransition<Move_Crawl>();
			
			return NoTransition();
		}
	};

	struct Move_Crawl : BaseState
	{
		virtual void OnEnter()
		{
			SetStateValue(Owner().mSpeedScale) = 0.5f; // Half speed when crawling
		}

		virtual Transition GetTransition()
		{
			if (!Owner().mCrawl)
				return SiblingTransition<Move_Walk>();

			return NoTransition();
		}
	};

	struct Swim : BaseState
	{
		virtual void OnEnter()
		{
			SetStateValue(Owner().mSpeedScale) = 0.3f; // ~1/3 speed when swimming
		}

		virtual Transition GetTransition()
		{
			if (!Owner().mInWater)
				return SiblingTransition<OnGround>();

			return NoTransition();
		}
	};
};

Character::Character()
	: mInWater(false)
	, mMove(false)
	, mCrawl(false)
	, mSpeedScale(0.0f) // By default we don't move
{
	mStateMachine.Initialize<CharacterStates::Alive>(this);
	mStateMachine.SetDebugInfo("TestHsm", TraceLevel::Basic);
}

void Character::Update()
{
	// Update state machine
	mStateMachine.ProcessStateTransitions();
	mStateMachine.UpdateStates();

	// Move character
	const float MAX_SPEED = 100.0f;
	float currSpeed = mSpeedScale * MAX_SPEED;
	mPhysicsComponent.SetSpeed(currSpeed);
	mPhysicsComponent.Move();

	printf("Current speed: %f\n", currSpeed);
}

int main()
{
	Character character;
	character.Update();

	character.mMove = true;
	character.Update();

	character.mCrawl = true;
	character.Update();

	character.mInWater = true;
	character.Update();

	character.mInWater = false;
	character.mMove = false;
	character.mCrawl = false;
	character.Update();
}

The first difference to notice is that the former declaration 'float mSpeedScale;' has now become:

	hsm::StateValue<float> mSpeedScale; // [0,1]

Consequently, the three states that set mSpeedScale have also changed and have become much simpler:

	struct Move_Walk : BaseState
	{
		virtual void OnEnter()
		{
			SetStateValue(Owner().mSpeedScale) = 1.0f; // Full speed when moving normally
		}
		<snip>
	};

	struct Move_Crawl : BaseState
	{
		virtual void OnEnter()
		{
			SetStateValue(Owner().mSpeedScale) = 0.5f; // Half speed when crawling
		}
		<snip>
	};

	struct Swim : BaseState
	{
		virtual void OnEnter()
		{
			SetStateValue(Owner().mSpeedScale) = 0.3f; // ~1/3 speed when swimming
		}
		<snip>
	};

Notice how we no longer have a local data member in each state to save the original value, nor an OnExit to restore it. All we need to do is call SetStateValue(myStateValue) = newValue, and we're done.

The output from the program is the same as before:

HSM_1_TestHsm: Init    : struct CharacterStates::Alive
HSM_1_TestHsm:  Entry   : struct CharacterStates::OnGround
HSM_1_TestHsm:   Entry   : struct CharacterStates::Stand
Current speed: 0.000000
HSM_1_TestHsm:   Sibling : struct CharacterStates::Move
HSM_1_TestHsm:    Entry   : struct CharacterStates::Move_Walk
Current speed: 100.000000
HSM_1_TestHsm:    Sibling : struct CharacterStates::Move_Crawl
Current speed: 50.000000
HSM_1_TestHsm:  Sibling : struct CharacterStates::Swim
Current speed: 30.000002
HSM_1_TestHsm:  Sibling : struct CharacterStates::OnGround
HSM_1_TestHsm:   Entry   : struct CharacterStates::Stand
Current speed: 0.000000

How does it work?

The StateValue<T> template class is a light wrapper around any type T (float in our example) that provides read-only access to the value that's wrapped. The only way to modify the wrapped value is by invoking State::SetStateValue from within a state, which returns a modifiable reference to the wrapped value.

When you call SetStateValue in a state member function, the state saves a pointer to the wrapped variable as well as its original value when SetStateValue was called. When the state is destroyed, and after OnExit is called, the wrapped variable's value is restored to the value that was saved on the state.

Initializing StateValues

One important thing to note about StateValue is that you should initialize it with a default value via the StateValue constructor. In our example, as hsm::StateValue<float> mSpeedScale is a data member of the owner Character class, we initialize it in the constructor's initialization list:

Character::Character()
	: mInWater(false)
	, mMove(false)
	, mCrawl(false)
	, mSpeedScale(0.0f) // By default we don't move

Sometimes you will want to initialize a StateValue after the constructor is called; for instance, in a Reset function on the owner class. For this, you can use StateValue::SetInitialValue. Of course, this function essentially allows you to bypass the read-only access to the wrapped value, but you should never use it once the state machine has been updated, as this could invalidate values set by states.

Reading StateValues

Another thing to note about StateValue is that it provides an implicit cast to const T&, which means you can treat the variable as its wrapped type when reading it, as is done in the example:

	const float MAX_SPEED = 100.0f;
	float currSpeed = mSpeedScale * MAX_SPEED;

Here, the compiler implicitly casts mSpeedScale to const float& to multiply it with MAX_SPEED.

In certain cases, the compiler will not be able to implicitly cast to const T&, in which case you can either explicitly cast yourself (e.g. static_cast<float>(mSpeedScale)), or you can simply invoke the StateValue::Value function instead (e.g. mSpeedScale.Value()).

Modifying StateValues

It may seem odd that State::SetStateValue returns a modifiable reference to the variable it wraps. Why not pass in the new value as a second parameter? The reason is that a StateValue can be used to wrap not only fundamental types like bool, float, and int, but also aggregate types like structs or classes. In the case of aggregate types, it is useful to be able to retrieve a reference to the type in order to modify a single field, or invoke a single function.

For example, we could define a StateValue that wraps a struct as follows:

struct MoveParams
{
	MoveParams() : mSpeed(0.0f), mGravity(-9.8f) {}
	float mSpeed;
	float mGravity;
};

StateValue<MoveParams> mMoveParams;

In our states, we'd be able to modify any field of our struct easily:

SetStateValue(Owner().mMoveParams).mGravity = 0.0f; // In this state, we're in space!

When the state exits, the fields that were modified on the struct would be restored. There is no specical handling here - the entire struct is saved on the state when SetStateValue is first called, and restored entirely when the state exits.

When to modify StateValues

In the example above, we modify the StateValue mSpeedScale in the OnEnter function of our three states; however, nothing stops you from modifying StateValues from other functions in a state. For instance, it can be useful to change a value dynamically in a state's Update function. When a state modifies a StateValue, it will save the original value only the first time it modifies it. Subsequent writes to the StateValue will simply modify its value, and when the state exits, it will restore the original value, as expected.

For example, in the Swim state's OnEnter, we set mSpeedScale to 0.3; however, we could instead set mSpeedScale to a range of values from 0 to 0.3 based on how long the user presses movement input in Swim's Update function. When transitioning away from the Swim state, mSpeedScale would return to whatever value it had before Swim was entered.

State Arguments

Another useful feature of HSM is the ability to pass arguments to states when transitioning to them. As with functions, the ability to pass arguments to a state goes a long way to making that state reusable.

Let's take a look at an example of a character controller that makes use of state arguments:

// state_args.cpp

#include "hsm/statemachine.h"

using namespace hsm;

class AnimComponent
{
public:
	AnimComponent() : mLoop(false) {}
	void PlayAnim(const char* name, bool loop, float blendTime, float rate)
	{
		printf(">>> PlayAnim: %s, looping: %s\n", name, loop ? "true" : "false");
		mLoop = loop;
	}

	bool IsFinished() const { return !mLoop; }

private:
	bool mLoop;
};

class Character
{
public:
	Character();
	void Update();

	// Public to simplify sample
	bool mMove;
	bool mJump;

private:
	friend struct CharacterStates;
	StateMachine mStateMachine;

	AnimComponent mAnimComponent;
};

struct CharacterStates
{
	struct BaseState : StateWithOwner<Character>
	{
	};

	struct PlayAnim : BaseState
	{
		struct Args : StateArgs
		{
			Args(const char* animName, bool loop = true, float blendTime = 0.2f, float rate = 1.0f)
				: animName(animName), loop(loop), blendTime(blendTime), rate(rate) {}

			const char* animName;
			bool loop;
			float blendTime;
			float rate;
		};

		virtual void OnEnter(const Args& args)
		{
			Owner().mAnimComponent.PlayAnim(args.animName, args.loop, args.blendTime, args.rate);
		}

		virtual Transition GetTransition()
		{
			if (Owner().mAnimComponent.IsFinished())
				return SiblingTransition<PlayAnim_Done>();
			
			return NoTransition();
		}
	};

	struct PlayAnim_Done : BaseState
	{
	};


	struct Alive : BaseState
	{
		virtual Transition GetTransition()
		{
			return InnerEntryTransition<Stand>();
		}
	};

	struct Stand : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mMove)
				return SiblingTransition<Move>();

			return InnerEntryTransition<PlayAnim>(PlayAnim::Args("Anim_Stand"));
		}
	};

	struct Move : BaseState
	{
		virtual Transition GetTransition()
		{
			if (!Owner().mMove)
				return SiblingTransition<Stand>();

			if (Owner().mJump)
			{
				Owner().mJump = false; // We've processed jump input, clear to avoid infinite transitions
				return SiblingTransition<Jump>();
			}

			return InnerEntryTransition<PlayAnim>(PlayAnim::Args("Anim_Move"));
		}
	};

	struct Jump : BaseState
	{
		virtual Transition GetTransition()
		{
			if (IsInInnerState<PlayAnim_Done>())
				return SiblingTransition<Move>();

			return InnerEntryTransition<PlayAnim>(PlayAnim::Args("Anim_Jump", false));
		}
	};
};

Character::Character()
	: mMove(false)
	, mJump(false)
{
	mStateMachine.Initialize<CharacterStates::Alive>(this);
	mStateMachine.SetDebugInfo("TestHsm", TraceLevel::Basic);
}

void Character::Update()
{
	// Update state machine
	mStateMachine.ProcessStateTransitions();
	mStateMachine.UpdateStates();
}

int main()
{
	Character character;

	character.Update();

	character.mMove = true;
	character.Update();

	character.mJump = true;
	character.Update();
}

Before we look at the code, let's take a look at the plotHsm output:

state_args

From the plot, we can infer a few things:

  • The character can Stand, Move, or Jump
  • The only way to jump is while moving
  • The three states, Stand, Move, and Jump, all push the state PlayAnim

That last point is of particular interest: PlayAnim is a state that is pushed from multiple states. This is an example of a reusable state. Although not required, typically a state is made reusable by being able to pass arguments to it. Let's take a look at PlayAnim and it's sibling PlayAnim_Done:

	struct PlayAnim : BaseState
	{
		struct Args : StateArgs
		{
			Args(const char* animName, bool loop = true, float blendTime = 0.2f, float rate = 1.0f)
				: animName(animName), loop(loop), blendTime(blendTime), rate(rate) {}

			const char* animName;
			bool loop;
			float blendTime;
			float rate;
		};

		virtual void OnEnter(const Args& args)
		{
			Owner().mAnimComponent.PlayAnim(args.animName, args.loop, args.blendTime, args.rate);
		}

		virtual Transition GetTransition()
		{
			if (Owner().mAnimComponent.IsFinished())
				return SiblingTransition<PlayAnim_Done>();
			
			return NoTransition();
		}
	};

	struct PlayAnim_Done : BaseState
	{
	};

The first thing to notice is the nested struct named Args that derives from StateArgs. This is the mechanism by which you add arguments to a state: you must declare a struct named Args, and it must derive from StateArgs (note: StateArgs is a type defined in the hsm namespace). In this struct, we can declare whatever arguments we want as variables. In this case, we add 4 typical animation-related parameters, and make the first one, animName, a required parameter of the constructor.

The next thing to notice is how the OnEnter function now accepts the Args struct as a const reference parameter. When transitioning to state PlayAnim, it is this OnEnter that will be invoked. In this function, the parameter can be read, processed, or even copied into a data member of the state of type Args. In this case, state PlayAnim simply forwards the args, one by one, to Owner().mAnimComponent.PlayAnim.

Before we look at how to pass arguments to states, note that PlayAnim will sibling to the PlayAnim_Done state when the AnimComponent returns that the animation has finished playing. This is part of the PlayAnim state's "API", and we will see how it's used in the state machine below.

Now for how to pass arguments to the PlayAnim state. In the code, PlayAnim is a state that is pushed onto the stack as an InnerEntryTransition from three states: Stand, Move, and Jump. Let's look at how Stand does it:

	struct Stand : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mMove)
				return SiblingTransition<Move>();

			return InnerEntryTransition<PlayAnim>(PlayAnim::Args("Anim_Stand"));
		}
	};

As we can see, to pass the expected arguments to the PlayAnim state, you must simply pass an instance of the nested Args struct as a parameter to the transition function. Notice that we make use of the constructor to make it easier to construct a temporary instance inline, so this looks very much like a regular function call.

NOTE: State arguments can be passed via any of the three transition functions: SiblingTransition, InnerTransition, and InnerEntryTransition. The only requirement is that the Args struct be copy-constructible, as a copy is made and kept until OnEnter is invoked.

The Move state, just like Stand, also pushes PlayAnim to play a looping animation. The Jump state is a little different:

	struct Jump : BaseState
	{
		virtual Transition GetTransition()
		{
			if (IsInInnerState<PlayAnim_Done>())
				return SiblingTransition<Move>();

			return InnerEntryTransition<PlayAnim>(PlayAnim::Args("Anim_Jump", false));
		}
	};

In this case, Jump pushes PlayAnim to play the jump anim in a non-looping fashion. The API we've defined for PlayAnim is that it will sibling to PlayAnim_Done when the animation that it plays is finished, which would normally only happen for non-looping animations. As such, the Jump state uses the state stack query function IsInInnerState<PlayAnim_Done>() to know when the jump animation has finished playing so that it can sibling back to the Move state. This is a common pattern when designing state machines in HSM, and is considered good practice.

Finally, let's look at our main function and the output of our program:

int main()
{
	Character character;

	character.Update();

	character.mMove = true;
	character.Update();

	character.mJump = true;
	character.Update();
}

Output:

HSM_1_TestHsm: Init    : struct CharacterStates::Alive
HSM_1_TestHsm:  Entry   : struct CharacterStates::Stand
HSM_1_TestHsm:   Entry   : struct CharacterStates::PlayAnim
>>> PlayAnim: Anim_Stand, looping: true
HSM_1_TestHsm:  Sibling : struct CharacterStates::Move
HSM_1_TestHsm:   Entry   : struct CharacterStates::PlayAnim
>>> PlayAnim: Anim_Move, looping: true
HSM_1_TestHsm:  Sibling : struct CharacterStates::Jump
HSM_1_TestHsm:   Entry   : struct CharacterStates::PlayAnim
>>> PlayAnim: Anim_Jump, looping: false
HSM_1_TestHsm:   Sibling : struct CharacterStates::PlayAnim_Done
HSM_1_TestHsm:  Sibling : struct CharacterStates::Move
HSM_1_TestHsm:   Entry   : struct CharacterStates::PlayAnim
>>> PlayAnim: Anim_Move, looping: true

We can see that PlayAnim is pushed from Stand, Move, and Jump; and in Jump's case, we see that PlayAnim siblings to PlayAnim_Done, which results in Jump sibling back to Move.

Storing Data on Cluster Root State

We've already seen many examples of a state storing state-specific data as data members of its class. Scoping data as narrowly as possible like this makes sense; but what if you have a group of states that need to access a shared set of data? In this section, we'll take a look at a good technique to do exactly that.

The gist of the technique is simple: given a group - or cluster - of states, store the shared data on the cluster's root state. Then, inner states access the data by retrieving a pointer to the root state, and access the shared data through it. Let's take a look at an example of this technique:

// cluster_root_state_data_1.cpp

#include "hsm/statemachine.h"

using namespace hsm;

class Character
{
public:
	Character();
	void Update();

	bool mJump;

private:
	friend struct CharacterStates;
	StateMachine mStateMachine;
};

struct CharacterStates
{
	struct BaseState : StateWithOwner<Character>
	{
	};

	struct Alive : BaseState
	{
		virtual Transition GetTransition()
		{
			return InnerEntryTransition<Stand>();
		}
	};

	struct Stand : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mJump)
			{
				Owner().mJump = false;
				return SiblingTransition<Jump>();
			}
			
			return NoTransition();
		}
	};

	struct Jump : BaseState
	{
		int mJumpValue1;
		float mJumpValue2;
		bool mJumpValue3;

		Jump() : mJumpValue1(0), mJumpValue2(0.0f), mJumpValue3(false) { }

		virtual Transition GetTransition()
		{
			if (IsInInnerState<Jump_Done>())
				return SiblingTransition<Stand>();

			return InnerEntryTransition<Jump_Up>();
		}
	};

	struct Jump_Up : BaseState
	{
		virtual void OnEnter()
		{
			GetOuterState<Jump>()->mJumpValue1 = 1;
			GetOuterState<Jump>()->mJumpValue2 = 2.0f;
			GetOuterState<Jump>()->mJumpValue3 = true;
		}

		virtual Transition GetTransition()
		{
			return SiblingTransition<Jump_Down>();
		}
	};

	struct Jump_Down : BaseState
	{
		virtual void OnEnter()
		{
			GetOuterState<Jump>()->mJumpValue1 = 2;
			GetOuterState<Jump>()->mJumpValue2 = 4.0f;
			GetOuterState<Jump>()->mJumpValue3 = false;
		}

		virtual Transition GetTransition()
		{
			return SiblingTransition<Jump_Done>();
		}
	};

	struct Jump_Done : BaseState
	{
	};
};

Character::Character()
	: mJump(false)
{
	mStateMachine.Initialize<CharacterStates::Alive>(this);
	mStateMachine.SetDebugInfo("TestHsm", TraceLevel::Basic);
}

void Character::Update()
{
	// Update state machine
	mStateMachine.ProcessStateTransitions();
	mStateMachine.UpdateStates();
}

int main()
{
	Character character;
	character.Update();
	character.mJump = true;
	character.Update();
}

Here's the plotHsm for this example:

cluster_root_state_data_1

In this example, the Jump state cluster has some data it wants to share across two of its inner states: Jump_Up and Jump_Down. The shared data is declared simply as data members of the Jump state itself:

	struct Jump : BaseState
	{
		int mJumpValue1;
		float mJumpValue2;
		bool mJumpValue3;

		Jump() : mJumpValue1(0), mJumpValue2(0.0f), mJumpValue3(false) { }

		virtual Transition GetTransition()
		{
			if (IsInInnerState<Jump_Done>())
				return SiblingTransition<Stand>();

			return InnerEntryTransition<Jump_Up>();
		}
	};

The Jump state is the root state of this cluster, which means that when we want the character to jump, we always transition to the Jump state first. Then inner states are pushed, and transition between each other. Because of this, inner states can always access the outer Jump state using the GetOuterState state stack query function. Both Jump_Up and Jump_Down do exactly that to grab a pointer to Jump and access its members:

	struct Jump_Up : BaseState
	{
		virtual void OnEnter()
		{
			GetOuterState<Jump>()->mJumpValue1 = 1;
			GetOuterState<Jump>()->mJumpValue2 = 2.0f;
			GetOuterState<Jump>()->mJumpValue3 = true;
		}

		virtual Transition GetTransition()
		{
			return SiblingTransition<Jump_Down>();
		}
	};

	struct Jump_Down : BaseState
	{
		virtual void OnEnter()
		{
			GetOuterState<Jump>()->mJumpValue1 = 2;
			GetOuterState<Jump>()->mJumpValue2 = 4.0f;
			GetOuterState<Jump>()->mJumpValue3 = false;
		}

		virtual Transition GetTransition()
		{
			return SiblingTransition<Jump_Done>();
		}
	};

This works as expected; however, invoking GetOuterState to access each member carries some overhead, as it crawls up the state stack to check if the input state is on the stack. To avoid this overhead, we can cache a pointer to the root state, as shown in this next example:

// cluster_root_state_data_2.cpp

<snip>

	struct JumpBaseState : BaseState
	{
		JumpBaseState() : mJumpState(NULL) {}

		virtual void OnEnter()
		{
			mJumpState = GetOuterState<Jump>();
			assert(mJumpState);
		}

		Jump& JumpState()
		{
			return *mJumpState;
		}

	private:
		Jump* mJumpState;
	};

	struct Jump_Up : JumpBaseState
	{
		virtual void OnEnter()
		{
			JumpBaseState::OnEnter();
			JumpState().mJumpValue1 = 1;
			JumpState().mJumpValue2 = 2.0f;
			JumpState().mJumpValue3 = true;
		}

		virtual Transition GetTransition()
		{
			return SiblingTransition<Jump_Down>();
		}
	};

	struct Jump_Down : JumpBaseState
	{
		virtual void OnEnter()
		{
			JumpBaseState::OnEnter();
			JumpState().mJumpValue1 = 2;
			JumpState().mJumpValue2 = 4.0f;
			JumpState().mJumpValue3 = false;
		}

		virtual Transition GetTransition()
		{
			return SiblingTransition<Jump_Done>();
		}
	};

In this second version, we introduced a new base class named JumpBaseState, that derives from the usual BaseState. In its OnEnter function, it caches the result of GetOuterState<Jump> in a data member, and provides a getter named JumpState that returns it. The inner states Jump_Up and Jump_Down now derive from JumpBaseState, making sure to first invoke JumpBaseState::OnEnter in their own OnEnter functions before invoking JumpState to access the cluster root state.

We can generalize this technique of accessing the cluster root state from inner states by transforming JumpBaseState into a template class, as show in this this third version of our example:

// cluster_root_state_data_3.cpp

<snip>

	template <typename RootStateType, typename BaseStateType>
	struct ClusterBaseState : BaseStateType
	{
		ClusterBaseState() : mRootState(NULL) {}

		virtual void OnEnter()
		{
			mRootState = this->template GetOuterState<RootStateType>();
			assert(mRootState);
		}

		RootStateType& ClusterRootState()
		{
			return *mRootState;
		}

	private:
		RootStateType* mRootState;
	};

<snip>

	typedef ClusterBaseState<Jump, BaseState> JumpBaseState;

	struct Jump_Up : JumpBaseState
	{
		virtual void OnEnter()
		{
			JumpBaseState::OnEnter();
			ClusterRootState().mJumpValue1 = 1;
			ClusterRootState().mJumpValue2 = 2.0f;
			ClusterRootState().mJumpValue3 = true;
		}

		virtual Transition GetTransition()
		{
			return SiblingTransition<Jump_Down>();
		}
	};

	struct Jump_Down : JumpBaseState
	{
		virtual void OnEnter()
		{
			JumpBaseState::OnEnter();
			ClusterRootState().mJumpValue1 = 2;
			ClusterRootState().mJumpValue2 = 4.0f;
			ClusterRootState().mJumpValue3 = false;
		}

		virtual Transition GetTransition()
		{
			return SiblingTransition<Jump_Done>();
		}
	};

JumpBaseState is now replaced by template class ClusterBaseState that can be used to declare a base type for any cluster in your state machine. In this case, we use a typedef to declare JumpBaseState:

typedef ClusterBaseState<Jump, BaseState> JumpBaseState;

The Jump_Up and Jump_Down states now invoke ClusterRootState() to access the Jump the state on the stack.

NOTE: The reason why the call to GetOuterState in ClusterBaseState is prefixed by this->template is because of C++ argument dependent lookup (ADL) rules. We must let the compiler know that we mean to call the member function named GetOuterState, and one way is to make it explicit by calling it via 'this'. Alternatively, we could also bring in the name by adding a using BaseStateType::GetOuterState; declaration to the class.

Restarting States

One useful feature of HSM is the ability to restart a state. This is accomplished by having a state return a sibling transition to itself. When this happens, the state class is exited, destroyed, and the same state is then created and entered.

Let's take a look at an example of how restarting states can be used to implement attack combos in a character controller:

// restarting_states.cpp

#include "hsm/statemachine.h"

using namespace hsm;

class AnimComponent
{
public:
	AnimComponent() {}
	void PlayAnim(const char* name)
	{
		printf(">>> PlayAnim: %s\n", name);
	}

	bool IsFinished() const { return false; } // Stub

	// Return true if input event was processed in animation
	bool PollEvent(const char*) { return true; } // Stub
};

class Character
{
public:
	Character();
	void Update();

	// Public to simplify sample
	bool mMove;
	bool mAttack;

private:
	friend struct CharacterStates;
	StateMachine mStateMachine;

	AnimComponent mAnimComponent;
};

struct CharacterStates
{
	struct BaseState : StateWithOwner<Character>
	{
	};

	struct Alive : BaseState
	{
		virtual Transition GetTransition()
		{
			return InnerEntryTransition<Locomotion>();
		}
	};

	struct Locomotion : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mAttack)
			{
				// Start attack sequence with combo index 0
				return SiblingTransition<Attack>( Attack::Args(0) );
			}

			return InnerEntryTransition<Stand>();
		}
	};

	struct Stand : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mMove)
				return SiblingTransition<Move>();

			return NoTransition();
		}
	};

	struct Move : BaseState
	{
		virtual Transition GetTransition()
		{
			if (!Owner().mMove)
				return SiblingTransition<Stand>();

			return NoTransition();
		}
	};

	struct Attack : BaseState
	{
		struct Args : StateArgs
		{
			Args(int comboIndex) : mComboIndex(comboIndex) {}
			int mComboIndex;
		};

		virtual void OnEnter(const Args& args)
		{
			Owner().mAttack = false;

			mComboIndex = args.mComboIndex;

			static const char* AttackAnim[] =
			{
				"Attack_1",
				"Attack_2",
				"Attack_3"
			};
			assert(mComboIndex < sizeof(AttackAnim) / sizeof(AttackAnim[0]));

			Owner().mAnimComponent.PlayAnim(AttackAnim[mComboIndex]);
		}

		virtual Transition GetTransition()
		{
			// Check if player can chain next attack
			if (Owner().mAttack
				&& mComboIndex < 2
				&& Owner().mAnimComponent.PollEvent("CanChainCombo"))
			{
				// Restart state with next combo index
				return SiblingTransition<Attack>( Attack::Args(mComboIndex + 1) );
			}

			if (Owner().mAnimComponent.IsFinished())
				return SiblingTransition<Locomotion>();

			return NoTransition();
		}

		virtual void Update()
		{
			printf(">>> Attacking: %d\n", mComboIndex);
		}

		int mComboIndex;
	};
};

Character::Character()
	: mMove(false)
	, mAttack(false)
{
	mStateMachine.Initialize<CharacterStates::Alive>(this);
	mStateMachine.SetDebugInfo("TestHsm", TraceLevel::Basic);
}

void Character::Update()
{
	// Update state machine
	mStateMachine.ProcessStateTransitions();
	mStateMachine.UpdateStates();
}

int main()
{
	Character character;

	character.Update();

	character.mMove = true;
	character.Update();

	// First attack
	character.mAttack = true;
	character.Update();
	character.Update();
	character.Update();

	// Second attack combo
	character.mAttack = true;
	character.Update();
	character.Update();
	character.Update();

	// Third attack combo
	character.mAttack = true;
	character.Update();
	character.Update();
	character.Update();

	// Another attack has no effect (reached our max combo of 3)
	character.mAttack = true;
	character.Update();
}

Here's the plotHsm for this code:

restarting_states

From the plot, we can see that our character toggles between Locomotion and Attack states. While in Locomotion, our character can Stand or Move. The state that interests us is the Attack state: although it may not be perfectly clear in the image, along with the sibling transition arrows between Attack and Locomotion, there is an arrow that loops from Attack back to itself. This last arrow signifies a sibling transition to itself.

Let's focus on the Attack state:

	struct Attack : BaseState
	{
		struct Args : StateArgs
		{
			Args(int comboIndex) : mComboIndex(comboIndex) {}
			int mComboIndex;
		};

		virtual void OnEnter(const Args& args)
		{
			Owner().mAttack = false;

			mComboIndex = args.mComboIndex;

			static const char* AttackAnim[] =
			{
				"Attack_1",
				"Attack_2",
				"Attack_3"
			};
			assert(mComboIndex < sizeof(AttackAnim) / sizeof(AttackAnim[0]));

			Owner().mAnimComponent.PlayAnim(AttackAnim[mComboIndex]);
		}

		virtual Transition GetTransition()
		{
			// Check if player can chain next attack
			if (Owner().mAttack
				&& mComboIndex < 2
				&& Owner().mAnimComponent.PollEvent("CanChainCombo"))
			{
				// Restart state with next combo index
				return SiblingTransition<Attack>( Attack::Args(mComboIndex + 1) );
			}

			if (Owner().mAnimComponent.IsFinished())
				return SiblingTransition<Locomotion>();

			return NoTransition();
		}

		int mComboIndex;
	};

The idea behind this code is that we want to be able to chain up to three attacks. If the player presses the attack input, the character plays a first attack animation. The attack animations contain an event "CanChainCombo" at a specific time that signify that after this event, another attack can be chained. Thus, during the first attack, after the "CanChainCombo" event, and before the animation ends, if the player presses the attack input again, a second attack will be chained. The same logic is repeated for chaining a third attack.

To achieve the chaining of attacks, the Attack state accepts the "combo index" as an argument. When we enter Attack the first time, from the Locomotion state, index 0 is passed in. Once in the Attack state, if another attack is to be chained, GetTransition returns a sibling transition to itself, but this time passing in the current combo index + 1.

In the main function, we simulate the three attacks:

int main()
{
	Character character;

	character.Update();

	character.mMove = true;
	character.Update();

	// First attack
	character.mAttack = true;
	character.Update();
	character.Update();
	character.Update();

	// Second attack combo
	character.mAttack = true;
	character.Update();
	character.Update();
	character.Update();

	// Third attack combo
	character.mAttack = true;
	character.Update();
	character.Update();
	character.Update();

	// Another attack has no effect (reached our max combo of 3)
	character.mAttack = true;
	character.Update();
}

The output from the program clearly shows how the Attack state siblings to itself for the second and third attack:

HSM_1_TestHsm: Init    : struct CharacterStates::Alive
HSM_1_TestHsm:  Entry   : struct CharacterStates::Locomotion
HSM_1_TestHsm:   Entry   : struct CharacterStates::Stand
HSM_1_TestHsm:   Sibling : struct CharacterStates::Move
HSM_1_TestHsm:  Sibling : struct CharacterStates::Attack
>>> PlayAnim: Attack_1
>>> Attacking: 0
>>> Attacking: 0
>>> Attacking: 0
HSM_1_TestHsm:  Sibling : struct CharacterStates::Attack
>>> PlayAnim: Attack_2
>>> Attacking: 1
>>> Attacking: 1
>>> Attacking: 1
HSM_1_TestHsm:  Sibling : struct CharacterStates::Attack
>>> PlayAnim: Attack_3
>>> Attacking: 2
>>> Attacking: 2
>>> Attacking: 2
>>> Attacking: 2

In general, restarting states is a useful paradigm when you find yourself writing a sequence of very similar states that all do the same thing, but with slightly different parameters, as in the above example.

Selector States

In this section, we will learn a useful technique that can be used to reduce the number of explicit transitions in a set of sibling states by centralizing them into a selector state.

First let's take a look at an example that does not use selector states:

// selector_states_without.cpp

#include "hsm/statemachine.h"

using namespace hsm;

class Character
{
public:
	Character();
	void Update();

	// Public to simplify sample
	bool mMove;
	bool mJump;

private:
	friend struct CharacterStates;
	StateMachine mStateMachine;
};

struct CharacterStates
{
	struct BaseState : StateWithOwner<Character>
	{
	};

	struct Alive : BaseState
	{
		virtual Transition GetTransition()
		{
			return InnerEntryTransition<Locomotion>();
		}
	};

	struct LocomotionBaseState : BaseState
	{
		bool ShouldJump() const
		{
			return Owner().mJump;
		}

		bool ShouldMove() const
		{
			// Jumping has priority over moving
			return !ShouldJump() && Owner().mMove;
		}

		bool ShouldStand() const
		{
			return !ShouldJump() && !ShouldMove();
		}
	};

	struct Locomotion : LocomotionBaseState
	{
		virtual Transition GetTransition()
		{
			if (ShouldJump())
				return InnerEntryTransition<Jump>();

			if (ShouldMove())
				return InnerEntryTransition<Move>();

			assert(ShouldStand());
			return InnerEntryTransition<Stand>();
		}
	};

	struct Stand : LocomotionBaseState
	{
		virtual Transition GetTransition()
		{
			if (ShouldJump())
				return SiblingTransition<Jump>();

			if (ShouldMove())
				return SiblingTransition<Move>();

			return NoTransition();
		}
	};

	struct Move : LocomotionBaseState
	{
		virtual Transition GetTransition()
		{
			if (ShouldJump())
				return SiblingTransition<Jump>();

			if (ShouldStand())
				return SiblingTransition<Stand>();

			return NoTransition();
		}
	};

	struct Jump : LocomotionBaseState
	{
		virtual Transition GetTransition()
		{
			if (ShouldMove())
				return SiblingTransition<Move>();

			if (ShouldStand())
				return SiblingTransition<Stand>();

			return NoTransition();
		}
	};
};

Character::Character()
	: mMove(false)
	, mJump(false)
{
	mStateMachine.Initialize<CharacterStates::Alive>(this);
	mStateMachine.SetDebugInfo("TestHsm", TraceLevel::Basic);
}

void Character::Update()
{
	printf(">>> Character::Update\n");

	// Update state machine
	mStateMachine.ProcessStateTransitions();
	mStateMachine.UpdateStates();
}

int main()
{
	Character character;

	character.Update();

	character.mMove = true;
	character.Update();

	character.mJump = true;
	character.Update();

	character.mJump = false;
	character.Update();

	character.mMove = false;
	character.Update();
}

This code is yet another example of a typical character controller for a character that can stand, move, and jump. Here's the output of the program:

>>> Character::Update
HSM_1_TestHsm: Init    : struct CharacterStates::Alive
HSM_1_TestHsm:  Entry   : struct CharacterStates::Locomotion
HSM_1_TestHsm:   Entry   : struct CharacterStates::Stand
>>> Character::Update
HSM_1_TestHsm:   Sibling : struct CharacterStates::Move
>>> Character::Update
HSM_1_TestHsm:   Sibling : struct CharacterStates::Jump
>>> Character::Update
HSM_1_TestHsm:   Sibling : struct CharacterStates::Move
>>> Character::Update
HSM_1_TestHsm:   Sibling : struct CharacterStates::Stand

And the plotHsm for this state machine:

selector_states_without

The plot of this state machine shows quite a mess of transition arrows between the Locomotion, Jump, Move, and Stand states. First let's take a look at the Locomotion state code:

	struct Locomotion : LocomotionBaseState
	{
		virtual Transition GetTransition()
		{
			if (ShouldJump())
				return InnerEntryTransition<Jump>();

			if (ShouldMove())
				return InnerEntryTransition<Move>();

			assert(ShouldStand());
			return InnerEntryTransition<Stand>();
		}
	};

So far in most of our examples, a state like Locomotion would usually select a single inner entry state, such as Stand. Why would we make it choose among Jump, Move, or Stand as its initial state like this? Well, in this case, let's assume it's possible for the player to activate the jump or move inputs before we enter this state. Always going through Stand first may not be desirable, especially if Stand's OnEnter has a side-effect that may affect the behaviour of the character when it finally ends up in Move or Jump. For instance, perhaps Stand::OnEnter resets some physics momentum variables, which is not what we want if we actually intend to transition to Move. In such cases, it makes sense to select the correct entry state right away.

Ok, so that explains the three arrows from Locomotion to Stand, Move, and Jump. Between these three sibling states, each state may transition to any other two:

	struct Stand : LocomotionBaseState
	{
		virtual Transition GetTransition()
		{
			if (ShouldJump())
				return SiblingTransition<Jump>();

			if (ShouldMove())
				return SiblingTransition<Move>();

			return NoTransition();
		}
	};

	struct Move : LocomotionBaseState
	{
		virtual Transition GetTransition()
		{
			if (ShouldJump())
				return SiblingTransition<Jump>();

			if (ShouldStand())
				return SiblingTransition<Stand>();

			return NoTransition();
		}
	};

	struct Jump : LocomotionBaseState
	{
		virtual Transition GetTransition()
		{
			if (ShouldMove())
				return SiblingTransition<Move>();

			if (ShouldStand())
				return SiblingTransition<Stand>();

			return NoTransition();
		}
	};

With three states, the transition code is not that hard to understand; however, as you add more sibling states, this can become more cumbersome to manage. It's easy to forget a transition, and the transition code itself becomes more noise than signal. To solve this problem, you can make use of a selector state. Let's take a look at the same example, but this time modified to make use of a selector state:

// selector_states_with.cpp

#include "hsm/statemachine.h"

using namespace hsm;

class Character
{
public:
	Character();
	void Update();

	// Public to simplify sample
	bool mMove;
	bool mJump;

private:
	friend struct CharacterStates;
	StateMachine mStateMachine;
};

struct CharacterStates
{
	struct BaseState : StateWithOwner<Character>
	{
	};

	struct Alive : BaseState
	{
		virtual Transition GetTransition()
		{
			return InnerEntryTransition<Locomotion>();
		}
	};

	struct LocomotionBaseState : BaseState
	{
		bool ShouldJump() const
		{
			return Owner().mJump;
		}

		bool ShouldMove() const
		{
			// Jumping has priority over moving
			return !ShouldJump() && Owner().mMove;
		}

		bool ShouldStand() const
		{
			return !ShouldJump() && !ShouldMove();
		}
	};

	struct Locomotion : LocomotionBaseState
	{
		virtual Transition GetTransition()
		{
			return InnerEntryTransition<Selector>();
		}
	};

	struct Selector : LocomotionBaseState
	{
		virtual Transition GetTransition()
		{
			if (ShouldJump())
				return SiblingTransition<Jump>();

			if (ShouldMove())
				return SiblingTransition<Move>();

			assert(ShouldStand());
			return SiblingTransition<Stand>();
		}
	};

	struct Stand : LocomotionBaseState
	{
		virtual Transition GetTransition()
		{
			if (!ShouldStand())
				return SiblingTransition<Selector>();

			return NoTransition();
		}
	};

	struct Move : LocomotionBaseState
	{
		virtual Transition GetTransition()
		{
			if (!ShouldMove())
				return SiblingTransition<Selector>();

			return NoTransition();
		}
	};

	struct Jump : LocomotionBaseState
	{
		virtual Transition GetTransition()
		{
			if (!ShouldJump())
				return SiblingTransition<Selector>();

			return NoTransition();
		}
	};
};

Character::Character()
	: mMove(false)
	, mJump(false)
{
	mStateMachine.Initialize<CharacterStates::Alive>(this);
	mStateMachine.SetDebugInfo("TestHsm", TraceLevel::Basic);
}

void Character::Update()
{
	printf(">>> Character::Update\n");

	// Update state machine
	mStateMachine.ProcessStateTransitions();
	mStateMachine.UpdateStates();
}

int main()
{
	Character character;

	character.Update();

	character.mMove = true;
	character.Update();

	character.mJump = true;
	character.Update();

	character.mJump = false;
	character.Update();

	character.mMove = false;
	character.Update();
}

The main difference in this code is the addition of a new state named Selector that simply returns transitions to Stand, Move, or Jump based on the current inputs:

	struct Selector : LocomotionBaseState
	{
		virtual Transition GetTransition()
		{
			if (ShouldJump())
				return SiblingTransition<Jump>();

			if (ShouldMove())
				return SiblingTransition<Move>();

			assert(ShouldStand());
			return SiblingTransition<Stand>();
		}
	};

First, this selector state is used by Locomotion, simplifying its GetTransition:

	struct Locomotion : LocomotionBaseState
	{
		virtual Transition GetTransition()
		{
			return InnerEntryTransition<Selector>();
		}
	};

The three sibling states, Stand, Move, and Jump also transition to Selector as soon as they know they should no longer be in their state:

	struct Stand : LocomotionBaseState
	{
		virtual Transition GetTransition()
		{
			if (!ShouldStand())
				return SiblingTransition<Selector>();

			return NoTransition();
		}
	};

	struct Move : LocomotionBaseState
	{
		virtual Transition GetTransition()
		{
			if (!ShouldMove())
				return SiblingTransition<Selector>();

			return NoTransition();
		}
	};

	struct Jump : LocomotionBaseState
	{
		virtual Transition GetTransition()
		{
			if (!ShouldJump())
				return SiblingTransition<Selector>();

			return NoTransition();
		}
	};

As you can see, the states are simpler to understand. More importantly, using a selector state like this makes it significantly easier to scale up the state machine: adding a new sibling state involves simply adding a transition in Selector, and modifying the Should* input query functions to manage the priority of the input values.

Now let's take a look at the plotHsm for this code:

selector_states_with

Compared to the plot from the example without a selector state, it should be easier to understand the transitions between the states.

And finally, let's look at the output for this example using selector states:

>>> Character::Update
HSM_1_TestHsm: Init    : struct CharacterStates::Alive
HSM_1_TestHsm:  Entry   : struct CharacterStates::Locomotion
HSM_1_TestHsm:   Entry   : struct CharacterStates::Selector
HSM_1_TestHsm:   Sibling : struct CharacterStates::Stand
>>> Character::Update
HSM_1_TestHsm:   Sibling : struct CharacterStates::Selector
HSM_1_TestHsm:   Sibling : struct CharacterStates::Move
>>> Character::Update
HSM_1_TestHsm:   Sibling : struct CharacterStates::Selector
HSM_1_TestHsm:   Sibling : struct CharacterStates::Jump
>>> Character::Update
HSM_1_TestHsm:   Sibling : struct CharacterStates::Selector
HSM_1_TestHsm:   Sibling : struct CharacterStates::Move
>>> Character::Update
HSM_1_TestHsm:   Sibling : struct CharacterStates::Selector
HSM_1_TestHsm:   Sibling : struct CharacterStates::Stand

As expected, we can see how the Selector state is used to move between the states. It's interesting to note that selector states like this never remain on the state stack once it has settled (i.e. once StateMachine::UpdateStateTransitions has completed). It's sole purpose is to route transitions between states that do remain on a settled state stack.

Done States

As you know by now, there is no way for an inner state to directly force a transition for an outer state. This is by design as it reduces coupling between sets of states at different depths by keeping dependencies in one direction - that is, only outer states know about inner states. Of course, when designing state machines, you will often find yourself in a situation where you would like an inner state to communicate to an outer state that it can now make a sibling transition. In this section, we'll take a look at a common technique to solve this problem.

Let's say we're implementing a character state machine, and we'd like to add the ability for our character to open doors. Opening a door is a sequence of actions: first get into position in front of the door, then play the open door animation. One this sequence is done, we'd like our character to go back to the normal standing position. The following example shows how we could implement this:

// done_states.cpp

#include "hsm/statemachine.h"

using namespace hsm;

class Character
{
public:
	Character();
	void Update();

	// Public to simplify sample
	bool mOpenDoor;

private:
	friend struct CharacterStates;
	StateMachine mStateMachine;
};

struct CharacterStates
{
	struct BaseState : StateWithOwner<Character>
	{
	};

	struct Alive : BaseState
	{
		virtual Transition GetTransition()
		{
			return InnerEntryTransition<Stand>();
		}
	};

	struct Stand : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mOpenDoor)
			{
				Owner().mOpenDoor = false;
				return SiblingTransition<OpenDoor>();
			}
			
			return NoTransition();
		}
	};

	struct OpenDoor : BaseState
	{
		virtual Transition GetTransition()
		{
			if (IsInInnerState<OpenDoor_Done>())
				return SiblingTransition<Stand>();

			return InnerEntryTransition<OpenDoor_GetIntoPosition>();
		}
	};

	struct OpenDoor_GetIntoPosition : BaseState
	{
		bool IsInPosition() const { return true; } // Stub

		virtual Transition GetTransition()
		{
			if (IsInPosition())
				return SiblingTransition<OpenDoor_PlayOpenAnim>();

			return NoTransition();
		}
	};

	struct OpenDoor_PlayOpenAnim : BaseState
	{
		bool IsAnimDone() const { return true; } // Stub

		virtual Transition GetTransition()
		{
			if (IsAnimDone())
				return SiblingTransition<OpenDoor_Done>();

			return NoTransition();
		}
	};

	struct OpenDoor_Done : BaseState
	{
	};
};

Character::Character()
	: mOpenDoor(false)
{
	mStateMachine.Initialize<CharacterStates::Alive>(this);
	mStateMachine.SetDebugInfo("TestHsm", TraceLevel::Basic);
}

void Character::Update()
{
	printf(">>> Character::Update\n");

	// Update state machine
	mStateMachine.ProcessStateTransitions();
	mStateMachine.UpdateStates();
}

int main()
{
	Character character;

	character.Update();

	character.mOpenDoor = true;
	character.Update();
}

Here's a plot of the state machine:

done_states

From the plot, we can clearly see that once Stand siblings to OpenDoor, OpenDoor starts up a sequence of inner states: first it enters OpenDoor_GetIntoPosition, which siblings to OpenDoor_PlayOpenAnim, which finally siblings to OpenDoor_Done. This last transition is the key to making it all work. If we take a look at the code for the OpenDoor outer state, we can see that it uses the IsInInnerState state stack query function to look for when OpenDoor_Done is on the stack, upon which it siblings back to Stand:

	struct OpenDoor : BaseState
	{
		virtual Transition GetTransition()
		{
			if (IsInInnerState<OpenDoor_Done>())
				return SiblingTransition<Stand>();

			return InnerEntryTransition<OpenDoor_GetIntoPosition>();
		}
	};

One very important thing to realize about using done states is that the done state itself, OpenDoor_Done in this case, is what we call a "transient state" because it does not remain on the stack very long. In fact, by the time the StateMachine::ProcessStateTransitions function is finished, it is guaranteed to never be on the state stack. As explained in Chapter 3, GetTransition will be called over and over from outermost to innermost state every time a transition occurs until the state stack has settled. So when OpenDoor_PlayOpenAnim siblings to OpenDoor_Done, outer state OpenDoor's GetTransition will be called, where it will see that OpenDoor_Done is on the stack, and make a sibling transition to Stand, thereby exiting both OpenDoor_Done and OpenDoor.

In effect, a done state acts much like a boolean return value from the set of inner states to an outer state. In fact, it can be much more than a simple boolean; for instance, we could store any data on a done state, perhaps passing them by state arguments, and the outer state could retrieve this data by using the GetInnerState query function. In our example, this would look something like:

	struct OpenDoor : BaseState
	{
		virtual Transition GetTransition()
		{
			if (OpenDoor_Done* doneState = GetInnerState<OpenDoor_Done>())
			{
				// Now we know we're done and we can read values on doneState
				//...
				return SiblingTransition<Stand>();
			}

			return InnerEntryTransition<OpenDoor_GetIntoPosition>();
		}
	};

When the call to GetInnerState returns a valid value, we know that the done state is on the stack, and we've got a pointer to it, so we can read any values stored on it.

Sharing Functions Across States

While authoring states in HSM, you will likely find yourself repeating bits of code. Naturally, you will want to factor these out into functions. In this section, we'll take a look at some techniques for doing that.

The trick to sharing functions across states is to define the function in a base class that the states derive from. So far in most of our examples, we have defined a base class for our states that derives from hsm::StateWithOwner<OwnerType>. For example:

// sharing_functions_across_states.cpp
<snip>

	struct BaseState : StateWithOwner<Character>
	{
	};

Note that this empty class, BaseState, could just have easily been declared using a typedef:

	typedef StateWithOwner<Character> BaseState;

However, we opted to define an empty base class because it's a good place to add shared functions that can be used across states. For example:

	struct BaseState : StateWithOwner<Character>
	{
		void ClearJump() { Owner().mJump = false; }
	};

Here we've added a shared function ClearJump that any state can use to clear a flag in the owner class. Note that because BaseState derives from StateWithOwner, it can access the owner instance via the Owner function, as in any other state.

If you only need to share some functions across a group of states, rather than all states, create a new BaseState-derived class from which your set of states will derive, and implement your shared functions in this new class. For example:

	struct LocomotionBaseState : BaseState
	{
		bool ShouldJump() const
		{
			return Owner().mJump;
		}

		bool ShouldMove() const
		{
			// Jumping has priority over moving
			return !ShouldJump() && Owner().mMove;
		}

		bool ShouldStand() const
		{
			return !ShouldJump() && !ShouldMove();
		}
	};

We've defined a base class LocomotionBaseState that derives from BaseState, and that exposes some utility functions for our locomotion-related states.

Finally, what if you've created a few different utility base states, and you want a state to have access to more than one of them? Your first thought might have the state class multiply inherit from each of the utility base state classes. Unfortunately, inheriting from multiple states that eventually derive from hsm::State is not supported by HSM for various reasons.

NOTE: The main reason for which state classes can not multiply inherit from classes that implement hsm::State is that a state must be convertible to hsm::State*, and multiple inheritance leads to ambiguity. Furthermore, even if state classes were to use virtual inheritance to guarantee a single hsm::State base, a dynamic_cast would be required to perform the cast; however, C++ RTTI is explicitly not required by HSM.

The solution is to chain base states using a template parameter to specify the base state:

	template <typename BaseType = BaseState>
	struct JumpBaseState : BaseType
	{
		using BaseType::Owner;

		void ClearJump() { Owner().mJump = false; }
	};

	template <typename BaseType = BaseState>
	struct MoveBaseState : BaseType
	{
		using BaseType::Owner;

		void ClearMove() { Owner().mMove = false; }
	};

	struct JumpAndMove : JumpBaseState< MoveBaseState<> >
	{
		virtual void OnEnter()
		{
			ClearJump();
			ClearMove();
		}
	};

As you can see, JumpAndMove is able to bring in the utility functions from both JumpBaseState and MoveBaseState by chaining them together. Effectively, in this case, the inheritance order is: JumpAndMove : JumpBaseState : MoveBaseState : BaseState : hsm::State.

This technique of making a template state is useful in many cases; for instance, it can also be used to share states across different state machines that each have a different OwnerType. In these cases, the BaseType template parameter would not specify a default, as in our example above; it would be explicitly passed in when used.

A final note on this technique: you may have noticed the using BaseType::Owner; declaration in the template base states. This is needed by certain compilers that cannot determine the scope of the name 'Owner'.

Deferred Transitions

So far, we've seen how a state returns what transition to make via its GetState function. You may have noticed that the GetState function is expected to return a Transition object, and we use functions like SiblingTransition<TargetState>, InnerTransition<TargetState>, and InnerEntryTransition<TargetState> to create these Transition objects.

There is nothing special about the Transition class itself; it's a simple class that stores the type of transition it represents (sibling, inner, inner entry, or no transition), the target state of the transition, and optionally state arguments. As such, you can store a Transition instance as a state data member, and return its value via a state's GetTransition function. Doing so allows you to separate the point at which you decide what transition to make from the point at which it's returned. We call this technique deferring a transition, and we'll take a look at an example of how to use them in this section.

One reason to use deferred transitions is to avoid (or fix) infinite transition loops. Let's take a look at an example:

// deferred_transitions.cpp

#include "hsm/statemachine.h"

using namespace hsm;

class Character
{
public:
	Character();
	void Update();

	// Public to simplify sample
	bool mCrouchInputPressed;

private:
	friend struct CharacterStates;
	StateMachine mStateMachine;
};

struct CharacterStates
{
	struct BaseState : StateWithOwner<Character>
	{
	};

	struct Alive : BaseState
	{
		virtual Transition GetTransition()
		{
			return InnerEntryTransition<Stand>();
		}
	};

	struct Stand : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mCrouchInputPressed)
			{
				return SiblingTransition<Crouch>();
			}

			return NoTransition();
		}
	};

	struct Crouch : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mCrouchInputPressed)
				return SiblingTransition<Stand>();

			return NoTransition();
		}
	};
};

Character::Character()
	: mCrouchInputPressed(false)
{
	mStateMachine.Initialize<CharacterStates::Alive>(this);
	mStateMachine.SetDebugInfo("TestHsm", TraceLevel::Basic);
}

void Character::Update()
{
	// Update state machine
	mStateMachine.ProcessStateTransitions();
	mStateMachine.UpdateStates();
}

int main()
{
	Character character;

	character.Update();

	printf(">>> Crouch!\n");
	character.mCrouchInputPressed = true;
	character.Update();
	character.mCrouchInputPressed = false;
	character.Update();

	printf(">>> Stand!\n");
	character.mCrouchInputPressed = true;
	character.Update();
	character.mCrouchInputPressed = false;
	character.Update();

	printf(">>> Crouch!\n");
	character.mCrouchInputPressed = true;
	character.Update();
	character.mCrouchInputPressed = false;
	character.Update();
}

In this simple character controller, we want to be able to toggle between the Stand and Crouch states by pressing the same input button, the state of which is represented by the Character::mCrouchInputPressed bool. The above example presents a typical first attempt, except it doesn't work and produces the following output:

HSM_1_TestHsm: Init    : struct CharacterStates::Alive
HSM_1_TestHsm:  Entry   : struct CharacterStates::Stand
>>> Crouch!
HSM_1_TestHsm:  Sibling : struct CharacterStates::Crouch
HSM_1_TestHsm:  Sibling : struct CharacterStates::Stand
HSM_1_TestHsm:  Sibling : struct CharacterStates::Crouch
HSM_1_TestHsm:  Sibling : struct CharacterStates::Stand
HSM_1_TestHsm:  Sibling : struct CharacterStates::Crouch
HSM_1_TestHsm:  Sibling : struct CharacterStates::Stand
HSM_1_TestHsm:  Sibling : struct CharacterStates::Crouch
HSM_1_TestHsm:  Sibling : struct CharacterStates::Stand
HSM_1_TestHsm:  Sibling : struct CharacterStates::Crouch
HSM_1_TestHsm:  Sibling : struct CharacterStates::Stand
HSM_1_TestHsm:  Sibling : struct CharacterStates::Crouch
HSM_1_TestHsm:  Sibling : struct CharacterStates::Stand
HSM_1_TestHsm:  Sibling : struct CharacterStates::Crouch
HSM_1_TestHsm:  Sibling : struct CharacterStates::Stand
HSM_1_TestHsm:  Sibling : struct CharacterStates::Crouch
HSM_1_TestHsm:  Sibling : struct CharacterStates::Stand
HSM_1_TestHsm:  Sibling : struct CharacterStates::Crouch
HSM_1_TestHsm:  Sibling : struct CharacterStates::Stand
HSM_1_TestHsm:  Sibling : struct CharacterStates::Crouch
HSM_1_TestHsm:  Sibling : struct CharacterStates::Stand
HSM_1_TestHsm:  Sibling : struct CharacterStates::Crouch
<snip>
Assertion failed: (false) && "ProcessStateTransitions: detected infinite transition loop", file C:\hsm\src\statemachine.cpp, line 125

From the output we see that we have introduced an infinite transition loop between the Stand and Crouch states (HSM issues an assertion when it detects this condition in debug builds). The reason this happens is that, as you may recall, whenever a transition is made, ProcessStateTransitions will call GetTransition on the entire state stack from outermost to innermost state all over again, until no more transitions are made (until the stack has settled). In this case, both states use the same condition to transition between each other, causing a transition from one to the other infinitely.

There are a few different ways to solve this problem. As we've done in previous examples, one way is to simply make sure to reset the bool, Character::mCrouchInputPressed in this case, just before we transition. Although this works in our examples, in production code, it's not always feasible. For instance, in a game engine, we'd normally query the input state of a controller or keyboard, and this state is usually read only, except when it gets updated once per frame. A potential solution is to mirror the input state into variables for use by the state machine, which would then be allowed to write to them; but this is cumbersome.

A better solution is to defer the transition by a frame by moving the decision making logic to the Update function:

	struct Stand : BaseState
	{
		virtual Transition GetTransition()
		{
			return mTransition;
		}

		virtual void Update()
		{
			if (Owner().mCrouchInputPressed)
				mTransition = SiblingTransition<Crouch>();
		}

		Transition mTransition;
	};

	struct Crouch : BaseState
	{
		virtual Transition GetTransition()
		{
			return mTransition;
		}

		virtual void Update()
		{
			if (Owner().mCrouchInputPressed)
				mTransition = SiblingTransition<Stand>();
		}

		Transition mTransition;
	};

As you can see, we now store a Transition data member named mTransition on each state, which has type "no transition" by default, so returning it in GetTransition as we do will have no effect until we set it. Now in Update, we perform the same check as we did previously, but this time we store the sibling transition into mTransition. By doing this, we delay the transition by "one frame" - or rather, by one call to StateMachine::ProcessStateTransitions. In between, the input state can be updated. An input pressed event would be true for only one frame until the player releases and re-presses the same button/key, so we'd only sibling from one state to the other for a single input pressed event. Now the program behaves as expected, as we can see from the output:

HSM_1_TestHsm: Init    : struct CharacterStates::Alive
HSM_1_TestHsm:  Entry   : struct CharacterStates::Stand
>>> Crouch!
HSM_1_TestHsm:  Sibling : struct CharacterStates::Crouch
>>> Stand!
HSM_1_TestHsm:  Sibling : struct CharacterStates::Stand
>>> Crouch!
HSM_1_TestHsm:  Sibling : struct CharacterStates::Crouch

One important thing to note is that deferring transitions this way should not be a general solution. In other words, except when needed, do not opt to compute transitions in Update and return the cached value in GetTransition by default. It is always better to compute and return transitions in GetTransition as much as possible. The rule of thumb should be: after calling StateMachine::ProcessStateTransitions, calling the same function a second time immediately should have no effect on the state stack. This is the best way to avoid being in an unexpected or "in between" state. In our example, calling StateMachine::ProcessStateTransitions a second time would result in a different state stack, but we've made an exception in this case because we know we have ended in a valid state, and purposely deferred the transition to avoid an infinite transition loop.

In the example above, we saw how to use deferred transitions to avoid infinite transition loops. There are many other ways to apply this technique, not only to fix problems like infinite transition loops, but to improve state reuse, as we'll see in the next section.

Reusable States

When implementing state machines, as with programming in general, you will want to avoid duplicating code. We've already seen how to avoid duplicating functions by sharing them across states via base state classes. Another useful way to avoid code duplication is by creating reusable states, which is the topic of this section.

The key to reusable states is combining two techniques we've covered so far: state arguments and deferred transitions. Let's take a look at an example:

// reusable_states.cpp

#include "hsm/statemachine.h"

using namespace hsm;

class AnimComponent
{
public:
	AnimComponent() : mLoop(false) {}
	void PlayAnim(const char* name, bool loop)
	{
		printf(">>> PlayAnim: %s, looping: %s\n", name, loop ? "true" : "false");
		mLoop = loop;
	}

	bool IsFinished() const { return !mLoop; }

private:
	bool mLoop;
};

class Character
{
public:
	Character();
	void Update();

	// Public to simplify sample
	bool mAttack;

private:
	friend struct CharacterStates;
	StateMachine mStateMachine;

	AnimComponent mAnimComponent;
};

struct CharacterStates
{
	struct BaseState : StateWithOwner<Character>
	{
	};

	struct PlayAnim_Done : BaseState
	{
	};

	struct PlayAnim : BaseState
	{
		struct Args : StateArgs
		{
			Args(const char* animName
				, bool loop = true
				, const Transition& doneTransition = SiblingTransition<PlayAnim_Done>()
				)
				: animName(animName)
				, loop(loop)
				, doneTransition(doneTransition)
			{}

			const char* animName;
			bool loop;
			Transition doneTransition;
		};

		virtual void OnEnter(const Args& args)
		{
			Owner().mAnimComponent.PlayAnim(args.animName, args.loop);
			mDoneTransition = args.doneTransition;
		}

		virtual Transition GetTransition()
		{
			if (Owner().mAnimComponent.IsFinished())
				return mDoneTransition;
			
			return NoTransition();
		}

		Transition mDoneTransition;
	};

	struct Alive : BaseState
	{
		virtual Transition GetTransition()
		{
			return InnerEntryTransition<Stand>();
		}
	};

	struct Stand : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mAttack)
			{
				Owner().mAttack = false;
				return SiblingTransition<Attack>();
			}

			return NoTransition();
		}
	};

	struct Attack : BaseState
	{
		virtual Transition GetTransition()
		{
			if (IsInInnerState<PlayAnim_Done>())
				return SiblingTransition<Stand>();

			return InnerEntryTransition<PlayAnim>(PlayAnim::Args("Attack_1", false,
				SiblingTransition<PlayAnim>(PlayAnim::Args("Attack_2", false,
				SiblingTransition<PlayAnim>(PlayAnim::Args("Attack_3", false))))));
		}
	};
};

Character::Character()
	: mAttack(false)
{
	mStateMachine.Initialize<CharacterStates::Alive>(this);
	mStateMachine.SetDebugInfo("TestHsm", TraceLevel::Basic);
}

void Character::Update()
{
	// Update state machine
	mStateMachine.ProcessStateTransitions();
	mStateMachine.UpdateStates();
}

int main()
{
	Character character;

	character.Update();

	character.mAttack = true;
	character.Update();
}

This example is similar to the one from the State Arguments section, where a reusable state named PlayAnim is used to play an animation from any other state. In this version, we've removed a few state arguments to PlayAnim, but have added a new one named doneTransition:

	struct PlayAnim : BaseState
	{
		struct Args : StateArgs
		{
			Args(const char* animName
				, bool loop = true
				, const Transition& doneTransition = SiblingTransition<PlayAnim_Done>()
				)
				: animName(animName)
				, loop(loop)
				, doneTransition(doneTransition)
			{}

			const char* animName;
			bool loop;
			Transition doneTransition;
		};

The new state argument, doneTransition, is a deferred transition that is returned by the PlayAnim state when it detects that the animation has finished playing. By exposing which transition to make when the animation is done playing makes this reusable state much more flexible. For instance, the Attack state queues up three PlayAnims to play a sequence of three animations before returning back to the Stand state:

	struct Attack : BaseState
	{
		virtual Transition GetTransition()
		{
			if (IsInInnerState<PlayAnim_Done>())
				return SiblingTransition<Stand>();

			return InnerEntryTransition<PlayAnim>(PlayAnim::Args("Attack_1", false,
				SiblingTransition<PlayAnim>(PlayAnim::Args("Attack_2", false,
				SiblingTransition<PlayAnim>(PlayAnim::Args("Attack_3", false))))));
		}
	};

The Attack state begins by returning an InnerEntryTransition to PlayAnim to play the first animation, and passes in as the doneTransition argument a sibling transition to another instance of PlayAnim, this time to play the second animation, and sibling to a third instance as its doneTransition. The third and final PlayAnim will sibling to PlayAnim_Done, the default doneTransition value, which Attack::GetTransition looks for to sibling back to Stand.

Here's the output from our program:

HSM_1_TestHsm: Init    : struct CharacterStates::Alive
HSM_1_TestHsm:  Entry   : struct CharacterStates::Stand
HSM_1_TestHsm:  Sibling : struct CharacterStates::Attack
HSM_1_TestHsm:   Entry   : struct CharacterStates::PlayAnim
>>> PlayAnim: Attack_1, looping: false
HSM_1_TestHsm:   Sibling : struct CharacterStates::PlayAnim
>>> PlayAnim: Attack_2, looping: false
HSM_1_TestHsm:   Sibling : struct CharacterStates::PlayAnim
>>> PlayAnim: Attack_3, looping: false
HSM_1_TestHsm:   Sibling : struct CharacterStates::PlayAnim_Done
HSM_1_TestHsm:  Sibling : struct CharacterStates::Stand

From the output, we can see clearly that the first PlayAnim state siblings to another PlayAnim instance, which in turn siblings to a third PlayAnim instance that finally siblings to PlayAnim_Done, at which point the outer state Attack siblings back to Stand.

Passing in transitions to states via state arguments is a powerful technique that increases state reuse. One downside to using reusable states is that it can make it more difficult to read and understand a state machine. For instance, in our example, Stand could have made a sibling transition directly to PlayAnim, queuing up the next 2 PlayAnims, and setting the last one to sibling back to Stand. However, to improve readability, Stand siblings to Attack, which is just a "shell" state that starts up the PlayAnim sequence as inner states. This is a good technique as it effectively gives a name to each instance of a reusable state usage.

Sharing HSMs

In all our examples so far, we've looked at how to author a single state machine, say, for a character controller. However, in a real project, you will likely find yourself needing to implement multiple state machines for different systems. In such situations, we would like to avoid duplicating code as much as possible. In this section, we will look at a couple of different techniques to share state machine code.

Sharing states with unrelated owners

One common situation that often arises when authoring state machines is the need to share a state, or a set of states, across different state machines. For example, say we're writing a game, and we have a state machine to control the hero, and another one to control enemies, and in both these state machines, we want to be able to play animations. The following example code shows how we can share PlayAnim states across both state machines:

// shared_states_unrelated_owners.cpp

#include "hsm/statemachine.h"

using namespace hsm;

struct SharedStates
{
	template <typename BaseState>
	struct PlayAnim_Done : BaseState
	{
	};

	template <typename BaseState>
	struct PlayAnim : BaseState
	{
		using BaseState::Owner;

		struct Args : StateArgs
		{
			Args(const char* animName
				, bool loop = true
				, const Transition& doneTransition = SiblingTransition< PlayAnim_Done<BaseState> >()
				)
				: animName(animName)
				, loop(loop)
				, doneTransition(doneTransition)
			{}

			const char* animName;
			bool loop;
			Transition doneTransition;
		};

		virtual void OnEnter(const Args& args)
		{
			Owner().mAnimComponent.PlayAnim(args.animName, args.loop);
			mDoneTransition = args.doneTransition;
		}

		virtual Transition GetTransition()
		{
			if (Owner().mAnimComponent.IsFinished())
				return mDoneTransition;

			return NoTransition();
		}

		Transition mDoneTransition;
	};
};

class AnimComponent
{
public:
	AnimComponent() : mLoop(false) {}
	void PlayAnim(const char* name, bool loop)
	{
		printf(">>> PlayAnim: %s, looping: %s\n", name, loop ? "true" : "false");
		mLoop = loop;
	}

	bool IsFinished() const { return !mLoop; }

private:
	bool mLoop;
};


////////////////////// Hero //////////////////////

class Hero
{
public:
	Hero();
	void Update();

	// Public to simplify sample
	bool mAttack;

private:
	friend struct SharedStates;
	friend struct HeroStates;
	StateMachine mStateMachine;

	AnimComponent mAnimComponent;
};

struct HeroStates
{
	struct BaseState : StateWithOwner<Hero>
	{
	};

	typedef SharedStates::PlayAnim_Done<BaseState> PlayAnim_Done;
	typedef SharedStates::PlayAnim<BaseState> PlayAnim;

	struct Alive : BaseState
	{
		virtual Transition GetTransition()
		{
			return InnerEntryTransition<Stand>();
		}
	};

	struct Stand : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mAttack)
			{
				Owner().mAttack = false;
				return SiblingTransition<Attack>();
			}

			return NoTransition();
		}
	};

	struct Attack : BaseState
	{
		virtual Transition GetTransition()
		{
			if (IsInInnerState<PlayAnim_Done>())
				return SiblingTransition<Stand>();

			return InnerEntryTransition<PlayAnim>(PlayAnim::Args("Attack_1", false));
		}
	};
};

Hero::Hero()
	: mAttack(false)
{
	mStateMachine.Initialize<HeroStates::Alive>(this);
	mStateMachine.SetDebugInfo("TestHsm", TraceLevel::Basic);
}

void Hero::Update()
{
	// Update state machine
	mStateMachine.ProcessStateTransitions();
	mStateMachine.UpdateStates();
}

////////////////////// Enemy //////////////////////

class Enemy
{
public:
	Enemy();
	void Update();

	// Public to simplify sample
	bool mAttack;

private:
	friend struct SharedStates;
	friend struct EnemyStates;
	StateMachine mStateMachine;

	AnimComponent mAnimComponent;
};

struct EnemyStates
{
	struct BaseState : StateWithOwner<Enemy>
	{
	};

	typedef SharedStates::PlayAnim_Done<BaseState> PlayAnim_Done;
	typedef SharedStates::PlayAnim<BaseState> PlayAnim;

	struct Alive : BaseState
	{
		virtual Transition GetTransition()
		{
			return InnerEntryTransition<Stand>();
		}
	};

	struct Stand : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mAttack)
			{
				Owner().mAttack = false;
				return SiblingTransition<Attack>();
			}

			return NoTransition();
		}
	};

	struct Attack : BaseState
	{
		virtual Transition GetTransition()
		{
			if (IsInInnerState<PlayAnim_Done>())
				return SiblingTransition<Stand>();

			return InnerEntryTransition<PlayAnim>(PlayAnim::Args("Attack_1", false));
		}
	};
};

Enemy::Enemy()
	: mAttack(false)
{
	mStateMachine.Initialize<EnemyStates::Alive>(this);
	mStateMachine.SetDebugInfo("TestHsm", TraceLevel::Basic);
}

void Enemy::Update()
{
	// Update state machine
	mStateMachine.ProcessStateTransitions();
	mStateMachine.UpdateStates();
}


////////////////////// main //////////////////////

int main()
{
	Hero hero;
	hero.Update();
	hero.mAttack = true;
	hero.Update();

	Enemy enemy;
	enemy.Update();
	enemy.mAttack = true;
	enemy.Update();
}

In this example, we have two state machines: one for the Hero class and one for the Enemy class. In both state machines, state Attack wants to push the PlayAnim state, and checks for the PlayAnim_Done state. The definitions for these states is in a struct named SharedStates:

struct SharedStates
{
	template <typename BaseState>
	struct PlayAnim_Done : BaseState
	{
	};

	template <typename BaseState>
	struct PlayAnim : BaseState
	{
		using BaseState::Owner;

		<snip>
	};
};

These states look very much like the usual states that we write, but with a couple of important differences. First of all, they are template classes so that the base state class can be specified. We do this because the base state classes are different and unrelated types in both our state machines:

struct HeroStates
{
	struct BaseState : StateWithOwner<Hero>
	{
	};
	
	<snip>
};

<snip>

struct EnemyStates
{
	struct BaseState : StateWithOwner<Enemy>
	{
	};
	
	<snip>
};

As usual, our BaseState is a StateWithOwner, and in our example, the OwnerTypes, Hero and Enemy are unrelated. Now one consequence of using template classes to represent our states is that we need to deal with C++ argument dependent lookup (ADL) rules. For instance, the PlayAnim state makes calls to the Owner() function, which is expected to be implemented in its base class. As the base class is a template type, the calls to Owner() depend on the template argument, so we must let the compiler know that this is the case. We solve this by bringing in the dependent name into the PlayAnim class with a using declaration: using BaseState::Owner;. Alternatively, you can also prefix each call to Owner() with this-> to achieve the same result.

That's all there is to it! With this technique, states can be easily shared between arbitrary state machines with unrelated owners. Of course, one important thing to keep in mind is that, although the base states don't have to be related, they must provide the same functions or types that are accessed by that state. In our example, the PlayAnim state expects an AnimComponent named mAnimComponent to exist on the Owner type.

Sharing states with related owners

Although the technique we presented in the previous section works for states with unrelated owner types, we can simplify the technique when the owner types are related. Let's take a look at a modified version of our example where both Hero and Enemy derive from a common base named Character:

// shared_states_related_owners.cpp

#include "hsm/statemachine.h"

using namespace hsm;

class AnimComponent
{
public:
	AnimComponent() : mLoop(false) {}
	void PlayAnim(const char* name, bool loop)
	{
		printf(">>> PlayAnim: %s, looping: %s\n", name, loop ? "true" : "false");
		mLoop = loop;
	}

	bool IsFinished() const { return !mLoop; }

private:
	bool mLoop;
};

////////////////////// Character //////////////////////

class Character
{
public:
	void Update();

protected:
	friend struct SharedStates;
	StateMachine mStateMachine;
	AnimComponent mAnimComponent;
};

void Character::Update()
{
	// Update state machine
	mStateMachine.ProcessStateTransitions();
	mStateMachine.UpdateStates();
}

struct SharedStates
{
	// These states can be shared by state machines with Character-derived owners

	struct BaseState : StateWithOwner<Character>
	{
	};

	struct PlayAnim_Done : BaseState
	{
	};

	struct PlayAnim : BaseState
	{
		struct Args : StateArgs
		{
			Args(const char* animName
				, bool loop = true
				, const Transition& doneTransition = SiblingTransition<PlayAnim_Done>()
				)
				: animName(animName)
				, loop(loop)
				, doneTransition(doneTransition)
			{}

			const char* animName;
			bool loop;
			Transition doneTransition;
		};

		virtual void OnEnter(const Args& args)
		{
			Owner().mAnimComponent.PlayAnim(args.animName, args.loop);
			mDoneTransition = args.doneTransition;
		}

		virtual Transition GetTransition()
		{
			if (Owner().mAnimComponent.IsFinished())
				return mDoneTransition;

			return NoTransition();
		}

		Transition mDoneTransition;
	};
};

////////////////////// Hero //////////////////////

class Hero : public Character
{
public:
	Hero();

	// Public to simplify sample
	bool mAttack;

private:
	friend struct HeroStates;
};


struct HeroStates
{
	struct BaseState : StateWithOwner<Hero, SharedStates::BaseState>
	{
	};

	typedef SharedStates::PlayAnim_Done PlayAnim_Done;
	typedef SharedStates::PlayAnim PlayAnim;

	struct Alive : BaseState
	{
		virtual Transition GetTransition()
		{
			return InnerEntryTransition<Stand>();
		}
	};

	struct Stand : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mAttack)
			{
				Owner().mAttack = false;
				return SiblingTransition<Attack>();
			}

			return NoTransition();
		}
	};

	struct Attack : BaseState
	{
		virtual Transition GetTransition()
		{
			if (IsInInnerState<PlayAnim_Done>())
				return SiblingTransition<Stand>();

			return InnerEntryTransition<PlayAnim>(PlayAnim::Args("Attack_1", false));
		}
	};
};

Hero::Hero()
	: mAttack(false)
{
	mStateMachine.Initialize<HeroStates::Alive>(this);
	mStateMachine.SetDebugInfo("TestHsm", TraceLevel::Basic);
}


////////////////////// Enemy //////////////////////

class Enemy : public Character
{
public:
	Enemy();

	// Public to simplify sample
	bool mAttack;

private:
	friend struct EnemyStates;
};

struct EnemyStates
{
	struct BaseState : StateWithOwner<Enemy, SharedStates::BaseState>
	{
	};

	typedef SharedStates::PlayAnim_Done PlayAnim_Done;
	typedef SharedStates::PlayAnim PlayAnim;

	struct Alive : BaseState
	{
		virtual Transition GetTransition()
		{
			return InnerEntryTransition<Stand>();
		}
	};

	struct Stand : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mAttack)
			{
				Owner().mAttack = false;
				return SiblingTransition<Attack>();
			}

			return NoTransition();
		}
	};

	struct Attack : BaseState
	{
		virtual Transition GetTransition()
		{
			if (IsInInnerState<PlayAnim_Done>())
				return SiblingTransition<Stand>();

			return InnerEntryTransition<PlayAnim>(PlayAnim::Args("Attack_1", false));
		}
	};
};

Enemy::Enemy()
	: mAttack(false)
{
	mStateMachine.Initialize<EnemyStates::Alive>(this);
	mStateMachine.SetDebugInfo("TestHsm", TraceLevel::Basic);
}


////////////////////// main //////////////////////

int main()
{
	Hero hero;
	hero.Update();
	hero.mAttack = true;
	hero.Update();

	Enemy enemy;
	enemy.Update();
	enemy.mAttack = true;
	enemy.Update();
}

In this example, we've introduced a new Character base class that both Hero and Enemy derive from. We've moved some of the common members into Character, such as the StateMachine and AnimComponent data members. Now that both Hero and Enemy derive from Character, the states in SharedStates are no longer template classes; instead, they now derive from StateWithOwner<Character>. Notice how we no longer have to deal with C++ ADL rules. The states simply access their owner type, Character, via the Owner() member.

Consequently, the typedefs for the shared states in both HeroStates and EnemyStates has also been simplified as we no longer need to specify the owner type as a template parameter:

	typedef SharedStates::PlayAnim_Done PlayAnim_Done;
	typedef SharedStates::PlayAnim PlayAnim;

There is one final difference between this example and the one from the last section. Take a look at how the BaseState structs are defined in both HeroStates and EnemyStates:

struct HeroStates
{
	struct BaseState : StateWithOwner<Hero, SharedStates::BaseState>
	{
	};

	<snip>
};

struct EnemyStates
{
	struct BaseState : StateWithOwner<Enemy, SharedStates::BaseState>
	{
	};

	<snip>
};

Notice how we specify SharedStates::BaseState as a second template parameter to StateWithOwner. This second template parameter is an optional parameter that specifies StateWithOwner's base class:

namespace hsm {

template <typename OwnerType, typename StateBaseType = State>
struct StateWithOwner : StateBaseType

<snip>

};

As you can see, by default, StateWithOwner will derive from hsm::State, the base class for all States in HSM. In our example, we choose, instead, to derive from SharedStates::BaseState. For example, the inheritance chain for HeroStates::BaseState looks like this (from base to most-derived):

Inheritance Chain
hsm::State
hsm::StateWithOwner
SharedStates::BaseState
hsm::StateWithOwner<Hero, SharedStates::BaseState>
HeroStates::BaseState

Effectively, we've simply made sure to derive HeroStates::BaseState from SharedStates::BaseState. Is this necessary? No, not in our example. However, by doing this, we can now add functions to SharedStates::BaseState that would be accessible by all states in both HeroStates and EnemyStates. So not only are we able to share states across our state machines, but also functions.

Sharing State Machines (State Overrides)

In the previous section, we learned how to share states across different state machines. This is a very useful technique that works well for leaf states - that is, states that are pushed deeper on the stack. However, it's not always easy to generalize the more outer states of state machines. In the previous section's example where Hero and Enemy that both derive from Character, the state machines for both classes are very similar. It would be nice to be able to share a core state machine structure between the two classes, while allowing each one to override specific states. In this section, we will take a look at how to achieve exactly that by using the state override feature of HSM:

// state_overrides.cpp

#include "hsm/statemachine.h"

using namespace hsm;

////////////////////// Character //////////////////////

class Character
{
public:
	Character();
	void Update();

	// Public to simplify sample
	bool mAttack;
	bool mJump;

protected:
	friend struct CharacterStates;
	StateMachine mStateMachine;
};

struct CharacterStates
{
	struct BaseState : StateWithOwner<Character>
	{
	};

	struct PlayAnim_Done : BaseState
	{
	};

	struct Alive : BaseState
	{
		virtual Transition GetTransition()
		{
			return InnerEntryTransition<Stand>();
		}
	};

	struct Stand : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mAttack)
			{
				Owner().mAttack = false;
				return SiblingTransition(GetStateOverride<Attack>());
			}

			if (Owner().mJump)
			{
				Owner().mJump = false;
				return SiblingTransition(GetStateOverride<Jump>());
			}

			return NoTransition();
		}
	};

	struct Attack : BaseState
	{
		virtual Transition GetTransition()
		{
			return SiblingTransition<Stand>();
		}
	};

	struct Jump : BaseState
	{
		virtual Transition GetTransition()
		{
			return SiblingTransition<Stand>();
		}
	};
};

Character::Character()
	: mAttack(false)
	, mJump(false)

{
	mStateMachine.Initialize<CharacterStates::Alive>(this);
	mStateMachine.SetDebugInfo("TestHsm", TraceLevel::Basic);
}

void Character::Update()
{
	// Update state machine
	mStateMachine.ProcessStateTransitions();
	mStateMachine.UpdateStates();
}


////////////////////// Hero //////////////////////

class Hero : public Character
{
public:
	Hero();

private:
	friend struct HeroStates;
};

struct HeroStates
{
	struct BaseState : StateWithOwner<Hero, CharacterStates::BaseState>
	{
	};

	struct Attack : BaseState
	{
		virtual Transition GetTransition()
		{
			return SiblingTransition<CharacterStates::Stand>();
		}
	};

	struct Jump : BaseState
	{
		virtual Transition GetTransition()
		{
			return SiblingTransition<CharacterStates::Stand>();
		}
	};
};

Hero::Hero()
{
	mStateMachine.AddStateOverride<CharacterStates::Attack, HeroStates::Attack>();
	mStateMachine.AddStateOverride<CharacterStates::Jump, HeroStates::Jump>();
}


////////////////////// Enemy //////////////////////

class Enemy : public Character
{
public:
	Enemy();

private:
	friend struct EnemyStates;
};


struct EnemyStates
{
	struct BaseState : StateWithOwner<Enemy, CharacterStates::BaseState>
	{
	};

	struct Attack : BaseState
	{
		virtual Transition GetTransition()
		{
			return SiblingTransition<CharacterStates::Stand>();
		}
	};
};

Enemy::Enemy()
{
	mStateMachine.AddStateOverride<CharacterStates::Attack, EnemyStates::Attack>();
}


////////////////////// main //////////////////////

int main()
{
	Hero hero;
	hero.Update();
	hero.mAttack = true;
	hero.Update();
	hero.mJump = true;
	hero.Update();

	printf("\n");

	Enemy enemy;
	enemy.Update();
	enemy.mAttack = true;
	enemy.Update();
	enemy.mJump = true;
	enemy.Update();
}

In the example above, we have a single state machine defined for the Character class in the CharacterStates struct. This state machine looks a lot like the ones we've seen so far, except for one important difference, which can be seen in the Stand state:

	struct Stand : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mAttack)
			{
				Owner().mAttack = false;
				return SiblingTransition(GetStateOverride<Attack>());
			}

			if (Owner().mJump)
			{
				Owner().mJump = false;
				return SiblingTransition(GetStateOverride<Jump>());
			}

			return NoTransition();
		}
	};

Rather than returning the usual SiblingTransition<Attack>(), for example, we now use an overload of SiblingTransition that accepts a runtime-defined parameter for which state to transition to, which in this case is returned by the function GetStateOverride<Attack>(). What this means is that we are asking the state machine to return a sibling transition to the CharacterStates::Attack state by default, unless an override has been defined for that state. We do this for both the Attack and Jump states, effectively providing hooks to override these specific states.

Now let's look at how these states are overridden. The Hero class wants to override both the Attack and Jump states, so it defines its own versions of these states in the HeroStates struct:

struct HeroStates
{
	struct BaseState : StateWithOwner<Hero, CharacterStates::BaseState>
	{
	};

	struct Attack : BaseState
	{
		virtual Transition GetTransition()
		{
			return SiblingTransition<CharacterStates::Stand>();
		}
	};

	struct Jump : BaseState
	{
		virtual Transition GetTransition()
		{
			return SiblingTransition<CharacterStates::Stand>();
		}
	};
};

To override the versions of these states defined in CharacterStates, Hero explicitly does so in its constructor:

Hero::Hero()
{
	mStateMachine.AddStateOverride<CharacterStates::Attack, HeroStates::Attack>();
	mStateMachine.AddStateOverride<CharacterStates::Jump, HeroStates::Jump>();
}

The StateMachine::AddStateOverride function can be used to dynamically add an override at any time for given state machine instance, while StateMachine::RemoveStateOverride can be used to remove an override. In this example, we want to permanently override the states, so we do this once in the constructor.

The Enemy class is implemented similarly to Hero, except it only overrides the Attack state.

Let's take a look at the output from this program:

HSM_1_TestHsm: Init    : struct CharacterStates::Alive
HSM_1_TestHsm:  Entry   : struct CharacterStates::Stand
HSM_1_TestHsm:  Sibling : struct HeroStates::Attack
HSM_1_TestHsm:  Sibling : struct CharacterStates::Stand
HSM_1_TestHsm:  Sibling : struct HeroStates::Jump
HSM_1_TestHsm:  Sibling : struct CharacterStates::Stand

HSM_1_TestHsm: Init    : struct CharacterStates::Alive
HSM_1_TestHsm:  Entry   : struct CharacterStates::Stand
HSM_1_TestHsm:  Sibling : struct EnemyStates::Attack
HSM_1_TestHsm:  Sibling : struct CharacterStates::Stand
HSM_1_TestHsm:  Sibling : struct CharacterStates::Jump
HSM_1_TestHsm:  Sibling : struct CharacterStates::Stand

From this output, we can clearly see how both the Hero and Enemy state overrides end up being transitioned to from the Stand state.

The mechanism behind state overrides isn't particularly complicated. When you add an override, internally the state machine just stores this mapping of source to target state, and when you call GetStateOverride<SourceState>, the mapped state is returned if found, otherwise the source state is returned.

You might be wondering why we need to bother with calling GetStateOverride explicitly - why doesn't the state machine just always check the mapping for every transition? There are two reasons: first, we don't want to pay the performance penalty of looking up state overrides for every transition, especially if we don't use this feature. Second, because the state override hooks are explicit, it makes it easier to understand a state machine and know where states might be overridden. This is very much analogous to how class functions must be explicitly declared as virtual to be overridden in derived classes.

Parallel State Machines

As we have seen, using a hierarchical state machine is a useful tool to represent multiple states at different levels of abstraction. In effect, it is very much like running a non-hierarchical state machine where any state itself may run another state machine, and so on. Although we are effectively running multiple state machines, the existence of inner machines depends on the outer state machines. Put another way: an inner state cannot "survive" across outer state transitions. Sometimes we need that, and the solution is to run multiple state machines in parallel.

HSM does not have any special support for parallel state machines. All you need to do is run multiple instances of hsm::StateMachine; the tricky part is keeping them in sync. In this section, we'll take a look at one way to do that using the features of HSM we've seen so far.

The motivation behind the example below is to implement a character controller with the following gameplay constraints:

  • Our character can stand, move, jump, and reload its weapon;
  • Reloading cannot happen while jumping. Thus, if the character is standing or moving around, it can reload; if it's jumping, it can't reload; and if it starts reloading while standing/moving, and then jumps, the reload is cancelled.

The key here is that reloading is a parallel process: you can start the reload process while toggling between standing and moving at any time; however, if you jump, reloading is cancelled and cannot happen again until jumping is finished.

In our example, we implement two state machines to solve this problem: a "full body" state machine that controls the full body actions and animations, like stand/move/jump; and an "upper body" state machine that controls upper-body actions and animations like reloading.

// parallel_state_machines.cpp

#include "hsm/statemachine.h"

using namespace hsm;

class Hero
{
public:
	Hero();
	void Update();

	// Public to simplify sample
	bool mMove;
	bool mJump;
	bool mReload;

private:

	// Functions that simulate playing an animation that always lasts 3 frames
	void PlayAnim(const char*) { mAnimFrame = 0; }
	bool IsAnimDone() const { return mAnimFrame >= 2; }

	void ReloadWeapon() { printf(">>> WEAPON RELOADED!\n"); }

	friend struct HeroFullBodyStates;
	friend struct HeroUpperBodyStates;
	StateMachine mStateMachines[2];

	hsm::StateValue<bool> mUpperBodyEnabled;

	int mAnimFrame;
};

struct HeroFullBodyStates
{
	struct BaseState : StateWithOwner<Hero>
	{
	};

	struct Alive : BaseState
	{
		virtual void OnEnter()
		{
			// By default, upper body is enabled
			SetStateValue(Owner().mUpperBodyEnabled) = true;
		}

		virtual Transition GetTransition()
		{
			return InnerEntryTransition<Stand>();
		}
	};

	struct Stand : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mMove)
			{
				return SiblingTransition<Move>();
			}

			if (Owner().mJump)
			{
				Owner().mJump = false;
				return SiblingTransition<Jump>();
			}

			return NoTransition();
		}
	};

	struct Move : BaseState
	{
		virtual Transition GetTransition()
		{
			if (!Owner().mMove)
			{
				return SiblingTransition<Stand>();
			}

			if (Owner().mJump)
			{
				Owner().mJump = false;
				return SiblingTransition<Jump>();
			}
			
			return NoTransition();
		}
	};

	struct Jump : BaseState
	{
		virtual void OnEnter()
		{
			// Don't allow upper body to do anything while jumping
			SetStateValue(Owner().mUpperBodyEnabled) = false;
			Owner().PlayAnim("Jump");
		}

		virtual Transition GetTransition()
		{
			if (Owner().IsAnimDone())
			{
				if (!Owner().mMove)
				{
					return SiblingTransition<Stand>();
				}
				else
				{
					return SiblingTransition<Move>();
				}
			}

			return NoTransition();
		}
	};
};

struct HeroUpperBodyStates
{
	struct BaseState : StateWithOwner < Hero >
	{
	};

	struct Disabled : BaseState
	{
		virtual Transition GetTransition()
		{
			if (Owner().mUpperBodyEnabled)
				return SiblingTransition<Enabled>();

			return NoTransition();
		}
	};

	struct Enabled : BaseState
	{
		virtual Transition GetTransition()
		{
			if (!Owner().mUpperBodyEnabled)
				return SiblingTransition<Disabled>();

			return InnerEntryTransition<Idle>();
		}
	};

	struct Idle : BaseState
	{
		virtual void OnEnter()
		{
			Owner().PlayAnim("Idle");
		}

		virtual Transition GetTransition()
		{
			if (Owner().mReload)
			{
				Owner().mReload = false;
				return SiblingTransition<Reload>();
			}

			return NoTransition();
		}
	};

	struct Reload : BaseState
	{
		virtual Transition GetTransition()
		{
			if (IsInInnerState<Reload_Done>())
				return SiblingTransition<Idle>();

			return InnerEntryTransition<Reload_PlayAnim>();
		}
	};

	struct Reload_PlayAnim : BaseState
	{
		virtual void OnEnter()
		{
			Owner().PlayAnim("Reload");
		}

		virtual Transition GetTransition()
		{
			if (Owner().IsAnimDone())
				return SiblingTransition<Reload_Done>();

			return NoTransition();
		}
	};

	struct Reload_Done : BaseState
	{
		virtual void OnEnter()
		{
			// Only once we're done the anim to we actually reload our weapon
			Owner().ReloadWeapon();
		}
	};
};

Hero::Hero()
	: mMove(false)
	, mJump(false)
	, mReload(false)
	, mUpperBodyEnabled(false)

{
	mStateMachines[0].Initialize<HeroFullBodyStates::Alive>(this);
	mStateMachines[0].SetDebugInfo("FullBody ", TraceLevel::Basic);

	mStateMachines[1].Initialize<HeroUpperBodyStates::Disabled>(this);
	mStateMachines[1].SetDebugInfo("UpperBody", TraceLevel::Basic);
}

void Hero::Update()
{
	// Update state machines
	for (int i = 0; i < 2; ++i)
	{
		mStateMachines[i].ProcessStateTransitions();
		mStateMachines[i].UpdateStates();
	}

	++mAnimFrame;
}

////////////////////// main //////////////////////

int main()
{
	Hero hero;
	
	int whichUpdate = 0;

	printf(">>> Update %d\n", whichUpdate++);
	hero.Update();

	printf(">>> Input: Reload\n");
	hero.mReload = true;

	printf(">>> Update %d\n", whichUpdate++);
	hero.Update();

	printf(">>> Input: Move\n");
	hero.mMove = true;

	printf(">>> Update %d\n", whichUpdate++);
	hero.Update();

	printf(">>> Update %d\n", whichUpdate++);
	hero.Update();

	printf(">>> Input: Reload\n");
	hero.mReload = true;

	printf(">>> Update %d\n", whichUpdate++);
	hero.Update();

	printf(">>> Input: Jump\n");
	hero.mJump = true;

	printf(">>> Update %d\n", whichUpdate++);
	hero.Update();

	printf(">>> Update %d\n", whichUpdate++);
	hero.Update();

	printf(">>> Update %d\n", whichUpdate++);
	hero.Update();
}

The first thing to notice is that the Hero class contains an array of 2 state machine instances, StateMachine mStateMachines[2], which are initialized in the constructor, and updated in Hero's Update function:

Hero::Hero()
<snip>
{
	mStateMachines[0].Initialize<HeroFullBodyStates::Alive>(this);
	mStateMachines[0].SetDebugInfo("FullBody ", TraceLevel::Basic);

	mStateMachines[1].Initialize<HeroUpperBodyStates::Disabled>(this);
	mStateMachines[1].SetDebugInfo("UpperBody", TraceLevel::Basic);
}

void Hero::Update()
{
	// Update state machines
	for (int i = 0; i < 2; ++i)
	{
		mStateMachines[i].ProcessStateTransitions();
		mStateMachines[i].UpdateStates();
	}
<snip>
}

The implementation of each state machine can be found in the HeroFullBodyStates struct and the HeroUpperBodyStates struct. Let's take a look at the plots of both state machines:

The FullBody state machine:

parallel_state_machines_fullbody

The UpperBody state machine:

parallel_state_machines_upperbody

The FullBody state machine is straightforward: it controls moving, standing, and jumping. The UpperBody state machine is slightly more complex: the upper body is either enabled or disabled; in a real game, being "enabled" would mean animations could override the upper part of the character's skeleton, otherwise when "disabled", the full body animation controls the entire skeleton. While enabled, the UpperBody is either Idle (playing an idle animation), or in the Reload state.

The Reload state starts off a sequence where it first enters Reload_PlayAnim, responsible for playing a reload animation, before it siblings to Reload_Done, which is when the character's weapon is actually reloaded. If the animation doesn't get a chance to finish playing, then the weapon does not get reloaded. This is exactly what would happen if the player were to jump while the reload animation is playing. To illustrate this situation, the example code implements animation playback as always taking 3 frames to complete (see mAnimFrame).

To synchronize the FullBody and UpperBody state machines, we use a StateValue: hsm::StateValue<bool> mUpperBodyEnabled. This boolean StateValue is set to true while the FullBody is in the Alive state, and is only set to false while in the Jump state (one of Alive's inner state). The UpperBody state machine simply polls this StateValue to determine whether it should be in the Enabled or Disabled state.

Now let's see this code in action. Rather than simply show the output from the code in one block, we will mix snippets of the code in main with the corresponding output, and explain each part.

Code from main:

	Hero hero;
	
	int whichUpdate = 0;

	printf(">>> Update %d\n", whichUpdate++);
	hero.Update();

Output:

>>> Update 0
HSM_1_FullBody : Init    : struct HeroFullBodyStates::Alive
HSM_1_FullBody :  Entry   : struct HeroFullBodyStates::Stand
HSM_1_UpperBody: Init    : struct HeroUpperBodyStates::Disabled
HSM_1_UpperBody: Sibling : struct HeroUpperBodyStates::Enabled
HSM_1_UpperBody:  Entry   : struct HeroUpperBodyStates::Idle

At this point, we've created a Hero with default values and have updated it once. Here we see how the FullBody state machine ends up in Alive.Stand, while the UpperBody state machine ends up in Enabled.Idle.

Code from main (continued):

	printf(">>> Input: Reload\n");
	hero.mReload = true;

	printf(">>> Update %d\n", whichUpdate++);
	hero.Update();

Output:

>>> Input: Reload
>>> Update 1
HSM_1_UpperBody:  Sibling : struct HeroUpperBodyStates::Reload
HSM_1_UpperBody:   Entry   : struct HeroUpperBodyStates::Reload_PlayAnim

We've initiated a reload, which results in the UpperBody state machine transitioning from Idle to Reload. Note that it starts up the reload sequence by playing a reload animation in the Reload_PlayAnim state. In our example, this will take 2 more Update calls to complete.

Code from main (continued):

	printf(">>> Input: Move\n");
	hero.mMove = true;

	printf(">>> Update %d\n", whichUpdate++);
	hero.Update();

Output:

>>> Input: Move
>>> Update 2
HSM_1_FullBody :  Sibling : struct HeroFullBodyStates::Move

Now while reloading, we decide to start moving. This simply affects the FullBody state machine, making it sibling from Stand to Move. Note that this has no affect on the UpperBody state machine, which happily continues reloading.

Code from main (continued):

	printf(">>> Update %d\n", whichUpdate++);
	hero.Update();

Output:

>>> Update 3
HSM_1_UpperBody:   Sibling : struct HeroUpperBodyStates::Reload_Done
>>> WEAPON RELOADED!
HSM_1_UpperBody:  Sibling : struct HeroUpperBodyStates::Idle

With one more update, the reload sequence is now complete. We see that the UpperBody state machine siblings to Reload_Done, which triggers the weapon reload, and then the Reload outer state siblings back to Idle.

Code from main (continued):

	printf(">>> Input: Reload\n");
	hero.mReload = true;

	printf(">>> Update %d\n", whichUpdate++);
	hero.Update();

Output:

>>> Input: Reload
>>> Update 4
HSM_1_UpperBody:  Sibling : struct HeroUpperBodyStates::Reload
HSM_1_UpperBody:   Entry   : struct HeroUpperBodyStates::Reload_PlayAnim

Now we initiate another reload, which once again results in the UpperBody state machine starting the reload sequence.

Code from main (continued):

	printf(">>> Input: Jump\n");
	hero.mJump = true;

	printf(">>> Update %d\n", whichUpdate++);
	hero.Update();

Output:

>>> Input: Jump
>>> Update 5
HSM_1_FullBody :  Sibling : struct HeroFullBodyStates::Jump
HSM_1_UpperBody: Sibling : struct HeroUpperBodyStates::Disabled

This time, we decide to jump while reloading. As we saw earlier, the Jump state disables the upper body state machine via the mUpperBodyEnabled StateValue. The result is that as the FullBody state machine enters the Jump state, the UpperBody enters the Disabled state, effectively stopping the Reload state from finishing.

Code from main (continued):

	printf(">>> Update %d\n", whichUpdate++);
	hero.Update();

	printf(">>> Update %d\n", whichUpdate++);
	hero.Update();

Output:

>>> Update 6
>>> Update 7
HSM_1_FullBody :  Sibling : struct HeroFullBodyStates::Move
HSM_1_UpperBody: Sibling : struct HeroUpperBodyStates::Enabled
HSM_1_UpperBody:  Entry   : struct HeroUpperBodyStates::Idle

The last two update calls allow the Jump state to complete its animation, which results in Jump sibling back to Move, and the UpperBody state machine is now re-enabled and enters Idle.

Although this example is minimal, even more complex set of parallel state machines are typically implemented in a similar fashion. One important thing to note is that the update order of the parallel state machines matter: in this case, we make sure to update the FullBody before the UpperBody, because the former effectively controls the latter. In practice, there is usually a "master" state machine, and one or more "slave" state machines; just make sure to update the master first.

Prev (Chapter 3) | Next (Chapter 5)