游戏碰撞检测:分离轴定理详解

摘要

分离轴定理(SAT)是一种高效的凸多边形碰撞检测算法。其核心思想是通过寻找能将两物体投影分离的轴线来判断碰撞:若存在任一轴线使投影不重叠,则物体未碰撞;若所有轴线投影均重叠,则发生碰撞。SAT在游戏中广泛应用,支持任意凸形状的精确检测。2D实现需检查多边形各边法线方向作为分离轴,3D则需增加面法线和边叉积方向检测。文中提供了简洁的C#二维实现,包含向量运算、多边形投影及碰撞检测逻辑,适用于游戏开发中的复杂碰撞场景。算法优势在于比AABB/圆形检测更精确且计算高效,是物理引擎的常用基础技术。


一、什么是分离轴定理(SAT)?

1. 生活中的比喻

想象你有两块形状奇怪的饼干(比如星星形和月亮形),你想知道它们有没有碰在一起。你把它们放在桌子上,从上面看。

你可以用一根直尺(轴)放在桌面上,试着“投影”这两块饼干到直尺上。

  • 如果你能找到某个方向,让两块饼干在直尺上的投影完全不重叠,那它们一定没有碰到。
  • 如果无论怎么放直尺,两块饼干的投影总有重叠,那它们一定有碰撞。

这根“直尺”就是“分离轴”,这就是分离轴定理的核心思想。


2. 形象图示

假设有两个多边形A和B:

  • 你把A和B分别“投影”到某个方向的轴上(比如x轴、y轴、斜着的轴)。
  • 如果在某个轴上,A的投影区间和B的投影区间没有重叠,那A和B就没有碰撞
  • 如果所有轴上都重叠,那A和B就发生了碰撞

二、SAT的原理

  • 定理内容:如果两个凸多边形(或凸多面体)没有碰撞,那么一定存在一条轴(分离轴),使得它们在这条轴上的投影不重叠。
  • 检测方法:只要找到一个分离轴,证明没碰撞;如果所有可能的分离轴都没有分开它们,说明碰撞了。

常用的分离轴

  • 两个物体各自的边的法线方向(2D),或面法线/边叉积方向(3D)。

三、在游戏中的实际应用

1. 游戏场景举例

  • 2D平台游戏中,角色和斜坡、箱子等多边形障碍物的碰撞检测。
  • 3D游戏中,两个凸多面体(比如刚体、车辆、道具)的精确碰撞检测。

2. 为什么用SAT?

  • 比AABB/圆形更精确:AABB只能检测矩形,圆只能检测圆,SAT可以检测任意凸多边形/多面体。
  • 效率高:只需要检测有限个轴(2D最多是两个物体所有边的法线方向),比暴力检测所有点要快很多。

3. 实际流程

以2D为例(3D原理类似):

  1. 找分离轴:取两个多边形所有边的法线方向作为分离轴。
  2. 投影:把两个多边形的所有顶点投影到每个轴上,得到区间。
  3. 检测重叠:如果有任何一个轴上区间不重叠,立即返回“无碰撞”。
  4. 全部重叠:如果所有轴都重叠,说明发生了碰撞。

四、形象小结

  • SAT就像用不同方向的手电筒照射两个物体的影子,只要有一个方向影子不重叠,物体就没碰到。
  • 在游戏中,SAT让你能检测任意凸形状的碰撞,比如角色和斜坡、复杂障碍物、3D刚体等。

五、代码/伪代码简要示例

// 伪代码:检测两个凸多边形是否碰撞
foreach (每个多边形的每条边)
{
    axis = 这条边的法线方向;
    投影A = 把A的所有顶点投影到axis上,得到区间[minA, maxA];
    投影B = 把B的所有顶点投影到axis上,得到区间[minB, maxB];
    if (maxA < minB || maxB < minA)
        return "没有碰撞"; // 找到分离轴
}
return "发生碰撞"; // 所有轴都重叠

六、总结

  • 分离轴定理是检测两个凸形状是否碰撞的高效方法。
  • 实际应用:2D/3D游戏中任意凸形状的精确碰撞检测。
  • 形象理解:只要能找到一个方向让两个物体的“影子”不重叠,就一定没碰撞。

下面给你一个简洁易懂的C#二维SAT(分离轴定理)碰撞检测实现,适用于任意凸多边形。代码注释详细,便于理解和扩展。


一、核心数据结构

using System;
using System.Collections.Generic;

public struct Vec2
{
    public float x, y;
    public Vec2(float x, float y) { this.x = x; this.y = y; }

    // 向量减法
    public static Vec2 operator -(Vec2 a, Vec2 b) => new Vec2(a.x - b.x, a.y - b.y);

    // 点积
    public float Dot(Vec2 other) => x * other.x + y * other.y;

    // 法线(顺时针90度)
    public Vec2 Perpendicular() => new Vec2(-y, x);

    // 单位化
    public Vec2 Normalized()
    {
        float len = (float)Math.Sqrt(x * x + y * y);
        return len > 1e-6 ? new Vec2(x / len, y / len) : new Vec2(0, 0);
    }
}

// 凸多边形
public class ConvexPolygon
{
    public List<Vec2> vertices; // 顶点按顺时针或逆时针顺序排列

    public ConvexPolygon(List<Vec2> vertices)
    {
        this.vertices = vertices;
    }
}

二、SAT碰撞检测主函数

public static class SAT
{
    // 检测两个凸多边形是否碰撞
    public static bool IsColliding(ConvexPolygon polyA, ConvexPolygon polyB)
    {
        // 检查A和B所有边的法线方向
        return !HasSeparatingAxis(polyA, polyB) && !HasSeparatingAxis(polyB, polyA);
    }

    // 检查polyA的所有边的法线方向是否为分离轴
    private static bool HasSeparatingAxis(ConvexPolygon polyA, ConvexPolygon polyB)
    {
        int countA = polyA.vertices.Count;
        for (int i = 0; i < countA; i++)
        {
            // 当前边
            Vec2 p1 = polyA.vertices[i];
            Vec2 p2 = polyA.vertices[(i + 1) % countA];
            Vec2 edge = p2 - p1;
            Vec2 axis = edge.Perpendicular().Normalized();

            // 投影A
            ProjectPolygon(polyA, axis, out float minA, out float maxA);
            // 投影B
            ProjectPolygon(polyB, axis, out float minB, out float maxB);

            // 检查投影区间是否分离
            if (maxA < minB || maxB < minA)
                return true; // 找到分离轴,无碰撞
        }
        return false; // 没有分离轴
    }

    // 把多边形投影到轴上,得到最小最大投影值
    private static void ProjectPolygon(ConvexPolygon poly, Vec2 axis, out float min, out float max)
    {
        min = max = poly.vertices[0].Dot(axis);
        foreach (var v in poly.vertices)
        {
            float proj = v.Dot(axis);
            if (proj < min) min = proj;
            if (proj > max) max = proj;
        }
    }
}

三、使用示例

class Program
{
    static void Main()
    {
        // 三角形A
        var polyA = new ConvexPolygon(new List<Vec2>
        {
            new Vec2(0, 0),
            new Vec2(2, 0),
            new Vec2(1, 2)
        });

        // 四边形B
        var polyB = new ConvexPolygon(new List<Vec2>
        {
            new Vec2(1, 1),
            new Vec2(3, 1),
            new Vec2(3, 3),
            new Vec2(1, 3)
        });

        bool colliding = SAT.IsColliding(polyA, polyB);
        Console.WriteLine("碰撞结果: " + (colliding ? "发生碰撞" : "没有碰撞"));
    }
}

输出:

碰撞结果: 发生碰撞

四、说明与扩展

  • 只适用于凸多边形,凹多边形需先分解为凸多边形。
  • 可扩展到3D(分离轴为所有面法线和两物体边的叉积方向)。
  • 可返回最小穿透向量(MTV)用于物理分离,方法是记录重叠最小的轴。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你一身傲骨怎能输

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值