Wednesday 17 February 2016

State machine using inheritance

In this post I want to show you how I like to go about creating a state machine. A state machine is usually used (or at least, I use it) for things like the game states (pause, playing, gameover,ecc) or AI for NPCs, like different enemy states (patrolling, chasing, shooting...).

This is a fairly standard method and we can achieve the same thing using interfaces. If you want to know how you can use interfaces to do this, I suggest you to take a look at this tutorial found on the Unity official website.

In this example I will show you some code of an hypothetical game controller which can run 3 states: playing, pause and gameover.

First thing, the State class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public abstract class State {

 protected string stateName;

 public abstract void OnStateEnter ();

 public abstract void UpdateState ();

 public abstract void OnStateExit ();

 public string getStateName()
 {
  return stateName;
 }

}

The State class is an abstract class, which means we will not be able to create instances of it. This is because all the states we are going to write are going to be derived from this base class, which is only intented to be used as a parent and we will not need objects of its type.

The string variable stateName is simply used to identify the state we are in in the console.

The 3 main methods used are also abstract methods. This implies that the classes which derive from this class MUST implement the methods body, which, in more complex projects, will most definitely behave in different ways.

Now that we have the base class ready, we can go on and write the 3 classes for our 3 states.

The pause state:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class GameController_PauseState : State {

 GameObject obj;

 public GameController_PauseState(GameObject g,string name)
 {
  stateName = name;
  obj = g;
 }

 public override void OnStateEnter ()
 {
  Debug.Log (obj.name + " enetered " + stateName); 
 }

 public override void OnStateExit()
 {
  Debug.Log (obj.name + " exited " + stateName);
 }

 public override void UpdateState ()
 {
  Debug.Log (obj.name + " is running " + stateName);
 }


}

The playing state:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class GameController_PlayingState : State {

 GameObject obj;

 public GameController_PlayingState(GameObject g,string name)
 {
  stateName = name;
  obj = g;
 }

 public override void OnStateEnter ()
 {
  Debug.Log (obj.name + " enetered " + stateName);
 
 }

 public override void OnStateExit()
 {
  Debug.Log (obj.name + " exited " + stateName);
 }

 public override void UpdateState ()
 {
  Debug.Log (obj.name + " is running " + stateName);
 }
}

The game over state:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class GameController_GameOverState : State {

 GameObject obj;

 public GameController_GameOverState(GameObject g,string name)
 {
  stateName = name;
  obj = g;
 }

 public override void OnStateEnter ()
 {
  Debug.Log (obj.name + " enetered " + stateName);

 }

 public override void OnStateExit()
 {
  Debug.Log (obj.name + " exited " + stateName);
 }

 public override void UpdateState ()
 {
  Debug.Log (obj.name + " is running " + stateName);
 }

}

You will notice that these three classes very much look alike, and that is because for this simple example these three states don't do much more than displaying the name of the state that is acutally running. In a real game development environment you will want these classes to perform specific actions: for example, in the OnStateEnter() of the GameController_PlayingState class you will want to enable all the movement and input detection, whereas in the GameController_GameOverState you will do the exact opposite.

For each state I declared a GameObject variable, which is a reference to the game object which all these states are going to be part of. In our case, a GameController object. I could have very well put that variable in the State class.

The constructor is the same for each class, which simply gets passed the state name and the object reference.

Then, the three abstract methods are overridden. It is important to use the override keyword, and that is because an abstract method is implicitly a virtual method. If you omit the override keyword the compiler will start screaming at you.

Now the three state classes are written we can create the actual monobehaviour object script, which I called GameController.



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class GameController : MonoBehaviour {

 State currentState = null;

 GameController_PauseState pauseState;
 GameController_GameOverState gameoverState;
 GameController_PlayingState playingState;

 void Start () {

  pauseState = new GameController_PauseState(this.gameObject,"Pause State");
  gameoverState = new GameController_GameOverState(this.gameObject,"GameOver State");
  playingState = new GameController_PlayingState(this.gameObject,"Playing State");

  setState (playingState);
 
 } 

 void Update () { 

  currentState.UpdateState (); 
 }

 public void setState(State newState)
 {
  if(currentState!=null)
   currentState.OnStateExit();

  currentState = newState;

  currentState.OnStateEnter ();
 }

 GameController_GameOverState getGameOverState()
 {
  return gameoverState;
 }

 GameController_PauseState getPauseState()
 {
  return pauseState;
 }

 GameController_PlayingState getPlayingState()
 {
  return playingState;
 }
}

Here we can see the beauty of inheritance and polymorphism.

We create a State type variable and we set it to null. Remember, we cannot instanciate objects of type State, but we can create variables of that type.

Then I create the 3 members of three different state types and, in the Start() method, I initialize them calling their respective constructor.

Next, the setState(...) method: this method accepts a State type variable which represents the states we wish to transit to. In its body, we first ensure the current state variable is not null, which is definitely going to be when the game starts as it has only been initialized to null.

Otherwise, if we are transiting from another state, the OnStateExit() of that particular state will be called.
After that, we simply assign the new state to the current state variable and call the OnStateEnter().

In the Update(), we simply call the UpdateState() method, which will run for which ever state object is contained in the currentState member.

This method is of course a public method so it can be called by other game objects in order to change the game state: for example, imagine your player being killed by an enemy while in the playing state. At this point you want to switch to the GameOver state and display a game over screen. From the player script you can call setState(...) and pass it in its own variable gameoverState using the getGameOverState() method.

This is a pretty decent and organized way to create a state machine, which can be used for different purposes.


No comments:

Post a Comment