C# XNA中使用Farseer Physics的OnCollision事件处理程序问题

6

目前我的游戏中这个功能还算正常,但我不是很擅长数学。当两个物体相撞时,如果施加到物体上的力超过设定的阈值,我希望它们能破碎成小块。目前我的碰撞事件处理程序如下。

public bool Collision(Fixture fixtureA, Fixture fixtureB, Manifold manifold) 
{ 
   Vector2 position = manifold.LocalNormal; 
   float angle = (float)Math.Atan2(position.Y, position.X); 
   Vector2 force = Vector2.Zero; 
   if (angle < 0) 
     force = new Vector2((float)(Math.Cos(angle) * fixtureA.Body.LinearVelocity.X), (float)Math.Sin(MathHelper.TwoPi + angle) * fixtureA.Body.LinearVelocity.Y); 
   else 
     force = new Vector2((float)(Math.Cos(angle) * fixtureA.Body.LinearVelocity.X), (float)Math.Sin(MathHelper.TwoPi - angle) * fixtureA.Body.LinearVelocity.Y); 
   double XForce = Math.Sqrt(force.X * force.X); 
   double YForce = Math.Sqrt(force.Y * force.Y); 
   double totalForce = XForce + YForce; 
   if ((Breakable) && (totalForce > BreakForce)) 
   { 
      Breakable = false; 
      Active = false; 
      BreakUp(fixtureA, fixtureB); 
   } 
   return true; 
} 

我很久以前就加入了这个功能,当时只是玩玩而已。但在某些情况下会引起一些问题。例如,如果一个原始体静止在地板上,另一个原始体从相当高的高度落下来,几乎总是落下的盒子爆炸了,而静止的盒子幸存了下来。此外,如果两个盒子并排下落,并且彼此轻微接触,那么两个盒子都会在空中爆炸。这真的不太完美。有没有人有什么想法来改进我的碰撞处理程序?提前感谢。

3个回答

8

好的,所以我的另一个答案是可行的。但我更仔细地看了一下Farseer 3.0(当前的SVN版本),发现它已经几乎完全实现了你想要做的事情。

找到 "BreakableBody.cs"。您可以直接使用它,但否则您可以只复制您想要的功能。

具体来说:您想要将函数附加到夹具的OnCollision而不是PostSolve上。它需要一个ContactConstraint,您可以深入其中并找到碰撞的冲量。

这是BreakableBody使用的函数的实现:

private void PostSolve(ContactConstraint contactConstraint)
{
    if (!Broken)
    {
        float maxImpulse = 0.0f;
        for (int i = 0; i < contactConstraint.manifold.PointCount; ++i)
        {
            maxImpulse = Math.Max(maxImpulse,
                         contactConstraint.manifold.Points[0].NormalImpulse);
            maxImpulse = Math.Max(maxImpulse,
                         contactConstraint.manifold.Points[1].NormalImpulse);
        }

        if (maxImpulse > Strength)
        {
            // Flag the body for breaking.
            _break = true;
        }
    }
}

显然, 管道中的脉冲数据只在PostSolve中正确设置,而不是在OnCollision中。


Russell先生,您真是位绅士。非常感谢您的帮助,我将在此处放弃代码并从那里窃取该功能。现在我很好奇将"_break"设置为true会实现什么效果...嗯嗯嗯 - DrLazer
它标记了“Update()”函数以执行实际的中断(我本来想链接代码:http://farseerphysics.codeplex.com/SourceControl/changeset/view/73518#1086713) - Andrew Russell

6
< p > < em >(虽然这是目前被接受的答案 - 我会引导任何人阅读我的其他答案,可能有更好的方法。)

你计算的碰撞力是完全错误的。你需要在接触点处获得相对速度 - 你得到了一些非常奇怪的东西...

你的代码看起来像是使用Farseer 3.0(因为那更像是Box2DX的一个分支,而不是Farseer 2.1)。在Farseer 2.1中(你有一个ContactList contacts而不是一个Manifold),我所做的是获取冲击速度:

foreach(Contact contact in contacts)
{
    Vector2 position = contact.Position;
    Vector2 v0;
    me.Body.GetVelocityAtWorldPoint(ref position, out v0);
    Vector2 v1 = new Vector2();
    if(!hit.Body.IsStatic)
        hit.Body.GetVelocityAtWorldPoint(ref position, out v1);
    v0 -= v1;

    float hitVelocity = v0.Length();
    // To then get the force, you need the mass of the two objects
}

从对Farseer 3.0源代码的简要查看中,似乎Manifold有一个成员:

public FixedArray2<ManifoldPoint> Points;

同时,ManifoldManifoldPoint都有成员:

public Vector2 LocalPoint;

修改我的Farseer 2.1代码以使用它们应该相当简单。

另外:我建议只需将两个对象标记为需要断开,然后在你的物理更新完成运行后实际断开它们(而不是在碰撞处理程序中)。


嗨,安德鲁,感谢您的回复。我的BreakUp()函数只是根据断裂基元(如果您喜欢,可以称之为芯片)的位置将大量新基元添加到列表中,它们直到下一个物理“Step()”才会被添加。同样,被摧毁的盒子只是被标记为在下一步中要删除。因此,在您的示例中...我应该使用LocalPoint而不是您函数中的position变量,对吗?此外,我可以看到这创建了一个hitVelocity变量,但我不确定它与施加于对象的力有何关系。我知道F = MA,但角度在这里起作用吗? - DrLazer
1
使用LocalPoint。我的Position变量在世界空间中(因此找到GetVelocityAtWorldPoint的本地空间等效项)。要找到力,您需要加速度(F = MA)。您可以通过确定速度在该帧中将改变多少(速度差除以时间)来近似加速度。 - Andrew Russell
@DrLazer:哦,获取特定点的速度会为您处理所有角度问题。 - Andrew Russell
只有最后一件事……你正在使用“me”和“hit”。我的假设是,me是你正在运行函数的类/对象/原语/任何东西,“hit”是它正在碰撞的对象。只是想确保,因为我猜这部分应用程序很重要。哦,那么速度变化是hitVelocity和其当前速度之间的差异吗? - DrLazer
1
@DrLazer:你的假设是正确的。hitVelocity是两个物体在碰撞点之间的相对速度(即考虑旋转/角速度)。换句话说,它是衡量碰撞强度的一个很好的近似值,但它并没有考虑质量。我使用它来确定是否应该播放撞击音效以及音效的音量大小。对于你的目的,我现在推荐我的另一个答案:https://dev59.com/I0_Sa4cB1Zd3GeqP-0Aa#3309849 - Andrew Russell

2

我没有使用过XNA,但作为一个物理问题,为什么不只需从A的线性速度中减去B的线性速度,并将“力”作为结果向量的平方(求组成部分的平方和)得到呢?对于一个非常简单的物理模型来说,这应该与涉及的动能相协调,即使我们忽略了质量(或者弹性或能量和动量之间的区别)。如果物体以相同的速度朝着同一方向移动,您会得到一个小值;如果一个物体以高速接近(或离开!),则您会得到一个大值。


2
这对于例如长的旋转梁“砰”击另一个物体的情况是行不通的。 - Andrew Russell
@Andrew:没错,它不会。只是比发布的代码更加现实一些... - Pontus Gagge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接