Meilleure façon de stocker des variables à l'échelle du jeu

23

J'ai un écran d'options pour des choses comme la difficulté, la résolution, le plein écran, etc., mais j'ai du mal à trouver la "meilleure" façon de stocker / obtenir ces variables au moment de l'exécution.

Actuellement, j'ai implémenté une Constantsclasse qui contient toutes les GameOptionénumérations, mais comment choisir une valeur par défaut pour toutes ces options? De plus, comment puis-je obtenir l'énumération actuellement sélectionnée?

En ce qui concerne la résolution, en particulier, j'ai décidé de stocker les valeurs, mais je ne sais pas comment obtenir les valeurs par défaut ou actuellement stockées. Toute direction serait formidable; Merci! :)

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: J'ai demandé à SO et ils m'ont indiqué cet endroit. Il y a un commentaire mais j'aimerais entendre différentes façons de le faire / les façons les plus utilisées.

R-nold
la source
1
Vous avez demandé au bon endroit; celui qui vous a envoyé ici s'est trompé. J'ai quand même répondu à la question pour vous aider, mais ce n'est pas une question spécifique au développement de jeux, c'est une question de programmation générale.
jhocking
Je viens de lire le fil SO; J'aime la réponse de Scott Chamberlin.
jhocking
@jhocking Je l'ai indiqué de cette façon au cas où il y aurait des aspects particuliers au développement de jeux qui pourraient différer d'une application ordinaire. J'ai aussi pensé que vous pourriez déjà avoir un Q&A canonique sur ce sujet car c'est si courant.
Chris Hayes
Tangentiel à la question réelle sur les globaux, veuillez ne pas supposer qu'il existe un ensemble fixe de résolutions.
Lars Viklund

Réponses:

32

Planification de la croissance:
les constantes codées en dur conviennent parfaitement aux petits projets, mais, à mesure que la taille de votre logiciel augmente, vous souhaiterez pouvoir modifier ces paramètres sans avoir à tout recompiler. Il y a plusieurs fois où vous voudrez modifier les paramètres pendant le jeu et vous ne pouvez pas le faire avec des constantes codées en dur.

CVars:
Une fois que votre projet se développe, vous voudrez peut-être jeter un œil aux CVAR . Un CVAR est, pour ainsi dire, une "variable intelligente" que vous pouvez modifier pendant l'exécution via une console, un terminal ou une interface utilisateur. Les CVAR sont généralement implémentés en termes d'un objet qui encapsule une valeur sous-jacente. L'objet peut alors garder une trace de la valeur ainsi que l'enregistrer / la charger dans / depuis le fichier. Vous pouvez stocker les objets CVAR dans une carte pour y accéder avec un nom ou un autre identifiant unique.

Pour illustrer le concept un peu plus loin, le pseudo-code suivant est un exemple simple d'un type CVAR qui encapsule une intvaleur:

// 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;

Accès global:
dans l'exemple ci-dessus, j'ai supposé que le constructeur de CVarenregistre toujours la variable avec la cvarscarte globale ; c'est assez utile, car cela vous permet de déclarer une variable comme ceci:

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

Cette variable est automatiquement mise à disposition dans la carte globale et vous pouvez y accéder de n'importe où ailleurs en indexant la carte avec le nom de la variable:

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

Persistance:
lorsque le jeu s'arrête, itérez la carte et enregistrez toutes les variables marquées comme CVAR_PERSISTENT, dans un fichier. La prochaine fois que le jeu démarre, rechargez-les.

Jurisprudence:
Pour un exemple plus spécifique d'un système CVAR robuste, consultez l'implémentation présentée dans Doom 3 .

glampert
la source
4

Eh bien d' abord un enum définit ce que les valeurs peuvent être , pas ce que les valeurs sont . Ainsi, vous devez toujours déclarer une autre variable après avoir déclaré l'énumération. Par exemple:

public enum Sound
{
    ON,
    QUIET,
    OFF
}

public Sound soundValue;

Dans cet exemple, vous pouvez maintenant définir soundValueON, QUIET ou OFF.


Ensuite, vous devez encore structurer votre code afin que d'autres parties de votre code puissent accéder à cet objet "paramètres". Je ne sais pas si vous avez également besoin d'aide pour cette partie, mais les modèles courants pour résoudre ce problème incluent les singletons (ceux-ci sont désapprouvés ces jours-ci) ou les localisateurs de service ou l'injection de dépendance.

jhocking
la source
1

La solution glampert est très complète, mais j'ajouterai mon expérience personnelle.

J'ai rencontré ce même problème et ma solution a été d'utiliser une classe de variables statiques.

La classe Variables conserve en interne une carte de chaîne en chaîne (jusqu'à présent, toutes mes variables ne sont que des chaînes) et est accessible via des getters et des setters.

Le fait est que l'accès aux variables globales peut introduire toutes sortes d'erreurs subtiles car des parties totalement indépendantes du code interfèrent soudainement les unes avec les autres.

Afin d'éviter cela, j'ai imposé la sémantique suivante: l'utilisation de la setméthode lève une exception si une variable portant ce nom existe déjà dans le dictionnaire et getsupprime la variable du dictionnaire avant de la renvoyer.

Deux méthodes supplémentaires fournissent ce que vous attendez, setAndOverwriteet getAndKeep. Le point de la sémantique des autres méthodes est que vous pouvez facilement repérer des erreurs du type "cette méthode est censée initialiser cette variable, mais une autre méthode l'a fait avant".

Afin d'initialiser le dictionnaire, les variables initiales sont stockées dans un fichier json, puis lues au démarrage du jeu.

Malheureusement, je ne suis pas encore allé trop loin avec mon jeu, je ne peux donc pas témoigner de la robustesse de cette approche. Pourtant, cela peut peut-être fournir quelque chose d'intéressant en plus des CVAR.

angarg12
la source