2D 物理/碰撞公式未按预期工作
2D physics / collision formula not working as intended
我有一个 2D 瓷砖矩阵(现在是墙),对于每面墙,我检查我的玩家是否是下一个 Rectangle
(next,如 它将在下一帧的位置) 与那堵墙发生碰撞,如果是,我希望我的玩家站在它旁边,而不是进入它内部。
这是我想出的公式(这只是为了左右碰撞):
//*move entity
if (KState.Down(Keys.W)) en.AddForce(new Vector2(0, -10));
if (KState.Down(Keys.S)) en.AddForce(new Vector2(0, 10));
if (KState.Down(Keys.A)) en.AddForce(new Vector2(-10, 0));
if (KState.Down(Keys.D)) en.AddForce(new Vector2(10, 0));
foreach(Item i in map.Items)
{
//resolve left-right collision
if (en.NextBody(elapsedTime).Intersects(i.Body))
{
//check distance between top left corners
int distance = (int)Math.Abs(i.Position.X - en.Position.X);
//stop player in his horisontal movement
en.AddForce(new Vector2(-en.Force.X, 0));
//place player next to the wall his about to collide with
en.Position = new Vector2(en.Position.X - distance + i.Body.Width, en.Position.Y);
}
}
//stop player in place
if (KState.Clicked(Keys.Space)) en.AddForce(en.Force*-1);
en.Update(elapsedTime);
**
这给了我这个结果(请注意 fps 不稳定并且当前矩形是正方形 32x32):
文本的第一行是玩家 Rectangle
,底部一行是玩家最后撞到的墙的 Rectangle
。
现在,当岩石(玩家)向左移动时,我按住左键一段时间,当向右移动时,我按住右键一段时间。
问题: 正如您在 gif 中看到的,当玩家向左移动时,他在左墙之间移动 1 个像素。当他向右移动时,他弹跳一次,然后紧靠着右边的墙。我不想要任何弹跳,但我不知道如何在我使用的逻辑中解决这个问题。
还有,如果你知道如何解决 Rectangle
冲突的好逻辑,这样它们就不会从任何一边相交,我很乐意与我分享.
编辑 1: 我设法解决了正确问题的碰撞。而不是上面的代码,我把
int sign = Math.Sign(i.Position.X - en.Position.X);
en.AddForce(new Vector2(-en.Force.X, 0));
//wall is right and player is left
if (sign > 0) en.Position = new Vector2(i.Body.Left - en.Body.Width, en.Position.Y);
//wall is left and player is right
if (sign < 0) en.Position = new Vector2(i.Body.Right, en.Position.Y);
但是左侧碰撞的问题是一样的。我试过了
if (sign < 0) en.Position = new Vector2(i.Body.Right + 1, en.Position.Y);
和
if (sign < 0) en.Position = new Vector2(i.Body.Right - 1, en.Position.Y);
以防万一,但问题是一样的(在“...-1...”情况下玩家卡在墙上)。
编辑 2: 根据@paste 的要求,这是来自玩家 class 的 NextBody(float elapsedTime)
的代码。
public Rectangle NextBody(float elapsedTime)
{ return new Rectangle((int)(force.X * elapsedTime) + body.X, (int)(force.Y * elapsedTime) + body.Y, body.Width, body.Height); }
事情是这样的:
第一次物体在岩石向左移动时发生碰撞,算法正常运行。它看到对象重叠,将力设置为 Vector2.Zero
,然后移动 en
使其接触 item
的右侧。好的。您的对象已停止并位于正确的位置。
下一帧发生的事情就是出错的地方。您设置力矢量,然后调用 NextBody()
。让我们看看 X 坐标。它设置为:
(int)(force.X * elapsedTime) + body.X
这意味着什么? force.X
是非零的,elapsedTime
也是非零的,但是它们都小于 1,所以当它们转换为 int
时,结果是零。因此,您的 body
与之前的位置相同,这意味着 Rectangle
没有 重叠。这意味着您的碰撞响应代码被跳过并且您的 force
向量仍然不为零!
然后,再往下,调用播放器的 Update()
方法。我猜你在那个代码中更新了玩家的 Position
和 Body
。但是由于 force
不为零,并且您使用 Vector2
作为 Position
,x 坐标最终会变成 31.841304...
而不是 32
.如果您使用该数字计算新的 Body
并在您的 Draw
方法中使用它,您将最终在 x = 31 处绘制它。下次检查时,它会检测到碰撞, 播放器会再次跳出。
有多种方法可以解决此问题。我在碰撞代码中所做的是首先实际移动对象并记录它们在前一帧中的位置。那样的话,最后一次调整他们的位置就是在他们被移出碰撞区域的时候。
我有一个 2D 瓷砖矩阵(现在是墙),对于每面墙,我检查我的玩家是否是下一个 Rectangle
(next,如 它将在下一帧的位置) 与那堵墙发生碰撞,如果是,我希望我的玩家站在它旁边,而不是进入它内部。
这是我想出的公式(这只是为了左右碰撞):
//*move entity
if (KState.Down(Keys.W)) en.AddForce(new Vector2(0, -10));
if (KState.Down(Keys.S)) en.AddForce(new Vector2(0, 10));
if (KState.Down(Keys.A)) en.AddForce(new Vector2(-10, 0));
if (KState.Down(Keys.D)) en.AddForce(new Vector2(10, 0));
foreach(Item i in map.Items)
{
//resolve left-right collision
if (en.NextBody(elapsedTime).Intersects(i.Body))
{
//check distance between top left corners
int distance = (int)Math.Abs(i.Position.X - en.Position.X);
//stop player in his horisontal movement
en.AddForce(new Vector2(-en.Force.X, 0));
//place player next to the wall his about to collide with
en.Position = new Vector2(en.Position.X - distance + i.Body.Width, en.Position.Y);
}
}
//stop player in place
if (KState.Clicked(Keys.Space)) en.AddForce(en.Force*-1);
en.Update(elapsedTime);
**
这给了我这个结果(请注意 fps 不稳定并且当前矩形是正方形 32x32):
文本的第一行是玩家 Rectangle
,底部一行是玩家最后撞到的墙的 Rectangle
。
现在,当岩石(玩家)向左移动时,我按住左键一段时间,当向右移动时,我按住右键一段时间。
问题: 正如您在 gif 中看到的,当玩家向左移动时,他在左墙之间移动 1 个像素。当他向右移动时,他弹跳一次,然后紧靠着右边的墙。我不想要任何弹跳,但我不知道如何在我使用的逻辑中解决这个问题。
还有,如果你知道如何解决 Rectangle
冲突的好逻辑,这样它们就不会从任何一边相交,我很乐意与我分享.
编辑 1: 我设法解决了正确问题的碰撞。而不是上面的代码,我把
int sign = Math.Sign(i.Position.X - en.Position.X);
en.AddForce(new Vector2(-en.Force.X, 0));
//wall is right and player is left
if (sign > 0) en.Position = new Vector2(i.Body.Left - en.Body.Width, en.Position.Y);
//wall is left and player is right
if (sign < 0) en.Position = new Vector2(i.Body.Right, en.Position.Y);
但是左侧碰撞的问题是一样的。我试过了
if (sign < 0) en.Position = new Vector2(i.Body.Right + 1, en.Position.Y);
和 if (sign < 0) en.Position = new Vector2(i.Body.Right - 1, en.Position.Y);
以防万一,但问题是一样的(在“...-1...”情况下玩家卡在墙上)。
编辑 2: 根据@paste 的要求,这是来自玩家 class 的 NextBody(float elapsedTime)
的代码。
public Rectangle NextBody(float elapsedTime)
{ return new Rectangle((int)(force.X * elapsedTime) + body.X, (int)(force.Y * elapsedTime) + body.Y, body.Width, body.Height); }
事情是这样的:
第一次物体在岩石向左移动时发生碰撞,算法正常运行。它看到对象重叠,将力设置为 Vector2.Zero
,然后移动 en
使其接触 item
的右侧。好的。您的对象已停止并位于正确的位置。
下一帧发生的事情就是出错的地方。您设置力矢量,然后调用 NextBody()
。让我们看看 X 坐标。它设置为:
(int)(force.X * elapsedTime) + body.X
这意味着什么? force.X
是非零的,elapsedTime
也是非零的,但是它们都小于 1,所以当它们转换为 int
时,结果是零。因此,您的 body
与之前的位置相同,这意味着 Rectangle
没有 重叠。这意味着您的碰撞响应代码被跳过并且您的 force
向量仍然不为零!
然后,再往下,调用播放器的 Update()
方法。我猜你在那个代码中更新了玩家的 Position
和 Body
。但是由于 force
不为零,并且您使用 Vector2
作为 Position
,x 坐标最终会变成 31.841304...
而不是 32
.如果您使用该数字计算新的 Body
并在您的 Draw
方法中使用它,您将最终在 x = 31 处绘制它。下次检查时,它会检测到碰撞, 播放器会再次跳出。
有多种方法可以解决此问题。我在碰撞代码中所做的是首先实际移动对象并记录它们在前一帧中的位置。那样的话,最后一次调整他们的位置就是在他们被移出碰撞区域的时候。