前段时间看到掘金上有个es6手写辨色了游戏,以为颇有意思,做者使用dom操做实现的游戏逻辑,感受能够用canvas实现,效率更高,因而闲着没事,手写了一个canvas版的辨色小游戏,具体效果以下:javascript
界面写得丑,忘轻喷。。。java
首先咱们须要准备一张画布,git
var canvas = document.getElementById('canvas');
if (!canvas.getContext('2d')) {
alert('你的浏览器不支持canvas,请换个浏览器试试');
}
var ctx = canvas.getContext('2d');
复制代码
而后我会定义一个Rect的方块类,这个方块须要具有位置,宽高,填充颜色几个属性,根据canvas绘制矩形的api,咱们能够用如下api绘制矩形es6
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来绘制,github
首先经过Rect具备一个绘制路径的函数:canvas
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函数绘制:api
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小方块,而且绘制到画布上,bash
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;
}
})
复制代码
而后咱们就能实现下一关的操做,从新再绘制游戏网格,从新生成新的颜色和位置了。
经过这个小游戏,能够学习到,canvas点击事件的监听和实现,由于canvas只是一张画布是一个状态机,无法经过dom同样直接操做,可是经过一些“奇淫巧技”咱们仍是能达到咱们的目的。后续我还会继续不按期更新一些canvas的文章,欢迎你们一块儿探讨和学习。
还看得过去的但愿各位看官给个star。