¿Cómo puedo actualizar la configuración de Pantalla desde una pantalla de Opciones sin reiniciar?

9

Actualmente estoy creando un RPG 2D en C ++ 11 con Allegro 5 y boost.

Mi objetivo es actualizar de alguna manera la configuración de mi juego cuando se cambia una opción en el Menú de opciones. No quiero obligar al usuario a reiniciar mi juego. Otros juegos no requieren reinicio al cambiar la resolución o pasar de pantalla completa a ventana, por lo que mi juego tampoco debería. Vea una vista simplificada del sistema a continuación.

Tenga en cuenta que no necesariamente quiero llamar directamente a mi objeto Juego desde la pantalla Opciones. La línea punteada es simplemente para ilustrar el efecto que estoy tratando de lograr; causar de alguna manera una actualización del juego cuando se cambia una opción en una parte diferente del sistema.

Explicación detallada

ScreenManager contiene una lista de todos los GameScreenobjetos que existen actualmente. Estas serán varias pantallas en el juego, incluidas ventanas emergentes. Este diseño se adhiere más o menos a la muestra de Game State Management en C # / XNA .

El ScreenManagercontiene una referencia a mi Gameobjeto. El Gameobjeto inicializa y modifica la configuración del juego. Si quiero cambiar la resolución, ir a pantalla completa o silenciar el volumen, lo haría en la Gameclase.

Sin embargo, la pantalla OptionsScreen actualmente no puede acceder a la clase Game. Vea el diagrama a continuación:

Un GameScreen puede señalar tres eventos onFinished, onTransitionStarty onTransitionEnd. No hay onOptionsChangedporque solo una pantalla hace eso. ScreenManager no puede configurar el manejo de eventos para eso porque maneja todas las pantallas como GameScreens.

Mi pregunta es, ¿cómo puedo cambiar mi diseño para que un cambio en el menú Opciones no requiera un reinicio, sino que se cambie de inmediato? Preferiblemente solicitaría que mi Gameobjeto se actualice una vez que se haga clic en el botón Aplicar.

IAE
fuente
Esta pregunta (aunque está relacionada con el juego) parece perfecta para programmers.stackexchange.com porque se trata más del diseño general de OO que del juego real
bughi
¿De dónde sacaste unos gráficos tan impresionantes?
joltmode
@psycketom: El primero es de Visio 2013 y el segundo es generado a partir del código por VS2012. ¡Espero que ayude!
IAE

Respuestas:

1

Por lo que he visto, el enfoque más fácil es leer un archivo de opciones en el inicio para determinar la configuración de visualización actual; luego, cuando se muestre la pantalla de opciones, cargue todas las opciones actuales de un archivo.

Cuando los cambios se finalizan mediante un botón applyo ok, se guardan nuevamente en un archivo. Si algún cambio afecta la pantalla, notifique al usuario que el juego debe reiniciarse para que surta efecto.

Cuando se reinicia el juego, la configuración de pantalla (ahora nueva) se vuelve a leer del archivo.

--EDITAR--

Y ... habría ayudado si me hubiera dado cuenta de la última frase. No quieres tener que reiniciar. Hace las cosas un poco más difíciles dependiendo de su implementación y su biblioteca de gráficos de fondo.

IIRC, Allegro tiene una llamada de función que le permite cambiar la configuración de la pantalla sobre la marcha. Todavía no estoy actualizado en Allegro 5, pero sé que podrías hacerlo en 4.

Casey
fuente
No creo que el problema esté relacionado con Allegro y sus capacidades. "Mi pregunta es, ¿cómo puedo cambiar mi diseño para que un cambio en el menú Opciones no requiera un reinicio, sino que se cambie de inmediato?" El reinicio se debe a que "OptionsScreen actualmente no puede acceder a la clase Game" y tiene que cargarlos desde el archivo de configuración si lo entiendo correctamente.
ClassicThunder
@Casey: Hola Casey! :) Sí, estoy usando un archivo de configuración para almacenar los datos de visualización relevantes, pero quiero poder evitar el reinicio. Es un juego pequeño, y otros juegos pueden cambiar la resolución sin tener que reiniciar, así que no veo por qué debería obligar al usuario a reiniciar con el mío. Es un problema de usabilidad. Si bien podría llamar a las funciones de allegro dispay, estoy tratando de mantener la OOP y no mezclar mis llamadas de gráficos solo porque puedo; No considero ese buen diseño.
IAE
Destaqué el bit "sin reiniciar" para que otros no tropiecen con la misma última oración. Un hecho tan importante no debería haber llegado al final, me disculpo):
IAE
@SoulBeaver ¿Quién dice que debes hacerlo para que no reinicies? Es viable exigir eso, de hecho, se está volviendo más común hoy en día. Algunos lanzamientos recientes (XCOM: Enemy Unknown, Skyrim, etc.) de juegos de gran presupuesto requieren un reinicio. (el hecho de que estén en Steam puede ser el culpable debido a su sincronización de opciones en el lado del servidor, pero no vayamos allí ...)
Casey
1
@Casey: Cierto, y para ciertas opciones puedo ver por qué eso es incluso necesario, pero para cosas como pantalla completa / ventana o la resolución de pantalla. Nunca he visto un juego que requiera un reinicio para esa configuración. Mi pregunta básica es: ¿por qué debería obligar a mi usuario a reiniciar cuando el juego puede cambiar la configuración automáticamente?
IAE
1

Esto es lo que hago por mi juego. Tengo 2 funciones separadas para inicializar cosas, 'init' y 'reset'. Init solo se llama una vez al inicio y hace cosas que no dependen de ninguna configuración, como cargar activos principales. Restablecer hace cosas como diseñar la interfaz de usuario en función de la resolución de la pantalla, por lo que se llama cada vez que cambia la configuración.

init();
bool quit = false;
while( !quit )
{
    createWindow();
    reset();

    bool settings_changed = false;
    while( !quit && !settings_changed )
    {
        ... main loop
        // set quit=true if user clicks 'quit' in main menu
        // set settings_changed=true if user clicks 'apply' in options
    }

    destroyCurrentWindow();
}

No estoy familiarizado con Allegro, pero mi respuesta es bastante general, así que espero que te ayude a ti oa cualquier otra persona con un problema similar.

DaleyPaley
fuente
Esa es una solución interesante. Estaba tratando de tener un control estricto cada vez que se llama a un reinicio, pero lo haces independientemente de un cambio. Eso es definitivamente algo que podría implementar, y lo investigaré, ¡gracias!
IAE
1

Sin alterar su arquitectura actual, veo dos formas. Primero, puede almacenar un puntero a la Gameinstancia en la OptionsScreenclase. En segundo lugar, puede que la Gameclase obtenga la configuración actual en un intervalo determinado, digamos cada segundo.

Para adaptarse realmente a la nueva configuración, la Gameclase debe implementar algún tipo de funciones de reinicio que recupere la configuración actual y se reinicialice en función de ellas.

Para una solución limpia, necesita un administrador global de algún tipo, por lo que es más difícil de implementar. Por ejemplo, un sistema de eventos o sistema de mensajería. Es muy útil dejar que las clases se comuniquen sin enlaces tan fuertes como la agregación o la composición, etc.

Con un administrador de eventos global, OptionsScreensimplemente podría disparar globalmente un evento redibujado que se Gamehaya registrado para escuchar antes.

En general, puede implementar una clase de administrador para almacenar eventos y devoluciones de llamada escuchándolos en un mapa hash. Luego puede crear una sola instancia de ese administrador y pasarle punteros a sus componentes. Usar C ++ más nuevo es bastante fácil, ya que puede usarlo std::unordered_mapcomo mapa hash y std::functionalmacenar devoluciones de llamada. Existen diferentes enfoques que puede usar como clave. Por ejemplo, puede hacer que el administrador de eventos se base en una cadena que hace que los componentes sean aún más independientes. En ese caso, usaría std::stringcomo clave en el mapa hash. Personalmente me gusta esto y definitivamente no es un problema de rendimiento, pero la mayoría de los sistemas de eventos tradicionales funcionan con eventos como clases.

danijar
fuente
Hola danijar Ya estoy usando boost :: señales para configurar un esquema rudimentario de manejo de eventos. La línea fuerte desde GameScreen hasta ScreenManager es en realidad ese manejo de eventos. ¿Tiene algún recurso que detalle la arquitectura de un administrador de eventos global? Más trabajo o no, esto parece que podría conducir a una implementación limpia.
IAE
Aunque no he leído ninguna investigación sobre esto, implementé un administrador de eventos de eventos global para mi pequeño motor. Si está interesado en la implementación, le enviaré un enlace. Actualicé mi respuesta para cubrir más detalles.
danijar
1

Bueno, este es un caso específico del patrón Observador.

Hay una solución que implica devoluciones de llamada. Esta es la mejor manera de hacer esto si desea un acoplamiento flojo, y creo que también es el más limpio. Esto no implicará ningún administrador global o singletons.

Básicamente, necesitarás tener algún tipo de SettingsStore. Ahí guarda la configuración. Cuando cree una nueva pantalla, necesitarán un puntero a la tienda. En el caso de OptionsScreenque modificará algunos de los valores de configuración en sí. En el caso del GameScreensolo los leerá. Por lo tanto, en su juego crearía solo una instancia, que se pasará por todas las pantallas que lo requieran.

Ahora, esa SettingsStoreclase tendrá una lista de notifiables. Son clases que implementan una determinada ISettingChangedinterfaz. La interfaz sería simple y contiene el siguiente método:

void settingChanged(std::string setting);

Luego, en su pantalla implementará la lógica para cada configuración que le interese. A continuación, agregarse a la tienda para ser notificado: store->notifyOnChange(this);. Cuando se cambia una configuración, la devolución de llamada se llama con el nombre de la configuración. El nuevo valor de configuración se puede recuperar de SettingsStore.

Ahora, esto se puede aumentar aún más con las siguientes ideas:

  • Utilice una clase que almacene las cadenas de configuración; puede ser incluso la SettingsStore(cadena de constante) para evitar que se copien las cadenas.
  • Aún mejor, en lugar de una cadena, use una enumeración para especificar en qué configuración tiene
  • Incluso podría especificar los valores antiguos y nuevos como argumentos en la devolución de llamada, pero eso sería más complicado, ya que puede tener valores de cadena o int, o lo que sea. Tu decides.
Timotei
fuente
0

Lea su configuración de un archivo en variables. Haga que su Administrador de pantalla rastree si la pantalla de la que acaba de salir era la pantalla Opciones y, si lo fuera, vuelva a cargar su configuración desde las variables. Cuando el usuario salga de su juego, vuelva a escribir la configuración en las variables en el archivo.

131nario
fuente