使用Cocos2dx-JS開發(fā)一個飛行射擊游戲

一、前言

筆者閑來無事宙帝,某天github閑逛,看到了游戲引擎的專題募闲,引起了自己的興趣步脓,于是就自己搗騰了一下Cocos2dx-JS。
由于是學(xué)習(xí)浩螺,所謂紙上得來終覺淺靴患,只是看文檔看sample看demo,并不會讓自己有多大的提升要出,于是一開始就計劃寫一個小游戲鸳君,以作為自己完成這個階段學(xué)習(xí)的一個標(biāo)志,也算是目標(biāo)導(dǎo)向吧患蹂。
完整源碼移步Github: https://github.com/RogerKang/JasonAmbition
Online Demo: http://www.rogerkang.site:3000 (由于需要WebGL的支持或颊,請使用較新版本的Chrome或者Firefox打開)
本游戲的圖片素材以及特效素材來自騰訊的《全民飛機大戰(zhàn)》,僅供學(xué)習(xí)參考传于,請勿用于其他用途囱挑。
筆者水平有限,本身也不是專業(yè)游戲開發(fā)者沼溜,如有錯漏平挑,還請多多指教。

二系草、Cocos2dx-JS

Cocos2dx的介紹請移步官網(wǎng): http://www.cocos2d-x.org/
目前Cocos2dx支持3種開發(fā)語言:C++通熄、Lua、 JavaScript找都,其中C++和Lua是使用較為廣泛開發(fā)語言唇辨,而JS的支持最早是作為獨立的項目進(jìn)行開發(fā)的,在3.7之后并入了Cocos2d-x的主repository檐嚣,成為了官方支持的正式開發(fā)語言助泽。
使用JS開發(fā)Cocos2dx游戲的優(yōu)點:

  1. 跨平臺啰扛,是唯一可以到達(dá)web端的分支,同時也支持編譯成iOS和Android原生APP
  2. 開發(fā)效率高嗡贺,開發(fā)難度相對較低

缺點:

  1. 運行效率相對較低
  2. JS-binding在功能和文檔以及開發(fā)者的使用經(jīng)驗上仍然落后于C++和Lua

閱讀本文之前隐解,你最好具備以下知識基礎(chǔ):

  • 基本的JavaScript技能
  • Cocos2dx-JS的基本知識

三、游戲簡介

游戲菜單
游戲過程

這個游戲是一個簡單的飛行射擊游戲诫睬,主要使用鼠標(biāo)點擊頁面(移動設(shè)備就是手指點擊屏幕)控制飛機的飛行煞茫,從而實現(xiàn)攻擊敵人,回避攻擊摄凡,獲取增強效果物品等常見的行為续徽。
具體細(xì)節(jié)的可以到Online Demo去看一看。

四亲澡、整體設(shè)計

1. Game Scene

游戲總共有3個scene钦扭;Menu、About床绪、Game客情,可以理解為不同的功能放在不同的場合之中,其中最重要的是菜單(Menu)場景和游戲(Game)場景癞己,又以游戲場景最為核心膀斋,當(dāng)我們控制飛機進(jìn)行游戲的時候,實際上就是在于游戲場景在進(jìn)行交互痹雅,此時我們看到的飛機仰担,敵人,子彈绩社,特效摔蓝,背景等等一切,都是游戲場景在承載铃将。

2. Game Layer

Layer的層次比Scene低一級项鬼,通常是一個scene會包含一個或者多個layer(當(dāng)然也可以一個都沒有)。
通常不同的內(nèi)容可以放在不同的layer中劲阎,比如游戲場景里面绘盟,就使用了一個獨立的layer來顯示飛機的血量和得分。
本游戲只有3個layer悯仙,GameLayer龄毡,GameInfo 和 GameOverLayer,顧名思義锡垄,一個作為游戲場景的承載沦零,一個顯示飛機的血量和得分,一個在飛機死亡的時候顯示相關(guān)的信息货岭。

3. Game Object

使用了面向?qū)ο蟮脑O(shè)計來組織游戲的各個對象:
Plane:玩家控制的飛機
Enemy:敵人的基類路操,提供了公共行為接口疾渴,包括回收與重用的接口
Bullet:子彈的基類,提供了公共行為接口屯仗,包括回收與重用的接口
./Enemies:擴展Enemy基類搞坝,不同的敵人有自己的行為
./Bullets:擴展Bullet基類,不同的子彈的效果自己定義
./Boss:擴展Enemy基類魁袜,實際上就是特殊的敵人作為Boss登場

4. Manager

設(shè)計了若干個manager來組織游戲的各個方面:
BulletManager:每幀更新子彈的位置桩撮,檢測碰撞和傷害,回收超出屏幕的子彈
EnemyManager:控制敵人和Boss的產(chǎn)生峰弹,檢測敵人與飛機的碰撞店量,控制敵人的攻擊,回收敵人
ItemManager:控制游戲中隨機增強item的產(chǎn)生鞠呈,并應(yīng)用其效果
MusicManager:音效的控制
ParticleManager:特效的控制融师,包括被攻擊的特效,item的特效蚁吝,Boss特殊攻擊的特效
StageManger:控制游戲階段诬滩,比如常規(guī)階段,boss階段等等

五灭将、啟動游戲

啟動游戲,我們需要設(shè)置game stage的配置以及初始化各個manager后控,并且以Menu Scene作為第一個scene:

var defaultStage = [{name:"normal", duration: 20}, {name:"mega", duration: 20}, {name:"boss", duration:100000}]; //game stage配置

cc.game.onStart = function(){
 cc.view.adjustViewPort(false);
 cc.view.setDesignResolutionSize(512, 768, cc.ResolutionPolicy.SHOW_ALL); // 設(shè)置分辨率
 cc.view.resizeWithBrowserSize(true);
 //load resources
 cc.LoaderScene.preload(g_resources, function () {
 cc.game.musicManager = new MusicManager(); //初始化各個manager
 cc.game.bulletManager = new BulletManager();
 cc.game.enemyManager = new EnemyManager();
 cc.game.itemManager = new ItemManager();
 cc.game.particleManager = new ParticleManager();
 cc.game.stageManager = new StageManager(defaultStage);
 cc.director.runScene(new MenuScene()); //首先啟動Menu Scene
 cc.director.setDisplayStats(false);
 }, this);
};
cc.game.run(); //運行游戲

var trace = function() {
 //cc.log(Array.prototype.join.call(arguments, ", "));
};

六庙曙、Menu Scene

Menu場景只需要一個背景圖片,一段BGM浩淘,以及一個按鈕開始游戲即可

var MenuScene = cc.Scene.extend({
 ctor:function(){
 this._super();
 var layer = new cc.Layer();
 this.addChild(layer);
 var winSize = cc.director.getWinSize();
 cc.game.winSize = winSize;
 var bgImage = new cc.Sprite("res/bg.png"); //背景圖片
 bgImage.x=winSize.width/2;
 bgImage.y = winSize.height/2;
 layer.addChild(bgImage);
 var gameName = new cc.LabelTTF("Jason's Ambition", "Arial",58, cc.Size(500, 200), cc.TEXT_ALIGNMENT_CENTER, cc.VERTICAL_TEXT_ALIGNMENT_TOP); //標(biāo)題文本
 gameName.x = winSize.width/2;
 gameName.y = winSize.height+100;
 var gameNameAction = cc.moveTo(3, cc.p(winSize.width/2, winSize.height-200)); //讓標(biāo)題動起來
 gameName.runAction(gameNameAction);
 layer.addChild(gameName);
 this.scheduleOnce(function(){
 this.showMenu();
 }.bind(this),3); //3秒之后顯示按鈕
 cc.game.musicManager.playStarWar();      //播放BGM
 },
 showMenu:function(){
 var startGameButton = new cc.MenuItemImage("res/startgame2.png", "res/startgame2.png", this.startGame, this); //創(chuàng)建按鈕并綁定回調(diào)事件
 var menu = new cc.Menu(startGameButton);
 this.addChild(menu);
 },
 startGame: function(){
 trace('Game start..');
 cc.director.runScene(new AboutScene()); //點擊按鈕后跳到About Scene
 }
});

在這個場景里面捌朴,我們加載了背景圖片,播放了BGM张抄,設(shè)置了會移動的標(biāo)題砂蔽,并在標(biāo)題停止移動后顯示開始按鈕,在按鈕上綁定回調(diào)事件署惯,點擊后跳轉(zhuǎn)到About Scene左驾。

七、About Scene

About Scene只是一段文本极谊,3秒后跳轉(zhuǎn)到游戲場景诡右。

var AboutScene = cc.Scene.extend({
 ctor:function(){
 this._super();
 var winSize = cc.director.getWinSize();
 var layer = new cc.Layer();
 this.addChild(layer);
 var aboutText = "Jason, a shameless man.\nHe want to kidnap all girls around the earth.\n";
 aboutText += "Hero, you have to stop him!";
 var aboutLabel = new cc.LabelTTF(aboutText, "Arial",24, cc.Size(500, 200), cc.TEXT_ALIGNMENT_CENTER, cc.VERTICAL_TEXT_ALIGNMENT_TOP);
 aboutLabel.x = winSize.width/2;
 aboutLabel.y = winSize.height-300;
 layer.addChild(aboutLabel);
 this.scheduleOnce(function(){
 var gameScene = new GameScene(); 
 cc.director.runScene(gameScene); //啟動游戲場景,游戲開始
 cc.game.gameScene = gameScene;
 },3);
 }
});

八轻猖、Game Scene

Game Scene的設(shè)計是最為復(fù)雜的帆吻,因為是游戲交互的場所,所以其之上需要處理的事情也最多咙边。
我們需要一點一點說猜煮。
GameScene的執(zhí)行流程:(簡書不支持流程圖次员,有興趣的同學(xué)可以把下面的代碼復(fù)制到支持flowchart流程圖的Markdown編輯器里面查看,注意使用flow代碼段包裹王带,例如https://maxiang.io)

st=>start: Start Game Scene
end=>end: End Game
addBackGround=>operation: Add scrolling background
addPlane=>operation: Add plane
listenClick=>operation: Listen to click operation
continueGame=>condition: Plane dead or Boss dead?
addEnemy=>operation: Add new enemies
enemyAttack=>operation: Enemy attack
bulletReuse=>operation: Reuse out bullets
bulletHit=>operation: Check collision between planes and bullets
bulletUpdate=>operation: Update bullets' action
enenmyCollide=>operation: Check the collision between enemies and plane
enemyOut=>operation: Reuse outing enemies
updateInfo=>operation: Update game information
genenrateItem=>operation: Generate items
itemCollide=>operation: Check the collision between items and plane
allyEffect=>operation: Apply items' effect to plane
planeBlood=>condition: Plane's dead?
gameOver=>operation: Game Over

st->addBackGround->addPlane->listenClick->continueGame
continueGame(yes)->addEnemy->enemyAttack->bulletReuse->bulletHit->bulletUpdate->enenmyCollide->enemyOut
enemyOut->updateInfo->genenrateItem->itemCollide->allyEffect->planeBlood
planeBlood(yes)->gameOver
planeBlood(no)->continueGame
continueGame(no)->gameOver
gameOver->end

1. 幀

首先解釋幀的概念淑蔚。
對于一個游戲而言,連貫的畫面其實是有快速切換的幀造成的辫秧,而每一幀之間會有細(xì)小的變化束倍,連續(xù)播放幀就會形成動作。而幀之間的變化盟戏,就是我們的代碼需要計算和處理的绪妹。
簡而言之,如果你需要一個物體移動到某個地方柿究,那你的代碼就應(yīng)該在每一幀里面對他的坐標(biāo)進(jìn)行一些改變邮旷,從而實現(xiàn)移動這個效果(當(dāng)然,任何一個優(yōu)秀的游戲引擎蝇摸,這種基本的動畫是不需要我們自己去做幀間的處理的婶肩,我們只需要調(diào)用一次,這個效果就會在它所進(jìn)行的時間內(nèi)在每幀進(jìn)行更新)貌夕。

在Scene的ctor函數(shù)里面調(diào)用this.scheduleUpdate 方法律歼,就會使用默認(rèn)的update方法去進(jìn)行幀間計算,我們在update方法里面需要做的事情:

  • 更新飛機的位置
  • 更新敵人的位置
  • 更新子彈的位置
  • 更新item的位置
  • 判斷飛機與敵人啡专、子彈险毁、item的碰撞,并應(yīng)用效果
  • 等等

其中们童,實際上我們只需要真的去在每一幀都處理的只有“更新子彈的位置” 以及 “判斷飛機與敵人政勃、子彈十性、item的碰撞汞扎,并應(yīng)用效果”其余的都只需要在他們真的需要更新的時候聲明一次action柠傍,之后的每幀,cocos都會幫我們?nèi)ヒ苿悠氚澹恍枰覀冊偃ビ嬎愀脑趺匆苿印?br> 如果說“判斷飛機與敵人吵瞻、子彈、item的碰撞甘磨,并應(yīng)用效果”的處理是理所當(dāng)然的听皿,那么為什么“更新子彈的位置”不能交給cocos去處理?事實上大多數(shù)的子彈的位置也都是有cocos去應(yīng)用action宽档,我們只需要在子彈發(fā)生的時候告訴cocos接下來怎么去移動子彈就可以了尉姨,但是有例外的情況:追蹤彈。它的移動和敵人相關(guān)吗冤,因此在敵人的位置又厉、存活發(fā)生變化的時候可能需要改變它的移動九府,這個時候就需要我們在幀間進(jìn)行處理:計算出新的移動方向,告訴cocos取消之前的action覆致,應(yīng)用新的action侄旬。

update函數(shù)的執(zhí)行時間與游戲的幀率是息息相關(guān)的,因為游戲的每一幀都是根據(jù)這個函數(shù)的計算結(jié)果來產(chǎn)生的煌妈,如果它的執(zhí)行時間太長儡羔,就會導(dǎo)致幀率下降。
舉個例子璧诵,如果update的執(zhí)行時間為0.1秒汰蜘,那么這個游戲最高也只可能是10fps,因為在1秒內(nèi)它最多執(zhí)行10次之宿,給出10次的結(jié)果族操,從而cocos也只能由此渲染10幀的畫面。

總而言之比被,如果你想實現(xiàn)什么效果色难,那就在update function里面計算這個效果在每一幀里面的作用,然后讓cocos去把幀渲染出來等缀,聯(lián)合起來就可以實現(xiàn)你要的效果枷莉。

2. 游戲背景

首先,作為一個飛行射擊游戲尺迂,我們需要一個可以滾動的背景依沮。自然,準(zhǔn)備一張高度足夠的圖片枪狂,讓它從上往下勻速下移是可以實現(xiàn)這個效果的,但是實際上應(yīng)該不會有人會這樣做宋渔。
在這里州疾,實際上我們只需要一張圖片,這張圖片剛好可以覆蓋整個游戲的可是窗口就可以了:


游戲背景圖

如果你仔細(xì)觀察皇拣,你會發(fā)現(xiàn)严蓖,這張圖片的上面和下面是可以接合的,也就是說氧急,如果你在這張圖片的正上方再放一張同樣的圖片颗胡,你會發(fā)現(xiàn)這兩張圖片的景色、河流都是剛好接合起來吩坝,看不出是兩種圖片毒姨,而像是一張圖片。
事實上钉寝,我們正式利用這樣的圖片來實現(xiàn)背景的無限滾動弧呐。
我們只需要在一開始就放一張圖片鋪滿顯示窗口闸迷,然后在窗口的正上方再放一張一樣的圖片,讓它們同時勻速往下移動俘枫,等到第一張圖片完全離開窗口腥沽,而第二種剛好鋪滿窗口的時候,在下一幀恢復(fù)它們的初始位置鸠蚪,不斷重復(fù)這個過程今阳,那么看起來背景就是在不斷的移動。

 _scrollBackground:function(){

 this.bgImage1.y -= Math.ceil(this.winSize.height*0.001);    //下移兩張圖片的位置
 this.bgImage2.y -= Math.ceil(this.winSize.height*0.001);

 if(this.bgImage1.y<-(this.winSize.height/2)){        //當(dāng)?shù)谝粡垐D片的y小于負(fù)的二分之一屏幕高度茅信,就是它剛好離開視窗的時候盾舌,此時恢復(fù)兩張圖片的初始位置

 this.bgImage1.y = this.winSize.height/2;      //第一張置于中間
 this.bgImage2.y = this.winSize.height/2+this.winSize.height;    //第二張放著第一張的上面

 }

 }

3. 添加/移動 飛機

飛機本身只是一個Sprite:

var Plane = cc.Sprite.extend({
 image:null,
 bullet:[],
 status:null,
 blood:5000,
 layer:null,
 gameScene:null,
 shotInterval:200, 
 allowShot:true,
 allowMissile:true,
 allowUltra:true,
 hitEffect:null,
 addedEffects:[],
 laserHit:true,
 ctor:function(gameScene, layer){
 this._super("res/main_plane.png"); //初始化飛機的圖片
 this.gameScene = gameScene;
 this.layer = layer;
 this.addedEffects = [];
 this.allowShot = true;
 this.allowMissile = true;
 this.allowUltra = true;
 this.laserHit = true;
 }
}

在Game Scene中添加飛機:

 var plane = new Plane(this, layer);
 plane.scale = 0.5;
 this.targetX = plane.x = winSize.width/2;
 this.targetY = plane.y = winSize.height/2;
 layer.addChild(plane,7);    //給game scene的layer添加飛機
 this.plane = plane;    

這樣在game scene上就會出現(xiàn)飛機的圖片,但是僅僅只是這樣是不夠的汹押,我們需要這個飛機可以根據(jù)我們的點擊進(jìn)行移動矿筝,原理也很簡單,就是獲取點擊的坐標(biāo)棚贾,然后讓飛機移動過去窖维,這里我們需要監(jiān)聽兩個事件:

  • Touch
  • MouseDown

在game scene里監(jiān)聽事件:

 if("touches" in cc.sys.capabilities){
 cc.eventManager.addListener({
 event: cc.EventListener.TOUCH_ONE_BY_ONE,
 onTouchBegan: this._onTouchBegan.bind(this)    //處理觸摸的回調(diào)
 }, this);
 } else {
 cc.eventManager.addListener({
 event: cc.EventListener.MOUSE,
 onMouseDown: this._onMouseDown.bind(this)    //處理鼠標(biāo)點擊的回調(diào)
 }, this);
 }

在回調(diào)里使用點擊的坐標(biāo)讓飛機移動:

 _onTouchBegan:function(touch, event){
 var clickX = touch.getLocation().x;
 var clickY = touch.getLocation().y;
 this.targetX = clickX;
 this.targetY = clickY;
 var speedo = Math.sqrt(Math.pow(clickX-this.plane.x,2)+Math.pow(clickY-this.plane.y,2))/300;
 var moveAction = cc.moveTo(speedo, cc.p(clickX, clickY));
 if(this.action!=null)
 this.plane.stopAllActions();
 this.plane.runAction(moveAction);
 this.action = moveAction;
 },
 _onMouseDown: function(event){
 var clickX = event.getLocationX();
 var clickY = event.getLocationY();
 this.targetX = clickX;
 this.targetY = clickY;
 var speedo = Math.sqrt(Math.pow(clickX-this.plane.x,2)+Math.pow(clickY-this.plane.y,2))/300;
 var moveAction = cc.moveTo(speedo, cc.p(clickX, clickY));
 if(this.action!=null)
 this.plane.stopAllActions();
 this.plane.runAction(moveAction);
 this.action = moveAction;
 }

這樣,我們點哪里妙痹,飛機就會移動到哪里铸史。
但是此時的飛機只能夠移動,并不能攻擊怯伊。

4. 添加/管理敵人

考慮到敵人可能有多種多樣琳轿,每種敵人會有自己的子彈、運動軌跡耿芹,所以我們先定義一個基類崭篡,里面定義了敵人的通用接口,具體的實現(xiàn)在每個敵人的之類里面完成吧秕。

var Enemy = cc.Sprite.extend({
 bullets:null,
 scene:null,
 layer:null,
 score:null,
 blood:null,
 explosionHarm:null,
 //ctor:function(image){
 //
 // this._super(image);
 //
 //
 //},
 appear:function(){},          //敵人出場
 attack:function(){},          //攻擊
 explode:function(){},        //爆炸
 hurt:function(){},              //被攻擊受到傷害
 move:function(){},          //敵人移動
 reuse:function(scene, layer){        //reuse和unuse方法用于敵人的緩存回收琉闪,這部分將獨立說明
 trace("reuse enemy:");
 this.ctor(scene, layer);
 },
 unuse:function(){
 this.layer.removeChild(this);
 }
});

Alpha(一個Enemy的子類,是一個具體的敵人)的部分實現(xiàn):


var Alpha = Enemy.extend({
 image:"res/enemy/alpha.png",
 allowShot:true,
 blood:null,
 score:100,
 explosionHarm:100,
 ctor:function(scene, layer){      //初始化Alpha砸彬,并調(diào)用appear方法颠毙,使得敵人在游戲場景中登場
 this.allowShot = true;
 this.scene = scene;
 this.gameScene = this.scene;
 this.layer = layer;
 this._super(this.image);
 this.blood = 50;
 this.threshold = 50;
 this.appear();
 layer.removeChild(this);
 if(scene.enemies.indexOf(this)!=-1)
 scene.enemies.splice(scene.enemies.indexOf(this), 1);
 layer.addChild(this, 2);
 scene.enemies.push(this);       //用一個數(shù)組存儲所有的敵人,在之后的攻擊判定砂碉、清理回收等會用到
 }蛀蜜,
 appear:function(){                 //登場,在可是窗口的上方的一個隨機位置里面出現(xiàn)
 this.scale = 0.5;
 this.x = Math.round(Math.random()*cc.game.winSize.width);
 this.y = cc.game.winSize.height+100;
 this.move();                      //登場后移動增蹭,移動到可視窗口下方的某個隨機位置

 }滴某,
 move:function(){
 var moveAction = cc.moveTo(12, cc.p(Math.random()*cc.game.winSize.width,-400));
 this.runAction(moveAction);        //移動敵人
 }
 }

這樣,如果我們創(chuàng)建一個Alpha的實例,那么它就會出現(xiàn)在游戲場景壮池,并且從游戲窗口的上方的某個位置勻速地向下移動偏瓤。

EnemyManager

為了更好地管理敵人的生成和回收,我們創(chuàng)建了一個enemyManager來管理敵人的添加椰憋、移除回收厅克、碰撞檢測:

var EnemyManager = cc.Class.extend({
 allowAddNewEnemy: true,
 privateSprite:null,
 enemyList:[
 Alpha,
 Beta,
 Gamma
 ],
 enemyFrequency:1,
 ctor: function () {
 this.privateSprite = new cc.Sprite();
 this.allowAddNewEnemy = true;
 },
 addEnemy: function (scene, layer, frequency) {      //負(fù)責(zé)添加新敵人
 if (this.allowAddNewEnemy == false)
 return;
 //new Alpha(scene, layer);
 //trace("New enemy");
 this.allowAddNewEnemy = false;

 var enemyType = this.enemyList[new Date().getTime() % this.enemyList.length];
 if(cc.pool.hasObject(enemyType)){
 cc.pool.getFromPool(enemyType, scene, layer);
 trace("reuse enemy");
 }else{
 new enemyType(scene, layer);
 }
 scene.scheduleOnce(function () {
 trace("allow new enemy");
 this.allowAddNewEnemy = true;
 }
 .bind(this),this.enemyFrequency);
 },
 addBoss : function(scene, layer){       //添加BOSS
 new Jason(scene, layer);
 },
 removeEnemy:function(scene, layer, enemy, index){      //從場景里面刪除一個敵人
 scene.score+=enemy.score;
 layer.removeChild(enemy);
 scene.enemies.splice(index, 1);
 cc.pool.putInPool(enemy);
 },
 enemyAttack:function(scene){                              //命令所有敵人進(jìn)行一次攻擊
 for(var i =0;i<scene.enemies.length;i++){
 scene.enemies[i].attack();
 }
 },
 _collide:function(x1, y1, x2, y2, threshold){              //碰撞檢測
 if((Math.pow((x1-x2),2)+Math.pow((y1-y2),2))>Math.pow(threshold, 2))
 return false;
 else
 return true;
 },
 collisionCheck:function(scene, layer, plane){        //如果敵人受到攻擊,且血量低于0橙依,讓敵人爆炸证舟,同時回收敵人
 var enemies = scene.enemies;
 for(var i = 0;i<enemies.length;i++){
 var checkEnemy = enemies[i];
 if(this._collide(checkEnemy.x, checkEnemy.y, plane.x, plane.y, checkEnemy.threshold) == true){
 plane.blood -= checkEnemy.explosionHarm;
 scene.score += checkEnemy.score;
 checkEnemy.explode();
 this.removeEnemy(scene, layer, checkEnemy, i);
 i--;
 }
 }
 },
 outEnemyCheck : function(scene, layer){      //如果敵人飛出了游戲場景,也回收敵人
 var enemies = scene.enemies;
 for(var i = 0;i<enemies.length;i++){
 var checkEnemy = enemies[i];
 if(checkEnemy.x>-200 && checkEnemy.x<cc.game.winSize.width+200 && checkEnemy.y>-200 && cc.game.winSize.height+200)
 ;
 else {
 trace("unuse out ennemy");
 this.removeEnemy(scene, layer, checkEnemy, i);
 i--;
 }
 }
 }
});

5. 子彈系統(tǒng)

飛機與敵人都可以發(fā)射子彈窗骑,我們需要解決的問題是:

  • 子彈的生成女责、發(fā)射(位置,角度)创译、速度抵知、傷害,動畫
  • 子彈與飛機/敵人的碰撞檢測
  • 子彈的回收

Bullet基類

與Enemy類似地软族,基類只定義了接口以及共同的方法:

var Bullet = cc.Class.extend({
 BulletOwner:null,
 type:null,
 plane:null,
 layer:null,
 action:null,
 bullets:null,
 scene:null,
 ctor:function(type, plane, layer, scene){        //根據(jù)給出的type初始化bullet
 this.type = type;
 this.plane = plane;
 this.layer = layer;
 this.scene = scene;
 this.bullets = [];
 this._shot();                            //初始化完畢后馬上發(fā)射子彈刷喜,_shot方法應(yīng)該在子彈之類里面具體實現(xiàn)
 },
 _validate:function(){            //檢測子彈是否離開了可視窗口
 var valid = false;
 for(var i =0;i<this.bullets.length;i++){
 var childBullet = this.bullets[i];
 if(childBullet.x>=-20 && childBullet.x<=cc.game.winSize.width+20 && childBullet.y>=0 && childBullet.y<=cc.game.winSize.height+20){
 valid = true;
 break;
 }else{
 valid = false;
 //break;
 }
 }
 return valid;
 },
 reuse:function(type, plane, layer, scene){        //reuse和unuse用于回收、重新利用子彈
 this.ctor(type, plane, layer, scene);
 },
 unuse:function(){
 for(var i =0;i<this.bullets.length;i++){
 this.bullets[i].stopAllActions();
 this.layer.removeChild(this.bullets[i]);
 }
 },
 _shot:function(){}
});

BasicBullet

BasicBullet是一個具體實現(xiàn)的子彈之類立砸,設(shè)置了子彈的圖片掖疮、數(shù)量、發(fā)射位置颗祝,實現(xiàn)了_shot方法:

var BasicBullet = Bullet.extend({
 BulletType:{
 "basic" : "res/bullet/bullet_basic.png"     //子彈的圖片
 },
 BulletLocation : {
 "basic" : [{x:0, y:0}]                  //數(shù)組的元素數(shù)量代表子彈的數(shù)量浊闪,x和y坐標(biāo)代表某個子彈的發(fā)射位置
 },
 BulletOwner:"enemy",
 harm : 5,                               //子彈的傷害
 _shot:function(){                        //_shot方法的作用:創(chuàng)建子彈Sprite,添加到scene中螺戳,調(diào)用runBulletCustomAction使得子彈開始運動
 for(var i =0;i<this.BulletLocation[this.type].length;i++){
 var bulletLocation = this.BulletLocation[this.type][i];
 var newBullet = new cc.Sprite(this.BulletType[this.type]);
 newBullet.scale = 0.15;
 newBullet.x = this.plane.x+bulletLocation.x;
 newBullet.y = this.plane.y+bulletLocation.y;
 newBullet.harm = this.harm;
 this.runBulletCustomAction(newBullet);
 this.layer.addChild(newBullet, 1);
 this.bullets.push(newBullet);
 }
 this.layer.bullets.push(this);
 },
 runBulletCustomAction:function(newBullet){   //實現(xiàn)了每種子彈自己的運動方式搁宾,不同的子彈的不同行為在這里定義
 var action = cc.moveTo(4, cc.p(newBullet.x, -(cc.director.getWinSize().height+300)));
 var rotation = cc.rotateBy(10,3600, 3600);
 newBullet.runAction(cc.spawn(action,rotation));
 }
});

實際的子彈種類會有多種,每個子彈的飛行軌跡也都各有不同倔幼。BasicBullet的飛行軌跡從初始化就已經(jīng)決定了盖腿,以后不再改變,直到發(fā)生碰撞或者離開窗口被回收凤藏。
但是也可以實現(xiàn)更復(fù)雜的飛行軌跡,比如跟蹤彈(MissileBullet)就會跟蹤離它最近的敵人的位置堕伪,這個子彈的運動就需要每幀更新):

MissileBullet

var MissileBullet = Bullet.extend({
updatePerFrames:true,      //是否需要每幀更新揖庄,對于MissileBullet來說是true
allowSwitchEnemy:false,   //是否允許在原追蹤敵人毀滅后更換瞄準(zhǔn)目標(biāo)
/* ... */
 aimedEnemy:function(bulletX, bulletY, oldX, oldY, bullet){        //瞄準(zhǔn)敵人,每幀都會計算當(dāng)前離追蹤彈最接近的敵人是哪一個欠雌,在allowSwitchEnemy為true時會在敵人摧毀后重新計算蹄梢,否則將會沿著之前的軌跡方向飛行
 var enemyX = 0;
 var enemyY = 0;
 var foundEnemy = false;
 if(this.allowSwitchEnemy == true || bullet.lockEnemy == false ) {
 if (this.scene.enemies != null && this.scene.enemies.length > 0) {
 var enemyList = this.scene.enemies;
 var leastDistance = 1000000000;
 var leastX = bulletX;
 var leastY = cc.game.winSize.height + 200;
 ;
 for (var i = 0; i < enemyList.length; i++) {
 var checkEnemy = enemyList[i];
 if (checkEnemy.x > 0 && checkEnemy.x < cc.game.winSize.width && checkEnemy.y > 0 && checkEnemy.y < cc.game.winSize.height)
 ;
 else
 continue;
 var distance = Math.pow((bulletX - checkEnemy.x), 2) + Math.pow((bulletY - checkEnemy.y), 2);
 if (distance < leastDistance) {
 foundEnemy = true;
 bullet.lockEnemy = true;
 bullet.enemy = checkEnemy;
 leastDistance = distance;
 leastX = checkEnemy.x;
 leastY = checkEnemy.y;
 }
 }
 enemyX = leastX;
 enemyY = leastY;
 }
 }else{
 if(bullet.enemy != null && this.scene.enemies.indexOf(bullet.enemy) > -1 ){
 foundEnemy = true;
 enemyX = bullet.enemy.x;
 enemyY = bullet.enemy.y;
 }
 }
 if(foundEnemy != true){
 var deltaX = bulletX - oldX;
 var deltaY = bulletY - oldY;
 var deltaD = Math.sqrt(Math.pow(deltaX,2)+Math.pow(deltaY,2));
 enemyX = bulletX + this.speed*deltaX/deltaD;
 enemyY = bulletY + this.speed*deltaY/deltaD;
 }
 var rotationAngle = Math.atan((enemyY - bulletY)/(enemyX - bulletX))*360/(2*Math.PI);
 if((enemyX - bulletX)>=0)
 ;
 else
 rotationAngle += 180;
 if(rotationAngle<0)
 rotationAngle +=360;
 rotationAngle = 90 - rotationAngle;
 //trace((enemyY - bulletY)+","+(enemyX - bulletX)+","+rotationAngle);
 //trace(enemyY+","+(bulletY)+","+(oldY));
 var duration = Math.sqrt(Math.pow(enemyX - bulletX, 2) + Math.pow(enemyY - bulletY, 2))/this.speed;
 //trace("bullet speed:"+duration);
 return {x:enemyX, y:enemyY, duration:duration, rotationAngle:rotationAngle};
 },
 perFramesUpdate:function(scene, layer, plane, bullet){     //每幀更新,每一幀都根據(jù)瞄準(zhǔn)的敵人的位置調(diào)整子彈的運動軌跡
 if(bullet.initial == true) {
 bullet.scheduleOnce(function(){
 this.initial = false;
 }.bind(bullet), 0.2);
 return ;
 }
 //if(Math.abs(bullet.x - bullet.oldX)>10 || Math.abs(bullet.y - bullet.oldY)>10)
 //if(bullet.moveAction!=null)
 // bullet.moveAction.speed(10000);
 //
 if(bullet.recentStop == null || bullet.recentStop == false) {
 bullet.stopAllActions();
 bullet.recentStop = true;
 bullet.scheduleOnce(function(){
 this.recentStop = false;
 }.bind(bullet), 0.02);
 }
 else
 return ;
 //if(bullet.moveAction!=null)
 // trace("speed:"+bullet.moveAction.getSpeed());
 //
 //bullet.stopAllActions();
 var enemyLocation = this.aimedEnemy(bullet.x, bullet.y, bullet.oldX, bullet.oldY, bullet);
 bullet.oldX = bullet.x;
 bullet.oldY = bullet.y;
 //trace("now:"+bullet.x+","+bullet.y);
 //trace(enemyLocation.x+","+enemyLocation.y);
 var action = cc.moveTo(enemyLocation.duration, cc.p(enemyLocation.x, enemyLocation.y));
 //var action = cc.moveTo(1, cc.p(bullet.x+1000, bullet.y+1000));
 //action.easing(cc.easeIn(20));
 //bullet.setRotationSkewX(enemyLocation.rotationAngle);
 var rotationAction = cc.rotateTo(0.01,enemyLocation.rotationAngle);
 //trace("angel:"+enemyLocation.rotationAngle);
 bullet.runAction(cc.spawn(action, rotationAction));
 bullet.moveAction = action;
 }
})

發(fā)射子彈

在飛機和敵人上發(fā)射子彈,是通過調(diào)用plane._shot()和enemy.attack()來進(jìn)行的禁炒,他們的實現(xiàn)其實是類似的:
飛機的_shot方法里有:

 if (this.allowShot) {

 //cc.game.musicManager.playEffect("bullet");
 if (cc.pool.hasObject(DefaultBullet)) {        //從回收池里取出DefaultBullet實例

 cc.pool.getFromPool(DefaultBullet, "normal", this, this.layer, this.gameScene);
 //trace("reuse bullets");

 } else {                                                   //沒有就new一個DefaultBullet的實例

 new DefaultBullet("normal", this, this.layer, this.gameScene);
 }

 this.allowShot = false;
 this.scheduleOnce(function () {                    //每0.2秒設(shè)計一次
 this.allowShot = true;

 }
 .bind(this), 0.2);

 }

碰撞檢測

飛機發(fā)射的子彈和敵人之間而咆,以及敵人的子彈與飛機之間,需要進(jìn)行碰撞檢測幕袱,如果發(fā)射了碰撞暴备,就需要回收子彈,計算傷害(并且根據(jù)血量摧毀敵人/飛機们豌,同時顯示特效等)涯捻。
碰撞檢測的方法有很多,這里我們只使用了最簡單的方法:計算子彈和機體的距離望迎,就是根據(jù)兩個目標(biāo)的x軸和y軸運用勾股定理計算距離障癌,當(dāng)距離小于一定的閾值(threshold)時判定為發(fā)生碰撞。
碰撞一共有3種:

  1. 子彈與機體
  2. 敵人與飛機
  3. 物品與飛機

這3種碰撞分別由3個manager來執(zhí)行檢查:BulletManager辩尊,EnemManager涛浙,ItemManager。
以BulletManager的子彈與機體的碰撞檢測為例:

 collisionCheck:function(scene, layer){          //分別進(jìn)行飛機與敵人的子彈摄欲,飛機的子彈與敵人的碰撞檢測轿亮,計算傷害,在血量為0時調(diào)用機體的explode方法蒿涎,回收敵人哀托,同時進(jìn)行子彈回收
 var playerX= scene.plane.x;
 var playerY= scene.plane.y;

 var bullets = layer.bullets;

 for(var i =0;i<bullets.length;i++) {

 var checkBullet = bullets[i];
 if (checkBullet.BulletOwner == "enemy") {

 for(var k =0;k<checkBullet.bullets.length;k++){

 var childBullet = checkBullet.bullets[k];

 if (this._collide(playerX, playerY, childBullet.x, childBullet.y, 50) == true) {

 layer.removeChild(childBullet);
 checkBullet.bullets.splice(k,1);
 k--;

 //var blinkAction = cc.blink(1,3);
 //scene.plane.runAction(blinkAction);
 scene.plane.showHitEffect(childBullet.harm);

 if(checkBullet.bullets.length==0){
 cc.pool.putInPool(checkBullet);
 layer.bullets.splice(i, 1);
 i--;

 }

 }

 }

 }
 }

 for(var s = 0;s<scene.enemies.length;s++){

 //trace("s:"+s);
 var checkEnemy = scene.enemies[s];
 var enemyX = checkEnemy.x;
 var enemyY = checkEnemy.y;

 for(var i =0;i<bullets.length;i++) {

 var checkBullet = bullets[i];
 if (checkBullet.BulletOwner == "player") {

 for(var k =0;k<checkBullet.bullets.length;k++){
 var childBullet = checkBullet.bullets[k];
 var bulletX = childBullet.x;
 var bulletY = childBullet.y;

 if (this._collide(enemyX, enemyY, childBullet.x, childBullet.y, checkEnemy.threshold) == true) {

 layer.removeChild(childBullet);
 checkBullet.bullets.splice(k,1);
 k--;
 //var blinkAction = cc.blink(1,3);
 //scene.plane.runAction(blinkAction);
 checkEnemy.showHitEffect(childBullet.harm, {x:bulletX, y:bulletY});

 if(checkBullet.bullets.length==0){
 cc.pool.putInPool(checkBullet);
 layer.bullets.splice(i, 1);
 i--;
 }
 }

 }

 }
 }
 // place out of inside loop, otherwise will case s++ run multiple times!
 trace(checkEnemy.blood);
 if(checkEnemy.blood<=0){
 checkEnemy.explode();
 cc.game.enemyManager.removeEnemy(scene, layer, checkEnemy, s);
 s--;
 }
 //check ennemy done
 }
 },
 _collide:function(x1, y1, x2, y2, threshold){                          //用勾股定理計算舉例,對比threshold判定碰撞

 if((Math.pow((x1-x2),2)+Math.pow((y1-y2),2))>Math.pow(threshold, 2))
 return false;
 else
 return true;
 }

子彈回收

碰撞了的子彈就會被回收劳秋,還有一種情況需要回收:沒發(fā)生碰撞但是飛出了可視窗口仓手。
這種情況在BulletManager里面進(jìn)行了每幀檢測和處理:

 manageBullet:function(layer){
 if(layer.bullets.length>0)
 for(var i=0;i<layer.bullets.length;i++){
 var bullet = layer.bullets[i];
 if(bullet._validate()==true)
 ;
 else{
 cc.pool.putInPool(bullet);
 layer.bullets.splice(i,1);
 i--;
 }
 }

 }

6. 物品(Item)系統(tǒng)

物品系統(tǒng)是飛行射擊游戲一個常見的系統(tǒng),具體來說就是可以控制飛機去吃一些隨機生成的物品玻淑,從而獲得一些額外的增強效果嗽冒。
筆者同樣給游戲添加了這個系統(tǒng)。

ItemManager

var ItemManager = cc.Class.extend({
 scene:null,
 layer:null,
 plane:null,
 items:{
 missile:"res/items/missile.png",                   //物品以及對于圖片
 blood:"res/items/blood.png",
 shield:"res/items/shield.png",
 bigBang:"res/items/bigBang.png",
 shine:"res/items/shine.png",
 ultra:"res/items/ultra.png"
 },
 itemName:["missile", "blood", "shield", "bigBang", "shine", "ultra"],      //物品名
 basicGap: 2,
 randomGap: 3,
 allowAddItem:true,
 floatingItems:[],

 privateSprite:null,
 durableEffectFunc:[],
 allowDurableEffect:true,
 ctor:function(){                                  //ItemManager初始化
 this.privateSprite = new cc.Sprite();
 this.durableEffectFunc = []; // remove durable effect when re-init game
 this.floatingItems = [];
 this.allowDurableEffect = true;
 this.allowAddItem = true;
 }
 }

添加物品

添加物品其實和添加敵人的做法是類似的补履,就是在一個隨機的位置上添加一個物品添坊,并讓它飛行。與敵人不同之處在于箫锤,物品只可能與飛機發(fā)生碰撞贬蛙,既不會與敵人也不會與子彈發(fā)生碰撞。

 generateItem:function(scene, layer, plane){

 if(this.allowAddItem == false)
 return ;

 if(this.allowAddItem == true) {
 var item = this.itemName[new Date().getTime() % this.itemName.length];      //隨機產(chǎn)生物品
 //item = this.itemName[1];
 var itemSprite = new cc.Sprite(this.items[item]);
 itemSprite.itemName = item;
 itemSprite.scale = 0.3;
 itemSprite.x = Math.round(Math.random() * cc.game.winSize.width);
 itemSprite.y = cc.game.winSize.height + 100;
 var moveAction = cc.moveTo(6, cc.p(Math.random() * cc.game.winSize.width, -400));
 itemSprite.runAction(moveAction);                                                  //移動物品

 layer.addChild(itemSprite, 5);
 scene.items.push(itemSprite);
 this.allowAddItem = false;

 scene.scheduleOnce(function(){              //物品產(chǎn)生的間隔為基礎(chǔ)間隔+隨機時間
 this.allowAddItem = true;
 }.bind(this), Math.random()*this.randomGap+this.basicGap);

 }
 

檢測物品碰撞

 _collide:function(x1, y1, x2, y2, threshold){

 if((Math.pow((x1-x2),2)+Math.pow((y1-y2),2))>Math.pow(threshold, 2))
 return false;
 else
 return true;

 },
 collisionCheck:function(scene, layer, plane){
 var itemList = scene.items;

 for(var i=0;i<itemList.length;i++){

 var checkItem = itemList[i];

 if(this._collide(checkItem.x, checkItem.y, plane.x, plane.y, 50) == true){
 this.takeEffect(scene, checkItem.itemName, plane);                        //如果物品與飛機發(fā)生了碰撞谚攒,那么就應(yīng)用物品的效果

 layer.removeChild(checkItem);
 scene.items.splice(i, 1);
 i--;
 }

 }
 }

物品效果

物品效果分兩種:即時效果和延時效果阳准,前者有加血、一次性范圍攻擊等馏臭,后者有護罩野蝇、增強型子彈等。

 takeEffect:function(scene, itemName, plane){

 switch(itemName){     //根據(jù)物品的名稱選擇要加的效果
 case "blood": cc.game.itemManager.addPlaneEffect(scene, "blood", "res/particle/wsparticle_revival01.ccbi", plane, 2);break;
 case "shield":cc.game.itemManager.addPlaneEffect(scene, "shield", "res/particle/wsparticle_buff01.ccbi", plane, 10);break;
 case "missile":cc.game.itemManager.addPlaneEffect(scene, "missile","res/particle/wsparticle_tailinga.ccbi" , plane, 10);break;
 case "bigBang":cc.game.itemManager.addPlaneEffect(scene, "bigBang","res/particle/wsparticle_item_boom_02.ccbi" , plane, 10);break;
 case "shine":cc.game.itemManager.addPlaneEffect(scene, "shine","res/particle/wsparticle_universallylight2.ccbi" , plane, 10);break;
 case "ultra":cc.game.itemManager.addPlaneEffect(scene, "ultra","res/particle/wsparticle_super02.ccbi" , plane, 10);break;

 }

 },

 addPlaneEffect:function(scene, effectName, effectCCBI, plane, duration){

 var timeStamp = new Date().getTime();

 var locationFunc = null;
 var followTarget = null;

 switch (effectName){                  //根據(jù)效果的名稱決定效果特效的起始位置

 case "bigBang":locationFunc = function(x, y){      //bigBang的特效位置是固定的

 return {
 x:cc.game.winSize.width/2,
 y:cc.game.winSize.height/2
 };

 };followTarget = false;break;
 default : locationFunc = function(x, y){        //其余的與飛機當(dāng)前位置相關(guān)

 return {
 x:x,
 y:y
 };

 };followTarget = true;break;          //followTarget 為true就以為則特效藥跟隨飛機
 }
 var ccbNode = cc.game.particleManager.showParticle(effectName, scene, plane, locationFunc,followTarget, duration);            //使用粒子系統(tǒng)來展示物品的應(yīng)用特效
 if(followTarget == true){                      //需要跟隨的效果加入到飛機的addedEffects數(shù)組中
 plane.addedEffects.push({name:effectName, effect:ccbNode, timeStamp:timeStamp});
 scene.scheduleOnce(function(){
 //this.removeChild(ccbNode);
 this.plane.removeAddedEffect(timeStamp);
 }.bind(scene), duration);
 }
 this.instantEffect(scene, effectName, plane);            //即時效果和延時效果使用不同的處理
 this.durableEffect(scene, effectName, plane);
 },

 instantEffect:function(scene, effectName,plane){      //即時效果
 switch (effectName){
 case "bigBang" : this.bigBang(scene, effectName,plane);break;
 case "blood" : this.addBlood(scene, effectName,plane);break;
 }
 },

 durableEffect:function(scene, effectName,plane){      //延時效果
 switch (effectName){
 case "shine" : this.shineField(scene, effectName,plane);break;        //shine效果需要添加每幀處理方法
 }
 },

shineField:function(scene, effectName,plane){   //對于有些效果,除了需要添加到飛機的addedEffects上绕沈,還需要給出一個效果function來在每幀執(zhí)行引用效果锐想,例如shine特效,會計算飛機周圍一定距離的敵人乍狐,并給予一定的傷害
 this.durableEffectFunc.push({

 effectName:"shine",
 effectFunc:function (scene, layer, plane) {
 var enemyList = scene.enemies;

 for (var i = 0; i < enemyList.length; i++) {
 var checkEnemy = enemyList[i];

 if (((Math.pow((plane.x-checkEnemy.x),2)+Math.pow((plane.y-checkEnemy.y),2))<Math.pow(300, 2)) == true) {
 checkEnemy.showHitEffect(10);
 //checkEnemy.blood -= 10;
 //
 //if (checkEnemy.blood < 0)
 // checkEnemy.blood = 0;
 }
 }
 }});
 scene.scheduleOnce(function(){

 for(var j =0 ;j<this.durableEffectFunc.length;j++){
 if(this.durableEffectFunc[j].effectName == "shine"){

 this.durableEffectFunc.splice(j, 1);
 break;
 }
 }

 }.bind(this), 10);
 }

7. 特效系統(tǒng)

特效系統(tǒng)負(fù)責(zé)產(chǎn)生并管理兩種特效:Particle System和幀動畫赠摇。
Particle System是設(shè)定一些規(guī)則,使用紋理圖片隨機產(chǎn)生一些粒子特效澜躺,這些效果是隨機的蝉稳,不可重復(fù)的;
幀動畫是使用一些圖片連續(xù)播放產(chǎn)生一些特效效果掘鄙,這些特效每一次播放都是相同的耘戚。
本游戲中,飛機被擊中時產(chǎn)生的火花效果是Particle System產(chǎn)生的操漠,而物品效果收津,Boss的激光以及機體爆炸則是幀動畫。
Particle Manager的作用:

  1. 在給定的位置上顯示特效
  2. 回收結(jié)束的特效

Particle Manager中有3個show方法浊伙,分別處理3種特效:

  1. showParticle:物品特效
  2. showFire:機體被擊中的特效
  3. showLaser:Boss的激光射擊特效

在需要顯示特效的時候撞秋,例如子彈碰撞檢測判定為擊中、敵人血量低于0要爆炸嚣鄙、Boss要發(fā)射激光攻擊吻贿、飛機吃到物品等等,就會調(diào)用Particle Manager相應(yīng)的方法來顯示特效哑子。

Particle System

產(chǎn)生Particle System特效的方法:讀取plist文件

Particle Manager:
hitEffect = new cc.ParticleSystem("res/particle2/particle.plist");
 hitEffect.duration = -1;
 hitEffect.setAutoRemoveOnFinish(true);
 hitEffect.x = plane.x;
 hitEffect.y = plane.y;
 plane.gameScene.addChild(hitEffect);
 plane.hitEffect = hitEffect;
 plane.scheduleOnce(function(){
 plane.hitEffect = null;
 //plane.gameScene.removeChild(hitEffect);
 hitEffect.x = -1000;
 hitEffect.y = -1000;
 this.fireEffect.push(hitEffect);
 }.bind(this), 1);

想要制作自定義的特效舅列,可以去這個網(wǎng)站制作:
http://www.onebyonedesign.com/flash/particleeditor/

幀動畫

對于本游戲的幀動畫,有兩個存儲格式:plist卧蜓,ccbi帐要。
其中ccbi文件是cocos2dx所支持的特效文件,在C++和Lua使用廣泛弥奸,但是JS中暫時找不到使用sample榨惠,在官方文檔中也找不到相應(yīng)的API,在stackoverflow中也沒有相應(yīng)的解答盛霎。
但是筆者在cocos2dx-js的最新版本的源碼中找到了ccbi相關(guān)的模塊赠橙,所以最終是通過閱讀這個模塊的源碼來得知cocos2dx-js是如何讀取ccbi文件的,希望可以給讀者一些參考愤炸。

Particle Manager:
particleList:{

 hit:"/res/particle/wsparticle_hit_01.ccbi",
 explode:"/res/particle/wsparticle_hit_02.ccbi",
 missile:"res/particle/wsparticle_tailinga.ccbi",
 blood:"res/particle/wsparticle_revival01.ccbi",
 shield: "res/particle/wsparticle_buff01.ccbi",
 bigBang: "res/particle/wsparticle_item_boom_02.ccbi",
 shine: "res/particle/wsparticle_universallylight2.ccbi",
 ultra:"res/particle/wsparticle_super02.ccbi",
 bomb:"/res/particle/wsparticle_hit_03.ccbi",
 warning:"/res/particle/wsparticle_warning.ccbi"

 };

var nodeLibrary = new cc.NodeLoaderLibrary();
 nodeLibrary.registerDefaultCCNodeLoaders();
 reader =new cc.BuilderReader(new cc.BuilderReader(nodeLibrary,null, null,null));

 var ccbNode = reader.readNodeGraphFromFile(this.particleList[particleName], null, null, new cc.BuilderAnimationManager());
 var location = locationFunc(target.x, target.y);
 ccbNode.x = location.x;
 ccbNode.y = location.y;
 scene.addChild(ccbNode);

 reader.ccbNode = ccbNode;

 reader.ccbNode.scheduleOnce(function(){

 this.readerCache[particleName].push(reader);
 //reader.ccbNode.stopAllActions();
 if(particleName == "missile") {

 // for missile particle re-show bug
 reader.ccbNode.x = -400;
 reader.ccbNode.y = -400;
 }
 else
 scene.removeChild(reader.ccbNode);

 }.bind(this), endurance);

8. Stage系統(tǒng)

所謂stage系統(tǒng)期揪,其實就是指游戲的階段管理。
眾所周知摇幻,一個游戲一般不會一上來就是打Boss横侦,也不會一上來就是最高難度,會有一個階段的推進(jìn)绰姻。
所以筆者設(shè)計了Stage系統(tǒng)來控制游戲的階段過渡枉侧。

Stage Manager

var StageManager = cc.Class.extend({

 stage:0,
 stageList:null,
 nextStage:true,
 stageFunc:null,
 preStageFunc:null,

 ctor:function(stageList){    //初始化,stageList包含游戲的階段信息狂芋,stageManager將會使用這些信息來切換stage
 this.stageList = stageList;
 this.nextStage = true;
 this.stage = -1;
 this.stageFunc={};
 this.preStageFunc = {};
 this.stageFunc["normal"] = this.normalStage;
 this.stageFunc["mega"] = this.megaStage;
 this.stageFunc["boss"] = this.bossStage;
 this.preStageFunc["boss"] = this.preBossStage;

 },
 manageGameStage:function(scene, layer, plane){  //stage切換方法榨馁,每個stage都有自己的執(zhí)行方法,主要是調(diào)整了一些運行配置帜矾,諸如敵人產(chǎn)生間隔等

 if(this.nextStage == true){        //nextStage 為true時切換至下一個stage

 if(this.stage<this.stageList.length-1){

 this.stage++;

 //console.log("Change stage to:"+this.stageList[this.stage].name);

 if(this.preStageFunc[this.stageList[this.stage].name] != null)
 this.preStageFunc[this.stageList[this.stage].name](scene, layer, plane);

 this.stageFunc[this.stageList[this.stage].name](scene, layer, plane);
 this.nextStage = false;

 scene.scheduleOnce(function(){

 this.nextStage = true;

 }.bind(this), this.stageList[this.stage].duration);
 }else{
 this.endStage(scene);
 }
 }else{
 this.stageFunc[this.stageList[this.stage].name](scene, layer, plane);
 }
 },
 endStage:function(scene){        //默認(rèn)的結(jié)束stage翼虫,游戲結(jié)束

 //console.log("End stage.");
 //cc.director.pause();
 new GameOverLayer(scene);

 },
 normalStage:function(scene, layer, plane){      //常規(guī)階段

 scene.plane._shot();
 cc.game.enemyManager.addEnemy(scene, layer, 0.2);
 cc.game.enemyManager.enemyAttack(scene);

 cc.game.bulletManager.manageBullet(layer);
 cc.game.bulletManager.collisionCheck(scene, layer);
 cc.game.bulletManager.updateBulletLocation(scene, layer, plane);
 cc.game.enemyManager.collisionCheck(scene, layer, plane);
 cc.game.enemyManager.outEnemyCheck(scene, layer);

 scene.plane.update();
 scene.gameInfo.update();

 if(scene.boss!=null)
 scene.boss.update();

 if(scene.laserCheck == true)
 scene.boss.laserCheck(scene, layer, plane);
 cc.game.itemManager.generateItem(scene, layer, plane);
 cc.game.itemManager.collisionCheck(scene, layer, plane);
 cc.game.itemManager.applyDurableEffect(scene, layer, plane);
 if(plane.blood<=0) {
 trace("dead");
 //cc.director.pause();
 cc.game.stageManager.nextStage = true;
 }
 },
 megaStage:function(scene, layer, plane){        //mega階段

 cc.game.enemyManager.enemyFrequency = 1;
 cc.game.stageManager.normalStage(scene, layer, plane);

 },
 preBossStage:function(scene, layer, plane){        //preBoss階段,Boss出來前顯示一個警告
 cc.game.enemyManager.addBoss(scene, layer);      //添加一個Boss
 cc.game.enemyManager.enemyFrequency = 3;
 cc.game.particleManager.showParticle("warning", scene, plane, function(x, y){
 return {
 x:cc.game.winSize.width/2,
 y:cc.game.winSize.height/2
 };
 },false, 4);
 },
 bossStage:function(scene, layer, plane){          //boss階段
 cc.game.stageManager.normalStage(scene, layer, plane);
 },
});

Stage 配置

在main.js里面屡萤,啟動游戲之前使用stageList配置來配置游戲的stage珍剑。

var defaultStage = [{name:"normal", duration: 20}, {name:"mega", duration: 20}, {name:"boss", duration:100000}];   //20秒normal stage,20秒mega stage死陆,然后一直是Boss stage
cc.game.onStart = function(){
 cc.view.adjustViewPort(false);
 cc.view.setDesignResolutionSize(512, 768, cc.ResolutionPolicy.SHOW_ALL);
 cc.view.resizeWithBrowserSize(true);
 //load resources
 cc.LoaderScene.preload(g_resources, function () {
 cc.game.musicManager = new MusicManager();
 cc.game.bulletManager = new BulletManager();
 cc.game.enemyManager = new EnemyManager();
 cc.game.itemManager = new ItemManager();
 cc.game.particleManager = new ParticleManager();
 cc.game.stageManager = new StageManager(defaultStage);
 cc.director.runScene(new MenuScene());
 cc.director.setDisplayStats(false);
 }, this);
};
cc.game.run();

var trace = function() {
 //cc.log(Array.prototype.join.call(arguments, ", "));
};

九招拙、 Android/iOS 原生App

Cocos2dx-JS最大的特點就是跨平臺,既可以以html的web形式發(fā)布措译,也可以編譯成Android和iOS的原生App發(fā)行别凤。但是實際上,要想在移動平臺獲得web的運行效果领虹,其中有大量的調(diào)優(yōu)和適配工作规哪。有興趣的同學(xué)可以自己研究一下。

十塌衰、總結(jié)

至此诉稍,筆者的小游戲就大致介紹完了,更加具體的細(xì)節(jié)猾蒂,需要讀者在學(xué)習(xí)了Cocos2dx-JS之后再參考源碼均唉。本文只是一個大致思路的說明。
筆者學(xué)習(xí)Cocos2dx-JS純屬偶然肚菠,更直接的原因其實是在亞馬遜上看到了相關(guān)的教程舔箭,于是就買了,看了蚊逢,然后就寫了层扶。這其中自然會遇到一些坑,但是在學(xué)習(xí)新知識的過程中烙荷,在不斷思考不斷改進(jìn)中收獲成長卻也是學(xué)習(xí)的最大樂趣镜会。
這篇簡單的教程是自己的小小的總結(jié),如果能幫到大家或者讓大家看到后能產(chǎn)生一點點學(xué)習(xí)新知識的興趣终抽,也就足夠了戳表。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末桶至,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子匾旭,更是在濱河造成了極大的恐慌镣屹,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件价涝,死亡現(xiàn)場離奇詭異女蜈,居然都是意外死亡,警方通過查閱死者的電腦和手機色瘩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門伪窖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人居兆,你說我怎么就攤上這事覆山。” “怎么了泥栖?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵汹买,是天一觀的道長。 經(jīng)常有香客問我聊倔,道長晦毙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任耙蔑,我火速辦了婚禮见妒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘甸陌。我一直安慰自己须揣,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布钱豁。 她就那樣靜靜地躺著耻卡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪牲尺。 梳的紋絲不亂的頭發(fā)上卵酪,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音谤碳,去河邊找鬼溃卡。 笑死,一個胖子當(dāng)著我的面吹牛蜒简,可吹牛的內(nèi)容都是我干的瘸羡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼搓茬,長吁一口氣:“原來是場噩夢啊……” “哼犹赖!你這毒婦竟也來了队他?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤峻村,失蹤者是張志新(化名)和其女友劉穎漱挎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雀哨,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年私爷,在試婚紗的時候發(fā)現(xiàn)自己被綠了雾棺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡衬浑,死狀恐怖捌浩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情工秩,我是刑警寧澤尸饺,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站助币,受9級特大地震影響浪听,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜眉菱,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一迹栓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧俭缓,春花似錦克伊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至惜姐,卻和暖如春犁跪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背歹袁。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工耘拇, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宇攻。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓惫叛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親逞刷。 傳聞我的和親對象是個殘疾皇子嘉涌,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容