【问题标题】:How to implement a fixed-step loop?如何实现固定步长循环?
【发布时间】:2011-05-24 22:45:39
【问题描述】:

我试图在我的程序中创建一个固定的步进循环,但由于某种原因,我似乎无法让它正常工作。基本上我需要的是一个循环:

while(!over) {
    Update(elapsedtime);
    Draw(elapsedtime);
}

或类似的东西,带有 .我试过使用 Thread.Sleep 但我不太确定它给了我一个真正的固定步骤循环,当我尝试在this 网站上实施解决方案时,我遇到了问题,因为我看不到一种保存受循环影响的对象状态的方法。这迫使我排除了那部分,当循环中有很多对象时,这会导致速度变慢。

我怎样才能得到一个固定步长循环,而不必不断地保存循环中每个对象的状态?

【问题讨论】:

  • 你试图达到什么帧率?这会极大地影响最佳方法。

标签: c# loops fixed


【解决方案1】:

如果您正在开发游戏引擎,Timer 可能无法正常工作。计时器没有足够的精度来处理固定步长循环并保持其规律性。

您需要使用高精度计时器来实现这一点。在这种情况下,您需要使用 the Stopwatch class. 这将使您有足够的精度来准确地跟踪您的 elapsedTime。

您只需要跟踪您想要更新的频率(以 Ticks 为单位),然后使用秒表来测量每次更新所需的时间。之后,您可能会使用 Sleep() 调用来阻止,但要意识到在大多数系统上 sleep 的精度只有 14-15 毫秒,即使您只休眠 0 或 1 毫秒。这可能会使您的循环减慢太多,因此您可能需要实现自旋锁或类似的东西(while (waiting) { do something })。 Sleep() 很好,因为如果可以的话,它会节省 CPU,自旋锁会在您等待时占用 CPU 周期,但会提供更高的精度。

【讨论】:

  • 是的,我发现一个网站上描述的解决方案使用了高分辨率计时器,所以我尝试了 Stopwatch 类和他的解决方案。问题是当我的粒子系统添加了许多粒子时,即使他的代码应该进行补偿,它也会导致循环运行速度变慢。我还是一头雾水。
  • 如果粒子系统花费的时间超过一个“tick”的时间,则无法补偿。你有两个选择——优化你的粒子更快,或者降低你的整体帧率。否则,长时间运行的帧会导致延迟。如果它比单个滴答声快,它应该处理它。
  • 你如何处理帧之间的停顿?你是调用 Sleep() 还是写自旋锁?
  • 在我最初的实现中,我调用了 Sleep。
  • 你的目标帧率是多少?睡眠永远不会少于 14-15 毫秒,而且它不是那么准确。这可能是你的问题。
【解决方案2】:

您是否要求一个基本上每 n 刻运行一次的循环?这听起来像Timer。 BCL 中有几个计时器。一个timer for servers 或一个for Window Forms

您可以按照这些思路使用它们。以下是伪代码,旨在大致说明这是如何完成的。换句话说,如果你只是复制和粘贴,它可能不会编译。

public class RepeatingTask
{
    public MyObjectState _objectState;

    public RepeatingTask(Timespan interval)
    {
       Timer timer=new Timer(Timer_Tick); //This assumes we pass a delegate. Each timer is different. Refer to MSDN for proper usage
       timer.Interval=interval;
       timer.Start();

    }
    private DateTime _lastFire;
    private void Timer_Tick()
    {
       DateTime curTime=DateTime.Now();
       DateTime timeSinceLastFire = curTime-lastFireTime;
       _lastFire=DateTime.Now(); //Store when we last fired...

       accumulatedtime+=timeSinceLastFire
       while(accumulatedtime>=physicsInterval)
       {
          Update(physicsInterval);
          accumulated-=physicInterval;
       }

               }

}

您还可以使用闭包来包装定义计时器的方法的状态。

编辑

我阅读了这篇文章并且理解了这个问题;但是,您仍然可以使用计时器,但您需要按照他的指示设置您的函数,以便为您设置物理引擎的每个间隔调用引擎。

您是否在使用 WPF,他们有一些我相信会以稳定的速率触发 doign 动画的事件。

编辑

我更新了我的代码示例以向您展示我的解释,但基本上您所做的是为您的物理引擎定义一个间隔,然后您在每次通过“循环/计时器”时需要做的事情是确定多少真实自上次迭代以来经过的时间。然后,您将该增量存储在一个累加器中,您将使用该累加器进行倒计时,直到您为上次调用以来错过的所有间隔调用物理引擎。

我遇到的问题是,如果在这里使用计时器更好,那么让专用线程休眠或其他一些实现。

【讨论】:

  • 只使用定时器会导致问题。如果 Update 方法花费的时间比循环预期的稍长怎么办?由于计时器总是以一定的间隔计时,这会增加间隔并导致减速。看看我链接到的文章。
  • 等等,你到底是什么意思?我不明白您所说的“为您设置物理引擎的每个间隔调用引擎”是什么意思。
  • 是的,我明白了。我尝试将这个系统与累加器一起使用,但它导致帧率下降到极低;发生的情况是,draw 函数位于累加器之后,并且它尽可能频繁地绘制,但 update 函数没有被频繁调用并导致延迟。
  • 你能把你对物理函数的调用移动到一个单独的线程吗?然后,您将根据一致的间隔进行绘制,并在一致的间隔上进行物理,但它们是单独的间隔......
  • 好吧,我试图避免过多的线程...我认为它可能会弄乱更新和绘制逻辑的顺序。
【解决方案3】:

Thread.Sleep 不会给你一个固定的步骤循环,它只会在让你的程序继续之前等待给定的时间。实际频率将取决于睡眠的准确程度以及程序在每一步运行相同数量的挂钟时间的一致性。但是,如果您可以适应一些漂移,这可能就足够了,而且肯定是最简单的。

要获得真正的固定步长循环,您需要一个interval timer,它会在计时器到期时通过回调执行您的代码。请注意,这不再是真正的循环,但是您的代码会定期执行,我认为这就是您想要的。同样,可能会有一些漂移,因此您可能需要调整每一步的间隔,以便如果您需要更高的准确性,下一个事件会在正确的时间触发。让它完全准确是不可能的——毕竟即使是基于硬件的间隔计时器也有一个精度——但希望你可以让它足够准确以达到你的目的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-03-14
    • 1970-01-01
    • 2016-09-17
    • 1970-01-01
    • 1970-01-01
    • 2012-10-03
    • 2019-02-09
    • 1970-01-01
    相关资源
    最近更新 更多