外推打破碰撞检测


10

在对精灵的运动进行推断之前,我的碰撞效果非常好。但是,在对精灵的运动进行推断(以使物体平滑)之后,碰撞不再起作用。

这是外推之前的工作方式:

在此处输入图片说明

但是,在执行推断后,碰撞例程将中断。我假设这是因为它作用于外推例程(位于我的render调用中)产生的新坐标。

应用推断后

在此处输入图片说明

如何纠正这种行为?

我尝试在推断后再进行一次额外的碰撞检查-这似乎确实解决了许多问题,但是我将其排除在外是因为将逻辑放入渲染中是不可能的。

我还尝试制作了spritesX位置的副本,对其进行推断并使用该位置而不是原始位置进行绘制,从而保留了原始位置以供逻辑使用-这似乎是一个更好的选择,但仍然会产生一些奇怪的效果与墙壁碰撞时。我很确定这也不是解决此问题的正确方法。

我在这里找到了几个类似的问题,但是答案并没有帮助我。

这是我的推断代码:

public void onDrawFrame(GL10 gl) {


        //Set/Re-set loop back to 0 to start counting again
        loops=0;

        while(System.currentTimeMillis() > nextGameTick && loops < maxFrameskip){

        SceneManager.getInstance().getCurrentScene().updateLogic();
        nextGameTick+=skipTicks;
        timeCorrection += (1000d/ticksPerSecond) % 1;
        nextGameTick+=timeCorrection;
        timeCorrection %=1;
        loops++;
        tics++;

     }

        extrapolation = (float)(System.currentTimeMillis() + skipTicks - nextGameTick) / (float)skipTicks; 

        render(extrapolation);
}

应用外推

            render(float extrapolation){

            //This example shows extrapolation for X axis only.  Y position (spriteScreenY is assumed to be valid)
            extrapolatedPosX = spriteGridX+(SpriteXVelocity*dt)*extrapolation;
            spriteScreenPosX = extrapolationPosX * screenWidth;

            drawSprite(spriteScreenX, spriteScreenY);           


        }

编辑

正如我上面提到的,我尝试专门复制精灵的坐标以进行绘制。

首先,无论复制如何,当精灵移动时,它都非常平滑,停止时,它会左右左右摆动-因为它仍在根据时间推断位置。这是正常的行为吗?当子画面停止播放时,我们可以“关闭”吗?

我尝试过为左/右设置标志,并且仅在启用这些标志时才进行推断。我也尝试过复制上一个和当前位置,看是否有任何区别。但是,就碰撞而言,这些都无济于事。

如果用户按下发言,则右按钮且精灵在向右移动,当碰到墙时,如果用户继续按住右键,则精灵将继续向右移动,同时被墙停住(因此实际上并没有移动),但是由于仍然设置了正确的标志,并且由于碰撞例程不断将精灵移出墙外,因此代码(而非播放器)仍然看到精灵仍在移动,因此推断仍在继续。所以玩家会看到的是精灵的“静态”(是的,它是动画的,但实际上并没有在屏幕上移动),并且随着推算试图做它的事情,它时不时地剧烈震动……。 ..希望有帮助


1
这需要一些摘要才能完全理解(“插值”似乎有十二种不同的含义,乍一看并不清楚您在这里的意思),但是我的第一个直觉是“您不应做任何事情来影响您的对象在渲染例程中的位置”。渲染器应在对象的给定位置绘制对象,并且操纵该对象存在麻烦的秘诀,因为它本质上将渲染器与游戏的物理耦合在一起。在理想的世界中,渲染器应该能够使用指向游戏对象的const指针。
Steven Stadnicki 2014年

@StevenStadnicki,您好,非常感谢您的评论,有许多示例显示了将插值传递到渲染器中的方法,请参见:mysecretroom.com/www/programming-and-software/…这是我改编我的文章的地方码。我有限的理解是,这会根据自上次更新以来所花费的时间来插值上次更新与下一次更新之间的位置-我同意这是一场噩梦!如果您可以提出建议并选择我可以更轻松地与我合作,我将不胜感激-欢呼
BungleBonce 2014年

6
好吧,我会说“仅仅是因为您可以为某事找到代码并没有使其成为最佳实践”。:-)但是,在这种情况下,我怀疑您链接到的页面使用插值确定了对象的显示位置,但实际上并没有使用它们来更新对象位置。您也可以做到这一点,只需在每帧中都有一个特定绘制的位置即可,但要保持该位置与对象的实际“物理”位置分开。
史蒂文·斯塔德尼基

@StevenStadnicki,您好,如我的问题所述(以“我也曾尝试制作副本”开头的段落),我实际上已经尝试使用“仅绘制”位置:-)您能提出一种插值方法吗?不需要在渲染例程中调整精灵的位置?谢谢!
BungleBonce

查看您的代码,看起来您是在进行外推而不是内插。
Durza007 2014年

Answers:


1

我还不能发表评论,所以我将其发布为答案。

如果我正确地理解了问题,那么它会变成这样:

  1. 首先你有碰撞
  2. 然后校正对象的位置(大概是通过碰撞检测例程)
  3. 对象的更新位置发送到渲染函数
  4. 然后,渲染功能使用外推法更新对象的位置
  5. 现在,推断对象的位置打破了碰撞检测程序

我可以想到3种可能的解决方案。我将按照最不希望恕我直言的顺序列出它们。

  1. 将外推移出渲染功能。外推对象的位置,然后测试碰撞。
  2. 或者,如果要将外推保留在render函数中,请设置一个标志以表示发生了碰撞。让碰撞检测像您已经做的那样纠正对象的位置,但是在计算外推值之前,请先检查碰撞标记。由于对象已经在需要的位置,因此无需移动它。
  3. 对我来说,最后一种可能性是更多的解决方法而不是解决方法,那就是对碰撞检测进行过度补偿。发生碰撞后,将物体移离墙壁,以便外推后物体回到墙壁。

#2的示例代码。

if (collisionHasOccured)
    extrpolation = 0.0f;
else
    extrapolation = (float)(System.currentTimeMillis() + skipTicks - nextGameTick) / (float)skipTicks;

我认为#2可能是最快且最容易启动的方法,尽管#1似乎在逻辑上更准确。取决于您处理增量时间解决方案的方式,#1可能会以大增量的方式破坏#1,在这种情况下,您可能必须同时使用#1和#2。

编辑:我误解了您的代码。循环旨在尽可能快地渲染并按设定的间隔进行更新。这就是为什么要插值精灵位置,以处理绘制多于更新的情况。但是,如果循环落后,那么您将轮询更新,直到陷入困境或跳过最大帧绘制次数。

话虽如此,唯一的问题是物体在碰撞后正在移动。如果发生碰撞,物体应停止朝该方向移动。因此,如果发生碰撞,请将其速度设置为0。这将阻止渲染功能进一步移动对象。


嗨,@ Aholio,我尝试了选项2,它确实可以工作,但会引起一些故障,也许是我做错了,我将重新讨论。尽管我找不到有关如何执行此操作的信息,但是我对选项1非常感兴趣。如何在逻辑例程中外推说?顺便说一句,我的增量是固定的,所以是1/60或0.01667(这是我用来积分的数字,尽管我的游戏每次迭代花费的时间显然会根据发生的情况而有所不同,但绝对不要超过0.01667 ),那么对此的任何帮助将非常感谢。
BungleBonce 2014年

也许我不完全了解您在做什么。对我来说似乎有些奇怪的是,您不仅在绘图功能中进行位置移动,而且还需要进行运动。您也在进行时间计算。是否对每个对象都完成了?正确的顺序应该是:在游戏循环代码中完成时间计算。游戏循环将增量传递给update(dt)函数。Update(dt)将更新所有游戏对象。它应该处理任何移动,外推和碰撞检测。在update()返回游戏循环之后,它会调用render()函数来绘制所有新近更新的游戏对象。
Aholio 2014年

嗯,目前我的更新功能可以完成所有工作。是否运动(通过运动,我的意思是计算精灵的新位置-这是根据我的增量时间计算得出)。我在渲染函数中唯一要做的事情(除了实际绘制之外)是推断“新”位置,但这当然要花费时间,因为它必须考虑从上次逻辑更新到渲染调用的时间。无论如何,这都是我的理解:-)您将如何做?我不明白如何仅在逻辑更新中使用外推法。谢谢!
BungleBonce 2014年

更新了我的答案。希望能
有所

谢谢,请看我最初的问题“停止时,它会稍微向左/向右摆动”-因此,即使我停止了精灵,外推仍然会使精灵“移动”(摆动)-发生这种情况是因为我没有使用sprite的旧位置和当前位置可以算出我的外推,因此,即使sprite是静态的,它仍然具有基于“外推”值的摆动效果,该值在每次帧迭代中都会计算出来。我什至尝试说过“如果旧的和当前的pos是相同的,那就不要推断”,但这似乎还是行不通的,不过我会回头再看一下!
BungleBonce 2014年

0

看来您需要将物理的渲染和更新完全分开。通常,基础仿真将在不连续的时间步长上运行,并且频率永远不会改变。例如,您可以每1/60秒模拟一次球的运动,仅此而已。

为了允许可变的帧速率,渲染代码应以可变的频率运行,但是任何模拟都应保持固定的时间步长。这样,图形就可以从模拟中以只读方式读取内存,并且可以设置插值而不是外推。

由于外推法试图预测未来的价值,移动的突然变化会给您带来巨大的外推误差。最好是在模拟之后的一帧中渲染场景,并在离散的已知位置之间进行插值。

如果您想了解实现的一些细节,我已经在这里的一篇文章中写了一个简短的章节来介绍该主题。请参阅“时间步长”一节。

这是文章中重要的伪代码:

const float fps = 100
const float dt = 1 / fps
float accumulator = 0

// In units seconds
float frameStart = GetCurrentTime( )

// main loop
while(true)
  const float currentTime = GetCurrentTime( )

  // Store the time elapsed since the last frame began
  accumulator += currentTime - frameStart( )

  // Record the starting of this frame
  frameStart = currentTime

  // Avoid spiral of death and clamp dt, thus clamping
  // how many times the UpdatePhysics can be called in
  // a single game loop.
  if(accumulator > 0.2f)
    accumulator = 0.2f

  while(accumulator > dt)
    UpdatePhysics( dt )
    accumulator -= dt

  const float alpha = accumulator / dt;

  RenderGame( alpha )

void RenderGame( float alpha )
  for shape in game do
    // calculate an interpolated transform for rendering
    Transform i = shape.previous * alpha + shape.current * (1.0f - alpha)
    shape.previous = shape.current
    shape.Render( i )

RenderGame功能最受关注。这个想法是在离散的仿真位置之间使用插值。渲染代码可以将其复制为模拟只读数据,并使用临时的内插值进行渲染。这将使您的动作非常顺畅,而不会出现任何看起来似乎很愚蠢的问题!

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.