打飞机这个游戏并不是我原创,只是拿来学习使用。望原作者见谅。刚刚开始是强烈建议简书可以上传源代码的,但是后来一想,已经有了github了可以直接链接过去,都不用简书浪费资源了。哈哈哈哈。我讲要分析的项目上传到了我的github仓库:github AirBarons
游戏玩耍地址:http://112.126.90.18/airbarons/
先看一下AirBarons目录结构。
- project.json 项目的一些配置信息
"project_type": "javascript",
"debugMode" : 1,
"showFPS" : true,
"frameRate" : 60,
"id" : "gameCanvas",
"renderMode" : 0,
"engineDir":"frameworks/cocos2d-html5",
"modules" : ["cocos2d"],
这些前一篇已经说过了。
看下AirBarons项目用到了哪些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数组中了
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中用到的配置相关参数js的引入
"src/config/GameConfig.js",
"src/config/EnemyType.js",
"src/config/Level.js",
- 接着是到了游戏各个分类的相关js文件的引入,该游戏有 <b>设置</b>、<b>关于</b>、<b>游戏</b>和<b>游戏结束后</b>四个模块。下面看看这四个模块都用到了Cocos2d 的哪些基础概念。
首先说一下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(游戏前准备)、restart(游戏重新启动)
resume(继续游戏)、run(启动游戏) setFrameRate(设置帧率)、step(一帧一帧运行游戏)这些方法。还有很多属性onStart就是其中的一个属性,这里将onStart定义成了一个function,在scripts引擎加载完毕后就会回调。 - <b>cc.view</b>
在onStart回调的方法中设置了cc.view的相关属性,cc.view是个代表了游戏窗口的单例的对象
这里使用setDesignResolutionSize(width, height, resolutionPolicy)设置了游戏窗口的大小和屏幕适配的策略。屏幕大小自适应的涉及到cc.ResolutionPolicy,其中有六种方式
EXACT_FIT会拉伸游戏,充满整个屏幕,最简单最粗暴,
SHOW_ALL保持游戏原比例,让一边占满屏幕,另外一侧黑边
NO_BORDER跟SHOW_ALL类似,但让短边占满屏幕,另外一侧超出屏幕,不显示黑边,一部分画面在屏幕外,无法显示,
FIXED_HEIGHT和FIXED_WIDTH都是NO_BORDER的升级版,指定那一侧充满屏幕,另外一侧超出屏幕,
UNKNOWN 六种方案。该游戏选择的是SHOW_ALL、
接着使用resizeWithBrowserSize(enabled)
这个方法只在web中起作用,canvas 跟着浏览器得大小变动而自适应。
- <b>cc.LoaderScene</b>和<b>cc.director</b>
这里涉及到Node、Scence、Director。即节点、导演和场景 ,其实还有Layer(层)、Sprite(精灵)。节点是Node是上层的对象。在Cocos2d-x-3.x引擎中,采用节点树形结构来管理游戏对象,一个游戏可以划分为不同的场景,一个场景又可以分为不同的层,一个层又可以拥有任意个可见的游戏节点(即对象,游戏中基本上所有的类都派生于节点类Node)。可以执行Action来修改游戏节点的属性,使其移动、旋转、放大、缩小等等。
看下官网给出的图即可明白
到眼控制着场景,层属于场景中的一个场景。然后精灵有事属于场景中的一个东东。精灵的移动,旋转,缩放,执行动画,并接受其他转换构成层的东西,各个层之间顺序执行然后构成一个场景。场景之间的切换最终构成了一个游戏,执行者就是导演。
cc.LoaderScene.preload了g_resources后传入回调函数
function () {
cc.director.runScene(new MainMenuScene());
}回调函数是导演执行了第一个场景。
再传入当前的对象 this);
好了看new出的第一个场景 MainMenuScene 看一下场景定义的规范
var MainMenuScene = cc.Scene.extend({
//this._super();重写后这个方法一定要记得写上,要不然导致后续不会执行
//这里是场景的各个函数的重写 场景是是继承自node的所以node中方法也可以被重写,其中这里有onEnter方法,这个方法是不需要主动调用的。
官方给出的解释Event callback that is invoked every time when CCNode enters the 'stage'. 可以看出 这个方法会每次都被调用 当进入到stage后.
var layer = new MainMenuLayer();
this.addChild(layer);这个场景只有一个层,最后将这个层假如到这个场景,由于只有一个层所以都不需要指定执行的顺序。
})
下面看MainMenuLayer,层的规范定义是
var MainMenuLayer = cc.Layer.extend({
// 这里可以声明属性
_backgroundLayer : null,
_touchLayer : null,
ctor方法相当于是构造方法,你在new这个层的时候就会被调用。
构造方法中又可以调用其他的方法
这里是调用了addBackgroundLayer和addTouchLayer可以看出是加了两个子层到该层中,这个是按加入的顺序执行的。
})
看一下假如的MMBackgroundLayer和MMTouchLayer层,
MMBackgroundLayer是创建了一个背景层,这个和上一个所讲的层基本一致。里面涉及到了精灵的创建
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
});
可以看出精灵的创建方式 可以指定属性一个其中有anchorPoint 锚点坐标,默认是 (0.5, 0.5) 这就说明这个精灵位于层的中央。x,y是宽和高的概念。
这里涉及到cocos2d的坐标概念
总之openGL的坐标和UI的坐标是不一致的,按openGL坐标来就可以了
坐标中还有模型坐标和世界坐标的概念 其实就是精灵相对的坐标概念
![Upload Paste_Image.png failed. Please try again.]
在游戏场景中有两个Node对象,其中Node1的坐标是(400, 500),大小是300 x 100像素。Node2是放置在Node1中的,它对于Node1的模型坐标是(0, 0),大小是150 x 50像素。
好了继续到MMTouchLayer了,在该层中 用到了cc.audioEngine去播放音乐。
/ 播放背景音乐,true代表循环无限次播放,false表示只播放一次。
if (GC.SOUND_ON){
if (cc.audioEngine.isMusicPlaying()){
return;
}
cc.audioEngine.playMusic(res.mm_bgMusic_mp3, true);
}
然后又去加载了cc.Menu
// 菜单。 对应三者关系:菜单里面有菜单项,菜单项中绑定要执行的方法,并且需要图片去显示。图片就是精灵
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);
构造方法传入的是cc.MenuItemSprite 看其中的一个MenuItemSprite 定义
var newGame = new cc.MenuItemSprite(
newGameNormal,
newGameSelected,
newGameDisabled,
function(){
this.onButtonEffect();
this.flareEffect(flare, this, this.onNewGame);
}.bind(this)
);
里面构造方法传入的也是各个精灵,精灵的创建中用到了cc.rect,看其中的一个。
// 根据rect区域去创建一个精灵,作为下面menuItemSprite显示的图片。
// 因为menuItem有Normal、Selected、Disabled三个状态,所以一个菜单项需要三张纹理图片
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)。
这样的话就渲染出来了两个层,一个背景层,一个菜单层。在菜单切换的过程中还涉及到了 动作 ,函数的回调 、按顺序执行一组动作、 同时执行一组动作
// 定义动作
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);
// 函数回调动作
var onComplete = cc.callFunc(callback, target);
var killflare = cc.callFunc(function () {
this.getParent().removeChild(this,true);
}, flare);
// 按顺序执行一组动作
var seqAction = cc.sequence(opacityAnim, biggerEase, opacDim, killflare, onComplete);
// 同时执行一组动作
var action = cc.spawn(seqAction, easeMove, rotateEase, bigger);
flare.runAction(action);
}
动作比较简单的。 就是精灵的旋转放大缩小等。
cc.callFunc是去执行一个函数,这里有this.getParent().removeChild方法,其实就是去移除创建的层。
cc.sequence是按顺序执行一组动作,传给 cc.spawn 是组合一组动作,然后精灵去执行 flare.runAction(action);
主要看一下GamePlayScene 游戏玩耍这个层,这个层里面涉及到了基础概念cc.spriteFrameCache缓存
cc.spriteFrameCache.addSpriteFrames(res.gp_TextureOpaquePack_plist);
直接这样调用就可以了。这个层假如了两个层GPTouchLayer和GPBackgroundLayer
GPBackgroundLayer比较简单就是放了一张背景图片而已。主要的碰撞动作在GPTouchLayer中
这个里面涉及到比较多和重要的几个概念。scheduleUpdate和schedule
scheduleUpdate相当于是调用层的update方法
// 游戏时时刷新
update:function (dt) {
if (this._state == STATE_PLAYING) {
// UI在这边更新
this.updateUI();
// 敌人在这里面产生,以及界面上
this.moveActiveUnit(dt);
// 碰撞检测
this.checkIsCollide();
// 检测我们的飞船重生
this.checkIsReborn();
// 这个部分被我直接干掉了。。。因为,重复代码,没什么内容
// this._movingBackground(dt);
}
},
schedule是一致去执行一个方法这里一直在执行分数计数方法。
// 分数在这里面加
scoreCounter:function () {
if (this._state == STATE_PLAYING) {
this._time++;
this._levelManager.loadLevelResource(this._time);
}
},
存放分数信息的是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
});
这个里面还有一个比较重要的就是碰撞检测
利用坐标是否落在区域去检测
看代码
// 碰撞坚持
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看名字就可以看出来各个代表什么精灵,既然有碰撞检测那么坐标移动在哪里呢,其实就是在各个精灵中自己定义的action
看BulletSprite这个精灵吧,
update:function (dt) {
// cc.log("这里。。。");
var y = this.y;
this.y = y - this.yVelocity * dt; //不断的移动坐标去达到移动的目的
if (y < 0 || y > GC.h + 10 || this.HP <= 0) {
this.destroy();
}
},
其中还有一些别的方法 死亡啊,新产生啊之类的,这个就是碰撞检测后的逻辑代码,不涉及到基础知识了,这里就过了。
真不容易写完了。这里贴出来试玩地址
http://112.126.90.18/airbarons/
算了还是放顶部吧
go home。