Si te interesan los software, hardware, y enlaces en línea, no dudes en visitar el entretenimiento en línea de juegos más destacados en español, como ruleta, blackjack gratis, tragamonedas, video poker y mucho más.

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…


Nethope, comenzando…

June 10th, 2006

Febrero terminó, desde el punto de vista universitario, siendo momento de retomar todos esos proyectos que comentaba en el post del año nuevo… En cuanto a los proyectos personales, he decidido continuar con NetHope, el juego tipo arkanoid.

Antes de meterme con el apartado trigonométrico (el rebote de la bola en función de velocidad y ángulo de impacto), voy a comentar la implementación previa a ello.

 

Niveles

La información y los archivos necesarios en cada nivel, están en subcarpetas, dentro de una carpeta general llamada levels. El archivo .xml, los emoticonos, las paredes y el resto de gráficos del nivel 1, por ejemplo, estarán en levels/level_1. Allí, habrá un level_1.xml y un level_1.png, siendo este último el fondo del nivel. Con esta estructura, puedo cargar a través de una función genérica cualquier nivel, pasando como parámetro el número de nivel y concatenando adecuadamente para tener las rutas de los archivos.

El archivo .xml, por el momento, sólo contiene los datos de la ubicación de los sprites en pantalla (smilies y paredes) y, más concretamente, las coordenadas x e y, el nombre del gráfico y su estado. En el juego es importante distinguir qué tipo de elemento es cada sprite, es decir, no es lo mismo que se trate de un smilie simple (que desaparezca al primer impacto) que uno doble (desaparece al recibir el segundo impacto) o que una pared que nunca va a destruirse. Es por ello que la variable estado ahora cobra sentido. (estado = 0, si el smilie ya ha desaparecido, 1, 2, 3…. resto de smilies, y 9 si se trata de una pared indestructible).

 

La bola y la barra

Estos elementos también son sprites, pero los trato de forma diferente a los anteriores comentados, entre otras cosas, por ser elementos comunes a todo el juego. Al comenzar el nivel, bola y barra se situan en el centro de la pantalla. La variable miembro i_state del sprite bola, valdrá entonces 0, indicando con ello que está en reposo. Mientras esté en este estado, si la barra se mueve horizontalmente, la bola seguirá ese movimiento horizontal. Pero… y al lanzar la bola, qué pasará?

Para calcular la trayectoria que describirá la bola, hay que tener en cuenta varios factores, como el movimiento de la barra. Es decir, si la barra se mueve hacia la derecha y en ese momento se lanza la bola… ésta tomará un impulso tal que la lleve a moverse de izquierda a derecha, hasta que choque con algo. De igual modo, cuando la bola impacte con ese algo, debemos tener en cuenta de donde viene, para posteriormente sacar el ángulo de entrada y calcular la trayectoria de salida. Por este motivo, creé unas variables que guardan la posición anterior de barra y bola. Es decir, cada vez que se mueven, guardo su posición y al entrar de nuevo en el game loop, ya tengo cual era su ubicación en la vuelta anterior del bucle.

En el próximo post sobre NetHope, contaré con más detalle el cálculo de todo esto, pues aún no está implementado del todo y algunos cambios son posibles. ;) :)

Dejo una captura. ;) :)