Canvas等距网格中建筑鼠标悬停与点击交互问题求解
等距网格建筑的鼠标悬停与点击交互问题
我在HTML Canvas中实现了一个等距网格,正尝试处理建筑的鼠标悬停交互,部分建筑存在不同高度。当前遇到两个问题:
- 鼠标指针离开地面瓦片或位于建筑图像中间时,高亮的瓦片会消失
- 需要实现单个建筑的点击功能
核心基础函数
let applied_map = ref([]); // tileMap let tile_images = ref([]); // 存储Canvas可用的已加载图片 let tile_height = ref(50); let tile_width = ref(100); const renderTiles = (x, y) => { let tileWidth = tile_width.value; let tileHeight = tile_height.value; let tile_half_width = tileWidth / 2; let tile_half_height = tileHeight / 2; for (let tileX = 0; tileX < gridSize.value; ++tileX) { for (let tileY = 0; tileY < gridSize.value; ++tileY) { let renderX = x + (tileX - tileY) * tile_half_width; let renderY = y + (tileX + tileY) * tile_half_height; let tile = applied_map.value[tileY * gridSize.value + tileX]; renderTileBackground(renderX, renderY + 50, tileWidth, tileHeight); if (tile !== -1) { if (tile_images.value.length) { renderTexturedTile( tile_images.value[tile].img, renderX, renderY + 40, tileHeight ); } } } } if ( hoverTileX.value >= 0 && hoverTileY.value >= 0 && hoverTileX.value < gridSize.value && hoverTileY.value < gridSize.value ) { let renderX = x + (hoverTileX.value - hoverTileY.value) * tile_half_width; let renderY = y + (hoverTileX.value + hoverTileY.value) * tile_half_height; renderTileHover(renderX, renderY + 50, tileWidth, tileHeight); } }; const renderTileBackground = (x, y, width, height) => { ctx.value.beginPath(); ctx.value.setLineDash([5, 5]); ctx.value.strokeStyle = "black"; ctx.value.fillStyle = "rgba(25,34, 44,0.2)"; ctx.value.lineWidth = 1; ctx.value.moveTo(x, y); ctx.value.lineTo(x + width / 2, y - height / 2); ctx.value.lineTo(x + width, y); ctx.value.lineTo(x + width / 2, y + height / 2); ctx.value.lineTo(x, y); ctx.value.stroke(); ctx.value.fill(); }; const renderTexturedTile = (imgSrc, x, y, tileHeight) => { let offsetY = tileHeight - imgSrc.height; ctx.value.drawImage(imgSrc, x, y + offsetY); }; const renderTileHover = (x, y, width, height) => { ctx.value.beginPath(); ctx.value.setLineDash([]); ctx.value.strokeStyle = "rgba(161, 153, 255, 0.8)"; ctx.value.fillStyle = "rgba(161, 153, 255, 0.4)"; ctx.value.lineWidth = 2; ctx.value.moveTo(x, y); ctx.value.lineTo(x + width / 2, y - height / 2); ctx.value.lineTo(x + width, y); ctx.value.lineTo(x + width / 2, y + height / 2); ctx.value.lineTo(x, y); ctx.value.stroke(); ctx.value.fill(); };

后续更新
基于Helder Sepulveda的思路,我编写了drawCube函数,并将其整合到点击函数和renderTiles中。点击时和帧更新时,会在建筑所在位置生成一个带三个面的立方体,并将Path存储在全局变量中,立方体位置与等距网格对齐。
在drawCube中添加了逻辑:若相邻瓦片存在建筑,则隐藏立方体的右侧面,避免悬停时误触发其他建筑。


部分点击函数代码
//...部分点击函数代码 //... if (tile_images.value[tileIndex] !== undefined) { drawCube( hoverTileX.value + tile_height.value, hoverTileY.value + Number(tile_images.value[tileIndex].img.height / 2) - 10, tile_height.value, // 向左延伸的X长度 tile_height.value, // 向右延伸的X长度 Number(tile_images.value[tileIndex].img.height / 2), // 高度 ctx.value, { tile_index: tileIndex - 1 < 0 ? 0 : tileIndex - 1, } ); }
drawCube函数代码
const drawCube = (x, y, wx, wy, h, the_ctx, options = {}) => { let path = new Path2D(); let hide_options = { left_face: false, right_face: false, top_face: false, }; if (options.hasOwnProperty("hide")) { hide_options = Object.assign(hide_options, options.hide); } // 绘制左侧面 if (!hide_options.left_face) { path.moveTo(x, y); path.lineTo(x - wx, y - wx * 0.5); path.lineTo(x - wx, y - h - wx * 0.5); path.lineTo(x, y - h * 1); } // 绘制右侧面 if ( !hide_options.right_face && !coliders.value[options.tile_index].hide_right_face ) { path.moveTo(x, y); path.lineTo(x + wy, y - wy * 0.5); path.lineTo(x + wy, y - h - wy * 0.5); path.lineTo(x, y - h * 1); } // 绘制顶面 if (!hide_options.right_face) { path.moveTo(x, y - h); path.lineTo(x - wx, y - h - wx * 0.5); path.lineTo(x - wx + wy, y - h - (wx * 0.5 + wy * 0.5)); path.lineTo(x + wy, y - h - wy * 0.5); } let isONHover = the_ctx.isPointInPath( path, mousePosition.x - 10, mousePosition.y - 10 ); the_ctx.fillStyle = null; if (isONHover) { // 处理点击事件 if (isMouseDown.value) { if (buildozer.value === true) { coliders.value[options.tile_index] = -1; applied_map.value[options.tile_index] = -1; } isMouseDown.value = false; } the_ctx.fillStyle = "green"; } the_ctx.fill(path); if ( coliders.value[options.tile_index] == -1 && applied_map.value[options.tile_index] ) { coliders.value[options.tile_index] = path; } };
内容的提问来源于stack exchange,提问作者James Allan
相关产品推荐
相关产品推荐

