Este tutorial es una fusión de dos que he encontrado, uno es el de la pagina de sdl y otro es un tutorial de sdl 1.2. Las páginas, por si alguien está interesado, son:
- http://www.sdltutorials.com/sdl-tutorial-basics
- http://www.opengl.org/wiki/Tutorial1:_Creating_a_Cross_Platform_OpenGL_3.2_Context_in_SDL_(C_/_SDL)
Un juego tiene una estructura básica de 4 pasos:
- Inicialización de todo.
- GameLoop o bucle de juego que se divide en 3:
- Control de Eventos.
- Procesamiento del estado de juego y cambios que se realizan en el Modelo.
- Renderización o procesamiento de los gráficos.
- Liberación de recursos
- Finalización del programa.
Un tutorial que vi decía: "No pongas todo en el main", y de 100 lineas se quedo en 70 con dos funciones (gran de avance... ¬¬). Imagino que lo hizo así y no lo complicó más para que los "alumnos" menos avanzados no se perdieran, pero yo he elegido la estructura que viene en el primer enlace, porque me parece fácil de entender si tienes ya cierto rodaje en la orientación a objetos.
Creamos una clase para ello creamos dos ficheros que yo he llamado Demo_1.h y Demo_1.cpp. El primero contiene la declaración de la clase y el segundo su definición como ya sabréis.
El GameLoop es un bucle que hay que indicarle cuando ha de parar por eso se suele poner con un while. Para poder indicarlo necesitamos una variable booleana que cambiemos cuando queramos finalizar. Como nuestro juego se va a desarrollar en una ventana gráfica y no en consola necesitamos una ventana de sdl.
Además de todo eso ponemos la definición de los 5 pasos en forma de funciones y dos funciones más: el constructor y una función para iniciar el juego.
Veréis que hay funciones del tipo OnXXXXX y XXXXX, es cosa mía, el ejemplo trata cada fase como un evento (OnExecute) que a mi me parece mejor llamar con una función imperativa (Execute), son simplemente alias de la función.
#ifndef DEMO_1_H_ #define DEMO_1_H_ #include "SDL2/SDL.h" class Demo_1 { private: SDL_Window* mainwindow; bool running; static const uint32_t WIN_HEIGHT = 512; //px static const uint32_t WIN_WIDTH = 512; //px public: Demo_1(); int Execute(){ return OnExecute(); } bool Init(){ return OnInit(); } void Loop(){ return OnLoop(); } void Render(){ return OnRender(); } void Cleanup(){ return OnCleanup(); } void Event(SDL_Event* Event){ OnEvent(Event); } int OnExecute(); bool OnInit(); void OnEvent(SDL_Event* Event); void OnLoop(); void OnRender(); void OnCleanup(); }; #endif
OnInit es de tipo bool porque debemos saber si se ha hecho bien la inicialización de todos los componentes y la carga de imágenes. Execute devuelve un int de la misma manera que lo devuelven los main:
- 0: ejecución correcta.
- De-otra-manera: ejecución incorrecta.
El resto son procedimientos de los que no necesitamos información que devolver.
Vamos a analizar ahora el .cpp:
El constructor sólo inicializa la ventana a null y la variable del gameLoop a false.
OnEvent, OnLoop y OnRender, los modificaremos mas tarde, de momento no necesitamos nada de ellos. He escrito por la salida estándar para que podamos ver el gameloop como funciona.
OnCleanup simplemente libera la ventana y llama al SDL_Quit.
#include "Demo_1.h" #include <iostream> Demo_1::Demo_1(): mainwindow(0), running(false){} void Demo_1::OnEvent(SDL_Event* Event) { std::cout << "Event" << std::endl; } void Demo_1::OnLoop() { std::cout << "Looping" << std::endl; } void Demo_1::OnRender() { std::cout << "Rendering" << std::endl; } void Demo_1::OnCleanup() { std::cout << "Cleaning" << std::endl; SDL_DestroyWindow(mainwindow); SDL_Quit(); } bool Demo_1::OnInit() { std::cout << "Init!" << std::endl; if(SDL_Init(SDL_INIT_EVERYTHING) < 0) return false; mainwindow = SDL_CreateWindow("Titulo de la Ventana", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WIN_WIDTH, WIN_HEIGHT, SDL_WINDOW_SHOWN); if(!mainwindow) return false; running = true; return true; } int Demo_1::OnExecute() { if (!Init()) return -1; int times = 10; while (running) { Event(NULL); Loop(); Render(); times--; if(!times) running = false; } SDL_Delay(2000); Cleanup(); return 0; }
Realizamos un control de errores e indicamos al programa que ha empezado a correr nuestro juego.
OnExecute básicamente lleva a cabo la estructura que hemos explicado antes. He creado una variable para que el bucle no se quede en un ciclo infinito ya que todavía no sabemos manejar eventos. Después del bucle espera 2 segundos ( SDL_Delay(milisec) ), para poder apreciar la ventana que vamos a manejar. Más tarde limpia los recursos y finaliza con ejecución exitosa (return 0;).
Para poder ejecutar este código nos creamos un main con el siguiente código:
#include "SDL2/SDL.h" int main(int argc, char* argv[]) { Demo_1 d; return d.Execute(); }
Para los que no os lo han explicado nunca y no se os ha ocurrido pensarlo, se puede ver que el init solo se ejecuta una vez al igual que el Cleanup, lo que significa que, si estas dos funciones tienen una carga de trabajo exagerada, es relativamente poco importante. Todo el mundo espera si un programa tiene que cargar al principio, sin embargo las funciones que deberían estar optimizadas son el Loop, el Render y la gestión de Eventos, ya que, siendo optimistas, deberían ejecutarse más de 60 veces por segundo (60fps), y si eso se viera ralentizado, afectaría a la experiencia de juego.
No hay comentarios:
Publicar un comentario