Mon. Dec 9th, 2019

Automation Mission

Nick Proud – Software Engineer

Oh Behave! – Making Simple Game AI in Unity

5 min read

I originally posted this on my old blog, ‘ProudCookie’ but since moving to AutomationProgrammer I feel this has a place here too.

Last year, I reached the point during the development of my video game, ‘Protectorate’ in which I needed to implement some basic AI.

As a Software Engineer, I work mostly in automation. Automation brings with it a certain need for intelligence, even if it is very basic. These days, the hot AI topics are machine and deep learning. The closest my work has brought me to these has been the odd spot of sentiment analysis.

So, faced with newborn, vacant game characters standing idly on the deck of their brand new space station I thought it best to do some research into the best techniques for implementing and managing their behaviour.

I needed a straightforward way to give each character a state and have them perform a specific task based on the state they were in. This raised questions such as

  • How will I transition each NPC between states?
  • How can I make each individual NPC perform a specific task depending on the state they are in?
  • How is each behaviour contained within each state?

I read a lot of inaccessible, wordy and scary-looking text about managing game AI until I came across a solution which seemed to fit all the requirements: The Finite State Machine.

What is a Finite State Machine?

Put simply, a Finite State Machine is something which can exist in one of many states at any time. Each state carries with it behaviours that play out depending on the state. The important point in this explanation is that the machine has one of many states; the many states being finite. It is useful to think of the machine as an object which has x number of potential states and that the current state can change at any time. What’s more, when the state changes, the state’s containing behaviours are played out.

So how do we apply this to our game’s characters?

A Finite State Machine for Each Character

Picture the scene:

You are looking down at your space station. It has just received several new recruits who are standing around doing nothing. You are their God. They await your orders. I click on one of my characters to select them and then click on my new Arboretum. This character is going to go and tend the plants in a giant greenhouse.

I need to trigger behaviour and for that, I need to change the character’s state. Their current state is, well it’s nothing. They are idle. There is no behaviour tied to this state other than standing staring into space. However, when I select them and then select the Arboretum, i change the character’s state.

The character’s new state is enroutetosystem. Notice how the state is not goingtodosomegardening or similar. At this point, we don’t really care about the end result where the character starts manning the Arboretum because that is a new state that we will change to later. For now, we just want to head over there. Once we get there, we can change the state to manningarboretum which would bring with it a different behaviour.

We can change state at any time. So if I wanted to stop the character from walking while they were on their way to the Arboretum, I could change the state back to Idle. What behaviour does the idle state have? Nothing. So the character now just stands still. We have changed state and caused behaviour to change.

Whenever we change state, we transition from one set of behaviours to another. We say, ‘when you are in this state, do this thing,’ and I can change the one state anytime, but I must always stop the current state. This is the most important thing to remember about transitioning between behaviours in this way. The types of states I have are finite and I can only occupy one at a time. 

Programming a Finite State Machine

Finite State Machines can be as basic or as complicated as you want them to be. For Protectorate, I’m using the Unity game engine and consequently most of the logic and behaviours that I wish to run over a long period of time are encapsulated within coroutines. Each coroutine embodies a set of behaviours that a character can carry out. Furthermore, a couroutine is started when I enter a state and stopped when I exit it. The flow on a basic level is as below:

  1. Enter a state
  2. Start the associated coroutine.
  3. Exit the above state
  4. Stop the above coroutine
  5. Repeat for relevant required state.

Pseudo-code for the above might look like this:

ENTER IDLE STATE

START COROUTINE(DO NOTHING)

………..time passes

EXIT IDLE STATE

STOP COROUTINE(DO NOTHING

ENTER ENROUTETODESTINATION STATE

START COROUTINE(WALK TO DESTINATION)

………..time passes…….character arrives which triggers:

EXIT ENROUTETODESTINATION STATE

STOP COROUTINE(WALK TO DESTINATION)

ENTER MAN SYSTEM STATE

START COROUTINE(MAN SYSTEM) etc

In Unity we are writing C# code. For this purpose we can easily represent states as a set of constants within an enum.

public enum State
{
Idle,
EnRouteToSystem,
ManningSystem,
EnRouteToQuarters,
InQuarters,
EnRouteToGenericLocation,
Sleeping
}

Then I create an instance of the State enum with get; and set; properties, making it so that we can start or stop the relevant behavioural coroutines just by setting the state to one of the above values.

 private State _currentState;

    public State state
    {
        get
        {
            return _currentState;
        }
        set
        {
            ExitState(_currentState);
            _currentState = value;
            EnterState(_currentState);
        }
    }

Below, you can see how we exiting the current state and entering a new one causes new couroutines so be started and stopped, enabling behaviour to change instantly.

 void ExitState(State state)
    {
        switch (state)
        {
            case State.Idle:
                StopCoroutine("Idle");
                break;
            case State.EnRouteToSystem:
                StopCoroutine("EnRouteToSystem");
                break;
            case State.ManningSystem:
                StopCoroutine("ManningSystem");
                break;
            case State.EnRouteToQuarters:
                StopCoroutine("EnRouteToQuarters");
                break;
            case State.InQuarters:
                StopCoroutine("InQuarters");
                break;
            case State.EnRouteToGenericLocation:
                StopCoroutine("EnRouteToGenericLocation");
                break;
            case State.Sleeping:
                StopCoroutine("Sleeping");
                break;
            default:
                break;
        }

    }



void EnterState(State state)
    {
        switch (state)
        {
            case State.Idle:
                StartCoroutine("Idle");
                break;
            case State.EnRouteToSystem:
                StartCoroutine("EnRouteToSystem");
                break;
            case State.ManningSystem:
                StartCoroutine("ManningSystem");
                break;
            case State.EnRouteToQuarters:
                StartCoroutine("EnRouteToQuarters");
                break;
            case State.InQuarters:
                StartCoroutine("InQuarters");
                break;
            case State.EnRouteToGenericLocation:
                StartCoroutine("EnRouteToGenericLocation");
                break;
            case State.Sleeping:
                StartCoroutine("Sleeping");
                break;
            default:
                break;
        }
    }

It’s no SkyNet but it is at least an automated manner of changing and performing scripted actions. The nice thing about this way of implementing a Finite State Machine is that it is extensible. In theory, you should only need to add new states to the list. Your coroutines can be in whichever class they need to be in.

As for whether this is truly clean code…..let’s not deal with that in this post eh?

Share

Leave a Reply

Your email address will not be published. Required fields are marked *

Copyright © All rights reserved. | Newsphere by AF themes.