Using Finite State Machines to Create Smart AI in Unity 2021
In this article, we’ll walk through a common situation in game development.
The game designer has handed you a diagram for their desired finite state machine related to the AI’s behavior.
This diagram has 3 main states and lists “triggers” for entering each state from the current state.
If the AI is walking and the E key is pressed, it will enter the jumping state.
If the distance to the target is < 0.5f, the AI leaves Jumping state and enters the attacking state.
If in the Attacking state and the target dies, enter the walking state.
The first thing we’ll need is to setup a way to track the AI’s state.
Enums are ideal for this, so we’ll create the AIState enums shown above and create a variable based on it to track the current state of the AI.
We’ll confirm what state our AI is currently in by adding the PrintAIState() method.
We can then call this PrintAIState() method from the Update() method and see on a frame-by-frame basis what state our AI is in.
We will have our AI default state be walking.
We know that while in the walking state, we want to transition into the jumping state if the player presses the “E” key.
In our new CheckInput() method, we check for the keypress, and as shown above, we can see that our AIState enum makes for a nice autogenerated code complete showing the available states to choose from.
We’ll choose jumping and check that off our to-do list based on the diagram.
Now, for the time being, I’ve disabled the NavMeshAgent related code in the Update() method of our AI script.
I’ve also disabled the NavMeshAgent component on the AI game object for the time being.
Now, if I hit play, we’ll see the default walking state is printing out to console as expected.
Once I push the “E” key, it changes to the Jumping state’s confirmation message.
In order to integrate my current waypoint pathing with the NavMesh system, I’ve move the related code that I had just commented out in the Update() method to its own method, “WalkingOnPath”.
Back in the Update() method, I do a simple check that the NavMeshAgent is not “stopped” and then call this new method.
Lastly, in the Unity Editor on the AI game object, I reenabled the NavMesh Agent component.
Now if I hit play, we can see that it is working as desired.
Next, we need to implement our attack or return to walking state logic.
In our CheckInput() method, we can remove our current NavMeshAgent.isStopped line.
Then we need to add an if statement checking the NavMeshAgent’s remaining distance to see if the target would be in range.
If that is true, we’ll enter the attack state by calling a coroutine that will symbolize that our AI is attacking by pausing for 3 seconds.
In a more developed prototype we’d use this coroutine or another method in its place to animate the attack and rotate the AI to face the target being attacked.
Our current coroutine sets NaveMeshAgent.isStopped to true so that the AI stops moving, then waits 3 seconds before setting NavMeshAgent.isStopped back to false.
Now we can run our game and test to see if it is working as desired.
One last thing, we need to make sure we are still updating our AIState enum!
Once we run our game, we can see that the AIState enum value is updating in real-time as desired.