This article assumes you know what are Finite State Machines (FSM). If not, I would recommend reading this article that shows common pitfalls when writing code handling character state and why we use FSMs. If you prefer a "TL;DR", the gist is that without FSMs you need to use plenty of flags in your program, which can lead to many nested "if-else" statements everywhere, leading to a terrifying debugging experience. You do not want to waste your time in this self-made hell.
// Example of self-made hell
private void handleInput()
{
if (Input.GetKeyDown(KeyCode.Space))
{
if (!isJumping && !isDucking)
{
// Jump...
}
}
else if (Input.GetKeyDown(KeyCode.D))
{
if (!isJumping)
{
isDucking = true;
setGraphics(IMAGE_DUCK);
}
}
else if (Input.GetKeyUp(KeyCode.D))
{
if (isDucking)
{
isDucking = false;
setGraphics(IMAGE_STAND);
}
}
}
FSMs are the kind of pattern that most people I know have a love/hate relationship with. Love it for it's ability to encapsulate dynamic application state, hate it for additional complexity. Personally I did not enjoy using code-only FSMs since juggling state transitions can be quite a handful especially in games with complex interactions between units but including a GUI solution for FSM like Playmaker can be pretty heavy.
On that note, what if there is a lightweight FSM system that we can use (or hijack) within Unity?
Using the AnimationController
as FSM
Unity already contains a state machine for animations that we can use, with some modification.
Setup
- Create an empty GameObject in the scene with an Animator attached.
- Create a new AnimatorController asset somewhere in your project folder. Attach this to the Animator you've created.
- Open Window→Animator→Parameters and create a trigger called "Next".
Customization
Create a class that inherits StateMachineBehaviour
so we can create custom states, provide a cleaner interface for the state machine, and enable debugging. We can call our custom class LogicalStateMachine
. StateMachineBehaviour
already provides methods such as OnStateEnter
, OnStateUpdate
, OnStateExit
, which we can use to provide interfaces such as these.
protected abstract void OnLogicalStateEnter(GameObject gameObject);
protected abstract void OnLogicalStateUpdate(GameObject gameObject);
protected abstract void OnLogicalStateExit(GameObject gameObject);
protected abstract void ResetState(GameObject gameObject);
public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
ResetState(ActiveAnimator.gameObject);
OnLogicalStateEnter(ActiveAnimator.gameObject);
}
public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
OnLogicalStateUpdate(ActiveAnimator.gameObject);
}
public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
OnLogicalStateExit(ActiveAnimator.gameObject);
}
To make it easier to debug, we'll use the trigger we've created before to create a Continue
method that can force the next state to occur.
protected Animator ActiveAnimator;
protected void Continue()
{
if (!ActiveAnimator) return;
ActiveAnimator.SetTrigger(Animator.StringToHash(Next));
}
public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
ActiveAnimator = animator;
ResetState(ActiveAnimator.gameObject);
ActiveAnimator.ResetTrigger(Animator.StringToHash(Next));
OnLogicalStateEnter(ActiveAnimator.gameObject);
}
We can make this even better by creating an Inspector Button for it in the editor.
Personally I just use the Odin Inspector to create one but if you'd prefer a native solution there's one here.
Voila! A simple state machine you can use in your project. You can find the code here as well: https://gitlab.com/glassblade/unityfsm
Effective State Management in Games
This advice applies to most complex applications, but game is a good example. Games typically contain two types of state : persistent state and dynamic state. For example, in the game, "Enter the Gungeon", the persistent state would contain the unlocked pool of guns, while the dynamic state would contain the gun picked up by the player.
FSMs contain two basic components, state and transitions and I would recommend the following to make it easier to debug your state in FSM:
- If you need to use a flag, don't. Use a state instead.
- Spending some time before you code to identify what would be persistent state and dynamic state in your game relative to the FSM. Dynamic state should be reset each time you enter a state while persistent state will not.
- Make an immutable copy of your persistent state on entering a state, only set persistent state on exiting a state. You can optimize this later on, but it makes it alot easier for debugging points of error.
Top comments (0)