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 asociada position
. Para encontrar la posición en la que estará el objeto en el siguiente cuadro, simplemente agregamos velocity * 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 asociadoposition
y previous_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 como previous_state[0]
y previous_state[1]
. De manera similar, necesita dos copias de él current_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]
a previous_state[state_index]
, y copia los nuevos datos relevantes para el dibujo, position
y rotation
en current_state[state_index]
. Luego lo hace state_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, y update_tick_length
es la cantidad de tiempo que tarda su velocidad de actualización fija por tick (por ejemplo, en 20FPS actualizaciones update_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.