打飛機(jī)這個游戲并不是我原創(chuàng),只是拿來學(xué)習(xí)使用。望原作者見諒。剛剛開始是強(qiáng)烈建議簡書可以上傳源代碼的,但是后來一想,已經(jīng)有了github了可以直接鏈接過去,都不用簡書浪費(fèi)資源了。哈哈哈哈。我講要分析的項(xiàng)目上傳到了我的github倉庫:github AirBarons
游戲玩耍地址:http://112.126.90.18/airbarons/
先看一下AirBarons目錄結(jié)構(gòu)。

- project.json 項(xiàng)目的一些配置信息
"project_type": "javascript",
"debugMode" : 1,
"showFPS" : true,
"frameRate" : 60,
"id" : "gameCanvas",
"renderMode" : 0,
"engineDir":"frameworks/cocos2d-html5",
"modules" : ["cocos2d"],
這些前一篇已經(jīng)說過了。
看下AirBarons項(xiàng)目用到了哪些js文件
"jsList" : [
"src/resource.js",
"src/config/GameConfig.js",
"src/config/EnemyType.js",
"src/config/Level.js",
"src/mainMenu/scene/MainMenu.js",
"src/mainMenu/layer/MMBackgroundLayer.js",
"src/mainMenu/layer/MMMainMenuLayer.js",
"src/mainMenu/layer/MMTouchLayer.js",
"src/setting/scene/Setting.js",
"src/setting/layer/STBackgroundLayer.js",
"src/setting/layer/STTouchLayer.js",
"src/about/scene/About.js",
"src/about/layer/ABBackgroundLayer.js",
"src/about/layer/ABTouchLayer.js",
"src/gamePlay/classes/LevelManager.js",
"src/gamePlay/scene/GamePlay.js",
"src/gamePlay/layer/GPBackgroundLayer.js",
"src/gamePlay/layer/GPTouchLayer.js",
"src/gamePlay/sprite/ShipSprite.js",
"src/gamePlay/sprite/BulletSprite.js",
"src/gamePlay/sprite/EnemySprite.js",
"src/gamePlay/sprite/ExplosionSprite.js",
"src/gamePlay/sprite/SparkEffectSprite.js",
"src/gameOver/scene/GameOver.js",
"src/gameOver/layer/GOBackgroundLayer.js",
"src/gameOver/layer/GOTouchLayer.js"
]
基本概念之<b>resource</b>
看到加載的第一個文件就是 "src/resource.js",典型的js文件。定義了res對象,里面存儲的是圖片和聲音的地址對象,最后將res對象的屬性都放到了全局g_resources數(shù)組中了
var res = {
HelloWorld_png : "res/HelloWorld.png",
CloseNormal_png : "res/CloseNormal.png",
CloseSelected_png : "res/CloseSelected.png",
// shared
sh_arial_14_fnt : 'res/shared/arial-14.fnt',
sh_arial_14_png : 'res/shared/arial-14.png',
// mainMenu
TextureTransparentPack_plist : "res/mainMenu/textureTransparentPack.plist",
TextureTransparentPack_png : "res/mainMenu/textureTransparentPack.png",
mm_bg_png : "res/mainMenu/bg.png",
mm_logo_png : "res/mainMenu/logo.png",
mm_mune_png : "res/mainMenu/menu.png",
mm_flare_jpg : "res/mainMenu/flare.jpg",
mm_btnEffect : "res/sound/effect/buttonEffect.mp3",
mm_bgMusic_mp3 : "res/sound/music/mainMainMusic.mp3",
// gamePlay
gp_TextureOpaquePack_plist : "res/gamePlay/textureOpaquePack.plist",
gp_TextureOpaquePack_png : "res/gamePlay/textureOpaquePack.png",
gp_b01_plist : "res/gamePlay/b01.plist",
gp_b01_png : "res/gamePlay/b01.png",
gp_Explosion_plist : "res/gamePlay/explosion.plist",
gp_Explosion_png : "res/gamePlay/explosion.png",
gp_explodeEffect_mp3 : 'res/sound/effect/explodeEffect.mp3',
gp_bgMusic_mp3 : "res/sound/music/bgMusic.mp3",
gp_shipDestroyEffect_mp3 : 'res/sound/effect/shipDestroyEffect.mp3',
// setting
st_menuTitle_png : "res/setting/menuTitle.png",
// gameOver
go_gameOver_png : "res/gameOver/gameOver.png",
go_cocos2d_html5_png : "res/gameOver/cocos2d-html5.png"
};
var g_resources = [];
for (var i in res) {
g_resources.push(res[i]);
}
- 接著是游戲js中用到的配置相關(guān)參數(shù)js的引入
"src/config/GameConfig.js",
"src/config/EnemyType.js",
"src/config/Level.js",
- 接著是到了游戲各個分類的相關(guān)js文件的引入,該游戲有 <b>設(shè)置</b>、<b>關(guān)于</b>、<b>游戲</b>和<b>游戲結(jié)束后</b>四個模塊。下面看看這四個模塊都用到了Cocos2d 的哪些基礎(chǔ)概念。
首先說一下js的地址是:api-ref/js/V3.12/
看main.js
cc.game.onStart = function(){
cc.view.adjustViewPort(true);
cc.view.setDesignResolutionSize(320, 480, cc.ResolutionPolicy.SHOW_ALL);
cc.view.resizeWithBrowserSize(true);
cc.LoaderScene.preload(g_resources, function () {
cc.director.runScene(new MainMenuScene());
}, this);
};
cc.game.run();
- <b>cc.game</b>
這個是An object to boot the game.(啟動游戲的對象)其中有 end(游戲停止)、isPaused(檢查游戲是否停止)、pause(停止游戲)、prepare(游戲前準(zhǔn)備)、restart(游戲重新啟動)
resume(繼續(xù)游戲)、run(啟動游戲) setFrameRate(設(shè)置幀率)、step(一幀一幀運(yùn)行游戲)這些方法。還有很多屬性onStart就是其中的一個屬性,這里將onStart定義成了一個function,在scripts引擎加載完畢后就會回調(diào)。 - <b>cc.view</b>
在onStart回調(diào)的方法中設(shè)置了cc.view的相關(guān)屬性,cc.view是個代表了游戲窗口的單例的對象
這里使用setDesignResolutionSize(width, height, resolutionPolicy)設(shè)置了游戲窗口的大小和屏幕適配的策略。屏幕大小自適應(yīng)的涉及到cc.ResolutionPolicy,其中有六種方式
EXACT_FIT會拉伸游戲,充滿整個屏幕,最簡單最粗暴,
SHOW_ALL保持游戲原比例,讓一邊占滿屏幕,另外一側(cè)黑邊
NO_BORDER跟SHOW_ALL類似,但讓短邊占滿屏幕,另外一側(cè)超出屏幕,不顯示黑邊,一部分畫面在屏幕外,無法顯示,
FIXED_HEIGHT和FIXED_WIDTH都是NO_BORDER的升級版,指定那一側(cè)充滿屏幕,另外一側(cè)超出屏幕,
UNKNOWN 六種方案。該游戲選擇的是SHOW_ALL、
接著使用resizeWithBrowserSize(enabled)
這個方法只在web中起作用,canvas 跟著瀏覽器得大小變動而自適應(yīng)。
- <b>cc.LoaderScene</b>和<b>cc.director</b>

這里涉及到Node、Scence、Director。即節(jié)點(diǎn)、導(dǎo)演和場景 ,其實(shí)還有Layer(層)、Sprite(精靈)。節(jié)點(diǎn)是Node是上層的對象。在Cocos2d-x-3.x引擎中,采用節(jié)點(diǎn)樹形結(jié)構(gòu)來管理游戲?qū)ο?,一個游戲可以劃分為不同的場景,一個場景又可以分為不同的層,一個層又可以擁有任意個可見的游戲節(jié)點(diǎn)(即對象,游戲中基本上所有的類都派生于節(jié)點(diǎn)類Node)??梢詧?zhí)行Action來修改游戲節(jié)點(diǎn)的屬性,使其移動、旋轉(zhuǎn)、放大、縮小等等。
看下官網(wǎng)給出的圖即可明白

到眼控制著場景,層屬于場景中的一個場景。然后精靈有事屬于場景中的一個東東。精靈的移動,旋轉(zhuǎn),縮放,執(zhí)行動畫,并接受其他轉(zhuǎn)換構(gòu)成層的東西,各個層之間順序執(zhí)行然后構(gòu)成一個場景。場景之間的切換最終構(gòu)成了一個游戲,執(zhí)行者就是導(dǎo)演。
cc.LoaderScene.preload了g_resources后傳入回調(diào)函數(shù)
function () {
cc.director.runScene(new MainMenuScene());
}回調(diào)函數(shù)是導(dǎo)演執(zhí)行了第一個場景。
再傳入當(dāng)前的對象 this);
好了看new出的第一個場景 MainMenuScene 看一下場景定義的規(guī)范
var MainMenuScene = cc.Scene.extend({
//this._super();重寫后這個方法一定要記得寫上,要不然導(dǎo)致后續(xù)不會執(zhí)行
//這里是場景的各個函數(shù)的重寫 場景是是繼承自node的所以node中方法也可以被重寫,其中這里有onEnter方法,這個方法是不需要主動調(diào)用的。
官方給出的解釋Event callback that is invoked every time when CCNode enters the 'stage'. 可以看出 這個方法會每次都被調(diào)用 當(dāng)進(jìn)入到stage后.
var layer = new MainMenuLayer();
this.addChild(layer);這個場景只有一個層,最后將這個層假如到這個場景,由于只有一個層所以都不需要指定執(zhí)行的順序。
})
下面看MainMenuLayer,層的規(guī)范定義是
var MainMenuLayer = cc.Layer.extend({
// 這里可以聲明屬性
_backgroundLayer : null,
_touchLayer : null,
ctor方法相當(dāng)于是構(gòu)造方法,你在new這個層的時(shí)候就會被調(diào)用。
構(gòu)造方法中又可以調(diào)用其他的方法
這里是調(diào)用了addBackgroundLayer和addTouchLayer可以看出是加了兩個子層到該層中,這個是按加入的順序執(zhí)行的。
})
看一下假如的MMBackgroundLayer和MMTouchLayer層,
MMBackgroundLayer是創(chuàng)建了一個背景層,這個和上一個所講的層基本一致。里面涉及到了精靈的創(chuàng)建
this._sptBg = new cc.Sprite(res.mm_bg_png);
this._sptBg.attr({
anchorX : 0.5,
anchorY : 0.5,
x: GC.w_2,
y: GC.h_2
});
可以看出精靈的創(chuàng)建方式 可以指定屬性一個其中有anchorPoint 錨點(diǎn)坐標(biāo),默認(rèn)是 (0.5, 0.5) 這就說明這個精靈位于層的中央。x,y是寬和高的概念。
這里涉及到cocos2d的坐標(biāo)概念
總之openGL的坐標(biāo)和UI的坐標(biāo)是不一致的,按openGL坐標(biāo)來就可以了


坐標(biāo)中還有模型坐標(biāo)和世界坐標(biāo)的概念 其實(shí)就是精靈相對的坐標(biāo)概念
![Upload Paste_Image.png failed. Please try again.]
在游戲場景中有兩個Node對象,其中Node1的坐標(biāo)是(400, 500),大小是300 x 100像素。Node2是放置在Node1中的,它對于Node1的模型坐標(biāo)是(0, 0),大小是150 x 50像素。
好了繼續(xù)到MMTouchLayer了,在該層中 用到了cc.audioEngine去播放音樂。
/ 播放背景音樂,true代表循環(huán)無限次播放,false表示只播放一次。
if (GC.SOUND_ON){
if (cc.audioEngine.isMusicPlaying()){
return;
}
cc.audioEngine.playMusic(res.mm_bgMusic_mp3, true);
}
然后又去加載了cc.Menu
// 菜單。 對應(yīng)三者關(guān)系:菜單里面有菜單項(xiàng),菜單項(xiàng)中綁定要執(zhí)行的方法,并且需要圖片去顯示。圖片就是精靈
var menu = new cc.Menu(newGame, gameSettings, about);
menu.alignItemsVerticallyWithPadding(10);
menu.x = GC.w_2;
menu.y = GC.h_2 - 80;
this.addChild(menu, 1, 2);
構(gòu)造方法傳入的是cc.MenuItemSprite 看其中的一個MenuItemSprite 定義
var newGame = new cc.MenuItemSprite(
newGameNormal,
newGameSelected,
newGameDisabled,
function(){
this.onButtonEffect();
this.flareEffect(flare, this, this.onNewGame);
}.bind(this)
);
里面構(gòu)造方法傳入的也是各個精靈,精靈的創(chuàng)建中用到了cc.rect,看其中的一個。
// 根據(jù)rect區(qū)域去創(chuàng)建一個精靈,作為下面menuItemSprite顯示的圖片。
// 因?yàn)閙enuItem有Normal、Selected、Disabled三個狀態(tài),所以一個菜單項(xiàng)需要三張紋理圖片
var newGameNormal = new cc.Sprite(res.mm_mune_png, cc.rect(0, 0, 126, 33));
cc.rect(0, 0, 126, 33));是指定cc.Rect(x, y, width, height)。
這樣的話就渲染出來了兩個層,一個背景層,一個菜單層。在菜單切換的過程中還涉及到了 動作 ,函數(shù)的回調(diào) 、按順序執(zhí)行一組動作、 同時(shí)執(zhí)行一組動作
// 定義動作
var opacityAnim = cc.fadeIn(0.5, 255);
var opacDim = cc.fadeIn(1, 0);
// 為動作加上easing效果,具體參考tests里面的示例
var biggerEase = cc.scaleBy(0.7, 1.2, 1.2).easing(cc.easeSineOut());
var easeMove = cc.moveBy(0.5, cc.p(328, 0)).easing(cc.easeSineOut());
var rotateEase = cc.rotateBy(2.5, 90).easing(cc.easeExponentialOut());
var bigger = cc.scaleTo(0.5, 1);
// 函數(shù)回調(diào)動作
var onComplete = cc.callFunc(callback, target);
var killflare = cc.callFunc(function () {
this.getParent().removeChild(this,true);
}, flare);
// 按順序執(zhí)行一組動作
var seqAction = cc.sequence(opacityAnim, biggerEase, opacDim, killflare, onComplete);
// 同時(shí)執(zhí)行一組動作
var action = cc.spawn(seqAction, easeMove, rotateEase, bigger);
flare.runAction(action);
}
動作比較簡單的。 就是精靈的旋轉(zhuǎn)放大縮小等。
cc.callFunc是去執(zhí)行一個函數(shù),這里有this.getParent().removeChild方法,其實(shí)就是去移除創(chuàng)建的層。
cc.sequence是按順序執(zhí)行一組動作,傳給 cc.spawn 是組合一組動作,然后精靈去執(zhí)行 flare.runAction(action);
主要看一下GamePlayScene 游戲玩耍這個層,這個層里面涉及到了基礎(chǔ)概念cc.spriteFrameCache緩存
cc.spriteFrameCache.addSpriteFrames(res.gp_TextureOpaquePack_plist);
直接這樣調(diào)用就可以了。這個層假如了兩個層GPTouchLayer和GPBackgroundLayer
GPBackgroundLayer比較簡單就是放了一張背景圖片而已。主要的碰撞動作在GPTouchLayer中
這個里面涉及到比較多和重要的幾個概念。scheduleUpdate和schedule
scheduleUpdate相當(dāng)于是調(diào)用層的update方法
// 游戲時(shí)時(shí)刷新
update:function (dt) {
if (this._state == STATE_PLAYING) {
// UI在這邊更新
this.updateUI();
// 敵人在這里面產(chǎn)生,以及界面上
this.moveActiveUnit(dt);
// 碰撞檢測
this.checkIsCollide();
// 檢測我們的飛船重生
this.checkIsReborn();
// 這個部分被我直接干掉了。。。因?yàn)?,重?fù)代碼,沒什么內(nèi)容
// this._movingBackground(dt);
}
},
schedule是一致去執(zhí)行一個方法這里一直在執(zhí)行分?jǐn)?shù)計(jì)數(shù)方法。
// 分?jǐn)?shù)在這里面加
scoreCounter:function () {
if (this._state == STATE_PLAYING) {
this._time++;
this._levelManager.loadLevelResource(this._time);
}
},
存放分?jǐn)?shù)信息的是cc.LabelBMFont
this._lbScore = new cc.LabelBMFont("Score: 0", res.sh_arial_14_fnt);
this._lbScore.attr({
anchorX: 1,
anchorY: 0,
x: GC.w - 5,
y: GC.h - 30
});
這個里面還有一個比較重要的就是碰撞檢測
利用坐標(biāo)是否落在區(qū)域去檢測
看代碼
// 碰撞堅(jiān)持
collide:function (a, b) {
var ax = a.x;
var ay = a.y;
var bx = b.x;
var by = b.y;
if (Math.abs(ax - bx) > MAX_CONTAINT_WIDTH || Math.abs(ay - by) > MAX_CONTAINT_HEIGHT)
return false;
var aRect = a.collideRect(ax, ay);
var bRect = b.collideRect(bx, by);
return cc.rectIntersectsRect(aRect, bRect);
},
這里面分了不同種類的精靈BulletSprite、EnemySprite、ExplosionSprite、ShipSprite、SparkEffectSprite看名字就可以看出來各個代表什么精靈,既然有碰撞檢測那么坐標(biāo)移動在哪里呢,其實(shí)就是在各個精靈中自己定義的action
看BulletSprite這個精靈吧,
update:function (dt) {
// cc.log("這里。。。");
var y = this.y;
this.y = y - this.yVelocity * dt; //不斷的移動坐標(biāo)去達(dá)到移動的目的
if (y < 0 || y > GC.h + 10 || this.HP <= 0) {
this.destroy();
}
},
其中還有一些別的方法 死亡啊,新產(chǎn)生啊之類的,這個就是碰撞檢測后的邏輯代碼,不涉及到基礎(chǔ)知識了,這里就過了。
真不容易寫完了。這里貼出來試玩地址
http://112.126.90.18/airbarons/
算了還是放頂部吧
go home。