还记得小时候看过母亲的十字绣吗,易学易懂,就是用专用的绣线和十字格布,通过平面坐标计找出位置,对照专用的图案进行刺绣,可作出心中所想的画,奈何所需材料成本不小,这里用小程序简单模拟十字绣,喜欢的话可用它练习一下。
打开微信开发工具,选择小程序,创建一个项目,
例如项目名称为
miniprogram-cross-sitich
,然后选择以下,再确定创建
- AppID 使用测试号
- 不使用云服务
- JavaScript - 基础模板
游戏页面
小程序创建好后,找到文件pages/index/index.js
,这是第一个页面的逻辑文件,
找到里面的方法onLoad()
,在里面添加如下代码,可自动进到游戏页面
wx.navigateTo({
url: '/pages/game/game',
})
页面布局
创建一个游戏页面,文件在pages/game/game.wxml
,
页面布局参照HTML网页的vue
组件化布局,做好的页面运行显示效果图如下
布局中用到了一个canvas和用作背景显示的image组件,
还有颜色盒,可以左右拖动选择更多颜色,
还有底部的四个按钮,可清空,缩放网格,预览效果
游戏逻辑
接下来,在文件pages/game/game.wxml
中写代码,实现游戏逻辑
初始化
实现的前提是做好初始化处理,把需要用到的全局变量,设置到data
中,以便后面能读取和重新赋值
data:{
bgImg:'',
colorList:['F08784','EB3324','774342','8E403A','3A0603','9FFCFD','73FBFD','3282F6','0023F5','00129A','16417C','000C7B','FFFE91','FFFD55','F09B59','F08650','784315','817F26','7E84F7','732BF5','3580BB','00023D','58135E','3A083E','A1FB8E','A1FA4F','75F94D','75FA61','75FA8D','818049','EF88BE','EE8AF8','EA3FF7','EA3680','7F82BB','75163F','377D22','377E47','367E7F','507F80','183E0C','173F3F','741B7C','39107B','000000','808080','C0C0C0','FFFFFF'].map(color=>'#'+color),
scale:1.0,
colorCurrent:0
}
colorList
就是颜色盒的数组数据,可选择的颜色都在这里定义;colorCurrent
指定数组中的其中的一个颜色值,选定笔色;scale
就是画布中的初始缩放尺寸,值1.0
是表示原始大小;
获取画布
当页面加载完成时,会调用其中的onReady()
事件,
在这里写代码,获取到布局文件中的canvas组件
wx.createSelectorQuery().select('#canv').fields({
size:true, node:true
},res=>{
//...省略了
const ctx = res.node.getContext('2d');
this.canvasData = {
ctx,
canvas:res.node,
width:ctx.canvas.width,
height:ctx.canvas.height
};
this.initCanvas();
}).exec()
传入的
ctx
就是画布的context
对象,可用来绘制,
调用的initCanvas()
是初始化方法,这里将继续处理,绘制网格
绘制网格
方法initCanvas()
是把所有网格数据建出来,用灰白色区分不同位置的格子,
类似于PS软件中的透明背景图,代码如下
// 获取屏幕宽度和像素比
const { windowWidth, pixelRatio } = wx.getSystemInfoSync();
// 获取画布宽高
const { width, height } = this.canvasData;
// 算出格子大小,列和行
const size = Math.floor(windowWidth/pixelRatio/16);
const cols = Math.floor(width/size);
const rows = Math.floor(height/size);
// 算出内边距
const paddingH = (width-cols*size)*0.5;
const paddingV = (height-rows*size)*0.5;
// 定义网格数组
const grids = [];
for(let r=0; r<rows; r++){
for(let c=0; c<cols; c++){
let g = {
left: c*size+paddingH,
top: r*size+paddingV,
//...省略了
};
// 将一个格子数据添加到数组中
grids.push(g);
}
}
// 设置网格数据,将上面定义的数据暂存起来
this.gridsData = {
grids,
//...省略了
positon: {
//...画布位置
}
};
this.redrawGrils();
其中
redrawGrils()
就是绘制所有网格的方法,用canvas组件的context对象去绘制grids
实现操作
网格画出来后,接下来处理用户的操作
触摸事件
当用户手指触摸到页面布局中画布canvas
区域时,画布会调用三个不同的事件,
分别是触摸的开始,移动,结束事件,分别做一下处理,
触摸开始事件
会调用onTouchStart(e)
方法,代码如下
const touch = e.touches[0];
//记录触摸开始时的点
this.downPositon = touch;
//重置清除 触摸结束时的点
this.upPosition = undefined;
触摸移动事件
调用onTouchMove(e)
方法,代码如下
const touch = e.touches[0];
const { downPositon } = this;
const { scale } = this.data;
this.upPosition = touch;
// 计算触摸结束到开始时的相对位置
let offsetPositon = {
left: touch.x - downPositon.x,
top: touch.y - downPositon.y
};
// 重新绘制所有网格
this.redrawGrils(scale, offsetPositon);
触摸移动时,就可以拖动画布,
调用方法redrawGrils(scale, offsetPositon)
,传入的第二个参数可以改变画布的位置,然后重新绘制网格和涂点位置
触摸结束事件
调用方法onTouchEnd()
,代码如下
const { downPositon, upPosition } = this;
// 当触摸结束时,判断结束点可区分是否移动过
if (upPosition) {
//省略了...
let offsetPositon = {
//省略了...
};
//如果移动了,就更新一下拖动画布后的位置
this.gridsData.positon.left+=offsetPositon.left;
this.gridsData.positon.top+=offsetPositon.top;
return;
}
// 执行到这里时,以下就是对点击操作处理
const { grids, size } = this.gridsData;
const { scale, colorList, colorCurrent } = this.data;
// 触摸按下时的开始点
let touch = downPositon;
// 根据缩放和位置,找出格子
let s = size*scale;
// 偏移一半格子的距离
let offset = s*0.5;
let left = touch.x-offset;
let top = touch.y-offset;
let grid = grids.find(grid=>grid.left<=left && grid.left+s>left && grid.top<=top && grid.top+s>top);
//判断是否碰到格子,没有就返回
if(grid==undefined) return;
//省略了...
//判断是否涂点过(刺绣)
if(grid.flag) {
//擦除涂点
grid.flag = false;
this.drawFlag(grid);
}else{
//涂点
grid.color = colorList[colorCurrent];
grid.flag = true;
this.drawFlag(grid);
}
其中
drawFlag(grid)
就是绘制格子方法,绘制交叉的涂点
选择颜色
这里实现选择颜色盒的颜色,很简单,
选择其中一个颜色格子时,会调用方法onClickColor(e)
,代码如下
const { index } = e.currentTarget.dataset;
// 更新指向颜色数组中的索引(游标)
this.setData({
colorCurrent: index
})
放大和缩小
由于是在手机屏幕上操作,当显示的网格过小,手指会点不准的,
需要点击放大按钮,放大网格,这样就点得准,方便涂点,
点击按钮会调用方法onClickKey(e)
,代码如下
switch(e.currentTarget.dataset.key){
case 'preview':// 预览
this.saveImage();
break;
case 'amplify':// 放大
this.amplifyGrids();
break;
case 'reduce':// 缩小
this.reduceGrids();
break;
case 'clear':// 清空
wx.showModal({
title: '系统提示',
content: '确定要清空吗?',
complete: (res) => {
//...
if (res.confirm) {
this.resetGridData()
}
}
});
break;
}
页面中的底部四个按钮都是调用方法
onClickKey(e)
,传入不同的参数,
例如,点击放大按钮时,调用方法传入参数amplify
,
再选择调用方法amplifyGrids()
进行放大处理
放大的方法,就是调用了redrawGrils(scale)
,实现缩放,代码如下
let { scale } = this.data;
//放大
scale+=0.1;
//省略了...
//重新绘制网格,传入缩放比例
this.redrawGrils(scale);
有没有看出来,很多地方都调用了方法
redrawGrils(scale)
重绘网格,
这个方法实现了缩放和平移,是稍微复杂一点,不到60行代码,不多吧
缩小的方法,调用reduceGrids()
后,也是调用了redrawGrils(scale)
,
缩小的代码同上面的一样,只是其中改成了scale-=0.1
预览图片
点击预览按钮,调用方法saveImage()
这里会先进行预览,
图片看着没问题的话,长按图片就可以保存和分享,如下是代码
const { canvas, ctx } = this.canvasData;
//省略了...
//重新绘制网格,这里添加白色背景
this.redrawGrils(1, undefined, '#ffffff');
//生成图片地址
const imgSrc = canvas.toDataURL();
//省略了...
//再重新绘制一下 恢复原来的画面
this.redrawGrils(scale);
//开启预览
wx.previewImage({
urls: [imgSrc],
showmenu: true, // 长按图片可保存
success:()=>{
}
});
调用系统的
wx.previewImage()
方法,可以轻松实现预览和保存,分享图片
运行测试
讲到这里,这一个项目基本上就能完成,可以运行测试,
如想看项目源码,请点击这里查看,在资源一栏(手机上看可能找不到哦,请用电脑浏览器上看),找到其中的 十字绣 小程序项目源码 ,请放心下载,感谢支持。
如遇到什么问题可以留言,作者看到会回复的,
最后,看一下运行效果,动图如下,作者亲自做的,好看吗
试试绣出《梦里花》
唯一纯白的茉莉花,盛开在琥珀色月牙…