A game state can be thought of like a mode or phase of the game. The play can be playing the game, the game can be initializing or the game could have come to an end. These three states can be found in most games so they are used here. So open the GameManager Script in Visual Studio Code and add the following code just after the BlockType enum.
enum GameState{
GS_INIT,
GS_PLAYING,
GS_END,
};
So we have created three states for initialization, playing and ending the game. What we want is that when the game starts, it should be in initialisation state which for us will be to show the StartMenu, then when the player clicks the Play button, the state should change to Playing and the startMenu should be removed from the screen. When the player jumps on an empty space instead of the road, the state should then switch to End and bring back the start menu.
We however have a problem, our mouse event listener is on from the moment the game loads which can lead to playing the game while the start menu is still on the screen which would be weird. So we need to disable the mouse event listeners and only enable them when the start switches to Playing. So open the playerController Script and change the setInputActive function so that it looks as shown below.
setInputActive(active: boolean) {
if (active) {
//switch on mouse event listener
input.on(Input.EventType.MOUSE_UP, this.onMouseUp, this);
} else {
//switch off mouse event listener
input.off(Input.EventType.MOUSE_UP, this.onMouseUp, this);
}
}
So the function now has a boolean named "active". From the GameManager, we will be sending either true or false, if true comes through, then we switch on the listener, if false comes in then we switch off the listener. For this to happen, we need to be able to pass information to this function from the GameManager Script. So open the GameManager Script and add the playerController reference. We will also want to manipulate the StartMenu so we will also create a reference for it right now.
//reference to the playerController
@property({type: PlayerController})
public playerCtrl: PlayerController | null = null;
//reference to the startMenu
@property({type: Node})
public startMenu: Node | null = null;
This will create 2 empty sections on the Game Manager Node in the Cocos Creator editor. So save and go back to the editor. Click on the Game Manager and see the inspector. There will be a start Menu and Player Ctrl sections waiting to be filled in. So drag the Player and startMenu nodes to their respective sections as shown in the figure.
Now that we have references to the correct Nodes, we can now write code that can switch between the states. When the game starts, it needs to set the state to initialization (GS_INIT). Open the GameManager and add the following code making changes to the generateRoad() function.
start () {
//set the current state to initialization
this.curState = GameState.GS_INIT;
}
init() {
// Activavte the start menu
// We make sure that the menu exists just in case we did not drag it
if (this.startMenu) {
//we enable the start menu to be visible
this.startMenu.active = true;
}
// Generate the road
this.generateRoad();
if(this.playerCtrl){
// Disable user input since we are on the menu
this.playerCtrl.setInputActive(false);
// Reset the player's position to 0,0,0
this.playerCtrl.node.setPosition(Vec3.ZERO);
}
}
//a way to switch between different states
set curState (value: GameState) {
switch(value) {
case GameState.GS_INIT://switch to initialization
this.init();
break;
case GameState.GS_PLAYING://switch to playing state
if (this.startMenu) {
this.startMenu.active = false;
//we remove the startMenu because the we are now playing
}
setTimeout(() => {
if (this.playerCtrl) {
this.playerCtrl.setInputActive(true);
}
}, 0.1);
//we make switch on the input after 0.1 seconds
//of switching to playing state
break;
case GameState.GS_END:
break;
}
}
To make the button work, we need to create an event listener that waits for the button to be clicked. So open the GameManager Script and add the following code
onStartButtonClicked() {
//change the game state to playing
this.curState = GameState.GS_PLAYING;
}
To make this code run, we need the button to be linked to this function. So save the script and go back to the editor. Click on the PlayButton in the startMenu and go to the inspector. Find the section that says "Click Events". We can change the value from 0 to 1 because we will be running one function. Since the function is written in the GameManager script, you will need to drag the game manager on to the section that says cc.Node under click events. This will allow us to access the Game Manager's script. Make sure to select the GameManager script on the second section and then select the function "onStartButtonClicked" on the third option. The screenshot below shows the necessary sections to alter.
At this point try running the project and see if the mouse inputs are disabled when the game loads and then click the play button and see that the startMenu disappears and mouse input returns.
Right now when we play the game, the player is not dying for any reason, what we can do to make it more interesting is to make it such that if the player steps on an empty space, they can be dead and this should trigger the game over state which should disable the mouse input for the player and bring the startMenu back on the screen. Open the PlayerController script and add the followin code.
//a variable to know how many steps the player has made
private _curMoveIndex = 0;
You will also need to edity the jumpByStep() function so that at the bottom of the already written code, it should also add the step to the _curMoveIndex
jumpByStep(step: number) {
// previous code doing other stuff...
// then we add the number of steps (1 or 2)
//to the move index
this._curMoveIndex += step;
}
We should then be able to send the _curMoveIndex value as part of our listener when a jump is completed. We would know how many steps the player made. So in the PlayerController script we add the following function.
onOnceJumpEnd() {
this.node.emit('JumpEnd', this._curMoveIndex);
}
We can now implement some rules to check whether the player is on an empty space or if the player has gone beyond the 50 blocks that we have set all of which should mean that the player has died and so the game is over. When the game is over we need to switch to the startMenu by changing the game state. This checkResult() function should be in the GameManager script
checkResult(moveIndex: number) {
//we get the position of the player
//we then make sure that the position is less than the road length (50)
if (moveIndex < this.roadLength) {
// Jumped on the pit
if (this._road[moveIndex] == BlockType.BT_NONE) {
//Since the player is within the 50 spaces,
//we check if they are stepping on an empty space
//we end the game if they are
this.curState = GameState.GS_INIT;
}
} else { //the player has gone beyond the road (more than 50)
this.curState = GameState.GS_INIT;
}
}
We can then check wether the player's listener on ending the jump has been trigered and run the function that checks where they would have landed. So first go to the start() funtion in the GameManager script and make sure it looks as shown below.
start () {
this.curState = GameState.GS_INIT;
//we check if the player JumpEnd has been triggered using the concise way
this.playerCtrl?.node.on('JumpEnd', this.onPlayerJumpEnd, this);
//this code is the shortcut of the one commented below.
// if(this.playerCtrl ! = null) this.playerCtrl.node.on('JumpEnd', this.onPlayerJumpEnd, this).
}
If the jumpEnd is triggered we can create a function that calls the checkResult() with the moveIndex supplied. This is the onPlayerJumpEnd() function. You can create it under the startMenu.
onPlayerJumpEnd(moveIndex: number) {
this.checkResult(moveIndex);
}
The only major thing left to prevent problems is to make sure the _curMoveIndex is reset when the game state chanages because we dont want a new game starting with the previously recorded steps. So we need a reset() function. This will be in the PlayerController script.
reset() {
//set the number of moved steps back to zero
this._curMoveIndex = 0;
}
We can then call this function whenever the init() function runs which only runs when we switch to the initialization game state. We will call this reset() function from the GameManager script.
The screenshot below shows how the init() function should now look and pay attention to the last line for the actual reset() function call.
At this point, the game should be able to now end when you go beyond the road or when you jump on an empty space.