Unity: Creating a simple Enemy-AI with Coroutines

Hey folks! Lately I was busy with different things. Rewriting the GUI, Saving and Loading different stuff and of course: AI.
I had a clear vision of our enemies for “Sir Eatsalot”. They should be basic, but not that dumb. They should be able to do some basic stuff and maybe even more.

Since I’m working with Unity and C# I thought about different setups. Should I run the Logic in Update() ? If so, how should I change the current states? First I run the AI in Update and used a Switch-Case-Method to determine which state should be active. It worked, but I wasn’t quite satisfied. In certain states, like “Attack”, Timers were used to determine when to stop the attack when the Player leaves the “Hitzone” of the Enemy.

It worked quite well, but I didn’t like the approach that much. I wanted a cleaner way to deal with the AI.
I also wanted more flexibility with the Timers.

So I used Coroutines instead. If you don’t know what Coroutines are, here’s a great explanation/tutorial on
that topic: Unitygems – Coroutines

So, with Coroutines you’re able to run commands step by step. This can come in handy in different situations.
If you want to use cutscenes in your game, you can do that with Coroutines, too.

With Coroutines you can run your AI, too.

The Enemy-Script I’m using in our game is a little bit more complex, but I’m going to show you a very basic StateMachine.

Let’s do this step by step. We create a new CS-Script called “EnemyAI”. We don’t need Update(), so I swap that with Awake(). (I’m heavily using the #region preprocessor directive just to have a better overview)

In this script we also create a new public enum called “ENEMY_STATE”. The script looks like this:

using UnityEngine;
using System.Collections;

public class EnemyAI : MonoBehaviour {

#region UNITY METHODS

public void Awake()
{

}

public void Start()
{

}

#endregion


}

#region enums

public enum ENEMY_STATE
{
  IDLE = 0,
  CHASE = 1,
  ATTACK = 2
}

#endregion

}

Now with our State-Enum we can create our StateMachine! In the next step we’re going to write our first Coroutine.

using UnityEngine;
using System.Collections;

public class EnemyAI : MonoBehaviour {

#region PUBLIC VARIABLES

public ENEMY_STATE states;

#endregion


#region UNITY METHODS

public void Awake()
{
 states = ENEMY_STATE.IDLE;
}

public void Start()
{
 StartCoroutine(EnemyFSM());
}

#endregion

#region ENEMY COROUTINES

IEnumerator EnemyFSM()
{
while(true)
{
  yield return StartCoroutine(states.ToString());
}
}

#endregion

}

#region enums

public enum ENEMY_STATE
{
  IDLE = 0,
  CHASE = 1,
  ATTACK = 2
}

#endregion

}

In Awake() we initialise our enum to the IDLE-value. In Start() we’re calling the Coroutine with the “StartCoroutine”-Method.

Let’s have a look at the Coroutine itself.

IEnumerator EnemyFSM()
{
while(true)
{
  yield return StartCoroutine(states.ToString());
}
}

As you see we have a while-loop. Why is that? As you may know the Start()-Method runs only once. But we want our AI to behave all the time. That’s why we use a while-loop and check for true. This way the Coroutine will execute every frame. Like Update.

Since the function “StartCoroutine()” can have a string as parameter we use just .ToString() on our ENEMY_STATE enum. But of course nothing will happen when we attach the script to an object and run the game in Unity. You should expect some errors. To be exact, you should get an error like this:
Coroutine ‘IDLE’ couldn’t be started!

Why?

IEnumerator EnemyFSM()
{
 yield return StartCoroutine(states.ToString());
}

As stated above, “StartCoroutine” can expect (among other parameters) a string. This string has to be the name of the Coroutine you want to start. In this case we’re starting Coroutines which have the same names as our ENEMY_STATE values. So this method wants to start a Coroutine which is called “IDLE” or “CHASE” or “ATTACK”.
But we don’t have any Coroutines with these names. So let’s make them!
Here’s our IDLE-Coroutine:

You can add these lines of code after our EnemyFSM-Coroutine.

IEnumerator IDLE()
{
 // ENTER THE IDLE STATE
Debug.Log("Alright, seems no evil Player is around, I can chill!");

// EXECUTE IDLE STATE
  while(states == ENEMY_STATE.IDLE)
  {

    yield return null;
  }

// EXIT THE IDLE STATE

Debug.Log("Uh, I guess I smell a Player!");
}

Now there won’t be any errors, since our script has indeed a Coroutine which has the same name like on of the ENEMY_STATE values!

Everytime the ENEMY_STATE is set to “IDLE” via Code or the Unity Inspector (public variables are exposed in the Inspector!) you will see exactly one Debug.Log which says “Alright, seems no evil Player is around, I can chill!”. And if you change the state you’ll see the Log “Uh, I guess I smell a Player!”.

These Debugs are just signalizing the enemy entered/exit the current state. Of course you can run there code, too, for example setting up certain variables.

The important thing is the while-loop. Already used in our first Coroutine, the principle is the same: we don’t want our behaviour to run once. We want it to run all the time. But this time with a little difference. The very first Coroutine is still working every frame.
But the Execution/Loop of our IDLE-Coroutine should only loop as long our AI is in it’s IDLE-State.
If you would use “while(true)” instead of “while(states == ENEMY_STATE.IDLE)” the Coroutine would never stop and it wouldn’t exit itself. That’s not what we want!

You may ask yourselves: “Well, it behaves like the Update()-Method. Why don’t we just use the Update()?”
As stated above: I wanted to have a clean and more flexible way to deal with my AI-Logic. Maybe you do, too!

What we can do now is something like this:

// EXECUTE IDLE STATE
  while(states == ENEMY_STATES.IDLE)
  {
    yield return new WaitForSeconds(1.0f);

    Debug.Log("Sitting here...");

    yield return new WaitForSeconds(1.0f);

    Debug.Log("...and waiting!");
  }

If I’d use Update() I had to use a Timer and needed an If-Clause to check if the timer reaches a certain value
in order to tell him to Log those phrases above. Our Coroutine does this way better:

– we’re entering the state
– first it waits 1 second
– then it logs “Sitting here…”
– then it waits again 1 second
– at last it logs “…and waiting!”

After that the loop starts again, since we’re still in the same state. And it loops as long as we don’t change the state!

In that Execution-Block you could move the Enemy from one point to another, let him wait some seconds and then move him back! That and more are the powers of Coroutines! Go and try to implement the other states and fool around with Coroutines!

I hope you could learn something. If you have problems with the code-snippets, have questions or even suggestions feel free to comment!