本篇為小程序的一學(xué)習(xí)筆記,以一小游戲‘層疊消融’為例子,對canvas組件進行展開學(xué)習(xí)。
開場白就沒了,直奔主題。先來看看小程序在組件使用上,對canvas做了什么。
em……,首先,指定了作為唯一標(biāo)識符的屬性canvas-id,因為小程序搭了一套MV*框架,所以對于元素綁定這個,和用原生的js的方式不同。然后就是一系列的手勢觸發(fā)事件。其他的,其實在使用上也沒做什么特別的處理嘛。既然如此,那就直接貼上對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之后要進行重新設(shè)置寬高。屏幕的寬高可以通過API:wx.getSystemInfo獲得。
接下來就是畫圖了?!畬盈B消融’這個游戲的主要效果就是圖片疊加部分雙數(shù)相消。具體效果如圖:

上面的圖形由三個基本圖形(正方形)組成,用這三個基本圖形拼出上面的目標(biāo)圖案,則算通關(guān)。
需求:多圖形相疊雙數(shù)相消。要實現(xiàn)這個效果,用傳統(tǒng)的css是很麻煩的,這時canvas的靈活性就體現(xiàn)出來了。canvas對圖片圖形的處理是像素級的,其本身提供了一系列的圖形遮蓋策略。globalCompositeOperation這個屬性設(shè)定了在畫新圖形時采用的遮蓋策略,其值是一個標(biāo)識12種遮蓋方式的字符串。其中‘xor’方式正能實現(xiàn)我們雙數(shù)相消的需求。
立即貼上主要的代碼:
//繪制一個反相圖案
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函數(shù),這里具體的調(diào)用我打包了一下,具體打包細節(jié)我就不再這里細講了)
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);
},
效果圖如下:

既然是小游戲,那就要會動是不是。需求:手指點上時,選中圖片跟著移動。
這時,就要用上小程序給我們提供的手勢觸發(fā)了,用到的有這兩個:

bindtouchstart用于判斷手指選中那個圖形,bindtouchmove用于圖形移動。
對于判定選中圖形這個問題,趕緊回憶起你的小學(xué)數(shù)學(xué):沿著判定點向圖形方向畫一條射線,若相交點為單數(shù),則判定點在圖形中,否則,判定點在圖形外。轉(zhuǎn)換為代碼模式如下:
block.prototype.checkPointInRegion = function(pt){
????let nCross = 0; // 定義變量,統(tǒng)計目標(biāo)點向右畫射線與多邊形相交次數(shù)
????for (let i = 0; i < this.path.length; i++) { //遍歷多邊形每一個節(jié)點
????????let p1 = this.path[i];
????????let p2 = this.path[(i+1)%this.path.length];
????????// p1是這個節(jié)點,p2是下一個節(jié)點,兩點連線是多邊形的一條邊
????????// 以下算法是用是先以y軸坐標(biāo)來判斷的
????????if ( p1.y == p2.y )continue;//如果這條邊是水平的,跳過
????????if ( pt.y < ((p1.y= ((p1.y>p2.y)?p1.y:p2.y))continue;//如果目標(biāo)點高于這個線段,跳過
????????//那么下面的情況就是:如果過p1畫水平線,過p2畫水平線,目標(biāo)點在這兩條線中間
????????let x = (pt.y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) + p1.x;
????????// 這段的幾何意義是 過目標(biāo)點,畫一條水平線,x是這條線與多邊形當(dāng)前邊的交點x坐標(biāo)
????????if ( x > pt.x ) nCross++; //如果交點在右邊,統(tǒng)計加一。這等于從目標(biāo)點向右發(fā)一條射線(ray),與多邊形各邊的相交(crossing)次數(shù)
????}
????if (nCross % 2 == 1) {
????????return true; //如果是奇數(shù),說明在多邊形里
????} else {
????????return false; //否則在多邊形外 或 邊上
????}
}
下面就是移動了,canvas的圖形的移動很粗暴,那就是擦掉舊的重新畫上新的。需要記錄下多次bindtouchmove觸發(fā)時手指的位移->計算出圖形移動后的新定點坐標(biāo)->在畫布上擦掉舊圖形->根據(jù)新坐標(biāo)畫上新圖形。下面貼上主要代碼:
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);
? ? }
}
效果圖如下:

接下去就是通關(guān)判定、關(guān)卡設(shè)置、關(guān)卡選擇……有點多,這些內(nèi)容就留到下期吧。