2012-10-02 39 views
20

这个问题是特定于反应 - 香蕉和具有物理和视觉组件(例如游戏)的实时模拟。如何在反应香蕉中实现游戏循环?

根据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)始终步进dtreactive-banana是否允许用户编写此样式循环?如果是这样如何?或许一个做实时物理模拟的例子是按顺序(或者已经存在)?

回答

19

对于本次讨论,我最感兴趣的部分是安排,以便物理引擎(调用集成)的调用总是由dt来完成。反应式香蕉是否允许用户编写此样式循环?

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

这个想法是,你写一个自定义事件循环,并钩入反应香蕉。图书馆没有对你从哪里获得你的活动做出任何假设,它只“解决了以现有的方式整齐地描述新事件的问题。特别是,您可以使用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。 (这是一种行为,而不是一个事件,因此内部“得出这样的!”更新是透明的。)

安德烈亚斯·伯恩斯坦的blackout clone using reactive-banana可能是这种风格来实现一个很好的例子。

+0

我不确定SDL,但是使用OpenGL和GLFW,它们都在进程的原始线程上使用线程本地存储(它必须是原始线程,供应商限制)。 GHCi默认在不同的线程中运行每个命令。这意味着像OpenGL/GLFW(以及其他几个gui库)的库无法正确访问其线程本地存储并从GHCi进入垃圾箱。解决方法是在启动GHCi时添加-fno-ghci-sandbox。您可以尝试一下,看看它是否可以修复您的SDL + GHCi问题:http://www.haskell.org/ghc/docs/7.0.1/html/users_guide/release-7-0-1.html –

+0

这是一个小问题恐怕比'-fno-ghci-sandbox'更困难。在Mac上,SDL需要进行编译,因为它将'main'重新定义为宏。由于我不了解其他不兼容性,GLFW版本往往会在GHCi中崩溃。 –

+1

如果使用'-fno-ghci-sandbox',GLFW-b不应该从GHCi崩溃。如果确实如此,那么您正在创建一个新的bug,因此请做出报告! :) –