A fast, tried-and-tested hierarchical finite state machine library for Unity, designed to be easy to use yet powerful without compromising performance.
A fast, tried-and-tested hierarchical finite state machine library for Unity, designed to be easy to use yet powerful without compromising performance.
A simple yet powerful hierarchical finite state machine for the Unity game engine. It is scalable and customisable by being class-based, but also supports functions (lambdas) for rapid prototyping.
Thanks to overloading, it minimises boilerplate code while still supporting generics.
It has a special focus on the temporal aspects of state transitions, making it ideal for timing and synchronisation sensitive problems.
State machines are an amazingly easy way to write and organise game logic. (As soon as you have states, e.g. Walk, Run, Sneak, Jump, you can use a state machine)
It's easy and straightforward to use
It helps you tame complex behaviour (e.g. weapon handling -> cooling, ammo, reload, fire) or write AI for NPCs
Helps you write self documenting code, that is maintainable and readable
Reduces the boilerplate code required to write a state machine
UnityHFSM is lightweight and efficient, making it perfect for solving small and big problems
It is especially designed for Unity, and supports coroutines, which would otherwise be difficult to integrate in a state machine
No GC Allocations for state changes / updates / ... after setting up the state machine (-> No unwanted GC related lag spikes because of the state machine)
The code is well documented
It is not a component (MonoBehaviour) and therefore has a low overhead
By being hierarchical, it can reduce the amount of duplicate code between states
In the wiki:
Table of contents:
To get started, download the latest version of UnityHFSM from the Releases page. Simply extract the zip file and put the folder anywhere in your Assets folder. Et voilà, you're ready to go!
To add OpenUPM to your project:
Open Edit/Project Settings/Package Manager
Add a new Scoped Registry:
Name: OpenUPM
URL: https://package.openupm.com/
Scope(s): com.inspiaaa.unityhfsmClick Save
Open Package Manager
Select My Registries in dropdown top left
Select UnityHFSM and click Install
You can also add it directly from GitHub on Unity 2019.4+. Note that you won't be able to receive updates through Package Manager this way, you'll have to update manually.
https://github.com/Inspiaaa/UnityHFSM.git#upm for the latest stable release (recommended)https://github.com/Inspiaaa/UnityHFSM.git#release for the development versionhttps://github.com/Inspiaaa/UnityHFSM.git#v1.8.0 for a specific version (v1.8.0 here).csproj project files (Edit > Preferences > External Tools > Regenerate project files)Here's a simple state machine for an enemy spy in a game.
As you can see, the enemy will try to stay outside of the player's viewing range while extracting intel. When the player goes too far away, it will follow the player again.
Create the state machine:
fsm = new StateMachine();Add states: Each state is represented by a StateBase object. You can either use one of the built-in state classes (e.g. the State class) to define the state's logic or write a custom class that inherits from StateBase.
fsm.AddState(
name,
new State(
onEnter,
onLogic,
onExit
)
);Add transitions: Transitions can be defined using TransitionBase objects. They determine when and under which conditions the state machine should switch to another state. As with the states, the simplest option is to use one of the builtin transition types (e.g. the Transition class). If you instead choose to create a custom transition class by inheriting from TransitionBase, see the class-based architecture section for more information.
fsm.AddTransition( new Transition(
from,
to,
condition
));Initialize the state machine
fsm.SetStartState(id);
fsm.Init();Run the state machine:
void Update() {
fsm.OnLogic()
}using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityHFSM; // Import the required classes for the state machine.
public class EnemyController : MonoBehaviour
{
private StateMachine fsm;
public float playerScanningRange = 4f;
public float ownScanningRange = 6f;
void Start()
{
fsm = new StateMachine();
}
}To be able to define the states' logic concisely, we'll need to create some helper methods and properties first. Their implementation is just an example and may differ for your scene setup.
Vector2 PlayerPosition => PlayerController.Instance.transform.position;
float DistanceToPlayer => Vector2.Distance(PlayerPosition, transform.position);
void MoveTowardsPlayer(float speed) {
transform.position = Vector2.MoveTowards(
transform.position,
PlayerPosition,
speed * Time.deltaTime
);
}Next, we can define the states in the Start method:
void Start()
{
fsm = new StateMachine();
// Empty state without any logic.
fsm.AddState("ExtractIntel");
fsm.AddState("FollowPlayer", new State(
onLogic: state => MoveTowardsPlayer(1)
));
fsm.AddState("FleeFromPlayer", new State(
onLogic: state => MoveTowardsPlayer(-1)
));
// This configures the entry point of the state machine.
fsm.SetStartState("FollowPlayer");
}Notice the fsm.AddState("ExtractIntel") call. Interestingly, we don't need to pass in a state object here. This is due to one of the many "shortcut" methods that UnityHFSM provides. These are meant to reduce the amount of boilerplate code required for common operations, such as adding blank states. The above line is equivalent to writing:
fsm.AddState("ExtractIntel", new StateBase<string>(needsExitTime: false));Furthermore, we can actually shorten the other state definitions even more by using shortcut methods. We can for example write the FollowPlayer state in a more concise way:
fsm.AddState("FollowPlayer", onLogic: state => MoveTowardsPlayer(1));Although this example is using lambda expressions for the states' logic, you can of course also just pass normal functions.
Side note: To keep things simple, we're using strings for the state identifiers. Just keep in mind that UnityHFSM is not limited to this, as it allows you to use any custom type (e.g. enums) for the state identifiers. See the generics chapter for more information.
…Although the above code snippet is definitely functional, you may notice that it seems a bit overly verbose and repetitive. That's because it is. We can do better!
Firstly, as creating transitions with the Transition class is so common, there are shortcut methods to minimise the amount of boilerplate code. The first transition is equivalent to writing:
fsm.AddTransition("ExtractIntel", "FollowPlayer",
transition => DistanceToPlayer > ownScanningRange);Secondly, it is a common pattern to define a transition in one direction when a condition is true, and in the other direction when it is false. UnityHFSM provides a special feature to implement this in code: two way transitions. Using a combination of this feature and shortcut methods we can reduce the original code snippet to the following:
fsm.AddTwoWayTransition("ExtractIntel", "FollowPlayer",
transition => DistanceToPlayer > ownScanningRange);
fsm.AddTwoWayTransition("ExtractIntel", "FleeFromPlayer",
transition => DistanceToPlayer < playerScanningRange);void Start()
{
// ...
// Initializes the state machine and must be called before OnLogic()
// is called.
fsm.Init();
}
void Update()
{
fsm.OnLogic();
}At this point, the basic version of the enemy controller is finished and ready to use :D
By the way, using all of the aforementioned tricks, the entire definition of the state machine can be reduced to remarkably few lines of code:
void Start()
{
fsm = new StateMachine();
fsm.AddState("ExtractIntel");
fsm.AddState("FollowPlayer", onLogic: state => MoveTowardsPlayer(1));
fsm.AddState("FleeFromPlayer", onLogic: state => MoveTowardsPlayer(-1));
fsm.SetStartState("FollowPlayer");
fsm.AddTwoWayTransition("ExtractIntel", "FollowPlayer",
transition => DistanceToPlayer > ownScanningRange);
fsm.AddTwoWayTransition("ExtractIntel", "FleeFromPlayer",
transition => DistanceToPlayer < playerScanningRange);
fsm.Init();
}When dealing with more complex behaviour, it is natural to break the problem down into sub-problems. This approach maps beautifully to state machines. First you define the states and then in a later step you can add the logic and behaviour to them. When designing a state, it can sometimes be useful to draw another state diagram just for it and to think about how you can break down its behaviour. The result of this is a hierarchy of states - a hierarchical state machine.
How can we implement this in UnityHFSM?
Because the StateMachine class inherits from StateBase, it can be treated as a normal state. This allows for the nesting of state machines together with states. On top of this, UnityHFSM provides additional features to describe the timing / exit conditions for a state machine (more on this later).
In the previous example, we left the Extract Intel state empty. Let's change this. When extracting intel, the spy should first collect data and then send it, repeating the process once completed.
So that you can see a visual difference, let's make it that the enemy spins when it enters the Send Data state, like it's sending the data out in all directions.
Create a separate state machine for the nested states (States in Extract Intel)
Add the nested states to the new state
No open issues yet, or sync has not completed.