In this simple Unity AI tutorial, we will create a horde of zombies who wander around the screen between random hidden waypoints until they see the player-controlled object, at which point they will give chase until they lose sight of the player. They will then resume their aimless wandering. It would be very easy to adapt this tutorial to have the zombies follow the waypoints in a predetermined order to create tower-defense-style AI.

About this project

Skill level 1
Time to complete 2 hours

New concepts

  1. Finite State Machines
  2. Using Mecanim to create a visual finite state machine
  3. Implementing a patrol and chase AI

Recommended preparation tutorials

Assumed previous experience

Finite State Machines & Unity Mecanim?

A finite state machine sounds complicated but at its simplest, it is just a way of keeping track of the situation (state) of an object and the rules which determine when that state will change.

If you want to build a finite state machine in code, there are dozens of tutorials out there already. Unity, however, provides a neat visual way to build an AI using Mecanim, the animation system built into Unity. If you are concerned that this tutorial is going to be some kind of dirty hack where we use the game engine in a way it was not designed to be used then let me assure you this is a legitimate use for Mecanim and as we will see the way we handle things like behaviors and communication between a finite state machine and its related objects is completely integrated into Unity. What you should be able to do by the end of this tutorial is implement your own, more complicated AI using these same simple techniques.

What kind of behavior will our zombie have?

We will see how to set up a set of waypoints and then get our zombies to choose one at random. The zombie will then set off towards it until it gets close and then it will pick another one. This simple behavior will continue forever unless the player enters the zombie’s field of view. Of course, the field of view is something we must define and we will do so with a triangle-shaped sprite to which we will attach a trigger collider. If the player enters the trigger, then the zombie has “seen” the player. It would be simple to give the zombie more “sense” perhaps a circle collider for hearing or smell.

When the player is seen by a zombie it will abandon its waypoint and home in on the player. If the player escapes the zombie’s AI sight, the zombie will stop for a few seconds and then pick a random waypoint and go back to doing what it was doing before lunch wandering into its triangular field of view.

Take a look at this diagram read words in the rectangles that represent the states a zombie can be in and also look at the lines connecting the states. You will see that each line has an arrow indicating the direction that a state transitions from state to state as well as some notes I have added indicating the parameters(variables of the state machine) that need to be true for the transition to take place.

Zombie AI Finite State Machine in Unity Mecanim

To explain the above, first of all, forget about the Exit and Any State states. We don’t need to do anything with them in this simple tutorial. When the state machine starts it enters the Entry state and immediately and unconditionally transitions to the Get New Waypoint state. When the Get New Waypoint state is entered it will communicate with the zombie and the zombie will pick a new waypoint. At this point, the distance from the chosen waypoint (distanceFromWaypoint) will be greater than one. This will cause a transition to the Move To Waypoint state. The state machine will communicate this change to the zombie object.

Every frame the zombie object will feed data into the state machine. One of these pieces of data is its distance from the current waypoint. When this distance is less than one a transition occurs back to the Get New Waypoint state and the zombie picks a new waypoint and sets off towards it… until it is less than one unit away and so this continues.

If however, the player stumbles into the zombie’s vision (playerInSight is true) the zombie object will let the state machine know and the state will change to Chase. The zombie’s behavior will be modified by the state machine and a battle for life and death will ensue. In order to keep this short enough for a single and functional tutorial, nothing happens when the zombie catches the player. You would probably want to kill the player or subtract hit points, etc. In addition, you would probably arm the player with a weapon, etc.

If the player manages to escape the zombie’s sight then playerInSight will turn to false and the state machine will transition to the Wait state. After the Wait state the state machine either goes back to the Get New Waypoint state or if the player was careless enough to wander back into the field of view then the Chase state will resume.

There might be a bunch of unanswered questions like how do the zombie and the state machine communicate with each other or how do you associate a state machine with an object? All these questions and more will be answered as we proceed.

Starting the project

Create a new project in Unity, call it Zombie AI, choose the 2D option, and click the Create Project button.

 

Create some new folders to stay organized as we proceed. You need an FSM, Prefabs, Scripts, and Sprites,  like this.

organize-your-folders

Import the three images below and keep them in the Sprites folder.

This is the graphic that will represent the controllable player

This is the graphic that will represent the controllable player

This is our Zombie graphic

This is our Zombie graphic

The vision cone could have the Sprite Renedere component disabled so it is invisible. But it willbe useful to see it while we are testing.

The vision cone could have the Sprite Renderer component disabled so it is invisible. But it will be useful to see it while we are testing.

To get started with the visuals grab yourself a fancy background from opengameart.org. Import it into the Sprites folder, drag it into the scene, and repeat, positioning the background like tiles until you have covered a little bit more than the camera area. This step is not essential, the entire project will work without a background; however with just a plain background, when we set the camera to follow the player there will be a reduced sense of movement. One alternative if you don’t want to bother with the background is to simply play your game while observing the scene view which has handy grid lines. Background or not, let’s move on to the project proper.

Here is what my Sprites folder looks like at this stage.

sprites-folder-containg-graphic-assets

Let’s create some waypoints for the zombies to wander between.

Creating the waypoints

Next, we will create some waypoints that the zombies will randomly choose from to wander between. Once you see the code, you will see you could just as easily make your zombies follow a path of waypoints if you prefer.

Right-click in the Hierarchy tab and select Create Empty. Now we have an empty object that is not visible to the player during the game. We want to make it easy for us to see in the Scene view, however. Rename the empty object to p1. Make sure that p1 is selected and in the Inspector window give it an icon to make it clearer in the Scene view. You do so by clicking this icon in the top-left of the inspector window.

change-icon-representing-game-object

Choose a new icon. I changed it to the red pill shape but you can choose whatever you prefer. You can see the new empty game object in the Scene view as shown in the next image.

Empty game object with new icon to make it easy to see.

An Empty game object with a new icon to make it easy to see.

We will now add a tag to this game object so we can easily find it through C# code.

  1. In the Inspector window select Tag | Add Tag
  2. Click the + icon
  3. Type p1 for the name of the new tag
  4. Select p1 in the Hierarchy window, click Tag in the Inspector window, and choose p1

The empty game object called p1 now has a nice clear icon and a tag called p1.

Repeat this process (empty object, icon, and tag)four more times and name them p2, p3, p4, and p5. Actually, you can make as many or as few waypoints as you like but you will then need to remember to vary the code as we proceed. Note that for this particular project to work all of them must have a unique tag.

Arrange your waypoints around the scene. This is what my scene view looks like.

It is not important to have your waypoints in the same place as mine. Also the waypoints can be outside the camera view because the camera will follow the player.

It is not important to have your waypoints in the same place as mine. Also the waypoints can be outside the camera view because the camera will follow the player.

Now the zombies have some waypoints let’s get started with the zombies themselves.

Prefabricating an intelligent(ish) zombie

We will do this in 6 stages as follows.

  1. Create a game object in the scene from the zombie and view-cone sprites
  2. Add an AI script to the zombie
  3. Add a collision script to the view cone
  4. Build a finite-state machine to receive data from the AI
  5. Add behaviors to the finite state machine to trigger the required behavior from the AI script
  6. Make a prefab from the finished zombie and add a whole bunch of them to the scene

Let’s get started.

The zombie and the vision cone sprites

In the Sprites folder select vision_cone. Then, in the Inspector window click the Pivot button, then choose bottom and click Apply. This has made the center-bottom(narrow part) of the cone the pivot point for this sprite. This will make things work when we combine two sprites next.

Drag the zombie and the sight_cone sprites into the scene. Then in the Hierarchy view, drag the vision_cone game object to be a child of the zombie game object. Set the X, Y and Z values of the vision_cone‘s Transform component to 0,0,0. This will make the vision_cone and zombie objects line up, just as we want them to.

This is what my Scene view looks like (zoomed in).

zombie-object-parent-of-vision-cone-object

This is what your Hierarchy window should look like at this stage.

The vision_cone game object is a child of the zombie game object.

The vision_cone game object is a child of the zombie game object.

Add a Rigidbody 2D component to the zombie object and set it to Is Kinematic. We want the zombie to move but not collide.

Now for the first of the C# code.

Adding the AI script to the zombie

Right-click in the Scripts folder and select Create | C# Script. Name it ZombieAi. Code the script as follows. Read through the code including the comments and then we can discuss it.

Add the script to the zombie game object by selecting the zombie game object in the Hierarchy window and clicking Add Component in the Inspector window. Now choose Scripts | ZombieAi. This is how the code works.

At a glance, the code is long and sprawling but it really doesn’t hold too much that we haven’t seen in other tutorials previously.

The variables initialized at the start include a Transform that will be used to track the position of the player.  The variables related to the state machine include an Animator. As we will see soon, an Animator is a state machine. For each frame we will send values to the Animator/state machine. These values are held in distanceFromTarget and inViewCone which will indicate how far away a zombie is from its current waypoint as well as if the player is currently inside its trigger collider. The chasing and waiting values do NOT get sent to the state machine. Rather, as we will see, the state machine will update these variables when appropriate. We then declare a bunch of variables that will handle movement. A Vector3 called direction which keeps track of the zombies heading, a float called walkSpeed which is how fast the zombie can move. A int called currentTarget which indicates the position in an array that holds the Transform of the current waypoint. The waypoints array into which we will load all the Transform components of our waypoints in the scene.

In the Awake method we use the tags of the waypoints and the player to get a reference to them. All the waypoints are stashed in the waypoints array. The interesting thing that happens in Awake is we initialize animator by getting a reference to an Animator component which is the finite state machine we will soon build.

In Update there are just two if statements. If the zombie is currently chasing calculate the direction to the player and turn to face him. If the zombie is not waiting just walk in whichever direction it is facing. Note that this direction will have been previously set to a waypoint. This implies that if the zombie is waiting it won’t move at all. We will see the rotateZombie method soon.

In FixedUpdate is where the action happens. The first line of code calculates how far the zombie is from the current waypoint and then calls SetFloat and SetBool on animator to set the values of distanceFromWaypoint and playerInSight, inside the state machine. These two values will be all that our finite state machine will need in order to make decisions and transition between states.

At this point, we have not seen any way that the state machine can communicate back to the zombie. We will get to the first part of the conundrum really soon.

Next up is the SetNextPoint method. SetNextPoint uses a do while loop to keep picking a new position in the waypoints array until it chooses one that is different to the current one. It then alters direction and calls rotateZombie to do a little math to face the zombie in the correct direction.

The Chase method sets direction relative to the player(rather than the next waypoint) then also calls rotateZombie.

StopChasing does one thing. It sets chasing to false. When we build our state machine we will see how it accesses this method in order to control how the zombie behaves. Note there is a corresponding StartChasing method as well.

The RotateZombie method uses a trigonometric function to calculate an angle to make the zombie face where it is headed. If you want to know more about this then take a look at the tutorial series on heading and trigonometric functions in games.

The last method, ToggleWaiting will also be called by the state machine to toggle the waiting state of the zombie.

Adding collision detection to the vision-cone game object

Add a collider by selecting the vision-cone in the Hierarchy and clicking the Add Component | Physics2D | Polygon Collider 2D. If you check the scene view you will see that a nice neat collider has been added to the vision-cone object. Check the Is Trigger checkbox so that we can code a script to respond to objects entering the perimeter of the collider.

vision-cone-with-polygon-collider

Right-click in the Scripts folder and select Create | C# Script. Name it ConeOnTrigger. Code the script as follows.

Add the script to the vision-cone game object by selecting the vision-cone game object in the Hierarchy window and clicking Add Component in the Inspector window. Now choose Scripts | ConeOnTrigger. This is how the code works.

The previous code has a reference to the zombie object. Every time the player either enters or leaves the trigger the inConeView boolean variable is updated appropriately. Remember the value of inConeView is sent to the state machine every frame during the FixedUpdate method in the ZombieAi script. We can start to see all the pieces of how we talk to the state machine.

Now we get to build the finite state machine that will receive the inputs and initiate the behaviors we have just coded.

Building the Zombie Finite State Machine in Mecanim

To get started let’s add a couple of components to the zombie game object.

Make sure the zombie object is selected in the Hierarchy then click Add Component in the Inspector. Choose Miscellaneous | Animator. If you look closely at this new component you will see a slot for a Controller. This is where we will add the finite state machine when we have built it. Next, add another component to the zombie. Click Add Component and choose Physics 2D | Rigidbody 2D. Set Gravity Scale to 0 to stop our zombie from falling to oblivion and set it to Is Kinematic. We want the zombie to move but not collide.

Let’s build the finite state machine in an animator controller. This sounds like a hack but it is a totally legitimate way to program AI in Unity. Select Window | Animator from the Unity main menu to create a workspace for this purpose. Dock the window (by dragging and dropping its tab) somewhere you will have a large workspace. I docked mine in the same space as the Scene.

docking-animator-tab-with-scene-tab-unity

Right-click in the FSM folder and select Create | Animator Controller. Name the Animator Controller ZombieFSM. Rearrange the states that are created for you like the following image. Note this is not necessary it is just keeping things tidy.

animator-fsm-default-states

Add the following states in the following order by right-clicking and selecting Create State | Empty. You rename the states by selecting them in the Animator window and adjusting the Name in the Inspector window.

  • Get New Waypoint
  • Move To Waypoint
  • Chase
  • Wait

Notice the first state you create is a different color and was automatically connected to the Entry state. Rearrange your states to look like this next image. Notice we are already getting close to how we envisioned our finite state machine at the start of the article.

Zombie finite state machine taking shape

Zombie finite state machine taking shape

Now we can add the two parameters and the transitions that their different values will trigger. To add a parameter click Parameters in the top left of the Animator window and then click the + icon. Add the following parameters as the following types.

playerInSight as a bool

distanceFromWaypoint as a float

Remember the FixedUpdate method in the ZombieAI class constantly updates these parameters. Here is a reminder of the code which executes each frame.

To add the transitions that values of these parameters will trigger we will start by adding the transition links and then plug in the values after that. To create a transition right-click on the state you are starting from, choose Make Transition and then left-click on the state you want to transition to. The direction is very important or our zombies will not behave as intended. Create the following transition links.

Get New Waypoint to Move To Waypoint

Move To Waypoint to Get New Waypoint

Move To Waypoint to Chase

Chase to Wait

Wait to Chase

Wait to Get New Waypoint

Check you have the exact same state machine as shown in this image.

Zombie finite state machine with transitions links

Zombie finite state machine with transitions links

Now we can plug in the values of the parameters that will initiate the transitions. Select the transition that goes from Get New Waypoint to Move To Waypoint by left clicking it. Notice the options that appear in the inspector window.

Uncheck Has exit time

Click the + icon of the Conditions option

Configure the new condition as distanceFromWaypoint Greater 1

This is how the inspector should look after you have done this.

configuring-transition-parameters

The Has Exit Time option optionally delays the exit when the condition is true. The Conditions is the actual condition that would cause this transition link to be made. So as the zombie is closer than one unit to the current waypoint it will get a new waypoint. Fill out all the rest of the transition parameters and exit times as detailed next.

  • Move To Waypoint to Get New Waypoint: Uncheck Has Exit Time. distanceFromWayPoint Less 1
  • Move To Waypoint to Chase: Uncheck Has Exit Time. playerInSight = True
  • Chase to Wait: Uncheck Has Exit Time. playerInSight = false
  • Wait to Chase: Uncheck Has Exit Time. playerInSight = true
  • Wait to Get New Waypoint: Check Has Exit Time and set to 3.0 under Settings. playerInSight = False

We have so far seen how the ZombieAi class repeatedly updates the state machine with the values it needs and we have just programmed how and when the state machine will step back and forth between states. Now we can see how the state machine communicates back to the ZombieAi class using behaviors.

Adding behaviors to the finite state machine

IMPORTANT NOTE: I am using an English|UK version of Unity and an English|US spell-checker. Note there is a disparity in the spelling of behavior/behaviour. It doesn’t matter which you use as long as you are consistent with regard to file names and class names. Throughout this tutorial, I have used behavior but I just noticed that in the code samples that follow I have used behaviour. Just make sure you are consistent and make any necessary changes if you are copying & pasting. End of important note.

Behaviors are how the FSM communicates with the AI. In this project, the behaviors don’t control HOW the AI does something they just choose WHICH ONE and WHEN.

To add a behavior we choose the state that we want to add a behavior to and then in the inspector window click the Add Behavior button. We can add behaviors on numerous different events as we will see.

Select the Get New Waypoint state and click Add Behavior. Choose New Script and type SelectWaypointState in the Name field. A new C# script has been added as a behavior. Open the script and edit the code to look like this.

The first thing you will notice when you view the auto-generated script is there are a few more methods. The OnStateEnter method is called when the state is first entered. For this behavior, that is exactly what we want. The code above, therefore, when the Get Next Waypoint state is entered will get a reference to the appropriate instance of the ZombieAi script and call its SetNextPoint method. The ZombieAi script will take care of rotating the zombie and sending it on its way.

Select the Chase state and follow the previous steps to create a behavior called ChaseStateBehaviour. Edit the code to look like this

In the Chase state, we get to use both OnStateEnter and OnStateExit. When the state machine first enters the state it calls StartChasing on the ZombieAi script which changes where the zombie is pointed and where it moves to each frame. In OnStateExit the same method is called which causes the zombie to revert to its previous behavior of wandering between waypoints.

Select the Wait state and follow the previous steps to create a behavior called WaitingStateBehaviour. Edit the code to look like this

In the WaitingStateBehavior script the same methods are used as in the ChaseStateBehavior script. When the state machine enters the Waiting state the ToggleWaiting method sets the zombie into a motionless state and when the Waiting state is exited it sets the zombie moving again. Remember this movement could either be towards the player or towards a new waypoint, depending upon whether the player is currently visible.

Finally, for this section of the tutorial, select the zombie object in the Hierarchy, drag ZombieFSM from the FSM folder to the Controller field in the Inspector.

Select the vision-cone object in the Hierarchy and drag the zombie object to the Inspector and drop it on the Zombie Ai field of the Cone On Trigger script.

We now have all the references in our scripts apart from the Player reference but we haven’t made that yet.

Making a zombie prefab and adding some instances to the scene

Our zombie is almost done. Drag it from the Hierarchy to the Prefabs folder to create a prefab from all of our hard work. Make sure the prefab has definitely been created in the Prefabs folder and delete the zombie from the Hierarchy. Drag and drop a whole load of zombies from the Prefabs folder into the scene.

Tip: If you can’t wait any longer, you could comment out the few lines of code in ZombieAi.cs that refer to playerTransform (and are causing an error), run the game and see all your zombies wandering around between the different waypoints.

We are so close now so let’s add the finishing touches.

Coding a controllable player

Get started by adding the player sprite to the scene. Add a tag called Player to the player object. If you need a reminder how to do this then refer back to when we created the waypoints. Add a Ridgidbody 2D and set its Gravity Scale to 0. Next, add a Box Collider 2D. Now add a  new script component named PlayerController. Edit the script to be the same as this next code.

This is very straightforward code to move the player and make him face the appropriate direction. Note that if you plug a controller into your PC/Mac you will get more refined rotations and movements compared to just using the cursor keys. We have discussed simple movement in several previous tutorials.

Making the camera follow the player

Select the Main Camera object in the hierarchy. Add a new C# script as a component in the Inspector and name it FollowCamera. Edit the code to be the same as this.

This script keeps a reference to the Transform component on the player so the camera follows the player as it moves.

Finally, select the Main Camera object and drag the player to the slot on the FollowCamera script.

Running the game

Run the game and observe it in the scene view if you didn’t bother with the fancy background or the game view if you did.

running-zombie-ai-game-in-unity-scene-view

You can add hundreds of objects before even my fairly modest laptop starts to struggle. There are optimizations we could make to our code but that would be for another tutorial. The point is that our finite state machine tracks the states and makes all the decisions for us. It might not be obvious how much coding this is saving us. Think about the vast number of if statements that would be necessary to replace what the state machine is doing. Furthermore, it is much less error-prone. Click on a zombie in the Scene view, switch to the Animator view and then run the game. You can see a nice animation of the states and when they transition. This is very clear to debug when things don’t behave as expected. The visual nature of the Animator is also really simple to add in extra states.

Congratulations on building the project.

patreon