我的物理引擎中更新问题的动量和顺序


22

在此处输入图片说明

这个问题是我上一个问题的“后续”问题,涉及到碰撞检测和解决,您可以在这里找到。


如果您不想阅读上一个问题,这里是有关我的物理引擎如何工作的简短描述:

每个物理实体都存储在称为SSSPBody的类中。

仅支持AABB。

每个SSSPBody存储在一个名为SSSPWorld的类中,该类更新每个身体并处理重力。

每帧,SSSPWorld都会更新每个身体。

每个更新的实体都在空间哈希中查找附近的实体,检查是否需要检测与它们的碰撞。如果是,它们将调用“碰撞”事件并检查是否需要解决与其之间的冲突。如果是,他们将计算穿透矢量和方向重叠,然后更改其位置以解决穿透问题。

当一个物体与另一个物体碰撞时,只需将其速度设置为自己的速度即可将其速度传递给另一个物体。

如果从上一帧起未更改位置,则将物体的速度设置为0。如果它也与移动的物体(例如升降机或移动的平台)相撞,则会计算升降机的运动差,以查看物体是否尚未从其最后位置移动。

同样,当一个实体的所有AABB角与某帧中的某些内容重叠时,它会引发“压碎”事件。

是我的游戏的完整源代码。它分为三个项目。SFMLStart是一个简单的库,用于处理实体的输入,绘制和更新。SFMLStartPhysics是最重要的类,其中SSSPBody和SSSPWorld类位于其中。PlatformerPhysicsTest是游戏项目,包含所有游戏逻辑。

是在SSSPBody类,评论和简化了“更新”的方法。如果您不想看整个SFMLStartSimplePhysics项目,则只能看一下。(即使这样做,您也应该看看它,因为它已被注释。)


.gif显示两个问题。

  1. 如果将实体放置在不同的顺序中,则会发生不同的结果。左侧的板条箱与右侧的板条箱相同,只是以相反的顺序放置(在编辑器中)。
  2. 两个板条箱都应朝屏幕顶部推进。在左边的情况下,没有箱子被推进。在右边,只有其中之一是。两种情况都是意外的。

第一个问题:更新顺序

这很容易理解。在左侧的情况下,最上面的板条箱先于另一个板条箱被更新。即使底部的板条箱将速度“传递”到另一个板条,它也需要等待下一帧移动。由于它没有移动,因此底部木箱的速度设置为0。

我不知道如何解决此问题。我希望解决方案不依赖于“排序”更新列表,因为我觉得我在整个物理引擎设计中做错了什么。

主要的物理引擎(Box2D,Bullet,花栗鼠)如何处理更新顺序?


第二个问题:只有一个板条箱被推向天花板

我还不明白为什么会这样。“弹簧”实体的作用是将物体的速度设置为-4000,然后将其重新放置在弹簧本身的顶部。即使禁用重新定位代码,该问题仍然会发生。

我的想法是,当底部板条箱与顶部板条箱发生碰撞时,其速度设置为0。我不确定为什么会这样。


尽管有机会看起来像放弃第一个问题的人,但我在上面发布了整个项目的源代码。我没有任何东西可以证明这一点,但是相信我,我努力解决了这个问题,但是我找不到解决方案,并且我以前没有任何物理和碰撞方面的经验。我一直在努力解决这两个问题超过一个星期,现在我很绝望。

我认为如果不从游戏中剥离许多功能(例如,速度传递和弹簧),就无法找到解决方案。

非常感谢您花时间阅读此问题,如果您甚至想提出解决方案或建议,也要多谢。


每当您堆叠盒子时,都可以结合它们的物理原理,以便将它们视为单个对象吗?
CiscoIPPhone

Answers:


12

实际上,更新问题的顺序对于普通的脉冲物理引擎来说非常普遍,您不能像Vigil所建议的那样延迟施加力,而当一个对象同时与另外两个对象碰撞时,您将破坏能量保存。通常,尽管采用不同的更新顺序会产生明显不同的结果,但是他们确实设法做出了看起来很真实的东西。

在任何情况下,出于您的目的,我都会建议您建立一个脉冲弹簧系统,以免产生冲动。

基本思想是,您无需试图一步一步地解决碰撞问题,而是要向碰撞的对象施加力,该力应等于对象之间的重叠量,这相当于碰撞过程中真实对象如何转换其碰撞力。运动能量转化为变形然后又重新运动,此系统的优点在于,它允许力传播通过一个对象,而该对象不必来回弹跳,并且可以合理地完全独立执行更新顺序。

为了使物体停止运动而不是无限期地反弹,您必须应用某种形式的阻尼,这可能会极大地影响游戏的样式和感觉,具体取决于您的操作方式,但是最基本的方法是向两个与其内部运动等效的物体施加力,您可以选择仅在它们彼此靠近或彼此远离时才施加力,后者可以用来完全防止物体反弹当它们落地时,还会使它们有些粘滞。

您还可以通过在碰撞的垂直方向上制动对象来产生摩擦效果,制动量应等于重叠量。

通过使所有对象具有相同的质量,可以很容易地绕开质量的概念,而如果您只是忽略加速它们,那么不动的对象将像无限质量一样工作。

一些伪代码,以防万一上面的内容不够清楚:

//Presuming that you have done collision checks between two objects and now have  
//numbers for how much they overlap in each direction.
overlapX
overlapY
if(overlapX<overlapY){ //Do collision in direction X
    if(obj1.X>obj2.X){
        swap(obj1,obj2)
    }
    //Spring effect:
    obj1.addXvelocity-=overlapX*0.1 //Constant, the lower this is set the softer the  
                                    //collision will be.
    obj2.addXvelocity+=overlapX*0.1
    //Dampener effect:
    velocityDifference=obj2.Xvelocity-obj1.Xvelocity
    //velocityDifference=min(velocityDifference,0) //Uncomment to only dampen when  
                                                   //objects move towards each other.
    obj1.addXvelocity+=velocityDifference*0.1 //Constant, higher for more dampening.
    obj2.addXvelocity-=velocityDifference*0.1
    //Friction effect:
    if(obj1.Yvelocity>obj2.Yvelocity){
        swap(obj1,obj2)
    }
    friction=overlapX*0.01
    if(2*friction>obj2.Yvelocity-obj1.Yvelocity){
        obj1.addYvelocity+=(obj2.Yvelocity-obj1.Yvelocity)/2
        obj2.addYvelocity-=(obj2.Yvelocity-obj1.Yvelocity)/2
    }
    else{
        obj1.addYvelocity+=friction
        obj2.addYvelocity-=friction
    }
}
else{ //Do collision in direction Y

}

addXvelocity和addYvelocity属性的要点是,在完成所有碰撞处理后,这些属性将添加到其对象的速度中。

编辑:
您可以按照以下顺序进行操作,其中每个子弹必须在执行下一个子弹之前在所有元素上执行:

  • 检测到冲突,可以在检测到冲突后立即解决它们。
  • 将addVelocity值添加到速度值,添加重力Yvelocity,将addVelocity值重置为0,根据对象的速度移动它们。
  • 渲染场景。

另外,我意识到在我的第一篇文章中以下内容可能并不完全清楚,在重力物体的影响下,当彼此重合时,它们会重叠,这表明它们的碰撞盒应略高于其图形表示,以避免重叠视觉上。如果以更高的更新速率运行物理,则此问题将较少。我建议您尝试以120Hz的频率运行,以在CPU时间和物理精度之间取得合理的平衡。

Edit2:
非常基本的物理引擎流程:

  • 碰撞和重力会产生力/加速度。 acceleration = [Complicated formulas]
  • 力/加速度被加到速度上。 velocity += acceleration
  • 将速度添加到位置。 position += velocity

看起来不错,从来没有想过平台游戏的质量弹簧。
赞叹有

当我回到家时,我将尝试在几个小时内实现这一目标。我是否应该同时移动(位置+ =速度)物体,然后检查碰撞,还是一个一个地移动并检查碰撞?[此外,我是否必须手动修改位置以解决冲突?还是改变速度会解决这个问题?]
Vittorio Romeo

我不确定如何解释您的第一个问题。碰撞分辨率将改变速度,因此仅间接影响位置。
aaaaaaaaaaaaaa

事实是,我通过手动将实体的速度设置为某个值来移动它们。要解决重叠问题,我从其位置移除了重叠距离。如果使用您的方法,是否必须使用力或其他方法移动实体?我从来没有做过
罗密欧

从技术上讲,是的,您将不得不使用力,但是在我的代码中,所有对象的权重均为1,因此有点简化了,因此力等于加速度。
aaaaaaaaaaaaaa

14

好吧,你显然不是一个容易放弃的人,你是一个真正的铁人,我会早得多地举手,因为这个项目与海带森林非常相似:)

首先,位置和速度随处可见,从物理子系统的角度来看,这是灾难的根源。此外,当通过各种子系统更改整体事物时,请创建私有方法,例如“ ChangeVelocityByPhysicsEngine”,“ ChangeVelocityBySpring”,“ LimitVelocity”,“ TransferVelocity”或类似的方法。它将增加检查逻辑的特定部分所做的更改的能力,并为这些速度更改提供其他含义。这样,调试会更容易。

第一个问题。

问题本身。现在,您只是按照外观和游戏逻辑的顺序“按原样”应用位置和速度修正。如果不仔细地对每个复杂事物的物理方式进行硬编码,那么这对于复杂的交互将不起作用。则不需要单独的物理引擎。

为了进行无干扰的复杂交互,您需要在基于由初始速度更改的位置和基于“后速度”更改的位置的最终检测碰撞之间添加一个额外的步骤。我想它会像这样:

  • 使用作用在物体上的所有力来积分速度(您现在直接应用速度固定,将速度计算留给物理引擎并使用力来移动物体),然后使用新的速度积分位置。
  • 检测碰撞,然后恢复速度和位置,
  • 然后处理碰撞(使用没有立即更新位置的脉冲,ofc,只有速度改变,直到最后一步)
  • 再次积分新的速度,并再次使用脉冲处理所有碰撞,除非现在碰撞是非弹性的。
  • 使用结果速度对位置进行最终积分。

可能会弹出其他内容,例如处理抽搐,在FPS较小时拒绝堆积,或准备其他类似的事情:)

第二个问题

这两个“沉重”板条箱的垂直速度永远不会从零变化。奇怪的是,您在PhysSpring的Update循环中分配了速度,但是在PhysCrate的Update循环中它已经为零。可能会找到一条速度出错的行,但是由于出现“收获了缝制”的情况,所以我在这里停止了调试。当调试变得困难时,是时候停止编码并开始重新考虑所有内容了。但是,即使到代码编写者都无法理解代码中正在发生什么的地步,那么您的代码库也已经死了,而您却没有意识到:)

第三个问题

我认为当您需要重新创建Farseer的一部分以执行基于平铺的简单平台程序时,有些事情发生了。就个人而言,我认为您当前的引擎具有丰富的经验,然后完全抛弃它,以使用更简单直接的基于图块的物理方法。在这样做的时候,明智的做法是选择Debug.Assert之类的东西,甚至恐怖的单元测试,因为这样可以更早地捕获意外的东西。


我喜欢那种“海带森林”的比较。

实际上,我对使用这样的词感到a愧,但是我认为,如果它导致一两次重构,那将是合理的。
2011年

仅在t处进行一次测试,是否总不会出现这种情况?我想您需要将t处的速度积分,然后在将任何速度设置为0之前检查t + 1中的碰撞?
乔纳森·康奈尔

是的,我们使用Runge-Kutta或其他方法对从t到t + dt的初始状态进行积分后,正在检测前方的碰撞。
2011年

“使用作用在身体上的所有力来积分速度”“将速度计算留给物理引擎”-我理解您要说的话,但是我不知道该怎么做。有什么例子/文章可以告诉我吗?
罗密欧

7

当一个物体与另一个物体碰撞时,只需将其速度设置为自己的速度即可将其速度传递给另一个物体。

您的问题是,这些根本上是关于运动的错误假设,因此您所获得的与您所熟悉的运动并不相似。

当一个物体与另一个物体碰撞时,动量得以保留。将其视为“ A命中B”与“ B命中A”是将及物动词应用于不及物动词。A和B碰撞;产生的动量必须等于初始动量。也就是说,如果A和B的质量相等,则它们现在都将以其原始速度的平均值行进。

您可能还需要一些碰撞坡度和迭代求解器,否则您将遇到稳定性问题。您可能应该阅读Erin Catto的一些GDC演示文稿。


2
如果碰撞是完全无弹性的,例如A和B是一块面团,它们只会得到原始速度的平均值。
MikaelÖhman2011年

“通过简单地将身体的速度设置为自己的速度”。像这样的陈述阐明了为什么它不起作用。总的来说,我总是发现,经验不足的人在编写物理系统时并不了解所涉及的基本原理。您永远不会“仅设置速度”或“简单地...”。人体特性的每一个改变都应直接应用动力学定律。包括动量,能量守恒等。是的,总会有一些不稳定性因素来补偿不稳定性,但是您绝对不能神奇地改变物体的速度。
MrCranky 2011年

首先尝试使发动机运转时,假定无弹性的物体是最容易的,复杂度越低越容易解决问题。
Patrick Hughes

4

我认为您付出了很大的努力,但似乎代码的结构存在根本问题。正如其他人所建议的那样,将操作分成谨慎的部分可能会有所帮助,例如:

  1. 广泛阶段:遍历所有对象-进行快速测试(例如AABB)以确定哪些对象可能会发生碰撞-丢弃那些不是的对象。
  2. 窄相:循环遍历所有碰撞对象-计算碰撞的渗透向量(例如,使用SAT)。
  3. 碰撞响应:在碰撞矢量列表中循环-根据质量计算力矢量,然后使用该值计算加速度矢量。
  4. 积分:遍历所有加速度矢量并积分位置(和旋转,如果需要)。
  5. 渲染:循环遍历所有计算出的位置并渲染每个对象。

通过分离阶段,所有对象将同步同步更新,而您将不会遇到当前正在苦苦挣扎的订单依赖性。通常,该代码也变得更简单,更容易更改。这些阶段中的每个阶段都是相当通用的,并且在拥有工作系统之后,通常可以用更好的算法代替。

也就是说,每个部分本身都是一门科学,并且可能会花费大量时间来尝试找到最佳解决方案。最好从一些最常用的算法开始:

  • 广义相碰撞检测空间哈希
  • 窄相碰撞检测:对于简单的瓷砖物理,您可以仅应用“轴对齐边界框(AABB)”相交测试。对于更复杂的形状,可以使用“ 分离轴定理”。无论使用哪种算法,它都应返回两个对象之间的交点的方向和深度(称为穿透向量)。
  • 碰撞响应:使用投影解决互穿。
  • 集成度:集成度是决定发动机稳定性和速度的最大因素。两种流行的选择是Verlet(快速但简单)或RK4(准确但缓慢)集成。使用Verlet集成可以导致极其简单的设计,因为大多数物理行为(弹跳,旋转)都可以轻松完成。我学习过RK4集成的最佳参考书目之一是Glen Fiedler的游戏物理系列

牛顿运动定律是一个很好的(显而易见的)起点。


谢谢回复。但是,如何在身体之间传递速度?它在整合阶段发生吗?
罗密欧

从某种意义上说是的。速度传递从碰撞响应阶段开始。那就是当您计算作用在物体上的力时。力通过公式加速度=力/质量转换为加速度。在积分阶段使用加速度来计算速度,然后使用速度来计算位置。积分阶段的精度决定了速度(以及随后的位置)随时间变化的精度。
卢克·范
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.