微信小游戏是小程序的一个类目,小游戏是微信开放给小程序的更多的能力,让小程序开发者有了开发游戏的能力。微信小游戏是在小程序的基础上添加了游戏库API。微信小游戏只能运行在小程序环境中,微信小游戏没有小程序中WXSS、WXML、多页面等内容,所以微信小游戏开发时小程序的组件就无法使用;但增加了一些渲染、文件系统以及后台多线程的功能。
小游戏提供了CommonJS风格的模块API,可以将一些公共的代码抽离成为一个单独的JS文件,作为一个模块。可以通过module.exports或者exports导出模块,通过require引入模块。
接宝石箱子小游戏运行后底部出现主角工人,屏幕上方不断掉落宝石箱子,主角工人每接住一个箱子积分增加1分,玩家通过触碰屏幕左侧或者右侧控制工人移动方向。本游戏运行界面如图1所示。
■ 图1 接宝石箱子小游戏运行效果
// common.js
function sayHello(name) {
console.log('Hello' + name + '!')
}
function sayGoodbye(name) {
console.log('Goodbye'+ name+'!')
}
module.exports.sayHello = sayHello //提供对外的接口
module.exports.sayGoodbye = sayGoodbye //提供对外的接口
在game.js中require common.js,就可以调用common模块导出的接口。所有输出接口都会成为输入对象的属性。
var common = require('./js/libs/common.js')
common.sayHello('xmj')
common.sayGoodbye('xmj')
在模块化时,也可以使用export default命令和import命令。
使用import命令是用户需要知道所加载的变量或者函数名,否则无法加载,为了方便用户,使其不用预先知道就能加载模块,这是可以使用export default为模块指定默认输出。
// common2.js
export default function test() { //当然也可以是一个类
console.log("大家好");
};
导入时可以如下:
import t from './js/libs/common2.js'
t('xmj')
这时使用import导入时可以使用任意名称指定模块js文件的导出方法test()。本质上export default就是输出一个叫做Default的方法,只是系统容许为它任意取名。微信小游戏支持ES6,所以开发时可以使用export default class定义各种类模块,例如Bullet子弹类,Sprite精灵类,Enemy敌人类模块,使用import导入类模块。
import Player from './player/index'
import Enemy from './npc/enemy'
小游戏并不能调用所有小程序的API,但是能调用到大部分的API。例如像wx.request,wx.chooseImage,wx.showToast等API,小游戏都可以调用到。更详细的API能力见微信官方API文档。
使用微信开发者工具新建项目,左侧类别选择小游戏,右侧的项目名称输入“接宝石箱子游戏”,单击“新建”按钮,就新建一个新的小游戏。
在图2界面中的中间部分,为文件结构区域。右键文件结构区域上方+号,下拉菜单中选择“目录”,建立文件夹test/images结构,放入相关游戏图片(箱子,工人)。
■ 图2 放入相关游戏图片
准备工作完成后,修改game.js文件。本游戏不使用Adapter(适配器),使用微信wx API自行开发。
/*import './js/libs/weapp-adapter'
import './js/libs/symbol'
import Main from './js/main'
new Main()
*/
const DROP_SPEED = 8 //箱子下移速度
const MOVE_SPEED = 4 //人物左右移速度
const canvas = wx.createCanvas()
const context = canvas.getContext('2d')
context.fillStyle = "#ffff00"
context.font = "40px Arial"
加载工人图片,并设置设置位置坐标(imgX,imgY)。
const image = wx.createImage() //人物图片
let imgX = canvas.width / 2 - 18 //人物位置
let imgY = canvas.height - 80
image.onload = function () {
context.drawImage(image, imgX, imgY)
}
image.src = 'test/images/player.png'
drawRect(x, y)函数清空屏幕,重绘宝石箱子、人物和积分文字。
let score = 0
const { windowWidth, windowHeight } = wx.getSystemInfoSync() //屏幕宽度和高度
function drawRect(x, y) {
context.clearRect(0, 0, windowWidth, windowHeight) //重绘清空屏幕
context.drawImage(boxImage, x, y) //画宝石箱子
context.drawImage(image, imgX, imgY) //画人物
context.fillText("积分" + score, 30, 50) //绘制积分文字
}
设置宝石箱子坐标初始位置(imgX,imgY)为屏幕顶端中央。调用box(rectX, rectY)在指定位置(rectX, rectY)绘制箱子。
let rectX = canvas.width / 2 – 30 //宝石箱子坐标
let rectY = 0
const boxImage = wx.createImage() //宝石箱子图片
box(rectX, rectY) //调用box((rectX, rectY)在指定位置(rectX, rectY)绘制箱子
function box(x, y) {
// Math.ceil(); 只要有小数总是向上取整数; 例如Math.ceil(1.1) 输出为2
let number = Math.ceil(Math.random() * 7) //随机产生1-7数字
let box_src = 'test/images/box' + number + '.png'
boxImage.onload = function () {
context.drawImage(boxImage, x, y)
}
boxImage.src = box_src
}
以下触摸(碰)事件处理。判断触碰位置在人物的左侧则移动方向direction值为-1,这样x坐标可以不断减少。触碰位置在人物的右侧则移动方向direction值为1,这样x坐标可以不断增加。
var direction = 0
var touchX = imgX
var touchY = imgY
wx.onTouchStart(function (res) {//触碰事件
touchX = res.changedTouches[0].clientX - 18
if (touchX < imgX) { //触碰位置在人物的左侧
direction = -1 //向左移动
console.log("left")
} else { //触碰位置在人物的右侧
direction = 1 //向右移动
console.log("right")
}
})
loop()是用来实现游戏帧循环达到动画效果。首先判断工人达到屏幕两侧边界,如果判断工人的位置到达屏幕最左侧,则改向右;到达屏幕最右侧,则改向左。箱子Y坐标不断,显示出下落效果,工人位置坐标根据移动方向不断修改,同时重新绘制箱子,工人,积分。
如果箱子到达底部区域,则判断与工人之间距离,如果小于50则认为碰撞(就是工人接到宝石箱子),进行加分处理。如果箱子出了屏幕底部,重新设置箱子图片并设置位于顶部。最后调用requestAnimationFrame(loop);起到循环渲染效果。
function loop() {
if (imgX <= 10) { //人物位置到达屏幕最左侧,改向右
direction = 1
} else if (imgX >= canvas.width - 50) {//人物位置到达屏幕最右侧,改向左
direction = -1
}
rectY += DROP_SPEED //箱子下移DROP_SPEED
imgX += direction * MOVE_SPEED //改变人物位置坐标
drawRect(rectX, rectY) //重新绘制箱子,人物,积分
if (rectY > canvas.height - 100 && rectY <= canvas.height) {//达到底部
let range = Math.abs(rectX - imgX) //与人物之间距离
if (range <= 50) { //检测碰撞
rectY = canvas.height
score++ //加分
}
}
if (rectY > canvas.height) {
rectY = 0
let spwanX = Math.floor(Math.random() * (canvas.width - 80))
box(spwanX, rectY) //重新设置箱子位于顶部
rectX = spwanX
}
requestAnimationFrame(loop); //循环渲染
}
loop()