Seguimos explicando los elementos esenciales del Genbeta Dev Engine, en esta ocasión vamos a ver como está implementado el Scene Manager y su vínculo con la la clase App que es el punto de acceso a nuestro engine que ya explicamos en el artículo anterior.
Concepto de escena
Una escena en un videojuego es un conjunto de objetos relacionados que tienen sentido juntos y se muestran en el mismo escenario. Esta definición puede parecer algo rara, pensemos en las escenas como las diferentes pantallas que se dan en un videojuego: Introducción inicial, menú principal, mapa del juego, menú de opciones, etc. Todos estas pantallas tienen una serie de objetos relacionados que interactuan con el usuario o entre sí. Las escenas (scene) son también llamadas pantalla (screen) o estado (state).
Como vemos la escenas son grupos aislados, si estás una en pantalla no pueda haber otra por lo que el usuario solo puede interactuar con una escena a la vez. Así que sería lógico aislar cada escena en una clase diferente para que no se mezclen entre sí.
Vamos a recordar de manera simplificada como sería el Game Loop de un videojuego. Una vez arrancado el juego se entra en un bucle que se ejecuta indefinidamente hasta que se sale del juego. Dentro de este bucle vamos a hacer en cada iteración lo siguiente:
Procesar eventos. Capturamos todos los eventos exteriores que llegan a nuestro juego (pulsación de tecla, pérdida de foco de la ventana, cierre...) y actuamos en consecuencia.
Actualizar (update). En este método procesamos toda la parte lógica del juego, aplicando la física, reglas, etc. Al final de este método los objetos deben de tener todos sus nuevos valores como posiciones, estados, etc. Digamos que se encarga de "Simular" la acción.
Dibujar (draw). Una vez procesado los eventos y actualizado el sistema es hora de mostrar el nuevo estado en pantalla. Lo hacemos con el método draw que se encarga de dibujar los objetos en pantalla atendiendo a los valores de los objetos visibles de la escena.
Esto sería una versión simplificada del llamado Game Loop de los videojuegos. Ahora pensemos en nuestras escenas. Está claro que si estamos en la pantalla del menú principal no tiene mucho sentido estar procesando los eventos, actualizando la lógica o dibujando los objetos de una escena que sea un mapa del juego ya que ni el usuario está interactuando con ella ni se va a mostrar en pantalla.
Sería mucho más lógico que en lugar de usar 3 métodos comunes para procesar la lógica cada escena tuviera sus propios métodos para procesar eventos, actualizar la lógica y dibujar. Según la escena que estuviera activa en un momento determinado llamaríamos a los métodos de una escena u otra.
La clase escena
Así pues metiéndonos ya en concepto de programación lo ideal sería tener una clase Scene abstracta de la que heredan todas las escenas del videojuego que tenga los tres métodos virtuales puros que deben encargarse de implementar cada escena. A continuación son llamados desde el Game Loop estos tres métodos. Una escena podría ser algo así.
Esta es una versión simplificada de la que usamos en el proyecto GDE, donde la esencia es la misma, pero tenemos algunos métodos más como pause o resume que se llamados al pausarse la escena y al volver a ella.
Como vemos los métodos son virtuales puros, la clase Scene solo es una interfaz que dice que requisitos mínimos debe cumplir una escena, queda a disposición del programador implementar la manera de actuar de cada escena.
Hago un paréntesis para hablar del método draw. En esta primera versión el método draw es virtual puro, pero la idea es que en futuras versiones sea simplemente virtual. Será la propia escena la que dibuje los objetos gráficos en función de si son visibles o no. Así el programador solo deberá hacer algo como objet.hide() para que un objeto no se dibuje o object.show() si quiere que vuelva a ser visible. Con esto se consigue que sea la propia escena la que dibuje los objetos que pertenezcan a ella y pueda gestionar más eficientemente que se debe dibujar y que no, por ejemplo, evitando dibujar todo aquello que no se está viendo en la ventana en ese momento. Explicaremos todo esto mejor en un artículo dedicado llegado el momento.
El Scene Manager
Bien ahora que tenemos escenas necesitamos alguna forma de gestionarlas. Añadir escenas, eliminar escenas, cambiar de escena activa, etc.
El método de la Pila
Un método común para la gestión de escenas es tener una pila donde se van llamando a los métodos del último elemento añadido a la pila, si queremos volver a la escena anterior hacemos "pop()" de la escena actual y la quitamos de la pila. Este método lo explican muy bien en siondream.
La desventaja de este método es que no es nada flexible debido a que solo podemos volver a la escena anterior o añadir una nueva que no esté en la pila. Imaginemos los siguientes casos.
Hemos añadido 10 escenas a nuestra pila y queremos volver a la segunda que añadimos ¿Hacemos 8 veces "pop"?
Queremos añadir una escena a la pila que ya está más abajo. ¿La duplicamos en la pila?
Como vemos este método que es el que se suele explicar limita bastante. Así que nosotros nos decidimos por uno más flexible.
El método Mapa de Escenas
Nosotros lo que haremos será tener una lista de escenas de escenas inactivas y una escena activa. La clase SceneManager se encargará de gestionar cual es la escena activa e intercambiarla con su lista de escenas inactivas cuando se requiera.
Nuestra clase SceneManager sería algo así.
Creo que los comentarios del código son autoexplicativos de como usar cada método. Merece la pena comentar unos cuantos detalles:
Como vemos existen 2 métodos muy parecidos changeScene y setActiveScene. El primero privado y el segundo público. La razón de esto es que el segundo solicita activar una nueva escena al SceneManager, pero no la activa. De esto se encarga changeScene que lo hace cuando es seguro hacerlo. Esto es así para que no se cambie de escena en medio del Game Loop sino que se espera al final de la iteración actual para evitar fallos.
Otro caso curioso es ese friend class App
. Lo que hacemos es dar acceso a los métodos privados del SceneManager a la clase App que es la que gestiona la aplicación, con esto le dapos poder para llamar al método changeScene al final de cada iteración del bucle si es necesario y también a los métodos eventScene, updateScene y drawScene que se encargan de llamar a los respectivos métodos de la escena activa actual.
Este sistema es mucho más flexible que el de la pila ya que el procedimiento es el siguiente:
Añadir una escena al SceneManager con AddScene. Se añade a la lista de escenas inactivas
Activar una escena con setActiveScene. Busca en la lista de escenas inactivas y la activa, pasando la actual escena activa a la lista de inactivas. Se llaman a los respectivos métodos desactivate y activate de las escenas que son ejecutados en cada cambio de escena.
Una consideración a tener en cuenta es que debemos indicar al engine antes que nada cual es la escena inicial que debe empezar a ejecutar, para esto hemos añadido a la clase App el método setFirstScene que establecerá el punto de entrada inicial de la aplicación antes de arrancar ya desde esta escena inicial podremos llamar a nuevas escenas, cambiarlas, etc
Espero que haya quedado más o menos clara la implementación del SceneManager, siempre es importante mirar el código para entenderlo en su concepto en este caso se recomienda mirar la clase App y SceneManager para comprender bien la integración de estas dos clases.
En Genbeta Dev | Proyecto GDE En Github | Genbeta Dev Engine
Ver todos los comentarios en https://www.genbeta.com
VER 0 Comentario