zoukankan      html  css  js  c++  java
  • 使用Cocos2dx-JS开发一个飞行射击游戏

    一、前言

    笔者闲来无事,某天github闲逛,看到了游戏引擎的专题,引起了自己的兴趣,于是就自己捣腾了一下Cocos2dx-JS。
    由于是学习,所谓纸上得来终觉浅,只是看文档看sample看demo,并不会让自己有多大的提升,于是一开始就计划写一个小游戏,以作为自己完成这个阶段学习的一个标志,也算是目标导向吧。

    百牛信息技术bainiu.ltd整理发布于博客园
    完整源码移步Github: https://github.com/RogerKang/JasonAmbition
    Online Demo: http://www.rogerkang.site:3000 (由于需要WebGL的支持,请使用较新版本的Chrome或者Firefox打开)
    本游戏的图片素材以及特效素材来自腾讯的《全民飞机大战》,仅供学习参考,请勿用于其他用途。
    笔者水平有限,本身也不是专业游戏开发者,如有错漏,还请多多指教。

    二、Cocos2dx-JS

    Cocos2dx的介绍请移步官网: http://www.cocos2d-x.org/
    目前Cocos2dx支持3种开发语言:C++、Lua、 JavaScript,其中C++和Lua是使用较为广泛开发语言,而JS的支持最早是作为独立的项目进行开发的,在3.7之后并入了Cocos2d-x的主repository,成为了官方支持的正式开发语言。
    使用JS开发Cocos2dx游戏的优点:

    1. 跨平台,是唯一可以到达web端的分支,同时也支持编译成iOS和Android原生APP
    2. 开发效率高,开发难度相对较低

    缺点:

    1. 运行效率相对较低
    2. JS-binding在功能和文档以及开发者的使用经验上仍然落后于C++和Lua

    阅读本文之前,你最好具备以下知识基础:

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

    三、游戏简介


    游戏菜单

    游戏过程

    这个游戏是一个简单的飞行射击游戏,主要使用鼠标点击页面(移动设备就是手指点击屏幕)控制飞机的飞行,从而实现攻击敌人,回避攻击,获取增强效果物品等常见的行为。
    具体细节的可以到Online Demo去看一看。

    四、整体设计

    1. Game Scene

    游戏总共有3个scene;Menu、About、Game,可以理解为不同的功能放在不同的场合之中,其中最重要的是菜单(Menu)场景和游戏(Game)场景,又以游戏场景最为核心,当我们控制飞机进行游戏的时候,实际上就是在于游戏场景在进行交互,此时我们看到的飞机,敌人,子弹,特效,背景等等一切,都是游戏场景在承载。

    2. Game Layer

    Layer的层次比Scene低一级,通常是一个scene会包含一个或者多个layer(当然也可以一个都没有)。
    通常不同的内容可以放在不同的layer中,比如游戏场景里面,就使用了一个独立的layer来显示飞机的血量和得分。
    本游戏只有3个layer,GameLayer,GameInfo 和 GameOverLayer,顾名思义,一个作为游戏场景的承载,一个显示飞机的血量和得分,一个在飞机死亡的时候显示相关的信息。

    3. Game Object

    使用了面向对象的设计来组织游戏的各个对象:
    Plane:玩家控制的飞机
    Enemy:敌人的基类,提供了公共行为接口,包括回收与重用的接口
    Bullet:子弹的基类,提供了公共行为接口,包括回收与重用的接口
    ./Enemies:扩展Enemy基类,不同的敌人有自己的行为
    ./Bullets:扩展Bullet基类,不同的子弹的效果自己定义
    ./Boss:扩展Enemy基类,实际上就是特殊的敌人作为Boss登场

    4. Manager

    设计了若干个manager来组织游戏的各个方面:
    BulletManager:每帧更新子弹的位置,检测碰撞和伤害,回收超出屏幕的子弹
    EnemyManager:控制敌人和Boss的产生,检测敌人与飞机的碰撞,控制敌人的攻击,回收敌人
    ItemManager:控制游戏中随机增强item的产生,并应用其效果
    MusicManager:音效的控制
    ParticleManager:特效的控制,包括被攻击的特效,item的特效,Boss特殊攻击的特效
    StageManger:控制游戏阶段,比如常规阶段,boss阶段等等

    五、启动游戏

    启动游戏,我们需要设置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); // 设置分辨率
     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); //标题文本
     gameName.x = winSize.width/2;
     gameName.y = winSize.height+100;
     var gameNameAction = cc.moveTo(3, cc.p(winSize.width/2, winSize.height-200)); //让标题动起来
     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); //创建按钮并绑定回调事件
     var menu = new cc.Menu(startGameButton);
     this.addChild(menu);
     },
     startGame: function(){
     trace('Game start..');
     cc.director.runScene(new AboutScene()); //点击按钮后跳到About Scene
     }
    });

    在这个场景里面,我们加载了背景图片,播放了BGM,设置了会移动的标题,并在标题停止移动后显示开始按钮,在按钮上绑定回调事件,点击后跳转到About Scene。

    七、About Scene

    About Scene只是一段文本,3秒后跳转到游戏场景。

    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.
    He want to kidnap all girls around the earth.
    ";
     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的设计是最为复杂的,因为是游戏交互的场所,所以其之上需要处理的事情也最多。
    我们需要一点一点说。
    GameScene的执行流程:(简书不支持流程图,有兴趣的同学可以把下面的代码复制到支持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. 帧

    首先解释帧的概念。
    对于一个游戏而言,连贯的画面其实是有快速切换的帧造成的,而每一帧之间会有细小的变化,连续播放帧就会形成动作。而帧之间的变化,就是我们的代码需要计算和处理的。
    简而言之,如果你需要一个物体移动到某个地方,那你的代码就应该在每一帧里面对他的坐标进行一些改变,从而实现移动这个效果(当然,任何一个优秀的游戏引擎,这种基本的动画是不需要我们自己去做帧间的处理的,我们只需要调用一次,这个效果就会在它所进行的时间内在每帧进行更新)。

    在Scene的ctor函数里面调用this.scheduleUpdate 方法,就会使用默认的update方法去进行帧间计算,我们在update方法里面需要做的事情:

    • 更新飞机的位置
    • 更新敌人的位置
    • 更新子弹的位置
    • 更新item的位置
    • 判断飞机与敌人、子弹、item的碰撞,并应用效果
    • 等等

    其中,实际上我们只需要真的去在每一帧都处理的只有“更新子弹的位置” 以及 “判断飞机与敌人、子弹、item的碰撞,并应用效果”其余的都只需要在他们真的需要更新的时候声明一次action,之后的每帧,cocos都会帮我们去移动,而不需要我们再去计算改怎么移动。
    如果说“判断飞机与敌人、子弹、item的碰撞,并应用效果”的处理是理所当然的,那么为什么“更新子弹的位置”不能交给cocos去处理?事实上大多数的子弹的位置也都是有cocos去应用action,我们只需要在子弹发生的时候告诉cocos接下来怎么去移动子弹就可以了,但是有例外的情况:追踪弹。它的移动和敌人相关,因此在敌人的位置、存活发生变化的时候可能需要改变它的移动,这个时候就需要我们在帧间进行处理:计算出新的移动方向,告诉cocos取消之前的action,应用新的action。

    update函数的执行时间与游戏的帧率是息息相关的,因为游戏的每一帧都是根据这个函数的计算结果来产生的,如果它的执行时间太长,就会导致帧率下降。
    举个例子,如果update的执行时间为0.1秒,那么这个游戏最高也只可能是10fps,因为在1秒内它最多执行10次,给出10次的结果,从而cocos也只能由此渲染10帧的画面。

    总而言之,如果你想实现什么效果,那就在update function里面计算这个效果在每一帧里面的作用,然后让cocos去把帧渲染出来,联合起来就可以实现你要的效果。

    2. 游戏背景

    首先,作为一个飞行射击游戏,我们需要一个可以滚动的背景。自然,准备一张高度足够的图片,让它从上往下匀速下移是可以实现这个效果的,但是实际上应该不会有人会这样做。
    在这里,实际上我们只需要一张图片,这张图片刚好可以覆盖整个游戏的可是窗口就可以了:


    游戏背景图


    如果你仔细观察,你会发现,这张图片的上面和下面是可以接合的,也就是说,如果你在这张图片的正上方再放一张同样的图片,你会发现这两张图片的景色、河流都是刚好接合起来,看不出是两种图片,而像是一张图片。
    事实上,我们正式利用这样的图片来实现背景的无限滚动。
    我们只需要在一开始就放一张图片铺满显示窗口,然后在窗口的正上方再放一张一样的图片,让它们同时匀速往下移动,等到第一张图片完全离开窗口,而第二种刚好铺满窗口的时候,在下一帧恢复它们的初始位置,不断重复这个过程,那么看起来背景就是在不断的移动。

     _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)){        //当第一张图片的y小于负的二分之一屏幕高度,就是它刚好离开视窗的时候,此时恢复两张图片的初始位置
    
     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上就会出现飞机的图片,但是仅仅只是这样是不够的,我们需要这个飞机可以根据我们的点击进行移动,原理也很简单,就是获取点击的坐标,然后让飞机移动过去,这里我们需要监听两个事件:

    • Touch
    • MouseDown

    在game scene里监听事件:

     if("touches" in cc.sys.capabilities){
     cc.eventManager.addListener({
     event: cc.EventListener.TOUCH_ONE_BY_ONE,
     onTouchBegan: this._onTouchBegan.bind(this)    //处理触摸的回调
     }, this);
     } else {
     cc.eventManager.addListener({
     event: cc.EventListener.MOUSE,
     onMouseDown: this._onMouseDown.bind(this)    //处理鼠标点击的回调
     }, this);
     }

    在回调里使用点击的坐标让飞机移动:

     _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. 添加/管理敌人

    考虑到敌人可能有多种多样,每种敌人会有自己的子弹、运动轨迹,所以我们先定义一个基类,里面定义了敌人的通用接口,具体的实现在每个敌人的之类里面完成。

    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的子类,是一个具体的敌人)的部分实现:

    var Alpha = Enemy.extend({
     image:"res/enemy/alpha.png",
     allowShot:true,
     blood:null,
     score:100,
     explosionHarm:100,
     ctor:function(scene, layer){      //初始化Alpha,并调用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);       //用一个数组存储所有的敌人,在之后的攻击判定、清理回收等会用到
     },
     appear:function(){                 //登场,在可是窗口的上方的一个随机位置里面出现
     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);        //移动敌人
     }
     }

    这样,如果我们创建一个Alpha的实例,那么它就会出现在游戏场景,并且从游戏窗口的上方的某个位置匀速地向下移动。

    EnemyManager

    为了更好地管理敌人的生成和回收,我们创建了一个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) {      //负责添加新敌人
     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){                              //命令所有敌人进行一次攻击
     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. 子弹系统

    飞机与敌人都可以发射子弹,我们需要解决的问题是:

    • 子弹的生成、发射(位置,角度)、速度、伤害,动画
    • 子弹与飞机/敌人的碰撞检测
    • 子弹的回收

    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){        //根据给出的type初始化bullet
     this.type = type;
     this.plane = plane;
     this.layer = layer;
     this.scene = scene;
     this.bullets = [];
     this._shot();                            //初始化完毕后马上发射子弹,_shot方法应该在子弹之类里面具体实现
     },
     _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是一个具体实现的子弹之类,设置了子弹的图片、数量、发射位置,实现了_shot方法:

    var BasicBullet = Bullet.extend({
     BulletType:{
     "basic" : "res/bullet/bullet_basic.png"     //子弹的图片
     },
     BulletLocation : {
     "basic" : [{x:0, y:0}]                  //数组的元素数量代表子弹的数量,x和y坐标代表某个子弹的发射位置
     },
     BulletOwner:"enemy",
     harm : 5,                               //子弹的伤害
     _shot:function(){                        //_shot方法的作用:创建子弹Sprite,添加到scene中,调用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){   //实现了每种子弹自己的运动方式,不同的子弹的不同行为在这里定义
     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的飞行轨迹从初始化就已经决定了,以后不再改变,直到发生碰撞或者离开窗口被回收。
    但是也可以实现更复杂的飞行轨迹,比如跟踪弹(MissileBullet)就会跟踪离它最近的敌人的位置,这个子弹的运动就需要每帧更新):

    MissileBullet

    var MissileBullet = Bullet.extend({
    updatePerFrames:true,      //是否需要每帧更新,对于MissileBullet来说是true
    allowSwitchEnemy:false,   //是否允许在原追踪敌人毁灭后更换瞄准目标
    /* ... */
     aimedEnemy:function(bulletX, bulletY, oldX, oldY, bullet){        //瞄准敌人,每帧都会计算当前离追踪弹最接近的敌人是哪一个,在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){     //每帧更新,每一帧都根据瞄准的敌人的位置调整子弹的运动轨迹
     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;
     }
    })

    发射子弹

    在飞机和敌人上发射子弹,是通过调用plane._shot()和enemy.attack()来进行的,他们的实现其实是类似的:
    飞机的_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秒设计一次
     this.allowShot = true;
    
     }
     .bind(this), 0.2);
    
     }

    碰撞检测

    飞机发射的子弹和敌人之间,以及敌人的子弹与飞机之间,需要进行碰撞检测,如果发射了碰撞,就需要回收子弹,计算伤害(并且根据血量摧毁敌人/飞机,同时显示特效等)。
    碰撞检测的方法有很多,这里我们只使用了最简单的方法:计算子弹和机体的距离,就是根据两个目标的x轴和y轴运用勾股定理计算距离,当距离小于一定的阈值(threshold)时判定为发生碰撞。
    碰撞一共有3种:

    1. 子弹与机体
    2. 敌人与飞机
    3. 物品与飞机

    这3种碰撞分别由3个manager来执行检查:BulletManager,EnemManager,ItemManager。
    以BulletManager的子弹与机体的碰撞检测为例:

     collisionCheck:function(scene, layer){          //分别进行飞机与敌人的子弹,飞机的子弹与敌人的碰撞检测,计算伤害,在血量为0时调用机体的explode方法,回收敌人,同时进行子弹回收
     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;
     }

    子弹回收

    碰撞了的子弹就会被回收,还有一种情况需要回收:没发生碰撞但是飞出了可视窗口。
    这种情况在BulletManager里面进行了每帧检测和处理:

     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)系统

    物品系统是飞行射击游戏一个常见的系统,具体来说就是可以控制飞机去吃一些随机生成的物品,从而获得一些额外的增强效果。
    笔者同样给游戏添加了这个系统。

    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;
     }
     }

    添加物品

    添加物品其实和添加敌人的做法是类似的,就是在一个随机的位置上添加一个物品,并让它飞行。与敌人不同之处在于,物品只可能与飞机发生碰撞,既不会与敌人也不会与子弹发生碰撞。

     generateItem:function(scene, layer, plane){
    
     if(this.allowAddItem == false)
     return ;
    
     if(this.allowAddItem == true) {
     var item = this.itemName[new Date().getTime() % this.itemName.length];      //随机产生物品
     //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(){              //物品产生的间隔为基础间隔+随机时间
     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);                        //如果物品与飞机发生了碰撞,那么就应用物品的效果
    
     layer.removeChild(checkItem);
     scene.items.splice(i, 1);
     i--;
     }
    
     }
     }

    物品效果

    物品效果分两种:即时效果和延时效果,前者有加血、一次性范围攻击等,后者有护罩、增强型子弹等。

     takeEffect:function(scene, itemName, plane){
    
     switch(itemName){     //根据物品的名称选择要加的效果
     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){                  //根据效果的名称决定效果特效的起始位置
    
     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){        //其余的与飞机当前位置相关
    
     return {
     x:x,
     y:y
     };
    
     };followTarget = true;break;          //followTarget 为true就以为则特效药跟随飞机
     }
     var ccbNode = cc.game.particleManager.showParticle(effectName, scene, plane, locationFunc,followTarget, duration);            //使用粒子系统来展示物品的应用特效
     if(followTarget == true){                      //需要跟随的效果加入到飞机的addedEffects数组中
     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来在每帧执行引用效果,例如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. 特效系统

    特效系统负责产生并管理两种特效:Particle System和帧动画。
    Particle System是设定一些规则,使用纹理图片随机产生一些粒子特效,这些效果是随机的,不可重复的;
    帧动画是使用一些图片连续播放产生一些特效效果,这些特效每一次播放都是相同的。
    本游戏中,飞机被击中时产生的火花效果是Particle System产生的,而物品效果,Boss的激光以及机体爆炸则是帧动画。
    Particle Manager的作用:

    1. 在给定的位置上显示特效
    2. 回收结束的特效

    Particle Manager中有3个show方法,分别处理3种特效:

    1. showParticle:物品特效
    2. showFire:机体被击中的特效
    3. showLaser:Boss的激光射击特效

    在需要显示特效的时候,例如子弹碰撞检测判定为击中、敌人血量低于0要爆炸、Boss要发射激光攻击、飞机吃到物品等等,就会调用Particle Manager相应的方法来显示特效。

    Particle System

    产生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);

    想要制作自定义的特效,可以去这个网站制作:
    http://www.onebyonedesign.com/flash/particleeditor/

    帧动画

    对于本游戏的帧动画,有两个存储格式:plist,ccbi。
    其中ccbi文件是cocos2dx所支持的特效文件,在C++和Lua使用广泛,但是JS中暂时找不到使用sample,在官方文档中也找不到相应的API,在stackoverflow中也没有相应的解答。
    但是笔者在cocos2dx-js的最新版本的源码中找到了ccbi相关的模块,所以最终是通过阅读这个模块的源码来得知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系统

    所谓stage系统,其实就是指游戏的阶段管理。
    众所周知,一个游戏一般不会一上来就是打Boss,也不会一上来就是最高难度,会有一个阶段的推进。
    所以笔者设计了Stage系统来控制游戏的阶段过渡。

    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都有自己的执行方法,主要是调整了一些运行配置,诸如敌人产生间隔等
    
     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){        //默认的结束stage,游戏结束
    
     //console.log("End stage.");
     //cc.director.pause();
     new GameOverLayer(scene);
    
     },
     normalStage:function(scene, layer, plane){      //常规阶段
    
     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形式发布,也可以编译成Android和iOS的原生App发行。但是实际上,要想在移动平台获得web的运行效果,其中有大量的调优和适配工作。有兴趣的同学可以自己研究一下。

    十、总结

    至此,笔者的小游戏就大致介绍完了,更加具体的细节,需要读者在学习了Cocos2dx-JS之后再参考源码。本文只是一个大致思路的说明。
    笔者学习Cocos2dx-JS纯属偶然,更直接的原因其实是在亚马逊上看到了相关的教程,于是就买了,看了,然后就写了。这其中自然会遇到一些坑,但是在学习新知识的过程中,在不断思考不断改进中收获成长却也是学习的最大乐趣。
    这篇简单的教程是自己的小小的总结,如果能帮到大家或者让大家看到后能产生一点点学习新知识的兴趣,也就足够了。



    作者:RogerKang
    链接:http://www.jianshu.com/p/61fbcf748ee3
    來源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    正则表达式中?=和?:和?!的理解
    Python isinstance() 函数
    什么是REST架构
    [iOS常见问题] 关于使用QQ做第三方登录的问题!
    js与webview 常用交互代码
    AFNetworking 使用 核心代码
    NSString / NSData / char* 类型之间的转换
    集合视图 代码
    IOS面试问题总结
    UIViewController的生命周期及iOS程序执行顺序
  • 原文地址:https://www.cnblogs.com/bainiu/p/7589097.html
Copyright © 2011-2022 走看看