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

大一开发者求助:用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 03:07:07