隨著HTML5的火熱,越來越多的人投入到HTML5開發(fā)中了,canvas作為HTML5中比較重要的一個(gè)元素,在很多官網(wǎng)的主頁面中被使用到。今天我們一起來看看如何使用canvas畫出一個(gè)夢幻的星空背景,還會(huì)有流星運(yùn)動(dòng)。
本文的代碼已經(jīng)放到Github上了,感興趣的可以自取,Github地址如下。
https://github.com/zhouxiongking/article-pages/blob/master/articles/starry/starry.html
HTML5
首先我們來看看通過canvas實(shí)現(xiàn)的星空效果圖,如下所示。
效果圖
接下來我們看看這個(gè)效果是如何通過代碼一步步實(shí)現(xiàn)的。
首先來看看頁面上的HTML代碼,只有一個(gè)Div元素。
HTML代碼
首先我們需要定義一些常量,比如畫布的寬和高,星星數(shù)量,流星個(gè)數(shù)。在這個(gè)星空中流星其實(shí)是星星的一個(gè),只是添加了動(dòng)態(tài)效果。
頁面初始化
然后是設(shè)定一個(gè)定時(shí)器,通過一段隨機(jī)時(shí)間生成一個(gè)流星的索引號(hào)。
流星索引號(hào)
緊接著來看看生成一個(gè)星星的方法,該方法返回一個(gè)星星的各項(xiàng)參數(shù),包括x,y軸坐標(biāo),透明度,x,y軸偏移量。
生成星星的參數(shù)
然后是最重要的render方法,通過該方法可以將星星渲染至畫布上,我們將這個(gè)方法拆開看,首先是對流星的繪制,流星索引號(hào)通過上面metor方法獲得。
畫流星
然后是對于星星各項(xiàng)參數(shù)的處理,比如有的星星生成的點(diǎn)坐標(biāo)超出了屏幕寬高,有的透明度是負(fù)數(shù),都要將其處理成正常參數(shù)。
各項(xiàng)參數(shù)判斷
最后是在畫布中進(jìn)行繪制。
畫布繪制
至此,這個(gè)畫面效果的講解完畢,如果代碼正確的話,就可以看到文中出現(xiàn)的效果圖。
今天這篇文章主要是借助HTML5中的canvas畫出了一個(gè)夢幻星空的效果,你學(xué)會(huì)了嗎?
、前言
在瀏覽一些圖片網(wǎng)站的時(shí)候,經(jīng)常會(huì)看到很多的漂亮的星空圖,比如,下面的圖片。其實(shí)這種星星圖片的效果,也可以通過html+css樣式和js的方式來實(shí)現(xiàn)。今天教大家如何實(shí)現(xiàn)星星圖的效果。
軟件:Dreamweaver
每次刷新產(chǎn)生隨機(jī)的星星個(gè)數(shù)。顯示畫布上。
<body>
<canvas id='canvas'></canvas>
</body>
給canva 畫布加上邊框,方便觀察。
<style type="text/css">
canvas{
border:2px solid #f00;
}
</style>
<script type="text/javascript">
var _canvas=document.getElementById("canvas")
_canvas.width=500;
_canvas.height=500;
var r,g ,b,a;
</script>
for (var j = 0; j < 150; j++) {
arc.x=Math.floor(Math.random()*_canvas.width);
arc.y=Math.floor(Math.random()*_canvas.height);
arc.r=Math.floor(Math.random()*31+10);
r=Math.ceil(Math.random()*256);
g=Math.ceil(Math.random()*256);
b=Math.ceil(Math.random()*256);
a=Math.random();
darw();
}
如何畫星星?(公式解析)(圖片來源百度)
星星有內(nèi)切圓和外切圓,每兩個(gè)點(diǎn)之間的角度是固定的,因此可得到星星的每個(gè)點(diǎn)的坐標(biāo),畫出星星。
/* 隨機(jī)產(chǎn)生星星*/
for (var i = 0; i < 5; i++) {
_ctx.lineTo(Math.cos((18+72*i)/180*Math.PI)*arc.r+arc.x, -Math.sin((18+72*i)/180*Math.PI)*arc.r+arc.y);
_ctx.lineTo(Math.cos((54+72*i)/180*Math.PI)*arc.r/2+arc.x, -Math.sin((54+72*i)/180*Math.PI)*arc.r/2+arc.y);
}
Math函數(shù)隨機(jī)產(chǎn)生0-225的RGB值。
/* 隨機(jī)顏色*/
_ctx.fillStyle="rgba(" + r + "," + g + "," + b + "," + a + ")";
_ctx.fill();
_ctx.strokeStyle="rgba(" + r + "," + g + "," + b + "," + a + ")";
_ctx.stroke();
}
darw();
1、點(diǎn)擊f12運(yùn)行到瀏覽器
2、每次刷新網(wǎng)頁,隨機(jī)產(chǎn)生不一樣的星星和隨機(jī)顏色。
想學(xué)習(xí)更多前端、Python爬蟲、大數(shù)據(jù)等計(jì)算機(jī)知識(shí),請前往:http://pdcfighting.com/
ocos Creator 開發(fā)游戲的一個(gè)核心理念就是讓內(nèi)容生產(chǎn)和功能開發(fā)可以流暢的并行協(xié)作,我們在上個(gè)部分著重于處理美術(shù)內(nèi)容,而接下來就是通過編寫腳本來開發(fā)功能的流程,之后我們還會(huì)看到寫好的程序腳本可以很容易的被內(nèi)容生產(chǎn)者使用。
如果您從沒寫過程序也不用擔(dān)心,我們會(huì)在教程中提供所有需要的代碼,只要復(fù)制粘貼到正確的位置就可以了,之后這部分工作可以找您的程序員小伙伴來解決。下面讓我們開始創(chuàng)建驅(qū)動(dòng)主角行動(dòng)的腳本吧。
創(chuàng)建腳本
注意: Cocos Creator 中腳本名稱就是組件的名稱,這個(gè)命名是大小寫敏感的!如果組件名稱的大小寫不正確,將無法正確通過名稱使用組件!
編寫組件屬性
在打開的 Player 腳本里已經(jīng)有了預(yù)先設(shè)置好的一些代碼塊,如下所示:
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) {}, });
我們來大概了解一下這些代碼的作用。首先我們可以看到一個(gè)全局的 cc.Class() 方法,什么是 cc呢?cc 是 Cocos 的簡稱,Cocos 引擎的主要命名空間,引擎代碼中所有的類、函數(shù)、屬性和常量都在這個(gè)命名空間中定義。而 Class() 就是 cc 模塊下的一個(gè)方法,這個(gè)方法用于聲明 Cocos Creator 中的類。為了方便區(qū)分,我們把使用 cc.Class 聲明的類叫做 CCClass。Class() 方法的參數(shù)是一個(gè)原型對象,在原型對象中以鍵值對的形式設(shè)定所需的類型參數(shù),就能創(chuàng)建出所需要的類。
例如:
var Sprite = cc.Class({ name: "sprite" });
以上代碼用 cc.Class() 方法創(chuàng)建了一個(gè)類型,并且賦給了 Sprite 變量。同時(shí)還將類名設(shè)為 sprite。類名用于序列化,一般可以省略。
對于 cc.Class 的詳細(xì)學(xué)習(xí)可以參考 使用 cc.Class 聲明類型。
現(xiàn)在我們回到腳本編輯器看回之前的代碼,這些代碼就是編寫一個(gè)組件(腳本)所需的結(jié)構(gòu)。具有這樣結(jié)構(gòu)的腳本就是 Cocos Creator 中的 組件(Component),他們能夠掛載到場景中的節(jié)點(diǎn)上,提供控制節(jié)點(diǎn)的各種功能。我們先來設(shè)置一些屬性,然后看看怎樣在場景中調(diào)整他們。
找到 Player 腳本里的 properties 部分,將其改為以下內(nèi)容并保存:
// Player.js //... properties: { // 主角跳躍高度 jumpHeight: 0, // 主角跳躍持續(xù)時(shí)間 jumpDuration: 0, // 最大移動(dòng)速度 maxMoveSpeed: 0, // 加速度 accel: 0, }, //...
Cocos Creator 規(guī)定一個(gè)節(jié)點(diǎn)具有的屬性都需要寫在 properties 代碼塊中,這些屬性將規(guī)定主角的移動(dòng)方式,在代碼中我們不需要關(guān)心這些數(shù)值是多少,因?yàn)槲覀冎髸?huì)直接在 屬性檢查器 中設(shè)置這些數(shù)值。以后在游戲制作過程中,我們可以將需要隨時(shí)調(diào)整的屬性都放在 properties 中。
現(xiàn)在我們可以把 Player 組件添加到主角節(jié)點(diǎn)上。在 層級管理器 中選中 Player 節(jié)點(diǎn),然后在 屬性檢查器 中點(diǎn)擊 添加組件 按鈕,選擇 添加用戶腳本組件 -> Player,為主角節(jié)點(diǎn)添加 Player 組件。
現(xiàn)在我們可以在 屬性檢查器 中(需要選中 Player 節(jié)點(diǎn))看到剛添加的 Player 組件了,按照下圖將主角跳躍和移動(dòng)的相關(guān)屬性設(shè)置好:
這些數(shù)值除了 jumpDuration 的單位是秒之外,其他的數(shù)值都是以像素為單位的,根據(jù)我們現(xiàn)在對 Player組件的設(shè)置:我們的主角將能夠跳躍 200 像素的高度,起跳到最高點(diǎn)所需的時(shí)間是 0.3 秒,最大水平方向移動(dòng)速度是 400 像素每秒,水平加速度是 350 像素每秒。
這些數(shù)值都是建議,一會(huì)等游戲運(yùn)行起來后,您完全可以按照自己的喜好隨時(shí)在 屬性檢查器 中修改這些數(shù)值,不需要改動(dòng)任何代碼。
編寫跳躍和移動(dòng)代碼
下面我們添加一個(gè)方法,來讓主角跳躍起來,在 properties: {...}, 代碼塊的下面,添加叫做 setJumpAction 的方法:
// Player.js properties: { //... }, setJumpAction: function () { // 跳躍上升 var jumpUp = cc.moveBy(this.jumpDuration, cc.v2(0, this.jumpHeight)).easing(cc.easeCubicActionOut()); // 下落 var jumpDown = cc.moveBy(this.jumpDuration, cc.v2(0, -this.jumpHeight)).easing(cc.easeCubicActionIn()); // 不斷重復(fù) return cc.repeatForever(cc.sequence(jumpUp, jumpDown)); },
這里就需要了解一下 Cocos Creator 的 動(dòng)作(Action)系統(tǒng) 了。由于動(dòng)作系統(tǒng)比較復(fù)雜,這里就簡單的介紹一下。
在 Cocos Creator 中,動(dòng)作 簡單來說就是 節(jié)點(diǎn)的位移、縮放和旋轉(zhuǎn)。
例如在上面的代碼中,moveBy() 方法的作用是在規(guī)定的時(shí)間內(nèi)移動(dòng)指定的一段距離,第一個(gè)參數(shù)就是我們之前定義主角屬性中的跳躍時(shí)間,第二個(gè)參數(shù)是一個(gè) Vec2(表示 2D 向量和坐標(biāo))類型的對象,為了更好的理解,我們可以看看官方給的函數(shù)說明:
/** * !#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 移動(dòng)指定的距離。 * @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.v2(windowSize.width - 40, windowSize.height - 40)); */ cc.moveBy = function (duration, deltaPos, deltaY) { return new cc.MoveBy(duration, deltaPos, deltaY); };
可以看到,方法 moveBy 一共可以傳入三個(gè)參數(shù),前兩個(gè)參數(shù)我們已經(jīng)知道,第三個(gè)參數(shù)是 Number 類型的 Y 坐標(biāo),我們可以發(fā)現(xiàn)第二個(gè)參數(shù)是可以傳入兩種類型的,第一種是 Number 類型,第二種才是 Vec2類型,如果我們在這里傳入的是 Number 類型,那么默認(rèn)這個(gè)參數(shù)就是 X 坐標(biāo),此時(shí)就要填第三個(gè)參數(shù),為 Y 坐標(biāo)。上面的例子中 cc.moveBy(this.jumpDuration, cc.v2(0, this.jumpHeight)) 第二個(gè)參數(shù)傳入的是使用 cc.v2 方法構(gòu)建的 Vec2 類型對象,這個(gè)類型表示的是一個(gè)坐標(biāo),即有 X 坐標(biāo)也有 Y 坐標(biāo),因?yàn)椴恍枰賯魅氲谌齻€(gè)參數(shù)!同時(shí)注意官方的一段話 x and y are relative to the position of the object.,這句話的意思是傳入的 X、Y 坐標(biāo)都是相對于節(jié)點(diǎn)當(dāng)前的坐標(biāo)位置,而不是整個(gè)坐標(biāo)系的絕對坐標(biāo)。
了解了參數(shù)的含義之后,我們再來關(guān)注 moveBy() 方法的返回值,看官方說明可以知道,這個(gè)方法返回的是一個(gè) ActionInterval 類型的對象,ActionInterval 在 Cocos 中是一個(gè)表示時(shí)間間隔動(dòng)作的類,這種動(dòng)作在一定時(shí)間內(nèi)完成。到這里我們就可以理解代碼 cc.moveBy(this.jumpDuration, cc.v2(0, this.jumpHeight)).easing(cc.easeCubicActionOut()) 前一部分 的意思了,它的意思就是構(gòu)造一個(gè) ActionInterval 類型的對象,這個(gè)對象表示在 jumpDuration 的時(shí)間內(nèi),移動(dòng)到相對于當(dāng)前節(jié)點(diǎn)的 (0,this.jumpHeight) 的坐標(biāo)位置,簡單來說,就是一個(gè)向上跳躍的動(dòng)作。
那么 后半部分 easing(cc.easeCubicActionOut()) 的作用是什么呢?easing 是 ActionInterval 類下的一個(gè)方法,這個(gè)方法可以讓時(shí)間間隔動(dòng)作呈現(xiàn)為一種緩動(dòng)運(yùn)動(dòng),傳入的參數(shù)是一個(gè)緩動(dòng)對象,返回一個(gè) ActionInterval 類型對象,這里傳入的是使用 easeCubicActionInOut 方法構(gòu)建的緩動(dòng)對象,EaseCubicInOut 是按三次函數(shù)緩動(dòng)進(jìn)入并退出的動(dòng)作,具體曲線可參考下圖:
詳細(xì)內(nèi)容可參考 API。
接下來在 onLoad 方法里調(diào)用剛添加的 setJumpAction 方法,然后執(zhí)行 runAction 來開始動(dòng)作:
// Player.js onLoad: function () { // 初始化跳躍動(dòng)作 this.jumpAction = this.setJumpAction(); this.node.runAction(this.jumpAction); },
onLoad 方法會(huì)在場景加載后立刻執(zhí)行,所以我們會(huì)把初始化相關(guān)的操作和邏輯都放在這里面。我們首先將循環(huán)跳躍的動(dòng)作傳給了 jumpAction 變量,之后調(diào)用這個(gè)組件掛載的節(jié)點(diǎn)下的 runAction 方法,傳入循環(huán)跳躍的 Action 從而讓節(jié)點(diǎn)(主角)一直跳躍。保存腳本,然后我們就可以開始第一次運(yùn)行游戲了!
點(diǎn)擊 Cocos Creator 編輯器上方正中的 預(yù)覽游戲 按鈕
,Cocos Creator 會(huì)自動(dòng)打開您的默認(rèn)瀏覽器并開始在里面運(yùn)行游戲,現(xiàn)在應(yīng)該可以看到我們的主角——紫色小怪獸在場景中間活潑的蹦個(gè)不停了。
移動(dòng)控制
只能在原地傻蹦的主角可沒前途,讓我們?yōu)橹鹘翘砑渔I盤輸入,用 A 和 D 來控制他的跳躍方向。在 setJumpAction 方法的下面添加鍵盤事件響應(yīng)函數(shù):
// Player.js setJumpAction: function () { //... }, onKeyDown (event) { // set a flag when key pressed switch(event.keyCode) { case cc.macro.KEY.a: this.accLeft = true; break; case cc.macro.KEY.d: this.accRight = true; break; } }, onKeyUp (event) { // unset a flag when key released switch(event.keyCode) { case cc.macro.KEY.a: this.accLeft = false; break; case cc.macro.KEY.d: this.accRight = false; break; } },
然后修改 onLoad 方法,在其中加入向左和向右加速的開關(guān),以及主角當(dāng)前在水平方向的速度。最后再調(diào)用 cc.systemEvent,在場景加載后就開始監(jiān)聽鍵盤輸入:
// Player.js onLoad: function () { // 初始化跳躍動(dòng)作 this.jumpAction = this.setJumpAction(); this.node.runAction(this.jumpAction); // 加速度方向開關(guān) this.accLeft = false; this.accRight = false; // 主角當(dāng)前水平方向速度 this.xSpeed = 0; // 初始化鍵盤輸入監(jiān)聽 cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this); cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this); }, onDestroy () { // 取消鍵盤輸入監(jiān)聽 cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this); cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this); },
有 Android 開發(fā)經(jīng)驗(yàn)的同學(xué)比較好理解,這里的監(jiān)聽器實(shí)質(zhì)上就和 Android 里的 OnClickListener 差不多,在 cocos 中通過 systemEvent 來監(jiān)聽系統(tǒng) 全局 事件。(鼠標(biāo)、觸摸和自定義事件的監(jiān)聽和派發(fā)的詳細(xì)內(nèi)容請參考 監(jiān)聽和發(fā)射事件。)這里通過向 systemEvent 注冊了一個(gè)鍵盤響應(yīng)函數(shù),在函數(shù)中通過 switch 判斷鍵盤上的 A 和 D 是否被按下或松開,若按下就執(zhí)行對應(yīng)的操作。
最后修改 update 方法的內(nèi)容,添加加速度、速度和主角當(dāng)前位置的設(shè)置:
// Player.js update: function (dt) { // 根據(jù)當(dāng)前加速度方向每幀更新速度 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); } // 根據(jù)當(dāng)前速度更新主角的位置 this.node.x += this.xSpeed * dt; },
update 在場景加載后就會(huì)每幀調(diào)用一次,我們一般把需要經(jīng)常計(jì)算或及時(shí)更新的邏輯內(nèi)容放在這里。在我們的游戲中,根據(jù)鍵盤輸入獲得加速度方向后,就需要每幀在 update 中計(jì)算主角的速度和位置。
保存腳本后,點(diǎn)擊 預(yù)覽游戲 來看看我們最新的成果。在瀏覽器打開預(yù)覽后,用鼠標(biāo)點(diǎn)擊一下游戲畫面(這是瀏覽器的限制,要點(diǎn)擊游戲畫面才能接受鍵盤輸入),然后就可以按 A 和 D 鍵來控制主角左右移動(dòng)了!
感覺移動(dòng)起來有點(diǎn)遲緩?主角跳的不夠高?希望跳躍時(shí)間長一些?沒問題,這些都可以隨時(shí)調(diào)整。只要為 Player 組件設(shè)置不同的屬性值,就可以按照您的想法調(diào)整游戲。這里有一組設(shè)置可供參考:
Jump Height: 150 Jump Duration: 0.3 Max Move Speed: 400 Accel: 1000
這組屬性設(shè)置會(huì)讓主角變得靈活無比,至于如何選擇,就看您想做一個(gè)什么風(fēng)格的游戲了。
制作星星
主角現(xiàn)在可以跳來跳去了,我們要給玩家一個(gè)目標(biāo),也就是會(huì)不斷出現(xiàn)在場景中的星星,玩家需要引導(dǎo)小怪獸碰觸星星來收集分?jǐn)?shù)。被主角碰到的星星會(huì)消失,然后馬上在隨機(jī)位置重新生成一個(gè)。
制作 Prefab
對于需要重復(fù)生成的節(jié)點(diǎn),我們可以將他保存成 Prefab(預(yù)制) 資源,作為我們動(dòng)態(tài)生成節(jié)點(diǎn)時(shí)使用的模板。關(guān)于 Prefab 的更多信息,請閱讀 預(yù)制資源(Prefab)。
首先從 資源管理器 中拖拽 assets/textures/star 圖片到場景中,位置隨意,我們只是需要借助場景作為我們制作 Prefab 的工作臺(tái),制作完成后會(huì)我們把這個(gè)節(jié)點(diǎn)從場景中刪除。
我們不需要修改星星的位置或渲染屬性,但要讓星星能夠被主角碰觸后消失,我們需要為星星也添加一個(gè)專門的組件。按照和添加 Player 腳本相同的方法,添加名叫 Star 的 JavaScript 腳本到 assets/scripts/中。
接下來雙擊這個(gè)腳本開始編輯,星星組件只需要一個(gè)屬性用來規(guī)定主角距離星星多近時(shí)就可以完成收集,修改 properties,加入以下內(nèi)容并保存腳本。
// Star.js properties: { // 星星和主角之間的距離小于這個(gè)數(shù)值時(shí),就會(huì)完成收集 pickRadius: 0, },
將這個(gè)腳本添加到剛創(chuàng)建的 star 節(jié)點(diǎn)上,在 層級管理器 中選中 star 節(jié)點(diǎn),然后在 屬性檢查器 中點(diǎn)擊 添加組件 按鈕,選擇 添加用戶腳本組件 -> Star,該腳本便會(huì)添加到剛創(chuàng)建的 star 節(jié)點(diǎn)上。然后在 屬性檢查器中把 Pick Radius 屬性值設(shè)為 60:
Star Prefab 需要的設(shè)置就完成了,現(xiàn)在從 層級管理器 中將 star 節(jié)點(diǎn)拖拽到 資源管理器 中的 assets 文件夾下,就生成了名叫 star 的 Prefab 資源。
現(xiàn)在可以從場景中刪除 star 節(jié)點(diǎn)了,后續(xù)可以直接雙擊這個(gè) star Prefab 資源進(jìn)行編輯。
接下去我們會(huì)在腳本中動(dòng)態(tài)使用星星的 Prefab 資源生成星星。
添加游戲控制腳本
星星的生成是游戲主邏輯的一部分,所以我們要添加一個(gè)叫做 Game 的腳本作為游戲主邏輯腳本,這個(gè)腳本之后還會(huì)添加計(jì)分、游戲失敗和重新開始的相關(guān)邏輯。
添加 Game 腳本到 assets/scripts 文件夾下,雙擊打開腳本。首先添加生成星星需要的屬性:
// Game.js properties: { // 這個(gè)屬性引用了星星預(yù)制資源 starPrefab: { default: null, type: cc.Prefab }, // 星星產(chǎn)生后消失時(shí)間的隨機(jī)范圍 maxStarDuration: 0, minStarDuration: 0, // 地面節(jié)點(diǎn),用于確定星星生成的高度 ground: { default: null, type: cc.Node }, // player 節(jié)點(diǎn),用于獲取主角彈跳的高度,和控制主角行動(dòng)開關(guān) player: { default: null, type: cc.Node } },
這里初學(xué)者可能會(huì)疑惑,為什么像 starPrefab 這樣的屬性會(huì)用 {} 括起來,括號(hào)里面還有新的 “屬性” 呢?其實(shí)這是屬性的一種完整聲明,之前我們的屬性聲明都是不完整的,有些情況下,我們需要為屬性聲明添加參數(shù),這些參數(shù)控制了屬性在 屬性檢查器 中的顯示方式,以及屬性在場景序列化過程中的行為。例如:
properties: { score: { default: 0, displayName: "Score (player)", tooltip: "The score of player", } }
以上代碼為 score 屬性設(shè)置了三個(gè)參數(shù) default、 displayName 和 tooltip。這幾個(gè)參數(shù)分別指定了 score的默認(rèn)值(default)為 0,在 屬性檢查器 里,其屬性名(displayName)將顯示為 Score (player),并且當(dāng)鼠標(biāo)移到參數(shù)上時(shí),顯示對應(yīng)的 tooltip。
下面是常用參數(shù):
default:設(shè)置屬性的默認(rèn)值,這個(gè)默認(rèn)值僅在組件第一次添加到節(jié)點(diǎn)上時(shí)才會(huì)用到
type:限定屬性的數(shù)據(jù)類型,詳見 CCClass 進(jìn)階參考:type 參數(shù)
visible:設(shè)為 false 則不在屬性檢查器面板中顯示該屬性
serializable: 設(shè)為 false 則不序列化(保存)該屬性
displayName:在屬性檢查器面板中顯示成指定名字
tooltip:在屬性檢查器面板中添加屬性的 tooltip
所以上面的代碼:
starPrefab: { default: null, type: cc.Prefab },
就容易理解了,首先在 Game 組件下聲明了 starPrefab 屬性,這個(gè)屬性默認(rèn)值為 null,能傳入的類型必須是 Prefab 預(yù)制資源類型。這樣之后的 ground、player 屬性也可以理解了。
保存腳本后將 Game 組件添加到 層級管理器 中的 Canvas 節(jié)點(diǎn)上(選中 Canvas 節(jié)點(diǎn)后,拖拽腳本到 屬性檢查器 上,或者點(diǎn)擊 屬性檢查器 的 添加組件 按鈕,并從 添加用戶腳本組件 中選擇 Game。)
接下來從 資源管理器 中拖拽 star 的 Prefab 資源到 Game 組件的 Star Prefab 屬性中。這是我們第一次為屬性設(shè)置引用,只有在屬性聲明時(shí)規(guī)定 type 為引用類型時(shí)(比如我們這里寫的 cc.Prefab 類型),才能夠?qū)①Y源或節(jié)點(diǎn)拖拽到該屬性上。
接著從 層級管理器 中拖拽 ground 和 Player 節(jié)點(diǎn)到 Canvas 節(jié)點(diǎn) Game 組件中相對應(yīng)名字的屬性上,完成節(jié)點(diǎn)引用。
然后設(shè)置 Min Star Duration 和 Max Star Duration 屬性的值為 3 和 5,之后我們生成星星時(shí),會(huì)在這兩個(gè)之間隨機(jī)取值,就是星星消失前經(jīng)過的時(shí)間。
在隨機(jī)位置生成星星
接下來我們繼續(xù)修改 Game 腳本,在 onLoad 方法 后面 添加生成星星的邏輯:
// Game.js onLoad: function () { // 獲取地平面的 y 軸坐標(biāo) this.groundY = this.ground.y + this.ground.height/2; // 生成一個(gè)新的星星 this.spawnNewStar(); }, spawnNewStar: function() { // 使用給定的模板在場景中生成一個(gè)新節(jié)點(diǎn) var newStar = cc.instantiate(this.starPrefab); // 將新增的節(jié)點(diǎn)添加到 Canvas 節(jié)點(diǎn)下面 this.node.addChild(newStar); // 為星星設(shè)置一個(gè)隨機(jī)位置 newStar.setPosition(this.getNewStarPosition()); }, getNewStarPosition: function () { var randX = 0; // 根據(jù)地平面位置和主角跳躍高度,隨機(jī)得到一個(gè)星星的 y 坐標(biāo) var randY = this.groundY + Math.random() * this.player.getComponent('Player').jumpHeight + 50; // 根據(jù)屏幕寬度,隨機(jī)得到一個(gè)星星 x 坐標(biāo) var maxX = this.node.width/2; randX = (Math.random() - 0.5) * 2 * maxX; // 返回星星坐標(biāo) return cc.v2(randX, randY); },
這里需要注意幾個(gè)問題:
保存腳本以后點(diǎn)擊 預(yù)覽游戲 按鈕,在瀏覽器中可以看到,游戲開始后動(dòng)態(tài)生成了一顆星星!用同樣的方法,您可以在游戲中動(dòng)態(tài)生成任何預(yù)先設(shè)置好的以 Prefab 為模板的節(jié)點(diǎn)。
添加主角碰觸收集星星的行為
現(xiàn)在要添加主角收集星星的行為邏輯了,這里的重點(diǎn)在于,星星要隨時(shí)可以獲得主角節(jié)點(diǎn)的位置,才能判斷他們之間的距離是否小于可收集距離,如何獲得主角節(jié)點(diǎn)的引用呢?別忘了我們前面做過的兩件事:
所以我們只要在 Game 腳本生成 Star 節(jié)點(diǎn)實(shí)例時(shí),將 Game 組件的實(shí)例傳入星星并保存起來就好了,之后我們可以隨時(shí)通過 game.player 來訪問到主角節(jié)點(diǎn)。讓我們打開 Game 腳本,在 spawnNewStar 方法最后面添加一句 newStar.getComponent('Star').game = this;,如下所示:
// Game.js spawnNewStar: function() { // ... // 在星星組件上暫存 Game 對象的引用 newStar.getComponent('Star').game = this; },
保存后打開 Star 腳本,現(xiàn)在我們可以利用 Game 組件中引用的 player 節(jié)點(diǎn)來判斷距離了,在 onLoad 方法后面添加名為 getPlayerDistance 和 onPicked 的方法:
// Star.js getPlayerDistance: function () { // 根據(jù) player 節(jié)點(diǎn)位置判斷距離 var playerPos = this.game.player.getPosition(); // 根據(jù)兩點(diǎn)位置計(jì)算兩點(diǎn)之間距離 var dist = this.node.position.sub(playerPos).mag(); return dist; }, onPicked: function() { // 當(dāng)星星被收集時(shí),調(diào)用 Game 腳本中的接口,生成一個(gè)新的星星 this.game.spawnNewStar(); // 然后銷毀當(dāng)前星星節(jié)點(diǎn) this.node.destroy(); },
Node 下的 getPosition() 方法 返回的是節(jié)點(diǎn)在父節(jié)點(diǎn)坐標(biāo)系中的位置(x, y),即一個(gè) Vec2 類型對象。同時(shí)注意調(diào)用 Node 下的 destroy() 方法 就可以銷毀節(jié)點(diǎn)。
然后在 update 方法中添加每幀判斷距離,如果距離小于 pickRadius 屬性規(guī)定的收集距離,就執(zhí)行收集行為:
// Star.js update: function (dt) { // 每幀判斷和主角之間的距離是否小于收集距離 if (this.getPlayerDistance() < this.pickRadius) { // 調(diào)用收集行為 this.onPicked(); return; } },
保存腳本,再次預(yù)覽測試,通過按 A 和 D 鍵來控制主角左右移動(dòng),就可以看到控制主角靠近星星時(shí),星星就會(huì)消失掉,然后在隨機(jī)位置生成了新的星星!
添加得分
小怪獸辛辛苦苦的收集星星,沒有獎(jiǎng)勵(lì)怎么行?現(xiàn)在讓我們來添加在收集星星時(shí)增加得分獎(jiǎng)勵(lì)的邏輯和顯示。
添加分?jǐn)?shù)文字(Label)
游戲開始時(shí)得分從 0 開始,每收集一個(gè)星星分?jǐn)?shù)就會(huì)加 1。要顯示得分,首先要?jiǎng)?chuàng)建一個(gè) Label 節(jié)點(diǎn)。在 層級管理器 中選中 Canvas 節(jié)點(diǎn),右鍵點(diǎn)擊并選擇菜單中的 創(chuàng)建新節(jié)點(diǎn) -> 創(chuàng)建渲染節(jié)點(diǎn) -> Label(文字),一個(gè)新的 Label 節(jié)點(diǎn)會(huì)被創(chuàng)建在 Canvas 節(jié)點(diǎn)下面,而且順序在最下面。接下來我們要用如下的步驟配置這個(gè) Label 節(jié)點(diǎn):
注意: Score: 0 的文字建議使用英文冒號(hào),因?yàn)?Label 組件的 String 屬性加了位圖字體后,會(huì)無法識(shí)別中文的冒號(hào)。
完成后效果如下圖所示:
在 Game 腳本中添加得分邏輯
我們將會(huì)把計(jì)分和更新分?jǐn)?shù)顯示的邏輯放在 Game 腳本里,打開 Game 腳本開始編輯,首先在 properties 區(qū)塊的 最后 添加分?jǐn)?shù)顯示 Label 的引用屬性:
// Game.js properties: { // ... // score label 的引用 scoreDisplay: { default: null, type: cc.Label } },
接下來在 onLoad 方法 里面 添加計(jì)分用的變量的初始化:
// Game.js onLoad: function () { // ... // 初始化計(jì)分 this.score = 0; },
然后在 update 方法 后面 添加名叫 gainScore 的新方法:
// Game.js gainScore: function () { this.score += 1; // 更新 scoreDisplay Label 的文字 this.scoreDisplay.string = 'Score: ' + this.score; },
保存 Game 腳本后,回到 層級管理器,選中 Canvas 節(jié)點(diǎn),然后把前面添加好的 score 節(jié)點(diǎn)拖拽到 屬性檢查器 里 Game 組件的 Score Display 屬性中。
在 Star 腳本中調(diào)用 Game 中的得分邏輯
下面打開 Star 腳本,在 onPicked 方法中加入 gainScore 的調(diào)用:
// Star.js onPicked: function() { // 當(dāng)星星被收集時(shí),調(diào)用 Game 腳本中的接口,生成一個(gè)新的星星 this.game.spawnNewStar(); // 調(diào)用 Game 腳本的得分方法 this.game.gainScore(); // 然后銷毀當(dāng)前星星節(jié)點(diǎn) this.node.destroy(); },
保存后預(yù)覽,可以看到現(xiàn)在收集星星時(shí)屏幕正上方顯示的分?jǐn)?shù)會(huì)增加了!
失敗判定和重新開始
現(xiàn)在我們的游戲已經(jīng)初具規(guī)模,但得分再多,不可能失敗的游戲也不會(huì)給人成就感。現(xiàn)在讓我們加入星星定時(shí)消失的行為,而且讓星星消失時(shí)就判定為游戲失敗。也就是說,玩家需要在每顆星星消失之前完成收集,并不斷重復(fù)這個(gè)過程完成玩法的循環(huán)。
為星星加入計(jì)時(shí)消失的邏輯
打開 Game 腳本,在 onLoad 方法的 spawnNewStar 調(diào)用 之前 加入計(jì)時(shí)需要的變量聲明:
// Game.js onLoad: function () { // ... // 初始化計(jì)時(shí)器 this.timer = 0; this.starDuration = 0; // 生成一個(gè)新的星星 this.spawnNewStar(); // 初始化計(jì)分 this.score = 0; },
然后在 spawnNewStar 方法最后加入重置計(jì)時(shí)器的邏輯,其中 this.minStarDuration 和 this.maxStarDuration 是我們一開始聲明的 Game 組件屬性,用來規(guī)定星星消失時(shí)間的隨機(jī)范圍:
// Game.js spawnNewStar: function() { // ... // 重置計(jì)時(shí)器,根據(jù)消失時(shí)間范圍隨機(jī)取一個(gè)值 this.starDuration = this.minStarDuration + Math.random() * (this.maxStarDuration - this.minStarDuration); this.timer = 0; },
在 update 方法中加入計(jì)時(shí)器更新和判斷超過時(shí)限的邏輯:
// Game.js update: function (dt) { // 每幀更新計(jì)時(shí)器,超過限度還沒有生成新的星星 // 就會(huì)調(diào)用游戲失敗邏輯 if (this.timer > this.starDuration) { this.gameOver(); return; } this.timer += dt; },
最后,在 gainScore 方法后面加入 gameOver 方法,游戲失敗時(shí)重新加載場景。
// Game.js gameOver: function () { this.player.stopAllActions(); //停止 player 節(jié)點(diǎn)的跳躍動(dòng)作 cc.director.loadScene('game'); }
這里需要初學(xué)者了解的是,cc.director 是一個(gè)管理你的游戲邏輯流程的單例對象。由于 cc.director 是一個(gè)單例,你不需要調(diào)用任何構(gòu)造函數(shù)或創(chuàng)建函數(shù),使用它的標(biāo)準(zhǔn)方法是通過調(diào)用 cc.director.methodName(),例如這里的 cc.director.loadScene('game') 就是重新加載游戲場景 game,也就是游戲重新開始。而節(jié)點(diǎn)下的 stopAllActions 方法就顯而易見了,這個(gè)方法會(huì)讓節(jié)點(diǎn)上的所有 Action 都失效。
以上,對 Game 腳本的修改就完成了,保存腳本,然后打開 Star 腳本,我們需要為即將消失的星星加入簡單的視覺提示效果,在 update 方法最后加入以下代碼:
// Star.js update: function() { // ... // 根據(jù) Game 腳本中的計(jì)時(shí)器更新星星的透明度 var opacityRatio = 1 - this.game.timer/this.game.starDuration; var minOpacity = 50; this.node.opacity = minOpacity + Math.floor(opacityRatio * (255 - minOpacity)); }
保存 Star 腳本,我們的游戲玩法邏輯就全部完成了!現(xiàn)在點(diǎn)擊 預(yù)覽游戲 按鈕,我們在瀏覽器看到的就是一個(gè)有核心玩法、激勵(lì)機(jī)制、失敗機(jī)制的合格游戲了。
加入音效
盡管很多人玩手游的時(shí)候會(huì)無視聲音,我們?yōu)榱私坛陶故镜墓ぷ髁鞒瘫M量完整,還是要補(bǔ)全加入音效的任務(wù)。
跳躍音效
首先加入跳躍音效,打開 Player 腳本,添加引用聲音文件資源的 jumpAudio 屬性:
// Player.js properties: { // ... // 跳躍音效資源 jumpAudio: { default: null, type: cc.AudioClip }, },
然后改寫 setJumpAction 方法,插入播放音效的回調(diào),并通過添加 playJumpSound 方法來播放聲音:
// Player.js setJumpAction: function () { // 跳躍上升 var jumpUp = cc.moveBy(this.jumpDuration, cc.v2(0, this.jumpHeight)).easing(cc.easeCubicActionOut()); // 下落 var jumpDown = cc.moveBy(this.jumpDuration, cc.v2(0, -this.jumpHeight)).easing(cc.easeCubicActionIn()); // 添加一個(gè)回調(diào)函數(shù),用于在動(dòng)作結(jié)束時(shí)調(diào)用我們定義的其他方法 var callback = cc.callFunc(this.playJumpSound, this); // 不斷重復(fù),而且每次完成落地動(dòng)作后調(diào)用回調(diào)來播放聲音 return cc.repeatForever(cc.sequence(jumpUp, jumpDown, callback)); }, playJumpSound: function () { // 調(diào)用聲音引擎播放聲音 cc.audioEngine.playEffect(this.jumpAudio, false); },
這里需要強(qiáng)調(diào)的是回調(diào)函數(shù)的作用,我們首先來看官方對 callFunc() 方法的定義:
/** * !#en Creates the action with the callback. * !#zh 執(zhí)行回調(diào)函數(shù)。 * @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 方法可以傳入三個(gè)參數(shù),第一個(gè)參數(shù)是方法的 selector,我們可以理解為方法名。第二個(gè)參數(shù)是 Object 類型,一般填入 this。第三個(gè)參數(shù)為帶回的數(shù)據(jù),可以是所有的數(shù)據(jù)類型,可以不填。我們再注意到這個(gè)方法的返回值 —— ActionInstant,這是一個(gè)瞬間執(zhí)行的動(dòng)作類。到這里我們就可以理解了,使用 callFunc 調(diào)用回調(diào)函數(shù)可以讓函數(shù)轉(zhuǎn)變?yōu)?cc 中的 Action(動(dòng)作),這一用法在 cc 的動(dòng)作系統(tǒng)里非常實(shí)用!例如在上面我們將播放聲音的函數(shù)傳入 callFunc 賦值給 callback,讓 callback 成為了一個(gè)播放聲音的動(dòng)作 Action,那么我們之后就能通過 cc.sequence 將跳躍和播放聲音的動(dòng)作組合起來,實(shí)現(xiàn)每跳一次就能播放音效的功能!
得分音效
保存 Player 腳本以后打開 Game 腳本,來添加得分音效,首先仍然是在 properties 中添加一個(gè)屬性來引用聲音文件資源:
// Game.js properties: { // ... // 得分音效資源 scoreAudio: { default: null, type: 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 節(jié)點(diǎn),然后從 資源管理器 里拖拽 assets/audio/jump 資源到 Player 組件的 Jump Audio 屬性上。
然后選中 Canvas 節(jié)點(diǎn),把 assets/audio/score 資源拖拽到 Game 組件的 Score Audio 屬性上。
這樣就大功告成了!完成形態(tài)的場景層級和各個(gè)關(guān)鍵組件的屬性如下:
現(xiàn)在我們可以盡情享受剛制作完成的游戲了,您能打到多少分呢?別忘了您可以隨時(shí)修改 Player 和 Game 組件里的移動(dòng)控制和星星持續(xù)時(shí)間等游戲參數(shù),來快速調(diào)節(jié)游戲的難度。修改組件屬性之后需要保存場景,修改后的數(shù)值才會(huì)被記錄下來。
總結(jié)
恭喜您完成了用 Cocos Creator 制作的第一個(gè)游戲!希望這篇快速入門教程能幫助您了解 Cocos Creator 游戲開發(fā)流程中的基本概念和工作流程。如果您對編寫和學(xué)習(xí)腳本編程不感興趣,也可以直接從完成版的項(xiàng)目中把寫好的腳本復(fù)制過來使用。
*請認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。