Skip to content

Instantly share code, notes, and snippets.

@andrew-raphael-lukasik
Last active April 13, 2024 14:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andrew-raphael-lukasik/e340d8d8b8ef926cbf2b6d15380aca17 to your computer and use it in GitHub Desktop.
Save andrew-raphael-lukasik/e340d8d8b8ef926cbf2b6d15380aca17 to your computer and use it in GitHub Desktop.
Goal: to write the most simple yet most useful FSM that convinces Unity devs who still use bool fields to keep track of ai states to change their misguided ways for the better.

The rules here are:

Dos:

  • Use OnStateTick to switch between states using ChangeState(EState.___); and execute typical FixedUpdate/Update code in relevant if(state == EState.___){ scope here}.
  • Use OnStateEnter to changing values (like moving speed etc.) and executing code once (switching animations, triggering animator states)
  • Use OnStateExit for resetting values set in OnStateEnter back to their defaults (like moving speed back to normal etc.).

Don'ts:

  • Do not change _state manually (i.e. using _state = EState.___). This is what ChangeState(_) is for.
  • Do not call ChangeState from OnStateEnter and `OnStateExit - this will cause errors (!) and makes no sense if you think about it.
using UnityEngine;
public class MinimumFsmBasedAiController : MonoBehaviour
{
public enum EState : byte
{
START = 0,
PATROL,
INVESTIGATE,
ATTACK,
TAKE_COVER,
DEAD,
}
EState _state = EState.START;
float _stateChangeTime;
void FixedUpdate ()
{
OnStateTick(_state, Time.time - _stateChangeTime);
}
/// <summary>Call this to change state.</summary>
/// <remarks>Do not modify value _state outside this.</summaremarksry>
void ChangeState ( EState nextState )
{
if( nextState==_state)
{
// do NOT comment this out, rethink the call logic to fix the damn warning
Debug.LogError($"{nameof(ChangeState)} fails as {nextState} is a current state already.");
return;
}
OnStateExit(_state);
_state = nextState;
_stateChangeTime = Time.time;
OnStateEnter(_state);
}
/// <summary>Good place to change animations, moving speed or play sounds etc.</summary>
void OnStateEnter ( EState state )
{
// state starts:
if( state==EState.START )
{
}
else if( state==EState.PATROL )
{
}
else if( state==EState.INVESTIGATE )
{
}
else if( state==EState.ATTACK )
{
}
else if( state==EState.TAKE_COVER )
{
}
else if( state==EState.DEAD )
{
}
else throw new System.NotImplementedException($"{state} not implemented");
}
/// <summary>Good place to reset values</summary>
void OnStateExit ( EState state )
{
// state ends:
if( state==EState.START )
{
}
else if( state==EState.PATROL )
{
}
else if( state==EState.INVESTIGATE )
{
}
else if( state==EState.ATTACK )
{
}
else if( state==EState.TAKE_COVER )
{
}
else if( state==EState.DEAD )
{
}
else throw new System.NotImplementedException($"{state} not implemented");
}
/// <summary>Good place to change states - always using ChangeState(_)!.</summary>
void OnStateTick ( EState state , float duration )
{
// state update:
if( state==EState.START )
{
ChangeState(EState.PATROL);
}
else if( state==EState.PATROL )
{
}
else if( state==EState.INVESTIGATE )
{
}
else if( state==EState.ATTACK )
{
}
else if( state==EState.TAKE_COVER )
{
}
else if( state==EState.DEAD )
{
}
else throw new System.NotImplementedException($"{state} not implemented");
}
#if UNITY_EDITOR
void OnDrawGizmos ()
{
if( Application.isPlaying ) UnityEditor.Handles.Label(transform.position, $"[{_state}]");
}
#endif
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment