Bienvenidos sean a este post, hoy repasaremos lo visto hasta ahora.
Como mencionamos, en este post veremos el codigo que estuvimos trabajando hasta ahora para crear nuestro primer proyecto y en los proximos posts segiremos mejorando con mas complementos.
Primero vamos a crear nuestro proyecto con las siguientes caracteristicas:
- Tipo de Lenguaje: C++
- Plataforma: Windows
- Tipo de Proyecto: Consola
- Plantilla: Proyecto vacío
- Nombre del proyecto: Juego
- Ubicacion: Dejan la predeterminada
- Solucion: Crear nueva solucion
- Nombre de la solucion: Juego
Una vez creado el proyecto debemos habilitar al SDL y SDL_Image. Si no poseen SDL deben ir a este post para instalar a SDL, y a este post para poder agregarlo a nuestro proyecto. Para SDL_Image deben seguir todos los pasos de este post para instalarlo y configurarlo. Con todo eso realizado, pasemos a crear primero un archivo de tipo .cpp al cual llamaremos main.cpp, para ello sobre el proyecto Juego deben hacer click con el boton derecho, seleccionar Agrega y luego Nuevo elemento. Luego le agregaremos este codigo:
main.cpp
#include "Juego.h"
#include <iostream>
int main(int argc, char* argv[]) {
std::cout << "Iniciando el juego..." << std::endl;
if (Eljuego::instanciar()->iniciar("Capitulo 1", 640, 480, false)) {
std::cout << "Inicio Exitoso" << std::endl;
while (Eljuego::instanciar()->corriendo()) {
Eljuego::instanciar()->manejaEventos();
Eljuego::instanciar()->actualizar();
Eljuego::instanciar()->renderizar();
SDL_Delay(10);
}
}
else {
std::cout << "Fallo el inicio..." << std::endl;
std::cout << SDL_GetError() << std::endl;
return 1;
}
std::cout << "Cerrando el juego..." << std::endl;
Eljuego::instanciar()->limpiar();
return 0;
}
Este es el corazon de nuestro programa y sera el encargado de manejar a todo el resto. Lo siguiente es crear una nueva clase con el nombre ObjetoJuego, el codigo de nuestro archivo de encabezado es el siguiente:
ObjetoJuego.h
#pragma once
#ifndef __ObjetoJuego__
#define __ObjetoJuego__
#include <SDL3/SDL.h>
#include <string>
#include "ManejarTexturas.h"
#include "CargadorParams.h"
class ObjetoJuego
{
public:
virtual void dibujar() = 0;
virtual void actualizar() = 0;
virtual void limpiar() = 0;
protected:
ObjetoJuego(const CargadorParams*);
virtual ~ObjetoJuego();
};
#endif //! defined(__ObjetoJuego__)
Esta sera nuestra clase base y abstracta para utilizar por el resto de las clases que se encarguen de manejar o representar los objetos. Pasemos a ObjetoJuego.cpp y agreguemos el siguiente codigo:
ObjetoJuego.cpp
#include "ObjetoJuego.h"
ObjetoJuego::ObjetoJuego(const CargadorParams* pParams) {}
ObjetoJuego::~ObjetoJuego() {}
A ObjetoJuego.cpp lo podriamos dejar en blanco, porque al tener funciones abstractas no se pueden definir directamente sino solamente a traves de sus herederos pero como tenemos al prototipo del constructor y destructor en el archivo de encabezado; por ende, debemos definirlos. Se podrian haber definido en el archivo de encabezado? Si, pero por un tema de orden y respetar el criterio que usamos para el resto lo hice asi. Con esto comentado, lo siguiente sera crear una nueva clase con el nombre de SDLObjetoJuego, una vez creada definiremos al archivo de encabezado:
SDLObjetoJuego.h
#pragma once
#ifndef __SDLObjetoJuego__
#define __SDLObjetoJuego__
#include <string>
#include "ObjetoJuego.h"
#include "CargadorParams.h"
#include "ManejarTexturas.h"
class SDLObjetoJuego : public ObjetoJuego
{
public:
SDLObjetoJuego(const CargadorParams*);
virtual void dibujar();
virtual void actualizar();
virtual void limpiar();
protected:
float m_x;
float m_y;
float m_ancho;
float m_alto;
int m_filaActual;
int m_frameActual;
std::string m_idTextura;
};
#endif // !__SDLObjetoJuego__
Esta es la clase que se encargara de controlar a los objetos en el «juego», pero solo son los prototipos. Lo siguiente sera completar el codigo de SDLObjetoJuego.cpp para definir las anteriores:
SDLObjetoJuego.cpp
#include "SDLObjetoJuego.h"
#include "Juego.h"
SDLObjetoJuego::SDLObjetoJuego(const CargadorParams* pParams) :
ObjetoJuego(pParams) {
m_x = pParams->getX();
m_y = pParams->getY();
m_ancho = pParams->getAncho();
m_alto = pParams->getAlto();
m_idTextura = pParams->getIdTextura();
m_frameActual = 1;
m_filaActual = 1;
}
void SDLObjetoJuego::dibujar() {
ManejarTexturas::instanciar()->dibujar_frame(m_idTextura,
m_x, m_y, m_ancho, m_alto, m_filaActual, m_frameActual,
Eljuego::instanciar()->getRenderer());
}
void SDLObjetoJuego::actualizar() {}
void SDLObjetoJuego::limpiar() {}
Esta clase se encargara iniciar todos los parametros, y definir de forma basica las funciones de nuestros objetos. Nuestro siguiente paso sera la creacion de dos clases, las cuales usaremos para el jugador y el enemigo, primero crearemos una crearemos la clase llamada Jugador y este es el codigo para el archivo de encabezado:
Jugador.h
#pragma once
#ifndef __Jugador__
#define __Jugador__
#include "SDLObjetoJuego.h"
#include "CargadorParams.h"
class Jugador : public SDLObjetoJuego
{
public:
Jugador(const CargadorParams*);
void dibujar();
void actualizar();
void limpiar();
};
#endif /* defined(__Jugador__)*/
Analicemos este archivo, donde establecemos el constructor para iniciar los parametros y luego las tres funciones que usaremos. Pasemos a agregar el codigo para el archivo .cpp:
Jugador.cpp
#include "Jugador.h"
Jugador::Jugador(const CargadorParams* pParams) :
SDLObjetoJuego(pParams) {
}
void Jugador::dibujar() {
SDLObjetoJuego::dibujar();
}
void Jugador::actualizar() {
m_x -= 1;
m_frameActual = int(((SDL_GetTicks() / 100) % 6));
}
void Jugador::limpiar() {}
Observen un par de curiosidades, la primera es el constructor donde pasaremos todos los datos al constructor de la clase anterior. La siguiente funcion llama al dibujar de la clase anterior. La funcion actualizar disminuira el valor del eje X e ira cambiando cada uno de los frames en la imagen que usaremos. Nuestro siguiente paso sera crear la clase que llamaremos Enemigo, veamos el codigo del archivo de encabezado:
Enemigo.h
#pragma once
#ifndef __Enemigo__
#define __Enemigo__
#include "SDLObjetoJuego.h"
#include "CargadorParams.h"
class Enemigo : public SDLObjetoJuego
{
public:
Enemigo(const CargadorParams*);
void dibujar();
void actualizar();
void limpiar();
};
#endif // !__Enemigo__
Pasemos a ver el codigo del archivo .cpp:
Enemigo.cpp
#include "Enemigo.h"
Enemigo::Enemigo(const CargadorParams* pParams) :
SDLObjetoJuego(pParams) {
}
void Enemigo::dibujar() {
SDLObjetoJuego::dibujar();
}
void Enemigo::actualizar() {
m_x += 1;
m_frameActual = int(((SDL_GetTicks() / 100) % 6));
}
void Enemigo::limpiar() {}
Este es igual a la clase Jugador pero la unica diferencia va a ser en actualizar, donde en lugar de disminuir al eje X lo incrementa. Lo siguiente sera crear una nueva clase a la cual llamaremos CargadorParams, en el archivo de encabezado agregaremos el siguiente codigo:
CargadorParams.h
#pragma once
#ifndef __CargadorParams__
#define __CargadorParams__
#include <string>
class CargadorParams
{
public:
CargadorParams(float, float, float, float, std::string);
float getX() const;
float getY() const;
float getAncho() const;
float getAlto() const;
std::string getIdTextura() const;
private:
float m_x;
float m_y;
float m_ancho;
float m_alto;
std::string m_idTextura;
};
#endif // !__CargadorParams__
Esta clase se encargara de cargar los parametros para nuestros objetos, como los ejes X e Y de la imagen, el ancho y alto de la misma, y su identificacion de lo cual hablaremos mas adelante. Estos son solo prototipos, pasemos a definirlos:
CargadorParams.cpp
#include "CargadorParams.h"
CargadorParams::CargadorParams(float x, float y,
float ancho, float alto, std::string id) :
m_x(x), m_y(y), m_ancho(ancho), m_alto(alto), m_idTextura(id) {
}
float CargadorParams::getX() const { return m_x; }
float CargadorParams::getY() const { return m_y; }
float CargadorParams::getAncho() const { return m_ancho; }
float CargadorParams::getAlto() const { return m_alto; }
std::string CargadorParams::getIdTextura() const { return m_idTextura; }
Definimos al constructor para iniciar los parametros con los valores recibidos. Las siguientes funciones solo se dedican a devolvernos los valores almacenados en cada variable. Lo siguiente sera crear una nueva clase a la cual llamaremos ManejarTexturas y modificaremos el archivo de encabezado de la siguiente manera:
ManejarTexturas.h
#pragma once
#ifndef __ManejarTexturas__
#define __ManejarTexturas__
#include <SDL3/SDL.h>
#include <SDL3/SDL_render.h>
#include <SDL3/SDL_surface.h>
#include <SDL3_image/SDL_image.h>
#include <string>
#include <map>
class ManejarTexturas
{
public:
static ManejarTexturas* instanciar();
bool cargar(std::string, std::string, SDL_Renderer*);
void dibujar(std::string, float, float, float, float,
SDL_Renderer*, SDL_FlipMode = SDL_FLIP_NONE);
void dibujar_frame(std::string, float, float, float, float, int, int,
SDL_Renderer*, SDL_FlipMode = SDL_FLIP_NONE);
static ManejarTexturas* e_pInstancia;
private:
ManejarTexturas();
std::map<std::string, SDL_Texture*> m_mapaTextura;
};
#endif // !__ManejarTexturas__
typedef ManejarTexturas Elmanejador;
Lo siguiente sera modificar a ManejarTexturas.cpp de la siguiente manera:
ManejadorTexturas.cpp
#include "ManejarTexturas.h"
ManejarTexturas* ManejarTexturas::e_pInstancia = 0;
ManejarTexturas* ManejarTexturas::instanciar() {
if (e_pInstancia == 0) {
e_pInstancia = new ManejarTexturas();
return e_pInstancia;
}
return e_pInstancia;
}
ManejarTexturas::ManejarTexturas() {}
bool ManejarTexturas::cargar(std::string nombreArchivo, std::string id,
SDL_Renderer* pRenderer) {
SDL_Surface* pSurfaceTemp = IMG_Load(nombreArchivo.c_str());
if (pSurfaceTemp == 0) return false;
SDL_Texture* pTextura = SDL_CreateTextureFromSurface(pRenderer,
pSurfaceTemp);
SDL_DestroySurface(pSurfaceTemp);
if (pTextura != 0) {
m_mapaTextura[id] = pTextura;
return true;
}
return false;
}
void ManejarTexturas::dibujar(std::string id, float x, float y,
float ancho, float alto,
SDL_Renderer* pRenderer, SDL_FlipMode inverso) {
SDL_FRect orgRect;
SDL_FRect dstRect;
orgRect.x = 0;
orgRect.y = 0;
orgRect.w = dstRect.w = ancho;
orgRect.h = dstRect.h = alto;
dstRect.x = x;
dstRect.y = y;
SDL_RenderTextureRotated(pRenderer, m_mapaTextura[id],
&orgRect, &dstRect,
0, 0, inverso);
}
void ManejarTexturas::dibujar_frame(std::string id, float x, float y,
float ancho, float alto,
int lineaActual, int frameActual,
SDL_Renderer* pRenderer, SDL_FlipMode inverso) {
SDL_FRect orgRect;
SDL_FRect dstRect;
orgRect.x = ancho * frameActual;
orgRect.y = alto * (lineaActual - 1);
orgRect.w = dstRect.w = ancho;
orgRect.h = dstRect.h = alto;
dstRect.x = x;
dstRect.y = y;
SDL_RenderTextureRotated(pRenderer, m_mapaTextura[id],
&orgRect, &dstRect,
0, 0, inverso);
}
Aqui tenemos las definiciones de las funciones que usaremos para manejar a las texturas o sprites de los objetos. Por ultimo debemos crear una nueva clase a la cual llamaremos Juego, en este caso debemos modificar el archivo de encabezado de la siguiente manera:
Juego.h
#pragma once
#ifndef __Juego__
#define __Juego__
#include <SDL3/SDL.h>
#include <SDL3/SDL_surface.h>
#include <SDL3/SDL_render.h>
#include <SDL3_image/SDL_image.h>
#include "ManejarTexturas.h"
#include "Jugador.h"
#include "Enemigo.h"
#include <vector>
class Juego
{
public:
~Juego() {}
bool iniciar(const char*, int, int, bool);
void renderizar();
void actualizar();
void manejaEventos();
void limpiar();
bool corriendo();
static Juego* instanciar();
SDL_Renderer* getRenderer() const;
private:
Juego();
static Juego* e_pInstanciar;
SDL_Window* m_pVentana;
SDL_Renderer* m_pRenderer;
bool m_bCorriendo;
int m_frameActual;
std::vector<ObjetoJuego*> m_objetosJuego;
};
#endif /* defined(__Juego__) */
typedef Juego Eljuego;
Aqui estableceremos a las funciones que seran parte del juego y seran para manejar a los elementos. El archivo .cpp debe ser modificado de la siguiente manera:
Juego.cpp
#include "Juego.h"
#include <Windows.h>
Juego* Juego::e_pInstanciar = 0;
Juego* Juego::instanciar() {
if (e_pInstanciar == 0) {
e_pInstanciar = new Juego();
return e_pInstanciar;
}
return e_pInstanciar;
}
Juego::Juego() {}
bool Juego::iniciar(const char* titulo, int ancho, int alto, bool pantallaCompleta) {
int flags = 0;
if (pantallaCompleta)
flags = SDL_WINDOW_FULLSCREEN;
if (SDL_Init(SDL_INIT_EVENTS) != 0) {
OutputDebugStringA("SDL_Init iniciado\n");
m_pVentana = SDL_CreateWindow(titulo, ancho, alto, flags);
if (m_pVentana != 0) {
OutputDebugStringA("Ventana Creada\n");
m_pRenderer = SDL_CreateRenderer(m_pVentana, NULL);
if (m_pRenderer != 0) {
OutputDebugStringA("Render Exitoso\n");
SDL_SetRenderDrawColor(m_pRenderer, 255, 255, 255, 255);
}
else {
OutputDebugStringA("Render Fallido\n");
return false;
}
}
else {
OutputDebugStringA("Ventana no Creada\n");
return false;
}
}
else {
OutputDebugStringA("Inicio Fallido\n");
return false;
}
OutputDebugStringA("Inicio Exitoso\n");
m_bCorriendo = true;
if (!Elmanejador::instanciar()->cargar("assets/animate-alpha.png",
"animado", m_pRenderer)) {
return false;
}
m_objetosJuego.push_back(new Jugador(
new CargadorParams(300, 100, 128, 82, "animado")));
m_objetosJuego.push_back(new Enemigo(
new CargadorParams(300, 300, 128, 82, "animado")));
return true;
}
void Juego::renderizar() {
SDL_RenderClear(m_pRenderer);
for (std::vector<ObjetoJuego*>::size_type i = 0;
i != m_objetosJuego.size(); i++) {
m_objetosJuego[i]->dibujar();
}
SDL_RenderPresent(m_pRenderer);
actualizar();
}
void Juego::manejaEventos() {
SDL_Event evento;
if (SDL_PollEvent(&evento)) {
switch (evento.type) {
case SDL_EVENT_QUIT:
m_bCorriendo = false;
break;
default:
break;
}
}
}
void Juego::actualizar(){
for (std::vector<ObjetoJuego*>::size_type i = 0;
i != m_objetosJuego.size(); i++) {
m_objetosJuego[i]->actualizar();
}
}
void Juego::limpiar() {
OutputDebugStringA("Limpiando Memoria\n");
SDL_DestroyWindow(m_pVentana);
SDL_DestroyRenderer(m_pRenderer);
SDL_Quit();
}
bool Juego::corriendo() { return m_bCorriendo; }
SDL_Renderer* Juego::getRenderer() const { return m_pRenderer; }
Como pueden ver estas funciones son para iniciar a los objetos, para dibujarlos o renderizarlos, actualizar cada elemento en pantalla, y eliminar todo una vez finalizada. Para finalizar debemos descargar la siguiente imagen y guardarla en una carpeta llamada assets dentro de nuestro proyecto, mas exactamente donde esta nuestro codigo y en donde esta el ejecutable que se genera con cada compilacion

Con esto creado deberan tener un programa que trabaje de la siguiente manera
Antes de finalizar, les dejo un link para descargar un archivo con el codigo final, hasta el momento, de nuestro proyecto:
Con esto tenemos nuestro primer proyecto ya funcionando, en este caso fuimos viendo como manejar imagenes, manipular animaciones y como poder manejar archivos mas estandares de internet, tengan en cuenta que es solo la base y lo seguiremos mejorando. Espero les haya sido de utilidad y les dejo un link a GitHub donde estan los codigos vistos 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





