In this tutorial, we will implement a Finite State Machine using C# Delegates in Unity. We will then demonstrate the use of this Finite State Machine (FSM) by applying it to control an NPC enemy game object in Unity.
Part 1 introduces a Finite State Machine and implements a generic Finite State Machine in C#. It uses the principles of inheritance and overloading to allow concrete implementations of application-specific States.
Part 2 uses the Finite State Machine created in part 1 and applies to a Unity project in a simple, straightforward UI implementation of a Splash Screen. This is a straightforward example implementation of the FSM created in Part 1.
Part 3 uses the same Finite State Machine and applies to a complex Unity project which handles multiple animation states of a 3D animated character. This is a slightly harder example implementation of the FSM created in Part 1.
This is Part 4 of my tutorials on Finite State Machine (FSM) in Unity. In this tutorial, we will not use inheritance to create different classes for individual States but use delegates to define the functionality of application states and instantiate the same State object. Thereafter we will demonstrate the use of the Finite State Machine by creating a simple key press based application in Unity.
In Part 5 of the tutorial, we will move further by demonstrating an enemy NPC behaviour, which handles multiple animation states of a 3D animated character, using the delegate based FSM.
What are C# Delegates?
C# delegates are a type that represents references to methods with a specific function signature. In short, delegates are references to methods that can be passed around like variables.
If you are familiar with C++, then delegates are equivalent to the function pointer concept in C++.
An instantiated delegate can be associated with any method that has a compatible signature and return type. One can then call the method through the delegate instance. C# Delegates are used to pass methods as arguments to other methods. In other words, delegates are methods that you can use as variables, like strings, integers, objects etc.
Based on C# syntax, we can create a delegate with the delegate keyword, followed by a return type and the signature of the methods that can be delegated to it. For example, we can declare a delegate method with no argument as:
public delegate void NoArgDelegate();
Code language: JavaScript (javascript)
Or a delegate with one argument (for this example, let’s use string):
public delegate void OneArgDelegate(string val);
Code language: JavaScript (javascript)
Or a delegate with two arguments (for this example, let’s use an integer as the first argument and string as the second argument):
Why Use C# Delegates?
Objects and variables can easily be sent as parameters into methods or constructors. However, methods are a bit more tricky. But every once in a while, you might feel the need to send a method as a parameter to another method, and that’s when you’ll need delegates.
By defining a delegate, you are saying to the user of your class, “Please feel free to assign any method that matches this signature to the delegate and it will be called each time my delegate is called” from Stackoverflow; delegates are useful to offer to the user of your objects some ability to customize their behaviour. Most of the time, you can use other ways to achieve the same purpose. It is just one of the easiest ways in some situations to get the thing done.
Typical use cases are events and observers—all the OnEventX delegate to the methods the user defines.
For a detailed read on C# delegates, read the tutorial on What Are C# Delegates And How To Use Them.
Finite State Machine Using C# Delegates in Unity
Please go through Part 1 of the tutorial before proceeding.
From Part 1, we have the below State class.
public class State
{
protected FSM m_fsm;
/* The constructor of the State class
* will require the parent FSM.
* So we create a constructor
* with an instance of the FSM
*/
public State(FSM fsm)
{
m_fsm = fsm;
}
/*!Virtual method for entry to the state.
* This method is called whenever this
* state is entered. Derived classes
* must implement this method and
* handle appropriately.
*/
public virtual void Enter() { }
/*!Virtual method for exit from the state.
* This method is called whenever this
* state is exited. Derived classes
* must implement this method and
* handle appropriately.
*/
public virtual void Exit() { }
/*!Virtual method that will be
* called in every Update call from Unity.
* The call will be routed via the
* FSM through the current state.
*/
public virtual void Update() { }
/*!Virtual method that will be
* called in every FixedUpdate call from
* Unity. The call will be routed via the
* FSM through the current state.
*/
public virtual void FixedUpdate() { }
}
Code language: PHP (php)
We won’t change this class as it provides the essence of any State for an FSM. We will, however, derive a new NPCState class from this base State class that will implement the delegates. This NPCState will represent all types of states for our NPC.
using Patterns;
public class NPCState : State
{
public NPCState(FSM fsm) : base(fsm)
{
}
// the delegate
public delegate void StateDelegate();
public StateDelegate OnEnterDelegate { get; set; } = null;
public StateDelegate OnExitDelegate { get; set; } = null;
public StateDelegate OnUpdateDelegate { get; set; } = null;
public StateDelegate OnFixedUpdateDelegate { get; set; } = null;
public override void Enter()
{
OnEnterDelegate?.Invoke();
}
public override void Exit()
{
OnExitDelegate?.Invoke();
}
public override void Update()
{
OnUpdateDelegate?.Invoke();
}
public override void FixedUpdate()
{
OnFixedUpdateDelegate?.Invoke();
}
}
Look at the code above. We have used one type of delegate for all 4 sections, viz., Enter, Exit, Update and FixedUpdate. These delegates are named OnEnterDelegate, OnExitDelegate, OnUpdateDelegate and OnFixedUpdateDelegate.
Do note on the calling convention of a delegate. Did you notice the? Behind the name of the delegate? This is a Null conditional check.
Null-conditional
The null-conditional operator lets you access members and elements only when the receiver is not null, returning a null result otherwise. If employees are a list, then:
int? count = employees?.Length; // count is null if employees is null
Code language: JavaScript (javascript)
The above is less equivalent to:
int? count = (employees!= null) ? (int?)employees.Length : null;
Code language: JavaScript (javascript)
Except that the employees variable is only evaluated once.
Null-conditional delegate invocation
Delegate invocation can’t immediately follow the? Operator – too many syntactic ambiguities. However, you can call it via the Invoke method on the delegate. So for our case, we instead write simply by calling the Invoke method.
OnFixedUpdateDelegate?.Invoke(); // for example
Code language: JavaScript (javascript)
In general, the calling of a delegate using the Null conditional will be
myDelegate?.Invoke(args); // where args are function arguments and myDelegate is the delegate.
Code language: JavaScript (javascript)
Testing the Finite State Machine Using C# Delegates in Unity
We will now do no-fuss testing of our Finite State Machine using C# Delegates using a simple project. For this testing, we will use Key Presses to transition from one state to another state and display the state transition using Debug.Log in Unity.
Enemy NPC States
The state diagram for our enemy NPC is given below.
Let’s start with the implementation. Create a new scene in your Unity Project. Call the scene to be FSMUsingDelegates. Create an empty game object and name it EnemyNPC. Create a Cube and add it to the EnemyNPC (to represent the NPC game object visually).
Create a new script called EnemyNPC.cs and add it as a component to the EnemyNPC.
Double click and open the EnemyNPC.cs script in your favourite IDE.
Add the various state types as an enum. For our tutorial, we will have the IDLE, CHASE, ATTACK, DAMAGE and DIE states.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyNPC : MonoBehaviour
{
public enum StateTypes
{
IDLE = 0,
CHASE,
ATTACK,
DAMAGE,
DIE
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
We now add the NPCState that we created earlier. We will amend the source code slightly for additional clarity and help.
#region States
public class NPCState : State
{
protected EnemyNPC mNPC;
private StateTypes mStateType;
public StateTypes StateType { get { return mStateType; } }
public NPCState(FSM fsm, StateTypes type, EnemyNPC npc) : base(fsm)
{
mNPC = npc;
mStateType = type;
}
public delegate void StateDelegate();
public StateDelegate OnEnterDelegate { get; set; } = null;
public StateDelegate OnExitDelegate { get; set; } = null;
public StateDelegate OnUpdateDelegate { get; set; } = null;
public StateDelegate OnFixedUpdateDelegate { get; set; } = null;
public override void Enter()
{
OnEnterDelegate?.Invoke();
}
public override void Exit()
{
OnExitDelegate?.Invoke();
}
public override void Update()
{
OnUpdateDelegate?.Invoke();
}
public override void FixedUpdate()
{
OnFixedUpdateDelegate?.Invoke();
}
}
#endregion
Note the slight amendment to the NPCState class. We have added the following to make it more friendly for our EnemyNPC class.
*******
protected EnemyNPC mNPC; // holds the reference to EnemyNPC object
private StateTypes mStateType; // the Statet type. This will be used as the key while adding to the FSM
public StateTypes StateType { get { return mStateType; } }
public NPCState(FSM fsm, StateTypes type, EnemyNPC npc) : base(fsm)
{
mNPC = npc;
mStateType = type;
}
********
Code language: PHP (php)
Now we have the FSM setup ready to be used. We will proceed with configuring our EnemyNPC game object.
************************
#region NPC Data and Parameters
public FSM mFsm;
#endregion
// Start is called before the first frame update
void Start()
{
mFsm = new FSM();
mFsm.Add((int)StateTypes.IDLE, new NPCState(mFsm, StateTypes.IDLE, this));
mFsm.Add((int)StateTypes.CHASE, new NPCState(mFsm, StateTypes.CHASE, this));
mFsm.Add((int)StateTypes.ATTACK, new NPCState(mFsm, StateTypes.ATTACK, this));
mFsm.Add((int)StateTypes.DAMAGE, new NPCState(mFsm, StateTypes.DAMAGE, this));
mFsm.Add((int)StateTypes.DIE, new NPCState(mFsm, StateTypes.DIE, this));
Init_IdleState();
Init_AttackState();
Init_DieState();
Init_DamageState();
Init_ChaseState();
mFsm.SetCurrentState(mFsm.GetState((int)StateTypes.IDLE));
}
void Update()
{
mFsm.Update();
}
void FixedUpdate()
{
mFsm.FixedUpdate();
}
#region Setup and initialize the various states.
void Init_IdleState()
{
}
void Init_AttackState()
{
}
void Init_DieState()
{
}
void Init_DamageState()
{
}
void Init_ChaseState()
{
}
#endregion
*****************
Code language: PHP (php)
The above code will only create the various state type objects and add them to the FSM. We shall now implement the various Init_* methods to set up the delegates for our different states. For this tutorial, we will do a simple key press and Debug.Log output based demonstration to test out our FMS using C# delegates.
IDLE State
We will simply add text messages into Debug.Log.
void Init_IdleState()
{
NPCState state = (NPCState)mFsm.GetState((int)StateTypes.IDLE);
// Add a text message to the delegates.
state.OnEnterDelegate += delegate ()
{
Debug.Log("OnEnter - IDLE");
};
state.OnExitDelegate += delegate ()
{
Debug.Log("OnExit - IDLE");
};
state.OnUpdateDelegate += delegate ()
{
//Debug.Log("OnUpdate - IDLE");
};
}
Code language: JavaScript (javascript)
Notice the += operator we are using for the delegates. Like this, we can add on multiple functions to our delegates. Refer to C# delegates using anonymous methods and delegate operator (C# reference) for more details.
Look at the state machine diagram above. We can see that from the IDLE state, you can go to CHASE or DAMAGE states. Let’s create two key inputs to handle these two transitions. We will use the “c” and “d” keys to handle the transitions from IDLE to CHASE and DAMAGE, respectively.
void Init_IdleState()
{
NPCState state = (NPCState)mFsm.GetState((int)StateTypes.IDLE);
// Add a text message to the OnEnter and OnExit delegates.
state.OnEnterDelegate += delegate ()
{
Debug.Log("OnEnter - IDLE");
};
state.OnExitDelegate += delegate ()
{
Debug.Log("OnExit - IDLE");
};
state.OnUpdateDelegate += delegate ()
{
//Debug.Log("OnUpdate - IDLE");
if(Input.GetKeyDown("c"))
{
SetState(StateTypes.CHASE);
}
else if(Input.GetKeyDown("d"))
{
SetState(StateTypes.DAMAGE);
}
else if (Input.GetKeyDown("a"))
{
SetState(StateTypes.ATTACK);
}
};
}
Code language: JavaScript (javascript)
We have used the method called SetState with an input parameter as the StateTypes type. This is a helper method that is implemented as follows:
// Helper function to set the state
public void SetState(StateTypes type)
{
mFsm.SetCurrentState(mFsm.GetState((int)type));
}
Code language: JavaScript (javascript)
ATTACK State
Looking at the state machine diagram above, we can see that similar to IDLE, here too from the ATTACK state; you can go to CHASE or DAMAGE states. We will reuse the “c” and “d” keys to handle the transitions from ATTACK to CHASE and DAMAGE states, respectively.
void Init_AttackState()
{
NPCState state = (NPCState)mFsm.GetState((int)StateTypes.ATTACK);
// Add a text message to the OnEnter and OnExit delegates.
state.OnEnterDelegate += delegate ()
{
Debug.Log("OnEnter - ATTACK");
};
state.OnExitDelegate += delegate ()
{
Debug.Log("OnExit - ATTACK");
};
state.OnUpdateDelegate += delegate ()
{
//Debug.Log("OnUpdate - ATTACK");
if (Input.GetKeyDown("c"))
{
SetState(StateTypes.CHASE);
}
else if (Input.GetKeyDown("d"))
{
SetState(StateTypes.DAMAGE);
}
};
}
Code language: JavaScript (javascript)
CHASE State
Looking at the state machine diagram above, we can see that from the CHASE state; you can go to either IDLE, DAMAGE or ATACK states. We will use the “i”, “d” and “a” keys to handle the transitions from CHASE to IDLE, DAMAGE and ATTACK states, respectively.
void Init_ChaseState()
{
NPCState state = (NPCState)mFsm.GetState((int)StateTypes.CHASE);
// Add a text message to the OnEnter and OnExit delegates.
state.OnEnterDelegate += delegate ()
{
Debug.Log("OnEnter - CHASE");
};
state.OnExitDelegate += delegate ()
{
Debug.Log("OnExit - CHASE");
};
state.OnUpdateDelegate += delegate ()
{
//Debug.Log("OnUpdate - CHASE");
if (Input.GetKeyDown("i"))
{
SetState(StateTypes.IDLE);
}
else if (Input.GetKeyDown("d"))
{
SetState(StateTypes.DAMAGE);
}
else if (Input.GetKeyDown("a"))
{
SetState(StateTypes.ATTACK);
}
};
}
Code language: JavaScript (javascript)
DAMAGE State
Looking at the state machine diagram above, we can see that you can go to either IDLE, CHASE, ATTACK or DIE states from the DAMAGE state. Let’s create two key inputs to handle these two transitions. We will use the “i”, “c”, and “a” keys to handle the transitions from DAMAGE to IDLE, CHASE and ATTACK states, respectively. For the transition to DIE state, we will count the number of damages that the NPC takes. In our case, we will use the number assigned to mMaxNumDamages. By default, this value is assigned to 5. That means after 5 damage hits, the NPC will die.
First, we add two variables. One public for the maximum number of damage hits before the NPC dies and the other for the damage count.
#region NPC Data and Parameters
public FSM mFsm;
public int mMaxNumDamages = 5;
private int mDamageCount = 0;
#endregion
Code language: PHP (php)
Now, we implement the initialization of the damage state.
void Init_DamageState()
{
NPCState state = (NPCState)mFsm.GetState((int)StateTypes.DAMAGE);
// Add a text message to the OnEnter and OnExit delegates.
state.OnEnterDelegate += delegate ()
{
mDamageCount++;
Debug.Log("OnEnter - DAMAGE");
};
state.OnExitDelegate += delegate ()
{
Debug.Log("OnExit - DAMAGE");
};
state.OnUpdateDelegate += delegate ()
{
//Debug.Log("OnUpdate - DAMAGE");
if(mDamageCount == mMaxNumDamages)
{
SetState(StateTypes.DIE);
return;
}
if (Input.GetKeyDown("i"))
{
SetState(StateTypes.IDLE);
}
else if (Input.GetKeyDown("c"))
{
SetState(StateTypes.CHASE);
}
else if (Input.GetKeyDown("a"))
{
SetState(StateTypes.ATTACK);
}
};
}
Code language: JavaScript (javascript)
DIE State
Finally, we implement the DIE state. This state is triggered when we reach the maximum number of damage hits to mMaxNumDamages. This state can be reached from DAMAGE state.
void Init_DieState()
{
NPCState state = (NPCState)mFsm.GetState((int)StateTypes.DIE);
// Add a text message to the OnEnter and OnExit delegates.
state.OnEnterDelegate += delegate ()
{
Debug.Log("OnEnter - DIE");
};
state.OnExitDelegate += delegate ()
{
Debug.Log("OnExit - DIE");
};
state.OnUpdateDelegate += delegate ()
{
//Debug.Log("OnUpdate - DIE");
};
}
Code language: JavaScript (javascript)
We now make a few changes to SetState so that when we set the DIE state, we delete the game object after a stated duration of time.
// Helper function to set the state
public void SetState(StateTypes type)
{
mFsm.SetCurrentState(mFsm.GetState((int)type));
if(type == StateTypes.DIE)
{
StartCoroutine(Coroutine_Die(1.0f));
}
}
IEnumerator Coroutine_Die(float duration)
{
yield return new WaitForSeconds(duration);
Debug.Log("NPC died. Removing gameobject");
Destroy(gameObject);
}
Code language: PHP (php)
We click play and run the project. The game starts with IDLE state. At this stage, you can click on “c”, “a”, or “d” to transit the state to CHASE, ATTACK or DAMAGE. When the DAMAGE state is triggered, there is an output showing the total damage taken so far. Then if the damage count reaches to mMaxNumDamages, the DIE state is triggered.
Once the DIE state is triggered, the game object is deleted after 1 second. This is handled by the coroutine Coroutine_Die.
See below for the demonstration.
This concludes Part 4 of the tutorial. In Part 5 of the tutorial, we will move further by demonstrating an enemy NPC behaviour, which handles multiple animation states of a 3D animated character, using the delegate based FSM created in this tutorial.
Read My Other Tutorials
- Implement Constant Size Sprite in Unity2D
- Implement Camera Pan and Zoom Controls in Unity2D
- Implement Drag and Drop Item in Unity
- Graph-Based Pathfinding Using C# in Unity
- 2D Grid-Based Pathfinding Using C# and Unity
- 8-Puzzle Problem Using A* in C# and Unity
- Create a Jigsaw Puzzle Game in Unity
- Implement a Generic Pathfinder in Unity using C#
- Create a Jigsaw Puzzle Game in Unity
- Generic Finite State Machine Using C#
- Implement Bezier Curve using C# in Unity
- Create a Jigsaw Tile from an Existing Image
- Create a Jigsaw Board from an Existing Image
- Solving 8 puzzle problem using A* star search
- A Configurable Third-Person Camera in Unity
- Player Controls With Finite State Machine Using C# in Unity
- Finite State Machine Using C# Delegates in Unity
- Enemy Behaviour With Finite State Machine Using C# Delegates in Unity
- Augmented Reality – Fire Effect using Vuforia and Unity
- Implementing a Finite State Machine Using C# in Unity
- Solving 8 puzzle problem using A* star search in C++
- What Are C# Delegates And How To Use Them
- How to Generate Mazes Using Depth-First Algorithm
References
- https://en.wikipedia.org/wiki/Finite-state_machine
- https://gameprogrammingpatterns.com/state.html
- https://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-implementation–gamedev-11867
A committed and optimistic professional who brings passion and enthusiasm to help motivate, guide and mentor young students into their transition to the Industry and reshape their careers for a fulfilling future. The past is something that you cannot undo. The future is something that you can build.
I enjoy coding, developing games and writing tutorials. Visit my GitHub to see the projects I am working on right now.
Educator | Developer | Mentor
Hello and thank you for your code, it seems neat.
However I am having trouble understanding where all this code is supposed to go. It is not clear whether what parts of your code is supposed to exist in different scripts or in the same script.
For example,is the state class you’ve shown here in the FSM.cs file or in it’s own script?
It’s not clear how after you typed up your code where you have placed it or how you have implemented it in the Unity interface.
Thanks.
Hello Christie. The codes of FSM can go in FSM.cs, and the State can go in State.cs (or you can combine both the classes into one file called FSM.cs).
For detailed implementation, you can use the Generic FSM from my GitHub repo https://github.com/shamim-akhtar/fsm-generic
Do let me know if you require any further information. I can set up a working project for you in GitHub.
Oh, and I have another question. How do you implement the NPCstate class?
My code does not recognize the “StateTypes” Type from the EnemyNPC class unless I include this:
using static EnemyNPC;
Hello Christie,
I am not sure what do you mean by “does not recognize the StateTypes”. There isn’t a need for EnemyNPC to be static. Are you following the tutorial? Maybe I can set up a GitHub project for you to refer to. Will that be helpful?
Thank you for your code,
But could you please clarify one point for me. How can the NPCState class create a “private StateTypes mStateType” variable when StateTypes is in EnemyNPC that it was created? By the way, it generates the error: “The type or namespace name ‘StateTypes’ could not be found”
Have a nice day,
Hi Lazrie,
Thank you for following my tutorials.
With regards to your query:
How can the NPCState class create a “private StateTypes mStateType” variable when StateTypes is in EnemyNPC.
>> You are right. The variable should be declared as private EnemyNPC.StateTypes mStateType.
Or you can bring out the whole
public enum StateTypes
{
IDLE = 0,
CHASE,
ATTACK,
DAMAGE,
DIE
}
outside of the EnemyNPC class.
I will amend my tutorial. Thank you again for pointing it out.
Have a great day!