大一开发者求助:用HTML、JS、CSS实现2D渲染D&D地牢地图
解决D&D动态地牢地图的分步实操建议
第一步:先落地地牢布局数组,别纠结完美
先搞一个最小可用的二维数组,用数字映射元素,再搭配简单渲染函数跑通可视化:
// 5x5极简地牢示例 const dungeonMap = [ [0, 0, 0, 0, 0], [0, 1, 1, 2, 0], [0, 1, 3, 1, 0], [0, 2, 1, 1, 0], [0, 0, 0, 0, 0] ]; // 0=墙,1=可通行地砖,2=门,3=宝箱 // 基础渲染函数:把数组转成DOM地砖 function renderMap(map) { const container = document.getElementById('map-container'); container.innerHTML = ''; map.forEach(row => { const rowDiv = document.createElement('div'); rowDiv.className = 'map-row'; row.forEach(tileType => { const tile = document.createElement('div'); tile.className = `tile tile-${tileType}`; // 仅给可通行区域绑定点击事件(后续移动用) if ([1,2].includes(tileType)) tile.addEventListener('click', handleTileClick); rowDiv.appendChild(tile); }); container.appendChild(rowDiv); }); }
用CSS给不同类型地砖加样式:
.tile { width: 30px; height: 30px; display: inline-block; } .tile-0 { background: #333; } /* 墙 */ .tile-1 { background: #ddd; } /* 地砖 */ .tile-2 { background: #8B4513; } /* 门 */ .tile-3 { background: #FFD700; } /* 宝箱 */
先把这一步跑通,能看到可视化地图后,再慢慢扩展手绘地图的完整布局。
第二步:角色位置追踪,别直接修改地砖数值
用地砖数值存角色会和地图元素冲突(比如把地砖1改成20后,分不清是门还是角色),单独维护角色状态才是正确方式:
// 角色列表 const characters = [ { name: 'Eowyn', id: 20, avatar: 'eowyn.png', position: { x: 1, y: 1 } }, { name: 'Gimli', id: 21, avatar: 'gimli.png', position: { x: 3, y: 1 } } ]; // 当前选中的操作角色 let selectedChar = null; // 生成角色选择按钮 function setupCharButtons() { const btnContainer = document.getElementById('char-buttons'); characters.forEach(char => { const btn = document.createElement('button'); btn.textContent = char.name; btn.addEventListener('click', () => { selectedChar = char; // 高亮选中按钮 document.querySelectorAll('.char-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); }); btn.className = 'char-btn'; btnContainer.appendChild(btn); }); } // 处理地砖点击,移动角色 function handleTileClick(e) { if (!selectedChar) return; // 计算点击地砖的坐标 const tile = e.target; const row = tile.parentElement; const x = Array.from(row.children).indexOf(tile); const y = Array.from(row.parentElement.children).indexOf(row); // 检查是否可通行 if (dungeonMap[y][x] !== 0) { selectedChar.position = { x, y }; renderMapWithChars(); // 重新渲染带角色的地图 } } // 渲染地图+角色头像 function renderMapWithChars() { renderMap(dungeonMap); // 先渲染基础地图 characters.forEach(char => { const targetTile = document.querySelector(`.map-row:nth-child(${char.position.y+1}) .tile:nth-child(${char.position.x+1})`); if (targetTile) { const avatar = document.createElement('img'); avatar.src = char.avatar; avatar.className = 'char-avatar'; targetTile.appendChild(avatar); } }); }
这样角色位置和地图元素完全分离,后续扩展多人同步也更方便。
第三步:区域逐块显示,从「视野范围」入手
先实现简单的角色视野功能,玩家只能看到周围N格区域:
// 渲染可见区域(角色周围3格) function renderVisibleArea() { const container = document.getElementById('map-container'); container.innerHTML = ''; const { x: charX, y: charY } = selectedChar || { x:1, y:1 }; const viewRange = 3; dungeonMap.forEach((row, y) => { const rowDiv = document.createElement('div'); rowDiv.className = 'map-row'; row.forEach((tileType, x) => { const tile = document.createElement('div'); tile.className = `tile tile-${tileType}`; // 判断是否在视野内 const isVisible = Math.abs(x - charX) <= viewRange && Math.abs(y - charY) <= viewRange; if (!isVisible) tile.classList.add('hidden'); // 隐藏区域用CSS设为黑色 if ([1,2].includes(tileType) && isVisible) tile.addEventListener('click', handleTileClick); rowDiv.appendChild(tile); }); container.appendChild(rowDiv); }); // 只渲染视野内的角色 characters.forEach(char => { if (Math.abs(char.position.x - charX) <= viewRange && Math.abs(char.position.y - charY) <= viewRange) { const targetTile = document.querySelector(`.map-row:nth-child(${char.position.y+1}) .tile:nth-child(${char.position.x+1})`); if (targetTile) { const avatar = document.createElement('img'); avatar.src = char.avatar; avatar.className = 'char-avatar'; targetTile.appendChild(avatar); } } }); }
如果要区分房间/走廊,可以把地图数组改成对象格式,额外存储区域类型:
const dungeonMap = [ [{ type:0, area:'wall' }, { type:0, area:'wall' }], [{ type:1, area:'room' }, { type:2, area:'door' }], // ... ];
渲染时根据area属性给不同区域加样式即可。
核心提醒:别同步推进三步,先跑通最小版本
先完成「地图渲染」,再加「角色选择+移动」,最后搞「视野/区域显示」。每一步只做最小可用功能——比如先不用头像,用文字显示角色名字,功能跑通后再替换成图片。避免因同时想太多陷入混乱。
内容的提问来源于stack exchange,提问作者Luigi531
相关产品推荐
相关产品推荐

