Location: Assets/Plugins/Animancer/Examples/06 State Machines/01 Character
Recommended After: Basic Character
Learning Outcomes: in this example you will learn:
How to use Animancer's Finite State Machine system.
A useful pattern for dividing character logic into multiple scripts.
Why dividing complex logic into multiple scripts is a good idea.
This example demonstrates how the logic from the Basic Character example can be divided into multiple scripts using Animancer's Finite State Machine system.
Summary
- This example doesn't teach any new animation concepts.
- It explains why you should split complex logic into multiple scripts and demonstrates how you can do so.
- It uses Animancer's Finite State Machine system, but the general concepts can be utilised in practically any system.
Controls
- W = Move.
- Left Click = Shoot.
Introduction
This example uses six scripts to achieve the same thing that BasicCharacterAnimations
did in one script. Click here if you're wondering why that might be a good idea.
The BasicCharacterAnimations
script has a lot of responsibilities squeezed into it:
- Reference all the character's animations.
- Set the Action animation to go to the Idle animation when it ends.
- Keep track of the character's current animation.
- Start with the Idle animation.
- When the character is not performing an action, allow them to Idle, Move, or perform an Action.
- When the character is performing an action, only allow them to restart the Action.
- Detect when the user Left Clicks and perform an Action if allowed.
- Detect when the user is holding the
W
key and play the Move animation if allowed.
Doing so many things in one script violates the Single Responsibility Principle. It's not too bad for a very simple situation like these examples, but game characters are often far more complex than that and can benefit greatly from having a properly designed code structure:
- It makes it easier to accomodate changes throughout the development of a project as you add new features and try different things without breaking old code and needing to waste time fixing it.
- It can potentially allow you to reuse scripts in multiple different situations throughout a project and in future projects.
- It allows bugs to be found and fixed more easily, without breaking other code.
Sharing Logic
A mistake commonly made by inexperienced developers is to write classes with names like Player
and Enemy
which end up having a significant amount of identical code. This is not always the case, but in a lot of games the enemies follow the same general structure and rules as the player:
Player | Enemy |
---|---|
Has a model or sprite that can be animated. | Has a model or sprite that can be animated. |
Has a model or sprite that can be animated. | Has a model or sprite that can be animated. |
Has a health pool and can take damage. | Has a health pool and can take damage. |
Has a Rigidbody or CharacterController for movement. |
Has a Rigidbody or CharacterController for movement. |
Has a system for managing the actions it can perform. | Has a system for managing the actions it can perform. |
Is controlled by user input from a controller/keyboard/mouse/etc. | Is controlled by some form of AI. |
Since there are so many similarities between them, it makes sense to structure their scripts in a way that allows them to share much of the same logic. Sharing logic is generally done by splitting it into multiple scripts and using Inheritance and/or Composition to connect them. This also helps adhere to the Single Responsibility Principle which aims to make code easier to reuse in different situations, easier to change throughout the development of a project, and easier to fix when bugs are found.
Overview
The general code structure in this example has been used effectively in a wide range of different situations (both with and without Animancer) but like all design patterns it can't be perfect for everything so you should always feel free to add your own variations or use a different approach entirely depending on the needs of your situation.
Here's an overview of the main parts which make up a character in this system:
Script | Description |
---|---|
Character | A centralised group of references to the common parts of a character and their state machine. |
State Machine | Determines what the character is currently doing and enforces the entry/exit rules defined by individual States. |
States | Determine what the character could do and what actually happens when they do it. |
Brain | Decides what the character wants to do (as a result of player input, AI, etc.). |
The code structure is much more complicated than the Basic Character example, but by the end of this example you should understand how it all fits together:
Character
The Character
class is responsible for holding references to other systems so they can all be easily referenced in one place.
The Character
is the core of any character, whether it be a player, enemy, NPC, person, animal, monster, or robot. A different term may be more suitable depending on the type of game you are making - for example, a strategy game might call it a Unit
while a space game might call it a Ship
- but the idea is always the same: all of the game's characters share the same standard script which holds references to each of its other components (and typically very little logic of its own). It references things like:
- The animation system (an
AnimancerComponent
if you use Animancer, but otherwise it might just be anAnimator
). - A state machine (this example uses Animancer's Finite State Machine system, but you can use any system you like).
- Any other common features that most characters have such as a
Rigidbody
, character attributes, inventory, health pool, and so on.
Having this central script means that other scripts can simply have one reference to the Character
and access all their other components through it. For example, the Action State wants to play an animation and then return the character to their Idle State. So instead of needing to reference the AnimancerComponent
, StateMachine
, and idle CharacterState
, it just references the Character
so it can access the Character.Animancer
, Character.StateMachine
, and Character.Idle
.
With that said, don't fall into the trap of thinking everything is a character. Otherwise, you might end up in a situation like Fallout 3 where the easiest way for them to implement a moving train was to make it as a character's hat and hide the character underground.
It references the AnimancerComponent
:
public sealed class Character : MonoBehaviour
{
[SerializeField]
private AnimancerComponent _Animancer;
And has an accessor Property so other scripts can use that reference but not re-assign it:
public AnimancerComponent Animancer => _Animancer;
The other fields aren't used in this example but are properly explained in later examples which reuse the scripts from this one:
Code | Example |
---|---|
|
Interruptions |
|
Brains |
|
Weapons |
In a real project, this is where you would also put references to things like a Rigidbody
or CharacterController
, a script that detects whether the character is currently on the ground, character stats, an inventory system, etc.
State Machine
The Character
class also holds the StateMachine
that will manage their actions. It uses the Serialized Field pattern explained on the Initialization page:
[DefaultExecutionOrder(-10000)]
public sealed class Character : MonoBehaviour
{
...
[SerializeField]
private CharacterState.StateMachine _StateMachine;
public CharacterState.StateMachine StateMachine => _StateMachine;
private void Awake()
{
StateMachine.InitializeAfterDeserialize();
}
}
The CharacterState
class is explained properly below, but for now we're just interested in its nested StateMachine
class which is using a State Machine with a Default State:
class CharacterState
{
[System.Serializable]
public class StateMachine : StateMachine<CharacterState>.WithDefault { }
...
}
Why is that StateMachine
class inside CharacterState
instead of Character
?
Two reasons:
- The generic parameter is
CharacterState
so it makes sense for it to be explicitly associated with that class every time you access it. Character
already has a property calledStateMachine
so the class would need a different name likeCharacterStateMachine
.
If those reasons seem very minor to you, that's because they are indeed very minor. Nothing would really change if you wanted to put it in the Character
class or not have it nested at all. It simply comes down to your personal preferences.
States
The base State Type in this example is CharacterState
.
It could Inherit from MonoBehaviour
and implement IState
, but it's easier to just Inherit from StateBehaviour
:
public abstract class CharacterState : StateBehaviour
{
The nested StateMachine
class was already mentioned above:
[System.Serializable]
public class StateMachine : StateMachine<CharacterState>.WithDefault { }
Every state has a reference to the Character
it controls:
[SerializeField]
private Character _Character;
public Character Character => _Character;
And in order to avoid needing the user to manually assign the Character
for every state, it has an OnValidate
method which uses the Extension Method Approach to automatically find an appropriate reference nearby:
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
gameObject.GetComponentInParentOrChildren(ref _Character);
}
#endif
That method needs to be in a #if UNITY_EDITOR
block because the base method it overrides in StateBehaviour
is also in a #if UNITY_EDITOR
block.
There are also several more properties to determine which states can interrupt each other. They are explained properly in the Interruptions example:
public virtual CharacterStatePriority Priority => CharacterStatePriority.Low;
public virtual bool CanInterruptSelf => false;
public override bool CanExitState
{
get
{
// There are several different ways of accessing the state change details:
// var nextState = StateChange<CharacterState>.NextState;
// var nextState = this.GetNextState();
var nextState = _Character.StateMachine.NextState;
if (nextState == this)
return CanInterruptSelf;
else if (Priority == CharacterStatePriority.Low)
return true;
else
return nextState.Priority > Priority;
}
}
}
This example has 3 state types which inherit from that base:
Idle State
The Idle state is very simple, just a Transition which it plays when the state is entered:
public sealed class IdleState : CharacterState
{
[SerializeField] private ClipTransition _Animation;
private void OnEnable()
{
Character.Animancer.Play(_Animation);
}
}
CharacterState
inherits from StateBehaviour
which means it will automatically disable itself by default and then enable itself when the state machine enters that state. So the OnEnable
method will effectively do the same as if we had used public override void OnEnterState()
.
Move State
There is no Move state script in this example because IdleState
already does exactly what it needs, so the character simply has a second IdleState
component with a different animation assigned.
The Brains example implements a proper MoveState
to allow the character to actually move around the scene, including walking and running, as well as turning.
Action State
The Action state starts the same as the others but has a bit more complexity below:
public sealed class ActionState : CharacterState
{
[SerializeField] private ClipTransition _Animation;
private void OnEnable()
{
Character.Animancer.Play(_Animation);
}
This example repeats that code for the sake of simplicity, but in a real project it's often a good idea to avoid duplicating code. Click here for some ideas on how that could be done.
If all of your states are going to want a single ClipTransition
for their animation then you could just put that field and OnEnable
method in the base CharacterState
class.
- If you do that, you absolutely must change the method from
private
toprotected
to avoid accidental bugs. - Giving the method the
virtual
Keyword is also generally a good idea in case a child class wants tooverride
it. - For example, if you put that
private
OnEnable
method in the baseCharacterState
and also wantedActionState
to have its ownOnEnable
method, Unity would only call the method in the child class (ActionState
) so it wouldn't run the base method which is supposed to play the animation. Making the methodprotected
means that adding a method with the same name in a child class will show a compile warning and making itvirtual
will mean the child class canoverride
it to have its own method and callbase.OnEnable();
to run the base method. - The above points apply to any MonoBehaviour event message in a non-
sealed
class. They should all beprotected virtual
in most cases.
That said, it's more common to have at least some states with other animation requirements such as a locomotion state using a Mixer State to blend between Idle, Walk, and Run animations. In that case, it would make sense to rename IdleState
to something like SimpleAnimatedState
and use it for anything with a single animation while still retaining the ability to have more complex states Inherit directly from CharacterState
.
And if you're only going to have a few states repeating a little bit of code then maybe there's no point in worrying about designing a fancy reusable architecture. It's something to consider on a case by case basis.
Unlike Idle and Move, this animation isn't looping so we want to return to Idle when it ends. The easiest way to do that is to set its End Event to return to its Default State:
private void Awake()
{
_Animation.Events.OnEnd = Character.StateMachine.ForceSetDefaultState;
}
It overrides the properties that control how it can be interrupted (which will be explained properly in the Interruptions example):
Priority
returnsMedium
so that it can interrupt theIdleState
but not be interrupted by it (becauseIdleState
uses the defaultLow
priority from the baseCharacterState
).CanInterruptSelf
returnstrue
so that the character can still rapid fire like they could in the Basic Character example.
public override CharacterStatePriority Priority => CharacterStatePriority.Medium;
public override bool CanInterruptSelf => true;
}
Brain
A character's brain is responsible for deciding what they want to do. The Brains example explains why the system in general is designed the way it is, so this section only focusses on what this specific script does.
There are two things this character can choose to do: Move and perform an Action. So the brain has a Character
to control and fields for each of those actions:
public sealed class BasicCharacterBrain : MonoBehaviour
{
[SerializeField] private Character _Character;
[SerializeField] private CharacterState _Move;
[SerializeField] private CharacterState _Action;
Then it has an Update
method which is similar to the Update
method in the Basic Character example, but this one is simpler because the responsibility of determining what the character can do at any given time is already handled by the State Machine:
private void Update()
{
UpdateMovement();
UpdateAction();
}
The UpdateMovement
and UpdateAction
methods are also very similar to the Methods in the Basic Character example and it avoids the extra complexity added in the Method Changes section because instead of directly telling Animancer to play animations it tells the character to try to enter states and leaves it up to those states to determine what actually happens:
private void UpdateMovement()
{
float forward = ExampleInput.WASD.y;
if (forward > 0)
{
_Character.StateMachine.TrySetState(_Move);
}
else
{
_Character.StateMachine.TrySetDefaultState();
}
}
private void UpdateAction()
{
if (ExampleInput.LeftMouseUp)
_Character.StateMachine.TryResetState(_Action);
}
}
There are several different methods of Changing States used there:
Method | Effect | Why use it here? |
---|---|---|
TrySetState |
Will do nothing if the character was already in the target state. | Because we call it every frame while the movement button is held and there's no point in repeatedly entering the Move state. |
TryResetState |
Will re-enter the target state if the character was already in it. | Because we want to allow Rapid Fire if the user clicks again while they were already performing the action. |
TrySetDefaultState |
Calls TrySetState with the Default State. |
Because the brain doesn't have a reference to the character's Idle state (it could be given one, but this way it doesn't need one). |
Scene Setup
Since we put all that effort into logically separating the character's scripts, it's worthwhile to logically group their scene objects as well to avoid ending up with a long list of components on a single object in the Inspector:
The Root Object has the Character
and IdleState
. In a real project, other components referenced by the Character
such as a Rigidbody
would also go here.
The Model has the Animator
and AnimancerComponent
. This allows the character's visuals to be rotated independently from the root character's physics or swapped out for something else. While setting up character prefabs this can allow you to simply make a copy of an existing one and replace its model while keeping the rest intact.
The Brain and Actions has the BasicCharacterBrain
, MoveState
, and ActionState
on the same object because the brain is what controls those actions. Note that the ActionState
's Animation
has its Start Time
ticked so that every time it is told to play it will set the time to 0 instead of letting it continue from where it was.
There are plenty of other ways the character could be structured. Some approaches are better than others, but in the end it mostly comes down to personal preference.
You could put each state onto its own GameObject
, but that's a lot of performance overhead to have an additional GameObject
and Transform
for every state.
You might also be tempted to put the Brain on a separate object from the Actions, but that can be inconvenient because when you drag and drop the Actions object from the Hierarchy into a CharacterState
field on the Brain Unity will simply assign the first CharacterState
component instead of letting you pick which one you want to assign.
- Inspector Gadgets Pro solves this by showing a menu to let you pick which component you want.
- Otherwise, it is possible to use the following workaround but it's not very convenient:
- Right Click on the header tab of any Unity window (such as the Inspector) to open the context menu.
- Use the Add Tab menu to open another Inspector window.
- Select the Brain object.
- Click the Lock icon in the top right of one of the Inspector windows.
- Select the Actions object.
- Now the locked Inspector will be showing the Brain and the un-locked one will be showing the Actions so you can drag a specific state component into the field you want on the brain component.
With all that set up, now you can enter Play Mode to see the character in action:
What Next?
Example | Topic |
---|---|
Interruptions | Ways of controlling which state changes are allowed and which aren't. |
Brains | A deeper look into the concept of character brains. |
Weapons | Equipping and attacking with various weapons that each have different animations. |
Platformer Game Kit | A separate (free) package which demonstrates a much more complete character implementation for a 2D platformer game. |
3D Game Kit | A more complex character framework based on Unity's 3D Game Kit Lite. |