【拼图】拼图游戏-微信小程序开发流程详解

news2024/12/26 9:53:54

还记得小时候玩过的经典拼图游戏吗,上小学时,在路边摊用买个玩具,是一个正方形盒子形状,里面装的是图片分割成的很多块,还差一块,怎么描述好呢,和魔方玩具差不多,有没有听说叫二维的魔方,这里用小程序把它实现,有感兴趣的同学可以来看看

准备

此文章适合新手学习,使用小程序开发的读者阅读哦

  • 会使用微信开发着工具,或使用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(),在移动动画结束时会调用

写到这里,拼图游戏讲解到此结束,理清了上面整个游戏思路吗,相信自己能做到吧,

关于项目

项目源码请在这里找点这里查看项目源码,在资源一栏下,其中有个叫拼图游戏的就是它,可以下载来看,谢谢支持!

运行测试

打开项目源码,游戏运行效果动图如下,

拼图游戏里面图片是可以更换的,换个自己喜欢的图片,这样才有新鲜感

在这里插入图片描述

如果有遇到什么问题请留言,作者会抽时间解答疑惑。

应用场景

这个拼图游戏应用场景,用于解锁攻关最合适不过

  • 🎵 我的梦有一把锁,我的心中一条河,等待有人开启有人穿越~🎵

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/392763.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Leetcode——重排链表】

文章目录一、重排链表思路1.思路2.总结一、重排链表 对于这道题&#xff0c;有两种思路&#xff1a; 思路1. 1.使用一个线性表&#xff0c;存储链表中的每个节点&#xff0c;然后按照题目的条件&#xff0c;来链接线性表的各个节点即可。 使用左下标和右下标来定位线性表中的…

硬件学习 软件 Cadence day09 芯片PCB 封装导出DXF 文件

1.打开自己要导出 DXF 文件的 PCB 封装 (Allegro 软件) 2.导出DXF 文件的按钮 1.点击按钮&#xff0c;打开窗口 2.填写数据 3. 按下 Edit... 按钮 4. 编辑数据 5. 导出数据 &#xff0c;生成DXF 文件 下面的选项自己选择 &#xff1a; Color mapping &#xff1a; …

希腊字母及读音

希腊字母24个希腊字母分别是&#xff1a;Αα、Ββ、Γγ、Δδ、Εε、Ϝϝ、Ζζ、Ηη、Θθ、Ιι、Κκ、Λλ、Μμ、Νν、Ξξ、Οο、Ππ、Ρρ、Σσ、Ττ、Υυ、Φφ、Χχ、Ψψ、Ωω。拼写Α α&#xff1a;阿尔法 AlphaΒ β&#xff1a;贝塔 BetaΓ γ&…

算法套路二:相向双指针

算法套路二:相向双指针 算法套路示例讲解&#xff1a;LeetCode167. 两数之和 II - 输入有序数组 给你一个下标从 1 开始的整数数组 numbers &#xff0c;该数组已按 非递减顺序排列 &#xff0c;请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是…

如何在MacOS上卸载IPGuard的软件--LAgent/LSDhelper程序

IPGuard类的软件一般企业用于办公设备监控&#xff0c;获取员工在设备上操作的信息&#xff0c;同时对文件等信息加密&#xff0c;用于防止企业信息外泄到网络上。但是设备上安装了此类软件一般不容易卸载掉&#xff0c;针对在macos上卸载过程作下讲解。 1. 一般服务类的程序都…

shell文件通配符:任意一个:?、任意数量:*、任意包含[]、[^]:任意不包含

文章目录一. 有哪些文件通配符二. 匹配任意?&#xff1a;匹配任意一个字符*&#xff1a;匹配任意数量的字符串三. 匹配任意指定一个字符1. []&#xff1a;匹配任意包含匹配任意包含字符范围2. [!]与[^]&#xff1a;匹配任意不包含四. 注意事项1. 通配符组合2. 不能跨越目录层级…

运筹系列67:大规模TSP问题的EAX遗传算法

1. 算法介绍 EAX是edge assembly crossover 算子的缩写。本算法有Y nagata教授公布&#xff0c;目前在VLSI最大的几个案例上获得了best的成绩。另外目前MonoLisa 100K问题的最优解也是由其公布&#xff0c;若能得到更优解&#xff0c;可以获得1000美元奖励。 算法步骤如下&…

【教学典型案例】用户称为设计者的正例

目录一&#xff1a;背景介绍二&#xff1a;设计理念三&#xff1a;设计过程按照设计理念设计的功能&#xff1a;1、用户可以根据自己的情况来选择显示哪些活动参与数据。2、用户可以对请假功能和点读功能进行开启和关闭操作&#xff08;默认为全部开启&#xff09;四&#xff1…

我一个女孩子居然做了十年硬件……

2011年&#xff0c;一个三本大学的电子信息专业的大三女学生跟2个通信专业的大二男生组成了一组代表学校参加2011年“瑞萨杯”全国大学生电子设计大赛&#xff0c;很意外的获得了湖北赛区省三等奖&#xff0c;虽然很意外&#xff0c;但还是挺高兴的&#xff0c;毕竟第一次为喜欢…

数据大爆炸时代,大容量硬盘为何不可或缺?

2月27日&#xff0c;中共中央、国务院正式印发《数字中国建设整体布局规划》&#xff08;以下简称《规划》&#xff09;&#xff0c;明确提出要夯实数字中国建设基础&#xff1a;一是打通数字基础设施大动脉&#xff0c;优化各种级别数据中心的合理梯次布局&#xff1b;二是畅通…

Arduino双色LED实验记录

接线图片&#xff1a;双色LED实物和布线有区别&#xff1a;代码&#xff1a;int RED_LED 11; //设置红色为11 int GREEN_LED 10; //设置绿色为10 int val 0;//全局变量val void setup() {// put your setup code here, to run once:pinMode(RED_LED,OUTPUT);//引脚配置pinMo…

HCIP总结(一)

抽象语言---编码---二进制---电信号----处理电信号 &#xff08;电脑工作流程&#xff09; OSI参考模型 ----OSI/RM (核心思想&#xff1a;分层) 应用层----提供各种应用服务&#xff0c;将抽象语言转换成编码&#xff0c;提供人机交互的接口 表示层----将编码转换成二进制 …

10个值得收藏的ChatGPT辅助编程技巧

在我们开始之前&#xff0c;你必须先了解编程语言&#xff0c;然后才能相信 ChatGPT 抛给你的任何东西。 我必须明确这一点&#xff0c;因为许多误入歧途的绵羊被告知 ChatGPT 是新的圣杯&#xff0c;开发人员将被淘汰。 推荐&#xff1a;用 NSDT场景设计器 快速搭建3D场景。 使…

Redis学习(三):五大数据类型及常用操作

五大数据类型 Redis-Key set [key] [value] # 向数据库添加一个键值对 keys * # 查看当前数据库所有的键值 EXISTS [key] # 查看key是否存在在当前数据库中&#xff0c;存在返回1&#xff0c;不存在返回0 move [key] [index] # 将key移动到编号为index的数据库&#xff0c;…

Docker(八)---Docker安全相关设定

文章目录一、理解docker安全二、容器资源控制1.cpu资源限制2.cpu优先级3.内存资源限制4.磁盘io限制三、docker安全加固&#xff08;隔离&#xff09;四、容器特权一、理解docker安全 Docker容器的安全性&#xff0c;很大程度上依赖于Linux系统自身&#xff0c;评估Docker的安全…

扬帆优配|多只“迷你基”清盘未果业绩反领跑 盲目追捧有风险

2023年以来&#xff0c;在信创板块一枝独秀的行情下&#xff0c;多只重仓该板块的基金成绩乘着春风起舞&#xff0c;但作为较为小众的职业&#xff0c;现在来看能够真正享受到红利的基金多为迷你基金。有剖析指出&#xff0c;不少“迷你基”经过押注单一赛道或许投资小盘股等方…

【数据库】排名问题

返回第N高的一个解决思路返回N组中的第N高解决思路分数排名解决思路窗口函数数据库经常被用来解决排名问题。 返回第N高的一个 单表查询: 表: Employee------------------- | Column Name | Type | ------------------- | id | int | | salary | int | ----…

HTML DOM 改变 HTML 内容

HTML DOM 允许 JavaScript 改变 HTML 元素的内容。改变 HTML 输出流在 JavaScript 中&#xff0c;document.write() 可用于直接向 HTML 输出流写内容。实例<!DOCTYPE html><html><body><script>document.write(Date());</script></body>&l…

算法分析与设计之并查集详解

算法分析与设计之并查集1.前言2.并查集的基础2.1.关于动态连通性2.2.动态连通性的应用场景&#xff1a;2.3.对问题建模&#xff1a;2.4.建模思路&#xff1a;2.5.API2.7.Quick-Find算法&#xff1a;2.8.Quick-Union算法&#xff1a;3. 并查集的应用1.前言 本文主要介绍解决动态…

day02_设计测试用例的常见方法

软件测试用例 概念&#xff1a;一个为了特定的目的&#xff08;检验开发的代码实现是否满足用户的需求&#xff09;而设计的文档&#xff08;包含测试输入、执行条件、预期结果&#xff09;&#xff0c;文档的形式可以是xmind、excel等。 测试用例的核心要素 常见测试用例的…