可曾记得,小时候上学路边买的透明铅笔盒,里面内嵌了一个小球,它用重力可从起点滚动到终点,对小朋友来说是感觉有趣的,在这个游戏的基础上,弄一款微信小程序的迷宫探索游戏试试,在不同关卡的迷宫中解开机关与谜题,最终到达迷宫的终点,人生要有自己的目标,就不会迷失方向。
打开微信开发工具,选择小程序,创建一个项目,
例如项目名称为
miniprogram-maze
,然后选择以下,再确定创建
- AppID 使用自己的测试号
- 不使用云服务
- JavaScript - 基础模板
开始页面
要实现从开始页面跳转到游戏页面,
页面文件在/pages/index/index.wxml
,添加选择关卡的表单组件,和点击按钮,绑定点击事件方法enterGame()
,
布局显示如下即可
3个关卡是不是少了点,这个可以通过一种叫地图编辑器操作来添加很多关卡
只需在文件/pages/index/index.js
里的enterGame()
方法中添加一段代码即可,代码如下
import Map from '../../utils/map.js';
Page({
data: {
//获取所有关卡显示
maps:(new Map()).getMaps(),
},
enterGame(e){
const { map } = e.detail.value;
wx.navigateTo({
url: '/pages/game/game?map='+map,//传入关卡id
})
}
})
可以看到导入的模块
map.js
,这就是存放所有关卡的数据Map
类,类似地图数据
游戏页面
在一个游戏页面文件/pages/game/game.wxml
中写,只需要一个canvas
画布组件即可,布局如下
<view class="page">
<view class="game-view" id="game-view">
<canvas class="canvas" type="2d" id="canv" bindtouchstart="onTouchStart" bindtouchmove="onTouchMove" bindtouchend="onTouchEnd" bindtouchcancel="onTouchEnd"></canvas>
</view>
</view>
要做的游戏页面如下,从入口到出口的路线是通的
游戏逻辑
理清一下迷宫游戏逻辑,这个基础的迷宫游戏,相信在座的各位都熟悉怎么玩吧,这里就不概述了
初始化
在页面准备完成时,调用方法onReady()
这里,开始写代码,
先获取到画布,然后处理初始化,看如下代码
wx.createSelectorQuery().select('#canv').fields({
size: true,
node: true
}, res => {
//...
this.canvasData = {
canvas: res.node,
ctx: res.node.getContext('2d')
};
//使用迷宫的地图数据,mapid是指定用哪个地图数据
const map = new Map();
const mapData = map.getMap(this.mapid || 0);
//...
const grids = [];
// 创建一个离屏画布
const canvas = wx.createOffscreenCanvas({
type: '2d',
width: res.width,
height: res.height
});
const ctx = canvas.getContext('2d');
// 在离屏画布上绘制迷宫
//先画背景
ctx.fillStyle = '#666666';
ctx.beginPath();
ctx.rect(paddingLeft, paddingTop, size * cols, size * rows);
ctx.fill();
//在画其它的
ctx.fillStyle = '#333333';
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
let g = {
//...
};
grids.push(g);
//...
//若是墙壁数据,就画出来
if (g.a=='1') {
ctx.beginPath();
ctx.rect(g.x, g.y, size, size);
ctx.fill();
}
}
}
//将所有数据设置到新的地图数据中
this.map = {
//...
grids,
//游戏选手数据
player: {
//...
}
};
//这里将离屏绘制好的地图设置到地图数据中,将来有用
this.map.data = ctx.getImageData(0,0,canvas.width,canvas.height);
//这里处理加载所需的图片资源数据
Promise.all(['../../static/player-1.png','../../static/player-2.png','../../static/player-3.png'].map(src=>new Promise((resolve,reject)=>{
let playerImg = res.node.createImage();
playerImg.onload = () => resolve(playerImg);
playerImg.onerror = reject;
playerImg.src = src;
}))).then(res=>{
//在游戏选手数据里放好所有图片数据
this.map.player.headImgs = res;
//重置游戏数据,开始游戏
this.restart();
}).catch(err => console.error(err))
}).exec()
从初始化的方法中可以看到,使用了离屏画布
wx.createOffscreenCanvas()
,可能有一些小伙伴不什么理解它,
离屏画布与组件canvas的用法是一样的,但是有所区别,离屏画布可以理解为后台绘制,绘制出来的图形是不可见的,
后台绘制完后,要用canvas组件绘制,也就是覆盖一下,才能看到图形,
它的绘制方法,在下面的重绘方法redraw()
中会提到
初始化方法处理完成,就进入开始游戏方法restart()
,代码大致如下
//...
const { player, inIndex, outIndex } = this.map;
const { canvas } = this.canvasData;
//重置游戏选手数据和状态
player.index = inIndex;//表示在迷宫的入口
player.timer = TIMER_FULL;//这是常量,最大计时60s
//...
this.isGameEnd = false;
this.preIndex = inIndex;
//...
//处理下一帧的方法
const nextFrame = () => {
//延迟执行
this.timer = setTimeout(() => {
if (player.timer <= 0) {
this.showModal('挑战失败!\n爱是一道光,没了你发慌...');
return;
}
else if (player.index == outIndex) {
this.showModal('挑战成功!\n头上带点绿,生活过的去...');
return;
}
else if (player.index == inIndex && this.preIndex!=inIndex) {
this.preIndex = inIndex;
// 如果走到原点(迷宫入口),就提示
wx.showToast({
title:'你不能回去...',
icon:'none'
});
}
//重绘
this.redraw();
//执行下一帧
canvas.requestAnimationFrame(() => nextFrame());
}, FRAME_TIME);
};
nextFrame();
其中
FRAME_TIME
就是常量,延迟执行下一帧的时间,它的值是60,单位毫秒
接下来,看看重绘方法redraw()
,代码大致如下
const { map } = this;
const { canvas, ctx } = this.canvasData;
const { player, grids, size, rows, cols, paddingTop, paddingLeft, outIndex } = map;
//绘制前,先清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
//判断游戏是否结束了
if (this.isGameEnd) {
//绘制迷宫地图
ctx.putImageData(map.data, 0, 0);
}else{
//在选手头像的位置周围内绘制(限制在一格距离),超过距离都绘制黑色,表示看不见远处的
let index = player.index;
let g = grids[index-cols-1];
//省略了...
//3个格的距离
let s = size*3;
//这里将离屏绘制出来的地图绘制出来,传入的参数可决定绘制的地图范围
ctx.putImageData(map.data, 0, 0, g.x, g.y, s, s);
//...
}
//获取选手所在的格子数据
let playerG = grids[player.index];
//如不在终点位置
let isFail = outIndex!=player.index;
//是否游戏结束
if (this.isGameEnd) {
//有没有到终点,绘制选手头像将是不一样的
if (isFail) ctx.drawImage(player.headImgs[1],playerG.x,playerG.y,size,size);
else ctx.drawImage(player.headImgs[2],playerG.x,playerG.y,size,size);
}else{
//绘制选手头像
ctx.drawImage(player.headImgs[0],playerG.x,playerG.y,size,size);
//...
//绘制出口的灯光泡,r是半径,可以改变,一大一小变化
playerG = grids[outIndex];
ctx.fillStyle = '#F4E286';
ctx.beginPath();
ctx.arc(playerG.x + r, playerG.y + r, r*s, 0, Math.PI * 2);
ctx.fill();
}
//绘制剩余时间,每过一秒就更新一下timer
ctx.fillStyle = '#ff0000';
ctx.font = 20+'px sans-serif';
ctx.fillText(`剩余时间 ${player.timer}`, paddingLeft, paddingTop*0.6);
在游戏进行中,迷宫地图应该是隐藏的,显示效果如下
由于是探索类迷宫,选手游戏时是没有地图可看的,
有方向感,可感知距离,还有手速,还是可以攻关的,
随着倒计时开始,会增加游戏紧张度吧,
关卡地图越大,挑战难度就越大。
接下来,是实现游戏交互逻辑了,通过触摸操作识别出移动方向,
请参考这个文章来实现 关于手机中的触摸手势操作实现过程详解,
主要是改变player.index
就可以实现移动
游戏测试
写到这里,这个迷宫游戏基本就算完成了,看着不是很多,是不是感觉简单呢,运行效果动图如下,
想看详细的可以去找一下对应的项目源码,点此查看,在资源类别下可以找到(如果在手机上看到的,可能看不到,请用电脑浏览器上看),感谢支持。
游戏用到素材,同下面,如觉得不合适,自己修改替换就好,
爱是一道光,没了你发慌。
如果被爱是一道光,那没了以后,就问你慌不慌
生活过得去,头上带点绿。
那点绿,可以理解为慢羊羊头上的那颗智慧草吗😄
以上讲得是选微信小程序开发的,
如果想要选小游戏开发,请参考这个文章 微信小程序的游戏转到小游戏上实现方法详解 来实现,
觉得有帮助,就点个赞呗~
附带一个,在此基础上修改的一个项目还有 推箱子游戏,以下是游戏运行效果图
对应的项目源码,点此查看