Bienvenidos sean a este post, hoy veremos una situacion y uno de los estados faltantes.
En el post anterior, modificamos a la clase Enemigo y como mencionamos anteriormente, veremos dos temas. Pasemos a hablar sobre el primero como son las colisiones, y para ello necesitaremos el codigo que estuvimos trabajando hasta ahora. 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 las colisiones vamos a realizar una modificacion que nos servira especialmente para el Game Over. Para iniciar, iremos a nuestra clase CargadorParams donde agregaremos la siguiente linea dentro de la parte privada de CargadorParams.h:
int m_numFrames;
Nuestro siguiente paso sera modificar el constructor de la siguiente forma:
CargadorParams(float, float, float, float, std::string, int = 6);
Solo agregamos un nuevo parametro de tipo int con un valor inicial y ya veremos para quien es para cuando la definamos. En esta misma seccion, agregaremos el siguiente prototipo:
int getNumFrames() const;
Nuestro siguiente paso sera definir nuevamente al constructor y a la nueva funcion. Para ello, iremos a CargadorParams.cpp y modificaremos al constructor de la siguiente manera:
CargadorParams::CargadorParams(float x, float y,
float ancho, float alto, std::string id, int numFrames):
m_x(x), m_y(y), m_ancho(ancho), m_alto(alto),
m_idTextura(id),m_numFrames(numFrames) {}
Como vimos anteriormente, solo agregamos un nuevo paramentro. Este sera para iniciar a la nueva variable que agregamos en la parte privada (m_numFrames). Teniendo como particularidad que si no es informado tomara el valor predeterminado. Pasemos a definir la nueva funcion que agregamos:
int CargadorParams::getNumFrames() const { return m_numFrames; }
Esta la usaremos para obtener el valor actual de m_numFrames, con esto tenemos las nuevas modificaciones establecidas dentro de la clase CargadorParams.
Antes de comenzar con las colisiones haremos una modificacion mas. Para ello iremos a la clase SDLObjetoJuego, y en la parte publica del archivo de encabezado, agregaremos los siguientes prototipos:
Vector2D& getPosicion();
float getAncho();
float getAlto();
Con estas agregadas, lo siguiente es ir a SDLObjetoJuego.cpp, donde definiremos las funciones anteriores:
Vector2D& SDLObjetoJuego::getPosicion() { return m_posicion; }
float SDLObjetoJuego::getAncho() { return m_ancho; }
float SDLObjetoJuego::getAlto() { return m_alto; }
Estas tres funciones seran para obtener tres valores de esta clase, la primera sera para obtener los valores de la posicion actual del objeto, la siguiente el ancho y la ultima el alto. Estos tres datos nos seran utiles para las colisiones y pasemos a trabajar con ellos.
Las colisiones son como su nombre lo indica, el encuentro entre dos o mas elementos. Este sera usado para crear el ultimo estado de nuestro juego. Para ello, iremos a la clase EstadoJugar y en la parte publica agregaremos el siguiente prototipo:
bool chekaColision(SDLObjetoJuego*, SDLObjetoJuego*);
Nuestro siguiente paso sera definir el prototipo en EstadoJugar.cpp agregando el siguiente codigo:
bool EstadoJugar::chekaColision(SDLObjetoJuego* p1, SDLObjetoJuego* p2) {
float leftA, leftB;
float rightA, rightB;
float topA, topB;
float bottomA, bottomB;
leftA = p1->getPosicion().getX();
rightA = p1->getPosicion().getX() + p1->getAncho();
topA = p1->getPosicion().getY();
bottomA = p1->getPosicion().getY()+p1->getAlto();
leftB = p2->getPosicion().getX();
rightB = p2->getPosicion().getX() + p2->getAncho();
topB = p2->getPosicion().getY();
bottomB = p2->getPosicion().getY() + p2->getAlto();
if (bottomA <= topB) return false;
if (topA >= bottomB) return false;
if (rightA <= leftB) return false;
if (leftA >= rightB) return false;
return true;
}
Si hicieron algun otro curso sobre programacion de videojuegos en mi blog sabran que para trabajar con colisiones, por lo general se utiliza un rectangulo o rect para determinar el area del objeto y estos estan compuestos de cuatro referencias:
- left, para indicar cual es el primer punto a la izquierda del rectangulo con respecto a la ventana
- top, para indica cual es el primer punto arriba del rectangulo con respecto a la ventana
- right, indica el punto opuesto y maximo con respecto a left del rectangulo
- bottom, indica el punto opuesto y maximo con respecto a top del rectangulo
Con estos cuatro datos establecemos el rectangulo que contendra al objeto, en la mayoria de los casos los de referencia seran left y top a los cuales establecemos un valor y despues right y bottom seran el incremento de left con el ancho y top con el alto respectivamente. Con esto aclarado volvamos al codigo, y en este caso vamos a declarar los cuatro parametros pero para dos rectangulos. Por esta razon les agregamos A y B para diferenciarlos (leftA y leftB). Si observan en los parametros tenemos dos atributos que seran para el jugador (p1) y para el enemigo (p2). Volviendo al codigo, cuando definimos a las variables lo haremos mediante los objetos informados al llamar a la funcion, tomemos el primer caso:
leftA = p1->getPosicion().getX();
rightA = p1->getPosicion().getX() + p1->getAncho();
topA = p1->getPosicion().getY();
bottomA = p1->getPosicion().getY()+p1->getAlto();
En este caso usamos a p1 donde mediante getPosicion obtendremos el valor almacenado en p1 y despues con getX sacaremos el valor de la posicion del eje X almacenado en la variable de la posicion. Con esto tenemos el valor que sera para left, el siguiente es para right y en este caso tenemos lo mismo pero le sumaremos el ancho del objeto lo cual establecera el otro punto sobre el eje X. El siguiente caso es el de top y hacemos lo mismo que con left pero en lugar de obtener el valor del eje X buscamos el valor del eje Y, por ultimo volvemos a buscar el valor del eje Y y le sumamos el alto del objeto para finalmente asignar el valor a bottom. Con estos cuatro datos creamos el primer rectangulo, el siguiente bloque hace exactamente lo mismo pero con el objeto p2 (jajaja p2). Con esto ya tenemos los dos rectangulos creados, pasemos al siguiente bloque:
if (bottomA <= topB) return false;
if (topA >= bottomB) return false;
if (rightA <= leftB) return false;
if (leftA >= rightB) return false;
Basicamente, lo que hacemos con estos cuatro condicionales es verificar si cualquiera de los lados del rectangulo A estan por fuera del rectangulo B, y en cualquiera de los casos devolvera un false indicando que no existe colision. En cambio, si no se cumplio ninguna de estas condiciones seguira con la siguiente instruccion y en este caso devuelve un true por lo cual hubo una colision en alguna parte. Por ultimo, en esta misma clase iremos a la funcion actualizar y agregaremos el siguiente bloque:
if (chekaColision(dynamic_cast<SDLObjetoJuego*>(m_objetosJuego[0]),
dynamic_cast<SDLObjetoJuego*>(m_objetosJuego[1]))) {
OutputDebugStringA("Oh, no! Hemos colisionado!\n");
}
Este condicional utiliza a la funcion chekaColision para verificar la colision pero para pasar los dos objetos utilizamos a dynamic_cast. Esto permitira castear o convertir los objetos de tipo ObjetoJuego* en objetos de tipo SDLObjetoJuego* sin ninguna dificultad; y en caso de que esto sea verdad mostrara un mensaje en la salida de depuracion. Si lo probamos despues de pulsar Jugar y hacen chocar los helicopteros debera aparecer el mensaje en la ventana de depuracion como se ve a continuacion

En esta imagen podemos ver como se logro hacer funcionar las colisiones, con esto realizado nuestro siguiente paso sera el estado de Game Over!
Primero debemos descargar el siguiente archivo que contienen las imagenes que usaremos para nuestro nuevo estado:
Descarguen el archivo, extraigan los dos archivos en la carpeta assets de nuestro proyecto y una vez realizado todo esto volvamos a nuestro codigo. Nuestro siguiente paso sera crear una nueva clase, la cual llamaremos GraficosAnimados, una vez creada iremos al archivo de encabezado y agregaremos el siguiente codigo:
GraficosAnimados.h
#pragma once
#ifndef __GraficosAnimados__
#define __GraficosAnimados__
#include <SDL3/SDL.h>
#include "CargadorParams.h"
#include "SDLObjetoJuego.h"
class GraficosAnimados : public SDLObjetoJuego
{
public:
GraficosAnimados(const CargadorParams*, int);
void actualizar();
private:
int m_velocAnim;
int m_numFrames;
};
#endif // !__GraficosAnimados__
Esta sera una clase simple donde tendremos un prototipo de constructor que recibira los parametros de tipo CargadorParams y un parametro adicional para la velocidad de animacion. Lo siguiente es un prototipo de funcion para actualizar, y por ultimo en la seccion privada tenemos dos variable que usaremos para la velocidad de animacion y establecer el numero de frames respectivamente. Con esto establecido agreguemos el siguiente codigo en el archivo .cpp:
GraficosAnimados.cpp
#include "GraficosAnimados.h"
GraficosAnimados::GraficosAnimados(const CargadorParams* pParams,
int velocAnim): SDLObjetoJuego(pParams),m_velocAnim(velocAnim){
m_numFrames = pParams->getNumFrames();
}
void GraficosAnimados::actualizar() {
m_frameActual = int(((SDL_GetTicks() / (1000 / m_velocAnim)) % m_numFrames));
}
Basicamente, lo que hicimos fue definir a nuestras dos funciones y para ello en el constructor hemos recibido los parametros, iniciamos el constructor de SDLObjetoJuego y a m_velocAnim. Dentro del bloque, establecemos el valor de m_numFrames por medio de getNumFrames, en la funcion actualizar establecemos el valor de m_frameActual mediante la formula de siempre pero esta vez en lugar de dividirlo por 100 usaremos a la division de 1000 por el valor de m_velocAnim y el modulo en lugar de hacerlo con el valor 6 lo haremos con el valor en m_numFrames. Con esto establecimos la nueva clase que por el momento no tiene ninguna utilidad pero ya veremos para que, pasemos a la siguiente modificacion.
Nuestra siguiente modificacion sera la creacion de la clase encargada de manejar el estado de Game Over!. Para ello debemos crear una nueva clase y la llamaremos EstadoFinal, una vez creado iremos a nuestro archivo de encabezado y agregaremos el siguiente codigo:
EstadoFinal.h
#pragma once
#ifndef __EstadoFinal__
#define __EstadoFinal__
#include <string>
#include <vector>
#include "EstadoJuego.h"
#include "ObjetoJuego.h"
#include "Juego.h"
#include "BotonMenu.h"
#include "GraficosAnimados.h"
class EstadoFinal : public EstadoJuego
{
public:
virtual void actualizar();
virtual void renderizar();
virtual bool enIngreso();
virtual bool enSalida();
virtual std::string getIdEstado() const;
private:
static void e_volverMenu();
static void e_reiniciar();
static const std::string e_idfinal;
std::vector<ObjetoJuego*> m_objetosJuego;
};
#endif // !__EstadoFinal__
Este archivo es similar a los vistos hasta ahora donde tenemos los prototipos de las funciones que heredamos. Tenemos los prototipos de las que usaremos para las llamadas callbacks y sus respectivas variables, pasemos al archivo .cpp al cual le agregaremos el siguiente codigo:
EstadoFinal.cpp
#include "EstadoFinal.h"
#include <Windows.h>
const std::string EstadoFinal::e_idfinal = "GAMEOVER";
std::string EstadoFinal::getIdEstado() const {
return e_idfinal;
}
void EstadoFinal::e_volverMenu() {
Eljuego::instanciar()->getEstadoMaquina()->
cambiaEstado(new EstadoMenu());
}
void EstadoFinal::e_reiniciar() {
Eljuego::instanciar()->getEstadoMaquina()->
cambiaEstado(new EstadoJugar());
}
void EstadoFinal::actualizar() {
for (int i = 0; i < m_objetosJuego.size(); i++)
m_objetosJuego[i]->actualizar();
}
void EstadoFinal::renderizar() {
for (int i = 0; i < m_objetosJuego.size(); i++)
m_objetosJuego[i]->dibujar();
}
bool EstadoFinal::enIngreso() {
if (!Elmanejador::instanciar()->cargar("assets/over.png",
"gameover", Eljuego::instanciar()->getRenderer()))
return false;
if (!Elmanejador::instanciar()->cargar("assets/volver.png",
"volvermenu", Eljuego::instanciar()->getRenderer()))
return false;
if (!Elmanejador::instanciar()->cargar("assets/reiniciar.png",
"reiniciar", Eljuego::instanciar()->getRenderer()))
return false;
ObjetoJuego* textoGameOver = new GraficosAnimados(new CargadorParams(
200, 100, 190, 30, "gameover", 2), 2);
ObjetoJuego* boton1 = new BotonMenu(new CargadorParams(
100, 170, 400, 100, "volvermenu"), e_volverMenu);
ObjetoJuego* boton2 = new BotonMenu(new CargadorParams(
100, 300, 400, 100, "reiniciar"), e_reiniciar);
m_objetosJuego.push_back(textoGameOver);
m_objetosJuego.push_back(boton1);
m_objetosJuego.push_back(boton2);
OutputDebugStringA("Entrando al estado Game Over\n");
return true;
}
bool EstadoFinal::enSalida() {
for (int i = 0; i < m_objetosJuego.size(); i++)
m_objetosJuego[i]->limpiar();
m_objetosJuego.clear();
Elmanejador::instanciar()->limpiaMapaTexturas("gameover");
Elmanejador::instanciar()->limpiaMapaTexturas("volvermenu");
Elmanejador::instanciar()->limpiaMapaTexturas("reiniciar");
OutputDebugStringA("Saliendo del Estado de Game Over\n");
return true;
}
Este codigo es muy similar a los anteriores donde definiremos las funciones para ser llamadas mediante los punteros de funciones, siendo e_volverMenu la encargada de enviarnos al menu principal y e_reiniciar se encargara de volver a ejecutar el juego o nuestro estado Jugar. Luego tenemos las dos funciones para actualizar y renderizar que trabajan de la misma manera que vinimos viendo hasta ahora, llamar a cada funcion del objeto contenido, para despues tener la funcion enIngreso donde primero chequearemos que se carguen correctamente los tres assets que usaremos (el de Game Over, volver al menu ppal y reiniciar respectivamente), y si alguna falla nos sacara de la funcion. En caso contrario, cargaremos las tres imagenes y luego crearemos los tres objetos. Veamos el unico disitinto de los tres objetos:
ObjetoJuego* textoGameOver = new GraficosAnimados(new CargadorParams(
200, 100, 190, 30, "gameover", 2), 2);
Creamos un ObjetoJuego pero en lugar de usar a BotonMenu, sera con GraficosAnimados y luego pasaremos todos los valores que vimos al principio del post. Los otros dos objetos son iguales a todos los menus que vimos anterioremente. Agregaremos los tres elementos a m_objetosJuego y mostraremos el mensaje en depuracion. La funcion enSalida es exactamente lo mismo a lo que vimos anteriormente en otros posts donde eliminaremos de memoria a los distintos elementos. Con esto tenemos creado nuestra clase para manejar el estado de Game Over. Lo siguiente sera ir a EstadoJugar.h y agregaremos esta linea:
#include "EstadoFinal.h"
Esta sera para poder incluir a la clase que definimos anteriormente. Con esto realizado, vayamos a EstadoJugar.cpp, y en la funcion actualizar modificaremos el siguiente condicional:
if (chekaColision(dynamic_cast<SDLObjetoJuego*>(m_objetosJuego[0]),
dynamic_cast<SDLObjetoJuego*>(m_objetosJuego[1]))) {
OutputDebugStringA("Oh, no! Hemos colisionado!\n");
}
De la siguiente manera:
if (chekaColision(dynamic_cast<SDLObjetoJuego*>(m_objetosJuego[0]),
dynamic_cast<SDLObjetoJuego*>(m_objetosJuego[1]))) {
Eljuego::instanciar()->getEstadoMaquina()->
pushEstado(new EstadoFinal());
}
En este caso quitamos la notificacion en la salida de depuracion por el llamado a un nuevo estado que sera el relacionado al Game Over. Con esto podemos compilar nuestro juego para probar su funcionamiento, veamos el siguiente video
Con esto tenemos un proyecto muy completo donde aplicamos los distintos estados que puede tomar el mismo, ya sea el inicio, el juego en si, la pausa del mismo y por ultimo uno de finalizacion del estado de juego ya sea para volver a jugar o volver al menu principal.
En resumen, hoy hemos terminado el ultimo estado de los basicos que podemos tomar durante un juego, si bien esto no es exactamente como debemos manejarlo si nos servira como base para cuando lleguemos a los ejemplos verdaderos, hemos visto como crear tambien una clase para graficos animados para un menu, tambien hemos creado un juego muy simple pero que tecnicamente es un juego, todavia no hemos llegado al final y esto se va a poner mas jugoso. Espero les haya sido de utilidad y les dejo un link a GitHub donde estan los codigos creados hoy:
Colisiones y Game over / 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





