Anuncios

Bienvenidos sean a este post, hoy comenzaremos con el controlador de estados.

Anuncios

Pero antes de comenzar, vamos a necesitar nuestro proyecto Juego. Sino lo poseen les dejo un link para descargarlo:

Proyecto Juego

Anuncios

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.

Anuncios

La idea es crear un controlador de estados para nuestro juego, el cual utiliza una forma de trabajo llamada FSM (Estado de maquinas finitas por sus siglas en ingles) lo cual nos permitira tener un solo estado por vez, conocido como estado actual, y poder cambiar de un estado a otro. A esto se lo denomina transicion. Para continuar con este post, vamos a crear una nueva clase a la cual llamaremos EstadoJuego. Una vez generado iremos al archivo de encabezado y agregaremos el siguiente codigo:

EstadoJuego.h
#pragma once
#ifndef __EstadoJuego__
#define __EstadoJuego__
#include <iostream>
#include <string>

class EstadoJuego
{
public:
	virtual void actualizar() = 0;
	virtual void renderizar() = 0;
	virtual bool enIngreso() = 0;
	virtual bool enSalida() = 0;
	virtual std::string getIdEstado() const = 0;
};

#endif // !__EstadoJuego__
Anuncios

Como pueden observar va a ser una clase base de la cual crearemos clases hijas de esta, como particularidad volvimos a crear una clase abstracta con solamente funciones abstractas. Dos de estas funciones son algunas que hemos visto hasta ahora, actualizar y renderizar, pero agregamos tres funciones nuevas, las cuales son:

  • enIngreso, sera para el estado de carga del juego
  • enSalida, sera para el estado de limpiar del juego
  • getIdEstado, la usaremos para obtener el id del estado
Anuncios

Tambien definimos el bloque que se encarga de verificar si esta definida la clase o no para evitar tener objetos duplicados en memoria. Con esto ya tenemos nuestra clase base creada y definida, comencemos con el primero de los estados y va a ser para nuestro menu.

Anuncios

Para ello, crearemos una nueva clase a la cual llamaremos EstadoMenu. Una vez generada, iremos al archivo de encabezado y agregaremos el siguiente codigo:

EstadoMenu.h
#pragma once
#ifndef __EstadoMenu__
#define __EstadoMenu__
#include "EstadoJuego.h"

class EstadoMenu : public EstadoJuego
{
public:
	virtual void actualizar();
	virtual void renderizar();
	virtual bool enIngreso();
	virtual bool enSalida();
	virtual std::string getIdEstado() const;

private:
	static const std::string e_idmenu;
};

#endif // !__EstadoMenu__
Anuncios

Esta clase sera la primera heredera de la clase anterior, como pueden observar tenemos los prototipos de las funciones de la clase base. Esta constante la declaramos en la parte privada de esta clase, nuestro siguiente paso sera agregar el siguiente codigo dentro de EstadoMenu.cpp:

EstadoMenu.cpp
#include "EstadoMenu.h"
#include <Windows.h>

const std::string EstadoMenu::e_idmenu = "MENU";

void EstadoMenu::actualizar(){}

void EstadoMenu::renderizar(){}

bool EstadoMenu::enIngreso() {
	OutputDebugStringA("Ingresando al Estado del menu\n");
	return true;
}

bool EstadoMenu::enSalida() {
	OutputDebugStringA("Saliendo del Estado del menu\n");
	return true;
}

std::string EstadoMenu::getIdEstado() const { return e_idmenu; }
Anuncios

Como esta clase representa el estado del menu, nuestro valor estatico sera para identificarlo de tal manera y como este solamente representa a este no necesitamos que se modifique. Por lo tanto, lo hacemos constante para evitar un cambio por error pero antes incluimos a Windows.h para acceder a la funcion para poder escribir en el debug. Las funciones actualizar y renderizar por el momento las dejaremos en blanco y en las funciones enIngreso y enSalida mostraran dos mensajes para la accion de sus respectivas acciones en la salida de depuracion y en ambos casos devolveremos un true. La ultima funcion, es simplemente para devolver el valor de la constante. Por el momento dejamos esta clase de esta manera, pasemos a la siguiente.

Anuncios

Pasemos a crear una nueva clase que llamaremos EstadoJugar, y una vez generada iremos a su archivo de encabezado y agregaremos el siguiente codigo:

EstadoJugar.h
#pragma once
#ifndef __EstadoJugar__
#define __EstadoJugar__
#include "EstadoJuego.h"

class EstadoJugar : public EstadoJuego
{
public:
	virtual void actualizar();
	virtual void renderizar();
	virtual bool enIngreso();
	virtual bool enSalida();
	virtual std::string getIdEstado() const;

private:
	static const std::string e_idjugar;
};

#endif // !__EstadoJugar__
Anuncios

Volvemos a repetir todo lo mismo que hicimos para la clase EstadoMenu, pero la unica diferencia esta en la constante que usaremos para identificarla donde en lugar de usar e_idmenu usamos a e_idjugar pero el resto son exactamente iguales. Pasemos al archivo .cpp y agreguemos el siguiente codigo:

EstadoJugar.cpp
#include "EstadoJugar.h"
#include <Windows.h>

const std::string EstadoJugar::e_idjugar = "JUGAR";

void EstadoJugar::actualizar() {}

void EstadoJugar::renderizar() {}

bool EstadoJugar::enIngreso() {
	OutputDebugStringA("Ingresando al Estado de jugar\n");
	return true;
}

bool EstadoJugar::enSalida() {
	OutputDebugStringA("Saliendo del Estado de jugar\n");
	return true;
}

std::string EstadoJugar::getIdEstado() const { return e_idjugar; }
Anuncios

Tecnicamente es igual a lo visto en EstadoMenu.cpp pero la diferencia esta en el valor de la constante estatica, donde en lugar de identificar al menu nos identifica que estamos en jugar. Tambien varia el mensaje de notificacion de los estados de ingreso y salida pero el resto es exactamente lo mismo. Tambien observen a getIdEstado, sigue devolviendo a la valor constante pero ahora de esta clase. Como podran deducir, este serra el encargado de identificar cada estado porque devuelven al correspondiente identificador. Con esto establecimos la base de las clases que se encargaran de manejar los estados del juego. Ahora podemos pasar a ver las distintas formas que debemos manejar los estados y estas son:

  • Remover un estado y agregar otro
  • Agregar un estado sin remover el anterior
  • Remover un estado sin agregar otro
Anuncios

La primera se usa para cambiar completamente de estado sin brindar la capacidad de poder volver al estado anterior. La segunda esta pensada para una opcion de pausa o similares y la ultima sera para remover completamente cualquier estado posible. Con esto aclarado, pasemos a la clase encargada de manejar estos estados. Crearemos una nueva clase y la llamaremos MaquinaEstadoJuego, una vez generado iremos al archivo de encabezado y le agregaremos el siguiente codigo:

MaquinaEstadoJuego.h
#pragma once
#ifndef __MaquinaEstadoJuego__
#define __MaquinaEstadoJuego__
#include "EstadoJuego.h"
#include <vector>

class MaquinaEstadoJuego
{
public:
	void pushEstado(EstadoJuego*);
	void cambiaEstado(EstadoJuego*);
	void popEstado();

private:
	std::vector<EstadoJuego*> m_estadoJuego;
};

#endif // !__MaquinaEstadoJuego__
Anuncios

Tenemos tres funciones para manejar los estados como dijimos anteriormente, por lo tanto:

  • pushEstado, se encarga de remover el estado y agregar otro
  • cambiaEstado, agregar un estado sin remover el otro
  • popEstado, removera el estado sin agregar otro
Anuncios

Y por ultimo tenemos una coleccion de tipo vector para almacenar los distintos estados que tendremos. Nuestro siguiente paso sera agregar el siguiente codigo para el archivo .cpp:

MaquinaEstadoJuego.cpp

#include "MaquinaEstadoJuego.h"

void MaquinaEstadoJuego::pushEstado(EstadoJuego* pEstado) {
	m_estadoJuego.push_back(pEstado);
	m_estadoJuego.back()->enIngreso();
}

void MaquinaEstadoJuego::popEstado() {
	if (!m_estadoJuego.empty()) {
		if(!m_estadoJuego.back()->enSalida()){
			delete m_estadoJuego.back();
			m_estadoJuego.pop_back();
		}
	}
}

void MaquinaEstadoJuego::cambiaEstado(EstadoJuego* pEstado) {
	if (!m_estadoJuego.empty()) {
		if (m_estadoJuego.back()->getIdEstado() == 
			pEstado->getIdEstado()) {
			return;
		}
		if (m_estadoJuego.back()->enSalida()) {
			delete m_estadoJuego.back();
			m_estadoJuego.pop_back();
		}
	}
	m_estadoJuego.push_back(pEstado);
	m_estadoJuego.back()->enIngreso();
}
Anuncios
Anuncios

En esta ocasion definiremos a las tres funciones que declaramos en el archivo de encabezado. La primera funcion es bastante obvia porque es la encargada de remover el estado y agregar uno nuevo. Para ello tomamos el atributo recibido en pEstado y mediante push_back lo agregamos en m_estadosJuego y despues llamamos a su respectiva funcion enIngreso. La siguiente funcion sera la que mencionamos para remover todos los estados porque verificara que exista un estado almacenado y en caso de ser cierto llama a su respectiva funcion enSalida. En caso contrario, la ejecucion procede a eliminar el estado almacenado. Esto lo haremos mientras exista un estado almacenado, con estas dos funciones tenemos la funcion para agregar nuevos estados (pushEstado) y el otro que sirve para eliminarlos (popEstado), pasemos a hablar sobre la ultima funcion.

Anuncios

Esta es la funcion mas «compleja» porque sera la encargada de agregar un nuevo estado sin quitar el otro, primero chequearemos si existe algun estado y en caso de ser igual al estado que informamos en pEstado no haremos nada y saldremos de la funcion. El siguiente condicional llama a enSalida y en caso de ser verdadero procede a eliminar el estado actual para luego agregar el nuevo estado informado en pEstado y por ultimo, llamamos a enIngreso del nuevo estado actual. Con esto concluimos con la nueva clase pasemos a la siguiente modificacion.

Anuncios

Para la siguiente modificacion iremos al archivo de encabezado de la clase Juego, donde primero incluiremos a la clase encargada de manejar el FSM, entre los archivos de inclusion agregaremos los siguientes:

#include "MaquinaEstadoJuego.h"
#include "EstadoMenu.h"
#include "EstadoJugar.h"
Anuncios

Esto nos permitira acceder a las distintas clases que hemos creado anteriormente. Lo siguiente sera crear la variable miembro en la parte privada del archivo:

MaquinaEstadoJuego* m_pMaquinaEstadoJuego;
Anuncios

Con esto declarado debemos pasar a Juego.cpp, buscamos la funcion iniciar y agregaremos las siguientes lineas antes del return true:

	m_pMaquinaEstadoJuego = new MaquinaEstadoJuego();
	m_pMaquinaEstadoJuego->cambiaEstado(new EstadoMenu());
Anuncios

La primera se encarga de definir a la variable miembro, despues tomaremos a este objeto y lo cambiaremos de estado creando un estado de menu. La segunda linea sera para una modificacion que haremos en breve. Pasemos a modificar la funcion manejaEventos de la siguiente manera:

void Juego::manejaEventos() {
    Controles::instanciar()->actualizar();
    if (Controles::instanciar()->teclaPresionada(SDL_SCANCODE_RETURN)) {
        m_pMaquinaEstadoJuego->cambiaEstado(new EstadoJugar());
    }
}
Anuncios

En este caso agregamos un condicional donde verifica si presionamos la tecla ENTER, en caso de ser verdadero procedera a cambiar de estado y en este caso pasaremos del estado del menu a jugar, si lo compilamos y probamos deberemos ver en la opcion de salida las notificaciones de cambio de estado, veamos un ejemplo

Anuncios

Como pueden observar tenemos el paso del estado del menu del juego al juego en si, obviamente esto es solamente superficial por el momento pero tecnicamente nuestro juego ya funciona manejando estados del juego. Vamos a hacer nuestra ultima modificacion y para ello volveremos a la MaquinaEstadoJuego.

Anuncios

Primero iremos al archivo de encabezado donde agregaremos los siguientes prototipos en la parte publica:

	void actualizar();
	void renderizar();
Anuncios

Estas seran las funciones encargadas de hacer la magia, nuestro siguiente paso sera definirlas en el archivo .cpp:

void MaquinaEstadoJuego::actualizar()
{
	if (!m_estadoJuego.empty())
		m_estadoJuego.back()->actualizar();
}

void MaquinaEstadoJuego::renderizar()
{
	if (!m_estadoJuego.empty())
		m_estadoJuego.back()->renderizar();
}
Anuncios
Anuncios

Tenemos dos funciones que trabajan de forma muy similar; la primera que sera para actualizar y verifica si existe algun estado almacenado. Verifica que no esta vacio como dice el condicional, y en caso de ser verdadero procede a llamar a la funcion actualizar del estado actual mediante back. La siguiente funcion hace exactamente lo mismo pero en lugar de llamar a la funcion de actualizar llama a la funcion renderizar del estado actual. Nuestra siguiente modificacion sera nuevamente en la clase Juego y para ello debemos ir a Juego.cpp e iremos a nuestras funciones actualizar y renderizar, y los modificaremos de la siguiente manera:

void Juego::renderizar() {
    SDL_RenderClear(m_pRenderer);
    m_pMaquinaEstadoJuego->renderizar();
    SDL_RenderPresent(m_pRenderer);
    actualizar();
}

void Juego::actualizar(){
    m_pMaquinaEstadoJuego->actualizar();
}
Anuncios
Nota: 
No necesariamente estan juntas pero lo hice asi para que las busquen y las modifiquen.
Anuncios

Como pueden observar en el caso de renderizar quitamos el bucle encargado de dibujar a los objetos almacenados en m_objetosJuego y lo reemplazamos por el llamado de renderizar de la clase MaquinaEstadoJuego. Lo mismo sucedio con actualizar donde eliminamos el bucle que pasaba por todos los elementos de m_objetosJuego por la funcion actualizar de la clase antes mencionada. Con esto oficialmente tenemos a FSM implementado en nuestro juego pero esto lo extenderemos en el proximo post.

Anuncios

En resumen, hoy hemos visto que es FSM, que nos permite controlar, hemos creado las clases necesarias para poder manipular los distintos estados, hemos creado las clases bases que se encargaran de manejar dos estados basicos de nuestro juego como son el menu y el juego en si, tambien hemos visto como crear una clase maestra o base encargada de manejar dos tipos basicos de estados de cada uno de los mismos como son el ingreso y la salida de los mismos, tambien hemos creado las clases que usaremos para el menu del juego y otra para cuando juguemos. Espero les haya sido de utilidad y les dejo un link a GitHub donde estan los codigos creados hoy:

FSM / GitHub

Les dejo algunas de mis redes sociales para seguirme o recibir una notificacion cada vez que subo un nuevo post:

Anuncios
pp258

Donación

Es para mantenimento del sitio, gracias!

$1.50