¿Cuál es el mejor patrón para crear un sistema en el que todos los objetos se posicionen para ser interpolados entre dos estados de actualización?
La actualización siempre se ejecutará con la misma frecuencia, pero quiero poder procesar en cualquier FPS. Por lo tanto, el renderizado será lo más suave posible sin importar los cuadros por segundo, ya sea menor o mayor que la frecuencia de actualización.
Me gustaría actualizar 1 cuadro en el futuro interpolar del cuadro actual al cuadro futuro. Esta respuesta tiene un enlace que habla sobre hacer esto:
Paso de tiempo semi-fijo o totalmente fijo?
Editar: ¿Cómo podría usar también la última y la velocidad actual en la interpolación? Por ejemplo, con solo interpolación lineal, se moverá a la misma velocidad entre las posiciones. Necesito una forma de interpolar la posición entre los dos puntos, pero tenga en cuenta la velocidad en cada punto para la interpolación. Sería útil para simulaciones de baja velocidad como efectos de partículas.
fuente
Respuestas:
Desea separar las tasas de actualización (tic lógico) y de dibujo (render tick).
Sus actualizaciones producirán la posición de todos los objetos en el mundo a dibujar.
Cubriré dos posibilidades diferentes aquí, la que solicitó, la extrapolación, y también otro método, la interpolación.
1)
La extrapolación es donde calcularemos la posición (predicha) del objeto en el siguiente cuadro, y luego interpolaremos entre la posición actual de los objetos y la posición en la que estará el objeto en el siguiente cuadro.
Para hacer esto, cada objeto a dibujar debe tener una
velocity
y asociadaposition
. Para encontrar la posición en la que estará el objeto en el siguiente cuadro, simplemente agregamosvelocity * draw_timestep
a la posición actual del objeto, para encontrar la posición pronosticada del siguiente cuadro.draw_timestep
es la cantidad de tiempo que ha pasado desde la marca de renderización anterior (también conocida como la llamada de extracción anterior).Si lo deja así, encontrará que los objetos "parpadean" cuando su posición prevista no coincidía con la posición real en el siguiente cuadro. Para eliminar el parpadeo, puede almacenar la posición pronosticada y alternar entre la posición predicha previamente y la nueva posición predicha en cada paso del sorteo, utilizando el tiempo transcurrido desde la marca de actualización anterior como el factor lerp. Esto seguirá dando como resultado un mal comportamiento cuando los objetos que se mueven rápidamente cambien de ubicación de repente y es posible que desee manejar ese caso especial. Todo lo dicho en este párrafo son las razones por las que no desea utilizar la extrapolación.
2)
La interpolación es donde almacenamos el estado de las dos últimas actualizaciones e interpolamos entre ellas en función de la cantidad de tiempo actual que ha pasado desde la última actualización. En esta configuración, cada objeto debe tener un asociado
position
yprevious_position
. En este caso, nuestro dibujo representará, en el peor de los casos, un tick de actualización detrás del estado de juego actual, y en el mejor de los casos, exactamente en el mismo estado que el tick de actualización actual.En mi opinión, es probable que desee la interpolación como la describí, ya que es la más fácil de implementar y dibujar una pequeña fracción de segundo (por ejemplo, 1/60 de segundo) detrás de su estado actual actualizado está bien.
Editar:
En caso de que lo anterior no sea suficiente para permitirle realizar una implementación, aquí hay un ejemplo de cómo hacer el método de interpolación que he descrito. No cubriré la extrapolación, porque no puedo pensar en ningún escenario del mundo real en el que deba preferirlo.
Cuando crea un objeto dibujable, almacenará las propiedades necesarias para dibujar (es decir, el estado información de necesaria para dibujarlo).
Para este ejemplo, almacenaremos la posición y la rotación. También es posible que desee almacenar otras propiedades como la posición de coordenadas de color o textura (es decir, si se desplaza una textura).
Para evitar que los datos se modifiquen mientras el hilo de renderizado lo dibuja (es decir, la ubicación de un objeto se cambia mientras el hilo de renderizado se dibuja, pero todos los demás aún no se han actualizado), necesitamos implementar algún tipo de búfer doble.
Un objeto almacena dos copias de él
previous_state
. Los pondré en una matriz y me referiré a ellos comoprevious_state[0]
yprevious_state[1]
. De manera similar, necesita dos copias de élcurrent_state
.Para realizar un seguimiento de qué copia del búfer doble se utiliza, almacenamos una variable
state_index
, que está disponible tanto para la actualización como para el subproceso de dibujo.El hilo de actualización primero calcula todas las propiedades de un objeto utilizando sus propios datos (cualquier estructura de datos que desee). Entonces, se copia
current_state[state_index]
aprevious_state[state_index]
, y copia los nuevos datos relevantes para el dibujo,position
yrotation
encurrent_state[state_index]
. Luego lo hacestate_index = 1 - state_index
, para voltear la copia actualmente usada del doble buffer.Todo en el párrafo anterior debe hacerse con un candado quitado
current_state
. Los hilos de actualización y sorteo eliminan este bloqueo. El bloqueo solo se retira mientras dura la copia de la información del estado, que es rápido.En el hilo de renderizado, entonces haces una interpolación lineal en la posición y la rotación de la siguiente manera:
current_position = Lerp(previous_state[state_index].position, current_state[state_index].position, elapsed/update_tick_length)
¿Dónde
elapsed
está la cantidad de tiempo que ha pasado en el subproceso de renderizado, desde la última actualización tick, yupdate_tick_length
es la cantidad de tiempo que tarda su velocidad de actualización fija por tick (por ejemplo, en 20FPS actualizacionesupdate_tick_length = 0.05
).Si no sabe cuál es la
Lerp
función anterior, consulte el artículo de Wikipedia sobre el tema: Interpolación lineal . Sin embargo, si no sabe qué es lerping, entonces probablemente no esté listo para implementar actualizaciones / dibujos desacoplados con dibujos interpolados.fuente
Lerp(previous_speed, current_speed, elapsed/update_tick_length)
). Puede hacerlo con cualquier número que desee almacenar en el estado. Lerping solo te da un valor entre dos valores, dado un factor lerp.Este problema requiere que piense en sus definiciones de inicio y finalización de manera un poco diferente. Los programadores principiantes a menudo piensan en el cambio de posición por cuadro y esa es una buena manera de comenzar al principio. Por el bien de mi respuesta, consideremos una respuesta unidimensional.
Digamos que tienes un mono en la posición x. Ahora también tiene un "addX" al que agrega a la posición del mono por cuadro basado en el teclado o algún otro control. Esto funcionará siempre que tenga una velocidad de fotogramas garantizada. Digamos que su x es 100 y su addX es 10. Después de 10 cuadros, su x + = addX debería acumularse a 200.
Ahora, en lugar de addX, cuando tiene una velocidad de cuadro variable, debe pensar en términos de velocidad y aceleración. Te guiaré a través de toda esta aritmética, pero es muy simple. Lo que queremos saber es qué tan lejos quiere viajar por milisegundo (1/1000 de segundo)
Si está disparando a 30 FPS, entonces su velX debe ser 1/3 de segundo (10 cuadros del último ejemplo a 30 FPS) y sabe que quiere viajar 100 'x' en ese tiempo, así que configure su velX en 100 distancias / 10 FPS o 10 distancias por cuadro. En milisegundos, eso equivale a 1 distancia x por 3.3 milisegundos o 0.3 'x' por milisegundo.
Ahora, cada vez que actualice, todo lo que necesita hacer es calcular el tiempo transcurrido. Ya sea que hayan pasado 33 ms (1/30 de segundo) o lo que sea, simplemente multiplique la distancia 0.3 por el número de milisegundos pasados. Esto significa que necesita un temporizador que le proporcione una precisión de ms (milisegundos), pero la mayoría de los temporizadores le proporcionan esto. Simplemente haz algo como esto:
var beginTime = getTimeInMillisecond ()
... más tarde ...
var time = getTimeInMillisecond ()
var elapsedTime = time-beginTime
beginTime = time
... ahora usa este tiempo transcurrido para calcular todas tus distancias.
fuente