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

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

 

UN POCO DE C (de nuevo)

En la entrada anterior utilizamos memoria estática para manejar decenas de balas y el resultado no era para nada malo. Pero esto conlleva un problema, y es que dicha memoria ya no se puede reutilizar para otras tareas. Por tanto si seguimos dicha metodología podríamos quedarnos sin memoria principal.

La forma natural de evitar esto es utilizar memoria dinámica. Esto es, reservar la memoria necesaria para cada período de juego y liberarla cuando no sea necesaria.

Mi primera aproximación fue utilizar listas enlazadas. Luego pensé ¡Qué demonios! Voy a usar listas doblemente enlazadas. De esta forma si tenemos ‘n’ balas, los bucles serán de ‘n’ iteraciones, ni más ni menos. Máxima optimización.

No obstante esto implica entrar de lleno en manejo de punteros, reservar y liberar memoria, vigilar si hay fugas, etc. ¿Vale la pena el esfuerzo? Respuesta corta: no.

 

CON UN POCO DE AZÚCAR

La alternativa es extraordinariamente sencilla: Reservar memoria (tantas posiciones como balas pueda generar el Boss) y tratar el puntero como un array simple tal y como hicimos con memoria estática. El resto del código queda igual. Al terminar la fase, libero dicha memoria.

Para no retocar demasiado el código de la entrada anterior, creo un struct de bala idéntico al «normal», sólo para el Boss:

//BALAS BOSS
typedef struct estructura_bala2{
  int a, x, y; // a=activa, x,y=coordenadas.
  int tipo; // 0=disparo normal, 1=disparo triple
  Sprite *spr; // sprite
}bala_boss;
//declaracion de puntero a arrays de structs
struct estructura_bala2 *lista_balas_boss;
//numero balas del boss (para optimizar el bucle)
int num_balas_boss;

Así que tenemos un puntero a la lista y el mismo contador que en la anterior entrada.
En la función de inicializar la lista, reservamos tanta memoria como en el ejemplo de memoria estática. Funciona como si fuera un simple array. Es la magia de C.

static void inicializa_lista_balas_boss(){

lista_balas_boss = MEM_alloc(MAX_BALAS_BOSS * sizeof(bala_boss));

  for(int cont = 0; cont <MAX_BALAS_BOSS; cont++){
    lista_balas_boss[cont].a = 0;
    lista_balas_boss[cont].x = 0;
    lista_balas_boss[cont].y = 0;
    lista_balas_boss[cont].tipo = 0;
    lista_balas_boss[cont].spr = NULL;
  }
  num_balas_boss = 0;
}

Una vez machaquemos al Boss, ya no necesitamos la lista de balas. Libero memoria:

MEM_free(lista_balas_boss);

 

Puedes encontrar éste ejemplo en mi github. Verás que el código no cambia mucho con respecto al ejemplo anterior, por tanto su virtud es ser muy sencillo y se puede liberar la memoria utilizada. Su defecto es no ser tan óptimo como podría serlo. Es posible llegar a tener 50 sprites en pantalla, pero las ralentizaciones están ahí tal y como pasaba con el ejemplo de memoria estática.

 

DEBUG

En la ventana Debug de GensKmod nos aparece información muy útil. Nada más lanzar la ROM:

Captura015

Al lanzar la ROM hay 79 sprites (el SGDK se reserva uno para uso propio) y tras cargar la nave quedan 78 libres. Además la memoria principal libre son aprox 40KB (40884 bytes).

Ahora una captura en plena orgía de balas y explosiones:

Captura016

78 sprites libres – 45 en pantalla = 33 sprites libres (tal y como se ve en la captura).

Importante dejar de disparar y comprobar que, al desaparecer todas las balas de la pantalla, volvemos a tener 78 sprites libres.

 

FPS

Comparando con el código de la entrada anterior (mem. estática), los fps son los mismos:

  • Sólo disparando la nave: un mín de 38 fps, pero suele estar por encima de 44.
  • Sólo disparando el Boss: un mín de 33 fps, pero suele estar por encima de 37.
  • Bullet Hell(todo a la vez): mín de 18 fps, pero suele mantenerse en 20-24 fps.

No hay sorpresa, al fin y al cabo es prácticamente igual, con la salvedad de que en este caso podemos liberar la memoria al terminar y en la entrada anterior no.

 

¿MEMORIA DINÁMICA? ¿RALENTIZACIONES?

Los grandes fans de la memoria dinámica habrán puesto el grito en el cielo después de leer las afirmaciones de esta entrada. Y con razón. Debería reservar y liberar memoria a medida que creo y destruyo balas. Y eso sería gestión de memoria dinámica de verdad.

Por otro lado, ¿podemos evitar las ralentizaciones?

Lo veremos en las siguientes entradas.

 

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