还记得小时候玩过的经典拼图游戏吗,上小学时,在路边摊用买个玩具,是一个正方形盒子形状,里面装的是图片分割成的很多块,还差一块,怎么描述好呢,和魔方玩具差不多,有没有听说叫二维的魔方,这里用小程序把它实现,有感兴趣的同学可以来看看
准备
此文章适合新手学习,使用小程序开发的读者阅读哦
- 会使用微信开发着工具,或使用HbuilderX工具会做uniapp项目
- 需要熟悉 HTML5 Canvas
- 适合新手入门
开始吧,在电脑上把微信开发者工具打开,选择新建项目,最后点确定
- 选择小程序,再点击
+
符号新建 - 选择 使用测试号(没有自己申请一个)
- 选择 不使用云服务
- 选择模板 JavaScript 基础模板
新建项目后有生成了一堆东西,不用管它,接下来,将在这基础上添加代码
页面制作
首先,要做的小程序页面同下面这样,
二维平面图片上的块一开始是打乱的,需要把它转正,到整个图片刚好看着没问题,这个过程叫拼图游戏,
移动图片,拼到正确就算攻关,完成过程时间越短越厉害,训练大脑,是个益智游戏
第一个页面
第一个页面是pages/index/index/wxml
,在里面加了一个表单form
,还有提交按钮button form-type="submit"
,点击开始游戏,相信很多同学都会自己写布局,这里不展开讲,
在第一个页面的pages/index/index.js
里,点击按钮事件里写开始游戏逻辑,就是打开第二个页面,很简单的,这里不展开讲,
具体的可以看文章的项目源码,放在文章结尾,可以找到,
点开始游戏前,给第二个页面传入游戏配置相关的两个参数即可
- 网格列数
cols
=3 - 选择图片
bgImg
=默认本地图片路径
第二个页面
第二个页面是pages/game/game.wxml
,布局很简单,只需要以下几个元素标签,其中画布canvas
标签才是主要的
<view class="content">
<canvas type="2d" id="canvA" class="canvas" bindtouchstart="onTouchStart" bindtouchmove="onTouchMove" bindtouchend="onTouchEnd" ></canvas>
<view class="padding">
<text>⏰游戏用时:{{timerNum}}s</text>
</view>
<view class="padding">
<button class="btn" size="mini" bindtap="onClick" data-key="help"><icon class="icon" type="info"></icon> <text>游戏说明</text></button>
<button class="btn" size="mini" bindtap="onClick" data-key="restart"><icon class="icon" type="clear"></icon> <text>重新开始</text></button>
</view>
</view>
还有页面相关的样式写在
pages/game/game.wxss
里,达到显示效果
游戏逻辑
初始化
接下来,在pages/game/game.js
写,实现画布canvas
的初始化逻辑,代码如下
Page({
/**
* 页面的初始数据
*/
data: {
//游戏配置
config:{
cols:3,
bgImg:'/static/1677722908380.jpg'
},
//计时数
timerNum:0,
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
const changel = this.getOpenerEventChannel();
if(changel && changel.once) {
//接收游戏配置参数
changel.once('args',res=>{
const { cols, bgImgSrc } = res;
this.data.config.cols = cols;
this.data.config.bgImg = bgImgSrc;
this.initCanvas();
})
}else{
this.initCanvas();
}
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
//清除所有计时器
if(this.data.timer){
clearTimeout(this.data.timer);
}
if(this.data.gameTimer){
clearInterval(this.data.gameTimer);
}
},
/**
* 初始化游戏画布canvas
*/
initCanvas(){
wx.createSelectorQuery().select('#canvA').fields({ size:true, node:true },res=>{
const { node, width, height } = res;
node.width = width;
node.height = height;
//使用游戏配置
const { cols, bgImg } = this.data.config;
//计算网格大小和边距
const gs = Math.trunc(width/cols);
const padding = Math.trunc(width%cols/2);
//设置画布相关的数据
this.data.canvas = {
node, width, height, padding,
gridSize: gs,
context: node.getContext('2d'),
};
//异步处理加载图片
Promise.resolve({
then(resolve,reject) {
let img = node.createImage();
img.onload=function () {
resolve(img)
};
img.onerror=reject;
img.src=bgImg;
}
}).then(res=>{
//分割图片方法
this.splitImg(res.currentTarget || res);
//重绘方法
this.reDraw();
//开始计时
this.data.gameTimer = setInterval(()=> {
if(this.data.isEnd) {
clearInterval(this.data.gameTimer);
this.data.gameTimer=null;
return;
}
this.setData({
timerNum:this.data.timerNum+1
})
},1000)
}).catch(err=>{
console.error(err)
})
}).exec();
},
//...
})
分割图片
初始化的方法中,还调用了一些方法,
这里主要讲分割图片splitImg(bgImg)
和重新绘制reDraw(isRest)
方法,展开说明,代码如下
const AnimationUpdateDelay=10;//更新动画延迟ms
const MovingPixelsOffset=5;//每次动画移动的单位距离px
Page({
//...
/**
* 清除画布
*/
clearBg(){
//...
},
/**
* 分割图片
* @param String - bgImg 背景图片元素
*/
splitImg(bgImg){
const { cols } = this.data.config;
const { width, height, context:c, gridSize:gs, padding } = this.data.canvas;
this.clearBg();
//绘制背景图片
c.drawImage(bgImg,0,0,width,height);
//定义网格集合
const grids = [];
for(let y=0; y<cols; y++){
for(let x=0; x<cols; x++){
const grid = {
//定义网格数据...
};
c.rect(grid.left,grid.top,gs,gs);
grids.push(grid);
}
}
//绘制网格
c.stroke();
//定义分割后的图片集合
let imgs = [];
let lastIndex;
grids.forEach(function (g,i) {
//将部分区域弄个图片集合中...
});
imgs.forEach(function (img,i) {
//将每个图片弄到网格集合中...
});
//设置最后的(空白位置)图片索引
this.data.lastIndex = lastIndex;
//设置网格集合
this.data.canvas.grids = grids;
},
/**
* 重绘画布方法
* @param Boolean - isRest 是否重置
*/
reDraw(isRest){
const { lastIndex } = this.data;
const { width, height, context:c, grids, gridSize:gs } = this.data.canvas;
this.clearBg();
//绘制网格上的图片
grids.forEach(function (g,i) {
if(isRest) {
//重置网格数据...
}
//绘制网格...
});
//绘制出来
c.rect(0,0,width,height);
c.stroke();
},
//...
})
游戏交互
接下来,实现游戏的交互逻辑,处理用户点击某按钮,
还有,获取用户点击(触摸)某图片,再处理下一步,逻辑代码如下
Page({
//...
/**
* 按钮点击事件处理
*/
onClick(e){
//...
},
/**
* 重新开始游戏
*/
reStart(){
//...
},
/**
* 在画布中开始触摸事件
*/
onTouchStart(e){
this.data.touch = e.touches[0];
},
/**
* 在画布中触摸并移动事件
*/
onTouchMove(e){
this.onTouchStart(e)
},
/**
* 在画布中不再触摸时事件
*/
onTouchEnd(){
const { touch, lastIndex, isAnimation, isEnd } = this.data;
//如果哦没有触摸,或在动画中,或已经结束,就直接返回不处理
if(!touch || isAnimation || isEnd) return;
const { grids, gridSize:gs } = this.data.canvas;
//获取在画布触摸到某图片的索引
let gIndex = grids.findIndex(function (g) {
//判断符合条件的某网格图片...
});
//如果触摸的是空白位置,直接返回不处理
if(gIndex==lastIndex) return;
let grid = grids[gIndex];
let lastGrid = grids[lastIndex];
//定义移动方向的偏移数据
let offsetMove;
if (grid.x==lastGrid.x){
//设置左右移动...
}else if(grid.y==lastGrid.y){
//设置上下移动...
}
//如果没有可移动的,直接返回不处理
if (!offsetMove) return;
//...处理交换图片后,更新索引
this.data.lastIndex = gIndex;
//开始移动图片动画
this.startMoveAnimation(lastIndex,offsetMove);
},
//...
})
游戏动画
这里实现开始移动动画的效果,
开始移动图片的方法是startMoveAnimation(lastIndex,offsetMove)
,实现稍微复杂一点,逻辑代码如下
const AnimationUpdateDelay=10;//更新动画延迟ms
const MovingPixelsOffset=5;//每次动画移动的单位距离px
Page({
//...
/**
* 开始移动图片动画
*/
startMoveAnimation(lastIndex,offsetMove){
const { grids, gridSize:gs, node:canvas } = this.data.canvas;
//定义移动单位距离
const offset = MovingPixelsOffset;
const activeGrid = grids[lastIndex];
//此处省略了一些处理逻辑...
//定义动画结束方法
const endAnimation = () => {
this.reDraw(true);
this.data.isAnimation = false;
this.isEndGame();
};
//定义动画更新方法
const updateAnimation = () => {
//判断条件,更新移动数据
if(offsetMove.x<0 && activeGrid.offsetX<0) activeGrid.offsetX+=offset;
//其余的一些判断逻辑省略了...
else {
endAnimation();
return;
}
this.reDraw();
//继续下一个更新动画
// this.data.timer = setTimeout(()=>{
canvas.requestAnimationFrame(() => updateAnimation());
// },AnimationUpdateDelay);
};
//设置动画进行中
this.data.isAnimation = true;
//开始更新动画
updateAnimation();
},
/**
* 判断游戏是否结束(通关)
*/
isEndGame(){
const { grids } = this.data.canvas;
let isEnd = grids.every(function (current,index) {
//...判断逻辑,检查网格集合里所有的图片顺序是否正确
});
//如果结束,就是顺序正确,弹出提示用户
if(isEnd){
this.data.isEnd=true;
wx.showModal({
title: '游戏结束',
content: `恭喜攻关!用时${this.data.timerNum}秒`,
confirmText:'重新开始',
complete: (res) => {
if (res.cancel) {
this.data.lastIndex=-1;
this.reDraw();
return;
}
if (res.confirm) {
this.reStart();
}
}
})
}
},
//...
})
游戏是否结束判断方法
isEndGame()
,在移动动画结束时会调用
写到这里,拼图游戏讲解到此结束,理清了上面整个游戏思路吗,相信自己能做到吧,
关于项目
项目源码请在这里找点这里查看项目源码,在资源一栏下,其中有个叫拼图游戏的就是它,可以下载来看,谢谢支持!
运行测试
打开项目源码,游戏运行效果动图如下,
拼图游戏里面图片是可以更换的,换个自己喜欢的图片,这样才有新鲜感
如果有遇到什么问题请留言,作者会抽时间解答疑惑。
应用场景
这个拼图游戏应用场景,用于解锁攻关最合适不过
- 🎵 我的梦有一把锁,我的心中一条河,等待有人开启有人穿越~🎵