Nei commenti, ho pubblicato una versione ridotta della mia risposta vecchia risposta:
L'utilizzo DrawableGameComponent
ti blocca in un unico set di firme dei metodi e in uno specifico modello di dati conservati. Inizialmente si tratta in genere di una scelta errata e il lock-in ostacola in modo significativo l'evoluzione del codice in futuro.
Qui cercherò di illustrare perché questo è il caso inviando un po 'di codice dal mio progetto attuale.
L'oggetto root che mantiene lo stato del mondo di gioco ha un Update
metodo simile al seguente:
void Update(UpdateContext updateContext, MultiInputState currentInput)
Esistono diversi punti di accesso Update
, a seconda che il gioco funzioni o meno sulla rete. È possibile, ad esempio, che lo stato del gioco venga ripristinato a un punto temporale precedente e quindi nuovamente simulato.
Esistono anche altri metodi simili agli aggiornamenti, come:
void PlayerJoin(int playerIndex, string playerName, UpdateContext updateContext)
All'interno dello stato del gioco c'è un elenco di attori da aggiornare. Ma questa è più di una semplice Update
chiamata. Ci sono passaggi aggiuntivi prima e dopo per gestire la fisica. Ci sono anche alcune cose fantasiose per la generazione / distruzione differita di attori, oltre alle transizioni di livello.
Al di fuori dello stato del gioco, esiste un sistema di interfaccia utente che deve essere gestito in modo indipendente. Tuttavia, alcune schermate dell'interfaccia utente devono essere in grado di funzionare anche in un contesto di rete.
Per disegnare, a causa della natura del gioco, esiste un sistema di ordinamento e abbattimento personalizzato che accetta input da questi metodi:
virtual void RegisterToDraw(SortedDrawList sortedDrawList, Definitions definitions)
virtual void RegisterShadow(ShadowCasterList shadowCasterList, Definitions definitions)
E poi, una volta capito cosa disegnare, chiama automaticamente:
virtual void Draw(DrawContext drawContext, int tag)
Il tag
parametro è obbligatorio perché alcuni attori possono aggrapparsi ad altri attori e quindi devono essere in grado di disegnare sia sopra che sotto l'attore detenuto. Il sistema di smistamento garantisce che ciò avvenga nell'ordine corretto.
C'è anche il sistema di menu da disegnare. E un sistema gerarchico per disegnare l'interfaccia utente, attorno all'HUD, attorno al gioco.
Ora confronta questi requisiti con ciò che DrawableGameComponent
prevede:
public class DrawableGameComponent
{
public virtual void Update(GameTime gameTime);
public virtual void Draw(GameTime gameTime);
public int UpdateOrder { get; set; }
public int DrawOrder { get; set; }
}
Non esiste un modo ragionevole per passare da un sistema DrawableGameComponent
al sistema sopra descritto. (E quelli non sono requisiti insoliti per un gioco di complessità anche modesta).
Inoltre, è molto importante notare che tali requisiti non sono semplicemente emersi all'inizio del progetto. Si sono evoluti in molti mesi di lavoro di sviluppo.
Ciò che le persone fanno in genere, se iniziano lungo il DrawableGameComponent
percorso, iniziano a costruire sugli hack:
Invece di aggiungere metodi, fanno un brutto down-casting al proprio tipo di base (perché non usarlo direttamente?).
Invece di sostituire il codice per l'ordinamento e il richiamo Draw
/ Update
, fanno alcune cose molto lente per cambiare i numeri di ordinazione al volo.
E la cosa peggiore di tutte: invece di aggiungere parametri ai metodi, iniziano a "passare" "parametri" in posizioni di memoria che non sono lo stack (l'uso di Services
per questo è popolare). Questo è contorto, soggetto a errori, lento, fragile, raramente sicuro, e probabilmente diventerà un problema se si aggiungono reti, si creano strumenti esterni e così via.
Rende anche più difficile il riutilizzo del codice , perché ora le tue dipendenze sono nascoste all'interno del "componente", anziché essere pubblicate al limite dell'API.
Oppure vedono quasi quanto sia folle tutto questo e iniziano a fare "manager" DrawableGameComponent
per aggirare questi problemi. Tranne che hanno ancora questi problemi ai confini del "manager".
Invariabilmente questi "gestori" potrebbero essere facilmente sostituiti con una serie di chiamate di metodo nell'ordine desiderato. Qualcos'altro è troppo complicato.
Naturalmente, una volta che inizi a utilizzare quegli hack, ci vuole un vero lavoro per annullare quegli hack una volta che ti rendi conto che hai bisogno di più di quello che DrawableGameComponent
fornisce. Diventi " bloccato ". E la tentazione è spesso quella di continuare ad accumulare hack - che in pratica ti impantaneranno e limiteranno i tipi di giochi che puoi realizzare a giochi relativamente semplicistici.
Molte persone sembrano arrivare a questo punto e hanno una reazione viscerale, probabilmente perché usano DrawableGameComponent
e non vogliono accettare di essere "sbagliate". Quindi si aggrappano al fatto che è almeno possibile farlo "funzionare".
Naturalmente può essere fatto di "lavorare"! Siamo programmatori: far funzionare le cose con restrizioni insolite è praticamente il nostro lavoro. Ma questa limitazione non è necessaria, utile o razionale ...
Ciò che è particolarmente sbalorditivo è che è sorprendentemente banale affiancare l'intera questione. Qui, ti darò anche il codice:
public class Actor
{
public virtual void Update(GameTime gameTime);
public virtual void Draw(GameTime gameTime);
}
List<Actor> actors = new List<Actor>();
foreach(var actor in actors)
actor.Update(gameTime);
foreach(var actor in actors)
actor.Draw(gameTime);
(È semplice aggiungere l'ordinamento come da DrawOrder
e UpdateOrder
. Un modo semplice è quello di mantenere due elenchi e utilizzare List<T>.Sort()
. È anche banale gestire Load
/ UnloadContent
.)
Non è importante che cosa sia effettivamente quel codice . Ciò che è importante è che posso modificarlo banalmente .
Quando mi rendo conto che ho bisogno di un diverso sistema di temporizzazione gameTime
, o ho bisogno di più parametri (o di un intero UpdateContext
oggetto con circa 15 cose al suo interno), o ho bisogno di un ordine di operazioni completamente diverso per il disegno, o ho bisogno di alcuni metodi extra per diversi passaggi di aggiornamento, posso solo andare avanti e farlo .
E posso farlo immediatamente. Non ho bisogno di pensare a come scegliere DrawableGameComponent
il mio codice. O peggio: hackeralo in qualche modo che renderà ancora più difficile la prossima volta che tale necessità si presenta.
Esiste davvero solo uno scenario in cui è una buona scelta da usare DrawableGameComponent
: vale a dire se stai letteralmente creando e pubblicando middleware in stile componente drag-and-drop per XNA. Qual era il suo scopo originale. Quale - aggiungerò - nessuno lo sta facendo!
(Allo stesso modo, ha davvero senso usare soloServices
quando si creano tipi di contenuto personalizzati ContentManager
.)