如何在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>
逻辑说明
- 预期位置检测:先计算玩家移动后的预期位置,提前判断是否会碰撞,避免玩家进入物体内部
- 碰撞方向判断:通过计算X/Y轴的重叠量,确定碰撞的主要方向(重叠量小的轴是碰撞方向,因为这是玩家刚接触的方向)
- 位置修正:将玩家推离碰撞对象的边缘,确保玩家不会卡在物体内部
- 速度重置:碰撞方向的速度置0,防止玩家继续向碰撞方向移动,实现"碰到固体无法移动"的效果
内容的提问来源于stack exchange,提问作者Pete21
相关产品推荐
相关产品推荐

