Ad esempio avere una classe base GameObject con una gerarchia di eredità profonda potrebbe essere utile per la manutenzione ...
In realtà, le gerarchie profonde sono generalmente peggiori per la manutenibilità rispetto a quelle poco profonde, e lo stile architettonico moderno per gli oggetti di gioco tende a approcci superficiali e basati su aggregazioni .
Tuttavia, penso che questo approccio possa creare problemi di prestazioni. D'altra parte, tutti i dati e le funzioni di tutti gli oggetti di gioco potrebbero essere globali. Che sarebbe un mal di testa da manutenzione ma potrebbe essere più vicino a un loop di gioco dalle prestazioni ottimali.
Il ciclo che hai mostrato potenzialmente ha problemi di prestazioni, ma non, come è implicito nella tua dichiarazione successiva, perché hai dati di istanza e funzioni membro nella GameObject
classe. Piuttosto, il problema è che se trattate tutti gli oggetti nel gioco esattamente come gli stessi, probabilmente non state raggruppando quegli oggetti in modo intelligente - sono probabilmente sparsi casualmente in tutto l'elenco. Potenzialmente, quindi, ogni chiamata al metodo di aggiornamento per quell'oggetto (indipendentemente dal fatto che quel metodo sia una funzione globale o meno e che l'oggetto abbia dati di istanza o "dati globali" fluttuanti in una tabella in cui stai indicizzando o qualunque altra cosa) è diverso dalla chiamata di aggiornamento nelle iterazioni dell'ultimo ciclo.
Ciò può aumentare la pressione sul sistema in quanto potrebbe essere necessario sfogliare la memoria con la relativa funzione dentro e fuori memoria e riempire più frequentemente la cache delle istruzioni, causando un ciclo più lento. Se questo è osservabile a occhio nudo (o anche in un profiler) dipende esattamente da cosa è considerato un "oggetto di gioco", quanti di essi esistono in media e cos'altro accade nella tua applicazione.
I sistemi di oggetti orientati ai componenti sono una tendenza popolare in questo momento, sfruttando la filosofia secondo cui l' aggregazione è preferibile all'eredità . Tali sistemi potenzialmente consentono di dividere la logica di "aggiornamento" dei componenti (in cui "componente" è approssimativamente definito come un'unità di funzionalità, come la cosa che rappresenta la parte fisicamente simulata di un oggetto, che viene elaborata dal sistema fisico ) su più thread, discriminati dal tipo di componente, se possibile e desiderato, che potrebbero avere un guadagno in termini di prestazioni. Come minimo è possibile organizzare i componenti in modo tale che tutti i componenti di un determinato tipo si aggiornino insieme , facendo un uso ottimale della cache della CPU. Un esempio di tale sistema orientato ai componenti è discusso in questo thread .
Tali sistemi sono spesso altamente disaccoppiati, il che è anche un vantaggio per la manutenzione.
La progettazione orientata ai dati è un approccio correlato: si tratta di orientarsi intorno ai dati richiesti dagli oggetti come una priorità assoluta, in modo che i dati possano essere effettivamente elaborati in blocco (ad esempio). Ciò significa generalmente un'organizzazione che cerca di mantenere insieme i dati utilizzati per lo stesso cluster di scopi e di operare contemporaneamente. Non è fondamentalmente incompatibile con il design OO e puoi trovare alcune chiacchiere sull'argomento qui su GDSE in questa domanda .
In effetti, al posto del tuo originale sarebbe un approccio più ottimale al loop di gioco
foreach(GameObject g in gameObjects) g.update();
qualcosa di più simile
ProcessUserInput();
UpdatePhysicsForAllObjects();
UpdateScriptsForAllObjects();
UpdateRenderDataForAllObjects();
RenderEverything();
In un mondo del genere, ogni GameObject
potrebbe avere un puntatore o riferimento al proprio PhysicsData
o di Script
o RenderData
, per i casi in cui si potrebbe aver bisogno di interagire con gli oggetti su base individuale, ma l'attuale PhysicsData
, Scripts
, RenderData
, eccetera saranno tutti di proprietà dei rispettivi sottosistemi (simulatore di fisica, ambiente di hosting degli script, renderer) ed elaborati in blocco come indicato sopra.
È molto importante notare che questo approccio non è un proiettile magico e non sempre produrrà un miglioramento delle prestazioni (sebbene sia generalmente una progettazione migliore di un albero di eredità profondo). In particolare, noterai praticamente nessuna differenza di prestazione se hai pochissimi oggetti o molti oggetti per i quali non puoi parallelizzare efficacemente gli aggiornamenti.
Sfortunatamente, non esiste un loop magico così ottimale: ogni gioco è diverso e potrebbe essere necessario ottimizzare le prestazioni in diversi modi. Quindi è molto importante misurare (profilare) le cose prima di andare alla cieca seguendo il consiglio di un ragazzo a caso su Internet.