假設(shè)大家都了解Cocos(3.3rc0),以及用過Cocos Code IDE(1.01)轮洋。這篇文章主要的內(nèi)容是這個游戲的的實現(xiàn)思路制市,具體細節(jié)會暫時(就)跳(是)過(懶)。
游戲總共有四個場景弊予,分別是
我們著重講一下BattleScene這個場景的結(jié)構(gòu)
BattleScene的層次
這個層次還是比較簡單的祥楣。一個Scene加一個Layer,其他元素都是AddChild到Layer上汉柒,包括英雄和怪物误褪。其中比較特殊的是camera,這是一個攝像機對象碾褂,用來控制畫面遠近兽间、視角等,另外uiLayer是掛在camera上的(我們在攝像機跟蹤一節(jié)解釋這個做法)
然后絕大部分邏輯由gameController這個定時器函數(shù)來控制正塌,其中包括碰撞檢測嘀略、攻擊指令、攝像頭跟蹤乓诽、以及游戲主邏輯帜羊。這個定時器函數(shù)每一幀都會被引擎調(diào)用。
最后currentLayer支持觸摸鸠天,這個用來觸發(fā)英雄的特殊技能和控制鏡頭讼育。
碰撞檢測
collisionDectect是gameController內(nèi)第一個被調(diào)用的函數(shù),用來檢查所有游戲中的所有角色稠集。如果角色離的太近奶段,會讓他們’推‘開;如果超過游戲場景邊緣巍杈,會把他們’挪‘回來忧饭。
每一個游戲角色有一個圓形區(qū)域,這個區(qū)域大小由_radius決定筷畦;另外它有重量词裤,由_mass決定;
所有有效的游戲角色都被放入了HeroManager和MonsterMananger內(nèi)鳖宾,所以碰撞檢測的邏輯就是:取出每一個角色吼砂,與剩下的角色進行計算距離,小于兩者半徑之和判定為發(fā)生‘碰撞’鼎文;按兩者中心點的直線方向渔肩,計算應(yīng)該后退的距離,設(shè)置新的位置坐標(由重量決定后退的比例)拇惋。
碰撞檢測結(jié)束后周偎,會順便檢測角色是否超過預(yù)先設(shè)定的場景邊緣位置抹剩,這在isOutOfBound內(nèi)實現(xiàn)。
注意蓉坎,碰撞檢測不會處理已經(jīng)‘死亡’的游戲角色澳眷,碰撞檢測中HeroManager和MonsterManager會移除死亡的游戲角色。
攝像機跟蹤
在我們的游戲中蛉艾,攝像機有兩種跟蹤方式:
第一種跟隨三個主角所在位置自動移動钳踊,同時根據(jù)玩家手指移動稍微改變位置,第二種是游戲角色釋放特殊技能時切換特殊的視角勿侯。
這兩種攝像機跟蹤都在同一個函數(shù)moveCamera內(nèi)實現(xiàn)拓瞪,核心邏輯是通過setPosition3D不斷改變攝像機的位置,同時通過lookAt讓攝像機‘看’向正確的位置助琐。你可以想象一支筆的兩端祭埂,一端是攝像頭位置,一端是它‘看’的位置弓柱。
另外沟堡,由于攝像機位置需要從A點改變到B點,看的位置從a點到b點矢空,我們不能直接setPosition3D(B)和lookAt(b)航罗,而是需要計算從A到B,以及從a到b的差值位置屁药。在游戲中我們通過cc.pLerp(A, B)來計算得到需要逐步移動的位置粥血。
在BattleScene的onTouchMoved函數(shù)內(nèi),玩家通過觸摸改變cameraOffset的值酿箭,攝像機位置會隨這個值改變复亏,于是實現(xiàn)了玩家在限制范圍內(nèi)控制攝像機位置的功能。
最后缭嫡,我們把uiLayer掛在攝像頭上(addChild)缔御,就是由于攝像機位置一直變化。如果放在currentLayer上妇蛀,攝像機一移動耕突,UI界面就看不到了。現(xiàn)在uiLayer會隨著攝像機移動改變位置评架,所以我們就能一直看到UI界面眷茁,這就像你帶了一副眼鏡,不管你怎么轉(zhuǎn)頭纵诞,眼鏡架還是在你鼻梁上上祈。
最后用幾張圖來表示攝像機在3D空間的位置和移動的方式
左圖是引擎默認情況下,攝像機的位置。左圖的綠色紅色箭頭是x軸登刺,藍色箭頭是y軸籽腕,綠色箭頭為z軸。通過閱讀Camera::initDefault源碼塘砸,可以知道节仿,在3D模式下晤锥,攝像機是位于(winSize.width/2, winSize.height/2, getZEye)掉蔬,并向(winSize.width/2, winSize.height/2)方向看。
右圖是游戲中攝像機的位置矾瘾,在運行時女轿,它會根據(jù)英雄當前所在的位置不斷調(diào)整x軸和y軸坐標,而z軸坐標不變壕翩。
UI層
這一層用來顯示游戲角色頭像蛉迹、血量、憤怒值放妈,角色受到攻擊頭像會震動北救,角色死亡后會變成黑白色,可以觸摸角色頭像觸發(fā)特殊技能攻擊敵人芜抒,勝利時顯示成功界面珍策。
值得一說的是,我們把UI層邏輯和游戲邏輯拆分開宅倒,用注冊消息\函數(shù)回調(diào)的方式來解決數(shù)據(jù)傳輸?shù)膯栴}攘宙。
在BattleScene中,我們注冊了BLOOD_MINUS拐迁,ANGRY_CHANGE消息蹭劈,用來接收血量變化、憤怒值變化线召,通過回調(diào)函數(shù)boodMinus铺韧,angryChange,調(diào)用uiLayer改變血量缓淹,憤怒值哈打。
角色類結(jié)構(gòu)
游戲角色類都放在actors文件夾下,它們的關(guān)系如下
每個游戲角色在創(chuàng)建的時候割卖,都擁有一個自身update函數(shù)前酿,運行時每幀調(diào)用一次。這個update函數(shù)負責調(diào)用如下邏輯:
baseUpdate函數(shù)鹏溯,它負責定時執(zhí)行角色的AI邏輯(具體內(nèi)容我們在角色AI一節(jié)解釋)罢维。
stateMachineUpdate函數(shù),它負責根據(jù)目前角色狀態(tài)值讓角色表現(xiàn)對應(yīng)狀態(tài)的動作、動畫肺孵。
movementUpdate函數(shù)匀借,它負責兩件事情,一是在不斷改變自己的位置平窘,讓角色在游戲中移動吓肋;二是改變旋轉(zhuǎn)角度,讓角色面向目標方向瑰艘。
角色的狀態(tài)機
角色的狀態(tài)在GlobalVariables.lua文件中定義是鬼,由下圖幾個狀態(tài)組成
角色的狀態(tài)被stateMachineUpdate控制,不斷的在以上六種狀態(tài)中切換紫新,切換的邏輯如下圖:
walkUpdate的邏輯是:如果角色已經(jīng)有一個目標(角色AI中實現(xiàn))均蜜,如果目標在攻擊范圍內(nèi),就切換到attackMode芒率;如果不在攻擊范圍囤耳,繼續(xù)移動過去。如果沒有目標偶芍,繼續(xù)往右走充择,如果右邊不能再走了,就停在那里發(fā)呆(移動在movementUpdate里實現(xiàn))匪蟀。
attackUpdate的邏輯是:每次攻擊需要花一定時間椎麦,首先判斷是否到了下一次攻擊的時間;如果是萄窜,根據(jù)概率發(fā)出普通攻擊或者特殊攻擊铃剔,然后播放攻擊動畫。特殊攻擊的時候做三件事情查刻,一键兜、除了發(fā)技能的角色意外的游戲畫面全部暗下來(通過發(fā)送SPECIAL_PERSPECTIVE消息,調(diào)用回調(diào)函數(shù)實現(xiàn))穗泵;二普气、讓攝像頭移動,形成特寫效果佃延;三慢哈、通過setTimeScale降低scheduler的速度浑吟,形成慢動作效果喷面。此外無論是特殊攻擊還是普通攻擊扣蜻,會生成攻擊環(huán)(這個在攻擊指令一節(jié)中說明)。
knockingUpdate的邏輯是:每次被擊中需要一定時間播放受擊動畫尺棋,首先判斷被攻擊動畫是否播放結(jié)束封锉;如果是,判斷目標是否在自己的攻擊范圍內(nèi),如果是成福,進入attackMode碾局,如果不是,切換到walkMode奴艾。
角色AI
角色的AI是被baseUpdate驅(qū)動的净当,每個角色有自己的_AIFrequency。所以AI的基本邏輯是:首先判斷是否到了下一次執(zhí)行AI的時間蕴潦,如果是像啼,執(zhí)行AI。
AI邏輯主要是尋找攻擊目標品擎,如果沒有找到目標埋合,根據(jù)當前狀態(tài)切換到walkMode或者idleMode;如果找到目標萄传,判斷目標是否在自己的攻擊范圍內(nèi),如果是蜜猾,切換到attackMode秀菱,如果不是,切換到walkMode去靠近目標蹭睡。
攻擊指令
攻擊指令是一個獨立類衍菱,它的baseClass是BasicCollider,結(jié)構(gòu)如下:
其中的sp變量就是游戲中我們看到的冰錐肩豁、冰球脊串、箭支,分別是一個sprite或者sprite3D對象實例清钥。
但是真正起到攻擊作用的琼锋,是BasicCollider的攻擊區(qū)域,它可能是一個扇形祟昭、圓形或者圓環(huán)缕坎,如圖
minRange決定攻擊最小半徑,可以為0篡悟;maxRange大于minRange谜叹;angle決定了扇形圓心角,值的范圍在(0, 360]搬葬;facing決定方向荷腊;這幾個屬性決定了攻擊區(qū)域的大小。例如A和B急凰,唯一的差別是A的minRange=0女仰,當目標在A或者B區(qū)域內(nèi),都會受到傷害。
攻擊指令在創(chuàng)建時董栽,就被加入到AttackManager里(BasicCollider:initData)码倦。而solveAttacks(還記得在哪里調(diào)用嗎)中會循環(huán)遍歷AttackManager,每次取出一個攻擊指令锭碳,判斷它與游戲角色是否發(fā)生碰撞袁稽,如果沒有,就繼續(xù)與其他游戲角色比較擒抛;如果有推汽,調(diào)用onCollide,播放攻擊特效歧沪、聲效歹撒,調(diào)用被攻擊角色的hurt函數(shù);在循環(huán)結(jié)束前诊胞,從AttackManager移除已經(jīng)無效的攻擊指令(無效是指curDuration > duration)暖夭。
攻擊指令中還有兩種特殊指令,一種是可以移動的攻擊指令撵孤,比如飛行的冰球迈着、箭支,一種是DOT(Damage over time邪码,在一段時間內(nèi)不斷對目標造成傷害)裕菠,只有這種指令才有curDOTTime和DOTTimer屬性。在solveAttacks的循環(huán)結(jié)束前闭专,有一個attack:onUpdate函數(shù)被調(diào)用奴潘,這個函數(shù)會不斷改變攻擊指令的位置,以實現(xiàn)‘飛行’的效果影钉;另外画髓,這函數(shù)會改變curDOTTime的值,這樣斧拍,當curDOTTime > DOTTimer時雀扶,onCollide就會對處于攻擊范圍內(nèi)的目標產(chǎn)生傷害。
游戲主邏輯
主邏輯靠GameMaster:update驅(qū)動肆汹,這個函數(shù)在BattleScene中g(shù)ameController中被調(diào)用愚墓。
游戲主邏輯包含二個功能:第一,創(chuàng)建英雄和怪物昂勉;第二浪册,在合適的時機顯示怪物和界面。
值得一說的是岗照,在游戲進行中村象,創(chuàng)建怪物會導致游戲卡頓笆环,所以我們在創(chuàng)建的時候就把所有的怪物放在了對應(yīng)的pool里,同時也addChild到了currentLayer上厚者,同時設(shè)置為隱藏躁劣;在游戲進行中,我們才把pool里的怪物拿出來库菲,放到MonsterManager里面账忘。