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

如何在JavaScript Canvas中实现高级2D矩形碰撞检测?

解决Canvas矩形碰撞与移动限制问题

你的核心问题在于碰撞检测时机错误(先移动玩家再检测,导致玩家已进入物体内部),且缺少正确的碰撞方向判断与响应逻辑。以下是修正后的完整代码和逻辑说明:

关键修改点

  • 调整更新顺序:先计算玩家预期移动位置,再检测碰撞,最后根据碰撞结果修正位置与速度
  • 实现碰撞方向判断:通过计算玩家与固体对象的重叠区域,确定碰撞方向(上/下/左/右)
  • 碰撞响应:将玩家推离碰撞对象,并重置对应方向的速度,防止继续向碰撞方向移动

完整修正代码

JavaScript部分

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cWidth = 400;
var cHeight = 250;
var keyboardKeys = [];
var friction = 0.8;

var solidObjs = [];

class Player {
  constructor(x, y, width, height, color) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.color = color;
    this.velX = 0;
    this.velY = 0;
    this.speed = 5;
  }
}

function createObj(x, y, width, height, color) {
  solidObjs.push({
    x: x,
    y: y,
    width: width,
    height: height,
    color: color
  });
}

var player1 = new Player(cWidth / 2, cHeight / 2, 25, 25, "#0080ff");

canvas.width = cWidth;
canvas.height = cHeight;

createObj(0, 0, 10, cHeight, "#808080");
createObj(cWidth - 10, 0, 10, cHeight, "#808080");
createObj(0, 0, cWidth, 10, "#808080");
createObj(0, cHeight - 10, cWidth, 10, "#808080");
createObj(50, 50, 100, 100, "#808080");

function drawFrame() {
  ctx.clearRect(0, 0, cWidth, cHeight);
  ctx.fillStyle = "#000000";
  ctx.fillRect(0, 0, cWidth, cHeight);

  // 处理玩家输入,更新速度
  if (keyboardKeys["w"] || keyboardKeys["ArrowUp"]) {
    if (player1.velY > player1.speed * -1) {
      player1.velY--;
    }
  }

  if (keyboardKeys["s"] || keyboardKeys["ArrowDown"]) {
    if (player1.velY < player1.speed) {
      player1.velY++;
    }
  }

  if (keyboardKeys["a"] || keyboardKeys["ArrowLeft"]) {
    if (player1.velX > player1.speed * -1) {
      player1.velX--;
    }
  }

  if (keyboardKeys["d"] || keyboardKeys["ArrowRight"]) {
    if (player1.velX < player1.speed) {
      player1.velX++;
    }
  }

  // 应用摩擦
  player1.velX *= friction;
  player1.velY *= friction;

  // 计算玩家的预期位置(先不直接移动)
  let nextX = player1.x + player1.velX;
  let nextY = player1.y + player1.velY;

  // 检测所有固体对象的碰撞
  for (let i = 0; i < solidObjs.length; i++) {
    const obj = solidObjs[i];
    // 先检测预期位置是否会碰撞
    if (simpleCollisionCheck({
      x: nextX,
      y: nextY,
      width: player1.width,
      height: player1.height
    }, obj)) {
      // 计算碰撞方向并修正位置与速度
      advancedCollisionCheck(player1, obj, nextX, nextY);
    }
  }

  // 绘制玩家
  ctx.fillStyle = player1.color;
  ctx.fillRect(player1.x, player1.y, player1.width, player1.height);

  // 绘制固体对象
  for (let i = 0; i < solidObjs.length; i++) {
    ctx.fillStyle = solidObjs[i].color;
    ctx.fillRect(solidObjs[i].x, solidObjs[i].y, solidObjs[i].width, solidObjs[i].height);
  }

  ctx.fillStyle = "white";
  ctx.font = "16px arial";
  ctx.fillText("Add collision to this box.", 50, 66);
}

function simpleCollisionCheck(obj1, obj2) {
  return obj1.x < obj2.x + obj2.width && 
         obj1.x + obj1.width > obj2.x &&
         obj1.y < obj2.y + obj2.height && 
         obj1.y + obj1.height > obj2.y;
}

function advancedCollisionCheck(player, obj, nextX, nextY) {
  // 计算玩家与物体在X和Y轴上的重叠量
  const overlapX = Math.min(player.x + player.width, obj.x + obj.width) - Math.max(nextX, obj.x);
  const overlapY = Math.min(player.y + player.height, obj.y + obj.height) - Math.max(nextY, obj.y);

  // 判断碰撞方向:重叠量小的轴就是碰撞的主要方向
  if (Math.abs(overlapX) < Math.abs(overlapY)) {
    // X轴碰撞(左/右)
    if (player.velX > 0) {
      // 向右移动碰撞,将玩家左移到物体左侧
      player.x = obj.x - player.width;
    } else {
      // 向左移动碰撞,将玩家右移到物体右侧
      player.x = obj.x + obj.width;
    }
    // 重置X轴速度,防止继续向碰撞方向移动
    player.velX = 0;
  } else {
    // Y轴碰撞(上/下)
    if (player.velY > 0) {
      // 向下移动碰撞,将玩家上移到物体顶部
      player.y = obj.y - player.height;
    } else {
      // 向上移动碰撞,将玩家下移到物体底部
      player.y = obj.y + obj.height;
    }
    // 重置Y轴速度,防止继续向碰撞方向移动
    player.velY = 0;
  }
}

setInterval(() => {
  drawFrame();
}, 1000 / 60);

document.body.addEventListener("keydown", (e) => {
  keyboardKeys[e.key] = true;
});

document.body.addEventListener("keyup", (e) => {
  keyboardKeys[e.key] = false;
});

HTML部分(无需修改)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    * {
      padding: 0px 0px;
      margin: 0px 0px;
      box-sizing: border-box;
    }
  </style>
  <title>Document</title>
</head>

<body>
  <canvas id="canvas"></canvas>
  <script src="pg2d.js"></script>
</body>

</html>

逻辑说明

  1. 预期位置检测:先计算玩家移动后的预期位置,提前判断是否会碰撞,避免玩家进入物体内部
  2. 碰撞方向判断:通过计算X/Y轴的重叠量,确定碰撞的主要方向(重叠量小的轴是碰撞方向,因为这是玩家刚接触的方向)
  3. 位置修正:将玩家推离碰撞对象的边缘,确保玩家不会卡在物体内部
  4. 速度重置:碰撞方向的速度置0,防止玩家继续向碰撞方向移动,实现"碰到固体无法移动"的效果

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 05:10:20