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

如何实现Canvas中玩家与随机生成墙体数组的碰撞检测?

圆形与矩形碰撞检测的正确实现

针对你的玩家(圆形)和墙体(矩形)穿墙问题,核心是要实现圆形与轴对齐矩形的精确碰撞检测,而非简单的方向边界判断。下面是具体实现方案和修改后的完整代码:

核心碰撞检测逻辑

圆形与矩形碰撞的判断逻辑是:

  1. 找到矩形上距离圆形中心最近的点(clampedPoint)
  2. 计算该点到圆心的距离平方(用平方避免开方,提升性能)
  3. 若距离平方小于等于圆形半径的平方,说明发生碰撞

封装碰撞检测函数

function checkCircleRectCollision(circle, rect) {
  // 锁定最近点到矩形范围内
  const clampedX = Math.max(rect.x, Math.min(circle.x, rect.x + rect.width));
  const clampedY = Math.max(rect.y, Math.min(circle.y, rect.y + rect.height));
  
  // 计算距离平方
  const dx = circle.x - clampedX;
  const dy = circle.y - clampedY;
  const distanceSquared = dx * dx + dy * dy;
  
  // 碰撞判定
  return distanceSquared <= circle.radius * circle.radius;
}

替换原碰撞检测代码

把你原来174-199行的方向判断逻辑,替换成遍历墙体数组的碰撞检测,并在碰撞后回退玩家位置:

function updatePlayer() {
  // 保存玩家当前位置,用于碰撞后回退
  const prevX = player.x;
  const prevY = player.y;
  
  // 保留你的移动逻辑
  if (keys['ArrowUp'] && player.y > player.radius) {
    player.y -= player.speed;
  }
  if (keys['ArrowDown'] && player.y < canvas.height - player.radius) {
    player.y += player.speed;
  }
  if (keys['ArrowLeft'] && player.x > player.radius) {
    player.x -= player.speed;
  }
  if (keys['ArrowRight'] && player.x < canvas.width - player.radius) {
    player.x += player.speed;
  }
  
  // 遍历墙体检测碰撞
  for (const wall of walls) {
    if (checkCircleRectCollision(player, wall)) {
      player.x = prevX;
      player.y = prevY;
      break; // 检测到碰撞即停止,避免多次回退
    }
  }
}

关键说明

  • 原方向判断的问题:只判断了玩家边界与墙体边界的重叠,没考虑圆形的特性——比如玩家圆心未碰到墙体,但圆形边缘已经和墙体重叠,或是斜向碰撞的情况。
  • 回退位置是最简单的碰撞处理方式,足够解决你的穿墙问题;如果想要更平滑的响应(比如沿碰撞方向反弹),可以进一步计算碰撞法线,但对当前游戏来说没必要。

完整项目代码

<!DOCTYPE html>
<html>
<head>
  <title>Maze Escape Game</title>
  <style>
    canvas {
      border: 1px solid black;
      background: #000;
    }
  </style>
</head>
<body>
  <canvas id="gameCanvas" width="800" height="600"></canvas>

  <script>
    const canvas = document.getElementById('gameCanvas');
    const ctx = canvas.getContext('2d');

    // 玩家对象
    const player = {
      x: canvas.width / 2,
      y: canvas.height / 2,
      radius: 15,
      speed: 5,
      color: 'white'
    };

    // 墙体数组
    const walls = [];
    // 生成随机墙体
    function generateWalls() {
      for (let i = 0; i < 10; i++) {
        walls.push({
          x: Math.random() * (canvas.width - 100),
          y: Math.random() * (canvas.height - 50),
          width: 100,
          height: 50,
          color: 'gray'
        });
      }
    }
    generateWalls();

    // 出口
    const exit = {
      x: canvas.width - 50,
      y: canvas.height / 2 - 25,
      width: 50,
      height: 50,
      color: 'white'
    };

    // 幽灵对象
    const ghosts = [{
      x: Math.random() * canvas.width,
      y: Math.random() * canvas.height,
      radius: 20,
      speed: 2,
      color: 'red'
    }];

    // 键盘控制
    const keys = {};
    document.addEventListener('keydown', (e) => {
      keys[e.key] = true;
    });
    document.addEventListener('keyup', (e) => {
      delete keys[e.key];
    });

    // 圆形与矩形碰撞检测函数
    function checkCircleRectCollision(circle, rect) {
      const clampedX = Math.max(rect.x, Math.min(circle.x, rect.x + rect.width));
      const clampedY = Math.max(rect.y, Math.min(circle.y, rect.y + rect.height));
      const dx = circle.x - clampedX;
      const dy = circle.y - clampedY;
      const distanceSquared = dx * dx + dy * dy;
      return distanceSquared <= circle.radius * circle.radius;
    }

    // 更新玩家位置
    function updatePlayer() {
      const prevX = player.x;
      const prevY = player.y;

      // 移动逻辑
      if (keys['ArrowUp'] && player.y > player.radius) {
        player.y -= player.speed;
      }
      if (keys['ArrowDown'] && player.y < canvas.height - player.radius) {
        player.y += player.speed;
      }
      if (keys['ArrowLeft'] && player.x > player.radius) {
        player.x -= player.speed;
      }
      if (keys['ArrowRight'] && player.x < canvas.width - player.radius) {
        player.x += player.speed;
      }

      // 墙体碰撞检测
      for (const wall of walls) {
        if (checkCircleRectCollision(player, wall)) {
          player.x = prevX;
          player.y = prevY;
          break;
        }
      }

      // 出口检测
      if (checkCircleRectCollision(player, exit)) {
        alert('你找到了出口!');
        player.x = canvas.width / 2;
        player.y = canvas.height / 2;
      }
    }

    // 更新幽灵
    function updateGhosts() {
      for (const ghost of ghosts) {
        // 简单追踪玩家
        if (ghost.x < player.x) ghost.x += ghost.speed;
        if (ghost.x > player.x) ghost.x -= ghost.speed;
        if (ghost.y < player.y) ghost.y += ghost.speed;
        if (ghost.y > player.y) ghost.y -= ghost.speed;

        // 幽灵与玩家碰撞检测
        const dx = ghost.x - player.x;
        const dy = ghost.y - player.y;
        const distance = Math.sqrt(dx * dx + dy * dy);
        if (distance < ghost.radius + player.radius) {
          alert('被幽灵抓到了!');
          player.x = canvas.width / 2;
          player.y = canvas.height / 2;
        }
      }
    }

    // 绘制游戏元素
    function draw() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);

      // 绘制墙体
      ctx.fillStyle = 'gray';
      for (const wall of walls) {
        ctx.fillRect(wall.x, wall.y, wall.width, wall.height);
      }

      // 绘制出口
      ctx.fillStyle = exit.color;
      ctx.fillRect(exit.x, exit.y, exit.width, exit.height);

      // 绘制幽灵
      for (const ghost of ghosts) {
        ctx.fillStyle = ghost.color;
        ctx.beginPath();
        ctx.arc(ghost.x, ghost.y, ghost.radius, 0, Math.PI * 2);
        ctx.fill();
      }

      // 绘制玩家
      ctx.fillStyle = player.color;
      ctx.beginPath();
      ctx.arc(player.x, player.y, player.radius, 0, Math.PI * 2);
      ctx.fill();

      // 绘制有限视野
      ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      ctx.globalCompositeOperation = 'destination-out';
      ctx.beginPath();
      ctx.arc(player.x, player.y, 150, 0, Math.PI * 2);
      ctx.fill();
      ctx.globalCompositeOperation = 'source-over';
    }

    // 游戏循环
    function gameLoop() {
      updatePlayer();
      updateGhosts();
      draw();
      requestAnimationFrame(gameLoop);
    }

    gameLoop();
  </script>
</body>
</html>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 03:43:13