我的 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((中规范化法线。如果没有这一点,投影值就会失真,不应该进行比较以获得正确的轴
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. 比较MTVBet((内部的绝对大小。当它们指向法线的另一个方向时,计算的幅度可以是负的
if (responseMagnitude != null && Math.Abs(responseMagnitude.Value) < Math.Abs(minResponseMagnitude))
  1. 不再需要以下代码,因为我们已经在1中规范了向量
//var normalLength = responseNormal.Length();
//responseNormal /= normalLength;
//minResponseMagnitude /= normalLength;

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

在MTVBetween((内部调用CheckOverlapSAT((方法似乎是多余的,当任何responseMigitude为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的情况下走一条更简单的路线。但我想你也想支持多边形。

最新更新