Finite State Machine Using C# Delegates in Unity

Finite State Machine Using C# Delegates in Unity

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.

https://faramira.com/wp-content/uploads/2020/08/demo.mp4

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

  1. Implement Constant Size Sprite in Unity2D
  2. Implement Camera Pan and Zoom Controls in Unity2D
  3. Implement Drag and Drop Item in Unity
  4. Graph-Based Pathfinding Using C# in Unity
  5. 2D Grid-Based Pathfinding Using C# and Unity
  6. 8-Puzzle Problem Using A* in C# and Unity
  7. Create a Jigsaw Puzzle Game in Unity
  8. Implement a Generic Pathfinder in Unity using C#
  9. Create a Jigsaw Puzzle Game in Unity
  10. Generic Finite State Machine Using C#
  11. Implement Bezier Curve using C# in Unity
  12. Create a Jigsaw Tile from an Existing Image
  13. Create a Jigsaw Board from an Existing Image
  14. Solving 8 puzzle problem using A* star search
  15. A Configurable Third-Person Camera in Unity
  16. Player Controls With Finite State Machine Using C# in Unity
  17. Finite State Machine Using C# Delegates in Unity
  18. Enemy Behaviour With Finite State Machine Using C# Delegates in Unity
  19. Augmented Reality – Fire Effect using Vuforia and Unity
  20. Implementing a Finite State Machine Using C# in Unity
  21. Solving 8 puzzle problem using A* star search in C++
  22. What Are C# Delegates And How To Use Them
  23. How to Generate Mazes Using Depth-First Algorithm

References

  1. https://en.wikipedia.org/wiki/Finite-state_machine
  2. https://gameprogrammingpatterns.com/state.html
  3. https://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-implementation–gamedev-11867

4 thoughts on “Finite State Machine Using C# Delegates in Unity”

  1. 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.

  2. 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;

    1. 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?

Leave a Reply

Your email address will not be published. Required fields are marked *