本篇为小程序的一学习笔记,以一小游戏‘层叠消融’为例子,对canvas组件进行展开学习。
开场白就没了,直奔主题。先来看看小程序在组件使用上,对canvas做了什么。
em……,首先,指定了作为唯一标识符的属性canvas-id,因为小程序搭了一套MV*框架,所以对于元素绑定这个,和用原生的js的方式不同。然后就是一系列的手势触发事件。其他的,其实在使用上也没做什么特别的处理嘛。既然如此,那就直接贴上对canvas进行元素绑定的代码吧。
<!-- canvas.wxml -->
<canvas canvas-id="testCanvas"></canvas>
<!-- canvas.js -->
Page({
onReady: function(e) {
// 使用wx.createContext获取绘图上下文 context
var context = wx.createCanvasContext('testCanvas');
context.setStrokeStyle('#00ff00');
context.rect(0,0,200,200);
context.draw();
}
})
因为小程序的canvas元素默认宽高是不会覆盖全屏的,所以在初始化canvas之后要进行重新设置宽高。屏幕的宽高可以通过API:wx.getSystemInfo获得。
接下来就是画图了。‘层叠消融’这个游戏的主要效果就是图片叠加部分双数相消。具体效果如图:
上面的图形由三个基本图形(正方形)组成,用这三个基本图形拼出上面的目标图案,则算通关。
需求:多图形相叠双数相消。要实现这个效果,用传统的css是很麻烦的,这时canvas的灵活性就体现出来了。canvas对图片图形的处理是像素级的,其本身提供了一系列的图形遮盖策略。globalCompositeOperation这个属性设定了在画新图形时采用的遮盖策略,其值是一个标识12种遮盖方式的字符串。其中‘xor’方式正能实现我们双数相消的需求。
立即贴上主要的代码:
//绘制一个反相图案
let revert = function(path){
this.context.beginPath();
this.context.fillStyle="#000";
this.context.globalCompositeOperation="xor";
for(let i=0;i<path.length;i++){
if(i==0){
this.context.moveTo(path[i].x,path[i].y);
}else{
this.context.lineTo(path[i].x,path[i].y);
}
}
this.context.fill();
return this;
}
(小程序的onReady函数,这里具体的调用我打包了一下,具体打包细节我就不再这里细讲了)
onReady: function(){
let canvasCtl = new this.canvasCtl();
canvasCtl.init(this);
let block1 = new util.block([{x:200,y:50},{x:100,y:150},{x:100,y:50}],canvasCtl);
block1.initBlock();
let block2 = new util.block([{x:150,y:70},{x:250,y:70},{x:250,y:200}],canvasCtl);
block2.initBlock();
let block3 = new util.block([{x:50,y:60},{x:110,y:60},{x:110,y:110},{x:50,y:110}],canvasCtl);
block3.initBlock();
this.blockList.push(block1);
this.blockList.push(block2);
this.blockList.push(block3);
},
效果图如下:
既然是小游戏,那就要会动是不是。需求:手指点上时,选中图片跟着移动。
这时,就要用上小程序给我们提供的手势触发了,用到的有这两个:
bindtouchstart用于判断手指选中那个图形,bindtouchmove用于图形移动。
对于判定选中图形这个问题,赶紧回忆起你的小学数学:沿着判定点向图形方向画一条射线,若相交点为单数,则判定点在图形中,否则,判定点在图形外。转换为代码模式如下:
block.prototype.checkPointInRegion = function(pt){
let nCross = 0; // 定义变量,统计目标点向右画射线与多边形相交次数
for (let i = 0; i < this.path.length; i++) { //遍历多边形每一个节点
let p1 = this.path[i];
let p2 = this.path[(i+1)%this.path.length];
// p1是这个节点,p2是下一个节点,两点连线是多边形的一条边
// 以下算法是用是先以y轴坐标来判断的
if ( p1.y == p2.y )continue;//如果这条边是水平的,跳过
if ( pt.y < ((p1.y= ((p1.y>p2.y)?p1.y:p2.y))continue;//如果目标点高于这个线段,跳过
//那么下面的情况就是:如果过p1画水平线,过p2画水平线,目标点在这两条线中间
let x = (pt.y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) + p1.x;
// 这段的几何意义是 过目标点,画一条水平线,x是这条线与多边形当前边的交点x坐标
if ( x > pt.x ) nCross++; //如果交点在右边,统计加一。这等于从目标点向右发一条射线(ray),与多边形各边的相交(crossing)次数
}
if (nCross % 2 == 1) {
return true; //如果是奇数,说明在多边形里
} else {
return false; //否则在多边形外 或 边上
}
}
下面就是移动了,canvas的图形的移动很粗暴,那就是擦掉旧的重新画上新的。需要记录下多次bindtouchmove触发时手指的位移->计算出图形移动后的新定点坐标->在画布上擦掉旧图形->根据新坐标画上新图形。下面贴上主要代码:
searchBlock:function(e){
if(this.movePath.length>10){
this.movePath.shift();
}
this.movePath.push(e);
for(let i = 0; i < this.blockList.length; i++){//让新选中的图形永远处于第一位
if(this.blockList[i].checkPointInRegion({ x: e.touches[0].x , y:e.touches[0].y })){
[this.blockList[0],this.blockList[i]] = [this.blockList[i],this.blockList[0]]
};
}
},
moveBlock:function(e){
if(this.movePath.length>10){
this.movePath.shift();
}
this.movePath.push(e);
let oldPath = this.movePath[this.movePath.length-2]?this.movePath[this.movePath.length-2]:null;
let newPath = e;
this.blockList[0].move(oldPath.touches[0],newPath.touches[0]);
},
block.prototype.move = function(oldPath,newPath){
if(oldPath){
var x = newPath.x - oldPath.x;
var y = newPath.y - oldPath.y;
this.changeLocation(x,y);
}
}
效果图如下:
接下去就是通关判定、关卡设置、关卡选择……有点多,这些内容就留到下期吧。
网友评论