如何在HTML5 Canvas中实现任意形状的碰撞检测?
HTML5 Canvas 游戏碰撞检测实用方案
矩形与矩形碰撞检测
这是游戏开发中最常用的基础检测,适合用矩形包围盒简化角色/物体的碰撞判断。
- 核心逻辑:判断两个矩形的边界是否存在重叠,需同时满足以下4个条件:
- A的右边界 > B的左边界
- A的左边界 < B的右边界
- A的下边界 > B的上边界
- A的上边界 < B的下边界
- 代码实现:
function rectRectCollision(rectA, rectB) { return ( rectA.x + rectA.width > rectB.x && rectA.x < rectB.x + rectB.width && rectA.y + rectA.height > rectB.y && rectA.y < rectB.y + rectB.height ); }
圆形与圆形碰撞检测
适用于圆形角色、子弹或场景元素,通过圆心距离与半径的关系判断。
- 核心逻辑:计算两个圆心的距离平方(避免开方运算提升性能),若小于等于两半径之和的平方则碰撞
- 代码实现:
function circleCircleCollision(circleA, circleB) { const dx = circleB.x - circleA.x; const dy = circleB.y - circleA.y; const distanceSquared = dx * dx + dy * dy; const radiusSum = circleA.radius + circleB.radius; return distanceSquared <= radiusSum * radiusSum; }
矩形与圆形碰撞检测
常见组合(比如矩形角色+圆形障碍),通过找到矩形上离圆心最近的点来判断。
- 核心逻辑:计算矩形上距离圆形圆心最近的点,再判断该点到圆心的距离是否小于等于圆的半径
- 代码实现:
function rectCircleCollision(rect, circle) { // 找到矩形上离圆心最近的点 const closestX = Math.max(rect.x, Math.min(circle.x, rect.x + rect.width)); const closestY = Math.max(rect.y, Math.min(circle.y, rect.y + rect.height)); const dx = circle.x - closestX; const dy = circle.y - closestY; return dx * dx + dy * dy <= circle.radius * circle.radius; }
多边形与多边形碰撞检测(分离轴定理SAT)
针对不规则凸多边形的通用检测方案,是2D碰撞检测的核心算法之一。
- 核心原理:如果能找到一条轴,使得两个多边形在该轴上的投影不重叠,则两者不碰撞;反之则碰撞
- 代码实现(凸多边形适用):
// 计算多边形在指定轴上的投影范围 function getProjection(polygon, axis) { let min = dotProduct(polygon[0], axis); let max = min; for (let i = 1; i < polygon.length; i++) { const dot = dotProduct(polygon[i], axis); min = Math.min(min, dot); max = Math.max(max, dot); } return { min, max }; } // 计算向量点积 function dotProduct(v1, v2) { return v1.x * v2.x + v1.y * v2.y; } // 检查两个投影是否重叠 function projectionsOverlap(p1, p2) { return p1.max >= p2.min && p2.max >= p1.min; } // 多边形碰撞检测主函数 function polygonPolygonCollision(polyA, polyB) { // 遍历第一个多边形的所有边,生成检测轴 for (let i = 0; i < polyA.length; i++) { const p1 = polyA[i]; const p2 = polyA[(i + 1) % polyA.length]; // 生成边的垂直法线轴并归一化 const axis = { x: p2.y - p1.y, y: p1.x - p2.x }; const length = Math.sqrt(axis.x * axis.x + axis.y * axis.y); axis.x /= length; axis.y /= length; const projA = getProjection(polyA, axis); const projB = getProjection(polyB, axis); if (!projectionsOverlap(projA, projB)) return false; } // 遍历第二个多边形的所有边,生成检测轴 for (let i = 0; i < polyB.length; i++) { const p1 = polyB[i]; const p2 = polyB[(i + 1) % polyB.length]; const axis = { x: p2.y - p1.y, y: p1.x - p2.x }; const length = Math.sqrt(axis.x * axis.x + axis.y * axis.y); axis.x /= length; axis.y /= length; const projA = getProjection(polyA, axis); const projB = getProjection(polyB, axis); if (!projectionsOverlap(projA, projB)) return false; } return true; }
性能优化技巧
- 先做粗略检测:用矩形包围盒快速过滤掉明显不碰撞的对象,再进行精细检测
- 空间划分:对大量对象使用网格、四叉树等方式分组,减少每帧需要检测的对象数量
- 针对性检测:只对移动中的对象或处于同一区域的对象进行碰撞判断,避免全量遍历
内容的提问来源于stack exchange,提问作者Md Nazmul Islam
相关产品推荐
相关产品推荐

