微信小游戏推出已有几天了,这个功能对小程序和小游戏的推动影响不用多说,大家赶紧摩拳擦掌往上撸就可以了。关于如何开发官方文档已经说明了,这篇则是对官方的打飞机 demo 一些小改造。
(v1.02.1712280)
- 微信开发者工具
使用无 AppID 模式创建一个微信小游戏后可以看到官方 demo,其中入口文件和配置文件:game.js 和 game.json。game.js 引入并初始化包含整个打飞机的游戏场景、参与者(玩家飞机和敌方飞机)、游戏逻辑的主函数的 main.js。在 main.js 中我们可以发现由于 Adapter 的存在,这里的代码和我们平常的代码写法没什么差异了。游戏的主逻辑如下图:
在 loop 中,玩家每隔 20 帧射一次,每隔 60 帧生成新的敌机。每帧检查玩家和敌机是否死亡,玩家死亡游戏结束,敌机死亡分数 + 1。只有玩家可以射击,且射击方式固定,通过躲避敌机生存。接下来我们针对这些进行改造,提升游戏的可玩性和挑战性。
首先用编辑器打开 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.js 的 update 函数加入升级逻辑。
- // 其他代码...
- 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.js 的 shoot 函数中我们修改射击的逻辑。玩家 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,并颠倒保存为
, 然后我们重用 js/player/bullet.js,在构造函数处增加敌机的子弹配置项,并修改敌人子弹更新逻辑。
- images/bullet-down.png
- 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.js 中 Bullet 类修改 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.js 的 shoot,为其中创建的 bullets 提供归属
- /**
- * 玩家射击操作
- * 射击时机由外部决定
- */
- shoot() {
- for(let i = 0; i < this.level; i++) {
- const bullet = databus.pool.getItemByClass('bullet', Bullet, { direction: 'up', owner: this });
同样处理 js/npc/enemy.js 的 shoot
- /**
- * 敌机射击操作
- * 射击时机由外部决定
- */
- shoot() {
- const bullet = databus.pool.getItemByClass('bullet', Bullet, { direction: 'down', owner: this });
最后处理 js/databus.js 中 removeBullets 的回收逻辑
- /**
- * 回收子弹,进入对象池
- * 此后不进入帧循环
- */
- 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.js 的 collisionDetection 我们判断增加每一颗子弹如果是敌方的,就判断其是否打中玩家,是则游戏结束。玩家的子弹判断保持不变。
- // 全局碰撞检测
- 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://segmentfault.com/a/1190000012656215