Razón Artificial

La ciencia y el arte de crear videojuegos

[BGE] La clase SceneManager

Escrito por adrigm el 26 de septiembre de 2011 en Programación | 7 Comentarios.

Antes de ver la clase App vamos a  ver el SceneManager, ya que hemos visto en primer lugar la clase Scene ahora vamos a ver la clase que va a manejar los objetos Scene y decidir cual esta activo y cual no.

Sobre SceneManager he visto muchas formas de implementarlo, la más común es utilizar una pila donde haciendo push y pop se puede cambiar de escena fácilmente, la desventaja de una pila es la poca flexibilidad para ir de una escena a otra, veamos un ejemplo práctico.

Añadimos una escena inicial:
>> scene_manager->push("SceneStart")
Ahora "SceneStart" es la escena activa.
>> scene_manager->push("SceneMenuPrincipal")
Ahora "SceneMenuPrincipal" es la escena activa.
>> scene_manager->push("SceneGame")
Ahora "SceneGame" es la escena activa
>> scene_manager->pop()
Ahora "SceneMenuPrincipal" es la escena activa

Parece un buen método, es fácil poner una escena y volver a la anterior mediante pop, pero, ¿Y si queremos volver 2 escenas atrás? Pues hacemos 2 veces pop y solucionado, ¿Y si son 15? Esto ya empieza a ser mas engorroso. ¿Y que sucede si queremos hacer push de una escena que ya está en la pila?

Como vemos es un sistema muy rígido que no permite cambiar a la escena que queramos con facilidad.

Mi idea es utilizar un simple array con una lista de escenas inactivas y una variable con un puntero a la escena activa.

/// Escena actualmente activa
Scene* active_scene;
// Lista de escenas inacticas
std::vector<Scene*> inactives_scenes;

Estas dos objetos pertenecen a la clase SceneManager y con diferentes métodos los controlaremos. Puedes ver los diferentes métodos de la clase y su documentación en GitHub

Como ves tenemos métodos para añadir escenas, cambiarla, eliminarlas, eliminarlas todas, etc. Todas están perfectamente documentadas, pero si surgen dudas podéis dejar un comentario.

Un detalle es que los métodos ChangeScene y RemoveAllScene son privados. Esto es así por que el primero se encarga de cambiar la variable scene_active y si no se hace en el sitio correcto dentro de la clase App puede haber un error, se verá mejor cuando veamos la clase App. En cuanto a RemoveAllScene hace lo mismo que RemoveAllInactiveScene, pero también destruye la escena activa, se usa como limpieza cuando se termina la aplicación.

Como vemos tenemos a la clase App como amiga, esto es así porque nos interesa que pueda acceder a estos métodos privados y a la escena activa.

Otro detalle importante es que la clase está diseñada con el patrón Singleton, esto hace que solo pueda haber un SceneManager y no se cree más de uno por error.

7 Comentarios en "[BGE] La clase SceneManager"

  1. Drazul dice:

    Hola buenas.
    LLevo tiempo siguiendo tu blog y queria felicitarte por el gran trabajo que estas haciendo en hacer tutoriales y explicar lo que hace cada clase del BGE.

    Entro a preguntar sobre tu decision de utilizar un array en vez de una pila para las escenas activas. Yo diria que una pila seria mas eficiente en cuanto a cantidad de memoria, al menos en este caso, ya que el pop, ademas de devolvernos la anterior escena activa, elimina de la pila la ultima, por lo que vas recuperando memoria. Por otro lado no se me ocurre ninguna situacion en la que haya tantas escenas en la pila para que sea tan engorroso como dices. Una situacion comun en practicamente todos los juegos y que yo supongo que implementaran con una pila (para almacenar las escenas en pausa) son los menus de juego, por ejemplo:

    -SceneGame
    -SceneMenuPrincipal
    -SceneMenuOpciones
    -SceneMenuVideo

    Ahora mismo esta seria la pila mas grande que se me ocurre que se podria encontrar en un juego.

  2. adrigm dice:

    si te fijas, realmente lo que guarda el array no son las escenas en sí, sino un puntero a la escena, es decir, simplemente una dirección de memoria que lo que ocupa es ínfimo. Cuando una escena se elimina del array, el SceneManager se encarga de llamar al método cleanup y al destructor de la escena.

    Una pila lo que vuelve eficiente es la velocidad de acceso, pero como bien dice necesitaríamos una pila de escenas enorme para que se notara algo, buscar en un array de unas 10-15 escenas como mucho no tiene ningún problema de rendimiento.

  3. Lucas dice:

    Hola,

    No se si te lo han dicho ya, pero tus enlaces a los ficheros de código no funcionan.
    Enhorabuena tienes un blog muy interesante.

  4. adrigm dice:

    Si Lucas, es que he restructurado el engine estoy escribiendo un artículo con los cambios y tengo que cambiar los enlaces, puedes buscar los archivos en la carpeta del repositorio mientras: https://github.com/adrigm/Basic-Game-Engine

  5. Lucas dice:

    Hola otra vez y gracias por el link a los ficheros fuente.

    Otra duda que me ha surgido al ver el código es que tienes dos métodos para establecer la escena activa (SetActiveScene y ChangeScene) ¿porque?.

    Gracias y un saludo

  6. adrigm dice:

    Lucas, si te das cuenta SetActiveScene es publica y ChangeScene es privada, la que realmente cambia de escena es ChangeScene si lo miras en la implementación, SetActiveScene lo único que hace es cambiar una variable diciendo que la escena necesita ser cambiada, esta variable se comprueba al final del método Loop de la clase App que se encarga de llamar al método ChangeScene. Si te das cuenta App puede acceder al método ChangeScene porque declaramos a App friend de SceneManager que le permite acceder a sus métodos privados.

    ¿Por qué tanto lio y no llamas directamente a ChangeScene? Con esto se evitan los llamados fallos de segmentación, recuerda que en realidad lo unico que cambiamos al cambiar de escena es un punterohacia una dirección u otra, si permitieramos cambiar ese puntero cuando quisiera el usuario del engine posiblemente habrían fallos de memoria. Con este método la perición de cambio se realiza al final del bucle despues de los métodos event, update y draw evitando fallos.

  7. Lucas dice:

    Ahh ok, cierto, no me había fijado que en el método SetActiveScene, no cambias el puntero a la escena activa sino una variable que indica la escena a cambiar en cuanto acabe el Loop.

    Otra cosa, porque sacas y metes punteros en la lista inactiva, no seria mas facil tener la lista de todas las escenas cargadas y un int sceneactiva con el indice de la lista de la scene activa, no se si me explico.

Deja un comentario