Come evitare l'oggetto god di GameManager?


51

Ho appena letto una risposta a una domanda sulla strutturazione del codice di gioco . Mi ha fatto meravigliare della GameManagerclasse onnipresente e di come spesso diventi un problema in un ambiente di produzione. Lascia che lo descriva.

Innanzitutto, c'è la prototipazione. A nessuno importa scrivere un ottimo codice, proviamo solo a far funzionare qualcosa per vedere se il gameplay si somma.

Poi c'è un semaforo verde e, nel tentativo di ripulire le cose, qualcuno scrive a GameManager. Probabilmente per contenere un sacco di GameStates, forse per memorizzarne alcuni GameObjects, niente di grosso, davvero. Un manager carino, piccolo.

Nel pacifico regno della pre-produzione, il gioco si sta evolvendo bene. I programmatori hanno notti di sonno adeguate e molte idee per l'architettura della cosa con Great Design Patterns.

Quindi inizia la produzione e presto, naturalmente, c'è il tempo da perdere. La dieta equilibrata è scomparsa da tempo, il tracker dei bug è pieno di problemi, le persone sono stressate e il gioco deve essere rilasciato ieri.

A quel punto, di solito, GameManagerè un vero casino (rimanere educati).

Il motivo è semplice. Dopotutto, quando si scrive un gioco, beh ... tutto il codice sorgente è effettivamente qui per gestire il gioco . È facile aggiungere questa piccola funzionalità extra o correzione di bug nel GameManager, dove tutto il resto è già memorizzato comunque. Quando il tempo diventa un problema, non c'è modo di scrivere una classe separata o di dividere questo manager gigante in sub-gestori.

Naturalmente questo è un anti-modello classico: l' oggetto Dio . È una brutta cosa, un dolore da unire, un dolore da mantenere, un dolore da capire, un dolore da trasformare.

Cosa suggeriresti per evitare che ciò accada?

MODIFICARE

So che è allettante dare la colpa alla denominazione. Naturalmente creare una GameManagerclasse non è una grande idea. Ma la stessa cosa può accadere a un ambiente pulito GameApplicationo GameStateMachineo GameSystemche finisce per essere il dotto-tape-favorito per la fine di un progetto. Indipendentemente dalla denominazione, hai quella classe da qualche parte nel tuo codice di gioco, per ora è solo un embrione: semplicemente non sai ancora quale mostro diventerà. Quindi non mi aspetto risposte "incolpare il nome". Voglio un modo per impedire che ciò accada, un'architettura di codifica e / o un processo che un team può seguire sapendo che questo accadrà ad un certo punto della produzione. È semplicemente un peccato buttar via, diciamo, un mese di correzioni di bug e funzionalità dell'ultimo minuto, solo perché ora sono tutti in un enorme gestore non mantenibile.


Sia che si chiami GameManager o ControllerOfTheGame, o come si desidera chiamarlo, intendo evitare una classe incaricata di controllare la logica dell'intero gioco. Sai che lo stai facendo quando lo fai indipendentemente da come pensi di chiamarlo. Penso che il mio ultimo gioco abbia avuto un controller di livello che è iniziato solo per mantenere lo stato del livello per il salvataggio, ma se mi fossi fermato a pensarci, era ovvio che questo oggetto doveva gestire più dettagli di quanto dovrebbe.
Brandon,

@brandon Lasciami riformulare la mia domanda per te: come controlli la logica dell'intero gioco senza GameManageresporti agli effetti che ho descritto sopra?
Laurent Couvidou,

Dipende dalla logica. Le cose ovvie come il giocatore, il nemico, la struttura, ecc. Che ha chiaramente la sua logica, vengono inserite nelle rispettive classi. Quello che sembra chiederti di più è gestire lo stato del gioco. Di solito ci sarà una classe che tiene traccia dello stato dell'intero gioco, ma nella mia esperienza è una delle ultime cose di cui devi preoccuparti durante la prototipazione, e il suo scopo dovrebbe essere molto chiaro. Fondamentalmente o fai una pianificazione iniziale prima della prototipazione o inizi da zero quando avvii la produzione per evitare l'uso di classi spazzatura che erano solo per i test.
Brandon,

È interessante notare che XNA crea questo potenziale disastro per te. Per impostazione predefinita, viene creata una classe di gioco per gestire tutto. Contiene i principali metodi di aggiornamento, disegno, inizializzazione e caricamento. In realtà sono in procinto di migrare verso un nuovo progetto perché il mio gioco (non così complesso) è diventato troppo difficile da gestire con tutto ciò che è stipato in quella classe. Inoltre, i tutorial Microsoft sembrano incoraggiarlo.
Fibericon,

Risposte:


23

Poi c'è una luce verde e, nel tentativo di ripulire le cose, qualcuno scrive un GameManager. Probabilmente per contenere un sacco di GameState, forse per memorizzare alcuni GameObject, niente di grosso, davvero. Un manager carino, piccolo.

Sai, mentre stavo leggendo questo, ho avuto dei piccoli allarmi che mi suonavano in testa. Un oggetto con il nome "GameManager" non sarà mai carino o piccolo. E qualcuno ha fatto questo per ripulire il codice? Che aspetto aveva prima? OK, scherzi a parte: il nome di una classe dovrebbe essere una chiara indicazione di ciò che fa la classe, e questa dovrebbe essere una cosa (alias: principio di responsabilità singola ).

Inoltre, potresti ancora finire con un oggetto come GameManager, ma chiaramente esiste a un livello molto alto e dovrebbe occuparsi di compiti di alto livello. Mantenere un catalogo di oggetti di gioco? Forse. Facilitare la comunicazione tra oggetti di gioco? Sicuro. Calcolo delle collisioni tra oggetti? No! Questo è anche il motivo per cui il nome Manager è disapprovato: è troppo ampio e consente molti abusi sotto quel banner.

Una rapida regola empirica sulle dimensioni delle classi: se si verificano diverse centinaia di righe di codice per classe, qualcosa inizia a non funzionare. Senza essere troppo zelante, niente di più, diciamo, 300 LOC è un odore di codice per me, e se stai andando oltre 1000, le campane di avviso dovrebbero suonare. Credendo che in qualche modo 1000 righe di codice siano più semplici da comprendere rispetto a 4 classi ben strutturate da 250 ciascuna, ti stai illudendo.

Quando il tempo diventa un problema, non c'è modo di scrivere una classe separata o di dividere questo manager gigante in sub-gestori.

Penso che questo sia il caso solo perché il problema è autorizzato a propagarsi al punto in cui tutto è un casino completo. La pratica del refactoring è davvero ciò che stai cercando: è necessario migliorare continuamente il design del codice con piccoli incrementi .

Cosa suggeriresti per evitare che ciò accada?

Il problema non è tecnologico, quindi non dovresti cercare correzioni tecnologiche. Il problema è questo: c'è una tendenza nella tua squadra a creare pezzi monolitici di codice e la convinzione che sia in qualche modo utile a medio / lungo termine lavorare in questo modo. Sembra anche che al team manchi un forte vantaggio architettonico, che guiderebbe l'architettura del gioco (o almeno, questa persona è troppo impegnata per svolgere questo compito). Fondamentalmente, l'unica via d'uscita è far riconoscere ai membri del team che questo pensiero è sbagliato. Nessuno favorisce. La qualità del prodotto peggiorerà e il team passerà solo più notti a sistemare le cose.

La buona notizia è che i benefici immediati e tangibili della scrittura di codice pulito sono così grandi, che quasi tutti gli sviluppatori realizzano i loro benefici molto rapidamente. Convincere il team a lavorare in questo modo per un po ', i risultati faranno il resto.

La parte difficile è che sviluppare una sensazione per ciò che costituisce un codice errato (e un talento per trovare rapidamente un design migliore) è una delle abilità più difficili da imparare nello sviluppo. Il mio suggerimento si basa sulla speranza di avere qualcuno abbastanza anziano nella squadra che possa farlo - è molto più facile convincere le persone in quel modo.

Modifica - Altre informazioni:

In generale, non penso che il tuo problema sia limitato allo sviluppo del gioco. Alla base, è un problema di ingegneria del software, quindi i miei commenti in quella direzione. Ciò che potrebbe essere diverso è la natura del settore dello sviluppo del gioco, sia che si tratti di maggiori risultati e di scadenze rispetto ad altri tipi di sviluppo, non ne sono sicuro.

In particolare per lo sviluppo del gioco, tuttavia, la risposta accettata a questa domanda su StackOverflow in merito ai suggerimenti "soprattutto sull'architettura di gioco", afferma:

Segui i solidi principi del design orientato agli oggetti ....

Questo è essenzialmente esattamente quello che sto dicendo. Quando sono sotto pressione, mi ritrovo anche a scrivere grandi quantità di codice, ma mi sono messo in testa che si tratta di debito tecnico. Ciò che tende a funzionare bene per me, è trascorrere la prima metà (o tre quarti) della giornata producendo una grande quantità di codice di media qualità, e poi sedersi e pensarci un po '; faccio un po 'di progettazione nella mia testa, o su carta / lavagna, su come migliorare un po' il codice. Spesso noto codice ripetitivo e sono in grado di ridurre effettivamente le righe totali di codice suddividendo le cose, migliorando nel contempo la leggibilità. Questa volta investito si ripaga così in fretta, che chiamarlo "investimento" suona sciocco - abbastanza spesso raccoglierò bug che avrebbero potuto perdere metà della mia giornata (una settimana dopo), se gli avessi permesso di continuare.

  • Risolvi le cose nello stesso giorno in cui li codifichi.
  • Sarai felice di averlo fatto entro poche ore.

Arrivare a credere veramente quanto sopra è difficile; Sono riuscito a farlo per il mio lavoro solo perché ho sperimentato, ancora e ancora, gli effetti. Anche così, è ancora difficile per me giustificare la correzione del codice quando potrei sfornare di più ... Quindi capisco sicuramente da dove vieni. Sfortunatamente, questo consiglio è forse troppo generico e non facile da fare. Ci credo fermamente, comunque! :)

Per rispondere al tuo esempio specifico:

Il codice pulito di zio Bob fa un ottimo lavoro nel riassumere come sia un codice di buona qualità. Mi capita di essere d'accordo con quasi tutti i suoi contenuti. Quindi, quando penso al tuo esempio di una classe manager LOC di 30.000, non posso davvero essere d'accordo con la parte "buona ragione". Non voglio sembrare offensivo, ma pensare in questo modo porterà al problema. Non vi è alcuna buona ragione per avere così tanto codice in un singolo file: sono quasi 1000 pagine di testo! Qualsiasi vantaggio derivante dalla località (velocità di esecuzione o "semplicità" di progettazione) verrà immediatamente annullato dal fatto che gli sviluppatori siano completamente impantanati nel tentativo di attraversare quella mostruosità, e questo è ancora prima di discutere della fusione, ecc.

Se non sei convinto, il mio miglior suggerimento sarebbe quello di prendere una copia del libro sopra e dare un'occhiata attraverso di esso. Applicare quel tipo di pensiero a condurre le persone a creare volontariamente un codice pulito e autoesplicativo, che è ben strutturato.


Questo non è un difetto che ho visto una volta. L'ho visto in diversi team, per diversi progetti. Ho visto molte persone di talento e strutturate lottare con questo. Quando sei sotto pressione, il limite di 300 righe di codice non è un problema per il tuo produttore. Né il giocatore tra l'altro. Certo, è carino e dolce, ma il diavolo è nei dettagli. Il caso peggiore di un GameManagerho visto finora è stato di circa 30.000 righe di codice, e sono abbastanza sicuro che la maggior parte fosse qui per un ottimo motivo. Questo è estremo ovviamente, ma quelle cose accadono nel mondo reale. Sto chiedendo un modo per limitare questo GameManagereffetto.
Laurent Couvidou,

@lorancou - Ho aggiunto un po 'di informazioni extra al post, era un po' troppo per un commento, si spera che ti dia un po 'più di idea, anche se non posso indicarti alcuna architettura concreta esempi.
Daniel B,

Ah, non ho letto quel libro, quindi è sicuramente un buon suggerimento. Oh, e non intendevo dire che c'è una buona ragione per aggiungere codice in una classe a migliaia di righe. Intendevo dire che tutto il codice in quel tipo di oggetto dio fa qualcosa di utile. Probabilmente l'ho scritto un po 'troppo in fretta;)
Laurent Couvidou,

@lorancou Hehe, va bene ... così sta la follia :)
Daniel B,

"La pratica del refactoring è davvero ciò che stai cercando: devi migliorare continuamente la progettazione del codice con piccoli incrementi.". Penso che tu abbia un punto molto forte lì. Il disordine attrae il disordine, questa è la vera ragione dietro questo, quindi il disordine deve essere combattuto a lungo termine. Accetterò questa risposta, ma ciò non significa che non ci sia valore negli altri!
Laurent Couvidou,

7

Questo non è davvero un problema con la programmazione del gioco, ma con la programmazione in generale.

tutto il codice sorgente è effettivamente qui per gestire il gioco.

Questo è il motivo per cui dovresti disapprovare qualsiasi programmatore orientato agli oggetti che suggerirà le classi con "Manager" nel nome. Ad essere sinceri, penso che sia praticamente impossibile evitare oggetti "semigod" nel gioco, ma li chiamiamo semplicemente "sistema".

Qualsiasi software tende a sporcarsi quando la scadenza è vicina. Fa parte del lavoro. E non c'è nulla di intrinsecamente sbagliato nella " programmazione del tipo di condotto ", in particolare quando è necessario spedire il prodotto in poche ore. Non puoi eliminare completamente questo problema, puoi semplicemente ridurlo.

Tuttavia, ovviamente, se sei tentato di inserire elementi nel GameManager, significa che non hai una base di codici molto salutare. Potrebbero esserci due problemi:

  • Il tuo codice è troppo complesso. Troppi schemi, ottimizzazione prematura, nessuno capisce più il flusso. Cerca di usare più TDD durante lo sviluppo, se puoi, perché è un'ottima soluzione per combattere la complessità.

  • Il tuo codice non è ben strutturato. Stai (ab) usando la variabile globals, le classi non sono accoppiate liberamente, non c'è alcuna interfaccia, nessun sistema di eventi e così via.

Se il codice sembra corretto, dovrai ricordare, per ogni progetto, "cosa è andato storto" ed evitarlo la prossima volta.

Infine, potrebbe anche essere un problema di gestione del team: c'era abbastanza tempo? Tutti sapevano dell'architettura che stavi cercando? Chi diavolo ha permesso un commit con una classe con dentro la parola "Manager"?


Non c'è niente di sbagliato nella programmazione del nastro adesivo, te lo dico io. Ma sono abbastanza sicuro che le classi di Manager siano presenti nei motori di gioco, almeno le ho viste molto. Sono utili finché non diventano troppo grandi ... Cosa c'è che non va in a SceneManagero a NetworkManager? Non vedo perché dovremmo prevenirli o come la sostituzione di -Manager con -System aiuti. Sto cercando suggerimenti per un'architettura sostitutiva per ciò che di solito va in GameManagerqualcosa di meno incline al codice.
Laurent Couvidou,

Cosa c'è che non va in uno SceneManager? Bene, qual è la differenza tra una scena e uno sceneManager? Una rete e un gestore di rete? Dall'altro aspetto: non penso che questo sia un problema di architettura, ma se potessi dare un esempio concreto di qualcosa che si trova nel GameManager e non dovresti essere lì, sarebbe più facile suggerire una soluzione.
Raveline,

OK, quindi dimentica la cosa -Manager. Diciamo che hai una classe che gestisce i tuoi stati di gioco, chiamiamola GameStatesCollection. All'inizio avrai solo alcuni stati di gioco e modi per passare da uno all'altro. Poi ci sono le schermate di caricamento che arrivano e complicano tutto questo. Quindi alcuni programmatori devono affrontare un bug quando l'utente espelle il disco mentre si passa da Level_A a Level_B, e l'unico modo per risolverlo senza passare mezza giornata di refactoring è in GameStatesCollection. Boom, un oggetto divino.
Laurent Couvidou,

5

Quindi, portando una possibile soluzione dal mondo più Enterprise-y: hai verificato l'inversione dell'iniezione di controllo / dipendenza?

Fondamentalmente, ottieni questo framework che è un contenitore per tutto il resto del tuo gioco. Esso, e solo esso, sa come collegare tutto insieme e quali sono le relazioni tra i tuoi oggetti. Nessuno dei tuoi oggetti crea un'istanza all'interno dei loro costruttori. Invece di creare ciò di cui hanno bisogno, chiedono ciò di cui hanno bisogno dal framework IoC; al momento della creazione, il framework IoC "conosce" quale oggetto è necessario per soddisfare la dipendenza e la riempie. Giunto libero FTW.

Quando la tua classe EnemyTreasure richiede al contenitore un oggetto GameLevel, il framework sa quale istanza dargli. Come lo sa? Forse l'ha istanziato prima, forse chiede ad alcuni GameLevelFactory nelle vicinanze. Il punto è che EnemyTreasure non sa da dove provenga l'istanza di GameLevel. Sa solo che la sua dipendenza è ora soddisfatta e può andare avanti con la sua vita.

Oh, vedo che la tua classe PlayerSprite implementa IUpdateable. Bene, è fantastico, perché il framework sottoscrive automaticamente ogni oggetto IUpdateable al Timer, che è il punto in cui si trova il loop di gioco principale. Ogni Timer :: tick () chiama automaticamente tutti i metodi IUpdatedable :: update (). Facile vero?

Realisticamente, non hai bisogno di questo quadro bighugeohmygod per fare questo per te: puoi farlo da solo. Il punto è che se ti ritrovi a creare oggetti di tipo -Manager, allora è probabile che tu non stia distribuendo correttamente le tue responsabilità tra le tue classi o che manchi qualche classe da qualche parte.


Interessante, la prima volta che ho sentito parlare di IoC. Questo potrebbe essere troppo bighugeohmygod per la programmazione del gioco, ma lo controllerò!
Laurent Couvidou,

IoC, non lo usavamo solo per chiamarlo buonsenso?
brice

Data una mezza possibilità, un ingegnere farà di tutto un quadro. (= Inoltre, non sto sostenendo l'uso del framework, sto sostenendo il modello architettonico.
drhayes

3

In passato, in genere finisco con una classe divina se faccio quanto segue:

  • siediti all'editor di gioco / IDE / blocco note prima di aver scritto un diagramma di classe (o solo uno schizzo degli oggetti principali)
  • Inizia con un'idea molto vaga del gioco e codificalo immediatamente, quindi inizia a ripetere nuove idee man mano che arrivano
  • creare un file di classe chiamato GameManager quando non sto realizzando un gioco basato sullo stato come una strategia a turni in cui GameManager è in realtà un oggetto di dominio
  • non preoccuparsi dell'organizzazione del codice all'inizio

Questo può essere controverso, che riderò se lo è, ma non riesco a pensare nemmeno una volta alla prototipazione che non dovresti scrivere un diagramma di classe molto semplice prima di scrivere un prototipo giocabile. Tendo a testare le idee tecnologiche prima di pianificare, ma questo è tutto. Quindi, se volessi creare un portale, scriverei un codice molto semplice per far funzionare i portali, quindi direi ok ottimo momento per una pianificazione minore, quindi posso lavorare sul prototipo giocabile.


1

So che questa domanda è vecchia ora, ma ho pensato di aggiungere i miei 2 centesimi.

Durante la progettazione di giochi, ho quasi sempre un oggetto Gioco. Tuttavia, è di altissimo livello e in realtà non "fa nulla" da solo.

Ad esempio, dice alla classe Grafica di eseguire il rendering, ma non ha nulla a che fare con il processo di rendering. Potrebbe chiedere a LevelManager di caricare un nuovo livello, ma non ha nulla a che fare con il processo di caricamento.

In breve, uso un oggetto semi-divino per orchestrare l'uso delle altre classi.


2
Questo non è affatto simile a un dio - questo si chiama astrazione. :)
Vaughan Hilts l'
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.