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

Jas*_*git 20 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 );
}
Run Code Online (Sandbox Code Playgroud)

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

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

另外,当帧之间的时间差大得多时,dt有一个循环来处理以块的形式更新更新dt.关于死亡螺旋的说明仅指您的物理计算无法跟上所需的更新频率的情况,因此您可以跳过某些更新.

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

Hei*_*mus 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
}
Run Code Online (Sandbox Code Playgroud)

事实上,我已经用这种风格为旧版本的反应香蕉编写了一个游戏循环示例,但还没有进行抛光并将其发布到hackage上.我希望看到的重要事项是:

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

使用反应性香蕉的安德烈亚斯伯恩斯坦的停电克隆可能是这种风格的一个很好的例子.