Introducción

Ticks de física y frames renderizados

Un concepto clave para entender en Godot es la distinción entre los ticks de física (a veces denominados iteraciones o frames de física) y los frames renderizados. La física avanza a una velocidad de tick fija (establecida en ProjectSettings.physics/common/physics_fps), que por defecto es de 60 ticks por segundo.

Sin embargo, el motor no necesariamente renderiza a la misma velocidad. Aunque muchos monitores se refrescan a 60 Hz (ciclos por segundo), muchos se refrescan a frecuencias completamente diferentes (por ejemplo, 75 Hz, 144 Hz, 240 Hz o más). Aunque un monitor pueda mostrar un nuevo frame, por ejemplo, 60 veces por segundo, no hay garantía de que la CPU y la GPU puedan suministrar frames a esta velocidad. Por ejemplo, al ejecutarse con V-Sync, la computadora puede ser demasiado lenta para alcanzar los 60 FPS y solo llegar a los plazos de 30 FPS, en cuyo caso los frames que ves cambiarán a 30 FPS (resultando en tartamudeo).

Aquí hay un problema. ¿Qué sucede si los ticks de física no coinciden con los frames? ¿Qué sucede si la velocidad de tick de física está desfasada con la velocidad de frame? O peor aún, ¿qué sucede si la velocidad de tick de física es inferior a la velocidad de frame renderizado?

Este problema es más fácil de entender si consideramos un escenario extremo. Si estableces la velocidad de tick de física en 10 ticks por segundo, en un juego simple con una velocidad de frame renderizado de 60 FPS. Si trazamos un gráfico de las posiciones de un objeto en relación a los frames renderizados, puedes ver que las posiciones parecerán "saltar" cada 1/10 de segundo, en lugar de ofrecer un movimiento suave. Cuando la física calcula una nueva posición para un objeto, esta posición no se renderiza durante solo un frame, sino durante 6 frames.

../../../_images/fti_graph_fixed_ticks.png

Este salto se puede ver en otras combinaciones de velocidad de tick / velocidad de frame como fallas o vibraciones, causadas por este efecto de escalonamiento debido a la discrepancia entre el tiempo de tick de física y el tiempo de frame renderizado.

¿Qué podemos hacer cuando los fotogramas (frames) y los ticks están desincronizados?

¿Sincronizar la tasa de ticks y fotogramas juntos?

La solución más obvia es eliminar el problema asegurándose de que haya un tick físico que coincida con cada fotograma. Este solía ser el enfoque en las consolas antiguas y en las computadoras con hardware fijo. Si sabes que todos los jugadores estarán utilizando el mismo hardware, puedes asegurarte de que sea lo suficientemente rápido para calcular ticks y fotogramas a, por ejemplo, 50 FPS, y así garantizar que funcione de manera óptima para todos.

Sin embargo, los juegos modernos a menudo ya no se desarrollan para hardware fijo. Con frecuencia, se planea lanzar juegos en computadoras de escritorio, dispositivos móviles y más, todos los cuales tienen grandes variaciones en rendimiento, así como diferentes tasas de actualización de los monitores. Necesitamos encontrar una mejor manera de lidiar con este problema.

¿Adaptar la tasa de ticks?

En lugar de diseñar el juego con una tasa de ticks de física fija, podríamos permitir que la tasa de ticks se ajuste según el hardware del usuario final. Podríamos utilizar, por ejemplo, una tasa de ticks fija que funcione para ese hardware en particular, o incluso variar la duración de cada tick de física para que coincida con una duración de fotograma específica.

Esto funciona, pero hay un problema. La física (y la lógica del juego, que a menudo también se ejecuta en el _physics_process) funcionan mejor y de manera más consistente cuando se ejecutan a una tasa de ticks fija y predeterminada. Si intentas ejecutar la física de un juego de carreras diseñado para 60 TPS (ticks por segundo) a, por ejemplo, 10 TPS, la física se comportará de manera completamente diferente. Los controles pueden ser menos responsivos, las colisiones y trayectorias pueden ser completamente distintas. Puedes probar tu juego a fondo a 60 TPS y luego descubrir que se rompe en las máquinas de los usuarios finales cuando se ejecuta a una tasa de ticks diferente.

Esto puede dificultar la calidad del aseguramiento debido a los errores difíciles de reproducir, especialmente en juegos AAA, donde problemas de este tipo pueden ser muy costosos. También puede ser problemático para juegos multijugador en términos de integridad competitiva, ya que ejecutar el juego a ciertas tasas de ticks puede ser más ventajoso que otras.

Bloquea la tasa de ticks, pero utiliza interpolación para suavizar los fotogramas entre los ticks de física

Es cierto, esta ha llegado a ser una de las aproximaciones más populares para lidiar con el problema. Es compatible con Godot 3.5 y versiones posteriores en 3D (aunque es opcional y viene desactivado por defecto).

Hemos establecido que el arreglo más deseable para la consistencia y la previsibilidad de la física y la lógica del juego es una tasa de ticks de física fija en el momento del diseño. El problema radica en la discrepancia entre la posición de la física registrada y dónde "queremos" que un objeto de física se muestre en un fotograma para lograr un movimiento suave.

La respuesta resulta ser simple, pero puede ser un poco difícil de comprender al principio.

En lugar de llevar un registro únicamente de la posición actual de un objeto de física en el motor, llevamos un registro de tanto la posición actual del objeto como la posición anterior en el tick de física anterior.

¿Por qué necesitamos la posición anterior (de hecho, toda la transformación, incluida la rotación y el escalado)? Mediante un poco de magia matemática, podemos utilizar interpolación para calcular cuál sería la transformación del objeto entre esos dos puntos, en nuestro mundo ideal de movimiento suave y continuo.

../../../_images/fti_graph_interpolated.png

Interpolación lineal

La forma más sencilla de lograr esto es mediante interpolación lineal, también conocida como "lerp", que es posible que hayas utilizado antes.

Consideremos únicamente la posición y una situación en la que sabemos que la coordenada X del tick de física anterior era de 10 unidades, y la coordenada X del tick de física actual es de 30 unidades.

Nota

Aunque las matemáticas se explican aquí, no tienes que preocuparte por los detalles, ya que este paso se realizará automáticamente por ti. Bajo la superficie, Godot puede utilizar formas de interpolación más complejas, pero la interpolación lineal es la más fácil de explicar.

La fraccion de interpolación de físicas

Si nuestros ciclos de física ocurren 10 veces por segundo (para este ejemplo), ¿qué sucede si nuestro fotograma renderizado tiene lugar en el tiempo 0.12 segundos? Podemos hacer algunos cálculos para averiguar dónde estaría el objeto y lograr un movimiento fluido entre los dos ciclos.

Primero, tenemos que calcular qué tan lejos queremos que esté el objeto en el ciclo de física. Si el último ciclo de física ocurrió a los 0.1 segundos, estamos a 0.02 segundos (0.12 - 0.1) en un ciclo que sabemos que durará 0.1 segundos (10 ciclos por segundo). Por lo tanto, la fracción a través del ciclo es:

fraction = 0.02 / 0.10
fraction = 0.2

Esto se conoce como la fracción de interpolación de física, y Godot lo calcula automáticamente por ti. Puedes obtenerlo en cualquier fotograma llamando a Engine.get_physics_interpolation_fraction.

Calculando la posición interpolada

Una vez que tenemos la fracción de interpolación, podemos insertarla en una ecuación de interpolación lineal estándar. La coordenada X sería:

x_interpolated = x_prev + ((x_curr - x_prev) * 0.2)

Entonces, sustituyendo nuestro valor x_prev como 10, y x_curr como 30:

x_interpolated = 10 + ((30 - 10) * 0.2)
x_interpolated = 10 + 4
x_interpolated = 14

Descompongamos eso:

  • Sabemos que la coordenada X comienza desde la posición en el ciclo de la física anterior (x_prev), que es de 10 unidades.

  • Sabemos que después del ciclo de física completo, se habrá agregado la diferencia entre el ciclo actual y el ciclo anterior (x_curr - x_prev) (que son 20 unidades).

  • Lo único que necesitamos variar es la proporción de esta diferencia que agregamos, de acuerdo con qué tan lejos estemos en el ciclo de física.

Nota

Aunque este ejemplo interpola la posición, lo mismo se puede hacer con la rotación y la escala de los objetos. No es necesario conocer los detalles, ya que Godot hará todo esto por ti.

¡Transformaciones suavizadas entre tics de física?

Al poner todo esto junto, se muestra que debería ser posible tener una estimación agradable y suave de la transformación de objetos entre el tic de física actual y el tic anterior.

Pero espera, puede que hayas notado algo. Si estamos interpolando entre el tic actual y el tic anterior, no estamos estimando la posición del objeto ahora, sino que estamos estimando la posición del objeto en el pasado. Para ser exactos, estamos estimando la posición del objeto entre 1 y 2 ciclos en el pasado.

En el pasado

¿Qué significa esto? Este esquema funciona, pero significa que estamos introduciendo efectivamente un retraso entre lo que vemos en la pantalla y dónde deberían estar los objetos.

En la práctica, la mayoría de las personas no notarán este retraso, o más bien, normalmente no es objetable. Ya existen retrasos significativos en los juegos, simplemente no los notamos típicamente. El efecto más significativo es que puede haber un ligero retraso en la entrada, lo que puede ser un factor en juegos de reflejos rápidos. En algunas de estas situaciones de entrada rápida, es posible que desees desactivar la interpolación de física y utilizar un esquema diferente, o usar una alta frecuencia de ciclos, lo que mitiga estos retrasos.

¿Por qué mirar hacia el pasado? ¿Por qué no predecir el futuro?

Existe una alternativa a este esquema, que consiste en: en lugar de interpolar entre el tic anterior y el actual, utilizamos matemáticas para extrapolar hacia el futuro. Intentamos predecir dónde estará el objeto, en lugar de mostrar dónde estuvo. Esto se puede hacer y podría ofrecerse como una opción en el futuro, pero hay algunas desventajas significativas:

  • La predicción puede no ser correcta, especialmente cuando un objeto colisiona con otro objeto durante el tic de física.

  • Cuando una predicción es incorrecta, el objeto puede extrapolar hacia una posición "imposible", como dentro de una pared.

  • Si la velocidad de movimiento es lenta, es posible que estas predicciones incorrectas no sean un problema grave.

  • Cuando una predicción es incorrecta, el objeto puede tener que saltar o volver rápidamente a la trayectoria corregida. Esto puede ser visualmente desorientador o incómodo para el jugador.

Interpolación con paso de tiempo fijo

En Godot, todo este sistema se conoce como "interpolación de física", pero también puede escucharlo referido como "interpolación con paso de tiempo fijo", ya que está interpolando entre objetos movidos con un paso de tiempo fijo (tics de física por segundo). En ciertos aspectos, el segundo término es más preciso, porque también se puede utilizar para interpolar objetos que no están impulsados por la física.

Truco

Aunque la interpolación de física suele ser una buena opción, hay excepciones donde puede que decidas no utilizar la interpolación de física incorporada en Godot (o usarla de manera limitada). Un ejemplo son los juegos multijugador en línea. Los juegos multijugador a menudo reciben información basada en tics o tiempos de otros jugadores o un servidor, y estos pueden no coincidir con los tics de física locales, por lo que una técnica de interpolación personalizada puede ser una opción más adecuada.