XNA - Classi statiche da librerie di giochi in esecuzione dopo estensioni della pipeline di contenuti, come evitare il problema?


8

Bene, formulato in questo modo sono sicuro che suona oscuro, ma farò del mio meglio per descrivere il mio problema.

Prima di tutto, lasciami spiegare cosa c'è che non va nel mio vero codice sorgente.

Sto facendo un platform, e qui sono i componenti attuali del mio gioco: un livello , che sta tenendo una propria griglia di piastrelle e anche i fogli di mattonelle e una Tile , che mantiene solo il nome del suo foglio di piastrelle e l'indice all'interno della foglio di piastrelle. Tutti questi sono all'interno di un progetto di gioco XNA separato, chiamato GameLib.

Ecco come sono formattati i miei livelli in un file di testo:

x x . . . . .

. . . b g r .

. . . . . . .

X X X X X X X

Dove .rappresenta una tessera vuota, brappresenta una piattaforma blu, Xrappresenta un blocco con un colore casuale, ecc.

Invece di fare tutto il lavoro di caricamento di un livello all'interno della classe Level , ho deciso di sfruttare l'estensione della pipeline di contenuti e creare LevelContentPipelineExtension.

Appena ho iniziato, ho capito rapidamente che non volevo codificare più di 20 righe del tipo:

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

Quindi, nella speranza di almeno centralizzare tutta la creazione del contenuto in una classe, ho creato una classe statica, denominata LevelContentCreation , che contiene le informazioni su quale simbolo crea cosa e tutto questo tipo di cose. Contiene anche un elenco di tutti i percorsi delle risorse dei fogli a tessere, per caricarli in seguito. Questa classe rientra nel mio progetto di libreria di giochi GameLib.

Il problema è che sto usando questa classe statica LevelContentCreation sia nella mia classe Level (per caricare i fogli delle tessere effettive) sia nella mia estensione della pipeline di contenuti (per sapere quale simbolo rappresenta quale tessera) ...

Ed ecco come appare la mia classe LevelContentCreation :

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

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

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

Ora, poiché la classe LevelContentCreation fa parte della libreria di giochi, l'estensione della pipeline di contenuti tenta di usarla ma la chiamata a CreateAvailableTiles () non avviene mai e non riesco a caricare un oggetto Level dalla pipeline di contenuti.

Lo so perché la pipeline di contenuti viene eseguita prima della compilazione effettiva o qualcosa del genere, ma come posso risolvere il problema?

Non riesco davvero a spostare la classe LevelContentCreation sull'estensione della pipeline, perché quindi la classe Level non sarebbe in grado di usarla ... Sono piuttosto persa e non so cosa fare ... Conosco questi sono decisioni di progettazione piuttosto sbagliate e mi piacerebbe se qualcuno potesse indicarmi la giusta direzione.

Grazie per il tuo prezioso tempo.

Se qualcosa non è abbastanza chiaro o se dovessi fornire ulteriori informazioni / codice, lascia un commento qui sotto.


Qualche informazione in più sui miei progetti:

  • Gioco - Progetto di gioco XNA per Windows. Contiene riferimenti a: Engine, GameLib e GameContent
  • GameLib - Progetto di libreria di giochi XNA. Contiene riferimenti a: Motore
  • GameContent - Progetto di contenuto XNA. Contiene riferimenti a: LevelContentPipelineExtension
  • Motore : progetto della libreria di giochi XNA. Nessun riferimento
  • LevelContentPipelineExtension - Estensione della pipeline del contenuto XNA. Contiene riferimenti a: Engine e GameLib

So quasi nulla di XNA 4.0 e sono un po 'perso su come funzionano i contenuti ora. Se hai bisogno di maggiori informazioni, dimmelo.


Non riesco a capire cosa, ma penso che manchi qualche informazione. Potresti spiegare esattamente quali progetti hai (gioco, libreria di giochi, estensione della pipeline di contenuti, progetto di contenuto) e quali riferimenti cosa?
Andrew Russell,

Certo, sapevo che non sarebbe stato abbastanza chiaro. Modifica in questo momento.
Jesse Emond,

A proposito, tutto sembra a posto, tranne che al momento della compilazione viene generata un'eccezione durante l'estensione della pipeline. L'eccezione in realtà mi dice che il contenuto del file di livello non può essere convertito, poiché i simboli disponibili non sono ancora caricati nel mio file LevelContentCreation. Ho anche testato inserendo un'eccezione sia nell'estensione della pipeline che nella classe statica e l'eccezione dell'estensione della pipeline è stata generata per prima, quindi il problema potrebbe effettivamente provenire da lì e avrei bisogno di ristrutturare il mio codice.
Jesse Emond,

Risposte:


11

Nei casi in cui si utilizzano singleton, classi statiche o variabili globali, il 99% delle volte ciò che si dovrebbe realmente usare è un servizio di gioco . I servizi di gioco vengono utilizzati quando desideri che un'istanza di una classe sia disponibile per tutte le altre classi, ma l'accoppiamento stretto delle altre opzioni ti dà un gusto divertente. Inoltre, i servizi non hanno i problemi di istanziazione che hai visto, sono più riutilizzabili e possono essere derisi (se ti interessa il test di unità automatizzato) .

Per registrare un servizio, devi fornire alla tua classe una propria interfaccia. Quindi chiama Game.Services.AddService(). Per utilizzare successivamente quel servizio in un'altra classe, chiama Game.Services.GetService().

Nel tuo caso, la tua classe sarebbe simile a questa:

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 ... */
}

Ad un certo punto all'inizio del programma, dovresti registrare il tuo servizio con Game.Services. Preferisco farlo all'inizio LoadContent(), ma alcune persone preferiscono registrare ogni servizio nel costruttore di quella classe.

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

    /* ... */
}

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

Ora, ogni volta che vuoi accedere alla tua LevelContentCreationclasse in qualsiasi altra classe, devi semplicemente fare questo:

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

Come nota a margine , questo paradigma è noto come Inversion of Control (IoC) ed è ampiamente utilizzato anche al di fuori dello sviluppo di XNA. Il framework più popolare per IoC in .Net è Ninject , che ha una sintassi migliore rispetto ai metodi Game.Services:

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

Supporta anche l' iniezione di dipendenza , una forma di IoC leggermente più complessa, ma molto più piacevole con cui lavorare.

[Modifica] Certo, puoi ottenere anche quella bella sintassi con 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));
    }
}

1
Wow, anche se ho risolto il mio problema, questo renderà il mio codice molto più pulito. Grazie per l'aiuto, accettando la tua risposta.
Jesse Emond,

1

In realtà ho trovato un modo per risolvere il problema: ho rimosso la classe statica e l'ho resa una classe normale. In questo modo sono sicuro che le giuste dichiarazioni vengano eseguite al momento giusto.

Avrei voluto non aver sprecato il tuo tempo in quel modo, ma in realtà mi è venuta l'idea di farlo leggendo i commenti / le risposte.

Grazie lo stesso.


Un'altra nota non correlata alla mia risposta sopra / sotto: se ti ritrovi a fare if/ switchistruzioni che semplicemente restituiscono tipi diversi, probabilmente vorrai che quei valori facciano parte della classe. Nel tuo caso, creerei una TileTypeclasse, che contiene informazioni (trama, lettera-file-salvataggio, ecc.) Su ciascun tipo di riquadro. Quindi per ottenere un TileTypeda una lettera, basta cercare nel tuo elenco di TileTypes, o se sei preoccupato per le prestazioni, memorizzare un statico Dictionary<char, TileType>nella TileTypeclasse (puoi riempirlo automaticamente nel TileTypecostruttore).
BlueRaja - Danny Pflughoeft il

Quindi ciascuna Tile(classe che rappresenta una singola tessera visualizzata sullo schermo) farebbe riferimento a una singola TileType. Se un numero sufficiente di riquadri ha un comportamento univoco che richiede un codice speciale, dovrai invece utilizzare una gerarchia di classi.
BlueRaja - Danny Pflughoeft il

0

Dai un'occhiata a questo: http://msdn.microsoft.com/en-us/library/bb447745.aspx

Per il layout di livello inserivo una funzione nella mia classe Level chiamata "LoadFromFile (nome stringa)". Perché se vuoi aggiungere il supporto per le mappe personalizzate nel tuo gioco in un secondo momento, dovrai comunque salvare e caricare i livelli dal tuo formato di file.

Puoi anche creare un importatore di contenuti, da caricare dalle risorse incluse e anche dai file forniti dall'utente. Guarda questa pagina per ulteriori informazioni su come creare un importatore

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


Grazie per la tua risposta. Il mio problema in realtà non proviene dalla creazione dell'estensione della pipeline del contenuto, ma dal fatto che la mia classe di creazione del livello viene utilizzata sia dalla mia classe Level che dalla mia estensione della pipeline del contenuto, quindi il codice della pipeline viene eseguito prima delle dichiarazioni della classe statica e nulla è inizializzato lì.
Jesse Emond,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.