8. Enemies & AI

In this section, we will add simple AI-controlled enemies to the game.

These enemies will patrol their platform randomly until the player comes nearby. When that happens, they will chase the player to inflict damage on collision. If the player escapes, they will go back to patrolling.

Fair warning: this section is the hardest part of the tutorial and will require the combined use of every skill we've learned so far: flow graphs, state graphs, super units, transitions, macros, custom events and variables. Make sure you understand every previous section before starting, because now that the concepts have been introduced, we will go a bit faster than usual. 

When we're done, the final set of graphs will look like this:

Take a short break, stretch your legs, and when you're ready, let's get started!

1. Root state machine

The first thing we'll do is create a root state machine on our enemy. It will have two states:

  • Alive: When the enemy is alive most of the logic happens, like patrolling, chasing and damaging. For the first time, this state will be a super state, meaning it will itself be contain a state graph. Previously, we only used flow states, where the child graph was a flow graph.
  • Dead: When the enemy is killed, it should slowly spin downwards then disappear. This will be a simple flow state that we will implement in the next section, when we add projectiles to kill enemies.

To create the root state machine:

  1. Open the Level3 scene
  2. Select the Enemy object
  3. Add a new State Machine component
  4. Convert its source to a macro called Enemy
  5. Apply the changes to the prefab

In the root state machine:

  1. Delete the default Start state, because it's a flow state and we need a super state
  2. Create a new Super State, open it and change its title to Alive
  3. Right click the Alive state and choose Toggle Start
  4. Create a new Flow State, open it and change its title to Dead
  5. Add a transition from Alive to Dead.

At this point, root state graph should look like this:

2. Alive state

Inside the Alive state graph, we will need three child states:

  • Patrol: When the player is away and the enemy is patrolling the platform randomly. This will be a Super State, because the patrol will include different states as well. Does this feel like inception already? :)
  • Chase: When the player is nearby, the enemy chases it. This will be a regular flow state.
  • Damage: In parallel to patrol and chase, the enemy can inflict damage when colliding with the player. This will be a regular flow state as well.

Parallel here means that there will be two "systems" of states in the alive state graph that will run at the same time: one for movement (Patrol or Chase), and one for damage (just one state). To do that, we only have to define multiple start states: a powerful feature unique to Bolt. Our two start states will be Patrol and Chase.

To recap:

  1. Add a super state called Patrol
  2. Add a flow state called Chase
  3. Add a flow state called Damage
  4. Toggle start on Patrol
  5. Toggle start on Damage
  6. Add transitions back and forth between Patrol and Chase

The graph in alive should then look like this:

3. Damage

Inflicting damage to the player is really easy now that we have already created all the health system in the previous section. As a reminder, we added a Damage custom event to the PlayerHealth state graph that took one argument: the amount of damage inflicted. 

Open the Damage state. 

Now, all we have to do is use our On Collision With macro to trigger that event and inflict 1 heart of damage:

That's it! If you test your game now, the enemy should inflict damage to the player, which in turn should become temporarily invulnerable:

Notice how keeping our graphs DRY and organized made this very simple: the collision code is fully handled on its own, and the player is responsible for its own health and damage system. The enemy, a separate entity, only has to trigger a single event. This is an example of how nesting and events in Bolt allow you to create a robust game architecture.

4. Walking

Let's take a moment to plan ahead: we know our enemy has to walk in multiple places. It has to walk left and right when patrolling a platform, and walk towards a player when chasing. That's 3 places already we're we'll need a walking graph. At this point, you should see where this is going: we'll keep everything DRY and create a reusable macro! Create a new flow macro asset called EnemyWalk. This graph will be similar to movement on the player, but not exactly the same.

4.1 Direction & movement

Add a new float input value called direction:

This direction input represents a X axis value. 

  • If it is above zero, the enemy should walk right
  • If it is below zero, the enemy should walk left
  • If it is equal to zero, the enemy should stay idle

Note that this direction isn't a speed: for example, if the direction is -5, the enemy should go left exactly as fast as if the direction had been -1. To do that, we will first normalize the direction before multiplying it by the speed. Normalizing keeps the sign (+ / -), but with an value of 1, unless the value was zero, in which case it leaves it as is. In practice, it means we calculate movement like this:

Notice we use a Speed object variable again. Because we want the player to be a bit faster than the enemies, we will add a speed variable with a value of 2 to our enemy game object and apply the changes to the prefab:

4.2 Flip

Like we did for the player earlier, we'll flip the enemy sprite by setting the X axis of the scale equal to the direction, but only if the direction isn't zero. If it is zero, meaning the enemy is idle, we'll skip flipping altogether and keep the last scale.

You'll notice that this time the Equal unit takes numbers and allows for an inline value for B. This is because Numeric is checked in the graph inspector for the unit:

It works is exactly the same as using the following nodes, but a bit more conveniently:

4.3 Velocity

Like we did for the player, we'll set the enemy rigidbody's X velocity to match the calculated movement variable:

4.4 Animation

Finally, we pass the speed to the enemy's animator controller:

At this point, the final EnemyWalk macro should look like this when zoomed out:

5. Patrol

Now that we have our walking macro ready, we can implement patrol.

Open the patrol state under Enemy > Alive > Patrol:

In this nested state graph, we will need three states:

  • Idle: When the enemy is idle and waiting. It adds a bit of realism to have it "sit and think"!
  • Walk Left: When the enemy decides to walk left
  • Walk Right: When the enemy decides to walk right

By default, our enemy will be idle. Setup your graph like so:

  1. Rename the default Start state to Idle
  2. Add a new flow state called Walk Left
  3. Add a new flow state called Walk Right
  4. Arrange the states in a pyramid with Idle on top

We will now add use our EnemyWalk macro as a super unit in each of these states. Delete every event in each state and drag & drop our macro to create the super unit.

  • In Idle, the direction should be 0
  • In Walk Left, the direction should be -1
  • In Walk Right, the direction should be +1

When you're done, each state should have a single node:

IdleWalk LeftWalk Right

Next up, we will create the transitions between these states.

5.1 Change mind transitions

Sometimes, the enemy should randomly change its mind about where it's going. It might be idle and decide to walk, or change direction, or stop walking. We will call this the "change mind" transition, and we'll create a single reusable macro to handle all of them.

Create a new flow macro called EnemyChangeMind, and set it up like this:

  • Random Range is located under Unity Engine > Codebase > Random
  • On Timer Elapsed is located under Events > Time
  • Trigger State Transition is located under Nesting

This simple transition randomly waits between 1 and 3 seconds to trigger. That's it!

Remember to give it a title so it's easier to understand what it does:

Next step: add it to the graph. An enemy can change its mind from any state of a patrol to any other, so we need back and forth transitions between each state in our triangle:


  1. Select each of these transitions
  2. Set its source to Macro
  3. Choose the EnemyChangeMind macro

The inspector for each transition should then look like this:

And the state graph should look like this:

If you test now, the enemy should start patrolling randomly... but it won't stop when it reaches the edge of its platform!

Let's fix that by adding another type of transition when the enemy reaches the edge of its platform.

5.2 Reach edge transitions

To determine whether the enemy has reached the end of its platform and should switch direction, we'll use a technique similar to how we detected whether the player was grounded earlier: we will use a downwards circle cast. However, this time, we'll add a small offset in the direction of the enemy, so that our circle cast predicts ahead of time if the end of the platform will be reached:

If that ground check with an offset returns false, we'll know that the enemy soon won't be grounded, and therefore has reached the edge of its platform.

5.2.1 Add parameters to the ground check

Since we already have a GroundCheck macro, we will only modify it to add some parameters. Open the GroundCheck macro we created earlier for the player controller:

Add a new Input unit with three parameters:

  • Offset (Vector 2, default 0,0): the offset to add to the origin of the circle cast from the object's position
  • Radius (float, default 0.3): the width of the circle cast
  • Distance (float, default 1.1): the distance to check downwards

We could leave radius and distance at constants, but while we're at it, why not turn them into arguments to make our macro really flexible?

Then, connect the new input ports to the circle cast node, using an Add to apply the offset:

5.2.2 Create a reach edge transition macro

Create a new flow macro called EnemyReachEdge for our transition. Set it up so that the transition triggers if the enemy is not grounded:

Then, we'll need to calculate the offset parameter so that it points in the direction the enemy is facing. We can use the X axis of the scale to know that, because we flip the enemy in our movement code. We then multiply it by 0.5 units forward, which is a small offset of about half the enemy's width.

Then, just connect the offset vector to the offset port of our super unit:

5.2.2 Add the reach edge transitions to the patrol state

Finally, we just need to add this transition to our patrol graph. Logically, an enemy should only ever reach an edge while walking either left or right, and when it does, it should immediately go in the opposite direction. Therefore, we'll only add our new transition back and forth between the Walk Left and Walk Right states:

If you test your game now, the enemy should properly avoid falling off the platform:

6. Chase

Now that our patrol state is complete, we'll implement the Chase state:

When chasing, the enemy should walk towards the player. Thanks to our EnemyWalk macro, this is fairly easy: we only need to calculate the direction from the enemy to the player and pass it to the super unit. 

Using simple vector maths, we subtract Player Position - Enemy Position to get our direction vector, and take the X component from it. Because the enemy walk macro takes care of normalizing the value to +1 / -1, we can pass that directly:

6.1 Chase transitions

The enemy should start chasing the player when it gets nearby, and stop when it escapes.

Add a Detection variable to the enemy object to indicate how far it starts seeing the player, and apply the changes to the prefab:

Open the transition going from Patrol to Chase:

Here, we will calculate the distance between the player and the enemy, and transition if it is lower than the detection radius:

Rename the transition to Player Nearby:

Back to the alive state, open the transition from Chase to Patrol:

Here we'll implement the opposite check: if the player is farther than the detection range, trigger the transition:

Rename the transition to Player Away and get back to the alive state:

If you test your game now, you'll see that the enemy does chase the player when it gets near... but even if that means falling off the ledge!

Fortunately, this is an easy fix: just use the Reach Edge transition to transition back to patrol when chasing:


You got through the hardest part! Congratulations!

At this point, your enemy should patrol the platform, chase the player when it gets near, and inflict damage on collision.

Here's a full HD screenshot of all the nested structure of our enemy graph (Full Resolution Link). We used the full power of nesting to reuse our code and make a robust AI:

Health & Damage Projectile Attack

This article was helpful for 18 people. Is this article helpful for you?

Hi just one small point - in 2. Alive State the recap should be "2. Add a flow state called Chase"

Hi again - on recapping this section I there's something I don't understand. In the Patrol logic, how does Bolt decide which state to transition to if the Change Mind random timers pick the same value? E.g. Enemy is in Idle state, so there's two possible transitions >Walk Left or >Walk Right, each controlled by the same logic. If both timers pick the same value at the same time which transition takes precedence? Or, do we presume that because they're floats, the chances are too small to worry about? Or am I missing something else...?

Also, in the very last stage, adding the Reach Edge transition from Chase back to Patrol doesn't work in practice because if the player is still within the detection radius, the enemy goes back to chasing and we get a transition loop - and it falls off the edge anyway! :)

The value of the timer gets picked when the parent state is entered. The lowest value of all transitions will be triggered first, and all other transitions will be ignored after.

If they both pick the exact same value (which is highly improbable), there is unspecified behaviour, meaning either of them could get traversed, and Bolt doesn't define or guarantee an order. Transitions have no notion of priority, they're all checked simultaneously. However in this case it doesn't matter at all, because both decisions would be adequate.

As for the last transition, I'm puzzled because it worked when I tested it (but yes, there's a transition loop; we're just being lazy to be honest!). I'll give it another look when I have a minute.


I tweaked the PlayerNearby transition to include a ground check.  This worked reasonably well.

Yeah, that worked for around 10 tries without making enemy stuck on the edge. Before this fix it wasn't that good :D 

On 4.2 Flip, my enemy flip is scaling the object by the speed variable. if i set it to 2, enemy will scale by 2, if speed variable is 3; enemy scale X will be 3.

I've checked all the nodes and nothing seems wrong.

Are you sure you used the Normalize unit?

Yeah, the Normalize ("Returns the unit length version of a scalar")

I get the error "state transition trigger cannot be used outside a state transition." when I try to play now. (once)  I've been following this to the T and I don't see a rogue state transition trigger anywhere. Any ideas? It highlights the enemy when I select the error in console.

Having the same issue here. Did you find what was wrong?


I found a fix. It's not pretty, but it works. It appears that triggering a state transition from within a super unit is what causes the error. What I did was copy/paste of the units from the EnemyReachEdge macro into both Reach Edge transitions. Again, it's not DRY, but it works...

You need to add the transition the same way as under "5.1 Change mind transitions".


You need to use the Graph Inspector to change the source of the transition to "Macro" and then choose "EnemyReachEdge". Same way as under "5.1 Change mind transitions".

If you added the EnemyChangeMind and EnemyWalk macros as components of the Enemy you will get errors like "state transition trigger cannot be used outside a state transition." Remove those components and your errors should disappear.

Can't find check box to enable Numeric on Equal unit. Bolt v.1.2.3 Alpha 3

"You'll notice that this time the Equal unit takes numbers and allows for an inline value for B. This is because Numeric is checked in the graph inspector for the unit:"

This should be in the graph inspector, it isn't on the unit itself (for some reason, not sure)

Yes, it's not there:)

Ok having trouble not sure what going on,..   I think it may be the enemy Direction

the enemy is flipping like crazy due the the value in coming from the Get X node  is flipping ?   the distance values going into the node don;t add up they should be much different since the player is so far away from the Enemy ?

I moved the player variable to the application to get the hud to work everything i think is the same as the tutorial

Also tried 3 vectors nodes same problem

I'm missing something simple please help

OK fixed my issue by adding a New Scene Variable for the player ( I already had one as a application Variable ) change the enemy to uses that Variable and all is working


When you add a Super Unit to a Flow Graph or Super State it should show this. EG if I add a Fixed Update to Damage (Damage being a Flow Graph it shows but not any other objects like Super Unit. It would also be great you could right click on jump to a Macro or State Machine file. As the project gets bigger you want to be able to do this. But like Right click in Visual Studio and say jump to a function that could live in another class file.

Type-O Its Codebase>Unity Engine>Random. Not Unity Engine>Codebase>Random

I had to declare a new scene variable in Level 3 otherwise the the Get Variable > Player for Player Nearby threw an error - it could not find the variable declared in Level 1. I think this is a change in the latest version of Bolt which cannot reference scene variables from other scenes. Is the better solution to create an application variable for all scenes?

Sounds right. Application is across everything while scene is for that scene only and does not five when new scene is loaded

I am having trouble at 6.1 - I have created the 2 transitions 'Player Away' and 'Player Nearby'. I made sure to use the get Application variable (and not scene as shown in tut) for both transition. If I play the scene, and watch the transition graph, it works well (finds the player and its position, compares it to the self position of the enemy... but as soon as 'Less' comparison returns true, I get the following error: "InvalidOperationException: Variable not found: 'Player'. Bolt.VariableDeclarations.Get (System.String variable)."

I cannot figure out where it comes from... any ideas? :)

Never mind... I had kept the Player var as a scene variable in the Chase state... my bad.