Eileen, comenzando…

October 3rd, 2006

Este fin de semana he estado ampliando y mejorando a Nessi. Para poner a prueba esas nuevas funcionalidades y con cierta mosca detrás de la oreja (nunca he hecho un tetris), he comenzado Eileen. A parte de los conceptos que todo juego ha de tener, como el de game loop, timing… para este juego tenía que pensar cómo gestionar las piezas y el tablero de juego (el rectángulo que va conteniendo las piezas que se van generando…). Bien…

La matriz Matrix

Pensando un poco sobre ello… una ruta a tomar podría ser basarlo en coordenadas y colisiones de los sprites (o piezas). Sin profundizar demasiado, me viene a la mente alguna de las limitaciones de tal planteamiento… como por ejemplo la eliminación de una fila tras hacer línea. Igual se podría hacer algún tipo de cálculo para solucionar cosas así, pero teniendo la experiencia exitosa de una matriz (en el buscaminas, el ajedrez y otros programillas que tengo por ahí con el interface más que simplón…) por debajo que controle todo… me decanté por ella.

Matrix quedó definida con 20 filas y 14 columnas… (el espacio del tablero dibujado en photoshop entre 25, tamaño del cuadro base). A través de una función de Reset, todos sus elementos son inicializados a cero, código que he tomado como marca de que esa casilla está vacía.

Las piezas

Otro aspecto importante, quizás de lo más importante, son las piezas. Antes he hablado del tamaño del cuadro base. En el tetris las piezas se desplazan por una especie de casillero con celdas (que no se ven, pero cada cuadro que forma una pieza, al desplazarse, se mueve de celda a celda). A donde quiero llegar es a cómo estarán hechas las piezas. Una pieza que sólo ocupe un cuadro o celda en cada movimiento (llamémosla A), no tiene complicación. Pero como es obvio, un tetris de sólo esa pieza quedaría muy pobre y poco jugable. Si meto una pieza que ocupe dos cuadros horizontales.. los problemas surgen. Una de las ideas era formar las piezas por unión de sprites únicos (la citada A): generar el tipo de pieza y en funcion de ello dibujar en pantalla la forma correspondiente, actualizando las posiciones de todos los A de la pieza y de todas las piezas. Mi principal problema con esto era gráfico. Me gustaba que las piezas tuvieran un borde alrededor, así que uniendo A con otra A, en el medio de la unión, tendría un borde algo feote. Como solución a esto podría haberme currado la pieza A sin borde una vez en cada lado… pero decidí optar por otra forma de hacerlo.

Cada pieza es un gráfico. En total, de momento, hay 10 piezas disponibles, que se irán generando con un random y moviéndose por Matrix. A tales efectos, cada pieza irá actualizando los elementos de Matrix que ocupe / libere con cada movimiento. Es decir, la pieza A comenzará en el elemento 0,0. Un ciclo despues… si el usuario no mueve hacia los lados, ocupará la posición 1,0. La manera de indicar ese ocupamiento es mediante números. (El número de pieza (1,2,3… hasta 10)) Para entender mejor a lo que me refiero… mira esta captura. En ella se ve Matrix y todos sus valores. Los números negativos indican que esa posición está ocupada por ese pieza. (-2, por la 2, -4, por la 4…).

Dibujando las piezas

Como comentaba en algún párrafo anterior, Eileen se basa en Matrix. A través del valor de sus elementos, el procedimiento void DrawMatrix(int Matrix[20][14]); dibuja el juego. En una instancia de Nessi_Sprite llamada piece, cargo todas las piezas, con el orden de piezas ya comentado. (la pieza uno la cargo primera, la pieza 2, segunda…). Hecho esto con dos bucles recorro cada elemento de la matriz. Si el elemento es mayor que cero, dibujo la pieza, seteando el frame activo a Matrix[j][k]-1. (El número de frames de un sprite comienza en cero).

/* Dibuja la matrix en pantalla */
void DrawMatrix(int Matrix[20][14])
{
int j,k;

int inicio_row=88;
int inicio_col=15;

Nessi_Sprite piece(9);
piece.AddFrame(”graphics/piece01.png”);
piece.AddFrame(”graphics/piece02.png”);
piece.AddFrame(”graphics/piece03.png”);
piece.AddFrame(”graphics/piece04.png”);
piece.AddFrame(”graphics/piece05.png”);
piece.AddFrame(”graphics/piece06.png”);
piece.AddFrame(”graphics/piece07.png”);
piece.AddFrame(”graphics/piece08.png”);
piece.AddFrame(”graphics/piece09.png”);

for(j=0;j<20;j++)
{
for (k=0;k<14;k++)
{
if (Matrix[j][k]>0)
{
piece.SetCurrentFrame(Matrix[j][k]-1);
piece.SetX(inicio_col+(25*k));
piece.SetY(inicio_row+(25*j));
piece.Draw();
}
}
}
}

Las dos variables int de inicio, es la referencia del width y del height en donde comienza el tablero de juego y a partir del cual basarse en el dibujado de piezas. A pesar de que cada pieza que ocupa varias celdas es un gráfico, a efectos de Matrix cuenta como si fueran varios A, pero en lugar de tomar el valor 1 para todos los cuadros, el primer cuadro de arriba a la izquierda vale el número de pieza y, el resto, el número de pieza en negativo. De ahí el if del segundo bucle. Si por ejemplo se encuentra un 4, con el setframe sabe que ha de dibujar la pieza 4 (la que tiene índice 3 en el array de frames) en la posición x e y calculada.

En la galería he subido varias dos caps del juego y una cap de todas las piezas, para que se entienda mejor la filosofía utilizada. Observando las piezas, es curioso que todas ellas, las disponibles en el juego, tiene el citado cuadrado arriba izquierdo dibujado. Esto es así por el motivo explicado antes: para el dibujado tengo que tener una referencia que me indique qué pieza es. Si el cuadro está vacío… tendría que indicar un cero en lugar del número de pieza, perdiendo esa referencia. Ya se me ocurren algunas cosillas como solución… que para no hacer un post kilométrico comentaré en otra ocasión. ;) :)

Por otro lado, comentar que para el cambio de sentido, como se verá en la cap de piezas, hago un cambio de sprite. Es decir, si tengo una pieza 6… y su pieza girada es, por ejemplo, la número 15… sólo hay que cambiar el id de la pieza actual ajustando el valor de los elementos de Matrix afectados.

Validaciones

Para que el juego de tetris sea tal, es necesario implementar un sistema de validaciones. Que la pieza no se salga del tablero por ninguno de los lados, que una pieza no ocupe una posición o alguna de las posiciones que esté ocupando otra pieza… con Matrix y los valores de sus elementos, se comprueba todo ello según la pieza actual. (básicamente, si hay cero, la celda está libre y se permite el movimiento).

Haciendo línea

Hay que tener en cuenta que cuando una línea esté totalmente llena, hay que borrar esa línea y bajar las superiores a ella. Esto lo logré en tres pasos…

  1. Convierto todas las celdas ocupadas a 1, menos las de la fila que tiene la linea.
  2. Seteo a cero las celdas de la fila que tiene línea.
  3. Muevo progresivamente las celdas hacia abajo. (Matrix[j+1][k]=Matrix[j][k]; ) Para que sea eficiente, una vez una fila es toda de ceros, dejo de mover hacia abajo.

Puntos, velocidad….

Ahora queda llevar el control de puntos, de la velocidad del juego según el nivel… y todos los detalles deseados. Quizá sea la parte menos complicada… así que tampoco entraré en detalles… ;) Todavía me quedan cosillas de esas y algún bug que otro por resolver y mejorar… así que tardaré algo má en publicar el .exe… :roll:


Nessi: modelo singleton

September 2nd, 2006

En el último post sobre Nessi, comentaba que había decidido ir montando Nessi sin usar el modelo singleton, sino a base de unas instancias globales, por resultar más cómoda su llamada. Esto traía, a parte del que el usuario podría declarar instancias sin ningún problema, otra serie de problemas por los que he tenido que volver al planteamiento inicial del modelo singleton, para aquellas clases que han de ser únicas y accesibles desde cualquier parte del programa. Y eso de singleton, qué es?

La wikipedia (como de costumbre, je,je,je…), en este artículo, lo explica muy bien. Como reza el enlace, es una forma de lograr que sólo haya una instancia de una determinada clase en ejecución. Para lograrlo.. hay que meter en constructor de la clase dentro de la parte privada, para que sea la propia clase desde dentro quién cree la instancia y, si ya está creada, devuelva su dirección. El proceso encargado de realizar esta tarea será otra función, esta vez pública, en el caso de Nessi, static Nessi* GetNessi();. Para acceder a sus miembros… se hace algo como Nessi::GetNessi()->miembro. Para evitar escribir tanto, en cada función miembro de las otras clases que hacen uno de ella o en el propio programa principal, uso variables locales.

Nessi *nessi_local;
nessi_local=Nessi::GetNessi();

Y luego ya trabajo con nessi_local, haciendo referencia del mismo modo que antes, nessi_local->miembro;.

Con el cambio de filosofía, me sigo ahorrando el paso de parámetros (el miembro screen de Nessi, por ejemplo, es accesible desde cualquier punto del programa y de la propia librería, al igual que el sistema de log, el de tiempo, el de efectos especiales….), siendo cómodo y teniendo más control sobre el asunto. ;) Así que, con un nuevo camino abierto, queda seguir migrando / implementando las clases y funciones… La documentación estará basada en la que genera un programa llamado doxygen… (google :)), sino recuerdo mal open source. Genera HTML y varios formatos más en modo muy guapete (organizado y vistoso), muy recomendado.. :D

pd.- Hasta después del deadline del proyecto con UC, no tenía pensando seguir con Nessi.. pero el otro día tuve un ratillo muerto…. así que me puse a probar, funcionó… y migré lo básico al nuevo modelo. Para la semana que viene, publicaré una pequeña (realmente pequeña) demo… un par de fades, algún efecto de sonido y poco más… todavía queda bastante curro para sacar una versión estable… pero bueno… coincidiendo con el rule de esa demo a los compañeros de curro….


Map Scrolling y niveles

July 19th, 2006

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.


Tiles y mapeado

July 19th, 2006

En el post anterior, comentaba la existencia de problemillas varios con el sistema de carga de mapas de Nessi Engine. La principal traba es que al ser un fichero de texto (para facilitar la edición del mapa a mano, desde un bloc de notas mismamente) hay que leerlo como tal. Una primera opción es leer caracter a caracter, quedándome sólo con los números (que han de ser de una cifra). La función standar get(), hace esa función, pero devuelve ese char como unsigned char convertido a integer, es decir, el código ascii del caracter leido. (Si lee un cero, por ejemplo, file_map.get() devuelve un 48 ). Jugando con el código ascii y restando 48 al número sacado, la conversión al número leido estaba hecha. (por ejemplo, 48-48=0, lo que habíamos leido). Hasta ahí bien, pero eso da sólo 10 combinaciones posibles o, mejor dicho, sólo puedo tener en el array de recursos 10 frames, para poder referenciarlos a todos (su índice, del 0 al 9, pues para que lo anterior funcione en el fichero solo puede haber números de una cifra). Esto se queda corto, 10 frames para confeccionar mapas interesantes es bastante escaso. Intentando tirar del hilo, también podría utilizar las letras, y nuevamente hacer esa conversión a un número restando al ascii. Con varios ifs controlaría en que rango está, y en función de eso, restaría un número al otro. (de 48 a 57, restaría 48 y de 65 (A) a 90 (Z), restaría 55). Si además se incluyen las minúsculas, tendría un mayor número de recursos posibles a referenciar. (Ahora en el fichero, no sólo habría numeros, sino letras que la función SetLevel se encargaría de pasar a números válidos). Pero a pesar de todo, sigue resultando algo escaso, con unos 50 o 60 recursos a tener como máximo. Igual puede que este sistema nunca se me quede corto, pero nunca me ha gustado tener este tipo de limitaciones, así que toca cambiar de estrategia o filosofía.

Algo claro es que el fichero ha de seguir un patrón, tanto para que siga siendo fácilmente editable como para que la función que lea el fichero pueda interpretarlo sin demasiados problemas. Tener por ejemplo 1 45 234 en el fichero, no sería apropiado para este planteamiento. Sin embargo, un buen formato sería 000.000.000…. es decir, grupos de 3 caracteres (numéricos) y un punto de separación entre grupos. Este punto sólo sirve para facilitar esa edición, viendo de forma clara la separación entre grupos. El fichero, además, conserva las 20 columnas (cada columna ahora es cada grupo) y nos da un abanico de hasta 1000 recursos a referenciar, desde el 000 (que sería el 0) hasta el 999. El flamante fichero ya está definido, cumpliendo la premisa de seguir un mismo patrón. Ahora toca implementar la función que lo lea…

La función getline, presente en File input stream class o ifstream, lee una línea. Ahora, leer caracter a caracter no interesa, optando por ir leyendo línea a línea, tratándolas según son leidas. Por tanto, codificando file_map.getline(linea,81);, se obtiene toda una línea ( 000.000.000…..) y se almacena en la variable linea. Con un bucle for, se lee por partes esa línea sacada… for(j=0;j<80;j+=4). Incremento la i en 4, pues es el tamaño de los grupos. Dentro del bucle, se trata caracter a caracter cada grupo, eliminando los ceros a la izquierda, a través de dos ifs. Si linea[j]=0, significa que prescindiremos de ese dígito, fijandonos en el siguiente, linea[j+1]. Si éste también es cero, sólo nos quedaremos con la última cifra del grupo, linea[j+2]. Pero sigue siendo necesario tener un int, para el índice del array de recursos. La función atoi(), presente en stdlib.h, convierte una cadena de caracteres en una variable de tipo integer. Si se logra tener una cadena que contenga el número en un formato adecuado para ser convertido, tendremos solucionado el problema. Es obvio que si los grupos son de 3 digitos, el número mayor después de quitar los ceros a la izquierda, será de 3 digitos. Teniendo un array como char catet[4];, podremos almacenarlo en forma de cadena. (3 posiciones para los digitos y otra para el fin de cadena o \0). Por ejemplo, en el caso que un grupo sea como 005

catet[0]=linea[j+2]; // Paso el último digito del grupo
catet[1]=’\0′; // Añado el final de cadena
num=atoi(catet); // Convierto a integer

Y una vez generado num para todos los casos (número de una, dos o tres cifras), se indica el frame a cargar en objeto MTile del nivel (st), st.AddFrame(maps.ArrayTiles[num].name);. Si en el fichero hay un 012, num valdrá 12, seleccionandose del array de recursos el elemento con índice 12. Si vale 000, num vale 0 y se selecciona el primer elemento, con índice cero, del susodicho array de recursos. La función donde se hace todo esto es bool SetLevel(MTile &st, MTile maps, char *level), que devuelve true si todo fue ok, o false en caso contrario. (comentada en el anterior post, como void).

Lo siguiente será implementar su scrolling… :P


Nessi Engine, primera parte

June 20th, 2006

Hará alguna semana, retomé el desarrollo de GeekWars, un juego que tenía en mente y que como de costumbre por la falta de tiempo había dejado aparcado. La Campus Party se acerca y es deber moral presentar algo, por poco que sea. Así pues.. manos a la obra!

Revisando la versión iniciada tiempo atrás, poco me servía. No llevaba mucho, apenas la estructura principal y varias funciones… que aunque funcionaba, no terminaban de convencerme. Por tanto, comencé de nuevo a estructurar todo… con una idea ya más clara de lo que quería. Hablando ya en presente, tras adaptar la función main a las nuevas necesidades y punto de vista, toca pensar en cómo se gestionará todo el cotarro…

Nessi Engine, pretende ser un mini-engine que controle todos los aspectos del juego de una manera más o menos independiente. Con el tiempo, puede que hasta sea un engine completo, pero debido a que los días se echan encima, habrá que conformarse y hacerlo un poco menos genérico. Nessi, llamado así en honor a una colega campusera, está compuesto de varios archivos, principalmente engine.h y engine.cpp. En ellos estará la definición y código de todas las clases, estructuras de datos, funciones…. que el juego necesite. Por otro lado, a fin de tener todo bien organizado, el programa hace uso de dos librerías más, dependientes de Nessi, una de efectos gráficos, gfx; y otra de funciones generales, principalmente de seteo, carga y utilidades varias.

Nessi cuenta ya con clases y funciones vitales, que se irán mejorando y ampliando según vaya progresando el desarrollo. Algunas de ellas son MSprite, MTile, MFrame y Nessi_Log. La primera almacena sprites, la segunda tiles y la tercera, frames de los sprites y tiles. La última, es la que gestiona el sistema de log, pasando a un fichero de texto todo lo que ocurre en el programa.

 

MSprite, MTile, MFrame. La idea de estas tres clases surgió de un buen libro de nivel iniciación / medio famosete por el mundo de SDL, más ahora que lo han liberalizado y se encuentra disponible en la red, en formato pdf. (Programación de VideoJuegos con SDL, por Alberto García Serrano). Hace tiempo que lo tengo en papel, me costó encontrarlo, pero mereció la pena. En algunos puntos no estoy muy de acuerdo con la forma de implementación, pero te da una idea clara de por donde caminar a lo largo del desarrollo de un juego. Haciendo otro pequeño offtopic (risas), recomiendo visitar el blog de Benko (ver enlaces), que se curró unos tutoriales de iniciación a SDL muy interesantes. Venga, que ya retomo el tema de las tres clases (más risas). MFrame almacena el frame o gráfico que le pasa como parámetro en su función miembro void Load(char *path);, guardando la ruta en una cadena de caracteres llamada name.

 

/* Clase donde se almacenarán los frames de cada sprite o tile */
class MFrame {

public:

char name[30];
SDL_Surface *frame;
void Load(char *path);
void Unload();

};

En breve name pasará a ser privado, creando la función que lo devuelva. (Actualmente esta así para simplicar pruebecillas varias).

 

MSprite es ya más grande y compleja. Por un lado, tiene la capacidad de crear un array dinámico de MFrame, donde estarán todos los frames del sprite. Al crear un objeto MSprite, se le pasa el número de frames que tendrá y el constructor de la clase se encarga de generar ese array, vacío, que luego se irá completando con una función miembro llamada AddFrame y que controlará el número máximo de frames a meter y el número de frames metidos o cargados. La clase también sabe en todo momento que frame dibujar, gracias a una variable que guarda ese dato y que va cambiando según la necesidad. Además, tiene funcionalidades relativas al posicionamiento y movimiento del sprite en pantalla. MSprite tiene una clase hija, que hereda sus características y añade dos más, SpaceShip. Aparte de todo lo anterior, SpaceShip controla el arma disponible en cada momento y la salud de la nave. De momento para no complicarlo, sólo tendrá un tipo de disparo a la vez, pero por supuesto se implementará que recoja tipos de disparo por el camino y éstos se vayan almacenando, pudiendo elegir arma entre las disponibles.

 

MTile, encargada de almacenar los mapas de cada fase, también se compone de un array de clases MFrame. El tema de su carga, está siendo un tema costoso, si bien parece que hemos dado con una solución más o menos eficiente. Lo suyo sería haberse creado un editor de mapas, para lo que no hay tiempo, o en su defecto usar uno… pero tras probar varios.. ninguno me convenció (bien por el sistema, bien por no soportar determinadas extensiones gráficas….). Total, que dándole vueltas la cosa terminó por tener en una carpeta de niveles archivos de texto, uno por nivel. En ellos, se almacena la info sobre los frames que componen el mapa. El formato, números enteros (identificadores del frame a cargar) separados por un espacio, en 20 columnas (número de frames que caben en el width de la pantalla, siendo cada frame de 40×40 pixels) y N filas, dependiendo de la longitud del mapa. (En cada pantalla caben 15 frames en vertical). Que el fichero sea de texto, me viene muy bien, pues puedo cambiar de forma fácil el frame a mostrar en cada posición, pero tiene la desventaja de que todo el mundo puede tocarlo con facilidad. (Al terminar las pruebas, miraré a codificar la info de alguna manera, je,je,je). Por otro lado, existe un objeto MTile llamado mapResources, donde almaceno todos los frames disponibles para confeccionar el mapa de cada fase. Para la carga de un nivel, se llama a void SetLevel(MTile &st, MTile maps, char *lvl);, pasandola el objeto MTile donde guardar la info, el objeto mapResources y el nivel a cargar. Este último, lo paso como char, pues la función se va automáticamente al fichero adecuado según ese número pasado, componiendo la ruta en donde está, y leyendo todo el fichero progresivamente. Esa es la idea, pero tengo problemillas varias al hacer las conversiones de lo leido (char o string) a lo necesitado (un int para el índice del array de recursos). Habrá que seguir dándole vueltecillas.. :roll:

 

Nessi_Log. Con el fin de tener una idea de lo que pasa en cada momento, Nessi tiene un sistema propio de log, gestionado a través de esta clase. Nessi_Log tiene 3 funciones miembro, una para crear e inicializar el archivo de log, otra para escribir en él y otra para cerrarlo. Para escribir en el log, se llama a void Write(char *message, int br_up);, donde se le pasa el mensaje a escribir y el número de saltos de línea a escribir antes del mensaje.

Aparte de estas clases, hay varias funciones interesantes, como void DrawCountDown(MSprite &dcd, SDL_Surface *srf);, que dibuja la cuenta atrás de 3,2,1, go! antes del inicio de cada fase; o las funciones de carga como void SetMapResources(MTile &smr); o void SetCountDown(MSprite &cd);.

Superadas ya las 1000 líneas y con mucho curro por delante en 24 “dias” de desarrollo, seguiremos informando…