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

《Asteroids》游戏移动多边形碰撞检测失效问题求助

问题分析与解决方案

你的多边形碰撞检测逻辑存在根本性缺陷:当前方法仅检查一个多边形的顶点是否落在另一个多边形内部,这种方式会漏掉两个多边形互相重叠但所有顶点都未进入对方内部的场景——比如移动中的飞船与小行星交叉穿透,但双方顶点都没进入对方多边形区域,这就是玩家移动时碰撞检测失效的核心原因。

正确的2D凸多边形碰撞检测应该使用分离轴定理(SAT),这是游戏开发中检测凸多边形碰撞的标准方案,核心逻辑是:若两个凸多边形不相交,必然存在一条轴,使得两个多边形在该轴上的投影完全不重叠。我们只需要遍历两个多边形所有边的垂直法线作为候选轴,检查所有轴上的投影是否重叠,只要有一条轴投影不重叠则无碰撞,所有轴都重叠则判定碰撞。

修复后的碰撞检测代码

替换你原有的isPolygonCollidingWithAnotherPolygon方法为以下实现:

public class Hits {
    // 分离轴定理实现凸多边形碰撞检测
    private static boolean isPolygonCollidingWithAnotherPolygon(Polygon p1, Polygon p2) {
        // 获取两个多边形的所有边的法线轴
        java.util.List<Vector2D> axes = getAxes(p1);
        axes.addAll(getAxes(p2));

        // 检查每个轴上的投影是否重叠
        for (Vector2D axis : axes) {
            Projection proj1 = projectPolygonOntoAxis(p1, axis);
            Projection proj2 = projectPolygonOntoAxis(p2, axis);

            if (!proj1.overlaps(proj2)) {
                // 存在分离轴,无碰撞
                return false;
            }
        }

        // 所有轴投影都重叠,存在碰撞
        return true;
    }

    // 获取多边形所有边的垂直法线轴(单位向量)
    private static java.util.List<Vector2D> getAxes(Polygon polygon) {
        java.util.List<Vector2D> axes = new java.util.ArrayList<>();
        int n = polygon.npoints;

        for (int i = 0; i < n; i++) {
            int j = (i + 1) % n;
            // 获取边的向量
            float x1 = polygon.xpoints[i];
            float y1 = polygon.ypoints[i];
            float x2 = polygon.xpoints[j];
            float y2 = polygon.ypoints[j];
            Vector2D edge = new Vector2D(x2 - x1, y2 - y1);
            // 生成垂直法线并单位化
            Vector2D normal = edge.perpendicular().normalize();
            axes.add(normal);
        }

        return axes;
    }

    // 将多边形投影到指定轴上,返回投影的最小和最大值
    private static Projection projectPolygonOntoAxis(Polygon polygon, Vector2D axis) {
        float min = Float.MAX_VALUE;
        float max = Float.MIN_VALUE;

        for (int i = 0; i < polygon.npoints; i++) {
            float x = polygon.xpoints[i];
            float y = polygon.ypoints[i];
            Vector2D point = new Vector2D(x, y);
            // 计算点在轴上的投影值(点积)
            float dot = point.dot(axis);

            if (dot < min) min = dot;
            if (dot > max) max = dot;
        }

        return new Projection(min, max);
    }

    // 辅助类:表示投影的范围
    private static class Projection {
        float min;
        float max;

        Projection(float min, float max) {
            this.min = min;
            this.max = max;
        }

        // 判断两个投影是否重叠
        boolean overlaps(Projection other) {
            return !(this.max < other.min || other.max < this.min);
        }
    }

    // 辅助类:2D向量运算
    private static class Vector2D {
        float x;
        float y;

        Vector2D(float x, float y) {
            this.x = x;
            this.y = y;
        }

        // 计算点积
        float dot(Vector2D other) {
            return this.x * other.x + this.y * other.y;
        }

        // 获取垂直向量(顺时针旋转90度)
        Vector2D perpendicular() {
            return new Vector2D(this.y, -this.x);
        }

        // 单位化向量
        Vector2D normalize() {
            float length = (float) Math.sqrt(x * x + y * y);
            if (length == 0) return new Vector2D(0, 0);
            return new Vector2D(x / length, y / length);
        }
    }

    // 以下保留你原有的其他方法
    public static boolean isBallHittingSpaceship(Ball b,Spaceship s) {
        return b.isFromShooter && s.getPolygon().contains(b.getX(),b.getY());
    }
    
    public static boolean isBallHittingAsteroid(Ball b,Asteroid a) {
        return a.getPolygon().contains(b.getX(),b.getY());
    }

    public static boolean isShooterHittingBall(Ball b, Player player) {
        return !b.isFromShooter && player.getPolygon().contains(b.getX(), b.getY());
    }
    
    public static boolean isSpaceshipHittingAsteroid(Spaceship spaceship ,Asteroid asteroid) {
        return isPolygonCollidingWithAnotherPolygon(spaceship.getPolygon(),  asteroid.getPolygon());
    }
    
    public static boolean isShooterHittingAsteroid(Player player , Asteroid asteroid) {
        return isPolygonCollidingWithAnotherPolygon(player.getPolygon(), asteroid.getPolygon());
    }

    public static boolean isSpaceshipHittingShooter(Spaceship spaceship, Player player) {
        return isPolygonCollidingWithAnotherPolygon(spaceship.getPolygon(), player.getPolygon());
    }
}

额外说明

  1. 分离轴定理仅适用于凸多边形,如果你的游戏中有凹多边形,需要先将其拆分为多个凸多边形再进行检测(Asteroids中的飞船、小行星一般都是凸多边形,所以这个方案完全适用)。
  2. 对于Ball的碰撞检测,如果Ball是圆形,更准确的方式是检测圆心到多边形的最短距离是否小于等于半径,而不是用contains方法——如果Ball半径很小,当前方法暂时可用,但大尺寸Ball建议改用圆形与多边形的碰撞检测逻辑。

内容的提问来源于stack exchange,提问作者Maor Krasner

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 20:31:07