The previous pattern illustrated not only 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 that all states inherit (are subclassed) 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.
This recipe builds upon the previous recipe. So, make a copy of that project, open it, and then follow the steps for this recipe.
To manage an object's behavior using the state pattern architecture, perform the following steps:
GameManager
with the following:using UnityEngine; using System.Collections; using UnityEngine.UI; public class GameManager : MonoBehaviour { public Text textGameStateName; public Button buttonWinGame; public Button buttonLoseGame; public StateGamePlaying stateGamePlaying{get; set;} public StateGameWon stateGameWon{get; set;} public StateGameLost stateGameLost{get; set;} private GameState currentState; private void Awake () { stateGamePlaying = new StateGamePlaying(this); stateGameWon = new StateGameWon(this); stateGameLost = new StateGameLost(this); } private void Start () { NewGameState( stateGamePlaying ); } private void Update () { if (currentState != null) currentState.StateUpdate(); } public void NewGameState(GameState newState) { if( null != currentState) currentState.OnMyStateExit(); currentState = newState; currentState.OnMyStateEntered(); } public void DisplayStateEnteredMessage(string stateEnteredMessage){ textGameStateName.text = stateEnteredMessage; } public void BUTTON_CLICK_ACTION_WIN_GAME(){ if( null != currentState){ currentState.OnButtonClick(GameState.ButtonType.ButtonWinGame); DestroyButtons(); } } public void BUTTON_CLICK_ACTION_LOSE_GAME(){ if( null != currentState){ currentState.OnButtonClick(GameState.ButtonType.ButtonLoseGame); DestroyButtons(); } } private void DestroyButtons(){ Destroy (buttonWinGame.gameObject); Destroy (buttonLoseGame.gameObject); } }
GameState
:using UnityEngine; using System.Collections; public abstract class GameState { public enum ButtonType { ButtonWinGame, ButtonLoseGame } protected GameManager gameManager; public GameState(GameManager manager) { gameManager = manager; } public abstract void OnMyStateEntered(); public abstract void OnMyStateExit(); public abstract void StateUpdate(); public abstract void OnButtonClick(ButtonType button); }
StateGamePlaying
:using UnityEngine; using System.Collections; public class StateGamePlaying : GameState { public StateGamePlaying(GameManager manager):base(manager){} public override void OnMyStateEntered(){ string stateEnteredMessage = "ENTER state: StateGamePlaying"; gameManager.DisplayStateEnteredMessage(stateEnteredMessage); Debug.Log(stateEnteredMessage); } public override void OnMyStateExit(){} public override void StateUpdate() {} public override void OnButtonClick(ButtonType button){ if( ButtonType.ButtonWinGame == button ) gameManager.NewGameState(gameManager.stateGameWon); if( ButtonType.ButtonLoseGame == button ) gameManager.NewGameState(gameManager.stateGameLost); } }
StateGameWon
:using UnityEngine; using System.Collections; public class StateGameWon : GameState { public StateGameWon(GameManager manager):base(manager){} public override void OnMyStateEntered(){ string stateEnteredMessage = "ENTER state: StateGameWon"; gameManager.DisplayStateEnteredMessage(stateEnteredMessage); Debug.Log(stateEnteredMessage); } public override void OnMyStateExit(){} public override void StateUpdate() {} public override void OnButtonClick(ButtonType button){} }
StateGameLost
:using UnityEngine; using System.Collections; public class StateGameLost : GameState { public StateGameLost(GameManager manager):base(manager){} public override void OnMyStateEntered(){ string stateEnteredMessage = "ENTER state: StateGameLost"; gameManager.DisplayStateEnteredMessage(stateEnteredMessage); Debug.Log(stateEnteredMessage); } public override void OnMyStateExit(){} public override void StateUpdate() {} public override void OnButtonClick(ButtonType button){} }
OnClick
action to call the BUTTON_CLICK_ACTION_WIN_GAME()
method from the GameManager component in the Main Camera GameObject.OnClick
action to call the BUTTON_CLICK_ACTION_LOSE_GAME()
method from the GameManager component in the Main Camera GameObject.The scene is very straightforward for this recipe. There is the single Main Camera GameObject that has the GameManager
script object component attached to it.
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:
ButtonType
, which defines the two possible button clicks that the game might generate: ButtonWinGame
and ButtonLoseGame
.gameManager
variable: so that each state object has a link to the game manager.GameManager
: that automatically makes the gameManager
variable refer to the passed in GameManager
object.OnMyStateEntered()
, OnMyStateExit()
, OnButtonClick(…)
, and StateUpdate()
. Note that abstract methods must have their own implementation for each subclass.When the GameManager
class' Awake()
method is executed, three state objects are created, one for each of the playing/win/lose classes. These state objects are stored in their corresponding variables: stateGamePlaying
, stateGameWon
, and stateGameLost
.
The GameManager
class has a variable called 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 stateGamePlaying
object.
For each frame, the GameManager
will receive Update()
messages. Upon receiving these messages, GameManager
sends a StateUpdate()
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 calls its (in this case, empty) StateUpdate()
method.
The StateGamePlaying
class implements statements in its OnButtonClick()
method so that 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 Button-win, the NewState()
method is passed to gameManager.stateGameWon
.
3.12.108.175