最近微信小遊戲開始萌芽,根據老師的要求開始學習cocos creator緊跟互聯網的潮流,先是花了幾天大概學了學HTML/CSS/Javascript,之後就直接開始看cocos creator的官方文檔,看了兩三天總算是根據教程一步一步的做了官方提供的第一個小遊戲,官方文檔上給的第一個小遊戲製作過程確實是比較詳細的,但是它沒有對用到的每個api做詳細的解釋,估計目的是讓初學者大概瞭解一下一個小遊戲的製作過程和cocos creator的大概使用方法。作爲一名喜歡鑽牛角尖的程序員,我看見不懂的api和用法就特別喜歡深究,所以就花了兩三天研究每一個函數,每一個屬性,學完之後感覺收穫良多,因此就打算寫一個官方文檔的補充版,以官方文檔的快速上手:製作第一個遊戲
爲基礎,整合了其他文檔內容,同時添加一下自己學習文檔的理解,不僅能複習一下自己學到的知識,也幫助新人學習開發更加簡單,好了,廢話不多說,進入正題。
ps:
1.觀看這篇的博客的讀者都默認掌握了Javascript基礎知識
2.直到編寫主角腳本部分之前都基本是官方文檔的內容,從編寫主角腳本部分開始會在官方文檔的基礎上加入自己的理解以及整合了官方沒有整合到快速上手:製作第一個遊戲
的文檔內容,灰底部分爲重點
3.有能力的讀者建議最好還是自己查閱官方API和說明來理解文檔內容,這樣理解會更加深刻。
4.如有理解錯誤,歡迎指正
打開初始項目
- 我們首先啓動 Cocos Creator,然後選擇打開其他項目
- 在彈出的文件夾選擇對話框中,選中我們剛下載並解壓完成的 start_project,點擊打開按鈕
- Cocos Creator 編輯器主窗口會打開,我們將看到如下的項目狀態
檢查遊戲資源
下面我們先來了解一下項目中都有哪些資源,請關注名爲資源管理器
的面板,這裏顯示的是項目中的所有資源樹狀結構。(項目資源指的就是遊戲的字體、場景圖片、主角模型、音樂音效等)
可以看到,項目資源的根目錄名叫assets,對應我們解壓之後初始項目中的 assets 目錄,只有這個目錄下的資源纔會被 Cocos Creator 導入項目並進行管理。
資源管理器可以顯示任意層次的目錄結構,我們可以看到這樣的圖標就代表一個文件夾,點擊文件夾左邊的三角圖標可以展開文件夾的內容。將文件夾全部展開後, 資源管理器 中就會呈現如下圖的狀態。
每個資源都是一個文件,導入項目後根據擴展名的不同而被識別爲不同的資源類型,其圖標也會有所區別,下面我們來看看項目中的資源各自的類型和作用:
- 聲音文件,一般爲 mp3 文件,我們將在主角跳躍和得分時播放名爲jump和score的聲音文件。
- 位圖字體,由 fnt 文件和同名的 png 圖片文件共同組成。位圖字體(Bitmap Font)是一種遊戲開發中常用的字體資源
- 各式各樣的縮略圖標,這些都是圖像資源,一般是 png 或 jpg 文件。圖片文件導入項目後會經過簡單的處理成爲texture類型的資源。之後就可以將這些資源拖拽到場景或組件屬性中去使用了。
創建遊戲場景
場景是承載了所有遊戲內容的載體
,當玩家運行遊戲時,就會載入遊戲場景,遊戲場景加載後就會自動運行所包含組件的遊戲腳本,實現各種各樣開發者設置的邏輯功能。所以除了資源以外,遊戲場景是一切內容創作的基礎,讓我們現在就新建一個場景。
- 在資源管理器中點擊選中assets目錄,確保我們的場景會被創建在這個目錄下
- 點擊資源管理器左上角的加號按鈕,在彈出的菜單中選擇 Scene
- 我們創建了一個名叫New Scene的場景文件,右鍵點擊它並選擇重命名,將它改名爲game
- 雙擊game,就會在
場景編輯器
和層級編輯器
中打開這個場景
瞭解Canvas
打開場景後, 層級管理器 中會顯示當前場景中的所有節點和他們的層級關係。我們剛剛新建的場景中只有一個名叫Canvas的節點,Canvas可以被稱爲畫布節點或渲染根節點,點擊選中Canvas,可以在 屬性檢查器
中看到他的屬性。
這裏的Design Resolution屬性規定了遊戲的設計分辨率,Fit Height和Fit Width規定了在不同尺寸的屏幕上運行時,我們將如何縮放Canvas以適配不同的分辨率。
由於提供了多分辨率適配的功能,我們一般會將場景中的所有負責圖像顯示的節點都放在Canvas下面
。這樣當Canvas的scale(縮放)屬性改變時,所有作爲其子節點的圖像也會跟着一起縮放以適應不同屏幕的大小。
設置場景圖像
添加背景
首先在 資源管理器 裏按照assets/textures/background的路徑找到我們的背景圖像資源,點擊並拖拽這個資源到 層級編輯器 中的Canvas節點上,直到Canvas節點顯示橙色高亮,表示將會添加一個以background爲貼圖資源
的子節點
。
這時就可以鬆開鼠標按鍵,可以看到Canvas下面添加了一個名叫background的節點
。當我們使用拖拽資源的方式添加節點時,節點會自動以貼圖資源的文件名來命名
。
我們在對場景進行編輯修改時,可以通過主菜單文件->保存場景來及時保存我們的修改。也可以使用快捷鍵Ctrl+S(Windows)或Cmd + S(Mac)來保存。
修改背景尺寸
在 場景編輯器 中,可以看到我們剛剛添加的背景圖像,下面我們將修改背景圖像的尺寸,來讓他覆蓋整個屏幕。
首先選中background節點,然後點擊主窗口左上角工具欄第四個 矩形變換工具:
使用這個工具我們可以方便的修改圖像節點的尺寸,將鼠標移動到 場景編輯器 中 background 的左邊,按住並向左拖拽直到 background 的左邊超出表示設計分辨率的藍色線框。然後再用同樣的方法將 background 的右邊向右拖拽。
之後需要拖拽上下兩邊,使背景圖的大小能夠填滿設計分辨率的線框。
在使用 矩形變換工具 修改背景圖尺寸時,可以在 屬性檢查器 中看到 Node (節點)中的 Size 屬性也在隨之變化,完成後我們的背景圖尺寸大概是 (1360, 760)。您也可以直接在 Size 屬性的輸入框中輸入數值,和使用 矩形變換工具 可以達到同樣效果。這樣大小的背景圖在市面流行的手機上都可以覆蓋整個屏幕,不會出現穿幫情況。
添加地面
我們的主角需要一個可以在上面跳躍的地面,我們馬上來添加一個。用和添加背景圖相同的方式,拖拽 資源管理器 中 assets/textures/ground 資源到 層級管理器 的 Canvas 上。這次在拖拽時我們還可以選擇新添加的節點和 background 節點的順序關係。拖拽資源的狀態下移動鼠標指針到 background 節點的下方,直到在 Canvas 上顯示橙色高亮框,並同時在 background 下方顯示錶示插入位置的綠色線條,然後鬆開鼠標,這樣 ground 在場景層級中就被放在了 background 下方,同時也是 Canvas 的一個子節點。
在 層級管理器 中,顯示在下方的節點的渲染順序會在上方節點的後面,我們可以看到位於最下的ground物體在 場景編輯器 中顯示在了最前。另外子節點也會永遠顯示在父節點之前,我們可以隨時調整節點的層級順序和關係來控制他們的顯示順序。
按照修改背景的方法,我們也可以使用矩形變換工具來爲地面節點設置一個合適的大小。在激活矩形變換工具的時候,如果拖拽節點頂點和四邊之外的部分,就可以更改節點的位置。下圖是我們設置好的地面節點狀態:
除了矩形變換工具之外,我們還可以使用移動工具來改變節點的位置。嘗試按住移動工具顯示在節點上的箭頭並拖拽,就可以一次改變節點在單個座標軸上的位置。
我們在設置背景和地面的位置和尺寸時不需要很精確的數值,可以憑感覺拖拽。如果您偏好比較完整的數字,也可以按照截圖直接輸入position和size的數值。
添加主角
接下來我們的主角小怪獸要登場了,從 資源管理器 拖拽assets/texture/PurpleMonster到 層級管理器 中 Canvas 的下面,並確保他的排序在 ground 之下,這樣我們的主角會顯示在最前面。 注意小怪獸節點應該是 Canvas 的子節點,和 ground 節點平行。
爲了讓主角的光環在場景節點中非常醒目,我們右鍵點擊剛剛添加的PurpleMonster節點,選擇重命名之後將其改名爲Player。
接下來我們要對主角的屬性進行一些設置,首先是改變錨點(Anchor)的位置。默認狀態下,任何節點的錨點都會在節點的中心,也就是說該節點中心點所在的位置就是該節點的位置。我們希望控制主角的底部的位置來模擬在地面上跳躍的效果,所以現在我們需要把主角的錨點設置在腳下。在 屬性檢查器 裏找到Anchor屬性,把其中的y值設爲0,可以看到 場景編輯器 中,表示主角位置的移動工具的箭頭出現在了主角腳下。
注意錨點的取值,錨點的取值爲(0,0)時表示錨點在節點的左下角,取值爲(1,1)時表示錨點在節點的右上角,(0.5,0.5)表示在節點的中心,其他的可以類推。
接下來 場景編輯器 中拖拽Player,把他放在地面上,效果如下圖:
這樣我們基本的場景美術內容就配置好了。下面一節我們要編寫代碼讓遊戲裏的內容生動起來。
編寫主角腳本
Cocos Creator 開發遊戲的一個核心理念就是讓內容生產和功能開發可以流暢的並行協作,我們在上個部分着重於處理美術內容,而接下來就是通過編寫腳本來開發功能的流程,之後我們還會看到寫好的程序腳本可以很容易的被內容生產者使用。
如果您從沒寫過程序也不用擔心,我們會在教程中提供所有需要的代碼,只要複製粘貼到正確的位置就可以了,之後這部分工作可以找您的程序員小夥伴來解決。下面讓我們開始創建驅動主角行動的腳本吧。
- 首先在 資源管理器 中右鍵點擊assets文件夾,選擇新建->文件夾
- 右鍵點擊New Folder,選擇重命名,將其改名爲scripts,之後我們所有的腳本都會存放在這裏。
- 右鍵點擊scripts文件夾,選擇新建->JavaScript,創建一個JavaScript腳本
- 將新建腳本的名字改爲Player。雙擊這個腳本,打開代碼編輯器。
注意: Cocos Creator 中腳本名稱就是組件的名稱,這個命名是大小寫敏感的!如果組件名稱的大小寫不正確,將無法正確通過名稱使用組件!
編寫組件屬性
打開腳本後我們會發現裏面已經有一些編輯好的代碼塊:
cc.Class({
extends: cc.Component,
properties: {
// foo: {
// // ATTRIBUTES:
// default: null, // The default value will be used only when the component attaching
// // to a node for the first time
// type: cc.SpriteFrame, // optional, default is typeof default
// serializable: true, // optional, default is true
// },
// bar: {
// get () {
// return this._bar;
// },
// set (value) {
// this._bar = value;
// }
// },
},
// LIFE-CYCLE CALLBACKS:
// onLoad () {},
start () {
},
// update (dt) {},
});
讓我們來大概瞭解一下這些代碼的作用。首先我們可以看到一個包裹了全局的cc.Class()方法,什麼是cc呢?cc是cocos的簡稱,cocos 引擎的主要命名空間,引擎代碼中所有的類,函數,屬性和常量都在這個命名空間中定義。而Class()就是cc模塊下的一個方法,這個方法用於聲明 Cocos Creator 中的類,爲了方便區分,我們把使用 cc.Class 聲明的類叫做 CCClass。Class()方法的參數是一個原型對象
,在原型對象中以鍵值對的形式設定所需的類型參數,就能創建出所需要的類。
例如:
var Sprite = cc.Class({
name: "sprite"
});
以上代碼用 cc.Class()方法創建了一個類型,並且賦給了 Sprite 變量。同時還將類名設爲 “sprite”,類名用於序列化,一般可以省略。
對於cc.Class的詳細學習可以參考:http://docs.cocos.com/creator/manual/zh/scripting/class.html
現在我們回到腳本編輯器看回之前的代碼,這些代碼就是編寫一個組件(腳本)所需的結構。具有這樣結構的腳本就是 Cocos Creator 中的組件(Component)
,他們能夠掛載到場景中的節點上
,提供控制節點的各種功能
。我們先來設置一些屬性,然後看看怎樣在場景中調整他們。
// Player.js
//...
properties: {
// 主角跳躍高度
jumpHeight: 0,
// 主角跳躍持續時間
jumpDuration: 0,
// 最大移動速度
maxMoveSpeed: 0,
// 加速度
accel: 0,
},
//...
Cocos Creator規定一個節點具有的屬性都需要寫在properties代碼塊中
,這些屬性將規定主角的移動方式,在代碼中我們不需要關心這些數值是多少,因爲我們之後會直接在屬性檢查器
中設置這些數值。
以後在遊戲製作過程中,我們將需要能夠隨時調整的屬性都可以放在properties中
現在我們可以把 Player 組件添加到主角節點上。在 層級編輯器 中選中 Player 節點,然後在 屬性檢查器 中點擊 添加組件 按鈕,選擇 添加用戶腳本組件->Player,爲主角節點添加 Player 組件。
現在我們可以在 屬性檢查器 中(需要選中 Player 節點)看到剛添加的 Player 組件了,按照下圖將主角跳躍和移動的相關屬性設置好:
這些數值除了 jumpDuration 的單位是秒之外,其他的數值都是以像素爲單位的,根據我們現在對 Player 組件的設置:我們的主角將能夠跳躍 200 像素的高度,起跳到最高點所需的時間是 0.3 秒,最大水平方向移動速度是 400 像素每秒,水平加速度是 350 像素每秒。
這些數值都是建議,一會等遊戲運行起來,您完全可以按照自己的喜好隨時在 屬性檢查器 中修改這些數值,不需要改動任何代碼,很方便吧!
編寫跳躍和移動代碼
下面我們添加一個方法,來讓主角跳躍起來,在 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));
},
這裏就需要了解一下Cocos Creator的動作(Action)系統了,由於動作系統比較複雜,這裏就簡單的和初學者形容一下,若形容不當歡迎指正。
在Cocos Creator中,動作簡單來說就是節點的位移,縮放和旋轉。
例如在上面的代碼中,moveBy()方法的作用是在規定的時間內移動指定的一段距離,第一個參數就是我們之前定義主角屬性中的跳躍時間,第二個參數是一個Vec2(表示 2D 向量和座標)類型的對象,爲了更好的理解,我們可以看看官方給的函數官方說明:
/**
* !#en
* Moves a Node object x,y pixels by modifying its position property. <br/>
* x and y are relative to the position of the object. <br/>
* Several MoveBy actions can be concurrently called, and the resulting <br/>
* movement will be the sum of individual movements.
* !#zh 移動指定的距離。
* @method moveBy
* @param {Number} duration duration in seconds
* @param {Vec2|Number} deltaPos
* @param {Number} [deltaY]
* @return {ActionInterval}
* @example
* // example
* var actionTo = cc.moveBy(2, cc.p(windowSize.width - 40, windowSize.height - 40));
*/
cc.moveBy = function (duration, deltaPos, deltaY) {
return new cc.MoveBy(duration, deltaPos, deltaY);
};
可以看到,方法moveBy一共可以傳入三個參數,前兩個參數我們已經知道,第三個參數是Number類型的Y座標,具體作用我自己也還沒有搞清楚,我大致的猜測是這樣的,我們可以發現第二個參數是可以傳入兩種類型的,第一種是Number類型,第二種纔是Vec2類型,如果我們在這裏傳入的是Number類型,那麼默認這個參數就是X座標,此時就要填第三個參數,爲Y座標。上面的例子中cc.moveBy(this.jumpDuration, cc.p(0, this.jumpHeight))
第二個參數傳入的是使用cc.p方法構建的Vec2類型對象,這個類型表示的是一個座標,即有X座標也有Y座標,因爲不需要再傳入第三個參數,這是我個人的理解,如果有錯的話希望大佬指出!同時注意官方的一段話x and y are relative to the position of the object.
,這句話的意思是傳入的X、Y座標都是相對於節點當前的座標位置,而不是整個座標系的絕對座標。
瞭解了參數的含義之後,我們再來關注moveBy()方法的返回值,看官方說明可以知道,這個方法返回的是一個ActionInterval
類型的對象,ActionInterval在cocos中是一個表示時間間隔動作的類, 這種動作在已定時間內完成。到這裏我們就能理解代碼
cc.moveBy(this.jumpDuration, cc.p(0, this.jumpHeight)).easing(cc.easeCubicActionOut())
前一部分的意思了,它的意思就是構造一個ActionInterval類型的對象,這個對象表示在jumpDuration的時間內,移動到相對於當前節點的(0,this.jumpHeight)的座標位置,簡單來說,就是一個向上跳躍的動作。
那麼後半部分easing(cc.easeCubicActionOut())
的作用是什麼呢?easing是ActionInterval類下的一個方法,這個方法可以讓時間間隔動作呈現爲一種緩動運動,傳入的參數是一個緩動對象,返回一個ActionInterval類型對象,這裏傳入的是使用easeCubicActionInOut方法構建的緩動對象,EaseCubicInOut是按三次函數緩動進入並退出的動作,具體曲線可參考下圖:
如果還有不懂的可以查看官方api及文檔:http://docs.cocos.com/creator/api/zh/modules/cc.html?h=easecubicactionout()
接下來看代碼的最後一句return cc.repeatForever(cc.sequence(jumpUp, jumpDown));
,repeatForever非常好理解,就如其字面意思:永遠地重複一個動作(返回ActionInterval),在這裏就是一直重複的跳起來,落下去,這個方法需要傳入一個FiniteTimeAction類型的參數,這裏傳入的參數是使用cc.sequence()方法返回一個ActionInterval類型的對象,sequence()方法的作用是根據傳入的Action對象順序執行動作,創建的動作將按順序依次運行,所以cc.sequence(jumpUp, jumpDown)
就是一個先起跳再下降的動作。
接下來回到遊戲製作過程,在onLoad
方法裏調用剛添加的setJumpAction
方法,然後執行runAction
來開始動作:
// Player.js
onLoad: function () {
// 初始化跳躍動作
this.jumpAction = this.setJumpAction();
this.node.runAction(this.jumpAction);
},
onLoad
方法會在場景加載後立刻執行,所以我們會把初始化相關的操作和邏輯都放在這裏面。我們首先將循環跳躍的動作傳給了jumpAction變量,之後調用這個組件掛載的節點下的runAction方法,傳入循環跳躍的Action從而讓節點(主角)一直跳躍。
保存腳本,然後我們就可以開始第一次運行遊戲了!
點擊 Cocos Creator 編輯器上方正中的預覽遊戲按鈕,Cocos Creator 會自動打開您的默認瀏覽器並開始在裏面運行遊戲,現在應該可以看到我們的主角——紫色小怪獸在場景中間活潑的蹦個不停了。
移動控制
只能在原地傻蹦的主角可沒前途,讓我們爲主角添加鍵盤輸入,用A和D來控制他的跳躍方向。
在setJumpAction
方法的下面添加setInputControl
方法:
// 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);
},
有Android開發經驗的同學比較好理解,這裏的監聽器實質上就和Android裏的OnClickListener差不多,在cc中通過eventManager來管理事件監聽器註冊和派發系統事件。原始設計中,它支持鼠標,觸摸,鍵盤,陀螺儀和自定義事件。 在 Creator 的設計中,鼠標,觸摸和自定義事件的監聽和派發請參考:http://cocos.com/docs/creator/scripting/events.html。
總之這裏通過向eventManager註冊了一個監聽器,這個監聽器用來監聽鍵盤輸入,通過
switch判斷鍵盤上的A和D是否被按下或鬆開,若按下就執行對應的操作,由於事件管理器和監聽器比較複雜,這裏不過多贅述,想了解的同學麻煩查看官方api及文檔。
然後修改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;
},
update
在場景加載後就會每幀調用一次,傳回的參數dt是每幀調用的時間,我們一般把需要經常計算或及時更新的邏輯內容放在這裏。在我們的遊戲中,根據鍵盤輸入獲得加速度方向後,就需要每幀在update中計算主角的速度和位置,this.node.x就是當前組件掛載的節點的x座標
。
保存腳本後,下面就可以去泡杯茶,點擊預覽遊戲來看看我們最新的成果。在瀏覽器打開預覽後,用鼠標點擊一下游戲畫面(這是瀏覽器的限制,要點擊遊戲畫面才能接受鍵盤輸入),然後就可以按A和D鍵來控制主角左右移動了!
感覺移動起來有點遲緩?主角跳的不夠高?希望跳躍時間長一些?沒問題,這些都可以隨時調整。只要爲Player
組件設置不同的屬性值,就可以按照您的想法調整遊戲。這裏有一組設置可供參考:
Jump Height: 150
Jump Duration: 0.3
Max Move Speed: 400
Accel: 1000
這組屬性設置會讓主角變得靈活無比,至於如何選擇,就看您想做一個什麼風格的遊戲了。
製作星星
主角現在可以跳來跳去了,我們要給玩家一個目標,也就是會不斷出現在場景中的星星,玩家需要引導小怪獸碰觸星星來收集分數。被主角碰到的星星會消失,然後馬上在隨機位置重新生成一個。
製作Prefab
對於需要重複生成的節點,我們可以將他保存成 Prefab(預製) 資源,作爲我們動態生成節點時使用的模板。關於 Prefab 的更多信息,請閱讀預製資源。
首先從 資源管理器 中拖拽 assets/textures/star 圖片到場景中,位置隨意,我們只是需要藉助場景作爲我們製作 Prefab 的工作臺,製作完成後會我們把這個節點從場景中刪除。
我們不需要修改星星的位置或渲染屬性,但要讓星星能夠被主角碰觸後消失,我們需要爲星星也添加一個專門的組件。按照和添加 Player 腳本相同的方法,添加名叫 Star 的 JavaScript 腳本到 assets/scripts/ 中。
接下來雙擊這個腳本開始編輯,星星組件只需要一個屬性用來規定主角距離星星多近時就可以完成收集,修改 properties,加入以下內容:
// Star.js
properties: {
// 星星和主角之間的距離小於這個數值時,就會完成收集
pickRadius: 0
},
保存腳本後,將這個腳本添加到剛創建的 star 節點上。然後在 屬性檢查器 中把 Pick Radius 屬性值設爲 60:
Star Prefab 需要的設置就完成了,現在從 層級管理器 中將 star 節點拖拽到 資源管理器 中的 assets 文件夾下,就生成了名叫 star 的 Prefab 資源。
現在可以從場景中刪除 star 節點了,後續可以直接雙擊這個 star Prefab 資源進行編輯。
接下去我們會在腳本中動態使用星星的 Prefab 資源生成星星。
添加遊戲控制腳本
星星的生成是遊戲主邏輯的一部分,所以我們要添加一個叫做Game的腳本作爲遊戲主邏輯腳本,這個腳本之後還會添加計分、遊戲失敗和重新開始的相關邏輯。
添加Game腳本到assets/scripts文件夾下,雙擊打開腳本。首先添加生成星星需要的屬性:
// 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
}
},
這裏初學者可能會疑惑,爲什麼像starPrefab這樣的屬性會用中括號括起來,括號裏面還有新的“屬性”呢?其實這是屬性的一種完整聲明
,之前我們的屬性聲明都是不完整的,有些情況下,我們需要爲屬性聲明添加參數,這些參數控制了屬性在 屬性檢查器
中的顯示方式,以及屬性在場景序列化過程中的行爲。例如:
properties: {
score: {
default: 0,
displayName: "Score (player)",
tooltip: "The score of player",
}
}
以上代碼爲 score 屬性設置了三個參數 default, displayName 和 tooltip。這幾個參數分別指定了 score 的默認值(default)爲 0,在 屬性檢查器 裏,其屬性名(displayName)將顯示爲:“Score (player)”,並且當鼠標移到參數上時,顯示對應的 Tooltip。
下面是常用參數:
default: 設置屬性的默認值,這個默認值僅在組件第一次添加到節點上時纔會用到
type: 限定屬性的數據類型,詳見 CCClass 進階參考:type 參數
visible: 設爲 false 則不在 屬性檢查器 面板中顯示該屬性
serializable: 設爲 false 則不序列化(保存)該屬性
displayName: 在 屬性檢查器 面板中顯示成指定名字
tooltip: 在 屬性檢查器 面板中添加屬性的 Tooltip
所以上面的代碼
starPrefab: {
default: null,
type: cc.Prefab
},
就可以理解了,首先在Game組件下聲明瞭starPrefab屬性,這個屬性默認值爲null,能傳入的類型必須是Prefab預製資源類型。這樣之後的ground、player屬性也可以理解了。
保存腳本後將Game組件添加到 層級編輯器 中的Canvas節點上(選中Canvas節點後,拖拽腳本到 屬性檢查器 上,或點擊 屬性檢查器 的 添加組件 按鈕,並從 用戶自定義腳本 中選擇 Game,接下來從 資源管理器 中拖拽star Prefab 資源到Game組件的Star Prefab屬性中。這是我們第一次爲屬性設置引用,只有在屬性聲明時規定type爲引用類型時(比如我們這裏寫的cc.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);
}
這裏需要注意幾個問題:
一、節點下的y屬性對應的是錨點所在的y座標,因爲錨點默認在節點的中心,所以需要加上地面高度的一半纔是地面的y座標。
二、instantiate方法的作用是:克隆指定的任意類型的對象,或者從 Prefab 實例化出新節點
,返回值爲Node或者Object
三、Node下的addChild方法結果是將新節點建立在該節點的下一級,所以新節點的顯示效果在該節點之上。
四、Node下的setPosition方法作用是:設置節點在父節點座標系中的位置。可以通過兩種方式設置座標點:1、傳入 2 個數值 x 和 y。2、傳入 cc.v2(x, y) 類型爲 cc.Vec2 的對象。
五、通過Node下的getComponent方法可以得到該節點上掛載的組件引用。
保存腳本以後點擊預覽遊戲按鈕,在瀏覽器中可以看到,遊戲開始後動態生成了一顆星星!用同樣的方法,您可以在遊戲中動態生成任何預先設置好的以 Prefab 爲模板的節點。
添加主角碰觸收集星星的行爲
現在要添加主角收集星星的行爲邏輯了,這裏的重點在於,星星要隨時可以獲得主角節點的位置,才能判斷他們之間的距離是否小於可收集距離,如何獲得主角節點的引用呢?別忘了我們前面做過的兩件事:
- Game組件中有個名叫player的屬性,保存了主角節點的引用。
- 每個星星都是在Game腳本中動態生成的。
所以我們只要在Game腳本生成Star節點實例時,將Game組件的實例傳入星星並保存起來就好了,之後我們可以在星星組件裏隨時通過game.player來訪問到主角節點,從而獲取到主角節點的位置。
讓我們打開Game腳本,在spawnNewStar方法最後面添加這樣一句:
// Game.js
spawnNewStar: function() {
// ...
// 將 Game 組件的實例傳入星星組件
newStar.getComponent('Star').game = this;
},
保存後打開Star腳本,現在我們可以利用Game組件中引用的player節點來判斷距離了,在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();
},
Node下的getPosition()方法返回的是節點的在父節點座標系中的位置(x, y),即一個Vec2類型對象。cc下的pDistance方法很簡單,這裏不多述,同時注意調用Node下的destroy()方法就可以銷燬節點。
然後在update方法中添加每幀判斷距離,如果距離小於pickRadius屬性規定的收集距離,就執行收集行爲:
// Star.js
update: function (dt) {
// 每幀判斷和主角之間的距離是否小於收集距離
if (this.getPlayerDistance() < this.pickRadius) {
// 調用收集行爲
this.onPicked();
return;
}
},
保存腳本,然後再次預覽測試,可以看到控制主角靠近星星時,星星就會消失掉,然後在隨機位置生成了新的星星!
添加得分
小怪獸辛辛苦苦的收集星星,沒有獎勵怎麼行,讓我們現在就在收集星星時添加得分獎勵的邏輯和顯示。
添加分數文字(Label)
遊戲開始時得分從0開始,每收集一個星星分數就會加1。要顯示得分,首先要創建一個 Label 節點。在 層級管理器 中選中Canvas節點,右鍵點擊並選擇菜單中的創建新節點->創建渲染節點->Label(文字),一個新的 Label 節點會被創建在Canvas下面,而且順序在最下面。接下來我們要用如下的步驟配置這個 Label 節點:
- 將該節點名字改爲score
- 將score節點的位置(position屬性)設爲(0, 180)。
- 選中該節點,編輯Label組件的string屬性,填入Score: 0的文字。
- 將Label組件的Font Size屬性設爲50。
- 從 資源管理器 中拖拽assets/mikado_outline_shadow位圖字體資源(注意圖標是bmfont)到Label組件的Font屬性中,將文字的字體替換成我們項目資源中的位圖字體。
這裏注意Label組件的String屬性加了字體後,無法識別中文的冒號,所以建議打英文的冒號。
完成後效果如下圖所示:
在 Game 腳本中添加得分邏輯
我們將會把計分和更新分數顯示的邏輯放在Game腳本里,打開Game腳本開始編輯,首先在properties區塊的最後添加分數顯示 Label 的引用屬性:
// 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();
},
這裏的toString()方法是將分數score轉換爲能String字符串類型便於輸出到屏幕。
保存 Game 腳本後,回到 層級管理器,選中 Canvas 節點,然後把前面添加好的 score 節點拖拽到 屬性檢查器 裏 Game 組件的 Score Display 屬性中。
在 Star 腳本中調用 Game 中的得分邏輯
下面打開Star腳本,在onPicked方法中加入gainScore的調用:
// Star.js
onPicked: function() {
// 當星星被收集時,調用 Game 腳本中的接口,生成一個新的星星
this.game.spawnNewStar();
// 調用 Game 腳本的得分方法
this.game.gainScore();
// 然後銷燬當前星星節點
this.node.destroy();
},
保存後預覽,可以看到現在收集星星時屏幕正上方顯示的分數會增加了!
失敗判定和重新開始
現在我們的遊戲已經初具規模,但得分再多,不可能失敗的遊戲也不會給人成就感。現在讓我們加入星星定時消失的行爲,而且讓星星消失時就判定爲遊戲失敗。也就是說,玩家需要在每顆星星消失之前完成收集,並不斷重複這個過程完成玩法的循環。
爲星星加入計時消失的邏輯
打開Game腳本,在onLoad方法的spawnNewStar調用之前加入計時需要的變量聲明:
// 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');
}
這裏需要初學者瞭解的是,cc.director 是一個管理你的遊戲的邏輯流程的單例對象。由於 cc.director 是一個單例,你不需要調用任何構造函數或創建函數,使用它的標準方法是通過調用cc.director.methodName()
,例如這裏的cc.director.loadScene('game')
就是重新加載遊戲場景game,也就是遊戲重新開始。
而節點下的stopAllActions方法就顯而易見了,這個方法會讓節點上的所有Action都失效。
對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));
}
這串代碼比較簡單,就是爲星星節點設置一個透明度,來提示玩家生成的星星何時會消失。
保存Star腳本,我們的遊戲玩法邏輯就全部完成了!現在點擊預覽遊戲按鈕,我們在瀏覽器看到的就是一個有核心玩法、激勵機制、失敗機制的合格遊戲了。
加入音效
儘管很多人玩手遊的時候會無視聲音,我們爲了教程展示的工作流程儘量完整,還是要補全加入音效的任務。
跳躍音效
首先加入跳躍音效,打開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);
},
這裏需要強調的是回調函數的作用,我們首先來看官方對callFunc()方法的定義:
/**
* !#en Creates the action with the callback.
* !#zh 執行回調函數。
* @method callFunc
* @param {function} selector
* @param {object} [selectorTarget=null]
* @param {*} [data=null] - data for function, it accepts all data types.
* @return {ActionInstant}
* @example
* // example
* // CallFunc without data
* var finish = cc.callFunc(this.removeSprite, this);
*
* // CallFunc with data
* var finish = cc.callFunc(this.removeFromParentAndCleanup, this._grossini, true);
*/
cc.callFunc = function (selector, selectorTarget, data) {
return new cc.CallFunc(selector, selectorTarget, data);
};
我們可以看到callFunc方法可以傳入三個參數,第一個參數是方法的selector,我們可以理解爲方法名,第二個參數是Object類型,一般填入this,第三個參數爲帶回的數據,可以是所有數據類型,可以不填。我們再注意到這個方法的返回值——ActionInstant,這是一個瞬間執行的動作類,到這裏我們就可以理解了,使用callFunc調用回調函數可以讓函數轉變爲cc中的Action(動作)
!這一用法在cc的動作系統裏非常好用!例如在上面我們將播放聲音的函數傳入callFunc賦值給callBack,讓callBack成爲了一個播放聲音的動作Action,那麼我們之後就能通過cc.sequence將跳躍和播放聲音的動作組合起來,實現每跳一次就能播放音效的功能
!
得分音效
保存Player腳本以後打開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);
},
保存腳本,回到 層級編輯器 ,選中Player節點,然後從 資源管理器 裏拖拽assets/audio/jump資源到Player組件的Jump Audio屬性上。
然後選中Canvas節點,把assets/audio/score資源拖拽到Game組件的Score Audio屬性上。
這樣就大功告成了!完成形態的場景層級和各個關鍵組件的屬性如下:
現在我們可以盡情享受剛製作完成的遊戲了,您能打到多少分呢?別忘了您可以隨時修改Player和Game組件裏的移動控制和星星持續時間等遊戲參數,來快速調節遊戲的難度。修改組件屬性之後需要保存場景,修改後的數值纔會被記錄下來。