Bienvenidos sean a este post, hoy veremos como agregar un objeto al nivel.
Hasta el post anterior, hemos creado el nivel o escenario para nuestro juego pero vacio. Hoy nos centraremos en como agregar a los elementos que teniamos antes. Estos los agregaremos en nuestro nivel. Por lo tanto, debemos volver a trabajar con Tiled para modificar al mapa.
Nota:
En este post, explicamos como instalar a Tiled.
Tambien necesitaremos el mapa que moidificamos en el post anterior, sino lo poseen les dejo un link para descargarlo:
Extraigan todo el contenido, y ya esta listo para ser utilizado con Tiled. Para comenzar a trabajar pueden abrir a mapa01.tmx o Juego.tiled-project con Tiled. Con nuestro proyecto abierto, lo primero que haremos es ir a Capa, luego a Nuevo y finalmente seleccionaremos Capa de Objetos. Esto nos agregara este nuevo elemento

El nombre es generado automaticamente, les sugiero dejarlo porque no tendra influencia en nuestro proyecto. Pero en un proyecto propio, es recomendable cambiarlo al que representa para una mejor identificacion. Lo siguiente es ir al nivel, y en ella elijan algun cuadrado y presionen la tecla R para que agregue un cuadrado de la siguiente manera

Sobre este cuadro pulsen con el boton derecho y seleccionen Atributos del objeto. Esto nos abrira del lado izquierdo los atributos los cuales modificaremos de la siguiente manera

Salvo los valores de X e Y, porque seran los del cuadrado que seleccionaron, el resto deben asignar los mismos que se ven en la imagen. Nuestro siguiente paso sera agregar los atributos de la imagen. Para ello, deben presionar el boton de mas y nos abrira la siguiente ventana

Deben elegir el tipo y el nombre identificador. Esto mismo lo debemos repetir pero para agregar los campos de altoTextura y anchoTextura. Y el ultimo argumento que agregaremos es este

Es lo mismo, pero ahora le cambiamos el tipo de dato que almacenara. Con esto realizado, a estos atributos les asignaremos los siguientes valores

Con estos valores establecidos, solo nos resta una modificacion mas. Para ello, deben ir a Mapa, y luego Atributos del Mapa. Nuevamente, en la parte izquierda tendran los atributos pero ahora del mapa. Abajo de todo tendran el boton de mas nuevamente y si lo presionan, saldra nuevamente esta ventana

Agregamos una nueva propiedad para poder representar al jugador. En este nuevo atributo le asignaremos el valor de assets/helicoptero.png para que sepa de donde debe tomarlo, tal como se ve en la siguiente imagen:

Con esto ya tenemos todas las modificaciones necesarias para poder implementar a nuestro objeto en el mapa. Solo nos resta copiar a mapa01.tmx en la carpeta assets de nuestro proyecto. Una vez copiado, verifiquen que las siguientes lineas se vean de esta forma:
<image source="assets/blocks1.png" width="614" height="376"/>
...
<image source="assets/blocks2.png" width="614" height="376"/>
Porque de lo contrario no podra encontrar los archivos que contienen las imagenes para generar nuestro nivel. Con todo esto realizado, ahora si podemos pasar a trabajar con nuestro codigo.
Vayamos a nuestro proyecto Juego 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.
Nuestro primer paso sera crear una nueva clase llamada CapaObjeto, despues de creada iremos al archivo encabezado y agregaremos el siguiente codigo:
CapaObjeto.h
#pragma once
#ifndef __CapaObjeto__
#define __CapaObjeto__
#include "Capa.h"
#include <vector>
#include "ObjetoJuego.h"
class CapaObjeto : public Capa
{
public:
virtual void actualizar();
virtual void renderizar();
std::vector<ObjetoJuego*>* getObjetosJuego();
private:
std::vector<ObjetoJuego*> m_objetosJuego;
};
#endif // !__CapaObjeto__
Esta sera heredera de Capa pero orientada a representar los objetos o elementos que podemos tener en nuestro juego, tales como son el jugador y el enemigo. Por esta razon, vamos a definir sus dos funciones abstractas y tenemos otra para obtener el valor de la variable en la parte privada. Hablando sobre esta, sera la encargada de almacenar todos los objetos del juego. Paasemos a definir estas funciones, y para ello debenn agregar el siguiente codigo en el archivo .cpp:
CapaObjeto.cpp
#include "CapaObjeto.h"
std::vector<ObjetoJuego*>* CapaObjeto::getObjetosJuego() {
return &m_objetosJuego;
}
void CapaObjeto::actualizar() {
for (int i = 0; i < m_objetosJuego.size(); i++) {
m_objetosJuego[i]->actualizar();
}
}
void CapaObjeto::renderizar() {
for (int i = 0; i < m_objetosJuego.size(); i++) {
m_objetosJuego[i]->dibujar();
}
}
La primera definicion es de la funcion encargada de devolver lo almacenado en m_objetosJuego, tal como mencionamos antes, y las otras dos son similares pero cada una para su propia funcion. En ambos casos mediante un bucle pasaremos por todos los objetos almacenados en m_objetosJuego. La unica diferencia es el llamado a la funcion relacionada a estas. En el caso de actualizar, llama a actualizar del objeto para tomar cualquier cambio y la otra llama a dibujar para mostrarla en pantalla.
Esta nueva clase solo se encarga de manipular lo necesario para representar a cada objeto pero la verdadera magia ocurre en otro lado. Para ello, debemos ir a ParserNivel.h y al inicio agregaremos esta linea:
#include "CapaObjeto.h"
Es para poder crear los nuevos objetos desde el archivo de mapa. Lo siguiente es ir a la parte privada y agregaremos esta lineas:
void parseTexturas(TiXmlElement*);
void parseCapaObjeto(TiXmlElement*, std::vector<Capa*>*);
La primer funcion se encargara de manejar las texturas del objeto y la segunda para los objetos en el nivel. Antes de definir a estas nuevas funciones, debemos modificar a parseNivel de la siguiente manera:
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);
TiXmlElement* pPropiedades = pRaiz->FirstChildElement();
for (TiXmlElement* e = pPropiedades->FirstChildElement(); e != NULL;
e = e->NextSiblingElement()) {
if (e->Value() == std::string("property"))
parseTexturas(e);
}
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("objectgroup") ||
e->Value() == std::string("layer")) {
if (e->FirstChildElement()->Value() == std::string("object"))
{
parseCapaObjeto(e, pNivel->getCapas());
}
else if (e->FirstChildElement()->Value() == std::string("data")) {
parseCapaPatron(e, pNivel->getCapas(), pNivel->getConjPatron());
}
}
}
return pNivel;
}
Vamos a hablar sobre las modificaciones realizadas. La primera es la definicion de pPropiedades para almacenar el primer elemento de nuestra raiz del archivo. Con este definido, vamos a usar un bucle para revisar los elementos almacenados en la variable anterior. En este bucle, tenemos un condicional donde solo verifica por los elementos denominados properties. Si lo encuentra, manda ese elemento a la funcion parseTexturas (la cual definiremos en un momento). La siguiente modificacion fue en el bucle que se encargaba de buscar los elementos layer y mostrarlos. En este caso, le agregamos la posibilidad de que evalue a objectgroup tambien. Esto es asi porque es el nuevo elemento conteniendo los nuevos objetos. Si detecta a alguno de los dos, utiliza otro condicional donde evalua si el nuevo elemento es un object o data. Si es el primer caso, lo pasa a la nueva funcion que agregamos pero aun no definimos. En caso contrario, utiliza el procediimiento que tenia antes para poder armar el nivel en base a la capa de patrones que recibe. Pasemos a definir la primera de las nuevas funciones que agregamos anteriormente:
void ParserNivel::parseTexturas(TiXmlElement* pRaizTextura) {
Elmanejador::instanciar()->cargar(pRaizTextura->Attribute("value"),
pRaizTextura->Attribute("name"),
Eljuego::instanciar()->getRenderer());
}
Esta se encarga de obtener todos los datos desde el archivo para pasarlos a la funcion cargar de ManejarTexturas. Para los dos primeros argumentos, usamos a Attribute en el argumento recibido y usamos a getRenderer para obtener donde renderizarlo.
void ParserNivel::parseCapaObjeto(TiXmlElement* pElementoObj,
std::vector<Capa*>* pCapas) {
CapaObjeto* pCapaObjeto = new CapaObjeto();
OutputDebugStringA(pElementoObj->FirstChildElement()->Value());
for (TiXmlElement* e = pElementoObj->FirstChildElement(); e != NULL;
e=e->NextSiblingElement()) {
OutputDebugStringA(e->Value());
if (e->Value() == std::string("object")) {
int x, y, ancho, alto, numFrames, idCallback = 0, velocAnim = 0;
std::string idTextura;
e->Attribute("x", &x);
e->Attribute("y", &y);
ObjetoJuego* pObjetoJuego = Elfabricante::instanciar()->
crear(e->Attribute("type"));
for (TiXmlElement* propiedades = e->FirstChildElement();
propiedades != NULL;
propiedades = propiedades->NextSiblingElement()) {
if (propiedades->Value() == std::string("properties")) {
for(TiXmlElement* propiedad=propiedades->FirstChildElement();
propiedad!=NULL;
propiedad=propiedad->NextSiblingElement()) {
if (propiedad->Value() == std::string("property")) {
if (propiedad->Attribute("name") ==
std::string("numFrames")) {
propiedad->Attribute("value", &numFrames);
}
else if (propiedad->Attribute("name") ==
std::string("altoTextura")) {
propiedad->Attribute("value", &alto);
}
else if (propiedad->Attribute("name") ==
std::string("anchoTextura")) {
propiedad->Attribute("value", &ancho);
}
else if (propiedad->Attribute("name") ==
std::string("idTextura")) {
idTextura = propiedad->Attribute("value");
}
else if (propiedad->Attribute("name") ==
std::string("idCallback")) {
propiedad->Attribute("value", &idCallback);
}
else if (propiedad->Attribute("name") ==
std::string("velocAnim")) {
propiedad->Attribute("value", &velocAnim);
}
}
}
}
}
pObjetoJuego->cargar(new CargadorParams(x,y,ancho,alto,idTextura,
numFrames,idCallback,velocAnim));
pCapaObjeto->getObjetosJuego()->push_back(pObjetoJuego);
}
}
pCapas->push_back(pCapaObjeto);
}
Primero crearemos a la variable que almacenara al elemento dentro del nivel. Vamos a tener varios bucles pero el primero sera para pasar todos los objetos. Si este es un object, vamos a trabajar sobre este. En este condicional, lo primero que haremos es declarar las variables donde almacenaremos los datos del objeto pero con una curiosidad. En este caso, estableceremos un valor para idCallback y velocAnim porque son las unicas propiedades que no obtendremos y nos generara un error mas adelante. Seguido a esto, asignaremos los valores de los atributos X e Y para estas variables. Para luego crear un objeto del juego, y le diremos que es Jugador porque es el valor del atributo type.
Volvemos a usar un bucle para pasar por todos los elementos nuevamente pero dentro del objeto. Chequeamos nuevamente pero ahora si el valor es properties. En caso de ser verdadero, volvemos a usar otro bucle para pasar por todos los elementos dentro de la anterior. Volvemos a usar un condicional pero ahora para verificar si tiene el valor de property. Y nuevamente, si es verdadero usaremos una serie de condicionales para verificar distintos datos. En caso de coincidir, procede a asignarlo a la variable correspondiente. Son todas de manera similar, salvo para el id de textura que se asigna de otra manera. Con todos los valores asignados lo pasaremos a CargadorParams. Si observan, como mencionamos anteriormente, si no le asignamos el valor de 0 a idCallback y velocAnim no tendran ninguno asignado y al momento de usarlo en este llamado nos devolvera un error. Con este objeto creado, lo agregamos a m_objetosJuego y luego agregamos esto al nivel en si.
Solo nos resta una modificacion mas, para este caso debemos volver a CapaPatron.cpp y modificaremos a la funcion actualizar de la siguiente manera:
void CapaPatron::actualizar() {
m_posicion += m_velocidad;
}
Esto evitara que nuestro nivel se «scrollee» automaticamente, dado que ya demostramos el punto en el post anterior. Con todo esto realizado, compilemos y veamos como trabaja ahora mediante el siguiente video
Como pueden ver, no solamente activamos al Jugador sino que tambien mantiene la posibilidad de ser controlado por nuestro mouse, asi como tambien los otros estados de nuestro juego.
En resumen, hoy hemos visto como agregar un objeto al nivel, primero mediante el programa Tiled, luego creamos las clases necesarias para controlar este nuevo objeto y habilitarlo para poder ser utilizado. Espero les haya sido de utilidad y les dejo un link a GitHub donde estan los codigos creados hoy:
Y agreguemos un objeto / GitHub
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





