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

新手向:如何在JavaScript Tilemap中实现玩家与墙体碰撞检测?

Tilemap玩家与墙体方块碰撞检测(新手简易实现)

问题背景

我一直在寻找如何检测Tilemap中玩家与指定方块的碰撞,但找到的都是进阶教程。作为编程新手,我希望用最简单的方式实现,理解核心原理:当玩家走到值为1的方块(墙体)时,无法移动到该位置。

以下是我的初始代码:

HTML代码

<canvas id="canvas"></canvas>

JavaScript代码

// Initi
ctx = null;
var ctx = document.getElementById("canvas").getContext("2d");

// Map
var gameMap = [
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 1, 1, 1, 0, 1, 1, 1, 1, 0,
  0, 1, 0, 0, 0, 1, 0, 0, 0, 0,
  0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
  0, 1, 0, 1, 0, 0, 0, 1, 1, 0,
  0, 1, 0, 1, 0, 1, 0, 0, 1, 0,
  0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
  0, 1, 0, 0, 0, 0, 0, 1, 0, 0,
  0, 1, 1, 1, 0, 1, 1, 1, 1, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0
];

var tileW = 40,
  tileH = 40;
var mapW = 10,
  mapH = 10;

window.onload = function() {
  requestAnimationFrame(drawGame);
  ctx.font = "bold 10pt sans-serif";
};

// Player
var x = 100;
var y = 100;
var radius = 10;
var upPressed = false;
var downPressed = false;
var leftPressed = false;
var rightPressed = false;
var speed = 1;

function drawPlayer() {
  ctx.fillStyle = "green";
  ctx.beginPath();
  ctx.arc(x, y, radius, 0, Math.PI * 2)
  ctx.fill();
}

// Inputs
function inputs() {
  if (upPressed) {
    y = y - speed;
  }
  if (downPressed) {
    y = y + speed;
  }
  if (leftPressed) {
    x = x - speed;
  }
  if (rightPressed) {
    x = x + speed;
  }
}

document.body.addEventListener("keydown", keyDown)
document.body.addEventListener("keyup", keyUp)

function keyDown(event) {
  if (event.keyCode == 38) {
    upPressed = true;
  }
  if (event.keyCode == 40) {
    downPressed = true;
  }
  if (event.keyCode == 37) {
    leftPressed = true;
  }
  if (event.keyCode == 39) {
    rightPressed = true;
  }
  if (event.keyCode == 65) {
    speedCodePressed = true;
    speed = 20;
  }
  if (event.keyCode == 32) {
    shootPressed = true;
  }
}

function keyUp(event) {
  if (event.keyCode == 38) {
    upPressed = false;
  }
  if (event.keyCode == 40) {
    downPressed = false;
  }
  if (event.keyCode == 37) {
    leftPressed = false;
  }
  if (event.keyCode == 39) {
    rightPressed = false;
  }
  if (event.keyCode == 32) {
    shootPressed = false;
  }
}

// game map draw function
function drawMap() {
  if (ctx == null) {
    return;
  }

  for (var y = 0; y < mapH; ++y) {
    for (var x = 0; x < mapW; ++x) {
      switch (gameMap[((y * mapW) + x)]) {
        case 0:
          ctx.fillStyle = "#685b48";
          break;
        default:
          ctx.fillStyle = "#5aa457";
      }
      ctx.fillRect(x * tileW, y * tileH, tileW, tileH);
    }
  }
}

// clear screen
function clearScreen() {
  ctx.fillStyle = "black";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
}

// game loop
function drawGame() {
  requestAnimationFrame(drawGame);
  clearScreen();
  drawMap();
  drawPlayer();
  inputs();
}

简易实现方案

核心逻辑

先预判玩家移动后的位置,检查该位置对应的方块是否为墙体(值为1),如果是,就不执行移动;如果不是,才更新玩家位置。

步骤1:添加方块检测工具函数

在代码中添加一个函数,用于根据屏幕坐标获取对应地图方块的值:

// 根据屏幕坐标获取对应地图方块的值
function getTileAt(screenX, screenY) {
    // 将屏幕坐标转换为地图格子的行列索引
    const tileX = Math.floor(screenX / tileW);
    const tileY = Math.floor(screenY / tileH);

    // 超出地图范围的区域视为墙体,防止玩家移出画布
    if (tileX < 0 || tileX >= mapW || tileY < 0 || tileY >= mapH) {
        return 1;
    }

    // 计算一维数组中的索引并返回方块值
    return gameMap[tileY * mapW + tileX];
}

步骤2:修改输入处理函数,加入碰撞检测

替换原来的inputs()函数,加入预判和检测逻辑:

function inputs() {
    // 先保存当前位置,计算预判的新位置
    let newX = x;
    let newY = y;

    if (upPressed) {
        newY = y - speed;
    }
    if (downPressed) {
        newY = y + speed;
    }
    if (leftPressed) {
        newX = x - speed;
    }
    if (rightPressed) {
        newX = x + speed;
    }

    // 检测新位置对应的方块是否为墙体
    const targetTile = getTileAt(newX, newY);
    // 只有不是墙体时,才更新玩家位置
    if (targetTile !== 1) {
        x = newX;
        y = newY;
    }
}

步骤3:(可选)优化碰撞检测精度

如果玩家半径较大,上面的中心检测可能会出现边缘穿墙的情况,可以改成检测玩家四个边缘的位置:

// 检测玩家位置是否合法(不碰到墙体)
function isPositionValid(newX, newY) {
    // 获取玩家四个边缘的坐标
    const topEdge = newY - radius;
    const bottomEdge = newY + radius;
    const leftEdge = newX - radius;
    const rightEdge = newX + radius;

    // 只要任意一个边缘碰到墙体,位置就不合法
    if (getTileAt(leftEdge, newY) === 1 || getTileAt(rightEdge, newY) === 1 ||
        getTileAt(newX, topEdge) === 1 || getTileAt(newX, bottomEdge) === 1) {
        return false;
    }
    return true;
}

// 修改inputs函数使用新的检测逻辑
function inputs() {
    let newX = x;
    let newY = y;

    if (upPressed) {
        newY = y - speed;
    }
    if (downPressed) {
        newY = y + speed;
    }
    if (leftPressed) {
        newX = x - speed;
    }
    if (rightPressed) {
        newX = x + speed;
    }

    if (isPositionValid(newX, newY)) {
        x = newX;
        y = newY;
    }
}

原理说明

  • 坐标转换:屏幕坐标除以方块大小,取整得到地图的行列索引,再通过tileY * mapW + tileX计算一维数组中的位置(因为你的地图是用一维数组存储的)。
  • 预判移动:先计算移动后的位置再检测,避免直接移动后回滚的复杂逻辑,更适合新手理解。
  • 边界处理:把地图外的区域视为墙体,防止玩家移出画布范围。

内容的提问来源于stack exchange,提问作者Karloux

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 14:01:35