Sweshi's Tutorials

Cocoos Creator Mind Your Step 3D Game Tutorial - Part 10 - Skeletal Animations

Most 3D modelling software such as blender allow the modeller to create skeletal animations. If 3D models imported into the project have skeletal animations, then we can use these animations in the game. I have linked all the assets of the project including the cocos 3D character that has skeletal animations. The cocos developers created this model with skeletal animations. Drag the folder named "Cocos" from the assets you downloaded into the assets section of your project on the bottom left. Open the folder that you have added to the project and look for a "Cocos" prefab. Click and drag this prefab ontop of the Body of our player in the node heirarchy as shown in the figure below.

Cocos Creator 3D Tutorial: Mind Your Step  - drag the cocos 3D object.

You can then click on the Body and then go to the inspector, in the MeshRenderer click on the 3 dots and click remove so that the cube does not show on the screen. Check the figure below.

Cocos Creator 3D Tutorial: Mind Your Step  - removing the body.

Switch to the 3D view so that we can add a spot light. Click on the Player and then right-click ->Create->Light->Spot Light.

Click on the spot light and change its rotation and position so that it points towards the player. You can manually move it or you can change the rotation and position numbers in the inspector. Because it is a child of the Player, it will follow the player movement.

Cocos Creator 3D Tutorial: Mind Your Step  - spot light.

We can now bring in skeletal animations. Open the PlayerController script. We need to make a reference to the skeletal animation component so add the following code.

//skeletal animations @property({type:SkeletalAnimation}) public CocosAnim: SkeletalAnimation|null = null;

We will not be using the old animations anymore so we need to get rid of the reference. Comment it out by putting forward slashes at the front as shown below.

// @property({type: Animation}) // public BodyAnim: Animation|null = null;

You will also have to comment out sections of the jumpByStep function as shown below.

jumpByStep(step: number) { // other code on top... // if (this.BodyAnim) { // if (step === 1) { // this.BodyAnim.play('oneStep'); // } else if (step === 2) { // this.BodyAnim.play('twoStep'); // } // } }

Save the file and then go to the editor. Click on the player node and go to the inspector. From there, you will be able to see that the PlayerController script will have a reference to the skeletal animation and so select the Cocos node from the Player and drag it to the reference as shown in the figure below.

Cocos Creator 3D Tutorial: Mind Your Step  - player skeletal animations.

Go to the PlayerController script and go to the jumpByStep() function. You will add the following code just before the code you commented out.

if(this.CocosAnim){ //animation time this.CocosAnim.getState('cocos_anim_jump').speed = 3.5; //the jump animation is named cocos_anim_jump this.CocosAnim.play('cocos_anim_jump'); }

You can see that we first check if the CocosAnim is valid which makes sure the reference was added in the editor. We then set the speed for the animation to 3.5. We then play the animation.

The jumpByStep() function should look as shown below.

jumpByStep(step:number) { if(this._startJump)//if the player is already jumping { return;// then we make sure the function does not make another jump } //the following code only runs if the player is not jumping currently this._startJump = true;//so that no other jump runs together this._jumpStep = step;// based on left or right click this._curJumpTime = 0;// Reset Jump time this._curJumpSpeed = this._jumpStep/this._jumpTime;// current jump step this.node.getPosition(this._curPos);//get the position of the player 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"); } }*/ this._curMoveIndex += step;//adds 1 or 2 to the current # of steps }


The final code for the PlayerController and the GameManager are as follows

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; //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 } //generate road this.generateRoad(); 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: //execute initialisation this.init(); 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 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
PlayerController Script Final Code
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); } } 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 { return;// then we make sure the function does not make another jump } //the following code only runs if the player is not jumping currently this._startJump = true;//so that no other jump runs together this._jumpStep = step;// based on left or right click this._curJumpTime = 0;// Reset Jump time // 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); } } } }

Videos