XNA - Classes statiques de bibliothèques de jeux s'exécutant après des extensions de pipeline de contenu, comment éviter le problème?

8

D'accord, formulé de cette façon, je suis sûr que cela semble obscur, mais je ferai de mon mieux pour décrire mon problème.

Tout d'abord, laissez-moi vous expliquer ce qui ne va pas avec mon vrai code source.

Je fais un jeu de plateforme, et voici les composants actuels de mon jeu: un niveau , qui tient sa propre grille de tuiles et aussi les feuilles de tuiles et une tuile , qui garde juste le nom de sa feuille de tuiles et son index dans le feuille de tuile. Tout cela fait partie d'un projet de jeu XNA distinct, nommé GameLib.

Voici comment mes niveaux sont formatés dans un fichier texte:

x x . . . . .

. . . b g r .

. . . . . . .

X X X X X X X

.représente une tuile vide, breprésente une plate-forme bleue, Xreprésente un bloc avec une couleur aléatoire, etc.

Au lieu de faire tout le travail de chargement d'un niveau dans la classe Level , j'ai décidé de profiter de l'extension du pipeline de contenu et de créer un LevelContentPipelineExtension.

Dès que j'ai commencé, j'ai vite réalisé que je ne voulais pas coder en dur 20+ lignes du genre:

if (symbol == 'b')
    return new Tile(/*...*/);

Donc, dans l'espoir de centraliser au moins toute la création de contenu dans une seule classe, j'ai créé une classe statique, appelée LevelContentCreation , qui contient les informations sur le symbole qui crée quoi et tout ce genre de choses. Il contient également une liste de tous les chemins d'accès aux actifs des feuilles de tuiles, pour les charger plus tard. Cette classe fait partie de mon projet de bibliothèque de jeux GameLib.

Le problème est que j'utilise cette classe statique LevelContentCreation à la fois dans ma classe Level (pour charger les feuilles de tuiles réelles) et dans mon extension de pipeline de contenu (pour savoir quel symbole représente quelle tuile) ...

Et voici à quoi ressemble ma classe LevelContentCreation :

public static class LevelContentCreation
{
    private static Dictionary<char, TileCreationInfo> AvailableTiles =
        CreateAvailableTiles();

    private static List<string> AvailableTileSheets { get; set; }

    /* ... rest of the class */
}

Maintenant, puisque la classe LevelContentCreation fait partie de la bibliothèque de jeux, l'extension de pipeline de contenu essaie de l'utiliser mais l'appel à CreateAvailableTiles () ne se produit jamais et je ne peux pas charger un objet Level à partir du pipeline de contenu.

Je sais que c'est parce que le pipeline de contenu s'exécute avant la compilation réelle ou quelque chose comme ça, mais comment puis-je résoudre le problème?

Je ne peux pas vraiment déplacer la classe LevelContentCreation vers l'extension de pipeline, car alors la classe Level ne serait pas en mesure de l'utiliser ... Je suis assez perdu et je ne sais pas quoi faire ... Je connais ces sont de très mauvaises décisions de conception, et j'aimerais que quelqu'un me pointe dans la bonne direction.

Merci pour votre précieux temps.

Si quelque chose n'est pas assez clair ou si je dois fournir plus d'informations / code, veuillez laisser un commentaire ci-dessous.


Un peu plus d'informations sur mes projets:

  • Jeu - Projet de jeu XNA Windows. Contient des références à: Engine, GameLib et GameContent
  • GameLib - Projet de bibliothèque de jeux XNA. Contient des références à: Moteur
  • GameContent - Projet de contenu XNA. Contient des références à: LevelContentPipelineExtension
  • Moteur - Projet de bibliothèque de jeux XNA. Pas de références.
  • LevelContentPipelineExtension - Extension de pipeline de contenu XNA. Contient des références à: Engine et GameLib

Je ne connais presque rien de XNA 4.0 et je suis un peu perdu sur la façon dont le contenu fonctionne maintenant. Si vous avez besoin de plus d'informations, dites-le-moi.

Jesse Emond
la source
Je ne peux pas vraiment mettre le doigt sur quoi - mais je pense que vous manquez des informations. Pourriez-vous expliquer exactement quels projets vous avez (jeu, bibliothèque de jeux, extension de pipeline de contenu, projet de contenu) et quelles références quoi?
Andrew Russell
Bien sûr, je savais que ce ne serait pas assez clair. Modification en ce moment.
Jesse Emond
Soit dit en passant, tout semble bien sauf qu'une exception est levée au moment de la compilation dans l'extension du pipeline. L'exception me dit en fait que le contenu du fichier de niveau ne peut pas être converti, car les symboles disponibles ne sont pas encore chargés dans mon fichier LevelContentCreation. J'ai même testé en mettant une exception à la fois dans l'extension du pipeline et dans la classe statique et l'exception de l'extension du pipeline a été levée en premier, donc le problème peut en fait venir de là et j'aurais besoin de restructurer mon code.
Jesse Emond

Réponses:

11

Dans les cas où vous utilisez des singletons, des classes statiques ou des variables globales, 99% du temps ce que vous devriez vraiment utiliser est un service de jeu . Les services de jeu sont utilisés lorsque vous souhaitez qu'une instance d'une classe soit disponible pour toutes les autres classes, mais le couplage étroit des autres options vous donne un goût amusant. Les services n'ont pas non plus les problèmes d'instanciation que vous avez vus, ils sont plus réutilisables et ils peuvent être moqués (si vous vous souciez des tests unitaires automatisés) .

Pour enregistrer un service, vous devez donner à votre classe sa propre interface. Appelez ensuite Game.Services.AddService(). Pour utiliser ultérieurement ce service dans une autre classe, appelez Game.Services.GetService().

Dans votre cas, votre classe ressemblerait à ceci:

public interface ILevelContentCreation
{
    void SomeMethod();
    int SomeProperty { get; }
}

public class LevelContentCreation : ILevelContentCreation
{
    private Dictionary<char, TileCreationInfo> availableTiles =
        CreateAvailableTiles();

    private List<string> AvailableTileSheets { get; set; }

    public void SomeMethod() { /*...*/ }
    public int SomeProperty { get; private set; }

    /* ... rest of the class ... */
}

À un moment donné au début du programme, vous devrez enregistrer votre service auprès de Game.Services. Je préfère le faire au début de LoadContent(), mais certaines personnes préfèrent enregistrer chaque service dans le constructeur de cette classe.

/* Main game */
protected override void LoadContent()
{
    RegisterServices();

    /* ... */
}

private void RegisterServices()
{
    Game.Services.AddService(typeof(ILevelContentCreation), new LevelContentCreation());
}

Maintenant, chaque fois que vous souhaitez accéder à votre LevelContentCreationclasse dans une autre classe, vous devez simplement faire ceci:

ILevelContentCreation levelContentCreation = (ILevelContentCreation) Game.Services.GetService(typeof(ILevelContentCreation));
levelContentCreation.SomeMethod(); //Tada!

En remarque, ce paradigme est connu sous le nom d' inversion de contrôle (IoC) et est également largement utilisé en dehors du développement de XNA. Le framework le plus populaire pour l'IoC dans .Net est Ninject , qui a une syntaxe plus agréable que les méthodes Game.Services:

Bind<ILevelContentCreation>().To<LevelContentCreation>(); //Ninject equivalent of Services.AddService()
ILevelContentCreation levelContentCreation = kernel.Get<ILevelContentCreation>(); //Ninject equivalent of Services.GetService()

Il prend également en charge l' injection de dépendance , une forme d'IoC qui est légèrement plus complexe, mais beaucoup plus agréable à utiliser.

[Edit] Bien sûr, vous pouvez aussi obtenir cette belle syntaxe avec XNA:

static class ServiceExtensionMethods
{
    public static void AddService<T>(this GameServiceContainer services, T service)
    {
        services.AddService(typeof(T), service);
    }

    public static T GetService<T>(this GameServiceContainer services)
    {
        return (T)services.GetService(typeof(T));
    }
}
BlueRaja - Danny Pflughoeft
la source
1
Wow, même si j'ai résolu mon problème, cela rendra mon code tellement plus propre. Merci pour l'aide, en acceptant votre réponse.
Jesse Emond
1

En fait, j'ai trouvé un moyen de résoudre le problème: j'ai supprimé la classe statique et en ai fait une classe normale. De cette façon, je suis sûr que les bonnes déclarations sont exécutées au bon moment.

J'aurais aimé ne pas avoir perdu votre temps ainsi, mais j'ai eu l'idée de le faire en lisant les commentaires / réponses.

Merci quand même.

Jesse Emond
la source
Une autre note sans rapport avec ma réponse ci-dessus / ci-dessous: si vous vous retrouvez à faire des instructions if/ switchqui renvoient simplement différents types, vous voulez probablement que ces valeurs fassent partie de la classe à la place. Dans votre cas, je créerais une TileTypeclasse, qui contient des informations (texture, lettre de fichier de sauvegarde, etc.) sur chaque type de tuile. Ensuite, pour obtenir un TileTypeextrait d'une lettre, recherchez simplement dans votre liste de TileTypes, ou si vous êtes préoccupé par les performances, stockez un statique Dictionary<char, TileType>dans la TileTypeclasse (vous pouvez le remplir automatiquement dans le TileTypeconstructeur).
BlueRaja - Danny Pflughoeft
Ensuite, chacune Tile(classe qui représente une seule tuile affichée à l'écran) ferait référence à une seule TileType. Si un nombre suffisant de vos tuiles ont un comportement unique qui nécessite un code spécial, vous souhaiterez plutôt utiliser une hiérarchie de classes.
BlueRaja - Danny Pflughoeft
0

Jetez un œil à ceci: http://msdn.microsoft.com/en-us/library/bb447745.aspx

Pour la disposition des niveaux, je viens de mettre une fonction dans ma classe Level appelée "LoadFromFile (string name)". Parce que si vous souhaitez ajouter ultérieurement la prise en charge de cartes personnalisées dans votre jeu, vous devrez de toute façon enregistrer et charger les niveaux à partir de votre propre format de fichier.

Vous pouvez également créer un importateur de contenu, à charger à partir des ressources incluses et également à partir des fichiers fournis par l'utilisateur. Regardez cette page pour plus d'informations sur la création d'un importateur

http://msdn.microsoft.com/en-us/library/bb447743.aspx

Riki
la source
Merci pour votre réponse. Mon problème ne vient pas en fait de la création de l'extension de pipeline de contenu, mais du fait que ma classe de création de niveau est utilisée à la fois par ma classe Level et mon extension de pipeline de contenu, donc le code du pipeline est exécuté avant les déclarations de classe statique et rien n'y est initialisé.
Jesse Emond