前兩天在Cocos官方公眾號(hào)上學(xué)習(xí)了「大掌教」的Cocos Creator 2.x Camera教程碉纺,總算是對(duì)攝像機(jī)組件有了一個(gè)初步的認(rèn)識(shí)。乘熱打鐵刻撒,Shawn即刻就使用Camera攝像機(jī)練習(xí)了一個(gè)飛機(jī)游戲的骨田,目前主要實(shí)現(xiàn)3個(gè)功能:
- 無(wú)限滾動(dòng)背景
- 控制飛機(jī)移動(dòng)
- 子彈發(fā)射
下面是游戲視頻:
視頻地址:https://v.youku.com/v_show/id_XNDA4NDAxODMyNA==.html?spm=a2hzp.8244740.0.0
1. 無(wú)限滾動(dòng)背景
滾動(dòng)背景我們當(dāng)然是使用最新的攝像機(jī)來(lái)實(shí)現(xiàn)优质,我這里做了一個(gè)卷軸攝像機(jī)組件ScrollCamera渠啤,我們現(xiàn)來(lái)看一下組件暴露的屬性:
ScrollCamera組件很像真實(shí)世界中的攝像機(jī)的推進(jìn)器,Speed是推進(jìn)速度次企,LoopGrounds是一個(gè)節(jié)點(diǎn)數(shù)組醋火,他們是一組可首尾銜接的精靈節(jié)點(diǎn)悠汽,看下圖:
我們?cè)倏匆幌耂crollCamera組件的代碼:
cc.Class({
editor: {
requireComponent: cc.Camera, //前置要求攝像機(jī)組件
},
extends: cc.Component,
properties: {
speed: 300, //滾動(dòng)速度
loopGrounds: [cc.Node], //循環(huán)節(jié)點(diǎn)
},
start () {
//獲取節(jié)點(diǎn)上的攝像機(jī)組件
this.camera = this.getComponent(cc.Camera);
},
/**
*每幀更新函數(shù)
*1. 更新攝像機(jī)位置
*2. 檢查循環(huán)節(jié)點(diǎn),設(shè)置新位置
**/
update(dt) {
//獲取當(dāng)前節(jié)點(diǎn)
let current = this.loopGrounds[0];
//計(jì)算當(dāng)前節(jié)點(diǎn)在攝像機(jī)中的位置
let pt = this.camera.getWorldToCameraPoint(current.position);
//當(dāng)前節(jié)點(diǎn)超出攝像機(jī)范圍(攝像機(jī)可視范圍就是屏幕大薪娌怠)
if (pt.y <= -cc.winSize.height) {
//取最后一個(gè)地圖節(jié)點(diǎn)
let last = this.loopGrounds[this.loopGrounds.length - 1];
//將當(dāng)前節(jié)點(diǎn)從數(shù)組中移除
this.loopGrounds.shift();
//將當(dāng)前節(jié)點(diǎn)放到數(shù)組最后
this.loopGrounds.push(current);
//將當(dāng)前節(jié)點(diǎn)位置移動(dòng)到最頂部位置
current.y = last.y + (last.height + current.height) / 2;
}
//更新攝像機(jī)節(jié)點(diǎn)位置
this.node.y += dt * this.speed;
}
});
推動(dòng)攝像機(jī)的代碼很簡(jiǎn)單柿冲,看update函數(shù)中的最后一行:
this.node.y += dt * this.speed;
update中前面的幾行代碼是在做loopGrounds節(jié)點(diǎn)的檢查和位置更新,每一行都注釋?zhuān)@里就不再過(guò)多贅述了兆旬。
將這個(gè)組件直接拖動(dòng)到場(chǎng)景編輯器或?qū)蛹?jí)管理器姻采,設(shè)置background節(jié)點(diǎn)為background分組:
同時(shí)設(shè)置ScrollCamera的cullingMask屬性只勾選background,看下圖:
通過(guò)上面的設(shè)置和ScrollCamera的十幾代碼爵憎,無(wú)限滾動(dòng)背景就搞定了慨亲。
2. 控制飛機(jī)移動(dòng)
不知道大家還記得公眾號(hào)之前的一篇文章《Cocos Creator基礎(chǔ)教程(11)—可拖拽組件》,我直接將Dragable.js組件腳本拿過(guò)來(lái)宝鼓,掛載到飛機(jī)節(jié)點(diǎn)上就OK了刑棵,代碼也很簡(jiǎn)單:
/**
* 可拖動(dòng)組件
*/
cc.Class({
extends: cc.Component,
onLoad() {
//注冊(cè)TOUCH_MOVE事件
this.node.on(cc.Node.EventType.TOUCH_MOVE, this._onTouchMove, this);
cc.log('onload');
},
_onTouchMove(touchEvent) {
//let location = touchEvent.getLocation();
//this.node.position = this.node.parent.convertToNodeSpaceAR(location);
//獲取觸摸移動(dòng)增量
let delta = touchEvent.getDelta();
//當(dāng)前節(jié)點(diǎn)位置+增量,更新節(jié)點(diǎn)位置
this.node.position = delta.add(this.node.position);
}
});
_onTouchMove函數(shù)稍微調(diào)整了一下愚铡,之前使用方法的是當(dāng)前節(jié)點(diǎn)設(shè)置為觸摸點(diǎn)位置蛉签,需要將全局坐標(biāo)轉(zhuǎn)換為當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)坐標(biāo)(設(shè)置一個(gè)節(jié)點(diǎn)的位置胡陪,是設(shè)置它在其父節(jié)點(diǎn)中的位置),拖動(dòng)時(shí)節(jié)點(diǎn)總是保持在移動(dòng)點(diǎn)的中心碍舍,特別是在第一次拖動(dòng)節(jié)點(diǎn)時(shí)會(huì)有一個(gè)跳躍感柠座,不夠平滑。
我這里簡(jiǎn)單改進(jìn)了一下片橡,通過(guò)獲取移動(dòng)增量再加上當(dāng)前節(jié)點(diǎn)位置妈经,可以拖動(dòng)節(jié)點(diǎn)的任意位置,不會(huì)出現(xiàn)突然將節(jié)點(diǎn)拉動(dòng)到手指中心的突兀感捧书。
Shawn在做這個(gè)飛機(jī)游戲過(guò)程中也嘗試了一下消滅病毒當(dāng)下這個(gè)火熱的游戲吹泡,他的整個(gè)屏幕任意位置都可以控制飛機(jī)的移動(dòng),它是怎么做的呢经瓷,大家先可以思考一下爆哑?
我們這里再修改一下Dragable組件,增加一個(gè)target節(jié)點(diǎn)屬性舆吮,將它從飛機(jī)節(jié)點(diǎn)上移到外層foreground節(jié)點(diǎn)揭朝,看下圖:
觸摸事件發(fā)生在foreground節(jié)點(diǎn)上,但移動(dòng)的是target屬性所指向的節(jié)點(diǎn)色冀,我們看下代碼:
/**
* 可拖動(dòng)組件
*/
cc.Class({
extends: cc.Component,
properties: {
target: cc.Node,
},
...
_onTouchMove(touchEvent) {
//獲取觸摸移動(dòng)增量
let delta = touchEvent.getDelta();
//如果this.target未設(shè)置潭袱,使用移動(dòng)當(dāng)前節(jié)點(diǎn)(兼容之前的用法)
let node = this.target || this.node;
//當(dāng)前節(jié)點(diǎn)位置+增量,更新節(jié)點(diǎn)位置
node.position = delta.add(node.position);
}
});
代碼就增加了一個(gè)target節(jié)點(diǎn)的定義呐伞,在TouchMove事件中檢查this.target存在就用它敌卓,不存在默認(rèn)移動(dòng)當(dāng)前節(jié)點(diǎn)慎式,這樣可以兼容曾經(jīng)該組件的地方伶氢,不用做修改。
3. 子彈發(fā)射
飛機(jī)游戲的一個(gè)亮點(diǎn)就是子彈發(fā)射的華麗視覺(jué)效果瘪吏,Shawn這里在網(wǎng)上找了些子彈特效圖片癣防,然后編輯了一個(gè)子彈Bullet的預(yù)制體,使用到之前文章《Cocos Creator基礎(chǔ)教程(12)—精靈變身》中的SpriteEx.js組件掌眠,在上面配置了幾張子彈圖片蕾盯,使用index屬性可以方便切換子彈的表現(xiàn)效果,看下圖:
Bullet子彈只是表現(xiàn)效果蓝丙,要讓子彈運(yùn)動(dòng)起來(lái)级遭,我這里編寫(xiě)了一個(gè)LineEmmiter.js(線性發(fā)射器)的腳本,將它掛載到飛機(jī)節(jié)點(diǎn)上渺尘,用它來(lái)實(shí)例化Bullet預(yù)制體并讓它動(dòng)起來(lái)挫鸽,先看一下LineEmmiter組件的屬性:
之前的文章中提到過(guò):組件為節(jié)點(diǎn)賦予能力,飛機(jī)節(jié)點(diǎn)上有一個(gè)Sprite可顯示圖片紋理鸥跟,我們?cè)賿焐螸ineEmmiter組件丢郊,讓它具有發(fā)射子彈的能力盔沫。
發(fā)射器的主要屬性是子彈預(yù)制體、發(fā)射頻率枫匾、子彈飛行速度架诞,OffsetX屬性要特別一點(diǎn),它可以控制子彈與飛機(jī)的偏移位置干茉,以實(shí)現(xiàn)同時(shí)發(fā)射多行子彈的效果谴忧,看下圖:
我們?cè)倏聪掳l(fā)射器的組件代碼:
cc.Class({
extends: cc.Component,
properties: {
prefab: cc.Prefab,
rate: 1, //發(fā)射間隔
speed: 1000, //移動(dòng)速度
offsetX: 0,
},
start() {
this.schedule(this._emmitNode, this.rate);
},
_emmitNode() {
//實(shí)例化節(jié)點(diǎn),設(shè)置位置&父節(jié)
let node = cc.instantiate(this.prefab);
node.position = this.node.position;
node.x += this.offsetX;
node.parent = this.node.parent;
//計(jì)算子彈需要飛行的距離等脂,飛行時(shí)間 = 距離 / 速度
let distance = ((cc.winSize.height / 2) - this.node.y);
let duration = distance / this.speed;
//使用moveBy動(dòng)作俏蛮,完成后刪除子彈節(jié)點(diǎn)
let moveBy = cc.moveBy(duration, cc.v2(0, distance));
let removeSelf = cc.removeSelf();
let sequence = cc.sequence(moveBy, removeSelf);
node.runAction(sequence);
}
});
發(fā)射器代碼也很簡(jiǎn)單:
- 實(shí)例化子彈節(jié)點(diǎn)
- 讓子彈飛起來(lái)
我們這里子彈是垂直飛行的,直接使moveBy動(dòng)作就可以完成上遥,子彈從當(dāng)前飛機(jī)節(jié)點(diǎn)出發(fā)直到屏幕頂部結(jié)束搏屑,這是它飛行的距離根據(jù)公式:距離/速度=時(shí)間,計(jì)算每顆子彈的飛行時(shí)間粉楚,保證飛機(jī)在不同位置辣恋,所有子彈都是按同樣的速度飛行。
4. 小結(jié)
本次教程我們實(shí)現(xiàn)了一個(gè)最小飛機(jī)游戲的簡(jiǎn)單原型模软,我們的核心地圖滾動(dòng)與子彈發(fā)射代碼只有70多行伟骨,有沒(méi)有覺(jué)得使用Cocos Creator開(kāi)發(fā)游戲飛一般的簡(jiǎn)單呢...
不過(guò)還有很多欠缺的地方,比如:限制飛機(jī)不要跑出屏幕之外燃异、子彈應(yīng)該使用內(nèi)存池進(jìn)行優(yōu)化携狭,在功能上還缺少敵機(jī)生成、少子彈碰撞回俐、得分計(jì)算等等逛腿,這些內(nèi)容我們留到下次繼續(xù)。
想獲得源碼的同學(xué)可以在仅颇,公眾號(hào)回復(fù):“飛機(jī)”或“飛機(jī)大戰(zhàn)”单默,如果你對(duì)飛機(jī)游戲有不同的實(shí)現(xiàn)方案,歡迎一起討論忘瓦!