FiniteStateMachine<T>¶
A simple Finite State Machine with states as objects inspired by Prime31’s excellent StateKit.
If your state classes have an empty constructor, the state machine can create the states automatically when needed (using ChangeStateAuto<T>()). If not you should create the state instance yourself and register the state in the machine.
Whether or not a null state is valid is up to your design.
Todo
Write how to use FiniteStateMachine<T>
Examples¶
Simple machine without automatic state creation¶
Let’s create some oversimplified AI for an enemy that should chase the player down and attack him once he’s within range.
public class IdleState : FsmState<Enemy>
{
public override void Begin()
{
base.Begin();
// start idle animation
}
public override void Reason()
{
base.Reason();
if (Context.Target != null)
{
// we've gained a target, let's chase him down!
Machine.ChangeState<ChaseState>();
}
}
}
public class ChaseState : FsmState<Enemy>
{
public override void Begin()
{
base.Begin();
// start running animation
}
public override void Reason()
{
base.Reason();
if (Context.Target == null)
{
// we lost our target, return to idle state
Machine.ChangeState<IdleState>();
}
else if (Context.GetDistanceToTarget() < Context.AttackRange)
{
// we're within range to attack!
Machine.ChangeState<AttackState>();
}
}
public override void Act(float deltaTime)
{
base.Act(deltaTime);
// move towards the target player
Context.Move(dirToTarget);
}
}
public class AttackState : FsmState<Enemy>
{
public override void Begin()
{
base.Begin();
// start attack animation
}
public override void Reason()
{
base.Reason();
if (Context.Target == null)
{
// target is lost, return to normal
Machine.ChangeState<IdleState>();
}
else if (Context.GetDistanceToTarget() > Context.AttackRange)
{
// the player is getting away! return to chasing him
Machine.ChangeState<ChaseState>();
}
}
public override void Act(float deltaTime)
{
base.Act(deltaTime);
// damage target
}
}
public class Enemy : MonoBehaviour
{
public Player Target { get; set; }
FiniteStateMachine<Enemy> _stateMachine;
void Awake()
{
_stateMachine = new FiniteStateMachine<Enemy>(this, new IdleState());
_stateMachine.RegisterState(new ChaseState());
_stateMachine.RegisterState(new AttackState());
}
void Update()
{
// look for the player and set the Target property
// update the active state
_stateMachine.Update(Time.deltaTime);
}
float GetDistanceToTarget()
{
// return distance
}
}
Simple machine with automatic state creation¶
Same code as before, but with two small differences:
- We don’t register the states after creating the machine
- We use ChangeStateAuto<T>()
public class IdleState : FsmState<Enemy>
{
public override void Begin()
{
base.Begin();
// start idle animation
}
public override void Reason()
{
base.Reason();
if (Context.Target != null)
{
// we've gained a target, let's chase him down!
Machine.ChangeStateAuto<ChaseState>();
}
}
}
public class ChaseState : FsmState<Enemy>
{
public override void Begin()
{
base.Begin();
// start running animation
}
public override void Reason()
{
base.Reason();
if (Context.Target == null)
{
// we lost our target, return to idle state
Machine.ChangeStateAuto<IdleState>();
}
else if (Context.GetDistanceToTarget() < Context.AttackRange)
{
// we're within range to attack!
Machine.ChangeStateAuto<AttackState>();
}
}
public override void Act(float deltaTime)
{
base.Act(deltaTime);
// move towards the target player
Context.Move(dirToTarget);
}
}
public class AttackState : FsmState<Enemy>
{
public override void Begin()
{
base.Begin();
// start attack animation
}
public override void Reason()
{
base.Reason();
if (Context.Target == null)
{
// target is lost, return to normal
Machine.ChangeStateAuto<IdleState>();
}
else if (Context.GetDistanceToTarget() > Context.AttackRange)
{
// the player is getting away! return to chasing him
Machine.ChangeStateAuto<ChaseState>();
}
}
public override void Act(float deltaTime)
{
base.Act(deltaTime);
// damage target
}
}
public class Enemy : MonoBehaviour
{
public Player Target { get; set; }
FiniteStateMachine<Enemy> _stateMachine;
void Awake()
{
_stateMachine = new FiniteStateMachine<Enemy>(this, new IdleState());
}
void Update()
{
// look for the player and set the Target property
// update the active state
_stateMachine.Update(Time.deltaTime);
}
float GetDistanceToTarget()
{
// return distance
}
}