如何在反应香蕉中实现游戏循环?

时间:2012-10-02 06:24:59

标签: haskell reactive-banana

此问题特定于反应性香蕉和实时模拟物理和视觉组件(例如,游戏)。

根据Fix Your Timestep!设置游戏循环的理想方法(假设物理需要可重复),您需要在帧之间固定时间步长。在考虑了许多真正的并发症之后,作者到达了这个游戏循环:

double t = 0.0;
const double dt = 0.01;

double currentTime = hires_time_in_seconds();
double accumulator = 0.0;

State previous;
State current;

while ( !quit )
{
     double newTime = time();
     double frameTime = newTime - currentTime;
     if ( frameTime > 0.25 )
          frameTime = 0.25;   // note: max frame time to avoid spiral of death
     currentTime = newTime;

     accumulator += frameTime;

     while ( accumulator >= dt )
     {
          previousState = currentState;
          integrate( currentState, t, dt );
          t += dt;
          accumulator -= dt;
     }

     const double alpha = accumulator / dt;

     State state = currentState*alpha + previousState * ( 1.0 - alpha );

     render( state );
}

概要是物理模拟总是以相同的时间增量(dt)为数值稳定性提供。安排这一点必须考虑到物理和视觉可能会以不同的频率更新,你不想太过落后。

例如,您可能希望以20hz的频率进行更新,但视频更新的帧速率为60hz。这个循环对物理进行线性插值,以弥补物理更新和图形更新之间的差异。

此外,当帧之间的时间差异远大于dt时,有一个循环来处理以dt的块为单位的更新步骤。关于死亡螺旋的说明只是指你的物理计算根本无法跟上所需的更新频率的情况,所以你允许它跳过一些更新。

对于这个讨论,我最感兴趣的部分是安排,以便对物理引擎的调用(对integrate的调用)始终由dt步进。 被动香蕉是否允许用户编写此样式循环?如果是这样的话?也许一个做实时物理模拟的例子是按顺序(或已经存在)?

1 个答案:

答案 0 :(得分:19)

  

对于这个讨论,我最感兴趣的部分是安排,以便对物理引擎的调用(对集成的调用)总是由dt步进。 reactive-banana是否允许用户编写此样式循环?

是的,反应性香蕉可以做到这一点。

这个想法是你编写一个自定义事件循环并将反应性香蕉钩入其中。图书馆不会对您从何处获取事件做出任何假设,它“仅”解决了根据现有事件整齐地描述新事件的问题。特别是,您可以使用newAddHandler函数创建两个在事件循环中的适当位置调用的回调函数。从本质上讲,反应性香蕉只是一种令人难以置信的方法来编写维持状态的普通回调函数。您何时以及如何调用这些功能取决于您。

这里有一个大致的概述:

-- set up callback functions
(renderEvent, render) <- newAddHandler
(stateUpdateEvent, stateUpdate) <- newAddHandler

-- make the callback functions do something interesting
let networkDescription = do
    eRender      <- fromAddHandler renderEvent
    eStateUpdate <- fromAddHandler stateUpdateEvent
    ...
    -- functionality here

actuate =<< compile networkDescription

-- event loop
while (! quit)
{
    ...
    while (accumulator >= dt)
    {
        stateUpdate (t,dt)      -- call one callback
        t += dt
        accumulator -= dt
    }
    ...
    render ()                   -- call another callback
}

事实上,我已经用这种风格为旧版本的反应香蕉写了一个game loop example,但还没有完成抛光并将其发布到hackage上。我希望看到的重要事项是:

  • 选择易于安装且可在GHCi中运行的图形引擎。这个概念使用SDL,但由于无法从GHCi中使用,因此非常尴尬。像OpenGL + GLFW这样的东西会很好。
  • 提供一个小抽象,以便更容易编写插值阶段。可能只有两件事:表示常规物理更新的事件eTimer :: Event t ()和测量自上次物理更新以来的时间的行为bSinceLastTimer :: Behavior t TimeDiff,可用于进行插值。 (这是一种行为而不是一种事件,所以内部“画这个!”更新是透明的。)

Andreas Bernstein的blackout clone using reactive-banana可能是以这种方式实施的一个很好的例子。