20

这个问题专门针对具有物理和视觉组件(例如游戏)的反应性香蕉和实时模拟。

根据修复您的时间步长!设置游戏循环的理想方式(假设物理需要可重现),您需要帧之间的固定时间步长。在考虑了一些真正的复杂性之后,作者得出了这个游戏循环:

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. reactive-banana是否允许用户编写这种样式循环?如果有怎么办?也许一个做实时物理模拟的例子是有序的(或已经存在的)?

4

1 回答 1

19

对于本次讨论,我最感兴趣的部分是安排对物理引擎的调用(对集成的调用)始终由 dt 执行。reactive-banana 是否允许用户编写这种样式循环?

是的,reactive-banana 可以做到这一点。

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

这里有一个大致的概述:

-- 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
}

事实上,我已经为旧版本的响应式香蕉编写了这种风格的游戏循环示例,但还没有开始完善并在 hackage 上发布它。我希望看到完成的重要事情是:

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

Andreas Bernstein 的使用响应式香蕉的停电克隆可能是实现这种风格的一个很好的例子。

于 2012-10-02T17:14:22.863 回答