六边形网格同类型相邻六边形合并实现及代码优化咨询
六边形网格合并相邻同类型单元格解决方案及代码点评
一、相邻同类型六边形合并实现方案
针对odd-r水平布局的六边形网格,最简洁的合并视觉效果实现方式是隐藏同类型相邻六边形的公共边,这样看起来就像多个六边形合并成了一个连续区域。以下是具体实现步骤和代码修改:
1. 新增邻接单元格与边判断工具函数
// 获取odd-r布局下,指定六边形的邻接单元格坐标 function getHexNeighbors(row, col, grid) { const neighbors = []; if (row % 2 === 0) { // 偶数行邻接规则 neighbors.push([row-1, col], [row-1, col+1]); neighbors.push([row, col-1], [row, col+1]); neighbors.push([row+1, col], [row+1, col+1]); } else { // 奇数行邻接规则 neighbors.push([row-1, col-1], [row-1, col]); neighbors.push([row, col-1], [row, col+1]); neighbors.push([row+1, col-1], [row+1, col]); } // 过滤超出网格范围的无效邻接单元格 return neighbors.filter(([r, c]) => r >= 0 && r < grid.length && c >= 0 && c < grid[r].length ); } // 判断当前六边形的某条边是否需要绘制(邻接单元格不存在/类型不同则绘制) function shouldDrawEdge(hexRow, hexCol, hexType, edgeIndex, grid) { // 对应hexPoints的6条边,每条边关联的邻接单元格坐标 const edgeToNeighbor = [ hexRow % 2 === 0 ? [hexRow+1, hexCol+1] : [hexRow+1, hexCol], // 边0-1(右上→右下) [hexRow, hexCol+1], // 边1-2(右下→中下) hexRow % 2 === 0 ? [hexRow+1, hexCol] : [hexRow+1, hexCol-1], // 边2-3(中下→左下) [hexRow, hexCol-1], // 边3-4(左下→左上) hexRow % 2 === 0 ? [hexRow-1, hexCol] : [hexRow-1, hexCol-1], // 边4-5(左上→中上) hexRow % 2 === 0 ? [hexRow-1, hexCol+1] : [hexRow-1, hexCol] // 边5-0(中上→右上) ]; const [neighborRow, neighborCol] = edgeToNeighbor[edgeIndex]; // 边界单元格的边需要绘制 if (neighborRow < 0 || neighborRow >= grid.length || neighborCol < 0 || neighborCol >= grid[neighborRow].length) { return true; } // 邻接单元格类型不同,需要绘制边 return grid[neighborRow][neighborCol] !== hexType; }
2. 修改drawGrid函数的绘制逻辑
替换原有的hexList.forEach循环,改为只绘制需要显示的边:
drawGrid() { lineCtx.clearRect(0, 0, canvas.width, canvas.height); const size = mapContainer.zoom * 100; const rect = mapContainer.getViewedHexes(); const grid = mapContainer.hexGrid; // 类型到颜色的映射,可根据需求扩展 const typeColorMap = { 0: "rgba(255,0,0,0.4)", 1: "rgba(0,255,0,0.4)", 2: "rgba(0,0,255,0.4)" }; // 直接遍历可视区域内的六边形,无需先收集到数组 for (let row = rect.top; row < rect.bottom; row++) { for (let col = rect.left; col < rect.right; col++) { const type = grid[row][col]; const center = hexCenter(size, row, col); const points = hexPoints(center, size, size); const fillColor = typeColorMap[type] || "rgba(255,0,0,0.4)"; lineCtx.beginPath(); lineCtx.moveTo(points[0].x, points[0].y); // 遍历每条边,判断是否绘制 for (let i = 0; i < 6; i++) { const nextIdx = (i + 1) % 6; if (shouldDrawEdge(row, col, type, i, grid)) { lineCtx.lineTo(points[nextIdx].x, points[nextIdx].y); } else { // 跳过公共边,直接移动到下一个顶点 lineCtx.moveTo(points[nextIdx].x, points[nextIdx].y); } } lineCtx.fillStyle = fillColor; lineCtx.fill(); lineCtx.strokeStyle = "#333"; // 可选:绘制区域外边框 lineCtx.stroke(); } } }
如果需要真正的多边形合并(将连通区域合并为单个多边形对象),可以引入多边形布尔运算库(如polybooljs),但上述边隐藏方案在视觉效果和性能上已经能满足大多数场景需求。
二、现有代码点评
1. 优点
- 六边形坐标计算逻辑清晰,
hexPoints、hexCorner、hexCenter的分工明确,符合单一职责原则 - 为
hexPoints的顶点顺序添加了注释,极大提升了后续维护的可读性 - 基于可视区域绘制六边形,避免了绘制屏幕外的无效内容,性能基础较好
2. 可优化点
(1)性能与内存优化
- 原代码中先将所有可视六边形收集到
hexList再遍历绘制,可改为直接在嵌套循环中处理绘制,减少内存占用 - 可缓存
size、mapX/Y不变时的六边形顶点计算结果,避免重复执行三角函数和乘法运算
(2)代码结构与复用性
- 辅助函数可封装到
HexUtils对象中,避免污染全局作用域:const HexUtils = { hexPoints(center, height, width) { ... }, hexCorner(center, height, width, i) { ... }, hexCenter(size, row, col) { ... } }; hexCenter中的offset计算可提取为单独函数,方便后续修改布局规则
(3)可读性与简洁性
hexCenter中的h = 3/2 * size / 2可简化为h = (3/4)*size,减少冗余计算- 可定义常量
DEG_TO_RAD = Math.PI / 180,替换hexCorner中的重复计算 hexPoints的循环可改为更简洁的写法:return Array.from({length:6}, (_,i) => hexCorner(center, height/2, width/2, i));
(4)健壮性
- 可添加对
mapContainer.hexGrid[row][col]的存在性判断,避免因网格数据异常导致报错
内容的提问来源于stack exchange,提问作者James Homer
相关产品推荐
相关产品推荐

