微信小游戲推出已有幾天了,這個(gè)功能對(duì)小程序和小游戲的推動(dòng)影響不用多說,大家趕緊摩拳擦掌往上擼就可以了。關(guān)于如何開發(fā)官方文檔已經(jīng)說明了,這篇?jiǎng)t是對(duì)官方的 打飛機(jī) demo一些小改造。
使用 無AppID 模式創(chuàng)建一個(gè)微信小游戲后可以看到官方demo,其中入口文件和配置文件: game.js 和 game.json 。 game.js 引入并初始化包含整個(gè) 打飛機(jī) 的游戲場景、參與者(玩家飛機(jī)和敵方飛機(jī))、游戲邏輯的主函數(shù)的 main.js 。在 main.js 中我們可以發(fā)現(xiàn)由于 Adapter 的存在,這里的代碼和我們平常的代碼寫法沒什么差異了。游戲的主邏輯如下圖:
在loop中,玩家每隔20幀射一次,每隔60幀生成新的敵機(jī)。每幀檢查玩家和敵機(jī)是否死亡,玩家死亡游戲結(jié)束,敵機(jī)死亡分?jǐn)?shù)+1。只有玩家可以射擊,且射擊方式固定,通過躲避敵機(jī)生存。接下來我們針對(duì)這些進(jìn)行改造,提升游戲的可玩性和挑戰(zhàn)性。
首先用編輯器打開 player/index.js ,將等級(jí)邏輯加入到玩家的類中。
export default class Player extends Sprite {
constructor() {
super(PLAYER_IMG_SRC, PLAYER_WIDTH, PLAYER_HEIGHT)
// 玩家默認(rèn)處于屏幕底部居中位置
this.x = screenWidth / 2 - this.width / 2
this.y = screenHeight - this.height - 30
// 用于在手指移動(dòng)的時(shí)候標(biāo)識(shí)手指是否已經(jīng)在飛機(jī)上了
this.touched = false
this.bullets = []
// 初始化事件監(jiān)聽
this.initEvent()
this.playerLevel = 1;
}
get level () {
return this.playerLevel;
}
set level (level) {
this.playerLevel = Math.min(level, 3);
}
接下來在 main.js 的 update 函數(shù)加入升級(jí)邏輯。
// 其他代碼...
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();
}
// 其他代碼...
好的,到此玩家已經(jīng)可以正常升級(jí)了。那么該給予玩家獎(jiǎng)勵(lì)品了。在 player/index.js 的 shoot 函數(shù)中我們修改射擊的邏輯。玩家1級(jí)時(shí)只有中間的射擊口,2級(jí)有左邊和中間的射擊口,3級(jí)有左中右三個(gè)射擊口。
// ...其他代碼
/**
* 玩家射擊操作
* 射擊時(shí)機(jī)由外部決定
*/
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)
}
}
// ...其他代碼
武器的最終形態(tài)如圖, 這時(shí)候的玩家已經(jīng)可以為所欲為了<_<,實(shí)際上都不需要躲避了。。。:

為了對(duì)抗愚昧的玩家,不讓他們?yōu)樗麨?,最后沒興趣玩下去~~,敵機(jī)裝備武器,反擊開始。
首先敵機(jī)的子彈是向下,所以復(fù)制一份 images/bullet.png ,并顛倒保存為 images/bullet-down.png , 然后我們重用 js/player/bullet.js ,在構(gòu)造函數(shù)處增加敵機(jī)的子彈配置項(xiàng),并修改敵人子彈更新邏輯。
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 結(jié)尾部分為敵人裝備武器, 子彈速度為敵人自身速度 +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]
// 對(duì)象回收
if ( this.y > window.innerHeight + this.height )
databus.removeEnemey(this)
}
/**
* 敵機(jī)射擊操作
* 射擊時(shí)機(jī)由外部決定
*/
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 中加入敵機(jī)的射擊邏輯,敵機(jī)移動(dòng)5次、60次時(shí)設(shè)計(jì)。
// 其他代碼...
let ctx = canvas.getContext("2d");
let databus = new DataBus();
const ENEMY_SPEED = 6;
// 其他代碼...
/**
* 隨著幀數(shù)變化的敵機(jī)生成邏輯
* 幀數(shù)取模定義成生成的頻率
*/
enemyGenerate(playerLevel) {
if (databus.frame % 60 === 0) {
let enemy = databus.pool.getItemByClass("enemy", Enemy);
enemy.init(ENEMY_SPEED);
databus.enemys.push(enemy);
}
}
// 其他代碼...
// 實(shí)現(xiàn)游戲幀循環(huán)
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();
}
});
// 游戲結(jié)束停止幀循環(huán)
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);
}
這時(shí)候我們發(fā)現(xiàn),由于不明宇宙的干擾射線的影響,玩家和敵機(jī)的子彈不受控制的亂飛。接下來我們就來恢復(fù)世界的秩序吧 ;
經(jīng)偵測發(fā)現(xiàn)是對(duì)象池 pool 的獲取邏輯問題導(dǎo)致子彈不受控問題,我們需要區(qū)分獲取玩家、每個(gè)敵機(jī)的子彈
首先,對(duì)象獲取我們加入對(duì)象屬性的判斷,當(dāng)有傳入對(duì)象屬性時(shí),我們獲取所有屬性值一致的已回收對(duì)象,若沒有找到或者對(duì)象池為空時(shí),則用屬性創(chuàng)建新對(duì)象
/**
* 根據(jù)傳入的對(duì)象標(biāo)識(shí)符,查詢對(duì)象池
* 對(duì)象池為空創(chuàng)建新的類,否則從對(duì)象池中取
*/
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)
}
相應(yīng)的我們需要給每個(gè)子彈設(shè)置歸屬,在 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 ,為其中創(chuàng)建的 bullets 提供歸屬
/**
* 玩家射擊操作
* 射擊時(shí)機(jī)由外部決定
*/
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
/**
* 敵機(jī)射擊操作
* 射擊時(shí)機(jī)由外部決定
*/
shoot() {
const bullet = databus.pool.getItemByClass('bullet', Bullet, { direction: 'down', owner: this });
最后處理 js/databus.js 中 removeBullets 的回收邏輯
/**
* 回收子彈,進(jìn)入對(duì)象池
* 此后不進(jìn)入幀循環(huán)
*/
removeBullets(bullet) {
const index = this.bullets.findIndex(b => b === bullet);
bullet.visible = false
this.bullets.splice(index, 1);
this.pool.recover('bullet', bullet)
}
}
這時(shí)候敵我的子彈就恢復(fù)正常了。不過這時(shí)候玩家中彈并不會(huì)死亡,現(xiàn)在來讓玩家 Go Die吧。在 js/main.js 的 collisionDetection 我們判斷增加每一顆子彈如果是敵方的,就判斷其是否打中玩家,是則游戲結(jié)束。玩家的子彈判斷保持不變。
// 全局碰撞檢測
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;
}
}
});
到此整個(gè)簡單改造計(jì)劃就結(jié)束了,以后還可以添加武器系統(tǒng),boss戰(zhàn)等等。下面是改造后的游戲動(dòng)圖錄屏
