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 相关文章推荐
Jquery跨域获得Json时invalid label错误的解决办法
Jan 11 Javascript
基于Jquery与WebMethod投票功能实现代码
Jan 19 Javascript
Jquery阻止事件冒泡 event.stopPropagation
Dec 11 Javascript
php读取sqlite数据库入门实例代码
Jun 25 Javascript
CSS图片响应式 垂直水平居中
Aug 14 Javascript
总结JavaScript三种数据存储方式之间的区别
May 03 Javascript
JS动态的把左边列表添加到右边的实现代码(可上下移动)
Nov 17 Javascript
简单实现jQuery轮播效果
Aug 18 jQuery
使用JS中的Replace()方法遇到的问题小结
Oct 20 Javascript
Vue中的Props(不可变状态)
Sep 29 Javascript
Servlet返回的数据js解析2种方法
Dec 12 Javascript
微信小程序跨页面数据传递事件响应实现过程解析
Dec 19 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制作新闻系统的思路
2006/10/09 PHP
用php实现让页面只能被百度gogole蜘蛛访问的方法
2009/12/29 PHP
使用PHP获取当前url路径的函数以及服务器变量
2013/06/29 PHP
浅析PKI加密解密 OpenSSL
2013/07/01 PHP
PHP处理SQL脚本文件导入到MySQL的代码实例
2014/03/17 PHP
浅析php适配器模式(Adapter)
2014/11/25 PHP
PHP给源代码加密的几种方法汇总(推荐)
2018/02/06 PHP
详解php用static方法的原因
2018/09/12 PHP
php微信公众号开发之关键词回复
2018/10/20 PHP
PHP的PDO预处理语句与存储过程
2019/01/27 PHP
Referer原理与图片防盗链实现方法详解
2019/07/03 PHP
laravel框架模型、视图与控制器简单操作示例
2019/10/10 PHP
js 调用百度地图api并在地图上进行打点添加标注
2014/05/13 Javascript
JavaScript onkeypress事件入门实例(按下或按住一个键盘按键)
2014/10/17 Javascript
js实现有时间限制消失的图片方法
2015/02/27 Javascript
AngularJS extend用法详解及实例代码
2016/11/15 Javascript
javascript replace()第二个参数为函数时的参数用法
2016/12/26 Javascript
js 颜色选择插件
2017/01/23 Javascript
微信小程序 PHP生成带参数二维码
2017/02/21 Javascript
详解vue-cli官方脚手架配置
2018/07/20 Javascript
Vue2.0 v-for filter列表过滤功能的实现
2018/09/07 Javascript
React Native开发封装Toast与加载Loading组件示例
2018/09/08 Javascript
搭建Vue从Vue-cli到router路由护卫的实现
2019/11/14 Javascript
JavaScript获取当前url路径过程解析
2019/12/27 Javascript
Node中对非阻塞I/O、事件循环的知识点总结
2020/01/05 Javascript
Python实现简单字典树的方法
2016/04/29 Python
详解Python中表达式i += x与i = i + x是否等价
2017/02/08 Python
Python中__slots__属性介绍与基本使用方法
2018/09/05 Python
Django的Modelforms用法简介
2019/07/27 Python
详解用python -m http.server搭一个简易的本地局域网
2020/09/24 Python
python如何实现递归转非递归
2021/02/25 Python
Tod’s英国官方网站:意大利奢华手工制作手袋和鞋履
2019/03/15 全球购物
师范生实习的个人自我鉴定
2013/10/20 职场文书
上班睡觉检讨书
2014/01/09 职场文书
公司经营目标责任书
2015/01/29 职场文书
2015年医德医风工作总结
2015/04/02 职场文书