¿Debe un bucle de juego basarse en pasos de tiempo fijos o variables? ¿Uno siempre es superior o la elección correcta varía según el juego?
Paso de tiempo variable
Las actualizaciones de física pasan un argumento de "tiempo transcurrido desde la última actualización" y, por lo tanto, dependen de la velocidad de fotogramas. Esto puede significar hacer cálculos como position += distancePerSecond * timeElapsed
.
Pros : suave, más fácil de codificar
Contras : no determinista, impredecible en pasos muy pequeños o grandes
deWiTTERS ejemplo:
while( game_is_running ) {
prev_frame_tick = curr_frame_tick;
curr_frame_tick = GetTickCount();
update( curr_frame_tick - prev_frame_tick );
render();
}
Paso de tiempo fijo
Es posible que las actualizaciones ni siquiera acepten un "tiempo transcurrido", ya que suponen que cada actualización es por un período de tiempo fijo. Los cálculos pueden hacerse como position += distancePerUpdate
. El ejemplo incluye una interpolación durante el render.
Pros : predecible, determinista (¿más fácil de sincronizar en red?), Código de cálculo más claro
Contras : no sincronizado para monitorear la sincronización en v (causa gráficos nerviosos a menos que interpoles), velocidad de fotogramas máxima limitada (a menos que interpoles), difícil de trabajar dentro de marcos que asumir pasos de tiempo variable (como Pyglet o Flixel )
deWiTTERS ejemplo:
while( game_is_running ) {
while( GetTickCount() > next_game_tick ) {
update();
next_game_tick += SKIP_TICKS;
}
interpolation = float( GetTickCount() + SKIP_TICKS - next_game_tick )
/ float( SKIP_TICKS );
render( interpolation );
}
Algunos recursos
- Gaffer en juegos: ¡arregla tu paso de tiempo!
- Artículo del bucle del juego de deWitter
- El FPS de Quake 3 afecta la física del salto, ¿sin duda una razón por la cual Doom 3 está bloqueado a 60 fps?
- Flixel requiere un paso de tiempo variable (creo que esto lo determina Flash), mientras que Flashpunk permite ambos tipos.
- Manual de Box2D § Simulando el mundo de Box2D sugiere que use pasos de tiempo constante.
fuente
Respuestas:
Hay dos cuestiones relacionadas con la pregunta.
En Glen fielder's Fix your time step, dice "Libera la física". Eso significa que su velocidad de actualización física no debe estar vinculada a su velocidad de fotogramas.
En las recomendaciones de Erin Catto para Box2D también defiende esto.
¿Debería la velocidad de paso de Física estar vinculada a su velocidad de cuadros? No.
Los pensamientos de Erin sobre el paso fijo versus el paso variable:
Pensamientos de Glen sobre los pasos fijos frente a los variables:
¿Debería la física ir con deltas constantes? Si.
La forma de aumentar la física con deltas constantes y no vincular la velocidad de actualización de la física a la velocidad de fotogramas es utilizar un acumulador de tiempo. En mi juego lo llevo un paso más allá. Aplico una función de suavizado al tiempo entrante. De esa manera, los picos FPS grandes no hacen que la física salte demasiado lejos, sino que se simulan más rápidamente para un cuadro o dos.
Usted menciona que con una velocidad fija, la física no se sincronizaría con la pantalla. Esto es cierto si la velocidad física objetivo está cerca de la velocidad de fotogramas objetivo. Es peor que la velocidad de fotogramas sea mayor que la velocidad física. En general, es mejor alcanzar una tasa de actualización física del doble de su FPS objetivo, si puede permitírselo.
Si no puede permitirse una gran tasa de actualización de física, considere la posibilidad de interpolar las posiciones de los gráficos entre cuadros para que los gráficos dibujados parezcan moverse más suavemente de lo que realmente se mueve la física.
fuente
Creo que realmente hay 3 opciones, pero las estás enumerando como solo 2:
Opción 1
Hacer nada. Intente actualizar y renderizar a cierto intervalo, por ejemplo, 60 veces por segundo. Si se queda atrás, déjalo y no te preocupes. Los juegos se ralentizarán en cámara lenta y desigual si la CPU no puede seguir el ritmo de tu juego. Esta opción no funcionará en absoluto para juegos multiusuario en tiempo real, pero está bien para juegos de un solo jugador y se ha utilizado con éxito en muchos juegos.
opcion 2
Use el tiempo delta entre cada actualización para variar el movimiento de los objetos. Genial en teoría, especialmente si nada en tu juego se acelera o desacelera, pero solo se mueve a una velocidad constante. En la práctica, muchos desarrolladores implementan esto mal, y puede conducir a una detección inconsistente de colisión y física. Parece que algunos desarrolladores piensan que este método es más fácil de lo que es. Si desea usar esta opción, necesita mejorar considerablemente su juego y sacar algunas matemáticas y algoritmos de armas grandes, por ejemplo, usando un integrador de física Verlet (en lugar del Euler estándar que usa la mayoría de las personas) y usando rayos para la detección de colisiones en lugar de simples controles de distancia de Pitágoras. Hice una pregunta sobre esto en Stack Overflow hace un tiempo y obtuve algunas respuestas excelentes:
https://stackoverflow.com/questions/153507/calculate-the-position-of-an-accelerating-body-after-a-certain-time
Opción 3
Utilice el enfoque de Gaffer "arregle su paso de tiempo". Actualice el juego en pasos fijos como en la opción 1, pero hágalo varias veces por cuadro procesado, en función de cuánto tiempo haya transcurrido, de modo que la lógica del juego se mantenga al día en tiempo real, mientras se mantiene en pasos discretos. De esta manera, la lógica de juego fácil de implementar, como los integradores de Euler, y la detección de colisión simple aún funcionan. También tiene la opción de interpolar animaciones gráficas basadas en el tiempo delta, pero esto es solo para efectos visuales, y nada que afecte la lógica de su juego principal. Potencialmente, puede meterse en problemas si sus actualizaciones son muy intensas: si las actualizaciones se retrasan, necesitará más y más para mantenerse al día, lo que puede hacer que su juego sea aún menos receptivo.
Personalmente, me gusta la Opción 1 cuando me salgo con la suya y la Opción 3 cuando necesito sincronizar en tiempo real. Respeto que la Opción 2 puede ser una buena opción cuando sabes lo que estás haciendo, pero conozco mis limitaciones lo suficientemente bien como para alejarme de ella.
fuente
Me gusta mucho la forma en que XNA Framework implementa un paso de tiempo fijo. Si una llamada de extracción determinada demora un poco, llamará a la actualización repetidamente hasta que "se ponga al día". Shawn Hargreaves lo describe aquí:
http://blogs.msdn.com/b/shawnhar/archive/2007/11/23/game-timing-in-xna-game-studio-2-0.aspx
El mayor profesional en mi opinión es el que mencionaste, que hace que todos tus cálculos de código de juego sean mucho más simples porque no tienes que incluir esa variable de tiempo por todas partes.
nota: xna también admite tiempos variables, es solo una configuración.
fuente
Hay otra opción: desacoplar la actualización del juego y la actualización de la física. Intentar adaptar el motor de física al paso de tiempo del juego conduce a un problema si arreglas tu paso de tiempo (el problema de girar fuera de control porque la integración necesita más pasos de tiempo que requieren más tiempo que necesita más pasos de tiempo), o hacer que sea variable y obtener física torpe.
La solución que veo mucho es hacer que la física se ejecute en un paso de tiempo fijo, en un hilo diferente (en un núcleo diferente). El juego se interpola o extrapola dados los dos marcos válidos más recientes que puede obtener. La interpolación agrega algo de retraso, la extrapolación agrega algo de incertidumbre, pero su física será estable y no perderá el control de su tiempo.
No es trivial de implementar, pero podría probarse a sí mismo como una prueba futura.
fuente
Personalmente, uso una variación de tiempo variable (que es una especie de híbrido de fijo y variable, creo). Hice hincapié en este sistema de temporización de varias maneras, y me encuentro usándolo para muchos proyectos. ¿Lo recomiendo para todo? Probablemente no.
Mis bucles de juego calculan la cantidad de fotogramas para actualizar (llamemos a esto F) y luego realizan actualizaciones lógicas discretas F. Cada actualización lógica supone una unidad de tiempo constante (que a menudo es 1/100 de segundo en mis juegos). Cada actualización se realiza en secuencia hasta que se realicen todas las actualizaciones lógicas discretas F.
¿Por qué actualizaciones discretas en pasos lógicos? Bueno, si intentas usar pasos continuos, y de repente tienes fallas físicas porque las velocidades y distancias calculadas para viajar se multiplican por un valor enorme de F.
Una implementación deficiente de esto solo haría F = hora actual - últimas actualizaciones de tiempo de cuadro. Pero si los cálculos se retrasan demasiado (a veces debido a circunstancias más allá de su control, como otro proceso que roba el tiempo de la CPU), verá rápidamente un salto horrible. Rápidamente, ese FPS estable que intentó mantener se convierte en SPF.
En mi juego, permito una desaceleración "suave" (más o menos) para restringir la cantidad de recuperación lógica que debería ser posible entre dos sorteos. Hago esto sujetando: F = min (F, MAX_FRAME_DELTA) que generalmente tiene MAX_FRAME_DELTA = 2/100 * so 3/100 * s. Entonces, en lugar de omitir fotogramas cuando está demasiado lejos de la lógica del juego, descarte cualquier pérdida de fotogramas masiva (lo que ralentiza las cosas), recupere algunos fotogramas, dibuje e intente nuevamente.
Al hacer esto, también me aseguro de que los controles del reproductor se mantengan sincronizados con lo que realmente se muestra en la pantalla.
El pseudocódigo del producto final es algo como esto (delta es F mencionado anteriormente):
Este tipo de actualización no es adecuada para todo, pero para los juegos de estilo arcade, preferiría ver que el juego se ralentiza porque hay muchas cosas en lugar de fallar cuadros y perder el control del jugador. También prefiero esto a otros enfoques de pasos de tiempo variable que terminan teniendo fallas irreproducibles causadas por la pérdida de cuadros.
fuente
Esta solución no se aplica a todo, pero hay otro nivel de paso de tiempo variable: paso de tiempo variable para cada objeto en el mundo.
Esto parece complicado, y puede serlo, pero piense en ello como modelar su juego como una simulación de evento discreto. Cada movimiento del jugador se puede representar como un evento que comienza cuando comienza el movimiento y termina cuando termina el movimiento. Si hay alguna interacción que requiera que el evento se divida (una colisión, por ejemplo), el evento se cancela y otro evento se inserta en la cola del evento (que probablemente sea una cola prioritaria ordenada por hora de finalización del evento).
La representación está totalmente separada de la cola de eventos. El motor de visualización interpola puntos entre los tiempos de inicio / finalización del evento según sea necesario, y puede ser tan preciso o descuidado en esta estimación como sea necesario.
Para ver una implementación compleja de este modelo, vea el simulador espacial EXOFLIGHT . Utiliza un modelo de ejecución diferente de la mayoría de los simuladores de vuelo: un modelo basado en eventos, en lugar del modelo tradicional de intervalo de tiempo fijo. El bucle principal básico de este tipo de simulación se ve así, en pseudocódigo:
La razón principal para usar uno en un simulador espacial es la necesidad de proporcionar una aceleración de tiempo arbitraria sin pérdida de precisión. Algunas misiones en EXOFLIGHT pueden tardar años de juego en completarse, e incluso una opción de aceleración 32x sería insuficiente. Necesitarías más de 1,000,000x de aceleración para un sim utilizable, lo cual es difícil de hacer en un modelo de segmento de tiempo. Con el modelo basado en eventos, obtenemos tasas de tiempo arbitrarias, de 1 s = 7 ms a 1 s = 1 año.
Cambiar la tasa de tiempo no cambia el comportamiento del sim, que es una característica crítica. Si no hay suficiente potencia de CPU disponible para ejecutar el simulador a la velocidad deseada, los eventos se acumularán y podríamos limitar la actualización de la interfaz de usuario hasta que se borre la cola de eventos. Del mismo modo, podemos avanzar rápidamente el sim tanto como queramos y asegurarnos de que no desperdiciamos CPU ni sacrificamos la precisión.
En resumen: podemos modelar un vehículo en una órbita larga y pausada (utilizando la integración Runge-Kutta) y otro vehículo que rebota simultáneamente en el suelo; ambos vehículos se simularán con la precisión adecuada, ya que no tenemos un paso de tiempo global.
Contras: Complejidad y falta de motores físicos disponibles que admitan este modelo :)
fuente
El paso de tiempo fijo es útil cuando se tiene en cuenta la precisión del punto flotante y para que las actualizaciones sean consistentes.
Es un código simple, por lo que sería útil probarlo y ver si funciona para tu juego.
El problema principal con el uso de un paso de tiempo fijo es que los jugadores con una computadora rápida no podrán usar la velocidad. Renderizar a 100 fps cuando el juego se actualiza solo a 30 fps es lo mismo que solo renderizar a 30 fps.
Dicho esto, puede ser posible usar más de un paso de tiempo fijo. Se pueden usar 60 fps para actualizar objetos triviales (como UI o sprites animados) y 30 fps para actualizar sistemas no triviales (como física y) e incluso temporizadores más lentos para administrar detrás de escena, como eliminar objetos no utilizados, recursos, etc.
fuente
Además de lo que ya has dicho, puede deberse a la sensación que deseas que tenga tu juego. A menos que pueda garantizar que siempre tendrá una velocidad de fotogramas constante, es probable que tenga una desaceleración en algún lugar y los pasos de tiempo fijos y variables se verán muy diferentes. Reparado tendrá el efecto de que tu juego vaya en cámara lenta por un tiempo, que a veces puede ser el efecto deseado (mira un juego de disparos de la vieja escuela como Ikaruga donde las explosiones masivas causan desaceleración después de vencer a un jefe). Los pasos de tiempo variables mantendrán las cosas en movimiento a la velocidad correcta en términos de tiempo, pero puede ver cambios repentinos en la posición, etc., lo que puede dificultar que el jugador realice acciones con precisión.
Realmente no puedo ver que un paso de tiempo fijo facilitará las cosas en una red, todos estarían un poco fuera de sincronización para empezar y se ralentizarían en una máquina, pero no otra haría que las cosas se desincronizaran más.
Siempre me he inclinado personalmente por el enfoque variable, pero esos artículos tienen algunas cosas interesantes en las que pensar. Sin embargo, todavía he encontrado pasos fijos bastante comunes, especialmente en consolas donde la gente piensa que la velocidad de fotogramas es de 60 fps constantes en comparación con las tasas muy altas que se pueden lograr en la PC.
fuente
Utilice el enfoque de Gaffer "arregle su paso de tiempo". Actualice el juego en pasos fijos como en la opción 1, pero hágalo varias veces por cuadro procesado, en función de cuánto tiempo haya transcurrido, de modo que la lógica del juego se mantenga al día en tiempo real, mientras se mantiene en pasos discretos. De esta manera, la lógica de juego fácil de implementar, como los integradores de Euler, y la detección de colisión simple aún funcionan. También tiene la opción de interpolar animaciones gráficas basadas en el tiempo delta, pero esto es solo para efectos visuales, y nada que afecte la lógica de su juego principal. Potencialmente, puede meterse en problemas si sus actualizaciones son muy intensas: si las actualizaciones se retrasan, necesitará más y más para mantenerse al día, lo que puede hacer que su juego sea aún menos receptivo.
Personalmente, me gusta la Opción 1 cuando me salgo con la suya y la Opción 3 cuando necesito sincronizar en tiempo real. Respeto que la opción 2 puede ser una buena opción cuando sabes lo que estás haciendo, pero conozco mis limitaciones lo suficientemente bien como para mantenerme alejado de ella.
fuente
Descubrí que los pasos de tiempo fijos sincronizados a 60 fps proporcionan una animación de espejo suave. Esto es especialmente importante para las aplicaciones de realidad virtual. Cualquier otra cosa es físicamente nauseabunda.
Los pasos de tiempo variables no son adecuados para la realidad virtual. Eche un vistazo a algunos ejemplos de Unity VR que utilizan pasos de tiempo variables. Es desagradable.
La regla es que si tu juego 3D es fluido en modo VR, será excelente en modo no VR.
Compare estos dos (aplicaciones Cardboard VR)
(Pasos de tiempo variables)
(Pasos de tiempo fijos)
Tu juego debe ser multiproceso para lograr un paso de tiempo / velocidad de fotogramas constante. La física, la interfaz de usuario y el renderizado se deben separar en hilos dedicados. Es horrible que PITA los sincronice, pero los resultados son ese reflejo suave que desea (especialmente para VR).
Los juegos móviles son especialmente desafiante porque las CPU y GPU integradas tienen un rendimiento limitado. Use GLSL (argot) con moderación para descargar la mayor cantidad de trabajo posible de la CPU. Tenga en cuenta que pasar parámetros a la GPU consume recursos del bus.
Mantenga siempre la velocidad de fotogramas mostrada durante el desarrollo. El verdadero juego es mantenerlo fijo a 60 fps. Esta es la tasa de sincronización nativa para la mayoría de las pantallas y también para la mayoría de los globos oculares.
El marco que está utilizando debería poder notificarle sobre una solicitud de sincronización o utilizar un temporizador. No inserte un retraso de sueño / espera para lograr esto, incluso se notan ligeras variaciones.
fuente
Los pasos de tiempo variable son para procedimientos que deben ejecutarse con la mayor frecuencia posible: ciclos de procesamiento, manejo de eventos, material de red, etc.
Los pasos de tiempo fijos son para cuando necesita que algo sea predecible y estable. Esto incluye, entre otros, la física y la detección de colisiones.
En términos prácticos, la detección física y de colisión debe estar desconectada de todo lo demás, en su propio paso de tiempo. La razón para realizar procedimientos como estos en un pequeño intervalo de tiempo fijo es mantenerlos precisos. Las magnitudes de impulso dependen en gran medida del tiempo, y si el intervalo se vuelve demasiado grande, la simulación se vuelve inestable y suceden cosas locas como una pelota que rebota en el suelo o rebota en el mundo del juego, ninguna de las cuales es deseable.
Todo lo demás puede ejecutarse en un paso de tiempo variable (aunque hablando profesionalmente, a menudo es una buena idea permitir bloquear la representación en un paso de tiempo fijo). Para que un motor de juego sea receptivo, cosas como los mensajes de red y la entrada del usuario deben manejarse lo antes posible, lo que significa que el intervalo entre sondeos debería ser lo más corto posible. Esto generalmente significa variable.
Todo lo demás se puede manejar de forma asincrónica, lo que convierte el momento en un punto discutible.
fuente