Bienvenidos sean a este post, hoy veremos la clase para los niveles.
En el post anterior, vimos lo que podemos denominar como la base de todo lo que necesitaremos para poder utilizar los patrones o imagenes de nuestros conjuntos, pero todavia nos falta la clase encagada de poder utilizar al archivo .tmx donde esta contenido el nivel. Tomemos a nuestro proyecto para trabajar, sino lo poseen les dejo un link para descargarlo:
Descarguen el archivo y extraigan el contenido en el PC. Lo unico que deben hacer es revincular a SDL y SDL_Image. Para ello, les recomiendo este post donde comento como hacerlo para SDL y este otro post donde hago lo mismo para SDL_Image. Una vez que este todo funcionando correctamente, podemos continuar.
Antes de comenzar con el nuevo codigo debemos hacer un par de implementaciones. La primera sera base64 para poder trabajar con esta codificacion. En este post, cuando creamos nuestro mapa mencionamos que iba a utilizar una compresion de base64. Por esta razon, vamos a necesitar por un lado una libreria para poder decodificar este tipo de base, y para ello usaremos la mas estandar para C++ como es la de Rene Nyffenegger, pueden copiarlas desde el siguiente repositorio:
https://github.com/ReneNyffenegger/development_misc/tree/master/base64
De aca deben descargar los achivos base64.h y base64.cpp en el directorio Juego de nuestro proyecto junto a los otros archivos que creamos. Por un tema de facilidad, les dejo un link para descargar estos dos archivos:
Antes de pasar a la otra libreria, en Visual Studio deben hacer click con el boton derecho sobre el proyecto (Juego) y seleccionen Agregar, luego Elemento Existente. En la ventana que nos abrira debemos seleccionar a estos dos nuevos archivos

Presionan Agregar y con esto podremos comenzar a utilizarla. Pasemos a la siguiente libreria, y esta es zlib. Para poder obtenerla debemos ir a la siguiente pagina web:
Aca tendran varias opciones para descargar pero les recomiendo el archivo con formato zip. Una vez extraido el contenido, deben agregar este proyecto en la solucion. Si no saben como hacerlo, les dejo el siguiente post donde lo explico:
Con esta nueva libreria habilitada, ya tenemos la posibilidad de agregarlas a nuestros codigos para poder realizar la decodificacion y descompresion de nuestros mapas, por lo menos para el caso que usamos en este ejemplo.
Con todo esto implementado, nuestro primer paso sera crear una nueva clase con el nombre de ParserNivel. Una vez creada, iremos al archivo de encabezado y agregaremos el siguiente codigo:
ParserNivel.h
#pragma once
#ifndef __ParserNivel__
#define __ParserNivel__
#include "tinyxml.h"
#include "Nivel.h"
#include "CapaPatron.h"
#include "ManejarTexturas.h"
#include "Juego.h"
#include "base64.h"
#include <zlib.h>
class ParserNivel
{
public:
Nivel* parseNivel(const char* archivo);
private:
void parseConjuntos(TiXmlElement*, std::vector<ConjuntoPatron>*);
void parseCapaPatron(TiXmlElement*, std::vector<Capa*>*,
const std::vector<ConjuntoPatron>*);
int m_tamPatron;
int m_ancho;
int m_alto;
};
#endif // !__ParserNivel__
Reincorporamos a una libreria que usamos hace tiempo como es tinyxml, usada para manejar los archivos XML. Tambien tenemos a las clases que vimos anteriormente, y las dos nuevas implementaciones como son base64 y zlib. Con esto comentado, veamos como se compone la nueva clase.
En la parte privada, tenemos dos funciones para manipular a los conjuntos de patrones (parseConjuntos) y otra para las capas de patrones (parseCapaPatron). Tambien tenemos tres variables para las caracteristicas de los patrones. En la parte publica, tenemos una sola funcion que sera la encargada de manipular a los ninveles. Todos son prototipos, por lo que debemos ir al archivo .cpp para comenzar a definirlos con el siguiente codigo:
ParserNivel.cpp
#include "ParserNivel.h"
Nivel* ParserNivel::parseNivel(const char* archivo) {
TiXmlDocument nivelDocumento;
nivelDocumento.LoadFile(archivo);
Nivel* pNivel = new Nivel();
TiXmlElement* pRaiz = nivelDocumento.RootElement();
pRaiz->Attribute("tilewidth", &m_tamPatron);
pRaiz->Attribute("width", &m_ancho);
pRaiz->Attribute("height", &m_alto);
for (TiXmlElement* e = pRaiz->FirstChildElement(); e != NULL;
e = e->NextSiblingElement()) {
if (e->Value() == std::string("tileset")) {
parseConjuntos(e, pNivel->getConjPatron());
}
}
for (TiXmlElement* e = pRaiz->FirstChildElement(); e != NULL;
e = e->NextSiblingElement()) {
if (e->Value() == std::string("layer")) {
parseCapaPatron(e, pNivel->getCapas(), pNivel->getConjPatron());
}
}
return pNivel;
}
void ParserNivel::parseConjuntos(TiXmlElement* pRaizConjuntos,
std::vector<ConjuntoPatron>* pConjuntos) {
Elmanejador::instanciar()->cargar(
pRaizConjuntos->FirstChildElement()->Attribute("source"),
pRaizConjuntos->Attribute("name"),
Eljuego::instanciar()->getRenderer());
ConjuntoPatron conjunto;
pRaizConjuntos->FirstChildElement()->Attribute("width", &conjunto.ancho);
pRaizConjuntos->FirstChildElement()->Attribute("height", &conjunto.alto);
pRaizConjuntos->Attribute("firstgid", &conjunto.primerCuadric);
pRaizConjuntos->Attribute("tilewidth", &conjunto.anchoPatron);
pRaizConjuntos->Attribute("tileheight", &conjunto.altoPatron);
pRaizConjuntos->Attribute("spacing", &conjunto.espaciado);
pRaizConjuntos->Attribute("margin", &conjunto.margen);
conjunto.nombre = pRaizConjuntos->Attribute("name");
conjunto.numColumnas = conjunto.ancho / (conjunto.anchoPatron + conjunto.espaciado);
pConjuntos->push_back(conjunto);
}
void ParserNivel::parseCapaPatron(TiXmlElement* pElemento,
std::vector<Capa*>* pCapas, const std::vector<ConjuntoPatron>* pConjuntos) {
CapaPatron* pCapaPatron = new CapaPatron(m_tamPatron, *pConjuntos);
std::vector<std::vector<int>> datos;
std::string idDecodificados;
TiXmlElement* pNodoDatos=NULL;
for (TiXmlElement* e = pElemento->FirstChildElement(); e != NULL;
e = e->NextSiblingElement()) {
if (e->Value() == std::string("data"))
pNodoDatos = e;
}
for (TiXmlNode* e = pNodoDatos->FirstChild(); e != NULL;
e = e->NextSibling()) {
TiXmlText* texto = e->ToText();
std::string t = texto->Value();
idDecodificados = base64_decode(t);
}
uLongf tamIds = m_ancho * m_alto * sizeof(int);
std::vector<int> ids(m_ancho * m_alto);
uncompress((Bytef*)&ids[0], &tamIds, (const Bytef*)idDecodificados.c_str(),
idDecodificados.size());
std::vector<int> filaCapa(m_ancho);
for (int j = 0; j < m_alto; j++)
datos.push_back(filaCapa);
for (int filas = 0; filas < m_alto; filas++) {
for (int cols = 0; cols < m_ancho; cols++)
datos[filas][cols] = ids[filas * m_ancho + cols];
}
pCapaPatron->setIdPatron(datos);
pCapas->push_back(pCapaPatron);
}
La primera definicion es de la funcion parseNivel. Esta recibe un solo argumento, y es el archivo del nivel. Por esto, nuestra primera accion es crear un objeto para manejar archivos XML. La siguiente es cargar el archivo informado en el objeto anterior. Seguido a esto crearemos dos objetos; el primero sera de tipo Nivel y el segundo para almacenar la raiz de nuestro archivo XML. Despues tomaremos tres atributos del mapa o nivel y los asignaremos a las variables informadas. Luego tenemos dos bucles iguales pero que hacen dos tareas distintas, pero su condicion es la misma. Veamos el primer caso:
for (TiXmlElement* e = pRaiz->FirstChildElement(); e != NULL;
e = e->NextSiblingElement()) {
if (e->Value() == std::string("tileset")) {
parseConjuntos(e, pNivel->getConjPatron());
}
}
Estableceremos como inicio al primer elemento en el archivo informado. Para ello es que usamos a pRaiz y la funcion FirstChildElement. El bucle se repetira mientras la variable sea distinta de NULL y lo terminara cuando se cumpla esto. Y para pasar de un elemento a otro, utilizaremos a NextSiblingElement. Dentro del bloque del bucle tenemos un condicional donde si el valor en el elemento es igual a tileset, llama a parseConjuntos para agregarlo pero sobre esto hablaremos en la proxima funcion.
El siguiente bucle hace lo mismo pero en lugar de buscar a tileset, lo hace con layer. Si lo encuentra procede a llamar a parseCapaPatron. Al igual que en el bucle anterior, hablaremos sobre esta funcion mas adelante. Entonces, esta funcion se encarga de repartir a las capas (layers) y los conjuntos de patrones o imagenes (tileset) en los objetos que usaremos para manipularlos. Para finalmente devolver al objeto que se encarga de representar al nivel. Pasemos a hablar sobre la funcion parseConjuntos.
El primer argumento que recibe son los elementos XML, y el segundo para los conjuntos de patrones que dispongamos. Luego tenemos una vieja conocida, como es la encargada de cargar las texturas. En este caso le pasamos los tres argumentos que necesita. El primero es el archivo de origen, por esta razon le pasamos el atributo source para que se lo informe. El siguiente es el identificador o id del mismo, por esta razon le pasamos name. Y el ultimo, es la funcion encargada de devolver donde se deben renderizar los objetos. Creamos un objeto de tipo ConjuntoPatron para almacenar los distintos datos, veamos esa seccion:
pRaizConjuntos->FirstChildElement()->Attribute("width",&conjunto.ancho);
pRaizConjuntos->FirstChildElement()->Attribute("height", &conjunto.alto);
pRaizConjuntos->Attribute("firstgrid", &conjunto.primerCuadric);
pRaizConjuntos->Attribute("tilewidth", &conjunto.anchoPatron);
pRaizConjuntos->Attribute("tileheight", &conjunto.altoPatron);
pRaizConjuntos->Attribute("spacing", &conjunto.espaciado);
pRaizConjuntos->Attribute("margin", &conjunto.margen);
conjunto.nombre = pRaizConjuntos->Attribute("name");
Son todas muy similares pero no iguales; las primeras dos son para obtener el ancho y alto del primer elemento del conjunto. En estos y los siguientes usamos a Attribute para obtener los datos pero en los dos primeros le agregamos a FirstChildElement por lo descripto anteriormente. Luego iremos obteniendo distintos parametros mediante Attribute pero el unico diferente es el ultimo. En todos los casos anteriores, obtenemos el valor y lo pasamos a la variable correspondiente en el objeto pero en el ultimo caso, se lo asignamos como si fuera una variable comun. La conducta es la misma pero con diferente sintaxis. Luego haremos un pequeño calculo con varios parametros para obtener el numero de columnas y lo asignaremos en la variable correspondiente. Para finalmente, agregar este nuevo conjunto en el vector de conjuntos.
La ultima funcion es la encagada de la capa patron; esta recibira tres tipos de argumentos. El primero sera para manejar los elementos XML, el segundo sera para las capas o elementos en el nivel, y el ultimo para el conjunto de patrones. Lo primero que haremos es crear un objeto de tipo CapaPatron con el tamaño y los conjuntos. Los datos que manejaremos seran en una coleccion bidimensional, la creada como datos, uno de tipo string para almacenar los id que se decodifiquen, sobre esto hablaremos en un minuto, y el ultimo objeto sera para cada elemento XML (pNodoDatos).
Tenemos dos bucles que son iguales en su manera de repetirse a la vista en la primera funcion. Porque pasara por todos los elementos XML. Si bien, su condicion de repeticion es la misma no hacen la misma tarea. En el primer bucle, almacenaremos cada elemento data en pNodoDatos. El segundo bucle almacenara cada elemento en una variable para almacenar texto de tipo XML. Para ello, usamos a la funcion text desde cada elemento obtenido sin importar el tipo de elemento. Luego creamos una variable de tipo string donde almacenaremos el valor de la variable anterior. Para finalmente, decodificar este texto (de esto se encarga base64.h) y almacenar cada elemento decodificado en idDecodificados.
Ya obtuvimos el valor sin codificar pero aun esta comprimido, para resolverlo se utilizan las siguientes tres lineas:
uLongf tamIds = m_ancho * m_alto * sizeof(int);
std::vector<int> ids(m_ancho * m_alto);
uncompress((Bytef*)&ids[0],&tamIds,(const Bytef*)idDecodificados.c_str(),
idDecodificados.size());
El tipo uLongf es uno definido por la libreria zlib y este lo usaremos para crear una variable que almacena el tamaño del buffer de destino. El segundo es el vector donde almacenaremos todos los elementos codificados, y el tamaño es el valor calculado anteriormente, y finalmente descomprimirlo. Para esta funcion, utlizamos otro tipo definido en zlib.h como es Bytef. El primer parametro sera donde almacenaremos todo lo decodificado, el segundo establece el tamaño de almacenamiento, y el ultimo es el origen de la informacion.
Luego creamos un vector que representa cada fila de capas que dispongamos, y cada una tendra el ancho que establecimos en m_width. Despues tenemos un bucle que ingresara por cada fila el vector anterior, y su limite sera la altura de nuestro mapa. Seguido a esto tenemos otro bucle donde ahora si almacenara correctamente cada ID decodificado. Este tuvo que ser bidimensional porque la ubicacion de cada uno debe ser entre una fila y una columna. Por lo tanto, en cada posicion usaremos a filas y cols para establecer cada dimesion y por eso cada uno tiene su bucle. Cuando finalice el de las columnas pasara recien a la siguiente fila, asi hasta que finalicen ambos. Para finalmente, establecer el id de cada patron y pasar las capas al encargado de almacenarlos.
Solo nos resta una modificacion mas, para ello debemos volver a Nivel.h y agregar lo siguiente en la parte privada:
friend class ParserNivel;
Nivel();
La primer linea permitira que la clase definida anteriormente pueda acceder a la parte privada de esta clase, y pasamos su constructor a la parte privada. Recuerden eliminar esta declaracion de la parte publica. Nuevamente, tenemos toda la base para manipular nuestros niveles pero todavia nos falta como mostrar a los mismos pero esto sera tema para el proximo post.
En resumen, hoy hemos visto a la clase ParserNivel, que es, para que nos servira, hemos comentado todo lo necesario para poder usarla y como esta nos permitira manipular a los niveles. Espero les haya sido de utilidad y les dejo un link a GitHub donde estan los codigos creados hoy:
Les dejo algunas de mis redes sociales para seguirme o recibir una notificacion cada vez que subo un nuevo post:


Donación
Es para mantenimento del sitio, gracias!
$1.50





