# Software Studio 2020 Spring Assignment 2
[TOC]
## Basic Components
|Component|Score|Y/N|
|:-:|:-:|:-:|
|Membership Mechanism|10%|Y|
|Complete Game Process|5%|Y|
|Basic Rules|45%|Y|
|Animations|10%|Y|
|Sound Effects|10%|Y|
|UI|10%|Y|
# Website Detail Description
## Web Mario [遊戲連結](https://assignment2-mario.web.app)
### 1. 網頁開啟預設主畫面:有兩個按鈕可選擇:
`Sign up` 或 `Log in`
本遊戲一定要登入才能玩,因為會**紀錄玩家狀態**,也符合本作業要求的`Membership mechanism`

**登入**:

**創建新帳號**:

### 2. 登入後:第一個畫面是 `Select Mode`
本遊戲有兩種模式:`One player`或`Two players`
顧名思義可以單人遊玩,或是雙人遊玩。

選擇**mode**之後,是選擇**Level**
建議從level 1 開始,分數會比較高。

### 3. 選擇Level後,就正式**進入遊戲啦!**
遊戲開始前,會先出現 **Rules**頁面進行規則講解,
使用者按下 `Start`按鈕後,就會正式進入遊戲囉!

而進入遊戲後畫面如下:

### 4. 破關成功後:若你從level 1 勝利,會進入level2(或你可以點選`EXIT`回到**模式選擇**頁面。

若選擇進入關卡2,則繼續破關!
Level2也過完後,會進入**遊戲勝利**頁面,
此頁面會記錄你**本次**過關的資訊,如`score`, `time_spent`和你的`Best score`
可以點選下方的`Leader Board`按鈕進入**排行榜**頁面~


# Basic Components Description :
1. World map :
我共繪致 **3個World map**:分別為`level1_map` `level2_map` `level1_2players_map`
使用助教資源包的**tileset**和**items**拼湊,
另外自己在網路上找免費圖片資源,加入`Pipe`和`Brick`

2. Player :
玩家部分有建立**Player Prefab**,但是在level2時設計上有些**困難**,因此我選擇多使用一個`Player_level2.ts`去進行角色腳本控制。礙於本次作業時間有限,邊製作邊熟悉cocos creator的過程,我承認在**Game management**部分做得不太好(太多東西擠在`Player.ts`控制了,導致程式馬太冗長,也難以維護)
在**Final Project**部分我會好好與組員規劃設計,讓程式碼**乾淨切分區塊**,使的遊戲**易於開發與維護**。這樣才是能夠長久開發的遊戲!
(1) **One Player mode:**
單一玩家模式:玩家可使用鍵盤的**左、右**控制玩家往左、往右移動;鍵盤**空白鍵**可以**跳躍**。
(2) **Two Player mode:** 雙人玩家模式:
**第一玩家**:可使用鍵盤的**左、右**控制玩家往左、往右移動;鍵盤**空白鍵**可以**跳躍**。
**第二玩家**:可使用鍵盤的**z、c**控制玩家往左、往右移動;鍵盤**a**可以**跳躍**。
3. Enemies :
本遊戲有四種敵人:
若玩家從**上方**攻擊敵人,敵人會**死去**
其他方向觸碰均為**玩家死去**
(1) Turtle :
![烏龜]()
若玩家從 **上方** 踩下烏龜:烏龜會**縮成龜殼**,接著玩家可以從**左右踢它**,龜殼會邊旋轉邊移動。
(2) **goomba:** 雙人玩家模式:
![小怪獸]()
(3) **Flower :**
花繪從水管跑出來,有三種**States**: `Idle` `Up` `Down`
用**時間**控制這個**Finite State Machine**,簡單講就是定時**往上**,定時**往下**。
p.s.其實可以用Action System完成,但那時候還沒學到,所以土法煉鋼控制linearVelocity XD
![花]()
(4) **Flying_goomba:** 雙人玩家模式:
這個敵人在**Level_2**時才會出現,會從天空掉下來,因為是**Dynamic**型態的Rigid Body。降落後遵循一般敵人的攻擊/死亡規則。
![飛翔怪物]()
4. Question Blocks :
我的**問號箱子**會有三`Prefabs`生成:分別為
(1) coin
(2) mushroom
(3) Life
吃到(1)金幣會記錄在上方UI介面,
吃到(2)蘑菇如果現在是**小馬力歐**會長大,如果已經是大馬力歐則沒反應
吃到(3)Life可以**補命**,如果已經三條則沒反應
5. Animations :
(1) Player: 共**11個**動畫
![截圖]()
(2) Enemy_Turtle: 共**3個**動畫
![截圖]()
(3) Enemy_goomba: 共**1個**動畫
![截圖]()
(4) Enemy_Flower: 共**1個**動畫
![截圖]()
6. Sound effects : [xxxx]
**(1) main scene :**
1個BGM
**(2) Rules scene:**
1個按下button的 sound effect
**(3) mario :**
10個 sound effect:
![截圖]()
**(4) Victory scene:**
1個BGM
7. UI :
**1. One Player Mode:**
![截圖]()
(1)Lives: 剩幾條生命
(2)Scores: 本次關卡獲得分數
(3)Time: 還剩下多少時間
(4)Level: 第幾個關卡
(5)Pause button: 暫停按鈕
**2. Two Player Mode:**
![截圖]()
與單人模式相同,但變成**兩列**
分別表示各個玩家
# Bonus Functions Description :
1. **Leader Board** : 排行榜部分我將每個user設立一個**Best_score** field去存取歷史遊戲紀錄中的**最佳成績**,
再將這些user以`Best_score`排序,藉此存取**前三名**。
``` typescript=
Draw(){
// Go search in firebase.
// OrderbyChild
var userRef = firebase.database().ref('Users');
userRef.orderByChild('Best_score').limitToFirst(3).once('value',(snapshot)=>{
let i =1;
snapshot.forEach((item)=>{
console.log(item.child('Best_score').val());
// cc.log(i);
if(i==3){
this.name1Text.string = String(item.child('Name').val());
this.score1Text.string = String(item.child('Best_score').val());
}else if(i==2){
this.name2Text.string = String(item.child('Name').val());
this.score2Text.string = String(item.child('Best_score').val());
}else{
this.name3Text.string = String(item.child('Name').val());
this.score3Text.string = String(item.child('Best_score').val());
}
i++;
})
})
}
```
2. **Multi players (offline)**: 多玩家模式,我多設了一個**scene**,叫做`level_1_2player`。並在這個scene內直接放置兩個players。
:::warning
要注意的是,我這邊的設定是:
**1.玩家2預設性命是3**
**2.玩家2死掉並不會影響玩家1遊戲**,但是玩家1死掉**遊戲就結束了**
**3.攝影機視角是跟隨玩家1**,若玩家2因為沒有跟上或擅自超出視角,**會死掉**
**4.玩家2重生的位置設定為:玩家1右側的(100,0)相對位置**
:::
* `Player2.ts`
``` typescript=
const {ccclass, property} = cc._decorator;
@ccclass
export default class Player extends cc.Component {
@property(cc.SpriteFrame)
BigMarioSprite: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
smallMarioSprite: cc.SpriteFrame = null;
@property({type:cc.AudioClip})
GrowSound: cc.AudioClip = null;
@property({type:cc.AudioClip})
ShrinkSound: cc.AudioClip = null;
@property({type:cc.AudioClip})
hurtSound: cc.AudioClip = null;
@property({type:cc.AudioClip})
dieSound: cc.AudioClip = null;
@property({type:cc.AudioClip})
jumpSound: cc.AudioClip = null;
@property({type:cc.AudioClip})
kickSound: cc.AudioClip = null;
@property({type:cc.AudioClip})
stompSound: cc.AudioClip = null;
@property({type:cc.AudioClip})
MushroomUp: cc.AudioClip = null;
@property({type:cc.AudioClip})
coinSound: cc.AudioClip = null;
@property({type:cc.AudioClip})
gameoverSound: cc.AudioClip = null;
@property({type: cc.Node})
Canvas: cc.Node = null;
@property({type: cc.Node})
mainCamera: cc.Node = null;
@property({type: cc.Node})
Mario1: cc.Node = null;
@property({type: cc.Node})
Qblocks: cc.Node[] = [];
private hitQblocks :boolean[] = [false,false,false,false,false,false,false,false];
@property([cc.Prefab])
ItemPrefabs: cc.Prefab[] = [];
// 0:M 1:coin
// private idleFrame: cc.SpriteFrame = null;
// private anim: cc.Animation = null;
private rebornPos = null;
isBig: boolean = false; //defaule: small mario.
lives: number = null;
// private ceilingPos: number = 155;
// private fallDown: boolean = false;
private damageTime: number = 0;
private growing: boolean = false;
private shrinking: boolean = false;
private dieing: boolean = false;
@property(cc.Node)
lifeContainer: cc.Node = null;
@property(cc.Prefab)
lifePrefab: cc.Prefab = null;
coinText : cc.Label = null;
scoreText : cc.Label = null;
score : number = 0;
coins : number = 0;
private anim = null; //this will use to get animation component
private anim_str: string = null;
private animateState = null; //this will use to record animationState
// private bulletPool = null; // this is a bullet manager, and it control the bullet resource
private playerSpeed: number = 0;
// LIFE-CYCLE CALLBACKS:
// @property(cc.Prefab)
// private bulletPrefab: cc.Prefab = null;
private LDown: boolean = false; // key for player to go left
private RDown: boolean = false; // key for player to go right
// private jDown: boolean = false; // key for player to shoot
private SpaceDown: boolean = false; // key for player to jump
private isDead: boolean = false;
private onGround: boolean = false;
onLoad() {
cc.director.getPhysicsManager().enabled = true;
// this.idleFrame = this.getComponent(cc.Sprite).spriteFrame;
this.anim = this.getComponent(cc.Animation);
this.lives = 3; //initiall lives.
for(let i=1; i<=this.lives; i++){
var newlife = cc.instantiate(this.lifePrefab);
newlife.name = "Life"+String(i);
cc.log(newlife.name);
this.lifeContainer.addChild(newlife);
newlife.scaleX = 0.3;
newlife.scaleY = 0.3;
newlife.setPosition(50 +30*(i-1) , 0);
}
}
start(){
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
this.coinText = cc.find("Canvas/Main Camera/level1/TopContent/Coins2/number").getComponent(cc.Label);
this.scoreText = cc.find("Canvas/Main Camera/level1/TopContent/Score2/value").getComponent(cc.Label);
// this.animateState = this.anim.play('idle'); // Initial animation
this.anim.play('idle');
// this.playerAnimation('idle');
// this.anim.play('idle');
this.rebornPos = this.node.position;
// Reset Q blocks to false.
for(let k=0; k<=7; k++){
this.hitQblocks[k] = false;
}
// this.level_1.node.getComponent('level_1').remain_lives = 3;
// this.level_1.getComponent("level_1").updateLife(this.lives);
}
onKeyDown(event) {
// cc.log("Key Down: " + event.keyCode);
if(event.keyCode == cc.macro.KEY.z) {
if(this.isBig)this.anim.play('Big_move');
else this.anim.play('move');
this.LDown = true;
this.RDown = false;
} else if(event.keyCode == cc.macro.KEY.c) {
if(this.isBig)this.anim.play('Big_move');
else this.anim.play('move');
this.RDown = true;
this.LDown = false;
} else if(event.keyCode == cc.macro.KEY.a) {
///////// TODO : BIG-JUMP animation. //////////
if(this.isBig)this.anim.play('Big_jump');
else this.anim.play('mario_jump');
this.SpaceDown = true;
}
}
onKeyUp(event) {
if(event.keyCode == cc.macro.KEY.z){
// this.animateState = this.anim.stop('move');
if(this.isBig) this.anim.play('Big_idle');
else this.anim.play('idle');
this.LDown = false;
}
else if(event.keyCode == cc.macro.KEY.c){
if(this.isBig) this.anim.play('Big_idle');
else this.anim.play('idle');
this.RDown = false;
}
else if(event.keyCode == cc.macro.KEY.a)
// if(this.isBig) this.anim.play('Big_idle');
// else this.anim.play('idle');
this.SpaceDown = false;
}
private playerMovement(dt) {
this.playerSpeed = 0;
if(this.isDead) {
cc.log('Player2 die.');
this.LDown = false;
this.RDown = false;
this.SpaceDown = false;
this.lives -- ;
this.anim.play('mario_die');
this.node.position = this.rebornPos;
this.isDead = false;
this.getComponent(cc.RigidBody).linearVelocity = cc.v2(0, 1500);
this.node.getComponent(cc.PhysicsBoxCollider).enabled = false; // Hide this node avoid multiple contact(DEAD).
// this.playerAnimation('mario_die');
this.anim.play('mario_die');
this.lifeContainer.getChildByName("Life"+String(this.lives+1)).destroy();
// CANNOT control player anymore~
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
if(this.lives <= 0){
// cc.audioEngine.pauseMusic();
// cc.audioEngine.playEffect(this.gameoverSound,false);
this.node.position = this.node.position;
this.mainCamera.x = this.node.x; // Don't move!!
// re gain lives
/*
firebase.database().ref('Users/'+user.displayName+'/').update({
Lives: 3
});*/
setTimeout(()=>{
// this.node.position = this.rebornPos;
// this.node.getComponent(cc.RigidBody).enabled = true;
// cc.director.loadScene("Gameover");
return;
}, 4000);
}else{
// Play die animation.
// Stop bgm.
cc.audioEngine.pauseMusic();
cc.audioEngine.playEffect(this.dieSound,false);
// this.node.getComponent(cc.PhysicsBoxCollider).enabled = false; // Hide this node avoid multiple contact(DEAD).
setTimeout(()=>{
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
this.anim.play('idle');
this.node.position = this.rebornPos;
this.node.getComponent(cc.PhysicsBoxCollider).enabled = true;
cc.audioEngine.resumeMusic();
return;
}, 2000);
}
}else{
// Still alive.
if(this.LDown){
this.playerSpeed = -300;
this.node.scaleX = -3.5;
}
else if(this.RDown){
this.playerSpeed = 300;
this.node.scaleX = 3.5;
}else{ // idle
}
this.node.x += this.playerSpeed * dt;
if(this.SpaceDown && this.onGround){
this.jump();
}
}
}
private jump() {
// cc.log("Press Jump.");
this.onGround = false;
cc.audioEngine.playEffect(this.jumpSound,false);
this.getComponent(cc.RigidBody).linearVelocity = cc.v2(0, 1500);
}
update(dt) {
if(this.node.x < (this.mainCamera.x - this.Canvas.width/2) || this.node.x > (this.mainCamera.x + this.Canvas.width/2) ){
this.isDead = true;
}
this.playerMovement(dt);
// this.mainCamera.x = this.node.x;
this.coinText.string = this.coins.toString();
this.scoreText.string = this.score.toString();
this.rebornPos = cc.v2(this.Mario1.x+100 , this.Mario1.y);
}
// Callback functions for the beginning of contact.
onBeginContact(contact, self, other) {
// Mario on Top.
if(contact.getWorldManifold().normal.y==-1){
if(other.node.name == "ground") {
this.onGround = true;
} else if(other.tag == 2) { // tag = 2: enemy.
cc.log("Enemy die.");
cc.audioEngine.playEffect(this.stompSound, false);
if(other.node.name == "Enemy_Turtle"){
if(other.node.getComponent('Enemy_Turtle').beenhit == true){
this.onGround = true; // mario can jump.
other.node.getComponent(cc.RigidBody).linearVelocity = cc.v2(0,0);
}
else{
this.getComponent(cc.RigidBody).linearVelocity = cc.v2(0,500);
// Turtle would shrink in its TypeScript.
}
}else if(other.node.name =="Enemy_goomba"){
// goomba destroy in its TypeScript.
}else{
other.node.destroy();
}
}
else if(other.tag == 3 || other.tag==4 || other.tag==7) { // any block or Qblock.
this.onGround = true;
}
else if(other.node.name == "left_bound" || other.node.name == "right_bound" || other.node.name == "Lower_bound"){
this.isDead = true;
}else if(other.node.name =="coin"){
cc.audioEngine.playEffect(this.coinSound,false);
this.coins += 1;
other.node.destroy();
}else if(other.tag == 5){
// grow up mushroom.
cc.log("Eat mushroom.");
if(!this.isBig){//can grow up
// First update state to firebase.
/* var user = firebase.auth().currentUser;
firebase.database().ref('Users/'+user.displayName+'/').update({
isBig : 1
}).then(()=>{*/
this.isBig = true;
this.growing = true;
this.anim.play('mario_grow');
// this.playerAnimation('mario_grow');
// this.animateState = this.anim.play('mario_grow');
this.getComponent(cc.RigidBody).linearVelocity = cc.v2(0,200);
this.changeSprite(this.BigMarioSprite);
cc.audioEngine.playEffect(this.GrowSound, false);
// })
}else{
cc.log("Already big!");
cc.audioEngine.playEffect(this.coinSound, false);
}
other.node.destroy();
// this.get bigger.
// this.sprite.frame = ;
}else if(other.node.name == "Lives"){
if(this.lives >=3){
this.lives = 3; //maintain.
cc.log("Life saturated.");
other.node.destroy();
}
else{
this.lives ++;
cc.log("Lives: " +this.lives);
other.node.destroy();
//update to firebase
/*
var user = firebase.auth().currentUser;
firebase.database().ref('Users/'+user.displayName+'/').update({
Lives: this.lives
});*/
// Draw UI Life :
var newlife = cc.instantiate(this.lifePrefab);
this.lifeContainer.addChild(newlife);
newlife.scaleX = 0.3;
newlife.scaleY = 0.3;
if(this.lives == 3){
newlife.name = "Life3"
newlife.setPosition(50 +30*2 , 0);
}else if(this.lives ==2){
newlife.name = "Life2";
newlife.setPosition(50 +30*1 , 0);
}else{
newlife.name = "Life1";
newlife.setPosition(50 +30*0 , 0);
}
}
}
}
// Mario at Bottom.
else if(contact.getWorldManifold().normal.y== 1){
if(other.node.name == "ground") {
} else if(other.tag == 2) { // tag = 2: enemy.
cc.log("Enemy die.");
// this.isDead = true;
}
else if(other.tag == 3 || other.tag==7) {
}else if(other.tag == 4) { // Qblock.
cc.log("Mario hits the question box.");
switch(other.node.name){
case "Qblock0":
if(!this.hitQblocks[0]){
this.hitQblocks[0] = true;
let idx = this.randomGenItem();
let item = cc.instantiate(this.ItemPrefabs[idx]); // item : cc.Node
this.Qblocks[0].addChild(item);
item.setPosition(0,this.Qblocks[0].height);
if(idx==0){
cc.audioEngine.playEffect(this.MushroomUp,false);
}else if(idx==1){
cc.audioEngine.playEffect(this.coinSound,false);
}else{
// Generate live.
cc.audioEngine.playEffect(this.GrowSound,false);
}
}else{
cc.log(other.node.name + " has been hit.");
}
break;
case "Qblock1":
if(!this.hitQblocks[1]){
this.hitQblocks[1] = true;
let idx = this.randomGenItem();
let item = cc.instantiate(this.ItemPrefabs[idx]); // item : cc.Node
this.Qblocks[1].addChild(item);
item.setPosition(0,this.Qblocks[1].height);
if(idx==0){
cc.audioEngine.playEffect(this.MushroomUp,false);
}else if(idx==1){
cc.audioEngine.playEffect(this.coinSound,false);
}else{
// Generate live.
cc.audioEngine.playEffect(this.GrowSound,false);
}
}else{
cc.log(other.node.name + " has been hit.");
}
break;
case "Qblock2":
if(!this.hitQblocks[2]){
this.hitQblocks[2] = true;
let idx = this.randomGenItem();
let item = cc.instantiate(this.ItemPrefabs[idx]); // item : cc.Node
this.Qblocks[2].addChild(item);
item.setPosition(0,this.Qblocks[2].height);
if(idx==0){
cc.audioEngine.playEffect(this.MushroomUp,false);
}else if(idx==1){
cc.audioEngine.playEffect(this.coinSound,false);
}else{
// Generate live.
cc.audioEngine.playEffect(this.GrowSound,false);
}
}else{
cc.log(other.node.name + " has been hit.");
}
break;
case "Qblock3":
if(!this.hitQblocks[3]){
this.hitQblocks[3] = true;
let idx = this.randomGenItem();
let item = cc.instantiate(this.ItemPrefabs[idx]); // item : cc.Node
this.Qblocks[3].addChild(item);
item.setPosition(0,this.Qblocks[3].height);
if(idx==0){
cc.audioEngine.playEffect(this.MushroomUp,false);
}else if(idx==1){
cc.audioEngine.playEffect(this.coinSound,false);
}else{
// Generate live.
cc.audioEngine.playEffect(this.GrowSound,false);
}
}else{
cc.log(other.node.name + " has been hit.");
}
break;
case "Qblock4":
if(!this.hitQblocks[4]){
this.hitQblocks[4] = true;
let idx = this.randomGenItem();
let item = cc.instantiate(this.ItemPrefabs[idx]); // item : cc.Node
this.Qblocks[4].addChild(item);
item.setPosition(0,this.Qblocks[4].height);
if(idx==0){
cc.audioEngine.playEffect(this.MushroomUp,false);
}else if(idx==1){
cc.audioEngine.playEffect(this.coinSound,false);
}else{
// Generate live.
cc.audioEngine.playEffect(this.GrowSound,false);
}
}else{
cc.log(other.node.name + " has been hit.");
}
break;
case "Qblock5":
if(!this.hitQblocks[5]){
this.hitQblocks[5] = true;
let idx = this.randomGenItem();
let item = cc.instantiate(this.ItemPrefabs[idx]); // item : cc.Node
this.Qblocks[5].addChild(item);
item.setPosition(0,this.Qblocks[5].height);
if(idx==0){
cc.audioEngine.playEffect(this.MushroomUp,false);
}else if(idx==1){
cc.audioEngine.playEffect(this.coinSound,false);
}else{
// Generate live.
cc.audioEngine.playEffect(this.GrowSound,false);
}
}else{
cc.log(other.node.name + " has been hit.");
}
break;
case "Qblock6":
if(!this.hitQblocks[6]){
this.hitQblocks[6] = true;
let idx = this.randomGenItem();
let item = cc.instantiate(this.ItemPrefabs[idx]); // item : cc.Node
this.Qblocks[6].addChild(item);
item.setPosition(0,this.Qblocks[6].height);
if(idx==0){
cc.audioEngine.playEffect(this.MushroomUp,false);
}else if(idx==1){
cc.audioEngine.playEffect(this.coinSound,false);
}else{
// Generate live.
cc.audioEngine.playEffect(this.GrowSound,false);
}
}else{
cc.log(other.node.name + " has been hit.");
}
break;
case "Qblock7":
if(!this.hitQblocks[7]){
this.hitQblocks[7] = true;
let idx = this.randomGenItem();
let item = cc.instantiate(this.ItemPrefabs[idx]); // item : cc.Node
this.Qblocks[7].addChild(item);
item.setPosition(0,this.Qblocks[7].height);
if(idx==0){
cc.audioEngine.playEffect(this.MushroomUp,false);
}else if(idx==1){
cc.audioEngine.playEffect(this.coinSound,false);
}else{
// Generate live.
cc.audioEngine.playEffect(this.GrowSound,false);
}
}else{
cc.log(other.node.name + " has been hit.");
}
break;
}
}else if(other.node.name =="coin"){
this.coins += 1;
cc.audioEngine.playEffect(this.coinSound,false);
other.node.destroy();
}else if(other.tag == 5){
// grow up mushroom.
cc.log("Eat mushroom.");
if(!this.isBig){//can grow up
// First update state to firebase.
/* var user = firebase.auth().currentUser;
firebase.database().ref('Users/'+user.displayName+'/').update({
isBig : 1
}).then(()=>{*/
this.isBig = true;
this.growing = true;
this.anim.play('mario_grow');
// this.playerAnimation('mario_grow');
this.getComponent(cc.RigidBody).linearVelocity = cc.v2(0,200);
this.changeSprite(this.BigMarioSprite);
cc.audioEngine.playEffect(this.GrowSound, false);
// })
// this.animateState = this.anim.play('mario_grow');
// this.isBig = true;
// this.getComponent(cc.RigidBody).linearVelocity = cc.v2(0,200);
// this.changeSprite(this.BigMarioSprite);
// cc.audioEngine.playEffect(this.GrowSound, false);
}else{
cc.log("Already big!");
cc.audioEngine.playEffect(this.coinSound, false);
}
other.node.destroy();
// this.get bigger.
// this.sprite.frame = ;
}else if(other.node.name == "Lives"){
if(this.lives >=3){
this.lives = 3; //maintain.
cc.log("Life saturated.");
other.node.destroy();
}
else{
this.lives ++;
cc.log("Lives: " +this.lives);
other.node.destroy();
//update to firebase
/*
var user = firebase.auth().currentUser;
firebase.database().ref('Users/'+user.displayName+'/').update({
Lives: this.lives
});*/
// Draw UI Life :
var newlife = cc.instantiate(this.lifePrefab);
this.lifeContainer.addChild(newlife);
newlife.scaleX = 0.3;
newlife.scaleY = 0.3;
if(this.lives == 3){
newlife.name = "Life3"
newlife.setPosition(50 +30*2 , 0);
}else if(this.lives ==2){
newlife.name = "Life2";
newlife.setPosition(50 +30*1 , 0);
}else{
newlife.name = "Life1";
newlife.setPosition(50 +30*0 , 0);
}
}
}
}
// Left or Right direction.
else{
if(other.node.name == "ground") {
this.onGround = true;
}
else if(other.tag == 2) { // tag = 2: enemy.
if(other.node.name == "Enemy_Turtle"){
if(other.node.getComponent('Enemy_Turtle').beenhit == true){
cc.audioEngine.playEffect(this.kickSound,false);
}
else{
if(this.isBig){//Should shrink (won't die)
// First update state to firebase.
/*
var user = firebase.auth().currentUser;
firebase.database().ref('Users/'+user.displayName+'/').update({
isBig : 0
}).then(()=>{*/
this.isBig = false;
this.shrinking = true;
this.anim.play('mario_shrink');
// this.playerAnimation('mario_shrink');
this.changeSprite(this.smallMarioSprite);
cc.audioEngine.playEffect(this.hurtSound, false);
// })
}else{
cc.audioEngine.playEffect(this.hurtSound,false);
this.isDead = true;
}
}
}
else{
if(this.isBig){//Should shrink (won't die)
// First update state to firebase.
/*
var user = firebase.auth().currentUser;
firebase.database().ref('Users/'+user.displayName+'/').update({
isBig : 0
}).then(()=>{*/
this.isBig = false;
this.shrinking = true;
this.anim.play('mario_shrink');
// this.playerAnimation('mario_shrink');
this.changeSprite(this.smallMarioSprite);
cc.audioEngine.playEffect(this.hurtSound, false);
// })
}else{
this.isDead = true;
}
}
}
else if(other.tag == 3 || other.tag==4) {
this.onGround = true;
}
else if(other.tag == 5){
// grow up mushroom.
cc.log("Eat mushroom.");
if(!this.isBig){//can grow up
// First update state to firebase./
/*
var user = firebase.auth().currentUser;
firebase.database().ref('Users/'+user.displayName+'/').update({
isBig : 1
}).then(()=>{*/
this.isBig = true;
this.growing = true;
this.anim.play('mario_grow');
// this.playerAnimation('mario_grow');
this.getComponent(cc.RigidBody).linearVelocity = cc.v2(0,200);
this.changeSprite(this.BigMarioSprite);
cc.audioEngine.playEffect(this.GrowSound, false);
// })
}else{
cc.log("Already big!");
cc.audioEngine.playEffect(this.coinSound, false);
}
other.node.destroy();
// this.get bigger.
// this.sprite.frame = ;
}
else if(other.node.name == "left_bound" || other.node.name == "right_bound" || other.node.name == "Lower_bound"){
this.isDead = true;
}
else if(other.tag == 6){//reach goal flag.
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
contact.disabled = true;
this.LDown = this.RDown = this.SpaceDown = false;
//this.playerSpeed = 0;
let action = cc.repeatForever(cc.moveBy(1, 0, 1500))
cc.log("Run action now.");
this.node.runAction(action);
// this.playerAnimation('mario_win');
this.animateState = this.anim.play('mario_win');
// Send "win" to level_1.ts.
let Rtime = cc.find("Canvas").getComponent("level_2").remainTime;
this.score = Rtime.toFixed(0).toString().replace(".", ":");
cc.find("Canvas").getComponent("level_2").win();
}else if(other.node.name =="coin"){
this.coins += 1;
cc.audioEngine.playEffect(this.coinSound,false);
other.node.destroy();
}else if(other.node.name == "Lives"){
if(this.lives >=3){
this.lives = 3; //maintain.
cc.log("Life saturated.");
other.node.destroy();
}
else{
this.lives ++;
cc.log("Lives: " +this.lives);
other.node.destroy();
//update to firebase
/*
var user = firebase.auth().currentUser;
firebase.database().ref('Users/'+user.displayName+'/').update({
Lives: this.lives
});*/
// Draw UI Life :
var newlife = cc.instantiate(this.lifePrefab);
this.lifeContainer.addChild(newlife);
newlife.scaleX = 0.3;
newlife.scaleY = 0.3;
if(this.lives == 3){
newlife.name = "Life3"
newlife.setPosition(50 +30*2 , 0);
}else if(this.lives ==2){
newlife.name = "Life2";
newlife.setPosition(50 +30*1 , 0);
}else{
newlife.name = "Life1";
newlife.setPosition(50 +30*0 , 0);
}
}
}
}
} // End onBeginContact.
private playerAnimation(anim_str: string)
{
var playing = this.anim.play(this.anim_str).isPlaying;
// Grow, shrink, die need to be frozen. CANNOT change to idle
if(anim_str == this.anim_str)return;
else{
cc.log("Change amim!");
cc.log("Now: "+this.anim_str+", Play: "+anim_str);
this.anim.stop();
this.anim_str = anim_str;
this.anim.play(this.anim_str);
}
}
randomGenItem()
{
let rand = Math.random();
//0: Mushroom, 1: coin, 2: lives.
let prob = [1,1,1];
let sum = prob.reduce((a,b)=>a+b);
for(let i = 1; i < prob.length; i++)
prob[i] += prob[i-1];
for(let i = 0; i < prob.length; i++)
{
prob[i] /= sum;
if(rand <= prob[i])
return i;
}
}
changeSprite(Sprite: cc.SpriteFrame) {
// change sprite frame to the one specified by the property
this.getComponent(cc.Sprite).spriteFrame = Sprite;
}
}
```
# Code Description :
:::success
程式碼講解:我針對**3個**較重點的程式碼說明。
:::
### 1. Player.ts
一切與玩家(mario)相關的code都寫在這裡面。從`onload()` `start()` `update(dt)` 三個核心**method**下去講解。
**(1)onload()**:
程式載入後做三件事情:
**1.開啟PhysicsManager()**
**2.取得user現在性命數量** (Lives)
**3.取得user現在大小狀態** (isBig)
``` typescript=
onLoad() {
cc.director.getPhysicsManager().enabled = true;
// this.idleFrame = this.getComponent(cc.Sprite).spriteFrame;
this.anim = this.getComponent(cc.Animation);
// Read lives from user.
var user = firebase.auth().currentUser;
var LifeRef = firebase.database().ref('Users/'+user.displayName+'/Lives');
// 快照
LifeRef.once('value', (snapshot)=>{
console.log(snapshot.val());
this.lives = snapshot.val();
}).then( ()=>{
// Draw life prefabs
for(let i=1; i<=this.lives; i++){
var newlife = cc.instantiate(this.lifePrefab);
newlife.name = "Life"+String(i);
cc.log(newlife.name);
this.lifeContainer.addChild(newlife);
newlife.scaleX = 0.3;
newlife.scaleY = 0.3;
newlife.setPosition(50 +30*(i-1) , 0);
}
});
////// Read isBig //////////
firebase.database().ref('Users/'+user.displayName+'/isBig').once('value', (snapshot)=>{
console.log(snapshot.val());
if(snapshot.val()==1) this.isBig = true;
else this.isBig = false;
}).then( ()=>{
// Draw mario sprite Frame.
if(this.isBig) this.changeSprite(this.BigMarioSprite);
else this.changeSprite(this.smallMarioSprite);
});
// this.lives = 3; //initiall lives.
// Reset Q blocks to false.
for(let k=0; k<=7; k++){
this.hitQblocks[k] = false;
cc.log("Qblock "+k+" :"+this.hitQblocks[k]);
}
}
```
**(2)start()**:
onLoad()後做四件事情:
**1.開啟Keyboard Event listener**
**2.取得UI的`cc.Label`元素,作為此class的Propteries** ``(coinText, scoreText)``
**3.初始化Player的動畫`idle`**
**4.取得reborn位置 `rebornPos`**
``` typescript=
start(){
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
this.coinText = cc.find("Canvas/Main Camera/level1/TopContent/Coins/number").getComponent(cc.Label);
this.scoreText = cc.find("Canvas/Main Camera/level1/TopContent/Score/value").getComponent(cc.Label);
this.anim.play('idle');
this.rebornPos = this.node.position;
}
```
**(3)update(dt)**:
每dt時間更新一次:
**1.更新玩家位置`playerMovement(dt)`**
**2.更新Camera位置`this.mainCamera.x`**
**3.更新`coin`數量**
**4.更新`score`數量**
``` typescript=
update(dt) {
this.playerMovement(dt);
this.mainCamera.x = this.node.x;
this.coinText.string = this.coins.toString();
this.scoreText.string = this.score.toString();
}
```
* 完整的Player.ts如下:
``` typescript=
// Learn TypeScript:
// - [Chinese] https://docs.cocos.com/creator/manual/zh/scripting/typescript.html
// - [English] http://www.cocos2d-x.org/docs/creator/manual/en/scripting/typescript.html
// Learn Attribute:
// - [Chinese] https://docs.cocos.com/creator/manual/zh/scripting/reference/attributes.html
// - [English] http://www.cocos2d-x.org/docs/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
// - [Chinese] https://docs.cocos.com/creator/manual/zh/scripting/life-cycle-callbacks.html
// - [English] http://www.cocos2d-x.org/docs/creator/manual/en/scripting/life-cycle-callbacks.html
// TODO : 1 : KEY.down : 蹲下(動畫?)
import GameManager from "./GameManager";
import level_1 from "./level_1";
import Enemy_Turtle from "./Enemy_Turtle";
const {ccclass, property} = cc._decorator;
@ccclass
export default class Player extends cc.Component {
@property(cc.SpriteFrame)
BigMarioSprite: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
smallMarioSprite: cc.SpriteFrame = null;
@property({type:cc.AudioClip})
GrowSound: cc.AudioClip = null;
@property({type:cc.AudioClip})
ShrinkSound: cc.AudioClip = null;
@property({type:cc.AudioClip})
hurtSound: cc.AudioClip = null;
@property({type:cc.AudioClip})
dieSound: cc.AudioClip = null;
@property({type:cc.AudioClip})
jumpSound: cc.AudioClip = null;
@property({type:cc.AudioClip})
kickSound: cc.AudioClip = null;
@property({type:cc.AudioClip})
stompSound: cc.AudioClip = null;
@property({type:cc.AudioClip})
MushroomUp: cc.AudioClip = null;
@property({type:cc.AudioClip})
reserveSound: cc.AudioClip = null;
@property({type:cc.AudioClip})
coinSound: cc.AudioClip = null;
@property({type:cc.AudioClip})
gameoverSound: cc.AudioClip = null;
@property(GameManager)
gameMgr: GameManager = null;
// @property(cc.Node)
// level_1: cc.Node = null;
@property({type: cc.Node})
mainCamera: cc.Node = null;
@property({type: cc.Node})
Qblocks: cc.Node[] = [];
private hitQblocks :boolean[] = [false,false,false,false,false,false,false,false];
@property([cc.Prefab])
ItemPrefabs: cc.Prefab[] = [];
// 0:M 1:coin
// private idleFrame: cc.SpriteFrame = null;
// private anim: cc.Animation = null;
private rebornPos = null;
isBig: boolean = false; //defaule: small mario.
lives: number = null;
// private ceilingPos: number = 155;
// private fallDown: boolean = false;
private damageTime: number = 0;
private growing: boolean = false;
private shrinking: boolean = false;
private dieing: boolean = false;
@property(cc.Node)
lifeContainer: cc.Node = null;
@property(cc.Prefab)
lifePrefab: cc.Prefab = null;
coinText : cc.Label = null;
scoreText : cc.Label = null;
private anim = null; //this will use to get animation component
private anim_str: string = null;
private animateState = null; //this will use to record animationState
// private bulletPool = null; // this is a bullet manager, and it control the bullet resource
private playerSpeed: number = 0;
// LIFE-CYCLE CALLBACKS:
// @property(cc.Prefab)
// private bulletPrefab: cc.Prefab = null;
private LDown: boolean = false; // key for player to go left
private RDown: boolean = false; // key for player to go right
// private jDown: boolean = false; // key for player to shoot
private SpaceDown: boolean = false; // key for player to jump
private isDead: boolean = false;
private onGround: boolean = false;
score : number = 0;
coins : number = 0;
onLoad() {
cc.director.getPhysicsManager().enabled = true;
// this.idleFrame = this.getComponent(cc.Sprite).spriteFrame;
this.anim = this.getComponent(cc.Animation);
// Read lives from user.
var user = firebase.auth().currentUser;
var LifeRef = firebase.database().ref('Users/'+user.displayName+'/Lives');
// 快照
LifeRef.once('value', (snapshot)=>{
console.log(snapshot.val());
this.lives = snapshot.val();
}).then( ()=>{
// Draw life prefabs
for(let i=1; i<=this.lives; i++){
var newlife = cc.instantiate(this.lifePrefab);
newlife.name = "Life"+String(i);
cc.log(newlife.name);
this.lifeContainer.addChild(newlife);
newlife.scaleX = 0.3;
newlife.scaleY = 0.3;
newlife.setPosition(50 +30*(i-1) , 0);
}
});
////// Read isBig //////////
firebase.database().ref('Users/'+user.displayName+'/isBig').once('value', (snapshot)=>{
console.log(snapshot.val());
if(snapshot.val()==1) this.isBig = true;
else this.isBig = false;
}).then( ()=>{
// Draw mario sprite Frame.
if(this.isBig) this.changeSprite(this.BigMarioSprite);
else this.changeSprite(this.smallMarioSprite);
});
// this.lives = 3; //initiall lives.
// Reset Q blocks to false.
for(let k=0; k<=7; k++){
this.hitQblocks[k] = false;
cc.log("Qblock "+k+" :"+this.hitQblocks[k]);
}
}
start(){
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
this.coinText = cc.find("Canvas/Main Camera/level1/TopContent/Coins/number").getComponent(cc.Label);
this.scoreText = cc.find("Canvas/Main Camera/level1/TopContent/Score/value").getComponent(cc.Label);
// this.animateState = this.anim.play('idle'); // Initial animation
this.anim.play('idle');
this.rebornPos = this.node.position;
}
onKeyDown(event) {
// cc.log("Key Down: " + event.keyCode);
if(event.keyCode == cc.macro.KEY.left) {
if(this.isBig)this.anim.play('Big_move');
else this.anim.play('move');
this.LDown = true;
this.RDown = false;
} else if(event.keyCode == cc.macro.KEY.right) {
if(this.isBig)this.anim.play('Big_move');
else this.anim.play('move');
this.RDown = true;
this.LDown = false;
} else if(event.keyCode == cc.macro.KEY.space) {
///////// TODO : BIG-JUMP animation. //////////
if(this.isBig)this.anim.play('Big_jump');
else this.anim.play('mario_jump');
this.SpaceDown = true;
}
}
onKeyUp(event) {
if(event.keyCode == cc.macro.KEY.left){
// this.animateState = this.anim.stop('move');
if(this.isBig) this.anim.play('Big_idle');
else this.anim.play('idle');
this.LDown = false;
}
else if(event.keyCode == cc.macro.KEY.right){
if(this.isBig) this.anim.play('Big_idle');
else this.anim.play('idle');
this.RDown = false;
}
else if(event.keyCode == cc.macro.KEY.space)
// if(this.isBig) this.anim.play('Big_idle');
// else this.anim.play('idle');
this.SpaceDown = false;
}
private playerMovement(dt) {
this.playerSpeed = 0;
if(this.isDead) {
this.LDown = false;
this.RDown = false;
this.SpaceDown = false;
this.lives -- ;
let user = firebase.auth().currentUser;
firebase.database().ref('Users/'+user.displayName+'/').update({
Lives: this.lives
});
this.isDead = false;
this.getComponent(cc.RigidBody).linearVelocity = cc.v2(0, 1500);
this.node.getComponent(cc.PhysicsBoxCollider).enabled = false; // Hide this node avoid multiple contact(DEAD).
// this.playerAnimation('mario_die');
this.anim.play('mario_die');
this.lifeContainer.getChildByName("Life"+String(this.lives+1)).destroy();
// CANNOT control player anymore~
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
if(this.lives <= 0){
cc.audioEngine.pauseMusic();
cc.audioEngine.playEffect(this.gameoverSound,false);
this.node.position = this.node.position;
this.mainCamera.x = this.node.x; // Don't move!!
// re gain lives
firebase.database().ref('Users/'+user.displayName+'/').update({
Lives: 3,
isBig: 0 // return to small mario.
})
setTimeout(()=>{
this.node.position = this.rebornPos;
// this.node.getComponent(cc.RigidBody).enabled = true;
cc.director.loadScene("Gameover");
return;
}, 4000);
}else{
// Play die animation.
// Stop bgm.
cc.audioEngine.pauseMusic();
cc.audioEngine.playEffect(this.dieSound,false);
// this.node.getComponent(cc.PhysicsBoxCollider).enabled = false; // Hide this node avoid multiple contact(DEAD).
setTimeout(()=>{
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
this.anim.play('idle');
this.node.position = this.rebornPos;
this.node.getComponent(cc.PhysicsBoxCollider).enabled = true;
cc.audioEngine.resumeMusic();
return;
}, 2000);
}
}else{
// Still alive.
if(this.LDown){
this.playerSpeed = -300;
this.node.scaleX = -3.5;
}
else if(this.RDown){
this.playerSpeed = 300;
this.node.scaleX = 3.5;
}else{ // idle
}
this.node.x += this.playerSpeed * dt;
if(this.SpaceDown && this.onGround){
this.jump();
}
}
}
private jump() {
// cc.log("Press Jump.");
this.onGround = false;
cc.audioEngine.playEffect(this.jumpSound,false);
this.getComponent(cc.RigidBody).linearVelocity = cc.v2(0, 1500);
}
update(dt) {
this.playerMovement(dt);
this.mainCamera.x = this.node.x;
this.coinText.string = this.coins.toString();
this.scoreText.string = this.score.toString();
}
// Callback functions for the beginning of contact.
onBeginContact(contact, self, other) {
// Mario on Top.
if(contact.getWorldManifold().normal.y==-1){
if(other.node.name == "ground") {
this.onGround = true;
} else if(other.tag == 2) { // tag = 2: enemy.
// cc.log("Enemy die.");
cc.audioEngine.playEffect(this.stompSound, false);
if(other.node.name == "Enemy_Turtle"){
if(other.node.getComponent('Enemy_Turtle').beenhit == true){
this.onGround = true; // mario can jump.
other.node.getComponent(cc.RigidBody).linearVelocity = cc.v2(0,0);
}
else{
this.getComponent(cc.RigidBody).linearVelocity = cc.v2(0,500);
// Turtle would shrink in its TypeScript.
}
}else if(other.node.name =="Enemy_goomba"){
// goomba destroy in its TypeScript.
}else{
other.node.destroy();
}
}
else if(other.tag == 3 || other.tag==4 || other.tag==7) { // any block or Qblock.
this.onGround = true;
}
else if(other.node.name == "left_bound" || other.node.name == "right_bound" || other.node.name == "Lower_bound"){
this.isDead = true;
}else if(other.node.name =="coin"){
cc.audioEngine.playEffect(this.coinSound,false);
this.coins += 1;
other.node.destroy();
}else if(other.tag == 5){
// grow up mushroom.
cc.log("Eat mushroom.");
if(!this.isBig){//can grow up
// First update state to firebase.
var user = firebase.auth().currentUser;
firebase.database().ref('Users/'+user.displayName+'/').update({
isBig : 1
}).then(()=>{
this.isBig = true;
this.growing = true;
this.anim.play('mario_grow');
// this.playerAnimation('mario_grow');
// this.animateState = this.anim.play('mario_grow');
this.getComponent(cc.RigidBody).linearVelocity = cc.v2(0,200);
this.changeSprite(this.BigMarioSprite);
cc.audioEngine.playEffect(this.GrowSound, false);
})
}else{
cc.log("Already big!");
cc.audioEngine.playEffect(this.coinSound, false);
}
other.node.destroy();
// this.get bigger.
// this.sprite.frame = ;
}else if(other.node.name == "Lives_item"){
cc.audioEngine.playEffect(this.reserveSound, false);
if(this.lives >=3){
this.lives = 3; //maintain.
cc.log("Life saturated.");
other.node.destroy();
}
else{
this.lives ++;
cc.log("Lives: " +this.lives);
other.node.destroy();
//update to firebase
var user = firebase.auth().currentUser;
firebase.database().ref('Users/'+user.displayName+'/').update({
Lives: this.lives
});
// Draw UI Life :
var newlife = cc.instantiate(this.lifePrefab);
this.lifeContainer.addChild(newlife);
newlife.scaleX = 0.3;
newlife.scaleY = 0.3;
if(this.lives == 3){
newlife.name = "Life3"
newlife.setPosition(50 +30*2 , 0);
}else if(this.lives ==2){
newlife.name = "Life2";
newlife.setPosition(50 +30*1 , 0);
}else{
newlife.name = "Life1";
newlife.setPosition(50 +30*0 , 0);
}
}
}
}
// Mario at Bottom.
else if(contact.getWorldManifold().normal.y== 1){
if(other.node.name == "ground") {
} else if(other.tag == 2) { // tag = 2: enemy.
// mario die.
}
else if(other.tag == 3 || other.tag==7) {
}else if(other.tag == 4) { // Qblock.
cc.log("Mario hits the question box.");
switch(other.node.name){
case "Qblock0":
if(!this.hitQblocks[0]){
this.hitQblocks[0] = true;
let idx = this.randomGenItem();
let item = cc.instantiate(this.ItemPrefabs[idx]); // item : cc.Node
this.Qblocks[0].addChild(item);
item.setPosition(0,this.Qblocks[0].height);
if(idx==0){
cc.audioEngine.playEffect(this.MushroomUp,false);
}else if(idx==1){
cc.audioEngine.playEffect(this.coinSound,false);
}else{
// Generate live.
cc.audioEngine.playEffect(this.GrowSound,false);
}
}else{
cc.log(other.node.name + " has been hit.");
}
break;
case "Qblock1":
if(!this.hitQblocks[1]){
this.hitQblocks[1] = true;
let idx = this.randomGenItem();
let item = cc.instantiate(this.ItemPrefabs[idx]); // item : cc.Node
this.Qblocks[1].addChild(item);
item.setPosition(0,this.Qblocks[1].height);
if(idx==0){
cc.audioEngine.playEffect(this.MushroomUp,false);
}else if(idx==1){
cc.audioEngine.playEffect(this.coinSound,false);
}else{
// Generate live.
cc.audioEngine.playEffect(this.GrowSound,false);
}
}else{
cc.log(other.node.name + " has been hit.");
}
break;
case "Qblock2":
if(!this.hitQblocks[2]){
this.hitQblocks[2] = true;
let idx = this.randomGenItem();
let item = cc.instantiate(this.ItemPrefabs[idx]); // item : cc.Node
this.Qblocks[2].addChild(item);
item.setPosition(0,this.Qblocks[2].height);
if(idx==0){
cc.audioEngine.playEffect(this.MushroomUp,false);
}else if(idx==1){
cc.audioEngine.playEffect(this.coinSound,false);
}else{
// Generate live.
cc.audioEngine.playEffect(this.GrowSound,false);
}
}else{
cc.log(other.node.name + " has been hit.");
}
break;
case "Qblock3":
if(!this.hitQblocks[3]){
this.hitQblocks[3] = true;
let idx = this.randomGenItem();
let item = cc.instantiate(this.ItemPrefabs[idx]); // item : cc.Node
this.Qblocks[3].addChild(item);
item.setPosition(0,this.Qblocks[3].height);
if(idx==0){
cc.audioEngine.playEffect(this.MushroomUp,false);
}else if(idx==1){
cc.audioEngine.playEffect(this.coinSound,false);
}else{
// Generate live.
cc.audioEngine.playEffect(this.GrowSound,false);
}
}else{
cc.log(other.node.name + " has been hit.");
}
break;
case "Qblock4":
if(!this.hitQblocks[4]){
this.hitQblocks[4] = true;
let idx = this.randomGenItem();
let item = cc.instantiate(this.ItemPrefabs[idx]); // item : cc.Node
this.Qblocks[4].addChild(item);
item.setPosition(0,this.Qblocks[4].height);
if(idx==0){
cc.audioEngine.playEffect(this.MushroomUp,false);
}else if(idx==1){
cc.audioEngine.playEffect(this.coinSound,false);
}else{
// Generate live.
cc.audioEngine.playEffect(this.GrowSound,false);
}
}else{
cc.log(other.node.name + " has been hit.");
}
break;
case "Qblock5":
if(!this.hitQblocks[5]){
this.hitQblocks[5] = true;
let idx = this.randomGenItem();
let item = cc.instantiate(this.ItemPrefabs[idx]); // item : cc.Node
this.Qblocks[5].addChild(item);
item.setPosition(0,this.Qblocks[5].height);
if(idx==0){
cc.audioEngine.playEffect(this.MushroomUp,false);
}else if(idx==1){
cc.audioEngine.playEffect(this.coinSound,false);
}else{
// Generate live.
cc.audioEngine.playEffect(this.GrowSound,false);
}
}else{
cc.log(other.node.name + " has been hit.");
}
break;
case "Qblock6":
if(!this.hitQblocks[6]){
this.hitQblocks[6] = true;
let idx = this.randomGenItem();
let item = cc.instantiate(this.ItemPrefabs[idx]); // item : cc.Node
this.Qblocks[6].addChild(item);
item.setPosition(0,this.Qblocks[6].height);
if(idx==0){
cc.audioEngine.playEffect(this.MushroomUp,false);
}else if(idx==1){
cc.audioEngine.playEffect(this.coinSound,false);
}else{
// Generate live.
cc.audioEngine.playEffect(this.GrowSound,false);
}
}else{
cc.log(other.node.name + " has been hit.");
}
break;
case "Qblock7":
if(!this.hitQblocks[7]){
this.hitQblocks[7] = true;
let idx = this.randomGenItem();
let item = cc.instantiate(this.ItemPrefabs[idx]); // item : cc.Node
this.Qblocks[7].addChild(item);
item.setPosition(0,this.Qblocks[7].height);
if(idx==0){
cc.audioEngine.playEffect(this.MushroomUp,false);
}else if(idx==1){
cc.audioEngine.playEffect(this.coinSound,false);
}else{
// Generate live.
cc.audioEngine.playEffect(this.GrowSound,false);
}
}else{
cc.log(other.node.name + " has been hit.");
}
break;
}
}else if(other.node.name =="coin"){
this.coins += 1;
cc.audioEngine.playEffect(this.coinSound,false);
other.node.destroy();
}else if(other.tag == 5){
// grow up mushroom.
cc.log("Eat mushroom.");
if(!this.isBig){//can grow up
// First update state to firebase.
var user = firebase.auth().currentUser;
firebase.database().ref('Users/'+user.displayName+'/').update({
isBig : 1
}).then(()=>{
this.isBig = true;
this.growing = true;
this.anim.play('mario_grow');
// this.playerAnimation('mario_grow');
this.getComponent(cc.RigidBody).linearVelocity = cc.v2(0,200);
this.changeSprite(this.BigMarioSprite);
cc.audioEngine.playEffect(this.GrowSound, false);
})
// this.animateState = this.anim.play('mario_grow');
// this.isBig = true;
// this.getComponent(cc.RigidBody).linearVelocity = cc.v2(0,200);
// this.changeSprite(this.BigMarioSprite);
// cc.audioEngine.playEffect(this.GrowSound, false);
}else{
cc.log("Already big!");
cc.audioEngine.playEffect(this.coinSound, false);
}
other.node.destroy();
// this.get bigger.
// this.sprite.frame = ;
}else if(other.node.name == "Lives_item"){
cc.audioEngine.playEffect(this.reserveSound, false);
if(this.lives >=3){
this.lives = 3; //maintain.
cc.log("Life saturated.");
other.node.destroy();
}
else{
this.lives ++;
cc.log("Lives: " +this.lives);
other.node.destroy();
//update to firebase
var user = firebase.auth().currentUser;
firebase.database().ref('Users/'+user.displayName+'/').update({
Lives: this.lives
});
// Draw UI Life :
var newlife = cc.instantiate(this.lifePrefab);
this.lifeContainer.addChild(newlife);
newlife.scaleX = 0.3;
newlife.scaleY = 0.3;
if(this.lives == 3){
newlife.name = "Life3"
newlife.setPosition(50 +30*2 , 0);
}else if(this.lives ==2){
newlife.name = "Life2";
newlife.setPosition(50 +30*1 , 0);
}else{
newlife.name = "Life1";
newlife.setPosition(50 +30*0 , 0);
}
}
}
}
// Left or Right direction.
else{
if(other.node.name == "ground") {
this.onGround = true;
}
else if(other.tag == 2) { // tag = 2: enemy.
if(other.node.name == "Enemy_Turtle"){
if(other.node.getComponent('Enemy_Turtle').beenhit == true){
cc.audioEngine.playEffect(this.kickSound,false);
}
else{
if(this.isBig){//Should shrink (won't die)
this.shrinking = true;
cc.log("shrink!");
// First update state to firebase.
var user = firebase.auth().currentUser;
firebase.database().ref('Users/'+user.displayName+'/').update({
isBig : 0
}).then(()=>{
this.isBig = false;
// this.shrinking = true;
this.anim.play('mario_shrink');
// this.playerAnimation('mario_shrink');
this.changeSprite(this.smallMarioSprite);
cc.audioEngine.playEffect(this.hurtSound, false);
})
}else{
if(this.shrinking){
cc.log("Now shrinking...");
// One second unhurtable time.
this.scheduleOnce( ()=>{
this.shrinking = false;
}, 1)
}else{
this.isDead = true;
}
}
}
}
else{ // goomba or flower.
if(this.isBig){//Should shrink (won't die)
this.shrinking = true
// First update state to firebase.
var user = firebase.auth().currentUser;
firebase.database().ref('Users/'+user.displayName+'/').update({
isBig : 0
}).then(()=>{
this.isBig = false;
// this.shrinking = true;
this.anim.play('mario_shrink');
this.changeSprite(this.smallMarioSprite);
cc.audioEngine.playEffect(this.hurtSound, false);
})
}else{
if(this.shrinking){
cc.log("Now shrinking...");
// One second unhurtable time.
this.scheduleOnce( ()=>{
this.shrinking = false;
}, 1)
}else{
this.isDead = true;
}
}
}
}
else if(other.tag == 3 || other.tag==4) {
this.onGround = true;
}
else if(other.tag == 5){
// grow up mushroom.
cc.log("Eat mushroom.");
if(!this.isBig){//can grow up
// First update state to firebase.
var user = firebase.auth().currentUser;
firebase.database().ref('Users/'+user.displayName+'/').update({
isBig : 1
}).then(()=>{
this.isBig = true;
this.growing = true;
this.anim.play('mario_grow');
// this.playerAnimation('mario_grow');
this.getComponent(cc.RigidBody).linearVelocity = cc.v2(0,200);
this.changeSprite(this.BigMarioSprite);
cc.audioEngine.playEffect(this.GrowSound, false);
})
}else{
cc.log("Already big!");
cc.audioEngine.playEffect(this.coinSound, false);
}
other.node.destroy();
// this.get bigger.
// this.sprite.frame = ;
}
else if(other.node.name == "left_bound" || other.node.name == "right_bound" || other.node.name == "Lower_bound"){
this.isDead = true;
}
else if(other.tag == 6){//reach goal flag.
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
contact.disabled = true;
this.LDown = this.RDown = this.SpaceDown = false;
//this.playerSpeed = 0;
let action = cc.repeatForever(cc.moveBy(1, 0, 1500))
cc.log("Run action now.");
this.node.runAction(action);
// this.playerAnimation('mario_win');
if(this.isBig)this.animateState = this.anim.play('Big_win');
else this.animateState = this.anim.play('mario_win');
// Send "win" to level_1.ts.
let Rtime = cc.find("Canvas").getComponent("level_1").remainTime;
this.score = Rtime.toFixed(0).toString().replace(".", ":");
cc.find("Canvas").getComponent("level_1").win();
}else if(other.node.name =="coin"){
this.coins += 1;
cc.audioEngine.playEffect(this.coinSound,false);
other.node.destroy();
}else if(other.node.name == "Lives_item"){
cc.audioEngine.playEffect(this.reserveSound, false);
cc.log("Gain_lives!");
if(this.lives >=3){
this.lives = 3; //maintain.
cc.log("Life saturated.");
other.node.destroy();
}
else{
this.lives ++;
cc.log("Lives: " +this.lives);
other.node.destroy();
//update to firebase
var user = firebase.auth().currentUser;
firebase.database().ref('Users/'+user.displayName+'/').update({
Lives: this.lives
});
// Draw UI Life :
var newlife = cc.instantiate(this.lifePrefab);
this.lifeContainer.addChild(newlife);
newlife.scaleX = 0.3;
newlife.scaleY = 0.3;
if(this.lives == 3){
newlife.name = "Life3"
newlife.setPosition(50 +30*2 , 0);
}else if(this.lives ==2){
newlife.name = "Life2";
newlife.setPosition(50 +30*1 , 0);
}else{
newlife.name = "Life1";
newlife.setPosition(50 +30*0 , 0);
}
}
}
}
} // End onBeginContact.
private playerAnimation(anim_str: string)
{
var playing = this.anim.play(this.anim_str).isPlaying;
// Grow, shrink, die need to be frozen. CANNOT change to idle
if(anim_str == this.anim_str)return;
else{
cc.log("Change amim!");
cc.log("Now: "+this.anim_str+", Play: "+anim_str);
this.anim.stop();
this.anim_str = anim_str;
this.anim.play(this.anim_str);
}
}
randomGenItem()
{
let rand = Math.random();
//0: Mushroom, 1: coin, 2: lives.
let prob = [1,1,1];
let sum = prob.reduce((a,b)=>a+b);
for(let i = 1; i < prob.length; i++)
prob[i] += prob[i-1];
for(let i = 0; i < prob.length; i++)
{
prob[i] /= sum;
if(rand <= prob[i])
return i;
}
}
changeSprite(Sprite: cc.SpriteFrame) {
// change sprite frame to the one specified by the property
this.getComponent(cc.Sprite).spriteFrame = Sprite;
}
}
```
### 2. level_1.ts
控制level_1的遊戲程序的主要程式。從`onload()` `start()` `update(dt)` 三個核心**method**下去講解。
**(1)onload()**:
程式載入後做兩件事情:
**1.初始化class properties**
**2.初始化`this.gameover`為 *false***
``` typescript=
onLoad () {
this.initProperties();
this.gameover = false;
}
```
其中`initPropterties()`:
``` typescript=
initProperties(){
this.levelText = cc.find("Canvas/Main Camera/level1/TopContent/Level_one/level").getComponent(cc.Label);
this.timeText = cc.find("Canvas/Main Camera/level1/TopContent/Time/time").getComponent(cc.Label);
}
```
**(2)start()**:
onLoad()後做兩件事情:
**1.撥放背景音樂**
**2.初始化Pause button `this.Toggle_init()`**
``` typescript=
start () {
this.playBGM();
this.Toggle_init();
}
```
**(3)update(dt)**:
每dt時間更新一次:
**1.若遊戲還在繼續 `if(!this.gameover)`**
**2.更新UI參數(`Time`,`level`)**
**3.如果時間倒數到0,遊戲結束**
``` typescript=
update (dt) {
if(!this.gameover){
this.updateUI(dt);
if(this.remainTime == 0){
this.gameover = true;
this.onGameover();
}
}
}
```
`updateUI(dt):`
``` typescript=
updateUI(dt){
this.levelText.string = this.gameLevel.toString();
if(!this.Win){
this.remainTime -= dt;
if(this.remainTime < 0){
this.remainTime = 0;
}
}
this.timeText.string = this.remainTime.toFixed(0).toString().replace(".", ":");
}
```
:::danger
**最重要的`win()` Property:**
``` typescript=
win(){
cc.log("win");
this.stopBGM();
// stop timer.
this.Win = true;
cc.audioEngine.playMusic(this.victory, false);
var user = firebase.auth().currentUser;
// num_of_times ++;
firebase.database().ref('Users/'+user.displayName+'/num_of_times').once('value',(snapshot)=>{
// get current time, then increment it by one.
firebase.database().ref('Users/'+user.displayName).update({
num_of_times: snapshot.val()+ 1
}).then(()=>{
// Log score data.
firebase.database().ref('Users/'+user.displayName+'/num_of_times').once('value',(snapshot)=>{
// get current times.
firebase.database().ref('Users/'+user.displayName+'/'+snapshot.val()).set({
scores : Number(cc.find("Canvas/mario").getComponent("Player").score),
time: Number(300- Number(this.remainTime.toFixed(0)))
})
})
})
})
// Check if need to update 'Best_score'
firebase.database().ref('Users/'+user.displayName+'/Best_score').once('value',(snapshot)=>{
// get current times.
if(cc.find("Canvas/mario").getComponent("Player").score.toString() > snapshot.val()){
// Update Best_score.
firebase.database().ref('Users/'+user.displayName+'/').update({
Best_score: cc.find("Canvas/mario").getComponent("Player").score.toString()
})
}
})
// Update user lives.
firebase.database().ref('Users/'+user.displayName+'/').update({
Lives: cc.find("Canvas/mario").getComponent("Player").lives
})
setTimeout(()=>{
cc.director.loadScene("1to2");
return;
}, 5000);
}
```
:::
* 另外在遊戲**暫停**部分,code部分如下
``` typescript=
Pause(event, customEventData) {
// 这里的 toggle 是事件发出的 Toggle 组件
// 这里的 customEventData 参数就等于之前设置的 "foobar"
// Pause.
// Turn off button listener.
// this.OFFPause();
this.count++;
if(this.count==1){
// Draw UI Life :
var pause = cc.instantiate(this.PauseBoard);
this.Canvas.addChild(pause);
pause.setPosition(this.MainCamera.x, this.MainCamera.y);
// Init resume btns.
this.resume_init();
cc.log("Pause");
this.stopBGM();
cc.director.pause();
}else{
cc.log("Cannot press Pause since Now pausing.");
}
}
```
* 然後暫停後的**恢復遊戲**,**離開遊戲**如下:
``` typescript=
Resume(event, customEventData){
// Destroy pause board and resume the game!
this.count = 0;
cc.log("Resume");
cc.find("Canvas/pause").destroy();
cc.director.resume();
this.resumeBGM();
}
Quit(event, customEventData){
// Quit game
this.count = 0;
cc.find("Canvas/pause").destroy();
cc.director.loadScene("setting");
}
```
* 完整的level_1.ts如下:
``` typescript=
// Learn TypeScript:
// - [Chinese] https://docs.cocos.com/creator/manual/zh/scripting/typescript.html
// - [English] http://www.cocos2d-x.org/docs/creator/manual/en/scripting/typescript.html
// Learn Attribute:
// - [Chinese] https://docs.cocos.com/creator/manual/zh/scripting/reference/attributes.html
// - [English] http://www.cocos2d-x.org/docs/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
// - [Chinese] https://docs.cocos.com/creator/manual/zh/scripting/life-cycle-callbacks.html
// - [English] http://www.cocos2d-x.org/docs/creator/manual/en/scripting/life-cycle-callbacks.html
import Player from "./Player";
const {ccclass, property} = cc._decorator;
@ccclass
export default class level_1 extends cc.Component {
@property({type:cc.AudioClip})
bgm: cc.AudioClip = null;
@property({type:cc.AudioClip})
touch_coin: cc.AudioClip = null;
@property({type:cc.AudioClip})
victory: cc.AudioClip = null;
@property(cc.Node)
Canvas: cc.Node = null;
@property(cc.Node)
MainCamera: cc.Node = null;
@property(cc.Prefab)
PauseBoard: cc.Prefab = null;
// @property(Player)
// mario: Player;
private count: number = 0;
private Win: boolean = false;
// remain_lives: number = 0;
levelText: cc.Label;
timeText: cc.Label;
gameLevel = 1;
remainTime = 300; // 300 second
gameover: boolean = false;
initProperties(){
this.levelText = cc.find("Canvas/Main Camera/level1/TopContent/Level_one/level").getComponent(cc.Label);
this.timeText = cc.find("Canvas/Main Camera/level1/TopContent/Time/time").getComponent(cc.Label);
}
updateUI(dt){
this.levelText.string = this.gameLevel.toString();
if(!this.Win){
this.remainTime -= dt;
if(this.remainTime < 0){
this.remainTime = 0;
}
}
// this.timeText.string = this.remainTime.toString();
this.timeText.string = this.remainTime.toFixed(0).toString().replace(".", ":");
}
// private count : number = 0;
playBGM(){
// ===================== TODO =====================
// 1. Play music. The audio clip to play is this.bgm
cc.audioEngine.playMusic(this.bgm, true)
// ================================================
}
stopBGM(){
// ===================== TODO =====================
// 1. Stop music.
cc.audioEngine.pauseMusic();
// ================================================
}
resumeBGM(){
cc.audioEngine.resumeMusic();
}
playEffect(){
// ===================== TODO =====================
// 1. Play sound effect. The audio clip to play is
cc.log("Click.");
cc.audioEngine.playEffect(this.touch_coin, false);
// ================================================
}
Toggle_init(){
let checkEventHandler = new cc.Component.EventHandler();
checkEventHandler.target = this.node; // 这个 node 节点是你的事件处理代码组件所属的节点
checkEventHandler.component = "level_1";
checkEventHandler.handler = "Pause";
checkEventHandler.customEventData = "foobar";
cc.find("Canvas/Main Camera/level1/UI/Pausebtn").getComponent(cc.Button).clickEvents.push(checkEventHandler);
// this.toggle.checkEvents.push(checkEventHandler);
}
resume_init(){
let conti = new cc.Component.EventHandler();
conti.target = this.node; // 这个 node 节点是你的事件处理代码组件所属的节点
conti.component = "level_1";
conti.handler = "Resume";
conti.customEventData = "foobar";
cc.find("Canvas/pause/Continue").getComponent(cc.Button).clickEvents.push(conti);
let quit = new cc.Component.EventHandler();
quit.target = this.node; // 这个 node 节点是你的事件处理代码组件所属的节点
quit.component = "level_1";
quit.handler = "Quit";
quit.customEventData = "foobar";
cc.find("Canvas/pause/Quit").getComponent(cc.Button).clickEvents.push(quit);
}
Resume(event, customEventData){
// Destroy pause board and resume the game!
this.count = 0;
cc.log("Resume");
cc.find("Canvas/pause").destroy();
cc.director.resume();
this.resumeBGM();
}
Quit(event, customEventData){
// Quit game
this.count = 0;
cc.find("Canvas/pause").destroy();
cc.director.loadScene("setting");
}
// EnablePause(){
// cc.find("Canvas/Main Camera/level1/UI/Pausebtn").on('click', this.Pause , this);
// }
// OFFPause(){
// cc.find("Canvas/Main Camera/level1/UI/Pausebtn").getComponent(cc.Button).onDisable();
// }
Pause(event, customEventData) {
// 这里的 toggle 是事件发出的 Toggle 组件
// 这里的 customEventData 参数就等于之前设置的 "foobar"
// Pause.
// Turn off button listener.
// this.OFFPause();
this.count++;
if(this.count==1){
// Draw UI Life :
var pause = cc.instantiate(this.PauseBoard);
this.Canvas.addChild(pause);
pause.setPosition(this.MainCamera.x, this.MainCamera.y);
// Init resume btns.
this.resume_init();
cc.log("Pause");
this.stopBGM();
cc.director.pause();
}else{
cc.log("Cannot press Pause since Now pausing.");
}
}
win(){
cc.log("win");
this.stopBGM();
// stop timer.
this.Win = true;
cc.audioEngine.playMusic(this.victory, false);
var user = firebase.auth().currentUser;
// num_of_times ++;
firebase.database().ref('Users/'+user.displayName+'/num_of_times').once('value',(snapshot)=>{
// get current time, then increment it by one.
firebase.database().ref('Users/'+user.displayName).update({
num_of_times: snapshot.val()+ 1
}).then(()=>{
// Log score data.
firebase.database().ref('Users/'+user.displayName+'/num_of_times').once('value',(snapshot)=>{
// get current times.
firebase.database().ref('Users/'+user.displayName+'/'+snapshot.val()).set({
scores : Number(cc.find("Canvas/mario").getComponent("Player").score),
time: Number(300- Number(this.remainTime.toFixed(0)))
})
})
})
})
// Check if need to update 'Best_score'
firebase.database().ref('Users/'+user.displayName+'/Best_score').once('value',(snapshot)=>{
// get current times.
if(cc.find("Canvas/mario").getComponent("Player").score.toString() > snapshot.val()){
// Update Best_score.
firebase.database().ref('Users/'+user.displayName+'/').update({
Best_score: cc.find("Canvas/mario").getComponent("Player").score.toString()
})
}
})
// Update user lives.
firebase.database().ref('Users/'+user.displayName+'/').update({
Lives: cc.find("Canvas/mario").getComponent("Player").lives
})
setTimeout(()=>{
cc.director.loadScene("1to2");
return;
}, 5000);
}
// LIFE-CYCLE CALLBACKS:
onLoad () {
this.initProperties();
this.gameover = false;
}
start () {
this.playBGM();
this.Toggle_init();
}
update (dt) {
if(!this.gameover){
this.updateUI(dt);
if(this.remainTime == 0){
this.gameover = true;
this.onGameover();
}
}
}
onGameover(){
cc.director.loadScene("Gameover");
}
}
```
### 3. Enemy_Turtle.ts
與烏龜相關的一切程式。從`onload()` `start()` `update(dt)` 三個核心**method**下去講解。
**(1)onload()**:
程式載入後做兩件事情:
**1.開啟物理引擎**
**2.初始化`this.anim`為`cc.Animation`物件**
``` typescript=
onLoad() {
cc.director.getPhysicsManager().enabled = true;
this.anim = this.getComponent(cc.Animation);
}
```
**(2)start()**:
onLoad()後做四件事情:
**1.取得rebornPos**
**2.初始化linearVelocity**
**3.初始化`this.isDead`為false**
**4.初始化Animation為`Turtle_anim`**
``` typescript=
start() {
this.rebornPos = this.node.position;
this.node.getComponent(cc.RigidBody).linearVelocity = cc.v2(-100, 0);
this.isDead = false;
this.animateState = this.anim.play('Turtle_anim');
}
```
**(3)update(dt)**:
每dt時間更新一次:
**1.若`isDead`為True**
**2.重生烏龜位置**
**3.reset `this.isDead` 為 false**
``` typescript=
update(dt) {
if(this.isDead) {
this.resetPos();
this.isDead = false;
}
}
```
:::info
本程式這次的**Turtle**部分沒有使用到`this.reborn()`,因為烏龜被踩扁後會變成龜殼,尚未做到讓烏龜死掉的部分
:::
* 完整的`Enemy_Turtle.ts`如下:
``` typescript=
const {ccclass, property} = cc._decorator;
@ccclass
export default class Enemy_Turtle extends cc.Component {
private beenhit: boolean = false;
private anim = null; //this will use to get animation component
private animateState = null; //this will use to record animationState
private rebornPos = null;
private isDead = true;
onLoad() {
cc.director.getPhysicsManager().enabled = true;
this.anim = this.getComponent(cc.Animation);
}
start() {
this.rebornPos = this.node.position;
this.node.getComponent(cc.RigidBody).linearVelocity = cc.v2(-100, 0);
this.isDead = false;
this.animateState = this.anim.play('Turtle_anim');
}
update(dt) {
if(this.isDead) {
this.resetPos();
this.isDead = false;
}
}
public resetPos() {
this.node.position = this.rebornPos;
this.node.scaleX = 1;
this.node.getComponent(cc.RigidBody).linearVelocity = cc.v2(-100, 0);
}
onBeginContact(contact, self, other) {
if(other.node.name == "left_bound") {
this.node.scaleX = -3.5; // turn right.
this.node.getComponent(cc.RigidBody).linearVelocity = cc.v2(100, 0);
} else if(other.node.name == "right_bound") {
this.node.scaleX = 3.5;
this.node.getComponent(cc.RigidBody).linearVelocity = cc.v2(-100, 0);
} else if(other.node.name == "bullet") {
this.isDead = true;
other.node.destroy();
}
else if(other.tag == 3){ // block.
if(this.beenhit){
if(this.node.scaleX == -3.5){ //Mow moving right.
this.node.scaleX = 3.5; // Turn Left.
this.node.getComponent(cc.RigidBody).linearVelocity = cc.v2(-200, 0); // Move left.
}
else{
this.node.scaleX = -3.5;
this.node.getComponent(cc.RigidBody).linearVelocity = cc.v2(200, 0);
}
}
else{
if(this.node.scaleX == -3.5){ //Mow moving right.
this.node.scaleX = 3.5; // Turn Left.
this.node.getComponent(cc.RigidBody).linearVelocity = cc.v2(-100, 0); // Move left.
}
else{
this.node.scaleX = -3.5;
this.node.getComponent(cc.RigidBody).linearVelocity = cc.v2(100, 0);
}
}
}else if(other.node.name =="mario" || other.node.name=="mario_level2"){
if(this.beenhit){
if(contact.getWorldManifold().normal.y ==1){
// stop speed.
this.node.getComponent(cc.RigidBody).linearVelocity = cc.v2(0, 0); // Move left.
this.anim.stop('Turtle_spin');
}
else{
// Just change direction.
this.animateState = this.anim.play('Turtle_spin');
if(other.node.x > this.node.x){ // mario is right of us.
this.node.scaleX = 3.5; // Turn Left.
this.node.getComponent(cc.RigidBody).linearVelocity = cc.v2(-200, 0); // Move left.
}
else{
this.node.scaleX = -3.5;
this.node.getComponent(cc.RigidBody).linearVelocity = cc.v2(200, 0); // Move right.
}
}
}
else{
if(contact.getWorldManifold().normal.y==1){ // Mario is on top of us.
this.anim.play('Turtle_shrink');
this.node.getComponent(cc.RigidBody).linearVelocity = cc.v2(0, 0);
this.beenhit = true;
}
else{
// Mario dead.
}
}
}
} // end onBeginContact.
}
```
###### tags: `Software Studio` `Software Lab` `軟體設計與實驗` `軟實`