【问题标题】:What's wrong with my SAT collision response system?我的 SAT 碰撞响应系统出了什么问题?
【发布时间】:2021-03-22 04:24:32
【问题描述】:

我正在为平台游戏创建 MonoGame 2D 引擎框架,但在创建碰撞响应系统时遇到了问题。虽然我已经让 SAT 检测 工作,但响应会穿过静态物体边缘的实际方向,而不是其正常方向。反转法线轴对我没有用,什么也没做,它只会造成身体离开屏幕的故障。

由于我正在尝试制作平台游戏,因此我只希望将静态主体的法线视为响应方向。例如,如果静态物体是一个盒子,我只希望移动物体沿 90 度法线行进。

这里是解决问题的视频:https://www.youtube.com/watch?v=-wyXfZkxis0

以及“碰撞”模块的来源,其中包含所有相关的几何计算(底部的平移向量算法):

using System;
using Microsoft.Xna.Framework;

namespace Crossfrog.Ferrum.Engine.Modules
{
public static class Collision
{
    public static bool RectsCollide(Rectangle rect1, Rectangle rect2)
    {
        return
            rect1.X <= rect2.X + rect2.Width &&
            rect1.Y <= rect2.Y + rect2.Height &&
            rect1.X + rect1.Width >= rect2.X &&
            rect1.Y + rect1.Height >= rect2.Y;
    }
    private static float DotProduct(Vector2 v1, Vector2 v2)
    {
        return (v1.X * v2.X) + (v1.Y * v2.Y);
    }
    private static Vector2 NormalBetween(Vector2 v1, Vector2 v2)
    {
        return new Vector2(-(v1.Y - v2.Y), v1.X - v2.X);
    }
    private struct ProjectionLine
    {
        public float Start;
        public float End;
    }
    private static ProjectionLine ProjectLine(Vector2[] points, Vector2 normal)
    {
        var projectionLine = new ProjectionLine() { Start = float.MaxValue, End = float.MinValue };
        foreach (var p in points)
        {
            var projectionScale = DotProduct(p, normal);
            projectionLine.Start = Math.Min(projectionScale, projectionLine.Start);
            projectionLine.End = Math.Max(projectionScale, projectionLine.End);
        }
        return projectionLine;
    }
    private static bool CheckOverlapSAT(Vector2[] shape1, Vector2[] shape2)
    {
        for (int i = 0; i < shape1.Length; i++)
        {
            var vertex = shape1[i];
            var nextVertex = shape1[(i + 1) % shape1.Length];

            var edgeNormal = NormalBetween(vertex, nextVertex);
            var firstProjection = ProjectLine(shape1, edgeNormal);
            var secondProjection = ProjectLine(shape2, edgeNormal);

            if (!(firstProjection.Start <= secondProjection.End && firstProjection.End >= secondProjection.Start))
                return false;
        }
        return true;
    }
    public static bool ConvexPolysCollide(Vector2[] shape1, Vector2[] shape2)
    {
        return CheckOverlapSAT(shape1, shape2) && CheckOverlapSAT(shape2, shape1);
    }

    private static float? CollisionResponseAcrossLine(ProjectionLine line1, ProjectionLine line2)
    {
        if (line1.Start <= line2.Start && line1.End > line2.Start)
            return line2.Start - line1.End;
        else if (line2.Start <= line1.Start && line2.End > line1.Start)
            return line2.End - line1.Start;
        return null;
    }

    public static Vector2 MTVBetween(Vector2[] mover, Vector2[] collider)
    {
        if (!ConvexPolysCollide(mover, collider))
            return Vector2.Zero;

        float minResponseMagnitude = float.MaxValue;
        var responseNormal = Vector2.Zero;

        for (int c = 0; c < collider.Length; c++)
        {
            var cPoint = collider[c];
            var cNextPoint = collider[(c + 1) % collider.Length];

            var cEdgeNormal = NormalBetween(cPoint, cNextPoint);

            var cProjected = ProjectLine(collider, cEdgeNormal);
            var mProjected = ProjectLine(mover, cEdgeNormal);

            var responseMagnitude = CollisionResponseAcrossLine(cProjected, mProjected);
            if (responseMagnitude != null && responseMagnitude < minResponseMagnitude)
            {
                minResponseMagnitude = (float)responseMagnitude;
                responseNormal = cEdgeNormal;
            }
        }

        var normalLength = responseNormal.Length();
        responseNormal /= normalLength;
        minResponseMagnitude /= normalLength;

        var mtv = responseNormal * minResponseMagnitude;
        return mtv;
    }
}
}

【问题讨论】:

    标签: c# geometry collision-detection game-physics monogame


    【解决方案1】:

    您的代码几乎是正确的,只需按照以下步骤操作即可。

    1. 在 NormalBetween() 中标准化您的法线。没有这个,您的预测值会被扭曲,不应进行比较以获得正确的轴。
    return Vector2.Normalize(new Vector2(-(v1.Y - v2.Y), v1.X - v2.X));
    
    1. 在 CollisionResponseAcrossLine() 中使用与 CheckOverlapSAT() 中相同的碰撞表达式。或者只对两者使用一种检测方法。
    if (line1.Start <= line2.Start && line1.End >= line2.Start) // use the >= operator
        return line2.Start - line1.End;
    else if (line2.Start <= line1.Start && line2.End >= line1.Start) // use the >= operator
        return line2.End - line1.Start;
    return null;
    
    1. 比较 MTVBetween() 中的绝对幅度。当它们指向法线的另一个方向时,计算的量级可能为负。
    if (responseMagnitude != null && Math.Abs(responseMagnitude.Value) < Math.Abs(minResponseMagnitude))
    
    1. 不再需要以下代码,因为我们已经对 1 中的向量进行了归一化。
    //var normalLength = responseNormal.Length();
    //responseNormal /= normalLength;
    //minResponseMagnitude /= normalLength;
    

    这应该可以让您的示例正常工作。但是,当您尝试使用具有不同分离轴的两个多边形时,它将不起作用,因为在碰撞响应代码中,您只检查静态对撞机的轴。还应检查移动器的轴,就像您在碰撞检测方法 CheckOverlapSAT() 中所做的那样。

    在 MTVBetween() 中调用 CheckOverlapSAT() 方法似乎是多余的,您也可以在任何 responseMagnitude 为 null 时中断 MTVBetween() 方法。

    最后但同样重要的是,考虑将您的 CollisionResponseAcrossLine() 代码替换为以下代码:

    private static float? CollisionResponseAcrossLine(ProjectionLine line1, ProjectionLine line2)
    {
        float distToStartOfLine2 = line2.Start - line1.End;
        if (distToStartOfLine2 > 0)
            return null;
    
        float distToEndOfLine2 = line2.End - line1.Start;
        if (distToEndOfLine2 < 0)
            return null;
    
        if (-distToStartOfLine2 < distToEndOfLine2) // negate distToStartOfLine2, cause it's always negative
            return distToStartOfLine2;
        else
            return distToEndOfLine2;
    }
    

    这也说明了玩家在障碍物内的情况。它比较两边的距离并选择较小的那个。以前,在这种情况下,玩家总是会走到同一个边缘。

    如果您想要只支持 AABB 的代码,那么您可以走一条更简单的路线,而无需依赖 SAT。但我猜你也想支持多边形。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-11
      • 1970-01-01
      • 1970-01-01
      • 2011-09-05
      • 1970-01-01
      相关资源
      最近更新 更多