Lightweight Context State Machine for Unity
- Supports condition transitions
- Can be used for any entity
- Awesome performance
Add files in your Unity project.
TInitializer
is the class to which the states will belong
public readonly StateMachine<Sample> StateMachine = new StateMachine<Sample>();
public StateMachine<Sample> StateMachine { get; } = new StateMachine<Sample>();
Create some states for an entity. In states you can override methods you need: OnEnter()
, OnRun()
, OnExit()
.
Method | Info |
---|---|
OnEnter() |
Called once upon entering the state |
OnRun() |
Execution is determined by update methods |
OnExit() |
Called once when exiting the state |
For example, let's create two states:
AwaitingState
is necessary to wait for the entity of some condition
You also need to pass an TInitializer
to the base State constructor. In this case, the initializer is the Sample
public class AwaitingState : State<Sample>
{
private IFollower Follower { get; }
public AwaitingState(IFollower follower, Sample sample) : base(sample)
{
Follower = follower;
}
public override void OnEnter()
{
Follower.StopFollow();
}
}
FollowingState
is necessary for the entity to follow some target
public class FollowingState : State<Sample>
{
private IFollower Follower { get; }
private Transform Target { get; }
public FollowingState(IFollower follower, Transform target, Sample sample) : base(sample)
{
Follower = follower;
Target = target;
}
public override void OnRun()
{
Follower.Follow(Target);
}
}
[RequireComponent(typeof(IFollower))]
public class Sample : MonoBehaviour
{
[SerializeField] private Health health;
public StateMachine<Sample> StateMachine { get; } = new StateMachine<Sample>();
public Transform Target { get; set; }
private AwaitingState _awaitingState;
private FollowingState _followingState;
private void Awake()
{
InstallStates();
}
private void InstallStates()
{
var follower = GetComponent<IFollower>();
_awaitingState = new AwaitingState(follower, this);
_followingState = new FollowingState(follower, Target, this);
}
}
You can add states with a method or a constructor
StateMachine.AddStates(_awaitingState, _followingState);
StateMachine = new StateMachine<Sample>(_awaitingState, _followingState);
You can only add states to the StateMachine once!
We have methods for binding transitions such as AddTransition
and AddAnyTransition
.
Method | Info |
---|---|
AddTransition<TStateFrom, TStateTo>(Func<bool> condition) |
Takes two states as arguments, from which state we want to go to the second state and the transition condition |
AddAnyTransition<TStateTo>(Func<bool> condition) |
Takes as arguments the one state we want to switch to and the transition condition |
Let's imagine a situation:
We want to switch from the AwatingState
to the FollowingState
if a target is found. We also want to switch back to the AwaitingState
if the target is lost. AddTransition
method will help us with this, let's add two transitions:
From AwaitingState
to FollowingState
StateMachine.AddTransition<AwaitingState, FollowingState>(condition: () => Target != null);
From FollowingState
to AwaitingState
StateMachine.AddTransition<FollowingState, AwaitingState>(condition: () => Target == null);
We will also add a transition to the AwaitingState
from any state.
Why might this be needed? If the entity is not alive, then it is logical that it will not be able to move and will have to switch to the idle state.
Add transition from any state to idle AwaitingState
StateMachine.AddAnyTransition<AwaitingState>(condition: () => health.IsAlive == false);
For the launch of StateMachine
you need to set first state and call the Run()
method in Update()
:
private void Start()
{
StateMachine.SetState<AwaitingState>();
}
private void Update()
{
StateMachine.Run();
}
using NTC.ContextStateMachine;
using UnityEngine;
[RequireComponent(typeof(IFollower))]
public class Sample : MonoBehaviour
{
[SerializeField] private Health health;
public StateMachine<Sample> StateMachine { get; } = new StateMachine<Sample>();
public Transform Target { get; set; }
private AwaitingState _awaitingState;
private FollowingState _followingState;
private void Awake()
{
InstallStates();
BindTransitions();
BindAnyTransitions();
StateMachine.SetState<AwaitingState>();
}
private void InstallStates()
{
var follower = GetComponent<IFollower>();
_awaitingState = new AwaitingState(follower, this);
_followingState = new FollowingState(follower, Target, this);
StateMachine.AddStates(_awaitingState, _followingState);
}
private void BindTransitions()
{
StateMachine.AddTransition<AwaitingState, FollowingState>(condition: () => Target != null);
StateMachine.AddTransition<FollowingState, AwaitingState>(condition: () => Target == null);
}
private void BindAnyTransitions()
{
StateMachine.AddAnyTransition<AwaitingState>(condition: () => health.IsAlive == false);
}
private void Update()
{
StateMachine.Run();
}
}
If you want to set states manually, you can disable TransitionsEnabled
in the StateMachine
:
StateMachine.TransitionsEnabled = false;
Then you can change state of StateMachine
by method SetState<TState>()
:
StateMachine.SetState<TState>();
Also if you don't want the StateMachine
to choose the state in the update method, you can use the method SetStateByTransitions()
when you need:
StateMachine.SetStateByTransitions();