Je veux me débarrasser de mon modèle de conception tout faire, statique et global, mais comment?

11

Je fais un petit robot de donjon dans l'espace, et j'aimerais entendre quelques conseils sur la façon de rendre le backend du moteur plus agréable. Fondamentalement, actuellement, tout est basé sur un crapload de gestionnaires:

  • BackgroundManager: possède une AddBackground(image, parallax)méthode pour créer des effets de fond sympas.
  • ConfigManager: lit / crée le fichier de configuration et contient également les données lues à partir de ce fichier de configuration.
  • DrawManager: a une Draw(sprite)méthode pour dessiner des choses à l'écran, et des choses comme SetView(view), GetView()etc.
  • EntityManager: contient toutes les entités actives et propose des méthodes pour ajouter, supprimer et rechercher des entités.
  • DungeonManager ( en fait appelé GridManager, mais cela est pour la simplicité): a des méthodes comme GenerateDungeon(), PlaceEnemies(), PlaceFinish()etc.

Maintenant, je ne suis même pas à mi-chemin de la liste de tous mes managers, donc je pense que cela doit changer. Mon jeu est également basé sur les écrans, ce qui le rend plus ennuyeux car je n'ai pas besoin de la moitié des choses que mes managers gèrent, par exemple, le menu principal (je n'ai pas besoin de physique / d'entités / de donjons dans mon menu principal !)

Maintenant, j'ai pensé à rendre les gestionnaires non statiques et à donner à mon ScreenMainGame une instance de tous les gestionnaires spécifiques au jeu. Mais cela fait d'appeler / d'obtenir quoi que ce soit lié aux gestionnaires un énorme gâchis ... par exemple, si je voulais tirer mon donjon de n'importe quel endroit qui n'est pas ScreenMainGame.Draw(), je devrais le faire ...

((ScreenMainGame)ScreenManager.CurrentScreen).GridManager.Draw()

C'est vraiment moche.

Alors, chers développeurs de jeux, l'un de vous a-t-il une idée de comment résoudre ce gâchis? J'apprécierais!

Dlaor
la source
Je ne sais pas exactement comment vous aider, mais je recommanderais de lire un bon livre sur les modèles de conception. J'ai lu les modèles de conception Head First et c'est très facile à comprendre. Les exemples sont en Java, mais je pense que ce serait assez proche de C # pour vous aider. Désolé si ma suggestion est trop novice.
Amplify91
Qu'utilisez-vous pour interfacer avec la carte graphique? XNA? SlimDX?
BlueRaja - Danny Pflughoeft
@BlueRaja: J'utilise SFML.NET.
Dlaor
Dans ce cas, vous devez simplement utiliser les méthodes normales de traitement de ces problèmes en C #: Voir mon dernier lien ci-dessous, ainsi que Qu'est-ce
BlueRaja - Danny Pflughoeft

Réponses:

10

Qu'en est-il d'un moteur basé sur des composants ?

Vous auriez une classe principale nommée Engine, qui conserverait une liste de GameScreens, qui détiendrait elle-même une liste de Components.

Le moteur a une Updateet une Drawméthode à la fois et les appels GameScreen« s Updateet des Drawméthodes qui se passent par chaque composant et appel Updateet Draw.

Présenté comme ça, je conviens que cela ressemble à un design pauvre et répétitif. Mais croyez-moi, mon code est devenu beaucoup plus propre en utilisant une approche basée sur les composants qu'avec toutes mes anciennes classes de gestionnaire .

Il est également beaucoup plus simple de conserver un tel code, car vous passez simplement par une grande hiérarchie de classes et vous n'avez pas à parcourir BackgroundManagertous les arrière-plans spécifiques. Vous avez juste un ScrollingBackground, ParallaxBackground, StaticBackground, etc. , qui tous proviennent d'une Backgroundclasse.

Vous finirez par créer un moteur assez solide que vous pouvez réutiliser sur tous vos projets avec de nombreux composants et méthodes d'assistance fréquemment utilisés (par exemple, un FrameRateDisplayerutilitaire de débogage, une Spriteclasse comme sprite de base avec une texture et des méthodes d'extension pour les vecteurs). et génération de nombres aléatoires).

Vous n'auriez plus de BackgroundManagerclasse, mais une Backgroundclasse qui se gérerait à la place.

Lorsque votre jeu démarre, tout ce que vous avez à faire est essentiellement:

// when declaring variables:
Engine engine;

// when initializing:
engine = new Engine();
engine.Initialize();
engine.LoadContent();
engine.AddGameScreen(new MainMenuScreen());

// when updating:
engine.Update();

// when drawing:
engine.Draw();

Et c'est tout pour votre code de démarrage de jeu.

Ensuite, pour l'écran du menu principal:

class MainMenuScreen : MenuScreen // where MenuScreen derives from the GameScreen class
{
    const int ENEMY_COUNT = 10;

    StaticBackground background;
    Player player;
    List<Enemy> enemies;

    public override void Initialize()
    {
        background = new StaticBackground();
        player = new Player();
        enemies = new List<Enemy>();

        base.AddComponent(background); // defined within the GameScreen class
        base.AddComponent(player);
        for (int i = 0; i < ENEMY_COUNT; ++i)
        {
            Enemy newEnemy = new Enemy();
            enemies.Add(newEnemy);
            base.AddComponent(newEnemy);
        }
    }
}

Vous avez l'idée générale.

Vous conserveriez également la référence de la Engineau sein de toutes vos GameScreenclasses, pour pouvoir ajouter de nouveaux écrans même au sein d'une GameScreenclasse (par exemple, lorsque l'utilisateur clique sur le bouton StartGame dans votre MainMenuScreen, vous pouvez passer au GameplayScreen).

Il en va de même pour la Componentclasse: elle doit contenir la référence de son parent GameScreen, pour avoir à la fois accès à la Engineclasse et à son parent GameScreenpour ajouter de nouveaux composants (par exemple, vous pouvez créer une classe liée au HUD appelée DrawableButtonqui contient un DrawableTextcomposant et un StaticBackgroundcomposant).

Vous pouvez même appliquer d'autres modèles de conception après cela, comme le "modèle de conception de service" (pas sûr du nom exact) où vous pouvez conserver différents services utiles dans votre Engineclasse (vous gardez simplement une liste de IServices et laissez les autres classes ajouter des services elles-mêmes ). Par exemple, je garderais un Camera2Dcomposant sur tout mon projet en tant que service pour appliquer sa transformation lors du dessin d'autres composants. Cela évite d'avoir à le passer partout en paramètre.

En conclusion, il peut certainement y avoir d'autres meilleures conceptions pour un moteur, mais j'ai trouvé le moteur proposé par ce lien très élégant, extrêmement facile à entretenir et réutilisable. Je recommanderais personnellement au moins de l'essayer.

Jesse Emond
la source
1
XNA prend en charge tout cela par le biais de GameComponents et de services. Pas besoin d'une Engineclasse séparée .
BlueRaja - Danny Pflughoeft
+1 pour cette réponse. En outre, serait-il judicieux de mettre le dessin dans un fil distinct du fil de mise à jour du jeu?
f20k
1
@BlueRaja - DannyPflughoeft: En effet, mais j'ai supposé que XNA n'était pas utilisé, j'ai donc expliqué un peu plus comment le faire.
Jesse Emond
@ f20k: Oui, à peu près de la même manière que XNA le fait. Je ne sais pas cependant comment il est mis en œuvre. = / Il doit y avoir un article quelque part sur Internet sur la façon de le faire. :)
Jesse Emond
Ouais, je n'utilise pas XNA mais cela semble être une bonne alternative à ce que je fais. Je lis actuellement le livre "Head First Design Patterns" pour voir s'il y a d'autres alternatives. À votre santé!
Dlaor
1

Ce que vous voulez faire, c'est séparer votre code en composants et services. XNA a déjà un support intégré pour ces derniers.

Voir cet article sur GameComponentset services. Consultez ensuite cette réponse sur l'utilisation des services dans XNA, et cette réponse plus générale sur les services dans n'importe quelle langue.

BlueRaja - Danny Pflughoeft
la source
-3

Ce n'est pas parce qu'ils sont statiques ou globaux qu'ils doivent toujours être chargés / initialisés. Utilisez un modèle Singleton et permettez aux gestionnaires d'être libérables et chargés à nouveau sur demande.

Bo Buchanan
la source
4
Un singleton n'est qu'une façade pour le problème sous-jacent que le PO essaie de résoudre ici.