Sweshi's Tutorials

Creating Game States

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.

Cocos Creator 3D Tutorial: Mind Your Step  - dragging the player and startMenu.
Adding code to change states

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; } }
Making the Button Work

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.

Cocos Creator 3D Tutorial: Mind Your Step  - button event listener.

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.

Adding some game over logic

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.

Cocos Creator 3D Tutorial: Mind Your Step  - init function now.

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.



Videos