碰撞检测和响应的正确操作顺序是什么?
What is the correct order of operations for collision detection and response?
我正在尝试制作一个 2D 游戏引擎,但我似乎无法让碰撞一直正常工作(通常事情会卡住或相互穿过)。不深入代码,这是我更新的顺序。
- 获取用户输入并更新玩家速度
- 保存每个实体的位置,然后移动速度/更新间隔单位
- 检查每个可移动实体是否与所有其他实体发生碰撞。如果实体与某物发生碰撞,它会移动到其原来的位置,并为两个碰撞实体设置新的速度。
碰撞冲量在此函数中计算:
private static void CollisionImpulse(PhysicsEntity a, PhysicsEntity b)
{
var relative = b.Velocity.Vector - a.Velocity.Vector;
var normal = Vector2.Normalize(relative);
var e = Math.Min(a.Material.Elasticity, b.Material.Elasticity);
var j = (-(1 + e) * Vector2.Dot(relative, normal)) /
(Vector2.Dot(normal, normal) * (a.InverseMass + b.InverseMass));
if (double.IsNaN(j)) return;
var velocityA = normal * (float) (j / a.Mass);
var velocityB = normal * (float) (j / b.Mass);
a.Velocity.X -= velocityA.X;
a.Velocity.Y -= velocityA.Y;
if (!b.Movable) return;
b.Velocity.X += velocityB.X;
b.Velocity.Y += velocityB.Y;
b.Position = b.OldPosition;
}
这是检查碰撞的函数:
public override void Update()
{
foreach (var entity in Universe.PhysicsEntities)
{
if (entity.Equals(this) || entity.Collided) continue;
CollisionResolution.ResolveCollision(this, entity);
if (!Collided) continue;
Position = OldPosition;
break;
}
}
几天来我一直在尝试调整代码,但我无法弄清楚哪里出了问题。我希望一些新鲜的眼光可以阐明我的困境。
第 3 步存在一个根本性问题 "If the entity collides with something, it is moved to its old position..." - 因为您现在再次移动这个实体,您需要重新运行 之前所有实体与这个实体的碰撞检测,否则你可能会造成重叠。
例如如果你有 3 个台球(A、B、C),你将所有 3 个移到他们想要的新位置 A'、B'、C'(在你的步骤 2 中)。现在对于第 3 步,您检查并且 A'B' 和 A'C' 不会发生碰撞,它们不会发生碰撞,但是随后您检查 B'C' 并且它们会发生碰撞,因此在您的解决方案中移动 B',C ' 回到 B,C - 但现在技术上你可能已经移动了 B and/or C 所以它现在与 A 相交。
要使第 3 步成功,您需要:
1) 确保开始时没有对象重叠,并且每当您在步骤 3 中检测到碰撞时,要么 (a) 重新运行 我们之前说过的每个先前对象的碰撞都没有不要与这些对象发生碰撞,或者继续重新运行整个步骤 3,直到在最后一个循环中没有对象被重置。如果您有很多对象并且它们都开始相互重置,这可能会变得昂贵。
或:
2) 不是每次都将对象重置到它们最后一帧的 "safe" 位置,而是合力(因此是加速度,而不是速度)与对象相互渗透的程度有关。这在 3D 物理引擎中很常见,如果它们相互渗透太多,可能会导致对象爆炸。
另请注意,如果您在 1 帧内将对象移动得很远(例如,超过其大小的一半),它可能会在碰撞重叠检测未触发的情况下穿过另一个对象。这通常被称为子弹穿纸问题
我正在尝试制作一个 2D 游戏引擎,但我似乎无法让碰撞一直正常工作(通常事情会卡住或相互穿过)。不深入代码,这是我更新的顺序。
- 获取用户输入并更新玩家速度
- 保存每个实体的位置,然后移动速度/更新间隔单位
- 检查每个可移动实体是否与所有其他实体发生碰撞。如果实体与某物发生碰撞,它会移动到其原来的位置,并为两个碰撞实体设置新的速度。
碰撞冲量在此函数中计算:
private static void CollisionImpulse(PhysicsEntity a, PhysicsEntity b)
{
var relative = b.Velocity.Vector - a.Velocity.Vector;
var normal = Vector2.Normalize(relative);
var e = Math.Min(a.Material.Elasticity, b.Material.Elasticity);
var j = (-(1 + e) * Vector2.Dot(relative, normal)) /
(Vector2.Dot(normal, normal) * (a.InverseMass + b.InverseMass));
if (double.IsNaN(j)) return;
var velocityA = normal * (float) (j / a.Mass);
var velocityB = normal * (float) (j / b.Mass);
a.Velocity.X -= velocityA.X;
a.Velocity.Y -= velocityA.Y;
if (!b.Movable) return;
b.Velocity.X += velocityB.X;
b.Velocity.Y += velocityB.Y;
b.Position = b.OldPosition;
}
这是检查碰撞的函数:
public override void Update()
{
foreach (var entity in Universe.PhysicsEntities)
{
if (entity.Equals(this) || entity.Collided) continue;
CollisionResolution.ResolveCollision(this, entity);
if (!Collided) continue;
Position = OldPosition;
break;
}
}
几天来我一直在尝试调整代码,但我无法弄清楚哪里出了问题。我希望一些新鲜的眼光可以阐明我的困境。
第 3 步存在一个根本性问题 "If the entity collides with something, it is moved to its old position..." - 因为您现在再次移动这个实体,您需要重新运行 之前所有实体与这个实体的碰撞检测,否则你可能会造成重叠。
例如如果你有 3 个台球(A、B、C),你将所有 3 个移到他们想要的新位置 A'、B'、C'(在你的步骤 2 中)。现在对于第 3 步,您检查并且 A'B' 和 A'C' 不会发生碰撞,它们不会发生碰撞,但是随后您检查 B'C' 并且它们会发生碰撞,因此在您的解决方案中移动 B',C ' 回到 B,C - 但现在技术上你可能已经移动了 B and/or C 所以它现在与 A 相交。
要使第 3 步成功,您需要:
1) 确保开始时没有对象重叠,并且每当您在步骤 3 中检测到碰撞时,要么 (a) 重新运行 我们之前说过的每个先前对象的碰撞都没有不要与这些对象发生碰撞,或者继续重新运行整个步骤 3,直到在最后一个循环中没有对象被重置。如果您有很多对象并且它们都开始相互重置,这可能会变得昂贵。
或:
2) 不是每次都将对象重置到它们最后一帧的 "safe" 位置,而是合力(因此是加速度,而不是速度)与对象相互渗透的程度有关。这在 3D 物理引擎中很常见,如果它们相互渗透太多,可能会导致对象爆炸。
另请注意,如果您在 1 帧内将对象移动得很远(例如,超过其大小的一半),它可能会在碰撞重叠检测未触发的情况下穿过另一个对象。这通常被称为子弹穿纸问题