CocosCreator學習1:做一個簡單的遊戲
把計步器寫好了,到了寫遊戲場景、控制元件什麼的時候,傻眼了。想做一個簡單的地圖,可以在地圖上點選選擇城市,發現用Cocos2D-X程式碼碼出來好麻煩,尤其是城市位置問題,需要除錯去找對畫素區域做一個按鈕控制,整個人都不好了。本來還想嘗試用Tiled map來做,想法很逗比,應該不能用。今晚被長輩連番教育中,說到了撞牆走彎路才想起來,當初光顧著學引擎看程式碼了,竟然忘了還有CocosCreator,這麼簡單實用的工具我竟然沒有去用,太傻了。而且現在距離比較提交程式結果時間也比較緊了,直接用CocosCreator做遊戲時最明智的。下面將轉向CocosCreator的學習。
開啟專案
教程中提供了一個簡單遊戲的初始版本,我就從這個遊戲入手進行學習。從CocosCreator選擇開啟其他專案,開啟start_project資料夾,選中即可開啟。注意下載的start_project不能放在CocosCreator路徑下。
開啟後如圖:
在資源管理器面板中,可以看到專案中的檔案。專案資源的根目錄叫asserts,對應於解壓初始版本得到的資料夾名。其中標註bf圖示的檔案是點陣圖字型,是一種字型資源,由fnt檔案和同名的png圖片檔案共同組成。
建立場景
下面來建立遊戲場景。遊戲場景一般包括:場景影象(Sprite)、文字(Label)、角色、以元件形式附加在場景節點上的遊戲邏輯指令碼。在資源管理器中點選asserts目錄前面的加號,選擇Scene,會建立一個New Scene的場景檔案。
雙擊New Scene,就會在場景編輯器和層級管理器中開啟這個場景。在層級管理器中可以看到當前場景的所有節點和他們的層級關係。
Canvas畫布節點/渲染根節點
現在New Scene中只有一個Canvas節點,稱為畫布節點或渲染根節點,點選選中Canvas可以在屬性檢查器中看到他的屬性。
其中的Design Resolution屬性用來指定遊戲解析度,Fit Hight和Fit Width是解析度自適應匹配方法。只需要把影象放到Canvas下,就可以完成對不同解析度的自適應,非常簡單。
設定場景影象
增加背景影象
在資源管理器中將textures中background影象拖到
層級管理器的Canvas下即可增加背景影象,會看到Canvas下增加了一個background子節點。
點選檔案->儲存場景或使用快捷鍵ctrl+s儲存場景後,在場景編輯器中便會顯示增加的背景影象。
點選視窗左上角工具欄的第四格矩形變換工具,然後選中場景編輯器中的背景影象,可以實現對影象尺寸、位置、錨點的修改。
當然也可以直接使用屬性檢查器來直接設定這些引數。
在實際的應用開發中背景影象一般都是要大於顯示範圍的,這樣可以讓背景圖覆蓋整個螢幕,不出現穿幫的情況。
增加地面
使用和增加背景影象相同的方法,將地面影象拖到層級管理器的Canvas上,將ground節點移到background節點下方。
在層級管理器中,下方的節點的渲染順序在上方節點之後,也就是說下方節點會遮擋上方節點。
使用相同的方法可以對地面影象的尺寸、位置、錨點進行修改。
增加主角
同樣的方法,將主角影象player拖到ground下方。在之後的動畫中,需要主角接觸地面並跳躍,因此將錨點改為最下方,即y值為0,然後將它拖到地面上。
編寫指令碼
建立並新增指令碼
指令碼也就是遊戲的邏輯規則,需要程式碼實現。
首先在資源管理器中右鍵點選assets資料夾,新建->資料夾,建立一個名為New Folder的資料夾,然後對其右擊選擇新建->JavaScript,建立一個JavaScript指令碼NewScript,重新命名為Player,雙擊即可開啟編寫介面。Cocos Creator 中指令碼名稱就是元件的名稱。
在Player中的properties部分增加以下程式碼:
// Player.js
//...
properties: {
// 主角跳躍高度
jumpHeight: 0,
// 主角跳躍持續時間
jumpDuration: 0,
// 最大移動速度
maxMoveSpeed: 0,
// 加速度
accel: 0,
},
//...
這些屬性用於規定主角的移動方式,可以在屬性檢查器中直接設定屬性的數值。
然後在層級編輯器中選擇player,在屬性檢查器中選擇新增元件,選擇增加使用者指令碼元件->Player,即可為主角新增元件。
現在可以在屬性檢查器看到Player元件並修改屬性數值了。
跳躍和移動程式碼
在properties程式碼塊下面定義setJumpAction方法:
// Player.js
properties: {
//...
},
setJumpAction: function () {
// 跳躍上升
var jumpUp = cc.moveBy(this.jumpDuration, cc.p(0, this.jumpHeight)).easing(cc.easeCubicActionOut());
// 下落
var jumpDown = cc.moveBy(this.jumpDuration, cc.p(0, -this.jumpHeight)).easing(cc.easeCubicActionIn());
// 不斷重複
return cc.repeatForever(cc.sequence(jumpUp, jumpDown));
},
修改onLoad: function:
// Player.js
onLoad: function () {
// 初始化跳躍動作
this.jumpAction = this.setJumpAction();
this.node.runAction(this.jumpAction);
},
onLoad方法會在場景載入完成後立即執行,所以在這裡進行初始化操作。
儲存指令碼執行一下試試,可以看到player不斷的在進行跳躍動作。
在setJumpAction下面定義setInputControl方法來實現用A和D控制主角的移動操作:
// Player.js
setJumpAction: function () {
//...
},
setInputControl: function () {
var self = this;
// 新增鍵盤事件監聽
cc.eventManager.addListener({
event: cc.EventListener.KEYBOARD,
// 有按鍵按下時,判斷是否是我們指定的方向控制鍵,並設定向對應方向加速
onKeyPressed: function(keyCode, event) {
switch(keyCode) {
case cc.KEY.a:
self.accLeft = true;
self.accRight = false;
break;
case cc.KEY.d:
self.accLeft = false;
self.accRight = true;
break;
}
},
// 鬆開按鍵時,停止向該方向的加速
onKeyReleased: function(keyCode, event) {
switch(keyCode) {
case cc.KEY.a:
self.accLeft = false;
break;
case cc.KEY.d:
self.accRight = false;
break;
}
}
}, self.node);
},
修改onLoad方法,在其中加入向左和向右加速的開關,以及主角當前在水平方向的速度,最後再呼叫我們剛新增的setInputControl方法,在場景載入後就開始監聽鍵盤輸入:
// Player.js
onLoad: function () {
// 初始化跳躍動作
this.jumpAction = this.setJumpAction();
this.node.runAction(this.jumpAction);
// 加速度方向開關
this.accLeft = false;
this.accRight = false;
// 主角當前水平方向速度
this.xSpeed = 0;
// 初始化鍵盤輸入監聽
this.setInputControl();
},
最後修改update方法,新增加速度、速度和主角當前位置的設定:
// Player.js
update: function (dt) {
// 根據當前加速度方向每幀更新速度
if (this.accLeft) {
this.xSpeed -= this.accel * dt;
} else if (this.accRight) {
this.xSpeed += this.accel * dt;
}
// 限制主角的速度不能超過最大值
if ( Math.abs(this.xSpeed) > this.maxMoveSpeed ) {
// if speed reach limit, use max speed with current direction
this.xSpeed = this.maxMoveSpeed * this.xSpeed / Math.abs(this.xSpeed);
}
// 根據當前速度更新主角的位置
this.node.x += this.xSpeed * dt;
},
這樣就可以實現對主角移動的控制了。
製作Prefab指令碼-星星
這裡增加遊戲規則,從隨機位置生成星星,用主角接住星星即可得分。
對於重複生成的節點,可以儲存成Prefab(預置)資源,作為動態生成節點時的模板。
這裡直接將assets/textures/star資源到場景中,位置隨意,給星星增加一個指令碼,當主角碰到星星時,讓星星消失。
// Star.js
properties: {
// 星星和主角之間的距離小於這個數值時,就會完成收集
pickRadius: 0
},
給star節點增加指令碼,將pickRadius值設定為60。將層級管理器中的star節點拖到資源管理器的assets資料夾下,將場景中的star節點刪除。star現在即為Prefab資源。
新增遊戲控制指令碼
在assets/scripts資料夾下新增遊戲的主邏輯指令碼Game,新增生成星星需要的屬性:
// Game.js
properties: {
// 這個屬性引用了星星預製資源
starPrefab: {
default: null,
type: cc.Prefab
},
// 星星產生後消失時間的隨機範圍
maxStarDuration: 0,
minStarDuration: 0,
// 地面節點,用於確定星星生成的高度
ground: {
default: null,
type: cc.Node
},
// player 節點,用於獲取主角彈跳的高度,和控制主角行動開關
player: {
default: null,
type: cc.Node
}
},
然後將Game新增到Canvas節點上,然後將star這個Prefab資源拖到Canvas屬性檢查器Game元件中的Star Prefab屬性上。把層級管理器中的ground和Player節點也拖到元件中相應屬性處即可。設定Min Star Duration和Max Star Duration值為3和5,之後生成星星時,會在這兩個值之間隨機取值作為星星消失前經過的時間。
在隨機位置生成星星
修改Game指令碼,在onLoad方法後面新增星星的邏輯:
// Game.js
onLoad: function () {
// 獲取地平面的 y 軸座標
this.groundY = this.ground.y + this.ground.height/2;
// 生成一個新的星星
this.spawnNewStar();
},
spawnNewStar: function() {
// 使用給定的模板在場景中生成一個新節點
var newStar = cc.instantiate(this.starPrefab);
// 將新增的節點新增到 Canvas 節點下面
this.node.addChild(newStar);
// 為星星設定一個隨機位置
newStar.setPosition(this.getNewStarPosition());
},
getNewStarPosition: function () {
var randX = 0;
// 根據地平面位置和主角跳躍高度,隨機得到一個星星的 y 座標
var randY = this.groundY + cc.random0To1() * this.player.getComponent('Player').jumpHeight + 50;
// 根據螢幕寬度,隨機得到一個星星 x 座標
var maxX = this.node.width/2;
randX = cc.randomMinus1To1() * maxX;
// 返回星星座標
return cc.p(randX, randY);
}
新增主角碰觸收集星星的行為
重點在於星星要隨時獲取主角節點的位置,才能判斷與主角的距離是否小於可收集距離。因此將Game元件的例項傳給星星並儲存,然後可以隨時通過game.player來訪問主角節點。
在Game指令碼的spawnNewStar方法最後新增如下程式碼:
// Game.js
spawnNewStar: function() {
// ...
// 將 Game 元件的例項傳入星星元件
newStar.getComponent('Star').game = this;
},
然後在star腳本里onLoad方法後面新增getPlayerDistance和onPicked方法:
// Star.js
getPlayerDistance: function () {
// 根據 player 節點位置判斷距離
var playerPos = this.game.player.getPosition();
// 根據兩點位置計算兩點之間距離
var dist = cc.pDistance(this.node.position, playerPos);
return dist;
},
onPicked: function() {
// 當星星被收集時,呼叫 Game 指令碼中的介面,生成一個新的星星
this.game.spawnNewStar();
// 然後銷燬當前星星節點
this.node.destroy();
},
在update方法中新增每幀判斷距離,如果距離小於pickRadius屬性規定值,就執行收集行為:
// Star.js
update: function (dt) {
// 每幀判斷和主角之間的距離是否小於收集距離
if (this.getPlayerDistance() < this.pickRadius) {
// 呼叫收集行為
this.onPicked();
return;
}
},
現在可以看效果了。
新增計分牌
在層級管理器的Canvas中右鍵建立新節點->建立渲染節點->Label(文字),命名為score。將position設為(0,180),string設為score:0,Font Size設為50。從資源管理器中將assets/mikado_outline_shadow點陣圖字型資源拖到Font屬性中。
新增得分邏輯
在Game的properties裡新增score需要的屬性:
// Game.js
properties: {
// ...
// score label 的引用
scoreDisplay: {
default: null,
type: cc.Label
}
},
在onLoad方法裡新增計分用的變數初始化:
// Game.js
onLoad: function () {
// ...
// 初始化計分
this.score = 0;
},
在update方法後面新增gainScore的新方法:
// Game.js
gainScore: function () {
this.score += 1;
// 更新 scoreDisplay Label 的文字
this.scoreDisplay.string = 'Score: ' + this.score.toString();
},
將score節點拖到Canvas屬性的Game元件中的Score Display屬性中。
在Star指令碼的onPicked方法中新增gainScore的呼叫:
// Star.js
onPicked: function() {
// 當星星被收集時,呼叫 Game 指令碼中的介面,生成一個新的星星
this.game.spawnNewStar();
// 呼叫 Game 指令碼的得分方法
this.game.gainScore();
// 然後銷燬當前星星節點
this.node.destroy();
},
這樣收集到星星,分數就會變化了。
失敗判斷和重新開始
加入給星星加入計時消失的邏輯,在Game指令碼的onLoad方法的spawNewStar呼叫之前加入需要的變數宣告:
// Game.js
onLoad: function () {
// …
// 初始化計時器
this.timer = 0;
this.starDuration = 0;
// 生成一個新的星星
this.spawnNewStar();
// 初始化計分
this.score = 0;
},
然後在spawnNewStar方法最後加入重置計時器的邏輯,其中this.minStarDuration和this.maxStarDuration是我們一開始宣告的Game元件屬性,用來規定星星消失時間的隨機範圍:
// Game.js
spawnNewStar: function() {
// ...
// 重置計時器,根據消失時間範圍隨機取一個值
this.starDuration = this.minStarDuration + cc.random0To1() * (this.maxStarDuration - this.minStarDuration);
this.timer = 0;
},
在update方法中加入計時器更新和判斷超過時限的邏輯:
// Game.js
update: function (dt) {
// 每幀更新計時器,超過限度還沒有生成新的星星
// 就會呼叫遊戲失敗邏輯
if (this.timer > this.starDuration) {
this.gameOver();
return;
}
this.timer += dt;
},
最後加入gameOver方法,遊戲失敗重新載入場景。
// Game.js
gameOver: function () {
this.player.stopAllActions(); //停止 player 節點的跳躍動作
cc.director.loadScene('game');
}
然後修改Star指令碼,加入即將消失星星的提示效果,在update方法中加入以下程式碼:
// Star.js
update: function() {
// ...
// 根據 Game 指令碼中的計時器更新星星的透明度
var opacityRatio = 1 - this.game.timer/this.game.starDuration;
var minOpacity = 50;
this.node.opacity = minOpacity + Math.floor(opacityRatio * (255 - minOpacity));
}
這樣遊戲畫面和邏輯就全部完成了,可以說遊戲已經基本完成了。
加入音效
首先加入跳躍音效,在Player指令碼中引用聲音檔案資源jumpAudio屬性:
// Player.js
properties: {
// ...
// 跳躍音效資源
jumpAudio: {
default: null,
url: cc.AudioClip
},
},
修改setJumpAction方法,插入播放音效的回撥,並通過新增playJumpSound方法來播放聲音:
// Player.js
setJumpAction: function () {
// 跳躍上升
var jumpUp = cc.moveBy(this.jumpDuration, cc.p(0, this.jumpHeight)).easing(cc.easeCubicActionOut());
// 下落
var jumpDown = cc.moveBy(this.jumpDuration, cc.p(0, -this.jumpHeight)).easing(cc.easeCubicActionIn());
// 新增一個回撥函式,用於在動作結束時呼叫我們定義的其他方法
var callback = cc.callFunc(this.playJumpSound, this);
// 不斷重複,而且每次完成落地動作後呼叫回撥來播放聲音
return cc.repeatForever(cc.sequence(jumpUp, jumpDown, callback));
},
playJumpSound: function () {
// 呼叫聲音引擎播放聲音
cc.audioEngine.playEffect(this.jumpAudio, false);
},
然後加入得分音效,在Game指令碼的properties新增引用聲音檔案:
// Game.js
properties: {
// ...
// 得分音效資源
scoreAudio: {
default: null,
url: cc.AudioClip
}
},
在gainScore方法新增播放聲音的程式碼:
// Game.js
gainScore: function () {
this.score += 1;
// 更新 scoreDisplay Label 的文字
this.scoreDisplay.string = 'Score: ' + this.score.toString();
// 播放得分音效
cc.audioEngine.playEffect(this.scoreAudio, false);
},
在層級管理器中,將assets/audio/jump聲音資源拖到Player節點的元件Jump Audio屬性上。將assets/audio/score資源拖拽到Canvas節點Game元件的Score Audio屬性上。
大功告成,一個完整的遊戲就做好了。