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 开发规范要求(图文并茂)
Jun 11 Javascript
js限制文本框只能输入中文的方法
Aug 11 Javascript
Jquery+Ajax+PHP+MySQL实现分类列表管理(下)
Oct 28 Javascript
bootstrap是什么_动力节点Java学院整理
Jul 14 Javascript
JS引用传递与值传递的区别与用法分析
Jun 01 Javascript
微信小程序自定义带价格显示日历效果
Dec 29 Javascript
详解JavaScript 的变量
Mar 08 Javascript
详解javascript对数组和json数组的操作
Apr 15 Javascript
node命令行工具之实现项目工程自动初始化的标准流程
Aug 12 Javascript
微信小程序列表时间戳转换实现过程解析
Oct 12 Javascript
vue addRoutes路由动态加载操作
Aug 04 Javascript
一定要知道的 25 个 Vue 技巧
Nov 02 Vue.js
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 XML备份Mysql数据库
2009/05/27 PHP
PHP判断远程图片是否存在的几种方法
2014/05/04 PHP
php使用Jpgraph绘制饼状图的方法
2015/06/10 PHP
Laravel中日期时间处理包Carbon的简单使用
2017/09/21 PHP
ASP Json Parser修正版
2009/12/06 Javascript
jQuery JSON实现无刷新三级联动实例探讨
2013/05/28 Javascript
setInterval与clearInterval的使用示例代码
2014/01/28 Javascript
《JavaScript DOM 编程艺术》读书笔记之JavaScript 图片库
2015/01/09 Javascript
jQuery使用slideUp方法实现控制元素缓慢收起
2015/03/27 Javascript
动态创建按钮的JavaScript代码
2016/01/29 Javascript
详解vue.js组件化开发实践
2016/12/14 Javascript
微信小程序自定义对话框弹出和隐藏动画
2018/07/19 Javascript
如何理解Vue的v-model指令的使用方法
2018/07/19 Javascript
vue组件挂载到全局方法的示例代码
2018/08/02 Javascript
Javascript中弹窗confirm与prompt的区别
2018/10/26 Javascript
webpack 最佳配置指北(推荐)
2020/01/07 Javascript
python实现决策树C4.5算法详解(在ID3基础上改进)
2017/05/31 Python
python基于socket实现的UDP及TCP通讯功能示例
2019/11/01 Python
Python使用Tkinter实现转盘抽奖器的步骤详解
2020/01/06 Python
Python使用文件操作实现一个XX信息管理系统的示例
2020/07/02 Python
python 制作python包,封装成可用模块教程
2020/07/13 Python
Python常用GUI框架原理解析汇总
2020/12/07 Python
波兰快递服务:Globkurier.pl
2019/11/08 全球购物
仓库门卫岗位职责
2013/12/22 职场文书
法律专业实习鉴定
2013/12/22 职场文书
物业门卫岗位职责
2013/12/28 职场文书
早餐连锁店计划书
2014/01/08 职场文书
初中英语课后反思
2014/04/25 职场文书
医药销售自荐书
2014/05/29 职场文书
经营理念口号
2014/06/21 职场文书
国际贸易毕业生求职信
2014/07/20 职场文书
支部书记四风问题对照检查材料
2014/10/04 职场文书
小学生作文批改评语
2014/12/25 职场文书
大学毕业生自我评价
2015/03/02 职场文书
投资公司董事长岗位职责
2015/04/16 职场文书
发票退票证明
2015/06/24 职场文书