凌波微步有云:
此步法精妙異常乱豆,習(xí)者可以用來躲避眾多敵人的進(jìn)攻伶棒,此外「凌波微步」每踏出一步,都與內(nèi)力息息相關(guān)项戴,決非單是邁步行走而已形帮,若無內(nèi)功根基之人,將「凌波微步」強(qiáng)行走將起來周叮,會(huì)造成自絕經(jīng)脈的危境辩撑。
一、英雄的窘境
上篇《英雄之舞—迷蹤“安可心”》一章中仿耽,我們研習(xí)了迷蹤步合冀,runAction是建立英雄與安可心之間的鏈接,最后還學(xué)習(xí)了逍遙訣项贺,而逍遙訣則是建立英雄與萬物之間鏈接君躺。
1. 多人動(dòng)作協(xié)同
多人指的是多個(gè)節(jié)點(diǎn),當(dāng)兩個(gè)節(jié)點(diǎn)在舞步中有先后次序時(shí)开缎,我們有那些可控制的方法呢棕叫?來看下面這段演示:
上圖是一個(gè)男孩與女孩的故事,我們的重點(diǎn)不是講故事奕删,而是講他們發(fā)生的動(dòng)作谍珊,研究相對(duì)高效可控的舞步控制手段。
言歸正傳,演示中男孩與Label砌滞,一前一后侮邀,使用逍遙訣cc.callFunc很容易控制,同時(shí)在一個(gè)完整動(dòng)作完畢時(shí)贝润,使用一個(gè)完成回調(diào)绊茧,顯示行動(dòng)完成,請(qǐng)看代碼:
//_moveAndCall函數(shù)分享具體的節(jié)點(diǎn)打掘,具體的迷蹤步就不贅述了
//參數(shù)1:移動(dòng)的節(jié)點(diǎn)
//參數(shù)2:移動(dòng)的位置
//參數(shù)3:要說的話
//參數(shù)4:動(dòng)作完成回調(diào)
this._moveAndCall(this._boy, cc.p(this._boy.x, 200),'妹妹快過來华畏!', () => {
this.log('呼叫妹妹完畢');
});
函數(shù)比較簡單,_moveAndCall主要是迷蹤步的封裝尊蚁,細(xì)節(jié)這里不表亡笑,我們繼續(xù)看女孩的回答:
女孩做了相同的動(dòng)作,這里我們可以復(fù)用this._moveAndCall方法
this._moveAndCall(this._boy, '妹妹快過來横朋!', () => {
this._moveAndCall(this._gril, '喊我過來做啥子嘛仑乌!', () => {
this.log('妹妹回答完畢');
});
});
我們高效地的利用_moveAndCall的最后一個(gè)回調(diào),讓女孩即時(shí)做出了回應(yīng)琴锭,繼續(xù)看他們的完整互動(dòng):
男孩向女孩提出了一個(gè)無理的要求晰甚,女孩大怒,大喝一聲决帖,一招“大海無量”厕九,被女孩給揍飛了!再次聲明地回,故事不重點(diǎn)扁远,節(jié)奏控制才是我們的重點(diǎn):
//男孩對(duì)女孩說:妹妹快過來!
this._moveAndCall(this._boy, cc.p(this._boy.x, 200), '妹妹快過來刻像!', () => {
//女孩回答
this._moveAndCall(this._gril, cc.p(this._gril.x, 200), '喊我做啥子畅买!', () => {
//男孩對(duì)女孩提出無理要求
this._boy.$Hero.sing('我要親親!', () => {
//女孩大怒
this._gril.$Hero.sing('流氓绎速,看招', () => {
//女孩準(zhǔn)備發(fā)招
this._gril.$Hero.sing('大海無量', () => {
//女孩向男孩發(fā)起攻擊
this._gril.$Hero.attack(this._boy, () => {
//男孩被打暈
this._boy.runAction(cc.rotateBy(2, 1000));
//同時(shí)被打跑了
this._moveAndCall(this._boy, this._boyPt, '不要啊...皮获!', () => {
cc.log('流氓被妹妹揍了!');
})
});
});
})
});
})
});
需要說明一下纹冤,這里的_boy和_gril是兩個(gè)預(yù)制node洒宝,綁定了一個(gè)Hero的魔靈(組件),同時(shí)這個(gè)代碼中使用了uikiller萌京,所以可以直接用$Xxx訪問節(jié)點(diǎn)上的組件(具體細(xì)節(jié)請(qǐng)參考《雷神之錘》)雁歌,node.$xxx與node.getComponent(‘xxx’) 是同樣的功能。
Hero魔靈提供了sing\attcak方法知残,除了必要的參數(shù)外靠瞎,還提供了一個(gè)完成回調(diào),通過這種層層回調(diào),可以嚴(yán)格地控制多人舞步的順序乏盐,代碼排版呈現(xiàn)出">"形佳窑!
2. 面臨大敵
男孩被打飛了,他非常地不甘心父能,經(jīng)過深刻總結(jié)與勤奮修練神凑,準(zhǔn)備再來一次:
男孩利用『乾坤大挪移』輕松化解了女孩的『大海無量』,并轉(zhuǎn)換成了愛心何吝!再次提醒溉委,邏輯控制才是重點(diǎn),請(qǐng)看下面代碼:
this._moveAndCall(this._boy, cc.p(this._boy.x, 200), '妹妹快過來爱榕!', () => {
this._moveAndCall(this._gril, cc.p(this._gril.x, 200), '喊我做啥子瓣喊!', () => {
this._boy.$Hero.sing('我想與你聊聊人生!', () => {
this._gril.$Hero.sing('流氓黔酥,看招', () => {
this._gril.$Hero.sing('大海無量', () => {
cc.director.getScheduler().setTimeScale(0.3);
/**
*注意段代碼:
*在Hero上有一個(gè)onWeaponEvent事件藻三,這里轉(zhuǎn)換攻擊為愛心
*/
this._gril.$Hero.onWeaponEvent = (weapon) => {
let delayTime = cc.delayTime(1);
let delayTime = cc.delayTime(1);
let pt = cc.p(_.random(-200, 200), _.random(-200, 200));
weapon.string = ";";
weapon.node.color = cc.Color.RED;
pt.x += _.random(-200, 200);
pt.y += _.random(-200, 200);
let moveTo = cc.moveTo(1, pt).easing(cc.easeCircleActionInOut(0.5));
weapon.node.runAction(cc.sequence(moveTo, delayTime, cc.removeSelf()));
};
this._boy.$Hero.sing('乾坤大挪移');
this._gril.$Hero.attack(this._boy, () => {
cc.director.getScheduler().setTimeScale(2);
this._moveAndCall(this._gril, this._grilPt, "暈,遇到個(gè)瘋子絮爷!", () => {
this.log('行動(dòng)完畢');
});
});
});
})
});
})
});
上面的代碼越來越多了趴酣,不好意思梨树,看起來可能會(huì)比較累坑夯!這里簡單講解一下Hero.attack,它會(huì)發(fā)射出許多的武器節(jié)點(diǎn)抡四,其實(shí)是用Lable + BFMFont的方案:
這里使用了GlyphDesigner這個(gè)字體成生工具
還需要注意Hero.onWeaponEvent事件函數(shù)柜蜈,用于監(jiān)聽女孩發(fā)出的招數(shù),此處給Label.string設(shè)置了新值指巡,同時(shí)改變節(jié)點(diǎn)的顏色:
//分號(hào)對(duì)應(yīng)了愛心圖案
weapon.string = ";";
//設(shè)置成紅色淑履,在演示中其實(shí)BFM字體用的是白色,這樣可以通過node.color進(jìn)行疊色
weapon.node.color = cc.Color.RED;
為了把女孩的發(fā)招過程演示的更加清晰藻雪,我還特地放慢鏡頭:
//使用setTimeScale函數(shù)進(jìn)行整個(gè)游戲的時(shí)間縮放
cc.director.getScheduler().setTimeScale(0.3);
其中參數(shù)0.3表示放慢到0.3倍的速度秘噪,如果是2則是2倍速。
?男孩這次是有備而來勉耀,悲催的是女孩被包圍的愛心給嚇跑了指煎!
二、 窘境中的思考
男孩百思不得其解便斥,再回頭看看我們的控制代碼至壤!我想聰明的你多半已經(jīng)明白了,我們正踏入了:Call Hell !
1. 地獄之路
call hell枢纠,又稱之為:回調(diào)地獄
由于舞步完成回調(diào)是異步響應(yīng)像街,每一層的回調(diào)都需要依賴上一層的回調(diào)執(zhí)行完成,形成了層層嵌套的關(guān)系,最終造成類似上面的回調(diào)地獄镰绎!
任何舞步都是英雄在一定時(shí)間上的形態(tài)變化脓斩,多個(gè)節(jié)點(diǎn)之間的協(xié)同最核心的是在時(shí)間上的同步與空間上協(xié)調(diào)!
男孩之前也算把迷蹤步給研習(xí)精通了畴栖,也能靈活運(yùn)用逍遙訣俭厚,但面對(duì)流程較長,節(jié)點(diǎn)較多的多人舞步驶臊,總是感覺力不從心挪挤,此刻想起『凌波微步』有言:
每踏出一步,都與內(nèi)力息息相關(guān)关翎,決非單是邁步行走而已扛门,若無內(nèi)功根基之人,將「凌波微步」強(qiáng)行走將起來纵寝,會(huì)造成自絕經(jīng)脈的危境论寨。
男孩之前一直沒有領(lǐng)悟文中之意,此刻一股寒襲來:
每踏出的一步爽茴,難道就是回調(diào)函數(shù)嗎葬凳?
簡單的邁步行走,就是走進(jìn)了一層層回調(diào)室奏?
強(qiáng)行走將起來火焰,不就進(jìn)入了回調(diào)地獄,造成自絕經(jīng)脈的危境胧沫?
2. 心靈感應(yīng)
男孩輾轉(zhuǎn)反側(cè)難以入眠昌简,仔細(xì)回憶著與女孩過招的每一幀,發(fā)現(xiàn)女孩的『大海無量』有些蹊蹺绒怨。大量的Label節(jié)點(diǎn)不斷涌出一個(gè)接著一個(gè)纯赎,幸好女孩只是個(gè)新手,一招『大海無量』施展出來只能算是娟娟細(xì)流南蹂!
回家后女孩心想犬金,自己的『大海無量』從來沒失過手,怎么會(huì)被輕易化解了呢六剥?
“下次讓我再遇到這種人晚顷,一定將他打個(gè)半死!”仗考,女孩一邊想著音同,一邊開始分析其中的破綻:
/**
* 攻擊函數(shù)
* @param {Node} target 要攻擊的目標(biāo)節(jié)點(diǎn)
* @param {Function} cb 攻擊完畢的回調(diào)函數(shù)
*/
attack(target, cb) {
//攻擊關(guān)生的最大節(jié)點(diǎn)數(shù),怪不得威力不大才20個(gè)
let num = 20;
let array = [];
//循環(huán)生成20個(gè)預(yù)制節(jié)點(diǎn)
for(let i = 0; i < num; i++) {
let weapon = cc.instantiate(this.weapon);
//預(yù)制是一個(gè)Label組件秃嗜,隨機(jī)設(shè)置string屬性
weapon.getComponent(cc.Label).string = _.sample(WEAPON);
//添加到父節(jié)點(diǎn)讓它可見
this._weapons.addChild(weapon);
//將所有weapon放入一個(gè)數(shù)組
array.push(weapon);
}
//關(guān)鍵來了:async.eachOfLimit用于異步控制权均,一次做次發(fā)射3動(dòng)作
async.eachOfLimit(array, 3, (weapon, i, cb) => {
//向目標(biāo)target扔出武器
this._throwWeapon(target, weapon, cb);
}, cb);
},
請(qǐng)打起十二分的精神注意async.eachOfLimit函數(shù)顿膨,它正是一記大招:
async.eachOfLimit(array, 3, (weapon, i, callback) => {
...
}, (error) => {
...
});
男孩似夢非夢之中將女孩的一招一式看的清清楚楚,async是異步叽赊,each是遍歷恋沃,limit是并發(fā)控制,遍歷的是array必指。完整詮釋就是囊咏,遍歷array數(shù)組中的元素,一次拿3個(gè)調(diào)用迭代函數(shù)塔橡,當(dāng)3次迭代函數(shù)異步返回梅割,又開始新一輪。
重點(diǎn)是async.eachOfLimit的第三個(gè)參數(shù)葛家,稱之為迭代函數(shù)户辞,迭代函數(shù)的第一個(gè)參數(shù)weapon是array中的一元素,i是weapon在array中的下標(biāo)癞谒,最后一個(gè)callback回調(diào)底燎,因?yàn)橐龅氖枪?jié)點(diǎn)的連綿飛行,當(dāng)一個(gè)節(jié)點(diǎn)飛出一定距離弹砚,調(diào)用callback告訴eachOfLimit一次異步任務(wù)完成双仍。我們這里是一次打出三個(gè)節(jié)點(diǎn),當(dāng)三個(gè)節(jié)點(diǎn)都調(diào)用了callback后桌吃,eachOfLimit繼續(xù)調(diào)用迭代器函數(shù)朱沃,進(jìn)行下一輪的任務(wù)。
當(dāng)array中的所有元素被迭代函數(shù)執(zhí)行完畢后读存,eachOfLimit第四個(gè)參數(shù)會(huì)被響應(yīng)为流,此時(shí)所有任務(wù)完成呕屎。
女孩把『大海無量』在腦子里溫習(xí)過了一遍让簿,她發(fā)現(xiàn)了招數(shù)威力不大的原因:一是節(jié)點(diǎn)數(shù)量較少只有20個(gè),二是并發(fā)一次只有3個(gè)節(jié)點(diǎn)秀睛。
與此同時(shí)尔当,男孩的腦子里就像播放錄象一樣,將女孩的『大海無量』也觀看了一遍蹂安,一字一句椭迎,清晰無比!男孩驚嘆地發(fā)現(xiàn)原來:“async.js就是的『凌波微步』田盈!”
三畜号、凌波微步
男孩讀取到女孩的思考,不知不覺中學(xué)會(huì)了eachOfLimit允瞧,更重要的是他發(fā)現(xiàn)async.js就是『凌波微步』這個(gè)秘密简软,他現(xiàn)在唯一想做的就是擼起袖子開干蛮拔!
請(qǐng)先看解劇情發(fā)展,gif太大效果不好切換成視頻: http://v.youku.com/v_show/id_XMzE3OTg0OTgyNA==.html#paction
(慘了痹升,Markdown不知道怎么插入視頻)
1. 飛鳧若神—async.series
男孩不知從那里藝成歸來(我猜多半是奎特爾星球上)建炫,這次的逼格完全上升了N個(gè)檔次!
async.series([
cb => this._moveAndCall(this._boy, cc.p(this._boy.x, 200), '妹妹快過來疼蛾!', cb),
cb => this._moveAndCall(this._gril, cc.p(this._gril.x, 200), '喊我做啥子肛跌!', cb),
cb => {
this.log('男孩這次開始吟詩了...');
this._boy.$Hero.sing('仿佛兮若輕云之蔽月', cb);
},
cb => {
this.log('女孩,還是同樣的暴脾氣...');
this._gril.$Hero.sing('流氓察郁,看招', cb);
},
cb => this._gril.$Hero.sing('大海無量', cb),
...
]);
男孩對(duì)行云流水的代碼發(fā)出了贊嘆“仿佛兮若輕云之蔽月”衍慎,async.series可以將多個(gè)異步函數(shù)串行執(zhí)行,每一個(gè)函數(shù)都有一個(gè)cb(callback)回調(diào)參數(shù)皮钠,當(dāng)異步動(dòng)作完成需要執(zhí)行下callback回調(diào)西饵,數(shù)組中的下一個(gè)異步函數(shù)接著執(zhí)行!代碼排版不在像之前像個(gè)頂著大肚子油膩的老男人了鳞芙。
可能有人看不明白這里的”=>”眷柔,它被我稱之為一陽指(箭頭函數(shù)),這里為了方便大家原朝,再給一個(gè)老式的寫法:
var self = this;
async.series([
function(cb) {
self._moveAndCall(self._boy, cc.p(self._boy.x, 200), '妹妹快過來驯嘱!', cb);
},
function(cb) {
self._moveAndCall(self._gril, cc.p(self._gril.x, 200), '喊我做啥子!', cb);
},
function(cb) {
self.log('男孩這次開始吟詩了...');
self._boy.$Hero.sing('仿佛兮若輕云之蔽月', cb);
},
function(cb) {
self.log('女孩喳坠,還是同樣的暴脾氣...');
self._gril.$Hero.sing('流氓鞠评,看招', cb);
},
function(cb) {
self._gril.$Hero.sing('大海無量', cb);
}
...
]);
async.series除了可以串行執(zhí)行一個(gè)數(shù)組中的函數(shù)外,還支持對(duì)象作為參數(shù):
async.series({
//男孩說
boySaid: cb => this._moveAndCall(this._boy, cc.p(this._boy.x, 200), '妹妹快過來壕鹉!', cb),
//女孩說
grilSaid: cb => this._moveAndCall(this._gril, cc.p(this._gril.x, 200), '喊我做啥子剃幌!', cb),
//男孩吟詩
boyPoetry: cb => {
this.log('男孩這次開始吟詩了...');
this._boy.$Hero.sing('仿佛兮若輕云之蔽月', cb);
},
//女孩發(fā)怒
grilAngy: cb => {
this.log('女孩,還是同樣的暴脾氣...');
this._gril.$Hero.sing('流氓晾浴,看招', cb);
},
//女孩吟唱準(zhǔn)備發(fā)招
grilSing: cb => this._gril.$Hero.sing('大海無量', cb),
...
});
async.series使用對(duì)象做為參數(shù)负乡,key為舞步名,value必須是異步函數(shù)脊凰,在這個(gè)函數(shù)中執(zhí)行舞步動(dòng)作抖棘。在一段舞步完成之后記得調(diào)用cb回調(diào),告訴async.series當(dāng)前任務(wù)完畢狸涌,請(qǐng)執(zhí)行下一個(gè)任務(wù)切省。
2. 微步生塵—async.eachSeries
繼續(xù)解讀下面的舞步:
async.series([
...接上面series中的代碼...
//女孩發(fā)起攻擊,具體操作封裝在this._grilAttackBoy函數(shù)中
cb => this._grilAttackBoy(cb),
//攻擊完畢帕胆,男孩繼續(xù)吟詩
cb => {
this.log('男孩繼續(xù)吟詩...');
this._boy.$Hero.sing('體迅飛鳧朝捆,飄忽若神', () => {
this._boy.$Hero.sing('凌波微步,羅襪生塵', cb);
});
},
//女孩見狀驚訝懒豹,開始搭話...
cb => {
this.log('對(duì)白....');
//注意eachSeries
async.eachSeries([
{node: this._gril, text:'败脚獭诊杆!「凌波微步」'},
{node: this._boy, text:'妹妹也曉得「凌波微步」?'},
{node: this._gril, text:'有所耳聞何陆,但未見過...'},
{node: this._boy, text:'你想學(xué)嗎晨汹?'},
{node: this._gril, text:'好呀!好呀贷盲!'},
{node: this._boy, text:'請(qǐng)關(guān)注『奎特爾星球』微信公眾號(hào)吧淘这!'},
], (item, cb) => {
item.node.$Hero.sing(item.text, cb);
}, cb);
},
//顯示奎特爾星球二維碼
(cb) => {
this._qr.active = true;
this._qr.runAction(cc.sequence(cc.rotateBy(2, 360*6), cc.callFunc(() => cb())));
}
], () => {
cc.log('舞步結(jié)束');
});
終于與女孩搭上話了!我們將重點(diǎn)聚交在async.eachSeries函數(shù)上:
//女孩見狀驚訝巩剖,開始搭話...
cb => {
async.eachSeries([
{node: this._gril, text:'奥燎睢!「凌波微步」'},
{node: this._boy, text:'妹妹也曉得「凌波微步」佳魔?'},
{node: this._gril, text:'有所耳聞曙聂,但未見過...'},
{node: this._boy, text:'你想學(xué)嗎?'},
{node: this._gril, text:'好呀鞠鲜!好呀宁脊!'},
{node: this._boy, text:'請(qǐng)關(guān)注『奎特爾星球』微信公眾號(hào)吧!'},
], (item, cb) => {
item.node.$Hero.sing(item.text, cb);
}, cb);
}
async.eachSeries的第一個(gè)參數(shù)是一個(gè)數(shù)組贤姆,數(shù)組元素中的內(nèi)容可以是任意類型榆苞。
第二個(gè)參數(shù)是一個(gè)迭代器函數(shù),迭代器函數(shù)的第一個(gè)參數(shù)是之前數(shù)組中的元素霞捡,第二個(gè)參數(shù)是一個(gè)回調(diào)函數(shù)坐漏,這與之前講到的async.eachOfLimit差不多,async.eachOfLimit提供了并發(fā)控制參數(shù)碧信,其實(shí)async.eachSeries就是并發(fā)控制為1的async.eachOfLimit赊琳,一次只拿數(shù)組中的一個(gè)元素交給迭代器函數(shù),形成串行執(zhí)行砰碴。
第三個(gè)參數(shù)是一個(gè)完成回調(diào)躏筏,數(shù)組中的所有元素被迭代器消耗完畢執(zhí)行這個(gè)回調(diào),在我們這里形成了一個(gè)async的嵌套調(diào)用衣式。
async.series([
...
], () => {
cc.log('舞步結(jié)束');
})
async.series的最后一個(gè)參數(shù)寸士,同樣是一個(gè)完成回調(diào),整個(gè)多人舞步華麗結(jié)束碴卧!
結(jié)語
男孩與女孩的演出終于結(jié)束,兩個(gè)菜鳥演員乃正,終于可以退場休息了住册!分享async.js在Cocos中應(yīng)用的想法很早就有了,但一直沒付諸行動(dòng)瓮具,有網(wǎng)友在公眾號(hào)上留言問什么時(shí)候出一篇使用async優(yōu)雅處理動(dòng)畫的教程荧飞,我當(dāng)時(shí)一口就答應(yīng)了凡人。但從《英雄之舞—預(yù)告篇》開始到今天有20多天了,對(duì)此不好意思叹阔,我一拖再拖挠轴,來晚一步請(qǐng)見諒!
async.js教程在網(wǎng)上有很多耳幢,這篇文章算是給不熟悉的人引進(jìn)門岸晦,我這只介紹了async.js的一點(diǎn)皮毛,async除了處理動(dòng)畫以外睛藻,可以處理各種異步的任務(wù)启上,比如連續(xù)的網(wǎng)絡(luò)請(qǐng)求,客戶端的對(duì)話框交互等等店印。
本文的demo演示也準(zhǔn)備好了點(diǎn)擊這里可以預(yù)覽冈在,服務(wù)器是阿里云1核1G1M,水管比較小按摘,加載可能會(huì)有點(diǎn)慢請(qǐng)海涵包券。如果覺得教程對(duì)你有幫助,分享給更多的朋友炫贤,謝謝兴使!
歡迎關(guān)注「奎特爾星球」微信公眾號(hào),有代碼照激、有教程发魄、有視頻、有故事俩垃,一起玩來玩吧励幼!