Galaga. Paso 03: Incremento de tiempo fijo

Creación de juegos desde cero: C# y SFML

El sistema que hemos visto en el paso anterior funciona bien, sobre todo si la velocidad de las iteraciones es muy rápida (el procesador es muy veloz y hay pocos cálculos a realizar en nuestros procesos de actualización del mundo). Sin embargo, en el caso de que las iteraciones se produzcan cada mucho tiempo o con variaciones muy variables, este sistema falla.

Para entenderlo mejor, veamos una caso de una bola que se mueve en ángulo hacia un muro

juegos_inc_tiempo_fijp

Un sistema muy lento (morado) dibujaría en un primer momento la pelota en la posición 1 pero, al tardar mucho en procesar, cabe la posibilidad que para el tiempo transcurrido entre la posición 1 y el momento en el que le toque dibujar la posición 2, esta se encuentre pasado el muro. Cuando una vez actualizada la posición se calculen las posibles colisiones, el sistema dirá (con razón) que no hay ninguna y, por lo tanto la pelota seguirá su curso  en al dirección incorrecta.

Sin embargo, en el caso de que el sistema sea rápido, el incremento de tiempo entre cada actualización será más pequeño y por lo tanto el control de posibles colisiones más exhaustivo y menos probable que pase el problema anterior.

Para evitar esto es aconsejable que las actualizaciones  se hagan a un tiempo constante [1].


Si el sistema es rápido, seguramente desaprovechará los ciclos y renderizará varias veces sin actualizar la lógica.  Vamos, que aunque sea capaz de funcionar a 120 frames por segundo, que no lo haga. Esta funcionalidad se podría hacer “durmiendo” la aplicación durante el tiempo sobrante hasta el siguiente frame, utilizando alguna función similar a sleep [2]. El problema es que la función sleep no da buenos resultados si la medición de tiempo resulta importante, así que el código se complica un poco más, ya que hay que calcular el tiempo que ha costado redibujar y actualizar cada frame. [3]

Por otro lado, si el sistema es lento, no renderizará todo, sólo el último estado, pero SI recalculará el estado de cada frame, pudiendo detectar si ha habido, por ejemplo, una colisión. Realmente sería más correcto decir que lo podrá hacer con más facilidad, ya que el problema sigue existiendo sólo que, al tomar en tiempos más homogéneos (evitando grandes espacios de redibujado) y pequeños, las distancias con respecto a las posibles colisiones son más pequeñas y por lo tanto más rápidas de detectar.

Veamos como implementamos esto en nuestro código. Desde la carpeta donde está el proyecto, hacemos git checkout Paso003:

C:\Users\alfredo\Documents\Visual Studio 2015\Projects\Galaga-SFML.Net [master]> git checkout Paso003

Asignamos un tiempo por frame, en este caso 60 frames por segundo

_timePerFrame = SFML.System.Time.FromSeconds(1f / 60f);

Luego, en cada iteración del bucle principal ponemos el tiempo a cero y tomamos el tiempo transcurrido desde la última puesta a cero

timeSinceLastUpdate += clock.Restart();

 mientras el tiempo transcurrido sea mayor que el que queremos por cada frame, restamos el tiempo del frame (ya que damos ese frame por procesado) y actualizamos el estado del mundo. Sólo renderizaremos si el tiempo transcurrido es menor o igual al tiempo por frame que hemos configurado

while (timeSinceLastUpdate > _timePerFrame)
{
     timeSinceLastUpdate -= _timePerFrame; 
     ...
     update(_timePerFrame); // actualizo el mundo
}

[1] Los motores de físicas trabajan mejor con este tipo de algoritmos de movimiento

[2] De hecho, hay una función basada en sleep en SFML que permite asignar el tiempo por frame

[3]  Aunque en nuestro caso la medición de tiempo no resulta tan crítica, vamos a trabajar realizando el cálculo del tiempo

Tags: , , , , ,

Compartir en Facebook Compartir en Twitter

Dejar un comentario

XHTML: Puedes usar estos tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>