Escape a GameLogic God Class.

3

Quando si programma un gioco semplice come esempio. Tendo sempre a lasciare una grande classe di GameLogic da qualche parte che è responsabile per incollare insieme tutte le altre parti del software.

Un esempio comune della classe GameLogic di cui parlo contiene funzioni come queste.

startNewGame();
showAd();
prepareLevel();
startLevel();
gameComplete();
restartLevel()
gameOver();

Questa classe può diventare molto grande.

C'è qualche consiglio su come strutturare meglio i miei giochi futuri? Devo dividere queste funzioni in classi dedicate in qualche modo per ridurre la complessità della classe di Dio?

    
posta user1059939 08.12.2014 - 15:42
fonte

4 risposte

3

Personalmente, mi piace molto l'idea di stage, scene e attori, anche per piccoli progetti. In questo modo, puoi sempre aggiungere nuove classi e semplicemente inserirle, e agiscono come singoli oggetti che possono essere pensati come tali.

Creando classi per tutti gli oggetti sottoposti a rendering in questo modo:

  • Stage (MainMenu)
    • Scena (MenuNavigator)
      • Attore (MenuItem)
      • Attore (MenuItem)
      • Attore (MenuItem)
      • Attore (MenuItem)
  • Stage (InGame)
    • Scene (Gameplay)
      • Attore (StarUnderlay)
      • Attore (nave)
      • Attore (nemico)
    • Scene (ScoreOverlay)
      • Attore (testo)
      • Attore (testo)
      • Attore (testo)
    • Scene (GameOver)
      • Attore (testo)
      • Attore (MenuItem)

Ottieni un mondo di vantaggi.

Classificando le tue entità, avrai riusabilità (come MenuItem, testo e creazione di nemici che usano classi comuni o classi anonime). Ciò significa che dovrai riscrivere meno codice e condividere la logica su tutti gli oggetti.

Classificando le scene, puoi anche riutilizzare questo codice, come avere uno stage InGame che utilizza un punteggiooverguardo, così come uno stage InReplay che utilizza anche tale visualizzazione del punteggio, in cui i dati sono compilati dallo stage.

Classificando le tue fasi, puoi mettere la tua logica di base lì come è dove è attualmente il tuo utente nel gioco. Una fase MainMenu può essere sostituita con uno stage InGame e, semplicemente sostituendo l'oggetto che si sta eseguendo, non dovrai preoccuparti di deallocare gli oggetti giusti dato che avrai o meno la garbage collection o la possibilità di richiedere l'assegnazione degli oggetti figlio. .

L'idea è di ridurre la responsabilità sia ora che nel futuro di tutte le tue classi, e così si finisce con le singole unità che lavorano insieme per produrre un prodotto finale. Se una cosa si rompe, è più probabile che tu sappia dove e perché, e non dovrai scavare attraverso un file di 800 righe, ma forse un file di 100 righe.

Quando si va a tirare insieme, si può pensare ad esso come se si stesse giocando (quindi perché le scene, le scene e gli attori sono espressioni usate comunemente). Hai bisogno di un mostro per apparire? Quindi aggiungi un nuovo attore per ritrarre quel mostro. Quando quel mostro non è più necessario, viene tirato fuori dalla scena.

Sì, probabilmente finirai con una buona quantità di classi, ma molte persone sosteranno che 100 classi sono meglio di 1.000 linee, perché di nuovo, saprai sempre cosa è quando i concetti vengono separati.

    
risposta data 08.12.2014 - 20:19
fonte
6

Should I be splitting these functions out into dedicated classes somehow as to reduce the complexity of the god class?

Sì, è una buona idea. Un buon punto di partenza potrebbe essere quello di suddividere la tua singola classe monolitica in classi più piccole con funzionalità correlate. La gestione dei livelli sembra essere un tema comune, quindi forse creare una classe LevelManager che contiene prepareLevel() , startLevel() e restartLevel() . Il codice relativo agli annunci potrebbe essere spostato in una classe AdManager.

I dettagli esatti di come si effettua il refactoring dipendono in larga misura da come il gioco è attualmente implementato, ma spero che questo ti dia una buona idea di come iniziare.

    
risposta data 08.12.2014 - 16:28
fonte
4

Quando il gioco è semplice, una classe del genere non dovrebbe essere così grande.

Quando il gioco progredisce, devi dividerlo nel modo che ritieni più naturale per te. Tendo a finire con una semplice macchina a stati finiti per lo stato dell'applicazione, quindi eseguo un kernel (cioè aggiornamento prioritario attività) in ogni stato.

Queste attività spesso vivono in una "scena", quindi posso caricare MainMenuScene, quindi caricare LevelOne. Ogni scena a sua volta ha la capacità di eseguire "compiti". Un'attività è semplicemente qualcosa che può essere aggiornata e può inviare e ricevere messaggi ad altre attività.

Infine, gli oggetti di gioco che vivono nella "scena" sono entrambi un'attività e seguono il schema di decorazione in modo che Posso aggiungere aspetti del comportamento a uno o più oggetti nella scena.

Ad esempio:

FSM
   State (start)
      Task (load assets and saves)
      ...
   State (main menu)
      Task (listen for menu selection)
   State (level1)
      Task & Kernel level1 main 
         GameObject (player)
            Component (camera)
            Component (rigidbody)
            Task (physics)

Forse questo ha senso, forse no. Dimmi in un commento e posso provare a renderlo più facile da capire.

Inoltre, questo è solo un modo per affrontarlo, non è sicuro che The One True Way esista.

    
risposta data 08.12.2014 - 16:04
fonte
4

Dovresti provare a disegnarlo su un foglio di carta o in qualche tipo di programma di simulazione UML. Ci vorranno alcune iterazioni per risolvere ciò che sembra modellare con più precisione quali ruoli dovrebbero essere assegnati a quali classi.

Dato il tuo esempio, hai alcune cose che sono completamente indipendenti nella stessa classe:

startNewGame();
showAd();
prepareLevel();
startLevel();
gameComplete();
restartLevel()
gameOver();

Il tuo metodo principale dovrebbe in realtà solo inizializzare e chiamare start su un oggetto di gioco.

Penso che il metodo prepareLevel dovrebbe essere in un'altra classe (la classe di livello)

Sto pensando qualcosa del tipo:

Main
--------

public static void main (String[] args){

  new Game().start

}

Game
-------

// Should be the main driving class for menus and such, but shouldn't do any real work
// as that should be in renderers, input processors.Should do all the calls such as:
//   -- Show menu
//   -- Start()

public start(){
  Campaign c = new Campaign();
  gameCore.loadCampaign(c)
}


GameCore
---------
// Game logic not related to rendering.


loadCampaign(Campaign c){
   LevelProcessor.startCampaign(c); // -- maybe this shows your screen and such
}


Campaign
---------
// List of levels, in order


LevelProcessor
-----------

// Makes some calls to load all the assets in your level, shoves it to your game container.

Container
-----------
// Handles the calls and stuff to a renderer which draws your view, handles calls to 
// Input handlers and such.
    
risposta data 08.12.2014 - 16:23
fonte

Leggi altre domande sui tag