Canvas六边形棋盘游戏交互问题:如何独立修改六边形颜色
解决Canvas六边形棋盘的独立交互与颜色修改问题
核心问题分析
你当前的错误在于为每个六边形创建新的Canvas上下文——一个Canvas元素只能对应一个2D上下文,重复创建上下文不仅无效,还会造成资源浪费。正确的思路是:把每个六边形的属性(位置、大小、颜色、顶点坐标)存储为数据对象,统一用单个Canvas上下文绘制所有六边形,修改颜色时只需更新对应六边形的颜色数据,再重新绘制即可。
修正后的实现代码
var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); ctx.canvas.width = window.innerWidth; ctx.canvas.height = window.innerHeight; // 存储所有六边形的数据,键为坐标标识,值为六边形属性对象 var tuiles = {}; // 生成随机颜色的工具函数 var randomColor = function() { return "#" + Math.floor(Math.random() * 16777215).toString(16); }; // 创建六边形数据(不直接绘制) var createHexaData = function(x, y, s) { // 计算六边形的所有顶点坐标,方便后续碰撞检测和绘制 var points = [ {x: x, y: y}, {x: x + (s/2), y: y - (s/5)}, {x: x + s, y: y}, {x: x + s, y: y + (s/2)}, {x: x + (s/2), y: y + (s/2) + (s/5)}, {x: x, y: y + (s/2)} ]; // 存储六边形的核心属性 return { x: x, y: y, size: s, points: points, color: randomColor() // 初始随机颜色 }; }; // 绘制单个六边形的函数 var drawHexa = function(hexaData) { ctx.beginPath(); // 遍历顶点绘制路径 hexaData.points.forEach((point, index) => { if (index === 0) { ctx.moveTo(point.x, point.y); } else { ctx.lineTo(point.x, point.y); } }); ctx.closePath(); ctx.stroke(); ctx.fillStyle = hexaData.color; ctx.fill(); }; // 绘制一列六边形 var col = function(x, y, s, c) { if (c > 0) { var key = `${x} ${y}`; tuiles[key] = createHexaData(x, y, s); col(x + s, y, s, c - 1); } }; // 绘制一行六边形(含交错排列) var row = function(x, y, s, c, l) { col(x, y, s, c); if (l > 1) { if (l % 2 === 0) { row(x + (s/2), y + (s/2) + (s/5), s, c, l - 1); } else { row(x - (s/2), y + (s/5) + (s/2), s, c, l - 1); } } }; // 初始化棋盘 var initGrid = function(x, y, s, c, l) { row(x, y, s, c, l); // 初始化完成后绘制所有六边形 redrawAllHexas(); }; // 重绘所有六边形 var redrawAllHexas = function() { // 先清除整个画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 遍历所有六边形数据并绘制 Object.values(tuiles).forEach(hexa => drawHexa(hexa)); }; // 点击修改六边形颜色的交互函数 canvas.addEventListener('click', function(e) { var mouseX = e.clientX; var mouseY = e.clientY; // 遍历所有六边形,检测点击是否在六边形内部 for (let key in tuiles) { var hexa = tuiles[key]; if (isPointInHexagon(mouseX, mouseY, hexa.points)) { // 修改颜色 hexa.color = randomColor(); // 重绘所有六边形(也可以局部重绘优化性能) redrawAllHexas(); break; } } }); // 点是否在六边形内部的检测函数(射线法) var isPointInHexagon = function(px, py, points) { var inside = false; for (var i = 0, j = points.length - 1; i < points.length; j = i++) { var xi = points[i].x, yi = points[i].y; var xj = points[j].x, yj = points[j].y; var intersect = ((yi > py) !== (yj > py)) && (px < (xj - xi) * (py - yi) / (yj - yi) + xi); if (intersect) inside = !inside; } return inside; }; // 初始化棋盘 initGrid(100, 100, ctx.canvas.height/15, 15, 15);
关键改进点
- 数据与绘制分离:
tuiles对象存储的是每个六边形的属性数据(位置、顶点、颜色),而非Canvas上下文,这是实现独立修改的核心。 - 统一绘制上下文:所有六边形都通过同一个
ctx绘制,避免资源浪费。 - 交互实现:通过点击事件结合射线法检测点击位置是否在六边形内部,修改对应六边形的颜色后重绘。
- 可扩展性:后续可以轻松添加更多交互(比如拖拽、选中状态),只需修改六边形的数据并重新绘制即可。
性能优化提示
如果棋盘很大,重绘整个画布会有性能问题,可以改为局部重绘:修改颜色后,只清除该六边形所在的区域,再单独绘制这个六边形。示例代码如下:
// 局部重绘单个六边形 var redrawSingleHexa = function(hexa) { // 计算六边形的包围盒,只清除这个区域 var minX = Math.min(...hexa.points.map(p => p.x)); var maxX = Math.max(...hexa.points.map(p => p.x)); var minY = Math.min(...hexa.points.map(p => p.y)); var maxY = Math.max(...hexa.points.map(p => p.y)); ctx.clearRect(minX - 2, minY - 2, maxX - minX + 4, maxY - minY + 4); // 重新绘制该六边形 drawHexa(hexa); }; // 修改点击事件中的重绘逻辑 canvas.addEventListener('click', function(e) { // ... 检测点击的六边形 ... hexa.color = randomColor(); redrawSingleHexa(hexa); // 用局部重绘代替全局重绘 // ... });
内容的提问来源于stack exchange,提问作者charles peyo
相关产品推荐
相关产品推荐

