资讯专栏INFORMATION COLUMN

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

dongfangyiyu / 1654人阅读

摘要:微信小游戏推出已有几天了,这个功能对小程序和小游戏的推动影响不用多说,大家赶紧摩拳擦掌往上撸就可以了。打飞机小游戏使用无模式创建一个微信小游戏后可以看到官方,其中入口文件和配置文件和。

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

开发预备式

下载最新版本的微信开发者工具(v1.02.1712280)

根据官方文档说明,目前不提供公开注册。因此目前只能使用无AppID模式进行体验

为了让HTML5游戏轻松接入,官方提供了Adapter。这个的作用就是提供HTML5写法和wx写法的全局转换层。

打飞机小游戏

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

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

玩家升级计划

玩家初始等级为1,玩家可通过击杀敌机升级,每击落30敌机升级一次

玩家每升级一次,增加一个射击口

玩家最多升级两次

首先用编辑器打开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)
      }
    }

// ...其他代码

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

敌人的反击号角

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

首先敌机的子弹是向下,所以复制一份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战等等。下面是改造后的游戏动图录屏

本文始发于本人的公众号:枫之叶。公众号二维码

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/90576.html

相关文章

  • 无聊吗,写个【飞机大战】来玩吧

    摘要:今天杭州又是大雨,被淋了个落汤鸡,都怪我家大狼狗非要骑电动车,我昨天吐槽要买的帅气的雨衣还没有买不过大雨和飞机大战小游戏更配哦。微信早已正式发布微信内置飞机大战游戏,目前该游戏已经下线。此时,界面中会显示此次玩家的飞机大战分数。showImg(https://user-gold-cdn.xitu.io/2019/5/15/16ab9377884b99f7); 今天杭州又是大雨,被淋了个落汤鸡...

    李涛 评论0 收藏0
  • 无聊吗,写个【飞机大战】来玩吧

    摘要:今天杭州又是大雨,被淋了个落汤鸡,都怪我家大狼狗非要骑电动车,我昨天吐槽要买的帅气的雨衣还没有买不过大雨和飞机大战小游戏更配哦。微信早已正式发布微信内置飞机大战游戏,目前该游戏已经下线。此时,界面中会显示此次玩家的飞机大战分数。 今天杭州又是大雨,被淋了个落汤鸡,都怪我家大狼狗非要骑电动车,我昨天吐槽要买的帅气的雨衣还没有买,不过大雨和飞机大战小游戏更配哦。 这篇文章来自我司的王老吉同...

    MSchumi 评论0 收藏0
  • 无聊吗,写个【飞机大战】来玩吧

    摘要:今天杭州又是大雨,被淋了个落汤鸡,都怪我家大狼狗非要骑电动车,我昨天吐槽要买的帅气的雨衣还没有买不过大雨和飞机大战小游戏更配哦。微信早已正式发布微信内置飞机大战游戏,目前该游戏已经下线。此时,界面中会显示此次玩家的飞机大战分数。 今天杭州又是大雨,被淋了个落汤鸡,都怪我家大狼狗非要骑电动车,我昨天吐槽要买的帅气的雨衣还没有买,不过大雨和飞机大战小游戏更配哦。 这篇文章来自我司的王老吉同...

    BlackMass 评论0 收藏0
  • 微信游戏体验

    摘要:前言前天一个跳一跳小游戏刷遍了朋友圈,也代表了微信小程序拥有了搭载游戏的功能早该往这方面发展了,这才是应该有的形态嘛。作为一个前端,我的大刀早已经饥渴难耐了,赶紧去下一波最新的微信官方开发工具,体验一波小游戏要如何开发。 本文旨在通过分析官方给出的一个飞机大战小游戏的源代码来说明如何进行小游戏的开发。 1.前言 前天一个跳一跳小游戏刷遍了朋友圈,也代表了微信小程序拥有了搭载游戏的功能(...

    elina 评论0 收藏0
  • 游戏开发上手体验 - Cocos Creator

    摘要:但开发的游戏是无法通过网页发给别人在线玩的,更不能做成微信小游戏。它使用作为开发语言,开发出的游戏可以直接生成微信小游戏网页安卓等平台上的版本。 微信群里最大的骚扰源有两种: 一是转发#吱口令#~!@#¥%……&*,长按复制此消息领红包之类的 另一种就是各种小程序和小游戏的分享 前天有同学无意间把一个小游戏分享到了答疑群中,我看了一下,其实游戏的代码逻辑并不复杂(简化版的跳一跳,套上个...

    zhiwei 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<