State machines

In life, as well as game development, state machines (or Finite State Machines (FSM) as they are more commonly called) are a core component for day-to-day running. At a basic level, they tell us exactly what we are doing right now, what we were doing previously, and what we can do next.

They are commonly used for:

  • Menu systems
  • Game-level transitions
  • AI/Behaviors

We can implement these within games in various ways, from the very basic (and generally hard to manage) to a more ordered system and beyond with full state managers.

A basic state machine is like a flowchart and looks something like the following diagram:

State machines

Defining states

In all implementations, we start with a collection of states: these define both what conditions/states are in the game and what we do when that state changes.

These states describe both what can happen when that state is active and what other potential states could result in an action from the current state. If we take the example from UnityGems, which describes a simple case using a television(TV), we would end up with the states listed in the following table:

State

Description

Actions

TV off

No activity is present and nothing is displayed.

The power button turns the TV on.

TV on

The TV displays images and plays sound.

The power button turns the TV off.

The up button selects the previous channel.

The down button selects the next channel.

The menu button displays the menu.

Menu displayed

The TV displays the menu, overlaying the normal display.

The power button turns the TV off.

The menu button turns the TV on (menu hidden).

The up button highlights the previous menu item.

The down button highlights the next menu item.

The ok button activates the menu item.

So from each individual state, there are a number of options; in some cases, the same action will lead to the same result (such as the power button), some actions will do different things based on what the current state is (such as the up and down buttons).

It's important to note that in any game, you will likely use many state systems, from menus to in-game controls and AI.

So once you have your collection ready, the next step is to define an enumeration in C# as follows, for example, using the previous states:

enum TvState
{
  Off,
  On,
  Menu
}

Simple singular choice

The simplest way to implement a state system is using the C# switch statement; the benefit here is that there can only be a single result:

if (Input.GetButtonDown("Up"))
{
    switch (currentTvState)
    {
        case TvState.Off:
            //Nothing, tv is off
            break;
        case TvState.On:
            //Channel Up
            break;
        case TvState.Menu:
            //Menu selection up
            break;
    }
}

So as you can see in the previous example, we have simply implemented the pattern for the Up button on the remote, and depending on what the television is doing currently, it will act appropriately.

This is good for menus, but is limiting in situations where based on the state, we might want to do multiple things.

Planning for multiple cases

The alternate simple approach to state machines is to use the if blocks to test what a state is: the only downside is that this can become very cumbersome to manage very quickly. Consider a slightly more complex scenario (related to the game) where a group of thugs are battling with you, but they are only confident when they are in a group and will run if their health is good. Such a system wouldn't be possible using the previous switch style (or at least will be difficult to do so), so by using several if blocks as shown in the following code, we can achieve something like this:

if (EnemyState == State.Idle)
{
    //Check for player
    // If player found EnemyState == State.Attacking
    //Check for fellow enemies
}
 
if (EnemyState == State.Attacking && PlayerState == State.Idle)
{
    //Enemy Sneak attack
}
 
if (EnemyState == State.Attacking)
{
    //Play Attacking Music
}
 
if (EnemyState == State.Attacking && Health < 5)
{
    //Run away
}
 
if (EnemyState == State.Attacking && PlayerState == State.RunningAway)
{
    //Give Chase
}

Now, although the previous code can be nested or transformed into switch statements, writing it this way gives us other advantages: for one, we control when and under what conditions certain things will happen, for example:

  • Battle music will always be played when the battle begins
  • Enemies will chase the player unless they have low health
  • At any point that the player is idle, the enemies will have a sneaking advantage

However, with either system, you are going to end up with a lot of code-making decisions around your game, such as the player, enemies, NPCs, and so on. This will make it hard to manage, and even worse to try debug; perhaps Unity offers us another way?

State managers

Following on from the Animation tutorial in Chapter 2, Building a Character, we have seen that Unity has a very powerful state machine system built in it already using Mecanim. We have only used it for animation so far, but like AnimationCurves, we can use this to build a nice graphical system that is easier to maintain.

Note

Although the state machine is very powerful for controlling what states are available and how they transition between states, it can't actually implement actions (other than animation). There are triggers built into the state system, but these are not fully supported on all platforms. So if you use them, keep it limited.

To achieve this properly, you need to separate out the responsibilities for what does what within the state system into the following parameters:

  • Inputs: What factors will be fed into the state system to affect change
  • The decision engine: The core logic that drives the state machine
  • Outputs: What the game will do based on the current state
State managers

The previous diagram shows an example of how you would componentize your state machine; this pattern is very extensible because it means you can apply separate scripts for each of the inputs, which also means many areas of the game can have an input to the state system. The outputs/reactions to states or state changes can also be componentized (but don't have to be) so that you can swap and change AI behaviors to the different states based on what you are implementing them on. Enemy 1 may be very brave and just act, and Enemy 2 might be a bit more cautious and require other enemies close by before attacking.

Implementing this in Mecanim Animation controllers is very simple since at its heart it is a state machine itself, as shown in the following screenshot:

State managers

In the previous screenshot, we can see a simple example of this: there are no animations connected to any of the states. We are just using them to track and control what drives our state machine. Using the parameters, it's easy to configure the following settings:

  • If the player is seen, the enemy attacks
  • If the player is seen and is attacking, the enemy should defend
  • If the player attacks when the enemy is attacking, the enemy should defend
  • If the player stops attacking, then the enemy should attack back
  • If at any time the enemy health is less than 2 and the player's health is greater than 2, the enemy should run away
  • If at any time the enemy loses sight of the player, then go back to idle

So by controlling the input, we know how the enemy will behave, and this is completely configurable within the controller without any complex scripting.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.129.90