【拼图】拼图游戏-微信小程序开发流程详解
还记得小时候玩过的经典拼图游戏吗,上小学时,在路边摊用买个玩具,是一个正方形盒子形状,里面装的是图片分割成的很多块,还差一块,怎么描述好呢,和魔方玩具差不多,有没有听说叫二维的魔方,这里用小程序把它实现,有感兴趣的同学可以来看看
准备
此文章适合新手学习,使用小程序开发的读者阅读哦
- 会使用微信开发着工具,或使用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;//每次动画移动的单位距离pxPage({//.../***  清除画布*/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;//每次动画移动的单位距离pxPage({//.../***  开始移动图片动画*/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(),在移动动画结束时会调用
写到这里,拼图游戏讲解到此结束,理清了上面整个游戏思路吗,相信自己能做到吧,
关于项目
项目源码请在这里找点这里查看项目源码,在资源一栏下,其中有个叫拼图游戏的就是它,可以下载来看,谢谢支持!
运行测试
打开项目源码,游戏运行效果动图如下,
拼图游戏里面图片是可以更换的,换个自己喜欢的图片,这样才有新鲜感

如果有遇到什么问题请留言,作者会抽时间解答疑惑。
应用场景
这个拼图游戏应用场景,用于解锁攻关最合适不过
- 🎵 我的梦有一把锁,我的心中一条河,等待有人开启有人穿越~🎵
相关文章:
 
【拼图】拼图游戏-微信小程序开发流程详解
还记得小时候玩过的经典拼图游戏吗,上小学时,在路边摊用买个玩具,是一个正方形盒子形状,里面装的是图片分割成的很多块,还差一块,怎么描述好呢,和魔方玩具差不多,有没有听说叫二维的…...
第六章 opengl之光照(颜色)
OpenGL光照颜色创建一个光照场景光照 颜色 颜色由RGB组成,分别是红色,绿色,蓝色。举例定义一个颜色向量: glm::vec3 coral(1.0f, 0.5f, 0.31f);而在现实中,人眼看到的是 物体反射后的颜色,也就是说不能被…...
 
C语言-基础了解-19-C位域
C位域 一、C位域 如果程序的结构中包含多个开关量,只有 TRUE/FALSE 变量,如下: struct {unsigned int widthValidated;unsigned int heightValidated; } status;这种结构需要 8 字节的内存空间,但在实际上,在每个变…...
 
MapReduce全排序和二次排序
排序是MapReduce框架中最重要的操作之一。MapTask和ReduceTask均会对数据按照key进行排序。该操作属于Hadoop的默认行为。任何应用程序中的数据均会被排序,而不管逻辑上是否需要。默认排序是按照字典顺序排序,且实现该排序的方法是快速排序。对于MapTask…...
 
【Vue3】封装数字框组件
数量选择组件-基本结构 (1)准备基本结构 <script lang"ts" setup name"Numbox"> // </script> <template><div class"numbox"><div class"label">数量</div><div cla…...
C++-简述strcpy、sprintf 和 memcpy 的区别
回答如下: strcpy 函数:用于将一个字符串(以 NULL 结尾)从源地址复制到目标地址。函数原型为 char* strcpy(char* destination, const char* source)。需要注意的是,该函数会复制整个字符串,包括 NULL 终止…...
用CPU大法忽悠ChatGPT写前端,油猴子工具库+1
文章目录用CPU大法忽悠ChatGPT写前端,油猴子工具库1源起对话1. 作为一名天才js程序员,开发一个油猴子脚本,实现所有浏览器网页的自动下滑功能,每一个步骤都加上中文注释2. 加一个按钮,只有我点击了按钮才会开始自动下滑…...
 
初识虚拟DOM渲染器
初识虚拟DOM渲染器什么是虚拟DOM什么是渲染器渲染器的实现组件是什么什么是虚拟DOM 首先简单说一下什么是虚拟DOM,虚拟DOM就是一个描述真实DOM的JS对象 例如: 真实的DOM元素 <div onClick"alert(click me)">click me</div>可以…...
 
工作日志day03
同时构建静态和动态库 //如果用这种方式,只会构建一个动态库,虽然静态库的后缀是.a ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC}) ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC}) //修改静态库的名字,这样是可以的,但是我们往往希望他…...
【数据挖掘与商务智能分析】第三章 线性回归模型
一元线性回归 一元线性回归的代码实现 1. 绘制散点图 import matplotlib.pyplot as plt X = [[1], [2], [4], [5]] Y...
 
iOS开发之UIStackView基本运用
UIStackView UIStackView是基于自动布局AutoLayout,创建可以动态适应设备方向、屏幕尺寸和可用空间的任何变化的用户界面。UIStackView管理其ArrangedSubview属性中所有视图的布局。这些视图根据它们在数组中的顺序沿堆栈视图的轴排列。由axis, distribution, align…...
【java】为什么 main 方法是 public static void ?
main 方法是我们学习Java编程语言时知道的第一个方法,你是否曾经想过为什么 main 方法是 public、static、void 的。当然,很多人首先学的是C和C,但是在Java中main方法与前者有些细微的不同,它不会返回任何值,为什么 ma…...
 
最简单的线性回归模型-标量
首先考虑yyy为标量,www为标量的情况,那么我们的线性函数为ywxbywxbywxb。每批输入的量batch size 为111,每批输入的xxx为一个标量,设为x∗x^*x∗,标签yyy同样为一个标量,设为y∗y^*y∗。因此每批训练的损失…...
k8s-Kubernetes集群升级
文章目录前言一、集群升级1.部署cri-docker (所有集群节点)2.升级master节点3.升级worker节点前言 一、集群升级 https://v1-24.docs.kubernetes.io/zh-cn/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/ 1.部署cri-docker (所有…...
 
Linux25 -- 监听队列链接上限测试、命令uname、ulimit
一、监听队列链接上限测试 1、res listen(sockfd,5); //创建监听队列res listen(sockfd,5);不懂版本有不同的限制,2.6早期版本有限制为128,超过默认为128,可使用uname -a 查看版本 2、测试将链接数到达上限, 方法࿱…...
 
idea:地址被占用
问题启动idea报:java.net.BindException: Address already in use: bind,具体截图如下:解决步骤1、首先想到的是改idea端口,但按网上方法试下了几个4位数和5位数的端口,没啥作用2、根据idea抛异常的弹出框提示…...
JavaScript常用小技巧(js优化)
JavaScript常用小技巧(js优化)常见JS操作1、解构交换两数2、短路赋值3、if 判断优化4、 switch 判断优化6、动态正则匹配Number1、幂运算2、安全计算String1、反转字符串、判断是否回文数2、数组求和3、初始化二维数组Object1、对象遍历2、冻结对象3、解…...
【项目实战】MySQL 5.7中的关键字与保留字详解
一、什么是关键字和保留字 关键字是指在SQL中有意义的字。 某些关键字(例如SELECT,DELETE或BIGINT)是保留的,需要特殊处理才能用作表和列名称等标识符。 这一点对于内置函数的名称也适用。 二、如何使用关键字和保留字 非保留关…...
 
Git图解-常用命令操作
目录 一、前言 二、初始化仓库 三、添加文件 四、Git 流程全景图 五、Git工作流程 六、工作区和暂存区 七、查看文件状态 八、查看提交日志 九、查看差异 十、版本回退 十一、管理修改 十二、修改撤销 十三、删除文件 十四、分支管理 十五、项目分支操作 十六、…...
 
LeetCode096不同的二叉搜索树(相关话题:卡特兰数)
目录 题目描述 解题思路 代码实现 进出栈序列理解卡特兰数分析策略 相关知识 参考文章 题目描述 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。 示例 1: …...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...
 
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
 
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...
 
基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...
vue3 daterange正则踩坑
<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...
如何配置一个sql server使得其它用户可以通过excel odbc获取数据
要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据,你需要完成以下配置步骤: ✅ 一、在 SQL Server 端配置(服务器设置) 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到:SQL Server 网络配…...
