vue+canvas实现拼图小游戏


Posted in Javascript onSeptember 18, 2020

利用 vue+canvas 实现拼图小游戏,供大家参考,具体内容如下

思路步骤

  • 一个拼图拼盘和一个原图参照
  • 对原图的切割以及随机排序
  • 通过W/A/D/S或上下左右进行移动
  • 难度的自主选择
  • 对拼图是否完成的判定

JS实现部分

数据分析

  • row:拼图的总行数;column:拼图的总列数。 (用来设置拼图难度,也是每个拼图块宽高的设置规则)
  • pic[{x,y,row,column,index}]:小拼图的集合,其内元素为小拼图的数据结构。 (x、y:拼图块在canvas的绘制规则,初始化后不会进行改变;row、column:对原图进行切割并绘制的规则;index:用来判定是否完成拼图的规则之一,绘制空白块的规则,其中空白块的index=-1)
  • num:随机排列的次数。
  • sign:空白块在拼图集合 pic 中的索引。 (数字类型,用来定位空白块,跟随空白块的移动而变化,是进行移动的规则之一;默认为:15)
  • isWin:用来判断是否完成拼图的条件。 (布尔类型,默认为false)
  • step:表示移动的有效步数。 (数字类型,默认为0,重新游戏及完成游戏会清零)
  • maskShow: 编辑游戏 的判定条件。 (布尔类型,用来显示与隐藏编辑游戏的对话框,默认为false)

方法分析

拼图集合 pic 的初始化及随机排列

randomHandler() {
 // pic的初始化
 for(let i=0;i<this.row*this.column;i++) {
 // 设置切割后每个小图片的位置
 let row = parseInt(i/this.row);
 let column = i - row*this.column;
 // 对在canvas的排列进行初始化,后续不会进行改变
 let x = parseInt(i/this.row);
 let y = i - x*this.column;
 this.pic[i] = {...this.pic[i],x:x,y:y,row:row,column:column,index:i};
 // 设置最后一个元素为空白块,index = -1
 if(i == (this.row*this.column-1)) {
  this.pic[i] = {...this.pic[i],row:row,column:column,index:-1};
 }
 }
 // 随机排列 pic集合
 for(let i=0;i<this.num;i++) {
 let ran1,ran2,temp={};
 // 随机获取0-14
 ran1 = parseInt((this.row*this.column-1)*Math.random())
 ran2 = parseInt((this.row*this.column-1)*Math.random())
 temp.row = this.pic[ran1].row
 temp.column = this.pic[ran1].column
 this.pic[ran1] = {...this.pic[ran1],row:this.pic[ran2].row,column:this.pic[ran2].column}
 this.pic[ran2] = {...this.pic[ran2],...temp}
 }
}

拼图的绘制 (根据得到的随机 pic 集合进行绘制)

drawHandler() {
 // 获取 canvas DOM元素
 let canvas = this.$refs.can;
 let ctx = canvas.getContext('2d');
 canvas.width = 400;
 canvas.height = 400;
 ctx.clearRect(0,0,400,400);
 // 每个小拼图的宽高,根据canvas的宽高和拼图行数row列数column来动态设置
 // 是进行难度动态设置的唯一方式
 let width = canvas.width/this.column;
 let height = canvas.width/this.row;
 // 必须通过 Image 构造函数动态创建,若是通过获取 DOM 节点,则onload只执行一次,无法进行移动
 let img = new Image();
 img.src = require('../../public/image/test.png');
 img.onload = () => {
 for(let i=0;i<this.row*this.column;i++) {
  // 绘制到canvas的各元素的起始坐标
  let dx = this.pic[i].y * width;
  let dy = this.pic[i].x * height;
  // 对图片进行切割的起始点坐标
  let cx = this.pic[i].column * width;
  let cy = this.pic[i].row * height;
  // 参数:img图片,切割的起始点坐标,切割的宽高,绘制的起始点坐标,绘制的宽高
  ctx.drawImage(img,cx,cy,width,height,dx,dy,width,height);
  if(this.pic[i].index == -1) {
  this.sign = i;
  ctx.clearRect(dx,dy,width,height);
  }
 }
 }
}

其中 img 必须通过 Image 构造函数动态创建

拼图的移动

// 在 mounted 钩子进行键盘的监听事件
mounted() {
 this.newGame();
 document.onkeydown = (event) => {
 let key = event.keyCode;
 if(key==38 || key==87) this.moveHandler('up');
 else if (key==40 || key==83 ) this.moveHandler('down');
 else if (key==37 || key==65 ) this.moveHandler('left');
 else if (key==39 || key==68 ) this.moveHandler('right');
 }
 }
methods: {
 moveHandler(dir) {
 // re:空白块根据方向最终需移动到的位置索引
 let re,temp = {};
 if(dir == 'up' && this.pic[this.sign].x>0) {
 // 根据空白块的row和column推算出上面一块图片的序号
 // 在将两个图片快进行互换位置,及交换row、column、index
 // 重新赋值this.sign(标志着空白块的序号:默认15)
 re = (this.pic[this.sign].x-1) * this.row + this.pic[this.sign].y;
 temp.row = this.pic[re].row;
 temp.column = this.pic[re].column;
 temp.index = this.pic[re].index;
 this.pic[re] = {...this.pic[re],row:this.pic[this.sign].row,column:this.pic[this.sign].column,index:this.pic[this.sign].index};
 this.pic[this.sign] = {...this.pic[this.sign],...temp};
 this.step = this.step + 1;
 }
 else if(dir == 'down' && this.pic[this.sign].x<this.row-1) {
 re = (this.pic[this.sign].x+1) * this.row + this.pic[this.sign].y;
 temp.row = this.pic[re].row;
 temp.column = this.pic[re].column;
 temp.index = this.pic[re].index;
 this.pic[re] = {...this.pic[re],row:this.pic[this.sign].row,column:this.pic[this.sign].column,index:this.pic[this.sign].index};
 this.pic[this.sign] = {...this.pic[this.sign],...temp};
 this.step = this.step + 1;
 }
 else if(dir == 'left' && this.pic[this.sign].y>0) {
 re = (this.pic[this.sign].x) * this.row + this.pic[this.sign].y-1;
 temp.row = this.pic[re].row;
 temp.column = this.pic[re].column;
 temp.index = this.pic[re].index;
 this.pic[re] = {...this.pic[re],row:this.pic[this.sign].row,column:this.pic[this.sign].column,index:this.pic[this.sign].index};
 this.pic[this.sign] = {...this.pic[this.sign],...temp};
 this.step = this.step + 1;
 }
 else if(dir == 'right' && this.pic[this.sign].y<this.column-1) {
 re = (this.pic[this.sign].x) * this.row + this.pic[this.sign].y+1;
 temp.row = this.pic[re].row;
 temp.column = this.pic[re].column;
 temp.index = this.pic[re].index;
 this.pic[re] = {...this.pic[re],row:this.pic[this.sign].row,column:this.pic[this.sign].column,index:this.pic[this.sign].index};
 this.pic[this.sign] = {...this.pic[this.sign],...temp};
 this.step = this.step + 1;
 }
 // 重新绘制拼图,也可以通过计算只重新绘制移动的部分区域
 this.drawHandler();
 }
}

完成拼图的判定

isWinHandler() {
 // 通过比较所有元素的x、y和row、column是否相等即可,也可以通过index来判断
 for(let i=0;i<this.row*this.column;i++) {
 if(this.pic[i].x == this.pic[i].row && this.pic[i].y == this.pic[i].column) {
  // 显示成功的状态以及清空步数
  this.isWin = true;
  this.step = 0;
 }
 }
}

重新游戏

newGame() {
 // 在 mounted 钩子进行
 // 隐藏完成状态,清空步数,获取随机排列,绘制拼图模块
 this.isWin = false;
 this.step = 0;
 this.randomHandler();
 this.drawHandler();
 }

JS总合

<script>
export default {
 data() {
 return {
 // row:拼图的总行数,column:拼图的总列数
 row:2,
 column:2,
 // 随机打乱的次数
 num:100,
 // pic:拼图的所有子集和;
 // 元素:index:子图片的位置编号
 // row/column:对原图分割后的横纵编号
 // x/y:在canvas中的坐标位置(不会改变)
 pic:[{x:0,y:0,row:0,column:0,index:0}],
 sign:15,
 isWin: false,
 step:0,
 maskShow:false
 }
 },
 mounted() { 代码在拼图移动模块中 },
 methods: {
 // 判断是否完成拼图
 isWinHandler() { ... },
 // 移动的函数方法
 moveHandler(dir) { ... },
 // 绘制拼图
 drawHandler() { ... },
 // 获取随机排序
 randomHandler() { ... },
 newGame() { ... }
}
</script>

HTML部分

<template>
 <div class="index">
 <div class="contain">
 <canvas class="can" ref="can"></canvas>
 <!-- 完成拼图的状态显示 -->
 <div v-if="isWin" class="win">游戏胜利!</div>
 <div class="btns">
 <span @click="newGame">重新游戏</span>
 <span @click="maskShow = true">编辑游戏</span>
 <span @click="isWinHandler">检验</span>
 <span>{{step}}</span>
 </div>
 <!-- 点击编辑游戏的弹出框 -->
 <div v-show="maskShow" class="mask">
 行:<input type="text" v-model="row" placeholder="请输入行数">
 列:<input type="text" v-model="column" placeholder="请输入列数">
 <button @click="maskShow = false">完成</button>
 </div>
 </div>
 <img ref="img" class="img" src="../../public/image/test.png" alt="error">
 </div>
</template>

CSS部分

<style scoped>
/* 编辑的弹出框 */
.mask {
 width: 200px;
 height: 200px;
 background-color: rosybrown;
 position: absolute;
 left: 510px;
 top: 0;
}
/* 按钮样式 */
.btns > span {
 display: inline-block;
 width: 80px;
 font-size: 12px;
 height: 24px;
 text-align: center;
 line-height: 24px;
 margin-bottom: 5px;
 background-color: thistle;
 cursor: pointer;
}
/* 右侧按钮区 */
.btns {
 width: 80px;
 height: 400px;
 border: 1px solid tan;
 border-radius: 5px;
 background-origin: border-box;
 padding: 5px;
 position: absolute;
 left: 412px;
 top: 0;
}
/* 完成拼图的状态 */
.win {
 width: 402px;
 height: 402px;
 line-height: 402px;
 text-align: center;
 font: 24px;
 opacity: 0.5;
 background-color: paleturquoise;
 position: absolute;
 top: 0;
 left: 0;
}
.img {
 display: inline-block;
}
/* canvas */
.can {
 border: 1px solid teal;
}
/* canvas容器 */
.contain {
 position: relative;
}
</style>

最终的完成结果图

vue+canvas实现拼图小游戏

代码地址:拼图游戏

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript使用cookie
Feb 02 Javascript
Javascript学习笔记5 类和对象
Jan 11 Javascript
Javascript面向对象之四 继承
Feb 08 Javascript
js 动态加载事件的几种方法总结
Dec 25 Javascript
详解JavaScript的AngularJS框架中的表达式与指令
Mar 05 Javascript
js实现带农历和八字等信息的日历特效
May 16 Javascript
原生js实现秒表计时器功能
Feb 16 Javascript
解决ztree搜索中多级菜单展示不全问题
Jul 05 Javascript
微信小程序实现图片上传、删除和预览功能的方法
Dec 18 Javascript
JavaScript实现重力下落与弹性效果的方法分析
Dec 20 Javascript
vue项目打包部署_nginx代理访问方法详解
Sep 20 Javascript
node.js连接mysql与基本用法示例
Jan 05 Javascript
JavaScript 常见的继承方式汇总
Sep 17 #Javascript
JavaScript 闭包的使用场景
Sep 17 #Javascript
javascript贪吃蛇游戏设计与实现
Sep 17 #Javascript
js实现简单的随机点名器
Sep 17 #Javascript
谈谈JavaScript中的垃圾回收机制
Sep 17 #Javascript
js对象属性名驼峰式转下划线的实例代码
Sep 17 #Javascript
详细分析JavaScript中的深浅拷贝
Sep 17 #Javascript
You might like
php中引用符号(&amp;)的使用详解
2013/11/13 PHP
10个实用的PHP正则表达式汇总
2014/10/23 PHP
php从完整文件路径中分离文件目录和文件名的方法
2015/03/13 PHP
Laravel 验证码认证学习记录小结
2019/12/20 PHP
jQuery实现复选框批量选择与反选的方法
2015/06/17 Javascript
js 基础篇必看(点击事件轮播图的简单实现)
2016/08/20 Javascript
分享javascript、jquery实用代码段
2016/10/20 Javascript
详解微信小程序——自定义圆形进度条
2016/12/29 Javascript
jquery实现百叶窗效果
2017/01/12 Javascript
利用Javascript裁剪图片并存储的简单实现
2017/03/13 Javascript
详解nodejs爬虫程序解决gbk等中文编码问题
2017/04/06 NodeJs
Javascript实现数组中的元素上下移动
2017/04/28 Javascript
基于vue实现移动端圆形旋钮插件效果
2018/11/28 Javascript
vue拖拽排序插件vuedraggable使用方法详解
2020/08/21 Javascript
vue element 生成无线级左侧菜单的实现代码
2019/08/21 Javascript
JavaScript 作用域实例分析
2019/10/02 Javascript
在vue中使用echars实现上浮与下钻效果
2019/11/08 Javascript
通过原生vue添加滚动加载更多功能
2019/11/21 Javascript
vue如何使用async、await实现同步请求
2019/12/09 Javascript
vue element-ui实现动态面包屑导航
2019/12/23 Javascript
nuxt.js服务端渲染中axios和proxy代理的配置操作
2020/11/06 Javascript
[01:38]完美世界高校联赛决赛花絮
2018/12/02 DOTA
python进阶教程之词典、字典、dict
2014/08/29 Python
python3.6+django2.0开发一套学员管理系统
2018/03/03 Python
python多线程之事件Event的使用详解
2018/04/27 Python
pycham查看程序执行的时间方法
2018/11/29 Python
python程序快速缩进多行代码方法总结
2019/06/23 Python
django多文件上传,form提交,多对多外键保存的实例
2019/08/06 Python
Django中使用haystack+whoosh实现搜索功能
2019/10/08 Python
Python 3.8 新功能来一波(大部分人都不知道)
2020/03/11 Python
浅谈在django中使用redirect重定向数据传输的问题
2020/03/13 Python
拉斯维加斯城市观光通行证:Las Vegas Pass
2019/05/21 全球购物
家长给老师的道歉信
2014/01/13 职场文书
教师先进个人材料
2014/12/17 职场文书
2015年清明节演讲稿范文
2015/03/17 职场文书
php将xml转化对象的实例详解
2021/11/17 PHP