Managing complex object behavior with the state pattern

The previous pattern not only illustrated the usefulness of modeling game states, but also how a game manager class can grow in size and become unmanageable. To manage the complexity of many states and complex behaviors of states, the state pattern has been proposed in the software development community. Design patterns are general purpose software component architectures that have been tried and tested and found to be good solutions to commonly occurring software system features. The key features of the state pattern are that each state is modeled by its own class, and all states inherit (are sub-classed) from a single parent state class. The states need to know about each other in order to tell the game manager to change the current state. This is a small price to pay for the division of the complexity of the overall game behaviors into separate state classes.

How to do it...

To manage an object's behavior using the state pattern architecture, perform the following steps:

  1. Create a new C# script class called GameState:
    // file: GameState
    using UnityEngine;
    using System.Collections;
    
    public abstract class GameState : MonoBehaviour {
      protected GameManager gameManager;
      protected void Awake() {
        gameManager = GetComponent<GameManager>();
      }
    
      public abstract void OnStateEntered();
      public abstract void OnStateExit();
      public abstract void StateUpdate();
      public abstract void StateGUI();
    }
  2. Add the following script class to the Main Camera:
    // file: GameManager
    using UnityEngine;
    using System.Collections;
    
    public class GameManager : MonoBehaviour {
    
      public StateGamePlaying stateGamePlaying;
      public StateGameWon stateGameWon;
      public StateGameLost stateGameLost;
    
      private GameState currentState;
    
      private void Awake () {
        stateGamePlaying = GetComponent<StateGamePlaying>();
        stateGameWon = GetComponent<StateGameWon>();
        stateGameLost = GetComponent<StateGameLost>();
      }
    
      private void Start () {
        NewGameState( stateGamePlaying );
      }
    
      private void Update () {
        if (currentState != null)
          currentState.StateUpdate();
      }
      
      private void OnGUI () {
        if (currentState != null)
          currentState.StateGUI();
      }
    
      public void NewGameState(GameState newState)
      {
        if( null != currentState)
          currentState.OnStateExit();
    
        currentState = newState;
        currentState.OnStateEntered();
      }
    }
  3. Also add the following script class to the Main Camera:
    // file: StateGamePlaying
    using UnityEngine;
    using System.Collections;
    
    public class StateGamePlaying : GameState {
      public override void OnStateEntered(){}
      public override void OnStateExit(){}
    
      public override void StateGUI() {
        GUILayout.Label("state: GAME PLAYING");
        bool winGameButtonClicked = GUILayout.Button("WIN the game");
        bool loseGameButtonClicked = GUILayout.Button("LOSE the game");
    
        if( winGameButtonClicked )
          gameManager.NewGameState(gameManager.stateGameWon);
    
        if( loseGameButtonClicked )
          gameManager.NewGameState(gameManager.stateGameLost);
      }
    
    
      public override void StateUpdate() {	
        print ("StateGamePlaying::StateUpdate() - would do something here"); 
      }
    }
  4. Then add the following script class to the Main Camera:
    // file: StateGameWon
    using UnityEngine;
    using System.Collections;
    
    public class StateGameWon : GameState {
      public override void OnStateEntered(){}
      public override void OnStateExit(){}
    
      public override void StateGUI() {
        GUILayout.Label("state: GAME WON");
      }
    
      public override void StateUpdate() {	
        print ("StateGameWon::StateUpdate() - would do something here"); 
      }
    }
  5. Add the following script class to the Main Camera too:
    // file: StateGameLost
    using UnityEngine;
    using System.Collections;
    
    public class StateGameLost : GameState {
      public override void OnStateEntered(){}
      public override void OnStateExit(){}
    
      public override void StateGUI() {
        GUILayout.Label("state: GAME LOST");
      }
      
      public override void StateUpdate() {
        print ("StateGameLost::StateUpdate() - would do something here"); 
      }
    }
  6. The following screenshot shows how the Main Camera has scripted components for the GameManager object, and each of the three state objects (StateGameLost, StateGamePlaying, and StateGameWon):
    How to do it...

How it works...

The scene is very straightforward for this recipe. There is the single GameObject Main Camera which has four scripted object components attached to it:

  • GameManager
  • StateGamePlaying
  • StateGameLost
  • StateGameWon

The GameManager class has three public variables, one for each of the states (StateGamePlaying, StateGameLost, and StateGameWon). While these public variables are unassigned before the game runs, when the Awake() method is executed, GetComponent() statements are used to assigned these variables to the three state object components in the Main Camera. See the information box to understand why this is necessary.

Note

Cannot create MonoBehavior objects with the "new" keyword

If we want our state objects to be able to respond to Unity messages such as Update() and OnGUI(),the State class must inherit from MonoBehavior. However, due to the way Unity works, MonoBehavior objects cannot be created using the new keyword.

The straightforward solution to this is to attach an object of each state to the same GameObject as the GameManager. The GameManager object is then able to use GetComponent() statements to access each state of the objects.

Another alternative would be to use Prefabs and Instantiate().

The GameManager class has one other variable currentState, which is a reference to the current state object at any time while the game runs (initially, it will be null). Since it is of the GameState class (the parent of all state classes), it can refer to any of the different state objects.

After Awake(), GameManager will receive a Start() message. This method initializes the currentState to be the gamePlayingState object.

For each frame, the GameManager will receive Update() and OnGUI() messages. Upon receiving these messages, GameManager sends StateUpdate() and StateGUI() messages to the currentState object. So for each frame the object for the current state of the game will execute those methods. For example, when the currentState is set to game playing, for each frame the gamePlayingObject will display the win/lose GUI buttons, and will print a console message.

When the user clicks on a button, the gamePlayingObject will call the GameManager instance's NewState() method, passing it the object corresponding to the new state. So if the user clicks on Win the Game, the NewState() method is passed gameManager.stateGameWon.

A C# scripted class is defined for each state that the game needs to manage, for this example the three states StateGamePlaying, StateGameWon and StateGameLost. Each of these state classes is a subclass of GameState. GameState defines properties and methods that all subclass states will possess:

  • The variable gameManager: so each state object has a link to the game manager)
  • The method Awake(): that automatically makes variable gameManager refer to the GameManager object in the Main Camera once the scene is running)
  • The four abstract methods OnStateEntered(), OnStateExit(), StateUpdate() and StateGUI(): abstract methods must have their own implementation for each subclass

See also

  • The Managing object behavior with states recipe
..................Content has been hidden....................

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