Codifica basata sui dati
Ogni cosa che menzioni è qualcosa che può essere specificato nei dati. Perché stai caricando aspecificmap
? Perché la configurazione del gioco dice che è di primo livello quando un giocatore inizia una nuova partita, o perché quello è il nome del punto di salvataggio corrente nel file di salvataggio del giocatore che ha appena caricato, ecc.
Come lo trovi aspecificmap
? Perché è in un file di dati che elenca gli ID delle mappe e le loro risorse su disco.
È necessario solo un insieme particolarmente ristretto di risorse "core" che sono legittimamente difficili o impossibili da evitare. Con un po 'di lavoro, questo può essere limitato a un singolo nome di asset predefinito codificato comemain.wad
o simile. Questo file può potenzialmente essere modificato in fase di esecuzione passando un argomento della riga di comando al gioco, aka game.exe -wad mymain.wad
.
La scrittura di codice basato sui dati si basa su alcuni altri principi. Ad esempio, si può evitare che sistemi o moduli richiedano una particolare risorsa e invertire invece tali dipendenze. Cioè, non DebugDrawer
caricare debug.font
nel suo codice di inizializzazione; invece, è necessario DebugDrawer
prendere un handle di risorse nel suo codice di inizializzazione. Tale handle potrebbe essere caricato dal file di configurazione del gioco principale.
Come esempi concreti della nostra base di codice, abbiamo un oggetto "dati globali" che viene caricato dal database delle risorse (che di per sé è la ./resources
cartella ma può essere sovraccaricato con un argomento della riga di comando). L'ID del database di risorse di questi dati globali è l'unico nome di risorsa hardcoded necessario nella base di codice (ne abbiamo altri perché a volte i programmatori diventano pigri, ma alla fine finiamo per risolverli / rimuoverli). Questo oggetto dati globale è pieno di componenti il cui unico scopo è fornire dati di configurazione. Uno dei componenti è il componente Dati globali dell'interfaccia utente che contiene gli handle di risorse per tutte le principali risorse dell'interfaccia utente (caratteri, file Flash, icone, dati di localizzazione, ecc.) Tra una serie di altri elementi di configurazione. Quando uno sviluppatore dell'interfaccia utente decide di rinominare l'asset dell'interfaccia utente principale da /ui/mainmenu.swf
a/ui/lobby.swf
aggiornano semplicemente quel riferimento globale di dati; nessun codice motore deve cambiare affatto.
Utilizziamo questi dati globali per tutto. Tutti i personaggi giocabili, tutti i livelli, l'interfaccia utente, l'audio, le risorse principali, la configurazione di rete, tutto. (beh, non tutto , ma quelle altre cose sono bug da correggere.)
Questo approccio ha molti altri vantaggi. Per uno, rende l'imballaggio e il raggruppamento delle risorse parte integrante dell'intero processo. I percorsi di codifica rigida nel motore tendono anche a significare che quegli stessi percorsi devono essere codificati in modo rigido in qualsiasi script o strumento impacchetta le risorse di gioco e tali percorsi possono quindi non essere sincronizzati. Basandoci invece su un singolo asset principale e catene di riferimento da lì, possiamo costruire un bundle di asset con un singolo comando come bundle.exe -root config.data -out main.wad
e sapere che includerà tutti gli asset di cui abbiamo bisogno. Inoltre, poiché il bundler seguirà solo i riferimenti alle risorse, sappiamo che includerà solo le risorse di cui abbiamo bisogno e salterà tutta la lanugine residua che inevitabilmente si accumula durante la vita di un progetto (inoltre possiamo generare automaticamente elenchi di ciò lanugine per potatura).
Un caso complicato di questa faccenda è negli script. Rendere il motore basato sui dati è concettualmente semplice, ma ho visto moltissimi progetti (passatempo per AAA) in cui gli script sono considerati dati e quindi "autorizzati" a utilizzare i percorsi delle risorse in modo indiscriminato. Non farlo. Se un file Lua ha bisogno di una risorsa e chiama solo una funzione come textures.lua("/path/to/texture.png")
quella, la pipeline delle risorse avrà molti problemi sapendo che lo script richiede /path/to/texture.png
per funzionare correttamente e potrebbe considerare quella trama inutilizzata e non necessaria. Gli script devono essere trattati come qualsiasi altro codice: tutti i dati di cui hanno bisogno, comprese le risorse o le tabelle, devono essere specificati in una voce di configurazione che il motore e la pipeline delle risorse possono verificare le dipendenze. I dati che dicono "carica script foo.lua
" invece dovrebbero dire "foo.lua
e assegnagli questi parametri "laddove i parametri includano qualsiasi risorsa necessaria. Se uno script genera casualmente nemici, ad esempio, passa l'elenco di possibili nemici nello script da quel file di configurazione. Il motore può quindi precaricare i nemici con il livello ( poiché conosce l'elenco completo di possibili spawn) e la pipeline di risorse sa raggruppare tutti i nemici con il gioco (poiché sono definitivamente referenziati dai dati di configurazione). Se gli script generano stringhe di nomi di percorso e chiamano semplicemente una load
funzione, allora nessuno dei due il motore né la pipeline di risorse hanno modo di conoscere in modo specifico quali risorse potrebbe essere caricata dallo script.