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.
To manage an object's behavior using the state pattern architecture, perform the following steps:
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(); }
// 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(); } }
// 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"); } }
// 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"); } }
// 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"); } }
GameManager
object, and each of the three state objects (StateGameLost
, StateGamePlaying
, and StateGameWon
):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.
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:
g
ameManager
: so each state object has a link to the game manager)Awake()
: that automatically makes variable gameManager
refer to the GameManage
r
object in the Main Camera once the scene is running)OnStateEntered()
, OnStateExit()
, StateUpdate()
and StateGUI()
: abstract methods must have their own implementation for each subclass18.218.171.212