Sweshi's Tutorials

Cocoos Creator Mind Your Step 3D Game Tutorial - Part 11 - Adding Buttons

To make it easier to play the game on mobile, I decided to add two buttons so that people without mouse input can test the game especially on mobile. To do this, right click the canvas->Create->UI Component->Button. Name the button as one

Cocos Creator 3D Tutorial: Mind Your Step  - creating the buttons.

Switch to the 2D view and place the buttons on the bottom left and right sections of the screens. Open each button and click on the labels. Change the String on the Label of each button. The button on the left should be labeled "One" and the right button should be labeled "Two".

Cocos Creator 3D Tutorial: Mind Your Step  - adding buttons.

Try running the game and make sure that the buttons are visible and well placed as shown in the figure below.

Cocos Creator 3D Tutorial: Mind Your Step  - running the game.

We will now have to create the functions for the buttons to do the same things that the mouse clicks do. Open the PlayerController script and add these two functions as shown.

leftButton(){ //left button clicked this.jumpByStep(1); } rightButton(){ //right button clicked this.jumpByStep(2); }

Notice that the code within both functions is copied from the onMouseUp. First with the left button and then the right button. Go back to the editor and click on one button, go to the inspector and increase the number of Click Events to 1. Then drag the Player to the node section and then select the PlayerController and then the function for the appropriate button. For the left button, use the leftButton() function and for the right button choose the rightButton() function. See the screenshot below.

Cocos Creator 3D Tutorial: Mind Your Step  - leftButton function added.

Do the same for the other button and select the other new function.

Cocos Creator 3D Tutorial: Mind Your Step  - rightButton function added.

You can test the game and see if the buttons work. When that is done, you will notice that the buttons are able to work even with the start menu on the screen, so we need to disable the buttons while in the initialisation state and enable them in the playing state. To do this, first open the GameManager and add the following properties.

//the left button @property({type:Node}) public oneStep:Node |null = null; //the right button @property({type:Node}) public twoStep:Node |null = null;

Save the GameManager and open the editor. Click on the Game Manager and see that the script will be expecting the button nodes in two sections. You will now have to drag the two buttons on these sections of the GameManager's inspector. See the screenshot below.

Cocos Creator 3D Tutorial: Mind Your Step  - Adding the buttons to the Game Manager.

Now open the GameManager so that we disable the buttons when the init() function runs in the initialisation state. So go to the init() function and make it look as shown below.

init(){ if(this.startMenu){//the menu is true and not null this.startMenu.active = true;//enables it } //disabling buttons if(this.oneStep){ this.oneStep.active = false; } if(this.twoStep){ this.twoStep.active = false; } this.generateRoad();//generate road if(this.playerCtrl){ //switch off mouse input this.playerCtrl.setInputActive(false); //reset player position to 0,0,0 this.playerCtrl.node.setPosition(Vec3.ZERO); this.playerCtrl.reset();//moveIndex = 0 } }

We can then enable the buttons when the game state is changed to the playing state. You can go to the setCurState() and add the code that enabled the buttons changing them to be active. See the code below.

set curState(value:GameState){ switch(value){ case GameState.GS_INIT: this.init();//execute initialisation break; case GameState.GS_PLAYING: if(this.startMenu){ this.startMenu.active = false;//disables the start menu } if(this.stepsLabel){ this.stepsLabel.string = '0';//reset to zero at the start of play } setTimeout(()=>{ if(this.playerCtrl){ this.playerCtrl.setInputActive(true);//enable the input } },0.1);//enable a small delay //ENABLING THE BUTTONS if(this.oneStep){ this.oneStep.active = true; } if(this.twoStep){ this.twoStep.active = true; } break; case GameState.GS_END: break; } }

The game should then be able to start without the left and right buttons and then when you click the play button, the buttons will then show up.This is what it will look like.

Cocos Creator 3D Tutorial: Mind Your Step  - Final game.

The complete code can be seen here.

The GameManager Script

import { _decorator, Component, instantiate, Node, Prefab, Vec3, Label } from 'cc'; import { PlayerController } from './PlayerController'; const { ccclass, property } = _decorator; enum BlockType{ BT_NONE, BT_STONE, }; enum GameState{ GS_INIT, GS_PLAYING, GS_END, }; @ccclass('GameManager') export class GameManager extends Component { //cube prefab @property({type:Prefab}) public cubePrfb: Prefab | null = null; // reference to the PlayerController @property({type:PlayerController}) public playerCtrl: PlayerController | null = null; //reference to the start menu @property({type:Node}) public startMenu: Node |null = null; @property({type:Node}) public oneStep:Node |null = null; @property({type:Node}) public twoStep:Node |null = null; //total length of the road public roadLength = 50; private _road:BlockType[] = []; //Label for the steps @property({type:Label}) public stepsLabel:Label|null = null; start() { //current state is initiliazation this.curState = GameState.GS_INIT; this.playerCtrl?.node.on("JumpEnd",this.onPlayerJumpEnd,this); } onPlayerJumpEnd(moveIndex:number){ if(this.stepsLabel){ //casting to string from number, make sure we always score up to 50 this.stepsLabel.string = ''+(moveIndex >= this.roadLength?this.roadLength:moveIndex); } this.checkResult(moveIndex); } init(){ if(this.startMenu){//the menu is true and not null this.startMenu.active = true;//enables it } //disabling buttons if(this.oneStep){ this.oneStep.active = false; } if(this.twoStep){ this.twoStep.active = false; } this.generateRoad();//generate road if(this.playerCtrl){ //switch off mouse input this.playerCtrl.setInputActive(false); //reset player position to 0,0,0 this.playerCtrl.node.setPosition(Vec3.ZERO); this.playerCtrl.reset();//moveIndex = 0 } } set curState(value:GameState){ switch(value){ case GameState.GS_INIT: this.init();//execute initialisation break; case GameState.GS_PLAYING: if(this.startMenu){ //disables the start menu this.startMenu.active = false; } if(this.stepsLabel){ //reset to zero at the start of play this.stepsLabel.string = '0'; } setTimeout(()=>{ if(this.playerCtrl){ //enable the input this.playerCtrl.setInputActive(true); } },0.1);//enable a small delay //ENABLING THE BUTTONS if(this.oneStep){ this.oneStep.active = true; } if(this.twoStep){ this.twoStep.active = true; } break; case GameState.GS_END: break; } } onStartButtonClicked(){ this.curState = GameState.GS_PLAYING; } checkResult(moveIndex:number){ //player position is not at beyond the end of the road if(moveIndex < this.roadLength){ //check if the player is not on a road if(this._road[moveIndex] == BlockType.BT_NONE){ this.curState = GameState.GS_INIT; } }else{//player is beyond the road this.curState = GameState.GS_INIT; } } generateRoad(){ //PRevent using old track this.node.removeAllChildren(); this._road = []; //First position has to be a stone this._road.push(BlockType.BT_STONE); for(let i = 1; i < this.roadLength;i++){ //if the previous position is an empty space if(this._road[i-1]===BlockType.BT_NONE) { //then this position has to be a stone 1 this._road.push(BlockType.BT_STONE); }else{ //randomize between 0 and 1 this._road.push(Math.floor(Math.random()*2)); } } //Generate tracks based on the runway for(let j = 0; j < this._road.length;j++){ let block:Node = this.spawnBlockByType(this._road[j]); if(block){ this.node.addChild(block); block.setPosition(j,-1.5,0); } } } spawnBlockByType(type:BlockType){ if(!this.cubePrfb) { return null; } let block:Node |null = null; //we can generate the road for real roads (1) switch(type) { case BlockType.BT_STONE: //instantiate block block = instantiate(this.cubePrfb); break; } return block; } update(deltaTime: number) { } }

The PlayerController Script

import { _decorator, Component, EventMouse, input, Input, Node, Vec3, Animation, SkeletalAnimation } from 'cc'; const { ccclass, property } = _decorator; @ccclass('PlayerController') export class PlayerController extends Component { //Animation Body // @property({type:Animation}) // public BodyAnim: Animation | null = null; //skeletal animations @property({type:SkeletalAnimation}) public CocosAnim: SkeletalAnimation|null = null; //check if player is jumping private _startJump:boolean = false; // The Jump Step (1 or 2 ) private _jumpStep:number = 0; // The current position of the player private _curPos:Vec3 = new Vec3(); // the target position of the player private _targetPos:Vec3 = new Vec3(); //total jump time private _jumpTime:number = 0.5; //current jump speed private _curJumpSpeed:number =0; //current jump time private _curJumpTime:number = 0; //The difference of the current frame movement position during a jump private _deltaPos:Vec3 = new Vec3(0,0,0); //track the number of steps private _curMoveIndex = 0; start() { //listen for click event //input.on(Input.EventType.MOUSE_UP,this.onMouseUp,this); } setInputActive(active:boolean){//true or false 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); } } leftButton(){ //left button clicked this.jumpByStep(1); } rightButton(){ //right button clicked this.jumpByStep(2); } onMouseUp(event:EventMouse) { //check if the user clicked the left (0) or the right (2) mouse button if(event.getButton()===0)//checks for left { this.jumpByStep(1); } else if(event.getButton()===2)//checks for right { this.jumpByStep(2); } } reset(){ this._curMoveIndex = 0;//when the game is over } jumpByStep(step:number) { if(this._startJump)//if the player is already jumping { // then we make sure the function does not make another jump return; } //the following code only runs if the player is not jumping currently //so that no other jump runs together this._startJump = true; // based on left or right click this._jumpStep = step; // Reset Jump time this._curJumpTime = 0; // current jump step this._curJumpSpeed = this._jumpStep/this._jumpTime; //get the position of the player this.node.getPosition(this._curPos); Vec3.add(this._targetPos,this._curPos, new Vec3(this._jumpStep,0,0)); //SKELETAL ANIMATION if(this.CocosAnim){ this.CocosAnim.getState('cocos_anim_jump').speed = 3.5; this.CocosAnim.play('cocos_anim_jump'); } //Play Animation /* if(this.BodyAnim){ if(step === 1){ this.BodyAnim.play("oneStep"); }else if(step === 2){ this.BodyAnim.play("twoStep"); } }*/ //adds 1 or 2 to the current # of steps this._curMoveIndex += step; } onOnceJumpEnd(){ if(this.CocosAnim){ this.CocosAnim.play('cocos_anim_idle'); } //custom event this.node.emit('JumpEnd',this._curMoveIndex); } update(deltaTime: number) { if(this._startJump)// if _startJump is true { //Jump time = last jump time + frame interval this._curJumpTime +=deltaTime; if(this._curJumpTime>this._jumpTime) { //the jump has ended //places the character where they should be at the end of a jump this.node.setPosition(this._targetPos); // make sure the jump state says we are not jumping this._startJump = false; this.onOnceJumpEnd(); }else{ //still jumping //tweening = making smooth transitions between frames //we have the player's position this.node.getPosition(this._curPos); // So that we know the length of the frame to displace this._deltaPos.x = this._curJumpSpeed * deltaTime; //setting the current position by adding the old current //position plus the delta position (difference) Vec3.add(this._curPos,this._curPos,this._deltaPos); //set the node to a new position this.node.setPosition(this._curPos); } } } }