微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

我的 SAT 碰撞响应系统有什么问题?

如何解决我的 SAT 碰撞响应系统有什么问题?

我正在为平台游戏创建 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;
    }
}
}

解决方法

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

  1. 在 NormalBetween() 中标准化你的法线。否则,您的投影值会失真,不应通过比较来获得正确的轴。
import library

from library.core.core_module1 import core_function
from library.feature.feature_module1 import feature_function
  1. 在 CollisionResponseAcrossLine() 中使用与 CheckOverlapSAT() 中相同的碰撞表达式。或者仅对两者使用一种检测方法。
return Vector2.Normalize(new Vector2(-(v1.Y - v2.Y),v1.X - v2.X));
  1. 比较 MTVBetween() 中的绝对幅度。当它们指向法线的另一个方向时,计算出的幅度可能为负。
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. 不再需要以下代码,因为我们已经在 1 中对向量进行了归一化。
if (responseMagnitude != null && Math.Abs(responseMagnitude.Value) < Math.Abs(minResponseMagnitude))

这应该能让您的示例发挥作用。但是当您尝试使用两个具有不同分隔轴的多边形时,它不起作用,因为在碰撞响应代码中,您只检查静态碰撞器的轴。还应检查动子的轴,就像您在碰撞检测方法 CheckOverlapSAT() 中所做的一样。

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

最后但并非最不重要的一点,请考虑将 CollisionResponseAcrossLine() 代码替换为以下内容:

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

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。