Lección 10 – Colisiones (4) Bullet Hell mem dinámica optimizada

Aventuras en Megadrive:
Bullet Hell (mem dinámica optimizada)

OPTIMIZANDO

¿Se puede mejorar el código de la anterior entrada y evitar ralentizaciones?
Fans de la memoria estática, podéis usar las mismas técnicas que aquí se exponen para las entradas anteriores.

 

PRE-CARGA DE SPRITES

Hasta ahora, con cada sprite creado utilizo SPR_addSprite() para informar al VDP. En condiciones normales no hay problema, pero en el caso de un bullet hell, esto es una carga innecesaria para el VDP.

Vamos a cambiarlo. En la inicialización de balas y explosiones asignaremos el sprite. Es decir, creo todos los sprites en el inicio tanto balas como explosiones. Si consultas la tabla de sprites del VPD verás que está casi llena nada más comenzar.

Ya no se crea una bala (y su sprite) al disparar. Porque se han creado todas al principio. Simplemente se marca como activa (a=1) y se pone en una posición visible. Sucede lo mismo con las explosiones.

Ya no se elimina un sprite, se marcar como inactivo (a=0), y se dibuja fuera de la pantalla visible. NO utilizo SPR_releaseSprite()No libero el recurso de la tabla de sprites. Procuraremos no meter más sprites de los necesarios para evitar parpadeos.

Finalmente, al terminar la fase del Boss, será cuando liberaremos memoria y liberaremos la tabla de sprites, utilizando MEM_free() y SPR_releaseSprite().

¿Mejoran los FPS la precarga de sprites? Comparando con el código de la entrada anterior, ahora nos mantenemos estables a unos 40fps con la pantalla  llena de balas, es un avance.

 

COMPRESIÓN DE SPRITES

Hasta ahora el archivo SPRITE.RES de los sprites tenía éste aspecto:

SPRITE bala_sprite "sprites/bala8x8.png"        1 1 FAST 0
SPRITE nave_sprite "sprites/nave.png"           4 3 FAST 0
SPRITE explosion_sprite "sprites/explosion.png" 4 4 FAST 4

El penúltimo parámetro le indica al SGDK como cargar los sprites en memoria, en la versión 1.4 del SGDK estos son los posibles valores:

  • BEST / AUTO: Máxima compresión.
  • NONE: Sin compresión. Los sprites se guardan tal cual en la ROM.
  • APLIB: Buena compresión, pero es lenta.
  • FAST/LZ4W: Comprime poco, pero es rápida.

En la práctica, usaremos sólamente FAST o NONE,
las otras son demasiado lentas para un bullet hell.

El motivo de usar compresión es ahorrar espacio en la ROM. En este ejemplo no lo necesitamos, pero en juegos con numerosos sprites puede haber cierta diferencia.

Por otro lado, a veces usar compresión hace que los sprites no se pinten correctamente. No obstante, como podemos elegir el tipo de compresión para cada sprite, podemos cambiarlo a NONE cuando sea necesario.

Por tanto y siguiendo con el ejemplo, cambiaremos la compresión de FAST a NONE.

SPRITE bala_sprite "sprites/bala8x8.png"        1 1 NONE 0
SPRITE nave_sprite "sprites/nave.png"           4 3 NONE 0
SPRITE explosion_sprite "sprites/explosion.png" 4 4 NONE 4

 

¿Mejoran los FPS la carga sin compresión de sprites? Hay una ligera mejora, se  suele mantener entre 40 y 50 fps a unos cuando tenemos la pantalla está llena de balas.

 

MINIMIZANDO CÓDIGO

La función MIDEBUG() muestra el número de sprites desglosado en balas del player, balas del boss y explosiones, además de mostrar los fps.

Esta función se llama cada frame, por tanto hemos de cuidar que se ejecute sólo lo necesario. En este caso, tenemos varias llamadas a distintos sprintf(), VDP_drawText(), utilizamos varios strings y tenemos una suma de 3 ints.

Para evitar las sumas, creo una nueva variable global (int total_sprites), que aumentaremos con cada activación de un sprite (sea bala, bala del boss o explosión) y que disminuiremos con cada desactivación. Por tanto esas sumas o restas no se hacen cada ciclo, sólo cuando es necesario.

Además y para simplificar, no mostraremos desglosado cada apartado. sólo el total de sprites:

//MIDEBUG: Muestra num de sprites en pantalla
static void MIDEBUG()
{
  //show total sprite number
  char mi_string[32];
  sprintf(mi_string, "Total sprites: %4d", total_sprites);
  VDP_drawText(mi_string, 1, 24);
  //fps
  VDP_showFPS(FALSE);
}

 

¿Mejoran los FPS la carga sin compresión de sprites? Hay una ligera mejora, no suele bajar de 50 fps. Desde luego la mejora desde la entrada anterior es espectacular.

 

Como siempre, puedes ver el código y su resultado en mi github.

 

 

REPARTO DE TRABAJO POR FRAMES

Una forma sencilla de mejorar el rendimiento es ejecutar el código de determinados elementos en momentos específicos. Dependiendo de la estrategia elegida podremos mejorar más o menos. El objetivo será siempre repartir el trabajo a lo largo del tiempo.

Por ejemplo, podemos ejecutar mantenimiento_balas_player() y mantenimiento_explosiones() en los ciclos pares y mantenimiento_balas_boss() en los impares. Recordemos que podemos tener hasta 40 sprites de balas del boss y hasta 25 sprites entre balas del player y explosiones.

Por supuesto esto puede dar a lugar a fallos en el juego (como una bala que atraviese al player sin colisionar) pero tomando precauciones no habrá problemas.

Para implementar esta estrategia una forma muy básica sería:

int ciclo = 1; //variable global

Y dentro del loop principal haremos:

//turno balas player: mueve, colisiona...
if(ciclo!=1) mantenimiento_balas_player();

//turno balas boss
if(ciclo==1) mantenimiento_balas_boss();

//turno explosiones
if(ciclo!=1) mantenimiento_explosiones();

//alterna 
ciclo = ciclo * (-1);

No obstante al no ser necesario en el ejemplo actual, no lo voy a implementar.

 

OTRAS OPTIMIZACIONES (a desarrollar en el futuro)

Se aceptan sugerencias:

¿Comprobar colisiones por partes de la pantalla?
¿Dividir la pantalla en zonas?
¿Mejoras en los bucles?

__ Iré añadiendo cosas aquí a medida que las aprenda __

 

 

GITHUB

El código de esta lección, y de todas las demás, lo podrás encontrar en mi github:

https://github.com/danibusvlc/aventuras-en-megadrive