Interpolate

En este episodio detallo el funcionamiento del método interpolate en mini2dx. En el primer minuto hay una errata. Obviamente no es el bucle white sino el bucle while.

Este curso ha sido marcado como anticuado y no está siendo revisado de forma activa. Es posible que la información pueda estar desactualizada o que los enlaces se hayan roto.

Cuando desarrollamos juegos, uno de los aspectos más importantes a controlar es cómo se ejecuta el bucle principal, que es el encargado de actualizar la lógica del juego y de dibujar en pantalla. En Mini2DX, este bucle se divide en tres métodos fundamentales: Update, Render e Interpolate. Aunque Update y Render son bastante comunes en muchas librerías, el método Interpolate es algo menos habitual y merece una explicación detallada para entender por qué es tan valioso.

En los primeros tiempos del desarrollo de videojuegos, los juegos simplemente ejecutaban el bucle principal tan rápido como el hardware lo permitía. Esto significaba que primero se llamaba a Update para actualizar la lógica, luego a Render para dibujar, y así sucesivamente sin ningún control sobre el tiempo transcurrido. El problema de este enfoque es que la velocidad del juego dependía directamente de la potencia del ordenador. Por ejemplo, si en el método Update movíamos un objeto 5 píxeles por llamada, en un ordenador rápido ese objeto se movería mucho más rápido que en uno lento, lo que generaba inconsistencias en la experiencia de juego.

Un caso curioso que ilustra este problema es el clásico Space Invaders. Al principio de la partida, cuando había muchos enemigos en pantalla, el método Render tardaba más en dibujar todo, por lo que el juego iba más lento. A medida que eliminábamos enemigos, el renderizado era más rápido y el juego parecía acelerarse, aunque la velocidad de los enemigos debería haber sido constante.

Para solucionar esto, se empezó a medir el tiempo transcurrido entre cada iteración del bucle y a pasar ese tiempo como parámetro a Update. Así, en lugar de mover un objeto una cantidad fija por llamada, se movía en función del tiempo transcurrido, por ejemplo, 5 píxeles cada 16 milisegundos si el juego corre a 60 fotogramas por segundo. Esto permitía que el juego se adaptara a diferentes velocidades de hardware, simulando el mismo tiempo en cada actualización.

Sin embargo, este método también tiene sus inconvenientes. En ordenadores muy potentes, donde el juego puede actualizarse muchas veces por segundo, la simulación de la física es mucho más precisa y fluida que en máquinas lentas, donde las actualizaciones son menos frecuentes y más espaciadas. Esto puede provocar problemas como colisiones que no se detectan a tiempo o movimientos poco naturales.

Para evitar estas diferencias, lo ideal es que el método Update se llame a una velocidad fija, independientemente de la velocidad de renderizado. Esto significa que Update debe ejecutarse, por ejemplo, 100 veces por segundo, mientras que Render puede ir tan rápido como el hardware lo permita. Así, la lógica del juego se mantiene constante y predecible en cualquier máquina.

Para lograrlo, se divide el tiempo transcurrido en intervalos fijos, por ejemplo, de 10 milisegundos. Si en un fotograma han pasado 40 milisegundos, se llama cuatro veces a Update, cada vez simulando 10 milisegundos. En Mini2DX, el método Render genera el tiempo transcurrido y el método Update lo consume en fragmentos iguales. Por ejemplo, en un juego que corre a 25 fotogramas por segundo (40 ms por fotograma), cada llamada a Render desencadena cuatro llamadas a Update.

Pero aquí surge un nuevo problema: no siempre el tiempo transcurrido es un múltiplo exacto del intervalo fijo. Por ejemplo, con un monitor a 60 Hz y Vsync activado, cada fotograma dura aproximadamente 16 ms, que no es divisible entre 10. Esto deja un sobrante de 6 ms que no encaja en un intervalo completo para llamar a Update.

Para manejar este sobrante, Mini2DX utiliza un acumulador que guarda el tiempo restante. Cada vez que el acumulador alcanza el valor del intervalo fijo (10 ms), se realiza una llamada adicional a Update. Así, el sistema mantiene un seguimiento preciso del tiempo y asegura que las actualizaciones se realicen con la frecuencia adecuada.

Aquí es donde entra en juego el método Interpolate. Este método se llama antes de Render y recibe como parámetro el porcentaje del intervalo de tiempo que ha transcurrido en el acumulador. Por ejemplo, si hay 5 ms acumulados, se pasa un 50%. Esto permite interpolar las posiciones de los objetos entre dos actualizaciones completas, haciendo que el movimiento en pantalla sea mucho más suave.

Imaginemos que en cada Update un personaje debe moverse 10 píxeles. Si en Interpolate recibimos un 50%, podemos moverlo 5 píxeles adicionales para que la animación no se vea a saltos, sino que fluya de manera continua. Esto es especialmente útil en ordenadores muy potentes donde el juego puede renderizar más rápido que la frecuencia de actualización lógica, evitando que la imagen se quede estática mientras espera la siguiente actualización.

Aunque el método Interpolate es opcional y podríamos hacer toda la lógica solo en Update, usarlo mejora notablemente la experiencia visual y la fluidez del juego.

Para quienes quieran profundizar más en este tema, existen recursos en la wiki de Mini2DX y en artículos relacionados con LibGDX que explican con ejemplos cómo implementar este sistema de actualización con interpolación.

En definitiva, separar la lógica de actualización con un intervalo fijo y usar la interpolación para suavizar el renderizado es una técnica clave para lograr juegos que funcionen de manera consistente y fluida en cualquier tipo de hardware.

Lista de reproducción
  1. 1
    Instalar Android Studio y crear proyecto
    12 minutos
  2. 2
    BasicGame y ejecutar proyecto
    11 minutos
  3. 3
    Renderizando imágenes
    11 minutos
  4. 4
    Entrada
    11 minutos
  5. 5
    Interpolate
    9 minutos
  6. 6
    Inyección de dependencia
    11 minutos