2012-10-02 16 views
20

Esta pregunta es específica de las simulaciones de plátano reactivo y en tiempo real con un componente físico y visual (por ejemplo, juegos).¿Cómo implementar un bucle de juego en plátano reactivo?

De acuerdo con Fix Your Timestep!, la forma ideal de configurar un bucle de juego (asumiendo que la física necesita ser reproducible), necesita un intervalo de tiempo fijo entre fotogramas. Después de considerar una serie de complicaciones reales, el autor llega a este bucle de juego:

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

La sinopsis es que la simulación de la física siempre se alimenta el mismo incremento de tiempo (dt) para la estabilidad numérica. Organizar para eso debe tener en cuenta que la física y las imágenes se pueden actualizar a diferentes frecuencias y no desea ir demasiado atrás.

Por ejemplo, es posible que desee actualizaciones a una frecuencia de 20 hz, pero una actualización visual con una tasa de fotogramas de 60 hz. Este ciclo realiza la interpolación lineal de la física para compensar la diferencia entre las actualizaciones físicas y las actualizaciones gráficas.

Además, cuando la diferencia en el tiempo entre fotogramas es mucho mayor que dt, hay un bucle para manejar el paso de las actualizaciones en fragmentos de dt. La nota sobre la espiral de la muerte solo se refiere a un caso en el que el cálculo de su física simplemente no puede mantenerse al día con la frecuencia deseada de actualizaciones, por lo que le permite omitir algunas actualizaciones.

Para esta discusión, la parte que más me interesa es organizar de modo que la llamada al motor de física (la llamada al integrate) siempre sea reemplazada por dt. ¿reactive-banana permite al usuario escribir este ciclo de estilo? ¿Si es así, cómo? Quizás un ejemplo de simulación de física en tiempo real esté en orden (o ya exista)?

Respuesta

19

Para esta discusión, la parte que más me interesa es organizar de modo que la llamada al motor de física (la llamada a integrar) sea siempre medida por dt. ¿Reactive-banana le permite al usuario escribir este ciclo de estilo?

Sí, reactive-banana puede hacer eso.

La idea es que usted escriba un bucle de evento personalizado y enganche reactivo-banana en eso. La biblioteca no hace suposiciones sobre dónde obtienes tus eventos, "solo" resuelve el problema de describir prolijamente nuevos eventos en términos de los existentes. En particular, puede usar la función newAddHandler para crear dos funciones de devolución de llamada que se invocan en los lugares apropiados en el bucle de evento. Esencialmente, el plátano reactivo es simplemente un método alucinante para escribir funciones de devolución de llamada ordinarias que mantienen el estado. Cuándo y cómo llama a estas funciones depende de usted.

Aquí un esquema general:

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

De hecho, he escrito un game loop example en este estilo para una versión anterior de reactivos con el plátano, pero no he tenido tiempo de pulido y su publicación en hackage Las cosas importantes que me gustaría ver completadas son:

  • Elija un motor de gráficos que sea fácil de instalar y funcione en GHCi. El concepto usa SDL, pero esto es bastante incómodo ya que no se puede usar desde GHCi. Algo como OpenGL + GLFW estaría bien.
  • Ofrezca una pequeña abstracción para facilitar la escritura de la fase de interpolación. Probablemente solo dos cosas: un evento eTimer :: Event t() que representa las actualizaciones físicas habituales y un comportamiento bSinceLastTimer :: Behavior t TimeDiff que mide el tiempo transcurrido desde las últimas actualizaciones físicas, que se pueden usar para realizar la interpolación. (Se trata de un comportamiento en lugar de un evento, por lo que el interno "dibujar esto!" Actualizaciones son transparentes.)

Andreas Bernstein blackout clone using reactive-banana puede ser un excelente ejemplo de implementar en este estilo.

+0

No estoy seguro acerca de SDL, pero con OpenGL y GLFW ambos usan el almacenamiento local de subprocesos en el subproceso original del proceso (tiene que ser el subproceso original, la limitación del proveedor). GHCi ejecuta cada comando en un hilo diferente por defecto. Esto significa que las bibliotecas como OpenGL/GLFW (y varias otras bibliotecas gui) no pueden acceder correctamente a su almacenamiento local de subprocesos y se vuelven locas desde GHCi. La solución es agregar -fno-ghci-sandbox al iniciar GHCi. Puede probar esto y ver si soluciona sus problemas con SDL + GHCi: http://www.haskell.org/ghc/docs/7.0.1/html/users_guide/release-7-0-1.html –

+0

Es un poco más difícil que '-fno-ghci-sandbox', me temo. En Mac, SDL necesita compilarse porque redefine 'main' para que sea una macro. Las versiones GLFW tienden a bloquearse en GHCi debido a otras incompatibilidades que no entiendo. –

+1

GLFW-b no debe bloquearse desde GHCi si usa '-fno-ghci-sandbox'. Si lo hace, está pegando un nuevo error así que ¡por favor haga un informe! :) –

Cuestiones relacionadas