La mejor manera de almacenar variables de todo el juego

23

Tengo una pantalla de opciones para cosas como dificultad, resolución, pantalla completa, etc., pero estoy luchando por encontrar la "mejor" forma de almacenar / obtener estas variables en tiempo de ejecución.

Actualmente, he implementado una Constantsclase que contiene todas las GameOptionenumeraciones, pero ¿cómo elijo un valor predeterminado para todas estas opciones? Además, ¿cómo obtengo la enumeración seleccionada actualmente?

En cuanto a la resolución, específicamente, he decidido almacenar los valores, pero no estoy seguro de cómo obtener los valores predeterminados o almacenados actualmente. Cualquier dirección sería genial; ¡Gracias! :)

namespace V1.test.RPG
{
  public class GameOptions
  {
    public enum Difficulty { EASY, MEDIUM, HARD }
    public enum Sound { ON, QUIET, OFF }
    public enum Music { ON, QUIET, OFF }
    public enum ResolutionWidth
    {
        SMALL      = 1280,
        MEDIUM     = 1366,
        LARGE      = 1920,
        WIDESCREEN = 2560
    }
    public enum ResolutionHeight
    {
        SMALL      = 800,
        MEDIUM     = 768,
        LARGE      = 1080,
        WIDESCREEN = 1080
    }
    public Boolean fullScreen = false;
  }
}

NB: pregunté a SO y me señalaron este lugar. Hay un comentario allí, pero me gustaría escuchar diferentes formas de hacerlo / las formas más utilizadas.

R-nold
fuente
1
Preguntaste en el lugar correcto; quien te envió aquí estaba equivocado. Respondí la pregunta de todos modos para ayudarte, pero esta no es una pregunta específica de desarrollo de juegos, esta es una pregunta general de programación.
jhocking
Acabo de leer el hilo SO; Me gusta la respuesta de Scott Chamberlin.
jhocking
@jhocking Lo señalé de esta manera en caso de que haya aspectos particulares del desarrollo del juego que puedan diferir de una aplicación ordinaria. También pensé que ustedes podrían tener preguntas y respuestas canónicas sobre este tema, ya que es muy común.
Chris Hayes
Tangencial a la pregunta real sobre los globales, no asuma que hay un conjunto fijo de resoluciones por ahí.
Lars Viklund

Respuestas:

32

Planificación para crecer:
las constantes codificadas están bien para proyectos pequeños pero, eventualmente, a medida que su software crezca en tamaño, deseará poder cambiar esas configuraciones sin tener que volver a compilar todo. Hay muchas veces que querrás cambiar la configuración mientras el juego se está ejecutando y no puedes hacerlo con constantes codificadas.

CVars: una
vez que su proyecto crezca, es posible que desee echar un vistazo a CVAR . Un CVAR es una "variable inteligente", por así decirlo, que puede modificar durante el tiempo de ejecución a través de una consola, terminal o interfaz de usuario. Los CVAR generalmente se implementan en términos de un objeto que envuelve un valor subyacente. El objeto puede, entonces, realizar un seguimiento del valor, así como guardarlo / cargarlo en / desde el archivo. Puede almacenar los objetos CVAR en un mapa para acceder a ellos con un nombre u otro identificador único.

Para ilustrar un poco más el concepto, el siguiente pseudocódigo es un ejemplo simple de un tipo CVAR que envuelve un intvalor:

// just some flags to exemplify:
enum CVarFlags {
    CVAR_PERSISTENT, // saved to file once app exits
    CVAR_VOLATILE    // never saved to file
};

class CVar {
public:
    // the constructor registers this variable with the global list of CVars
    CVar(string name, int value, int defaultValue, int flags);

    int getValue();
    void setValue(int v);
    void reset(); // reset to the default value

    // etcetera...

private:
    int flags; // flags like: save-to-file, etc.
    int value; // the actual value
    int defaultValue; // the default value to reset the variable to
};

// global list of all CVars:
map<string, CVar> g_cvars;

Acceso global:
en el ejemplo anterior, supuse que el constructor de CVarsiempre registra la variable con el cvarsmapa global ; Esto es bastante útil, ya que le permite declarar una variable así:

CVar my_var = new CVar("my_var", 0, 42, CVAR_PERSISTENT);

Esa variable se pone automáticamente a disposición en el mapa global y puede acceder a ella desde cualquier otro lugar indexando el mapa con el nombre de la variable:

CVar v = g_cvars.find("my_var");

Persistencia:
cuando el juego se está cerrando, itera el mapa y guarda todas las variables marcadas como CVAR_PERSISTENT, en un archivo. La próxima vez que comience el juego, vuelve a cargarlos.

Jurisprudencia: para
obtener un ejemplo más específico de un sistema CVAR robusto, consulte la implementación presentada en Doom 3 .

glampert
fuente
4

En primer lugar, una enumeración define cuáles pueden ser los valores , no cuáles son los valores . Por lo tanto, aún debe declarar otra variable después de haber declarado la enumeración. Por ejemplo:

public enum Sound
{
    ON,
    QUIET,
    OFF
}

public Sound soundValue;

En este ejemplo, ahora puede establecerlo soundValueen ON, QUIET u OFF.


Entonces aún necesita estructurar su código para que otras partes de su código puedan acceder a este objeto de "configuración". No sé si necesita ayuda con esa parte también, pero los patrones comunes para abordar este problema incluyen singletons (que están mal vistos en estos días) o localizadores de servicios o inyección de dependencia.

jhocking
fuente
1

La solución glampert es muy completa, pero agregaré mi experiencia personal.

Me encontré con este mismo problema, y ​​mi solución fue usar una clase de variables estáticas.

La clase Variables mantiene internamente un mapa de cadena a cadena (hasta ahora todas mis variables son solo cadenas) y se accede a través de getters y setters.

El punto es que obtener acceso a variables globales puede introducir todo tipo de errores sutiles, ya que partes del código totalmente no relacionadas interfieren repentinamente entre sí.

Para evitar esto, impuse la siguiente semántica: el uso del setmétodo arroja una excepción si ya existe una variable con ese nombre en el diccionario, y getelimina la variable del diccionario antes de devolverlo.

Dos métodos adicionales proporcionan lo que esperaría, setAndOverwritey getAndKeep. El punto de la semántica de los otros métodos es que puede detectar fácilmente errores del tipo "se supone que este método inicializa esta variable, pero otro método lo hizo antes".

Para inicializar el diccionario, las variables iniciales se almacenan en un archivo json y luego se leen cuando comienza el juego.

Lamentablemente, todavía no me he alejado demasiado con mi juego, por lo que no puedo testificar la solidez de este enfoque. Aún así, tal vez pueda proporcionar algo interesante además de los CVAR.

angarg12
fuente