Phaser实现飞机大战

先给一个线上地址,大家体验一下,鼠标拖动飞机可以进行移动。http://game.webxinxin.com/plane/

进行简单分析之后,我们发现飞机大战主要有三个场景,开始场景,游戏中场景和结束场景。

开始场景

开始场景其实很简单,一个背景,一个飞机,一个开始按钮和一个版权说明。背景,我们使用tileSprite,因为背景移动的时候,我们要使用它的autoScroll方法。版权说明,我们直接使用image,因为它没有任何动作。飞机我们使用sprite,它有一个帧动画要播放,而按钮,我们使用button,点击的时候,跳转到游戏中场景。

game.States.main = function() {
  this.create = function() {
    // 背景
    var bg = game.add.tileSprite(0, 0, game.width, game.height, 'background');
    // 版权
    this.copyright = game.add.image(12, game.height - 16, 'copyright');
    // 我的飞机
    this.myplane = game.add.sprite(100, 100, 'myplane');
    this.myplane.animations.add('fly');
    this.myplane.animations.play('fly', 12, true);
    // 开始按钮
    this.startbutton = game.add.button(70, 200, 'startbutton', this.onStartClick, this, 1, 1, 0);
  };
  this.onStartClick = function() {
    game.state.start('start');
  };
};

游戏中场景

游戏中场景是最复杂的一个场景,我们同样需要添加背景,添加飞机,这些和上面是一样的。然后,我们让背景开始滚动,给飞机加上一个tween动画,当动画结束的时候,开始发射子弹,显示分数,敌机也开始进入。同时,还要处理敌机被子弹打中时的逻辑,敌机爆炸的效果,以及我方被子弹打中的逻辑和我方的爆炸效果等等。所以我们一点一点来分析。

首先打开物理引擎,然后把背景加进来,让它自动滚动。然后把我们的飞机加进来,让它执行帧动画,同时设置它的碰撞规则,让它不能够超出游戏区域的边界。

在进入该场景的时候,我们让飞机直接进行一个tween动画,从中间到达底部,然后开始调用onStart

this.create = function() {
  // 物理系统
  game.physics.startSystem(Phaser.Physics.ARCADE);
  // 背景
  var bg = game.add.tileSprite(0, 0, game.width, game.height, 'background');
  bg.autoScroll(0, 20);
  // 我的飞机
  this.myplane = game.add.sprite(100, 100, 'myplane');
  this.myplane.animations.add('fly');
  this.myplane.animations.play('fly', 12, true);
  game.physics.arcade.enable(this.myplane);
  this.myplane.body.collideWorldBounds = true;
  this.myplane.level = 2;
  // 动画
  var tween = game.add.tween(this.myplane).to({y: game.height - 40}, 1000, Phaser.Easing.Sinusoidal.InOut, true);
  tween.onComplete.add(this.onStart, this);
};

首先需要处理的就是发射子弹和鼠标拖拽。鼠标拖拽在phaser中十分简单,只要设置inputEnabledtrue,然后enableDrag就行了。飞机不能拖拽出屏幕,当然,这一点我们已经在之前确保过了。

子弹的处理倒是有一定的技巧。首先要考虑的是,子弹要从飞机的首部发射出来,还得连续发射,超出边界的时候需要释放,如果一直进行子弹对象的创建和销毁,会一直进行内存的创建与回收,这样会浪费很多资源。

对于这种情况,一个比较通用的解决办法是对象池。它的原理是,先初始化一批对象,放到一个池子里,然后需要使用的时候,从池子里拿一个出来,再使用完成后,又放回池子里。这样就减少了不必要的内存创建与回收。this.mybullets.createMultiple(50, 'mybullet');就是一次性创建50个子弹,相当于一个对象池。然后我们设置该组的对象,在超过游戏边界的时候,进行回收。

this.onStart = function() {
  // 我的子弹
  this.mybullets = game.add.group();
  this.mybullets.enableBody = true;
  this.mybullets.createMultiple(50, 'mybullet');
  this.mybullets.setAll('outOfBoundsKill', true);
  this.mybullets.setAll('checkWorldBounds', true);
  this.myStartFire = true;
  this.bulletTime = 0;
  // 我的飞机允许拖拽
  this.myplane.inputEnabled = true;
  this.myplane.input.enableDrag(false);
};

敌机的逻辑和子弹其实很像,每种敌机,也都是从一个池子里拿出来,然后,当超过边界或者被子弹消灭之后,会被kill,就相当于放回池子里了。

但是每种敌机的逻辑都是一样的,只是里面有一些参数不太一样,所以这里把敌机抽象出来一个类,类中的一些方法也很简单,包括初始化,开火,被敌机打中,产生等等逻辑。这里简单说一下,被打中后消灭的爆炸效果。其实爆炸效果也是使用了对象池,因为同一刻可能存在好几个爆炸点,而爆炸结束后同样需要回收动画资源,所以也通过一个池子进行管理。每次发生爆炸的时候,从池子中拿出一个爆炸的动画对象,然后播放动画,完毕后再将爆炸资源回收。

function Enemy(config) {
  this.init = function() {
    this.enemys = game.add.group();
    this.enemys.enableBody = true;
    this.enemys.createMultiple(config.selfPool, config.selfPic);
    this.enemys.setAll('outOfBoundsKill', true);
    this.enemys.setAll('checkWorldBounds', true);
    // 敌人的子弹
    this.enemyBullets = game.add.group();
    this.enemyBullets.enableBody = true;
    this.enemyBullets.createMultiple(config.bulletsPool, config.bulletPic);
    this.enemyBullets.setAll('outOfBoundsKill', true);
    this.enemyBullets.setAll('checkWorldBounds', true);
    // 敌人的随机位置范围
    this.maxWidth = game.width - game.cache.getImage(config.selfPic).width;
    // 产生敌人的定时器
    game.time.events.loop(Phaser.Timer.SECOND * config.selfTimeInterval, this.generateEnemy, this);
    // 敌人的爆炸效果
    this.explosions = game.add.group();
    this.explosions.createMultiple(config.explodePool, config.explodePic);
    this.explosions.forEach(function(explosion) {
      explosion.animations.add(config.explodePic);
    }, this);
  }
  // 产生敌人
  this.generateEnemy = function() {
    var e = this.enemys.getFirstExists(false);
    if(e) {
      e.reset(game.rnd.integerInRange(0, this.maxWidth), -game.cache.getImage(config.selfPic).height);
      e.life = config.life;
      e.body.velocity.y = config.velocity;
    }
  }
  // 敌人开火
  this.enemyFire = function() {
    this.enemys.forEachExists(function(enemy) {
                            var bullet = this.enemyBullets.getFirstExists(false);
      if(bullet) {
        if(game.time.now > (enemy.bulletTime || 0)) {
          bullet.reset(enemy.x + config.bulletX, enemy.y + config.bulletY);
          bullet.body.velocity.y = config.bulletVelocity;
          enemy.bulletTime = game.time.now + config.bulletTimeInterval;
        }
      }
                   }, this);
  };
  // 打中了敌人
  this.hitEnemy = function(myBullet, enemy) {
    try {
      config.firesound.play();
    } catch(e) {}
    myBullet.kill();
    enemy.life--;
    if(enemy.life <= 0) {
      try {
        config.crashsound.play();
      } catch(e) {}
      enemy.kill();
      var explosion = this.explosions.getFirstExists(false);
      explosion.reset(enemy.body.x, enemy.body.y);
      explosion.play(config.explodePic, 30, false, true);
      score += config.score;
      config.game.updateText();
    }
  };
}

碰撞检测,可以使用phaser中的game.physics.arcade.overlap,也是十分的方便,指定两个对象进行碰撞后,需要回调的函数。

游戏中场景是一个非常重要的场景,也是这个游戏的核心,讲到这里已经差不多了,更加具体的逻辑,大家自己看源码进行分析吧。

结束场景

结束场景相对就比较简单了,背景、版权、飞机、分数、两个按钮,将它们排布好就行了。

在这里想介绍一下分享功能的实现。因为一般来说,我们做的游戏会放到微信中进行分享,分享给我们的小伙伴们。

我的需求是这样的,我希望在分享的时候,弹出一个新的场景,包含二维码,别人可以长按二维码进行识别,然后我诱导别人点击左上角菜单进行分享。当别人分享后,能够出现一个带logo和标题的消息,标题中把分数带进去。

实现的时候发现几个坑,首先,微信只有img标签的图片才能进行长按识别,而phaser是在canvas中进行绘制,所以直接在phaser中画图是没有长按识别功能的;第二,想要让分享的链接带一个图标,需要有一个大小大于300x300的图片,微信会找到链接中的第一个大小大于300x300的图片,把它设置为图标;第三,分享的标题其实就是页面的title


好了,所有的功能都实现完毕了。整个代码大概450js50html,熟练的话,大概一两个小时就能编码完毕,可见用phaser开发游戏,效率之高,速度之快。

只是有一个大多数html5游戏的一个问题,就是在低端安卓机上面性能太差,基本无法正常运行,但是我相信在不久的将来,随着硬件越来越强悍,浏览器内核越来越高级,这个问题一定会被解决。

转载请注明出处: http://www.channingbreeze.com/blog-14.html

你可能感兴趣的:(html,游戏,Phaser)