Normally when I have an enemy that I need to write AI for, I’ll use a finite state machine, like the one shown below:
public class SomeEnemy : Monobehaviour
{
public enum States {searchingForPlayer, chasingPlayer, attacking, recovering};
private States currentState = States.searchingForPlayer;
void Update()
{
FiniteStateMachine();
}
//Finite state machine-related functions
private void FiniteStateMachine()
{
switch (currentState)
{
case States.searchingForPlayer:
WhileSearchingForPlayer();
break;
case States.chasingPlayer:
WhileChasingPlayer();
break;
case States.attacking:
WhileAttacking();
break;
case States.recovering:
WhileRecovering();
break;
}
}
private void WhileSearchingForPlayer()
{
//Insert code here that checks to see if a player is in range
if (playerIsInRage)
{
currentState = States.chasing;
}
}
private void WhileChasingPlayer()
{
//Insert code here that checks to see if the player is within attacking distance
if (caughtUpToPlayer)
{
currentState = States.attacking;
}
}
private void WhileAttacking()
{
//Attacking animation here
if (animationIsOver)
{
currentState = States.recovering;
}
}
private void WhileRecovering()
{
//Insert code here that waits for a while and then goes back to the searching state
if (doneRecovering)
{
currentState = States.searchingForPlayer;
}
}
}
In most cases, this works really well. It helps me turn big problems into smaller, more manageable problems. Unfortunately, is has an obvious downside: it ends up making the code REALLY long. It’s still pretty manageable when I only have a few states and only make use of the normal Update() method.
But recently, I’ve run into a problem: I have an object that requires me to use all three of the update methods for each state. This means that when I have to add a new state, I need to create not one, but THREE methods devoted to this one state: one for Update(), one for FixedUpdate(), and one for LateUpdate(). I then have to remember to add each of those methods to TRHEE different switch-statements. With one or two states, I can manage it. But when I need to add four or more states, it becomes really messy really quickly.
It would turn the previous example into one looking like this:
public class SomeEnemy : Monobehaviour
{
public enum States {searchingForPlayer, chasingPlayer, attacking, recovering};
private States currentState = States.searchingForPlayer;
void Update()
{
FiniteStateMachine_NormalUpdate();
}
void FixedUpate()
{
FiniteStateMachine_FixedUpadate();
}
void LateUpdate()
{
FiniteStateMachine_LateUpdate();
}
//Finite state machine-related functions
private void FiniteStateMachine_NormalUpdate()
{
switch (currentState)
{
case States.searchingForPlayer:
WhileSearchingForPlayer_NormalUpdate();
break;
case States.chasingPlayer:
WhileChasingPlayer_NormalUpdate();
break;
case States.attacking:
WhileAttacking_NormalUpdate();
break;
case States.recovering:
WhileRecovering_NormalUpdate();
break;
}
}
private void FiniteStateMachine_FixedUpdate()
{
switch (currentState)
{
case States.searchingForPlayer:
WhileSearchingForPlayer_FixedUpdate();
break;
case States.chasingPlayer:
WhileChasingPlayer_FixedUpdate();
break;
case States.attacking:
WhileAttacking_FixedUpdate();
break;
case States.recovering:
WhileRecovering_FixedUpdate();
break;
}
}
private void FiniteStateMachine_LateUpdate()
{
switch (currentState)
{
case States.searchingForPlayer:
WhileSearchingForPlayer_LateUpdate();
break;
case States.chasingPlayer:
WhileChasingPlayer_LateUpdate();
break;
case States.attacking:
WhileAttacking_LateUpdate();
break;
case States.recovering:
WhileRecovering_LateUpdate();
break;
}
}
private void WhileSearchingForPlayer_NormalUpdate()
{
//Code
}
private void WhileSearchingForPlayer_FixedUpdate()
{
//Code
}
private void WhileSearchingForPlayer_LateUpdate()
{
//Code
}
private void WhileChasingPlayer_NormalUpdate()
{
//Code
}
private void WhileChasingPlayer_FixedUpdate()
{
//Code
}
private void WhileChasingPlayer_LateUpdate()
{
//Code
}
//And so on and so forth
}
As you can see, it very quickly becomes an ungodly amount of code for me to keep updated and organized.
So here’s my question: What is a smarter way for me to do this? Is there some clever way I can have a finite state machine that allows me to put different code in all three update methods for each state, without needing to write tons and tons of short methods? Is there any way I can keep all of the code for my states separated and organized?