如何实现Canvas中玩家与随机生成墙体数组的碰撞检测?
圆形与矩形碰撞检测的正确实现
针对你的玩家(圆形)和墙体(矩形)穿墙问题,核心是要实现圆形与轴对齐矩形的精确碰撞检测,而非简单的方向边界判断。下面是具体实现方案和修改后的完整代码:
核心碰撞检测逻辑
圆形与矩形碰撞的判断逻辑是:
- 找到矩形上距离圆形中心最近的点(clampedPoint)
- 计算该点到圆心的距离平方(用平方避免开方,提升性能)
- 若距离平方小于等于圆形半径的平方,说明发生碰撞
封装碰撞检测函数
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
相关产品推荐
相关产品推荐

