Map Scrolling y niveles
Días antes de parón obligado por temas campamentiles y con ritmo frenético de contrareloj, Nessi va cogiendo colorcillo, con sus ya cercanas 2000 líneas de código. Uno de los temas que más problemillas me ha estado dando estos días ha sido el scrolling de los mapas. Tras realizar las mil y una pruebas y de seguir tres filosofías distintas a través de 3 versiones diferentes de la función miembro de la clase MTile void Draw(SDL_Surface *srf);, por fin di con una solución efectiva. Básicamente, he seguido la técnica que se sugiere en el libro comentado en posts anteriores sobre SDL, adaptándola a Nessi y al formato propio de mapas. De forma paralela, he dejado habilitada otra función miembro que hace lo análogo, pero con los archivos .map generados por un editor de mapas llamado Mappy. De este editor no me convencen los formatos gráficos aceptados (sólo .bmp y .png), así como su configuración a la hora de generar el fichero binario donde guarda la info del mapa. No obstante, viene de perlas para mapear un escenario complejo, así que hasta que Nessi tenga su propio editor de mapas, utilizaré este programa para fases complicadas de mapear con los ficheros de texto que maneja el engine.
Esta función Draw, al igual que todas las que llevan ese mismo nombre o preposición, se encarga de dibujar en pantalla ciertos elementos, en este caso, todos los frames / tiles que componen una fase o nivel. Para ello, se sirve sólo de un parámetro, la superficie en donde dibujar o realizar su misión. Como comentaba antes, costó bastante lograr el efecto deseado, a pesar de que en sí la función no es larga ni tiene mucho código. Pero hay que pillar bien el concepto…. En primer lugar, añadí el miembro scroll a la clase MTile. Esta variable controla el progreso de cada frame, es decir, en un principio logré que el mapa hiciera scrolling, pero tile por tile, dando una sensación de saltos entre cada vuelta al game loop poco vistosa. Para evitar esto, la variable scroll controla que cada frame se vaya mostrando progresivamente, sin salto alguno de tile en tile. En cada llamada, aumenta en uno, (scroll++), siempre y cuando no sea mayor al tamaño de cada tile (height o altura). Si es mayor, scroll se resetea a cero, para que cuente de nuevo en el siguiente tile.
Por otro lado, hay que tener en cuenta que el mapa está cargado en la matriz miembro llamada ArrayTiles. En ella, los elementos están puestos según se fueron leyendo: los primeros corresponden a los primeros números del fichero y los últimos a los últimos leidos del fichero. (Es decir, de arriba a abajo). La nave protagonista dará la impresión de que se mueve hacia arriba (de abajo a arriba), por lo que se debe comenzar a dibujar el mapa desde las últimas posiciones del array e ir restado a un contador el número de tiles por fila, hasta llegar a la primera fila y con ello al fin del mapa. Puesto que en cada llamada ha de conservarse ese contador, añadí otro miembro a la clase para tal fin, index. Al tratarse de tiles de 40×40, en la pantalla caben 20 filas por 15 columnas, debiendo hacer en cada vuelta que scroll>=40, index-=20; y el citado scroll=0; Para que luego no se nos pase de rango, dejaremos de restar a index cuando lleguemos al principio de la matriz. (Recorremos la matriz de 20 en 20, desde el final al principio).
Y todo esto, para qué? El último paso es dibujar en pantalla lo calculado anteriormente, aquella parte del mapa que ha de ser dibujada. Siendo un mapa de dos dimensiones, está claro que se tratará de dos bucles for anidados. Calcular la posición x de cada tile, es simple, x*40, dentro del bucle for(x=0;x<20;x++) (el anidado). En cuanto a la y, habrá que hacer (y-1)*size_frame+scroll;. Con ello, tenemos la coordenada Y del tile a dibujar, con el scroll que le corresponde. Por último, el índice lo obtenemos de indice=index+(y*20+x); (El elemento del array de tiles, ArrayTiles, que se debe dibujar). Dicho en código, quedó como:
for(y=0;y<16;y++)
{
for(x=0;x<20;x++)
{
rect.x=x*size_frame;
rect.y=(y-1)*size_frame+scroll;
indice=index+(y*20+x);
SDL_BlitSurface(ArrayTiles[indice].frame,NULL,srf,&rect);
}
}
obteniendo un efecto de scroll suave. La idea es que haya dos tipos de niveles: uno por tiempo y otro objetivo de fin de fase. El primero, será un mapa infinito (finito, pero que se repite x veces o durante x tiempo) y el segundo será un mapa finito, una vez te termine el mapa, se quedará en ese lugar hasta cumplir el objetivo final, normalmente destruir un enemigo final. Por este motivo, tendré que retocar esta función Draw con algún código extra, que me controle cómo actuar en cada caso.
Además, estos días he realizado la estructura básica del sistema de niveles, una vez se pulsa en nuevo juego, el engine va generando los niveles… carga de nivel uno, nivel uno se genera hasta el fin de mapa, carga del nivel dos… y así sucesivamente… Para controlar todo esto y con miras ya a realizar un engine (o sentar sus bases) standar e independiente, creé una nueva clase llamada Nessi_Eng, donde se guardan todos los datos relativos al juego, como la resolución de pantalla, las funciones que inicializan modos gráficos, que vuelcan el buffer a la pantalla… y el nivel en el que se está en cada momento, así como un flag para indicar si el nivel ha sido o no ha sido cargado. Ya en el game loop, se comprueba con if(!Nessi_SEng.SayLevelLoaded()), esto último, cargando el nivel que toque en caso de no estar cargado. Después, se pregunta con una nueva función miembro si el mapa ha terminado o no, y en función de eso, sigue normalmente o resetea el flag de nivel a false (nuevo nivel no cargado) y a nivel actual a nivel actual++; Si estamos en la última fase, a su término, se regresa al menú.
Los siguientes pasos con los que ya estoy metido, son el gestor de balas y disparos y los enemigos, de momento con IA muy muy básica.