How to program a good and efficient AI?

Hello
Ive been programming for the past 3 years in C# inside unity, until now I thought my AI skills were acceptable and able to achieve every goal I have needed, but I many times get into situations that makes it diffcult to handle many states of an AI.
I did not follow any article or a tutorial, I just came up with a solution that I thought was good enough.

What I would really appriciate, is if you could judge my way of making AI. Im not familiar with other ways, and I dont know if my way is good enough. Or if you know of a good source that teaches a way of creating a good AI, please post it here :slight_smile:

This is how I currently program any AI:

1. I get a list of every state this AI can be, lets say it can:
a. Idle
b. Walk
c. Chase an entity
d. Attack
e. Get hit
f. Die

2. I find out how to link each of the states to another state(s), causing evey state to go to another eventually:
Idle → Walk (When a random time (4 ~ 20) has passed)
Idle → Chase an entity (When an entity is close enough (10 units) and can be seen)
Chase an entity → Attack (When the entity is close enough (1 unit) and can be attacked)
Chase an entity → Idle (When an entity is unchaseable (too far, cannot be seen or dead))
… and so on …

3. I write a coroutine for each of these states, linking every coroutine to another just like I have wrote.

private IEnumerator IdleRoutine()
{
    float startTime = Time.time;
    float randomTime = Random.Range(4, 21);

    do
    {
        // Check for an entity to chase. if found, chase it
        Entity entity = FindEntityToChase();
        if(entity != null)
        {
            // Start the chase entity state.
            StartCoroutine(ChaseEntityRoutine(entity));
            // Leave this state.
            yield break;  
        }        

        yield return null; 
    }
    while(Time.time < startTime + randomTime);

    // Code here initiated when the random time has passed, Starting the walk state:
    StartCoroutine(WalkRoutine());
}

This is something I just wrote, I didnt completed everything since I dont want this to take too much space, but I hope many of you get the idea.

The problems with my approach:
Take a second to think about the Get Hit state, it isnt linked to another state, instead, it is something which depends not on this script, but on the player script, or any other entity script (That can attack this AI).
The AI can not know when it will get hit, it can just wait until a player hits it.
This is one reason to be very careful, because when getting hit, you want to exit the current state you are in, you certently dont want to stay in the Idle state while you are getting hit… this will make an AI behave very badly, at any given point, an AI should only be on one state.

Unity lets you exit ALL coroutine at any time by calling the method: StopAllCoroutines(), but using this method has proven to be a bad idea, if not being careful.

Why? Lets say the AI is in a middle of a very important state, a state that changes the behaviour of many of its components and variables which are handled only by that state, like in the Attack state: In this state the AI instantiates an arrow, and aiming it to the player. when the AI gets hit, during this state, the arrow should be gone.
But when you get hit, you dont have any access to the coroutine currently on (the AttackRoutine in this example), you may stop it, but what about the arrow? you will need a reference to it, in order to destroy it, which isnt a hard thing to do, but sure can get hard to follow when having 20 states, or even 50 states the AI can be in.
The AI may have 20 different skills which each one of them creates a different object, changes values of the enemy like the speed, jump height… something you would like to reset when the state is over.
A run state which changes the speed to 30, get canceled because of the Get Hit state, if the speed value is not resetted, it will make the AI move at the speed of 30 instead of 20 when reaching the Walk state.

What I currently do to handle this issue, is to create a delegate which holds a ā€œBreak Functionā€ to the state the AI is currently in.
What the break function does, is to ā€œresetā€ the AI to the normal values after the current state has changed them, and whenever I call the function StopAllCoroutines(), I also make sure I Invoke the delegate referencing the break function.

Is this way of coding complex AI is the right way? If not, can you give me an example or tell me what to change with my way of coding AI?

Sorry about the long post S: but I really need to know, because my project im currently working on has a variety of complex AI’s

1 Like

Really need an answer to this S:

You’re probably not getting a response because you’re not really asking a concrete question. Is this the right way to do this? There are a bunch of ā€œrightā€ ways to solve a problem like this. The first question you’d probably get back is - does it work? If the answer is Yes then you’re already moving in the right direction.

Really though - if you have a good FSM written then most of the ā€œresettingā€ should be handled in each state’s individual Exit() method. So, for example. the Exit method in the Attack state should check if an arrow is present and destroy it. Attack doesn’t care why it’s exiting or who told it to exit - it just does its clean up and moves on. For transitional type states like Get Hit you would need some way of telling the FSM that it should revert to its previous state once Get Hit completes.

I have done things similar to this in the past. It worked, but it had issues. I have also done AI differently in the past, which also worked, but had issues. In my current project, rather than use something I was familiar with, I did a little research and decided to go with a behavior tree approach. Guess what? It works, but has issues.

There is no AI silver bullet algorithm, which is why there are so many viable alternatives that you can read about online. Each method has pros and cons. What you are doing here will probably work, though at times you will hate it and think that it is probably the worst possible way to do things, but it isn’t. They are all like that.

Regarding your specific question about the GetHit state, you probably want certain states to be ā€œinterrupt statesā€. They can be entered from anywhere based on events occurring, like being hit by something. You can decide if you want the interrupt to always return to the previous state or if it should always transition back to Idle and start over. You could even make this an option so the interrupt state can decide for itself.

1 Like

A ā€œbigā€ question!

To be truly honest, what helped me, was to read, read, read and take some AI courses. Lot of good books out there, if you need some titles i will gladly post them.

here ya go, champ.

This should be a good first step you take. There’s more books to read, more people to learn from, more classes to take. There’s no short cuts when you want to make good games. Educate yourself.

If you can, that would be great :slight_smile:

Thank you! will be checking that soon

I’m a bit of a noob but why not just a switch(State) in a OnThink() update routine with some custom behavior for each state ? What’s the advantage of using coroutines ?

Actually this book IS! very good, so start there.

Because that becomes a disaster when you have more than a handful of states. :slight_smile:

Go check out some Finite State Machine patterns to get a clearer picture.

I started by reading this which I thought was awesome.

I would suggest looking into how to write an FSM using the State Design Pattern.
Matt Buckland goes into extensive detail on how to do this in his excellent book ā€˜Programming Game AI by Example’.
And is well worth the money for anyone wanting to improve their AI skills.

The code samples are in C++ but if you have a rudimentary understanding of pointers and pointers to functions (much like delegates) you’ll understand the code, but more important is understanding the concept.

The State Design pattern revolves around an agent only being in one possible state at a time, and using a state transition diagram (similar to what you have mentioned in your point #2), you can inject each state (in it’s own separate class) into the agent and set that as their current state, without needing to have any if/else statements or other conditional spaghetti in your agents code.

I’m just brainstorming this, but why not have a function that checks for the delta change in health points, then have the AI use the CharacterControllerHit(i think the rigidbofy collider can do the same thing, if you’re using that) function to check in the direction of where the hit took place?

Thanks, I will look at that as well.

This is something Im trying to avoid, in a perfect code (in my opinion), you check only what is your job to check.
Its like, using your feet to turn off the lights in your room… It’s possible, but not the mose efficient way of doing so.
So why should I check every frame, if I lost health, when I can know exactly when I will get hit?

So its either:

  1. Im checking every frame if I lost health, when I do, Im doing some resetting, and exiting the coroutine.
  2. Im getting hit, exit all coroutines, and invoke the delegate to the last Reset function (Which I set from every coroutine im entering).

In RL, that’s what your body does, it constantly checks if it’s sick or infected or wounded. It does wait for the other to tell it it’s in pain, but does it’s own calculations as what to do with it.
Also, using enumerators and switches in update, I’ve never had to use stopcoroutine ever. It works well for me.

That’s the worst analogy ever. :slight_smile: Your body doesn’t have to be concerned with running at 60 frames per second.

If you don’t HAVE to incur the cost of checking for lost health every frame then why would you? Also - a delegate that stops a state’s coroutine is a concrete codification of ā€œthis event happenedā€. I’m not sure what you have against StopCoroutine.

Buckland’s FSM pattern is pretty great (as Meltdown and myself have already linked to his book). I would really suggest you check it out.

1 Like