canvas手寫辨色力小遊戲
前段時間看到掘金上有個es6手寫辨色了遊戲,覺得很有意思,作者使用dom操作實現的遊戲邏輯,感覺可以用canvas實現,效率更高,於是閒著沒事,手寫了一個canvas版的辨色小遊戲,具體效果如下:

介面寫得醜,忘輕噴。。。
繪製遊戲格子
首先我們需要準備一張畫布,
var canvas = document.getElementById('canvas'); if (!canvas.getContext('2d')) { alert('你的瀏覽器不支援canvas,請換個瀏覽器試試'); } var ctx = canvas.getContext('2d'); 複製程式碼
然後我會定義一個Rect的方塊類,這個方塊需要具備位置,寬高,填充顏色幾個屬性,根據canvas繪製矩形的api,我們可以用以下api繪製矩形
fillRect(x, y, width, height)//填充矩形 strokeRect(x, y, width, height)//矩形描邊 clearRect(x, y, width, height)//清除畫布,清除部分完全透明 rect(x, y, width, height)//矩形路徑,需要配和fill和stroke 複製程式碼
但是為了後面事件監聽更方便,我們這裡不使用fillRect方法,我們使用繪製線段的方法moveTo和lineTo來繪製,
首先通過Rect具有一個繪製路徑的函式:
getPoints: function () { var p1 = { x: this.x, y: this.y }; var p2 = { x: this.x + this.width, y: this.y }; var p3 = { x: this.x + this.width, y: this.y + this.height }; var p4 = { x: this.x, y: this.y + this.height }; this.points = [p1, p2, p3, p4]; return this.points; }, createPath: function () { var points = this.getPoints(); points.forEach(function (point, i) { ctx[i == 0 ? 'moveTo' : 'lineTo'](point.x, point.y); }) if (this.closed) { ctx.lineTo(this.points[0].x, this.points[0].y); } }, 複製程式碼
首先通過位置和寬高構造四個點,然後在通過moveTo和lineTo構造路徑,路徑構造好後我們需要繪製到畫布上,因此還需要一個draw函式繪製:
draw: function () { ctx.save(); ctx.fillStyle = this.fillStyle; ctx.beginPath(); this.createPath(); ctx.closePath(); ctx.stroke(); ctx.fill(); ctx.restore(); }, 複製程式碼
方塊類定義好後,我們開始定義顏色函式,顏色邏輯參考這篇文章;
/** * 根據關卡等級返回相應的一般顏色和特殊顏色 * @param {number} step 關卡級別 */ function getColor(step) { // rgb 隨機加減 random let random = Math.floor(100 / step); // 獲取隨機一般顏色,拆分三色值 let color = randomColor(17, 255), m = color.match(/[\da-z]{2}/g); // 轉化為 10 進位制 for (let i = 0; i < m.length; i++) m[i] = parseInt(m[i], 16); //rgb let specialColor = getRandomColorNumber(m[0], random) + getRandomColorNumber(m[1], random) + getRandomColorNumber(m[2], random); return ['#' + color, '#' + specialColor]; } /** * 獲取隨機顏色相近的 rgb 三色值 * @param {number} num 單色值 * @param {number} random 隨機加減的數值 */ function getRandomColorNumber(num, random) { let temp = Math.floor(num + (Math.random() < 0.5 ? -1 : 1) * random); if (temp > 255) { return "ff"; } else if (temp > 16) { return temp.toString(16); } else if (temp > 0) { return "0" + temp.toString(16); } else { return "00"; } } /** * 隨機顏色 * @param {number} min 最小值 * @param {number} max 最大值 */ function randomColor(min, max) { var r = randomNum(min, max).toString(16); var g = randomNum(min, max).toString(16); var b = randomNum(min, max).toString(16); return r + g + b; } 複製程式碼
具體邏輯參考程式碼,
然後我們開始new小方塊,並且繪製到畫布上,
var blockWidth = ((500 / col).toFixed(2) * 500 - 1) / 500; var randomCol = Math.floor(col * Math.random()); var randomCell = Math.floor(col * Math.random()); var colorObj = getColor(step); for (var i = 0; i < col ; i++) { for (var j = 0; j < col; j++) { var rect = new Rect({ x: (blockWidth + 5) * i + (canvas.width - blockWidth * col - (col - 1) * 5) / 2, y: (blockWidth + 5) * j + (canvas.width - blockWidth * col - (col - 1) * 5) / 2, width: blockWidth, height: blockWidth, fillStyle: colorObj[0] }); if (i == randomCol && j == randomCell) { rect.updateStyle(colorObj[1]); } rect.draw(); datas.push(rect); } } 複製程式碼
這樣我們基本就完成了遊戲的大概了。

新增事件
小方塊已經繪製好了,那麼接下來我們來是實現遊戲的關鍵點,那就是互動,我們如何過去到滑鼠點選的小方塊呢,這個小方塊只是canvas畫布上的一張圖,並不能直接像dom一樣新增事件監聽,這或許就是這個遊戲有意思的地方,那麼canvas上有沒有什麼方法能讓我們知道我們具體點選的是哪個小方塊呢?搜搜MDN,果然有一個方法可以判斷點是否在路徑上isPointInPath();
boolean ctx.isPointInPath(x, y); boolean ctx.isPointInPath(x, y, fillRule); boolean ctx.isPointInPath(path, x, y); boolean ctx.isPointInPath(path, x, y, fillRule); 複製程式碼
isPointInPath方法返回一個Boolean值,當檢測點包含在當前或指定的路徑內,返回 true;否則返回 false。
具體方法的使用請參考MDN
我們首先獲取到滑鼠點選canvas的座標點:
/** * @param{} canvas * @param{} x * @param{} y * @description 將滑鼠位置定位到canvas座標 */ function WindowToCanvas(canvas, x, y) { var box = canvas.getBoundingClientRect(); return { x: x - box.left * (canvas.width / box.width), y: y - box.top * (canvas.height / box.height) }; } 複製程式碼
獲取到滑鼠位置後換算成canvas的相對位置,然後我們給rect類新增一個新方法判斷點是否在當前路徑內,
/** * @param{Object} p {x: num, y: num} * @description 判斷點是否在這個路徑上, 構造路徑利用isPointInPath判斷點是否在此路徑上不用繪製到canvas上 */ isPointInPath: function (p) { var isIn = false; ctx.save(); ctx.beginPath(); this.createPath(); if (ctx.isPointInPath(p.x, p.y)) { isIn = true; } ctx.closePath(); ctx.restore(); return isIn; } 複製程式碼
這就是為什麼一開始我們沒有使用fillRect方法繪製矩形,因為fillRect方法會繪製到畫布上,然而我們只是需要構造路徑,來使用canvas的isPointInPath方法,而不是要繪製到畫布上,因此這裡我們巧妙的通過moveTo和lineTo構造路徑,然後判斷當前點是否在這個小方塊內,還記得我們在例項化小方塊的時候我們把所有小方塊都存到一個datas的數組裡嗎?我們通過遍歷datas陣列並且判斷點是否在方塊內,這樣我們就能實現獲取點選的具體的某一個小方塊了,
canvas.addEventListener('mousemove', function (e) { var pos = WindowToCanvas(canvas, e.clientX, e.clientY); for (var i = 0; i < datas.length; i++) { var rect = datas[i]; if (rect.isPointInPath(pos) && rect.isSpecial) { isOn = true; break; } else { isOn = false; } } }, false); canvas.addEventListener('click', function (e) { if (!START) return; if (isOn) { drawGame(); score++; scoreDom.innerText = score; } }) 複製程式碼
然後我們就能實現下一關的操作,重新再繪製遊戲網格,重新生成新的顏色和位置了。