Bienvenidos sean a este post, hoy veremos como mejorar el manejo de los sprites.
Hasta el post anterior cubrimos lo basico de como dibujar con SDL_Image. Esto no solo nos permite manejar otros formatos a parte de BMP y sus correspondientes ventajas.
Pero como dijimos anteriormente nosotros tenemos que lograr crear un forma de trabajo la cual se pueda implementar de manera facil y eficiente. Para esto vamos a crear un manejador de texturas o imagenes, para ello debemos volver a nuestro proyecto Juego. Sino lo poseen les dejo un link para descargarlo:
Una vez descargado, simplemente lo extraen en el PC y esta casi listo para ser usado. La siguiente modificacion que deben realizar es la revinculacion de SDL y SDL_Image. Para repetir todos los pasos de SDL, les recomiendo visitar este post. Y para el caso de SDL_Image les recomiendo este otro post. Si tienen todo habilitado y funcionando, vamos al proyecto y crearemos una nueva clase. Nuevamente, vamos al programa Juego en el explorador de soluciones, hacemos click con el boton derecho sobre este y seleccionamos Agregar, y luego Clase, nos aparecera una nueva ventana y deben completar los campos de la siguiente manera:
- Nombre de la clase: ManejarTexturas
- Archivo .h: ManejarTexturas.h
- Archivo .cpp: ManejarTexturas.cpp
El resto debe quedar destildado, presionan Aceptar y con esto ya creamos la nueva clase. Nuestro primer paso sera ir a ManejarTexturas.h y modificaremos su codigo 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:
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);
private:
std::map<std::string, SDL_Texture*> m_mapaTextura;
};
#endif // !__ManejarTexturas__
En este archivo de encabezado vuelve a usar a ifndef y define para verificar que no se cargue mas de una vez en memoria, luego incluimos las siguientes librerias:
#include <SDL3/SDL.h>
#include <SDL3/SDL_render.h>
#include <SDL3/SDL_surface.h>
#include <SDL3_image/SDL_image.h>
#include <string>
#include <map>
La primera es para tener acceso a todas las funciones de SDL, las siguientes son utilizadas para manejar lass imagenes que carguemos como texturas, la externa (SDL_Image) es la que usaremos para poder cargar otro tipos de imagenes y no solamente BMP, y las ultimas librerias seran para usar a los tipos string y map. Despues tenemos los prototipos de las funciones que usaremos para esta clase, veamos una breve descripcion:
- cargar: sera la funcion encargada de cargar la imagen
- dibujar: se encargara de dibujar la imagen
- dibujar_frame: se va a encargar de dibujar los distintos frames
En el caso de la funcion cargar recibira tres datos, la primera sera para el archivo de la imagen, el segundo sera el ID para identificarlo y el ultimo sera el objeto que usaremos para renderizar. La funcion dibujar y dibujar_frame son muy parecidos porque en ambos casos le enviamos el ID de la imagen, el ancho, el alto, el eje X y el eje Y asi como el inversor de imagenes. En ambos casos, establecemos un valor predeterminado para que no lo haga, especialmente para el caso de que no haber informado ningun efecto, la diferencia entre las dos funciones va a ser que en dibujar_frame recibira dos datos mas, una para la fila y otro para la posicion en la lamina, lo cual hara el cambio de frame. Nuestro ultimo dato esta en private, el cual sera un mapa donde almacenaremos en base al ID al objeto renderizador. Con esto terminamos el archivo de encabezado. Pasemos al archivo encargado de definir todas las funciones del archivo de encabezado:
ManejarTexturas.cpp
#include "ManejarTexturas.h"
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);
}
En este archivo nos dedicaremos pura y exclusivamente a definir las funciones del archivo de encabezado, vamos a tomar la funcion cargar:
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;
}
Recibe los tres datos que mencionamos anteriormente, lo primero que haremos es crear un objeto de tipo SDL_Surface que usaremos para cargar la imagen que pasemos en nombreArchivo, recuerden que IMG_Load devuelve un valor de tipo SDL_Surface. La funcion c_str se encargara de convertir el valor de nombreArchivo en cadena mas basico. Lo siguiente es un condicional donde verifica si este objeto es igual a cero, y en caso de ser verdadero sale de la funcion devolviendo un false, indicando que no se cargo la imagen. Despues de esto, crearemos un objeto de tipo SDL_Texture donde crearemos una textura desde la imagen, de manera similar a como vinimos haciendo hasta ahora, donde pasaremos el objeto para renderizar que informamos en los atributos, y luego le enviaremos la imagen que cargamos anteriormente.
Seguido a esto, liberamos el objeto donde cargamos la imagen porque en realidad ya lo pasamos al objeto anterior y este ya no es necesario. Por ultimo, tenemos un condicional donde verifica si pTextura es distinta de cero, implica que algo se cargo, entonces en base al id que informamos asignaremos el valor, o mejor dicho la imagen, que se cargo en pTextura. Y devolvemos true para decir que salio todo bien, en cambio si no se ejecuto correctamente devolvera un false indicando que algo fallo, veamos la siguiente funcion:
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);
}
De nuevo como antes, recibimos los datos antes mencionados. Para despues crear dos objetos de tipo SDL_FRect para el rectangulo de origen y destino, de manera similar a como trabajamos anteriormente. Despues basicamente iniciaremos el eje X e Y de orgRect con 0 porque necesitamos mostrar todo el cuadro, luego en base al ancho y al alto definimos el ancho (width) y el alto (height) de los dos rectangulos. Lo siguiente es definir el valor de X e Y de dstRect con los informados al llamar al funcion. Por ultimo, utilizamos a SDL_RenderTextureRotated porque nosotros debemos tener la posibilidad de poder invertir la imagen en caso de ser necesario, recuerden que en el prototipo establecimos un valor predeterminado en caso de no informar ninguno, en este caso pasamos el objeto de renderizacion. Luego en base al id informado buscamos en el mapa y pasamos el valor relacionado, pasamos los dos rectangulos que configuramos, los valores en cero son para el angulo y el centro de la rotacion. Lo dejamos asi porque no necesitamos modificarlo, por ultimo pasamos el valor para invertir o no, con esto ya tenemos el metodo encargado de dibujar la imagen.
El siguiente metodo es muy similar pero agregaremos dos valores mas, como son lineaActual y frameActual, esto nos serviran mas que nada para modificar el valor del eje X e Y del rectangulo de origen (orgRect):
orgRect.x = ancho * frameActual;
orgRect.y = alto * (lineaActual - 1);
Como pueden observar el valor del eje X, el eje horizontal, sera multiplicado por el valor del frame actual. Lo siguiente es calcular el valor del eje Y donde multiplicaremos el valor del alto por la linea actual menos uno, esto equivale a decir que usemos la primera linea o la linea cero. Salvo estas dos lineas, el resto trabajan de la misma forma, inclusive la funcion encargada de copiar la imagen, pero como se daran cuenta esta va a ser la encargada de crear la animacion. Con esto ya tenemos una clase encargada de recibir cualquier imagen y hacer todas las acciones que teniamos antes.
Nuestra siguiente modificacion sera en el codigo de Juego donde implementaremos todo esto que creamos y para ello debemos modificar primero a Juego.h. Podemos eliminar o comentar todas las variables relacionados a las texturas:
SDL_Texture* m_pTextura;
SDL_FRect m_origenRectangulo;
SDL_FRect m_destinoRectangulo;
El siguiente paso sera eliminar o comentar las siguientes lineas dentro de nuestra funcion iniciar en Juego.cpp:
SDL_Surface* iSurfaceTemp = IMG_Load("assets/animate-alpha.png");
m_pTextura = SDL_CreateTextureFromSurface(
m_pRenderer,
iSurfaceTemp);
SDL_DestroySurface(iSurfaceTemp);
SDL_GetTextureProperties(m_pTextura);
SDL_GetTextureSize(m_pTextura, &w, &h);
m_origenRectangulo.w = 128;
m_origenRectangulo.h = 82;
m_origenRectangulo.x = m_destinoRectangulo.x = 0;
m_origenRectangulo.y = m_destinoRectangulo.y = 0;
m_destinoRectangulo.w = m_origenRectangulo.w;
m_destinoRectangulo.h = m_origenRectangulo.h;
Con esto solo nos resta implementar algunos cambios para usar a nuestra nueva clase. Tambien debemos elimminar o comentar la siguiente linea en la funcion iniciar:
float w,h;
Si bien no pasa nada con dejarla, esto nos evitara notificaciones al momento de compilar porque ya no son utilizadas. Ya tenemos todo listo para usar a nuestra nueva clase pero primero vamos a incluirla, y para ello debemos ir a Juego.h y al inicio agregaremos la siguiente linea:
#include "ManejarTexturas.h"
Esta nos permitira tener acceso a las funciones y variables, nuestro siguiente paso sera declarar dos variables en la parte privada de la clase:
int m_frameActual;
ManejarTexturas m_manejarTexturas;
Lo siguiente sera modificar la funcion iniciar en Juego.cpp donde antes de la devolucion del true agregaremos el siguiente bloque:
m_manejarTexturas.cargar("assets/animate-alpha.png", "animado", m_pRenderer);
Con esto no solamente cargamos la imagen sino que le asignamos un ID, nuestro siguiente paso sera ir a la funcion renderizar y la modificaremos de la siguiente manera:
void Juego::renderizar() {
SDL_RenderClear(m_pRenderer);
m_manejarTexturas.dibujar("animado", 0, 0, 128, 82, m_pRenderer);
m_manejarTexturas.dibujar_frame("animado", 100, 100, 128, 82, 1,
m_frameActual, m_pRenderer);
SDL_RenderPresent(m_pRenderer);
actualizar();
}
Basicamente lo unico que modificamos fue eliminar la llamada SDL_RenderTexture para reemplazar por la llamada a dibujar y dibujar_frame de la instancia m_manejadorTexturas. Pasamos los datos que generamos en las funciones tanto iniciar como al comienzo pero las ubicamos en dos lugares distintos, si observan lo unico que variamos fue el eje X e Y de la funcion dibujar_frame. Con esto ya tenemos la funcion renderizar completa. Solo nos falta modificar la funcion actualizar de la siguiente manera:
void Juego::actualizar(){
m_frameActual = int(((SDL_GetTicks() / 100) % 6));
}
Como pueden ver no actualizara el eje X de la renderizacion sino que actualizara a la variable m_frameActual, la cual indica en que frame debe trabajar, y eliminamos el 128 porque ya no necesitamos calcular el ancho sino que esto lo hara la clase que creamos (ManejarTexturas), podemos compilar y probar como se ve en el siguiente video
En el video podemos ver como tenemos una imagen dibujada y otra imagen animada, esto es lo que nos permite nuestra nueva clase, es decir la posibilidad de poder manejar cualquier imagen la cantidad de veces que sea necesaria por medio de una sola clase.
En resumen, hoy hemos creado la primera de las mecanicas importantes que necesitamos crear para un videojuego, es decir la capacidad de procesar imagenes con una sola instancia de una clase y poder ser reutilizado todas las veces que necesitemos, tambien hemos visto como hacerlo por medio de un identificador unico. 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





