如何在Canvas中检测带旋转的两个对象间的碰撞?
旋转飞船与地面的碰撞检测解决方案
你当前使用的轴对齐矩形碰撞检测(AABB)仅适用于无旋转的矩形,飞船旋转后,实际碰撞区域是自定义的多边形,原检测逻辑完全失效。以下是两种适配旋转场景的解决方案:
方案一:基于顶点与边的碰撞检测
通过计算飞船旋转后的顶点世界坐标,检测以下三种情况判断碰撞:
- 飞船任意顶点进入地面矩形内
- 飞船的边与地面矩形的边相交
- 地面矩形的任意顶点进入飞船多边形内
工具函数实现
// 旋转本地坐标点到世界坐标 function rotatePoint(localX, localY, originX, originY, angle) { const rad = angle * Math.PI / 180; const cos = Math.cos(rad); const sin = Math.sin(rad); const rotatedX = localX * cos - localY * sin; const rotatedY = localX * sin + localY * cos; return { x: rotatedX + originX, y: rotatedY + originY }; } // 判断点是否在矩形内 function pointInRect(point, rect) { return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height; } // 判断线段AB与CD是否相交(跨立实验) function segmentsIntersect(A, B, C, D) { function ccw(A, B, C) { return (B.x - A.x) * (C.y - A.y) - (B.y - A.y) * (C.x - A.x); } const ccw1 = ccw(A, B, C); const ccw2 = ccw(A, B, D); const ccw3 = ccw(C, D, A); const ccw4 = ccw(C, D, B); if (((ccw1 > 0 && ccw2 < 0) || (ccw1 < 0 && ccw2 > 0)) && ((ccw3 > 0 && ccw4 < 0) || (ccw3 < 0 && ccw4 > 0))) { return true; } function pointOnSegment(A, B, P) { return Math.min(A.x, B.x) <= P.x && P.x <= Math.max(A.x, B.x) && Math.min(A.y, B.y) <= P.y && P.y <= Math.max(A.y, B.y); } if (ccw1 === 0 && pointOnSegment(A, B, C)) return true; if (ccw2 === 0 && pointOnSegment(A, B, D)) return true; if (ccw3 === 0 && pointOnSegment(C, D, A)) return true; if (ccw4 === 0 && pointOnSegment(C, D, B)) return true; return false; } // 判断点是否在凸多边形内(射线法) function pointInPolygon(point, polygon) { let inside = false; const { x, y } = point; for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { const { x: xi, y: yi } = polygon[i]; const { x: xj, y: yj } = polygon[j]; const intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); if (intersect) inside = !inside; } return inside; }
飞船对象扩展与碰撞检测修改
给飞船添加获取旋转后顶点的方法,并更新crashWith:
// 飞船对象添加获取顶点的方法 this.getVertices = function() { const localPoints = [ { x: 0, y: 0 }, { x: this.width / 2, y: -this.height / 2 }, { x: this.width, y: 0 }, { x: this.width, y: this.height }, { x: 0, y: this.height } ]; return localPoints.map(p => rotatePoint(p.x, p.y, this.x, this.y, this.angle)); }; // 更新碰撞检测函数 this.crashWith = function(otherobj) { const shipVertices = this.getVertices(); // 检测飞船顶点是否在地面内 for (const p of shipVertices) { if (pointInRect(p, otherobj)) return true; } // 获取地面矩形顶点 const rectVertices = [ { x: otherobj.x, y: otherobj.y }, { x: otherobj.x + otherobj.width, y: otherobj.y }, { x: otherobj.x + otherobj.width, y: otherobj.y + otherobj.height }, { x: otherobj.x, y: otherobj.y + otherobj.height } ]; // 检测飞船边与地面边是否相交 for (let i = 0; i < shipVertices.length; i++) { const A = shipVertices[i]; const B = shipVertices[(i + 1) % shipVertices.length]; for (let j = 0; j < rectVertices.length; j++) { const C = rectVertices[j]; const D = rectVertices[(j + 1) % rectVertices.length]; if (segmentsIntersect(A, B, C, D)) return true; } } // 检测地面顶点是否在飞船内 for (const p of rectVertices) { if (pointInPolygon(p, shipVertices)) return true; } return false; };
方案二:分离轴定理(SAT)
这是更高效的凸多边形碰撞检测方法,核心是检查两个多边形在所有边的垂直轴上的投影是否重叠,若所有轴投影都重叠则判定碰撞。
SAT实现与碰撞检测修改
// SAT凸多边形碰撞检测 function satCollision(polyA, polyB) { // 获取所有边的垂直轴 function getAxes(poly) { const axes = []; for (let i = 0; i < poly.length; i++) { const p1 = poly[i]; const p2 = poly[(i + 1) % poly.length]; const vx = p2.x - p1.x; const vy = p2.y - p1.y; // 垂直轴并归一化 const axis = { x: -vy, y: vx }; const len = Math.sqrt(axis.x ** 2 + axis.y ** 2); axis.x /= len; axis.y /= len; axes.push(axis); } return axes; } // 计算多边形在轴上的投影 function projectPoly(poly, axis) { let min = poly[0].x * axis.x + poly[0].y * axis.y; let max = min; for (let i = 1; i < poly.length; i++) { const proj = poly[i].x * axis.x + poly[i].y * axis.y; min = Math.min(min, proj); max = Math.max(max, proj); } return { min, max }; } // 检查投影是否重叠 function projectionsOverlap(projA, projB) { return !(projA.max < projB.min || projB.max < projA.min); } const axesA = getAxes(polyA); const axesB = getAxes(polyB); // 检查所有轴 for (const axis of axesA) { const projA = projectPoly(polyA, axis); const projB = projectPoly(polyB, axis); if (!projectionsOverlap(projA, projB)) return false; } for (const axis of axesB) { const projA = projectPoly(polyA, axis); const projB = projectPoly(polyB, axis); if (!projectionsOverlap(projA, projB)) return false; } return true; } // 更新飞船的crashWith方法 this.crashWith = function(otherobj) { const shipVertices = this.getVertices(); // 复用方案一中的getVertices方法 const rectVertices = [ { x: otherobj.x, y: otherobj.y }, { x: otherobj.x + otherobj.width, y: otherobj.y }, { x: otherobj.x + otherobj.width, y: otherobj.y + otherobj.height }, { x: otherobj.x, y: otherobj.y + otherobj.height } ]; return satCollision(shipVertices, rectVertices); };
内容的提问来源于stack exchange,提问作者Ninju4821
相关产品推荐
相关产品推荐

