You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 03:34:52