Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

微信小游戏体验之打飞机改造计划 #3

Open
maple-leaf opened this issue Dec 31, 2017 · 0 comments
Open

微信小游戏体验之打飞机改造计划 #3

maple-leaf opened this issue Dec 31, 2017 · 0 comments

Comments

@maple-leaf
Copy link
Owner

maple-leaf commented Dec 31, 2017

微信小游戏推出已有几天了,这个功能对小程序和小游戏的推动影响不用多说,大家赶紧摩拳擦掌往上撸就可以了。关于如何开发官方文档已经说明了,这篇则是对官方的打飞机demo一些小改造。

开发预备式

  1. 下载最新版本的微信开发者工具(v1.02.1712280)
  2. 根据官方文档说明,目前不提供公开注册。因此目前只能使用无AppID模式进行体验
  3. 为了让HTML5游戏轻松接入,官方提供了Adapter。这个的作用就是提供HTML5写法和wx写法的全局转换层。

打飞机小游戏

使用无AppID模式创建一个微信小游戏后可以看到官方demo,其中入口文件和配置文件:game.jsgame.jsongame.js引入并初始化包含整个打飞机的游戏场景、参与者(玩家飞机和敌方飞机)、游戏逻辑的主函数的main.js。在main.js中我们可以发现由于Adapter的存在,这里的代码和我们平常的代码写法没什么差异了。游戏的主逻辑如下图:

image

在loop中,玩家每隔20帧射一次,每隔60帧生成新的敌机。每帧检查玩家和敌机是否死亡,玩家死亡游戏结束,敌机死亡分数+1。只有玩家可以射击,且射击方式固定,通过躲避敌机生存。接下来我们针对这些进行改造,提升游戏的可玩性和挑战性。

玩家升级计划

  1. 玩家初始等级为1,玩家可通过击杀敌机升级,每击落30敌机升级一次
  2. 玩家每升级一次,增加一个射击口
  3. 玩家最多升级两次

首先用编辑器打开player/index.js,将等级逻辑加入到玩家的类中。

export default class Player extends Sprite {
  constructor() {
    super(PLAYER_IMG_SRC, PLAYER_WIDTH, PLAYER_HEIGHT)

    // 玩家默认处于屏幕底部居中位置
    this.x = screenWidth / 2 - this.width / 2
    this.y = screenHeight - this.height - 30

    // 用于在手指移动的时候标识手指是否已经在飞机上了
    this.touched = false

    this.bullets = []

    // 初始化事件监听
    this.initEvent()

    this.playerLevel = 1;
  }

  get level () {
    return this.playerLevel;
  }
  set level (level) {
    this.playerLevel = Math.min(level, 3);
  }

接下来在main.jsupdate函数加入升级逻辑。

// 其他代码...

    update() {
        this.bg.update();

        databus.bullets.concat(databus.enemys).forEach(item => {
            item.update();
        });

        this.enemyGenerate();

        this.player.level = Math.max(1, Math.ceil(databus.score / 30));

        this.collisionDetection();
    }

// 其他代码...

好的,到此玩家已经可以正常升级了。那么该给予玩家奖励品了。在player/index.jsshoot函数中我们修改射击的逻辑。玩家1级时只有中间的射击口,2级有左边和中间的射击口,3级有左中右三个射击口。

// ...其他代码

    /**
     * 玩家射击操作
     * 射击时机由外部决定
     */
    shoot() {


      for(let i = 0; i < this.level; i++) {
        const bullet = databus.pool.getItemByClass('bullet', Bullet);
        const middle = this.x + this.width / 2 - bullet.width / 2;
        const x = !i ? middle : (i % 2 === 0 ? middle + 30 : middle - 30);
        bullet.init(
          x,
          this.y - 10,
          10
        )

        databus.bullets.push(bullet)
      }
    }

// ...其他代码

武器的最终形态如图, 这时候的玩家已经可以为所欲为了<_<,实际上都不需要躲避了。。。:

image

敌人的反击号角

为了对抗愚昧的玩家,不让他们为所欲为,最后没兴趣玩下去~~,敌机装备武器,反击开始。

首先敌机的子弹是向下,所以复制一份images/bullet.png,并颠倒保存为images/bullet-down.png, 然后我们重用js/player/bullet.js,在构造函数处增加敌机的子弹配置项,并修改敌人子弹更新逻辑。

const BULLET_IMG_SRC = 'images/bullet.png'
const BULLET_DOWN_IMG_SRC = 'images/bullet-down.png'
const BULLET_WIDTH   = 16
const BULLET_HEIGHT  = 30

const __ = {
    speed: Symbol('speed')
}

let databus = new DataBus()

export default class Bullet extends Sprite {
    constructor({ direction } = { direction: 'up' }) {
        super(direction === 'up' ? BULLET_IMG_SRC : BULLET_DOWN_IMG_SRC, BULLET_WIDTH, BULLET_HEIGHT)
       
        this.direction = direction;

// 其他代码...

    // 每一帧更新子弹位置
    update() {
        if (this.direction === 'up') {
            this.y -= this[__.speed] 
            
            // 超出屏幕外回收自身
            if ( this.y < -this.height )
                databus.removeBullets(this)
        } else {
            this.y += this[__.speed]

            // 超出屏幕外回收自身
            if ( this.y > window.innerHeight + this.height )
                databus.removeBullets(this)
        }
    }
}

接着在js/npc/enemy.js结尾部分为敌人装备武器, 子弹速度为敌人自身速度+5

import Animation from '../base/animation'
import DataBus   from '../databus'
import Bullet from '../player/bullet';

const ENEMY_IMG_SRC = 'images/enemy.png'
// 其他代码...

  update() {
    this.y += this[__.speed]

    // 对象回收
    if ( this.y > window.innerHeight + this.height )
      databus.removeEnemey(this)
  }

  /**
   * 敌机射击操作
   * 射击时机由外部决定
   */
  shoot() {
      const bullet = databus.pool.getItemByClass('bullet', Bullet);
      bullet.init(
          this.x + this.width / 2 - bullet.width / 2,
          this.y + 10,
          this[__.speed] + 5
      );

      databus.bullets.push(bullet);
  }
}

接下来,在js/main.js中加入敌机的射击逻辑,敌机移动5次、60次时设计。

// 其他代码...
 let ctx = canvas.getContext("2d");
 let databus = new DataBus();

const ENEMY_SPEED = 6;
// 其他代码...

    /**
     * 随着帧数变化的敌机生成逻辑
     * 帧数取模定义成生成的频率
     */
    enemyGenerate(playerLevel) {
        if (databus.frame % 60 === 0) {
            let enemy = databus.pool.getItemByClass("enemy", Enemy);
            enemy.init(ENEMY_SPEED);
            databus.enemys.push(enemy);
        }
    }

// 其他代码...

    // 实现游戏帧循环
    loop() {
        databus.frame++;

        this.update();
        this.render();

        if (databus.frame % 20 === 0) {
            this.player.shoot();
            this.music.playShoot();
        }

        databus.enemys.forEach(enemy => {
            const enemyShootPositions = [
                -enemy.height + ENEMY_SPEED * 5,
                -enemy.height + ENEMY_SPEED * 60
            ];
            if (enemyShootPositions.indexOf(enemy.y) !== -1) {
                enemy.shoot();
                this.music.playShoot();
            }
        });

        // 游戏结束停止帧循环
        if (databus.gameOver) {
            this.touchHandler = this.touchEventHandler.bind(this);
          canvas.addEventListener("touchstart", this.touchHandler);
            this.gameinfo.renderGameOver(ctx, databus.score);

            return;
        }

        window.requestAnimationFrame(this.loop.bind(this), canvas);
    }

这时候我们发现,由于不明宇宙的干扰射线的影响,玩家和敌机的子弹不受控制的乱飞。接下来我们就来恢复世界的秩序吧 ;

经侦测发现是对象池pool的获取逻辑问题导致子弹不受控问题,我们需要区分获取玩家、每个敌机的子弹

首先,对象获取我们加入对象属性的判断,当有传入对象属性时,我们获取所有属性值一致的已回收对象,若没有找到或者对象池为空时,则用属性创建新对象

  /**
   * 根据传入的对象标识符,查询对象池
   * 对象池为空创建新的类,否则从对象池中取
   */
  getItemByClass(name, className, properties) {
    let pool = this.getPoolBySign(name)

    if (pool.length === 0) return new className(properties);
   
    if (!properties) return pool.shift();
   
    const index = pool.findIndex(item => {
        return Object.keys(properties).every(property => {
            return item[property] === properties[property];
        });
    });
    return index !== -1 ? pool.splice(index, 1)[0] : new className(properties)
  }

相应的我们需要给每个子弹设置归属,在js/player/bullet.jsBullet类修改constructor

export default class Bullet extends Sprite {
    constructor({ direction, owner } = { direction: 'up' }) {
        super(direction === 'up' ? BULLET_IMG_SRC : BULLET_DOWN_IMG_SRC, BULLET_WIDTH, BULLET_HEIGHT)

        this.direction = direction;

        this.owner = owner;
    }

接着修改js/player/index.jsshoot,为其中创建的bullets提供归属

    /**
     * 玩家射击操作
     * 射击时机由外部决定
     */
    shoot() {
      for(let i = 0; i < this.level; i++) {
        const bullet = databus.pool.getItemByClass('bullet', Bullet, { direction: 'up', owner: this });

同样处理js/npc/enemy.jsshoot

  /**
   * 敌机射击操作
   * 射击时机由外部决定
   */
  shoot() {
      const bullet = databus.pool.getItemByClass('bullet', Bullet, { direction: 'down', owner: this });

最后处理js/databus.jsremoveBullets的回收逻辑

  /**
   * 回收子弹,进入对象池
   * 此后不进入帧循环
   */
  removeBullets(bullet) {
    const index = this.bullets.findIndex(b => b === bullet);

    bullet.visible = false

    this.bullets.splice(index, 1);

    this.pool.recover('bullet', bullet)
  }
}

这时候敌我的子弹就恢复正常了。不过这时候玩家中弹并不会死亡,现在来让玩家Go Die吧。在js/main.jscollisionDetection我们判断增加每一颗子弹如果是敌方的,就判断其是否打中玩家,是则游戏结束。玩家的子弹判断保持不变。

    // 全局碰撞检测
    collisionDetection() {
        let that = this;

        databus.bullets.forEach(bullet => {
            for (let i = 0, il = databus.enemys.length; i < il; i++) {
                let enemy = databus.enemys[i];
                if (bullet.owner instanceof Enemy) {
                    databus.gameOver = this.player.isCollideWith(bullet);
                } else if (!enemy.isPlaying && enemy.isCollideWith(bullet)) {
                    enemy.playAnimation();
                    that.music.playExplosion();

                    bullet.visible = false;
                    databus.score += 1;

                    break;
                }
            }
        });

到此整个简单改造计划就结束了,以后还可以添加武器系统,boss战等等。下面是改造后的游戏动图录屏

demo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant