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

如何在Canvas中检测带旋转的两个对象间的碰撞?

旋转飞船与地面的碰撞检测解决方案

你当前使用的轴对齐矩形碰撞检测(AABB)仅适用于无旋转的矩形,飞船旋转后,实际碰撞区域是自定义的多边形,原检测逻辑完全失效。以下是两种适配旋转场景的解决方案:

方案一:基于顶点与边的碰撞检测

通过计算飞船旋转后的顶点世界坐标,检测以下三种情况判断碰撞:

  1. 飞船任意顶点进入地面矩形内
  2. 飞船的边与地面矩形的边相交
  3. 地面矩形的任意顶点进入飞船多边形内

工具函数实现

// 旋转本地坐标点到世界坐标
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 04:04:51