Bienvenidos sean a este post, hoy veremos como es un ciclo de juego.
En un juego tenemos dos caracteristicas muy importantes como son el diseño y la jugabilidad pero para nosotros sera mas importante las mecanicas subyacentes que se encargan justamente de manejar la interaccion entre los graficos, sonidos y controles. Por ejemplo, la seccion encargada de los graficos no necesita saber como trabaja la logica del juego sino que debe trabajar cuando esta se la solicita. Por esto, la estructura interna de un juego sera algo semejante a esto:

Este es el ciclo mas basico de un videojuego, siempre va a tener un comienzo donde inicia, verifica o toma los controles (es decir nuestra interaccion). Luego hace las fisicas o rutinas pertinentes como pueden ser el caminar, disparar, atacar, etc, para luego renderizar o dibujar en la pantalla el resultado del bloque anterior. En este chequea si debe volver a repetir el proceso o de lo contrario salir del bucle.
Esto que describimos es lo que habitualmente se llama un ciclo de juego, donde estaremos verificando constantemente las condiciones para repetir todas las acciones del juego hasta que se cumpla una condicion donde podremos salir del mismo. La idea sera crear un framework reutilizable, escribiremos un codigo para manejar cada uno de los distintos aspectos del juego y a su vez poder ser reutilizado para cada uno de los proyectos que vayamos creando. Vamos a ver un ejemplo con el proyecto que creamos en el post anterior pero antes hablaremos sobre algunas de las constantes o flags que disponemos en SDL.
En el post anterior cuando trabajamos con el codigo, lo hicimos directamente sin necesidad de iniciar ningun subsistema. Para esto, se utiliza a la funcion SDL_Init y esta basicamente se encargara de iniciar todos los eventos de SDL. Si bien, en SDL2 habia una constante para iniciar todos los subsistemas al mismo tiempo, a partir de SDL3 fue eliminado y estos deben iniciarse de forma individual. Veamos algunas de las disponibles para la inicializacion:
| Constante | Subsistema inicializado |
| SDL_INIT_HAPIC | Fuerza al subsistema de feedback |
| SDL_INIT_AUDIO | Subsistema de audio |
| SDL_INIT_VIDEO | Subsistema de video |
| SDL_INIT_GAMEPAD | Subsistema de joystick |
| SDL_INIT_JOYSTICK | Subsistema del joystick pero inicia eventos del subsistema |
| SDL_INIT_EVENTS | Todos los eventos de subsistemas |
| SDL_INIT_SENSOR | Subsistemas de sensores |
| SDL_INIT_CAMERA | Subsistemas de camara |
De esta tabla podemos ver como cada uno puede iniciar solamente lo que necesitemos, tambien podemos iniciar varias a la vez por medio del pipe (|), veamos un ejemplo:
SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO)
En este caso iniciaremos al subsistema de audio y video respectivamente, en caso de iniciarse correctamente devolvera un valor booleano para indicar si fue iniciado correctamente, true, o no y para ello enviara un false. A su vez nosostros podemos verificar si se inicio o no el subsistema informado, tomando el inicio que hicimos de ejemplo vamos a verificarlo de la siguiente manera:
if (SDL_WasInit(SDL_INIT_VIDEO) != 0)
{
std::cout << "Video fue inicializado\n";
}
Por medio de la funcion SDL_WasInit verificamos que SDL_INIT_VIDEO sea distinto de cero. En caso de ser verdadero (dado que no nos interesa que valor tenga sino que solamente sea distinta de cero) nos mostrara un mensaje indicando que el subsistema de video fue iniciado correctamente. Esto mismo aplica para las otras constantes, pasemos a ver las constantes pero para la renderizacion:
| Constante | Proposito |
| NULL | Permite que SDL lo seleccione por nosotros |
| SDL_RENDERER_SOFTWARE | Usa el render por software |
| SDL_RENDERER_ACCELERATED | Usa la aceleracion por Hardware |
| SDL_RENDERER_PRESENTVSYNC | Usa la actualizacion de render con la tasa de refresco |
| SDL_RENDERER_TARGETTEXTURE | Soporte el render a textura |
Cuando vimos el codigo del post anterior al momento de hablar sobre SDL_CreateRenderer, el ultimo parametro que pasamos era uno de los mencionados en la tabla anterior para justamente definir que tipo de renderizacion utilizaremos para poder aprovechar mejor el hardware que disponemos. En ese caso, pasamos a NULL para que SDL seleccione por nosotros. Con estos dos casos comentados, pasemos a modificar el codigo de nuestro ejemplo anterior. Si no lo tienen creado, las caracteristicas del proyectos son:
- Tipo de Lenguaje: C++
- Plataforma: Windows
- Tipo de Proyecto: Consola
- Nombre del proyecto: Primer Programa
- Ubicacion: Dejan la predeterminada
- Solucion: Crear nueva solucion
- Nombre de la solucion: Primer Programa
El codigo de Primer Programa.cpp es:
#include<SDL3/SDL.h>
SDL_Window* g_pWindow = 0;
SDL_Renderer* g_pRenderer = 0;
const char* mensaje = "Hola Mundo!";
const float scale = 4.0f;
int w = 0, h = 0;
float x, y;
int main(int argc, char* args[])
{
g_pWindow = SDL_CreateWindow(
"Instalando y probando a SDL",
640,
480,
SDL_WINDOW_OPENGL);
if (g_pWindow != 0)
g_pRenderer = SDL_CreateRenderer(g_pWindow, NULL);
SDL_GetRenderOutputSize(g_pRenderer, &w, &h);
SDL_SetRenderScale(g_pRenderer, scale, scale);
x = ((w/scale) - SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(mensaje)) / 2;
y = ((h/scale) - SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) / 2;
SDL_SetRenderDrawColor(g_pRenderer, 0, 0, 0, 255);
SDL_RenderClear(g_pRenderer);
SDL_SetRenderDrawColor(g_pRenderer, 255, 255, 255, 255);
SDL_RenderDebugText(g_pRenderer, x, y, mensaje);
SDL_RenderPresent(g_pRenderer);
SDL_Delay(5000);
SDL_Quit();
return 0;
}
Este codigo es simplemente para crear una ventana con un texto dentro, y despues de un cierto tiempo cerrara la misma al salir del programa. Si necesitan mas detalles, les recomiendo visitar el post anterior. Volviendo a nuestro codigo, vamos a realizar algunas modificaciones. Para ello, vamos a Primer Programa.cpp y modificaremos el codigo de la siguiente manera:
#include<SDL3/SDL.h>
SDL_Window* g_pWindow = 0;
SDL_Renderer* g_pRenderer = 0;
const char* mensaje = "Hola Mundo!";
const float scale = 4.0f;
int w = 0, h = 0;
float x, y;
bool g_bCorriendo = false;
bool iniciar(const char*, int, int, int);
void renderizar();
int main(int argc, char* args[])
{
if (iniciar("Ciclo de juego en SDL", 640, 480, SDL_WINDOW_OPENGL)) {
g_bCorriendo = true;
}
else {
return 1;
}
while (g_bCorriendo) { renderizar(); }
SDL_Quit();
return 0;
}
bool iniciar(const char* titulo, int ancho, int alto, int flags) {
if (SDL_Init(SDL_INIT_EVENTS) != 0) {
g_pWindow = SDL_CreateWindow(titulo, ancho, alto, flags);
if (g_pWindow != 0)
g_pRenderer = SDL_CreateRenderer(g_pWindow, NULL);
}
else {
return false;
}
return true;
}
void renderizar() {
SDL_GetRenderOutputSize(g_pRenderer, &w, &h);
SDL_SetRenderScale(g_pRenderer, scale, scale);
x=((w / scale) - SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(mensaje)) / 2;
y=((h / scale) - SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) / 2;
SDL_SetRenderDrawColor(g_pRenderer, 0, 0, 0, 255);
SDL_RenderClear(g_pRenderer);
SDL_SetRenderDrawColor(g_pRenderer, 255, 255, 255, 255);
SDL_RenderDebugText(g_pRenderer, x, y, mensaje);
SDL_RenderPresent(g_pRenderer);
}
Nota:
Para habilitar a SDL en Visual Studio deben seguir los pasos mencionados en este post.
La primera modificacion es el agregado de la variable g_bCorriendo que usaremos para crear y mantener el ciclo de juego. Este tendra un valor inicial de false para que sea establecido cuando sea necesario. Luego, tenemos dos prototipos para dos funciones. Estas las definiremos despues del main, recueden que el codigo cuando encuentra un prototipo al momento de compilarlo asume que estara definido mas adelannte. La primera funcion que definiremos es iniciar. Esta se encarga de crear la ventana y la seccion para trabajar con el renderizado. En este, tenemos un condicional donde verifica si se inicio correctamente a los eventos del subsistema. Si se cumple la condicion, ejecuta el codigo que teniamos antes para definir el objeto de la ventana y renderizado. En caso contrario, sale de la funcion devolviendo false, pero siempre devuelve un true si se cumple la condicion. La funcion renderizar se encargara de renderizar o dibujar el contenido en la ventana. Muy resumidamennte, las primeras cuatro lineas son para calcular todas las coordenadas necesarias para alinear el mensaje en la ventana. Las siguientes lineas se encargan de pintar la pantalla de negro y luego ubicar el mensaje en la misma. Si observan, basicamente lo que hicimos fue pasar todo el codigo original en main a estas dos funciones.
En el main, como dijimos ya no vamos a tener el codigo anterior y lo reemplazaremos con las nuevas funciones. Primero usaremos un condicional para verificar el resultado devuelto por la funcion iniciar. A esta le pasaremos todos los parametros necesarios para crear la ventana. En caso de que se inicie correctamente, establecemos un true para la variable g_bCorriendo. De lo contrario, devolvemos el valor de 1 para salir del main. Lo siguiente es un bucle donde mientras g_bCorriendo sea verdadero ejecutaremos a renderizar. Este es el ciclo de juego donde mientras se cumpla la condicion se llamara a renderizar. Por ultimo, tenemos a SDL_Quit para salir del programa. Si lo compilan y ejecutan deben obtener una ventana como la siguiente

Es la misma ventana que teniamos en el ejemplo del post anterior pero con la diferencia de que ahora no saldra nunca porque el bucle es infinito. Para finalizarlo, vamos a tomar el bucle que se encarga del ciclo del juego y lo modificaremos de la siguiente manera:
int contar = 0;
while (g_bCorriendo) {
contar++;
renderizar();
SDL_Delay(1000);
if (contar > 5) g_bCorriendo = false;
}
Primero iniciaremos una variable, la cual la incrementaremos en el bucle siguiente. Seguimos llamando a renderizar para mostrar en la ventana el mensaje. Agregamos una demora de un segundo mediante SDL_Delay. Para finalmente tener un condicional donde si la nueva variable es mayor que cinco le cambia el valor a g_bCorriendo, lo cual hara que salgamos del bucle. Con estas modificaciones realizadas, compilemos y veamos como es la nueva salida
Ahora si, al cumplirse la condicionn del ciclo del juego se da por terminado y procede a finalizarlo. Esto representa cuando en un juego se nos termina la energia o vidas de nuestro jugador. Una de las particularidades que mencionamos al inicio, es crear una base reutilizable para poder crear cualquier tipo de juego. El ciclo de un juego puede ser resumido de una forma muy generica de la siguiente manera:
void iniciar()
{
... instrucciones ...
}
void renderizar()
{
... instrucciones ...
}
void actualizar()
{
... instrucciones ...
}
void manejaEventos()
{
... instrucciones ...
}
void limpiar()
{
... instrucciones ...
}
bool g_bCorriendo = true;
int main()
{
iniciar();
while(b_gCorriendo)
{
manejaEventos();
actualizar();
renderizar();
}
limpiar();
}
Al comienzo definiremos las funciones que necesitaremos, este es un caso hipotetico asi que solamente las veremos por arriba el contenido que deberia tener lo reemplazaremos de forma general con la palabra instrucciones pero cada una hara lo siguiente:
- iniciar, sera la encargada de iniciar los subsistemas
- renderizar, sera la encargada de mostrar en pantalla los cambios del juego
- actualizar, sera la encargada de actualizar a todos los elementos del juego
- manejaEventos, sera la encargada de monitorear e interpretar las entradas del usuario
- limpiar, sera la encargada de liberar la memoria cuando sea necesario
Nota:
Cuando hablamos de entrada del usuario o jugador, nos referimos a cuando presiona un boton, el direccional o la pantalla (en el caso de los moviles)
Despues tendremos la variable que se encarga de informar que el juego esta corriendo o siendo usado. En el main, lo primero que haremos sera llamar a iniciar para iniciar todos los subsistemas, despues tendremos un while donde verifica que b_gCorriendo sera verdadero. Si es asi primero verifica que accion hizo el jugador, luego en base a esto actualiza todos los elementos. Por ultimo, llama a renderizar para dibujar los cambios. En actualizar tenemos la parte encargada de ver si debemos pasar a b_gCorriendo a false porque se cumplio la condicion que lo realiza, pero mientras esto no se cumpla seguira en el bucle que hablamos al principio del post. Para cuando se cumpla el caso de salir se considera que el juego se termino por ende debe llamar a limpiar para liberar de la memoria al juego. Esto es de una forma muy, pero muy, basica del ciclo de un juego normal. Mas adelante y con el paso de los posts iremos comprendiendo mejor como trabaja esto.
En resumen, hoy hemos visto como es el ciclo de un juego, que tiene y no tiene que ver con los ciclos de C++, porque tecnicamente no tiene nada que ver aunque su forma de trabajo sea similar, tambien hemos visto la base de lo que debemos crear para tener un codigo que pueda ser optimo para nuestros proyectos, ya que la idea sera crear funciones que puedan ser reutilizadas en distintas partes de nuestro codigo, tambien hemos visto como se crea el ciclo de una forma basica para nuestros codigo y por ultimo un croquis de como puede ser un ejemplo basico de un juego. Espero les haya sido de utilidad.
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





