L'intero punto di a spazio è tenere oggetti di gioco. Gli oggetti di gioco in uno spazio non dovrebbero avere qualunque modo di comunicare con gli oggetti del gioco in un altro spazio, quindi gli spazi forniscono un mezzo semplice per separare diversi gruppi di oggetti di gioco. In questo articolo, imparerai i vantaggi di tale architettura.
Se desideri vedere un esempio dell'implementazione degli spazi, consulta il motore di gioco open source SEL. Io stesso sono autore attivo di SEL e sono orgoglioso di presentarlo come una risorsa pienamente funzionale per i lettori di questo articolo.
Vorrei ringraziare Sean Middleditch per avermi insegnato i vantaggi di Spaces.
Mancia: Il termine spazio nel contesto di questo articolo si riferisce a un contenitore speciale di oggetti di gioco. In realtà non sono a conoscenza di un termine ufficiale chiaramente definito. Se ne conosci uno, ti preghiamo di commentare!
In un motore di gioco convenzionale, gli oggetti di gioco vengono solitamente memorizzati in un singolo contenitore. Tale contenitore può essere un allocatore insieme a un gestore di handle. A volte il contenitore è solo una lista collegata. Non importa quale sia l'implementazione effettiva, potrebbe esserci un solo contenitore contenente tutti gli oggetti del gioco e ogni oggetto di gioco creato è sempre in questo contenitore.
Questo va bene e funziona completamente, ma ha alcuni problemi organizzativi. Ad esempio, immagina un tradizionale gestore dello stato di gioco. Spesso, tra il passaggio da uno stato a un altro, tutti gli oggetti di gioco attualmente caricati vengono liberati e ne vengono letti di nuovi dal disco. Come ottimizzazione, gli oggetti di gioco per il prossimo stato (o livello) possono essere caricati su un thread separato prima del tempo in modo che le transizioni di stato siano istantanee.
Tuttavia, esiste un problema fastidioso che si presenta comunemente: come rappresentiamo gli elementi della GUI per i menu? Forse l'HUD del giocatore è codificato usando oggetti di gioco e script allegati a questi oggetti di gioco. Un'implementazione ingenua della gestione dello stato richiederebbe la distruzione di tutti gli elementi dell'HUD e la ricostruzione dello stato. Ciò significa che sarà necessario un codice personalizzato per gestire la transizione di determinati oggetti da uno stato all'altro.
O forse il design di un gioco richiede scenari di sfondo pazzi dove si svolge una battaglia enorme, ma questa battaglia non deve interferire con il primo piano (o il giocatore) in alcun modo.
Spesso, nascono strane soluzioni hacky per questo genere di cose, come la rappresentazione di elementi HUD estremamente distanti dal resto del gameplay nel mondo. I menu di pausa e simili vengono semplicemente spostati in vista quando necessario e si allontanano altrimenti. In generale, è necessario un codice personalizzato per gestire e organizzare gli oggetti del gioco, poiché tutti risiedono su un singolo stabilimento o contenitore.
Una soluzione accettabile a questo problema sarebbe quella di utilizzare gli spazi (vedere la descrizione del video aggiuntiva del mio collega Sean Middleditch). Poiché tutti gli oggetti di gioco in ogni spazio hanno zero interazione, gli spazi diventano un modo naturale per avvicinarsi all'organizzazione degli oggetti di gioco. Ciò può ridurre notevolmente la necessità di un codice speciale per mantenere un contenitore logico separato all'interno di un contenitore reale (come menzionato nella sezione precedente).
Diamo una rapida occhiata a un esempio di come potrebbe essere un'implementazione dello spazio in un linguaggio simile al C ++:
class Space public: GameObject CreateObject (nome stringa); const string GetName (void) const; private: string m_name; // Potrebbe essere una qualche forma di allocatore, forse solo un contenitore std :: vector m_objects; ;
Spesso è utile essere in grado di cercare uno spazio per nome. Questo è particolarmente utile per gli script in cui uno script può utilizzare il codice in questo modo:
// Esegue qualche logica per lo spazio locale dello sfondo = GetSpace ("Background") tornado = space.CreateObject ("Tornado") // Altrove possiamo fare qualcosa completamente isolato dallo sfondo, // come recuperare il giocatore (che per alcuni motivo è morto) e giocando un'animazione // morte sopra lo spazio locale del giocatore = GetSpace ("CurrentLevel") player = space.GetPlayer () space.CreateObjectAt ("DeathAnimation", player)Diagramma che mostra la semplice organizzazione di oggetti di gioco in contenitori isolati. Non tutti gli spazi hanno la stessa quantità di oggetti di gioco o persino gli stessi oggetti di gioco.
La risposta a questa domanda è: tutti loro! Qualsiasi tipo di oggetto di gioco verrà inserito in uno spazio. Se hai familiarità con l'aggregazione (o la progettazione basata sui componenti), allora un oggetto di gioco e i suoi componenti associati risiederanno tutti nello stesso spazio.
L'idea è di creare una funzionalità architettonica per consentire un modo semplice ed efficace di raggruppare insieme gli oggetti di gioco e isolarli da altri gruppi.
Gli spazi sono abbastanza simili ad altri concetti che sono stati in giro per un po 'di tempo. È stato detto che gli spazi sono simili all'elenco di visualizzazione in Flash. Se hai familiarità con i portali per il rendering del culling (particolarmente importante nei giochi 3D con molte sale interne), l'idea è abbastanza simile anche qui.
Tuttavia, c'è un'importante distinzione da fare qui. Gli spazi sono non una forma di partizionamento spaziale come avviene con i portali o altri partizionamenti spaziali (come il popolare albero BSP) per il rendering dell'occlusione. Gli spazi sono un architettonico funzione per consentire l'isolamento di oggetti di gioco generali.
Personalmente, mi piace pensare a spazi come i livelli di Photoshop: tutti i livelli possono essere visualizzati (o ascoltati) contemporaneamente, ma quando si dipinge su un livello, nessun altro livello viene mai direttamente influenzato; ogni strato è unico e isolato.
Il concetto di a sistema, ai fini del presente articolo, può essere descritto come un insieme di funzionalità (funzioni o metodi) che operano sugli oggetti di gioco di uno spazio. In questo modo, uno spazio viene consegnato a un sistema per eseguire un'azione; un particolare sistema è globale per l'intero gioco.
Diagramma che esemplifica la semplicità di consentire a un sistema di operare su uno spazio come input.Immagina un sistema grafico che contenga un void Graphics :: DrawWorld (Space space)
funzione. Questo DrawWorld
la funzione si sovrappone agli oggetti all'interno dello spazio dato che sono renderizzabili e li disegna sullo schermo.
L'idea è ora scrivere codice (sistemi) che funzioni su un determinato input di oggetti di gioco. All'interno di tali sistemi non deve avvenire alcun monitoraggio o gestione speciale degli oggetti del gioco. Un sistema non dovrebbe fare nulla se non eseguire operazioni sugli oggetti di gioco.
Questo stile offre alcuni vantaggi davvero interessanti, come descritto nella prossima sezione.
Il vantaggio più immediato di implementare gli spazi all'interno di un motore è che la gestione di elementi o menu della GUI diventa banale. Possiamo costruire uno spazio dedicato a un particolare menu e, ogni volta che questo menu è inattivo, i sistemi semplicemente non hanno bisogno di operare sul contenuto dello spazio. Quando un menu è inattivo, si trova in memoria (gli oggetti del gioco che comprendono la memoria siedono all'interno dello spazio del menu) e non fa nulla; non è né aggiornato da un sistema logico né reso da un sistema grafico.
Quando questo menu diventa di nuovo attivo, può essere semplicemente trasferito ai sistemi appropriati. Il menu può quindi essere aggiornato e reso in modo appropriato. Allo stesso tempo, il gameplay in corso dietro il menu può smettere di essere aggiornato in alcun modo, anche se forse è ancora passato al sistema grafico da rendere. Questo implementa banalmente un elegante e robusto tipo di funzionalità di pausa-ripresa che viene implicitamente dovuto al modo in cui gli spazi sono definiti.
Spesso, nei giochi in stile RPG come Pokémon, il giocatore entra e lascia le case e le capanne. Per quanto riguarda il gameplay, queste diverse case sono di solito completamente isolate; piccole case o caverne sono uno scenario ideale per applicare gli spazi. Si può costruire un intero spazio per contenere gli oggetti di gioco di una particolare casa, che possono essere caricati e inizializzati in memoria e riposati fino a quando necessario. Le transizioni istantanee possono essere realizzate semplicemente scambiando lo spazio che viene consegnato ai vari sistemi del motore.
Un'idea interessante per giochi 2D come i platform (o anche i giochi 3D) potrebbe essere quella di simulare livelli reali e nemici dal gioco in background. Questo potrebbe portare il mondo alla vita in un modo che in realtà non richiede alcun contenuto aggiuntivo, e quasi nessun ulteriore tempo di sviluppo. Il miglior esempio che ho trovato di questo è Rayman Legends:
I nemici reali, come il giocatore vede normalmente, potrebbero saltare o strisciare sui muri in lontananza. Le transizioni tra questi diversi "livelli" potrebbero offrire alcune possibilità di design molto interessanti.
Questo tipo di possibilità sono in realtà abbastanza rari per trovare esempi e l'idea di spazi non è realmente accettata dai moderni studi o motori AAA. Tuttavia, il design è solido e i benefici sono reali.
In molti giochi con supporto multiplayer in cui entrambi i giocatori giocano con lo stesso client di gioco, ci sono alcune limitazioni. Spesso i giocatori non possono andare in una nuova area senza essere entrambi vicini l'uno all'altro. A volte i giocatori non possono nemmeno lasciare lo schermo l'uno dell'altro. Ciò potrebbe essere dovuto al design del gioco, ma ho il sospetto che sia spesso dovuto a limiti architettonici.
Con gli spazi, possiamo supportare due giocatori, magari con schermi divisi, viaggiando da un livello o da un edificio all'altro. Ogni giocatore può risiedere nello stesso spazio, o in due spazi separati. Quando ciascun giocatore si trova in un altro edificio, potrebbero essere entrambi in due spazi separati. Uno converge sulla stessa area, il motore può trasferire uno degli oggetti di gioco del giocatore nello spazio opposto.
Questo è sicuramente il mio esempio preferito di come gli spazi sono fantastici. Negli editori ci sono spesso sezioni in cui puoi sviluppare un nuovo tipo di oggetto. Questo oggetto nella creazione di solito avrà una vista per vedere in anteprima la creazione mentre lo sviluppo continua.
Può essere impossibile per la maggior parte dei motori supportare naturalmente un simile editor. Cosa succede se l'utente modifica la posizione e all'improvviso si scontra con la simulazione e mette a repentaglio le cose o attiva qualche AI? È necessario creare un codice personalizzato per gestire in modo elegante l'oggetto in memoria in qualche modo. O codice di isolamento caso speciale, o qualche formato intermedio, deve essere modificato e tradotto dall'editor alla simulazione effettiva. I passaggi intermedi possono essere una qualche forma di serializzazione, o complesso "oggetto proxy" fittizio non intrusivo. Gli oggetti proxy possono spesso richiedere l'introspezione avanzata del codice per essere implementati in modo utile. Queste opzioni possono essere costose o non necessarie per molti progetti.
Tuttavia, se si dispone di spazi a disposizione, un oggetto camera e l'oggetto nella creazione possono essere collocati in uno spazio isolato. Questo spazio può quindi essere consegnato a qualsiasi sistema necessario e gestito in modo elegante. In questo scenario non sarebbe necessario alcun codice caso speciale o ulteriore authoring.
È possibile gestire più livelli all'interno degli editor con facilità. Più finestre e simulazioni possono essere eseguite simultaneamente in modo isolato. Cosa accadrebbe se uno sviluppatore volesse suddividere in due parti lo sviluppo di due livelli per spostarsi avanti e indietro rapidamente? Questo potrebbe essere un compito difficile di ingegneria del software, oppure l'architettura dell'editor potrebbe implementare una qualche forma di spazio.
Cosa gestisce tutti gli spazi? Molti sviluppatori di giochi potrebbero averlo nella loro pratica che ogni cosa deve poter essere "posseduta" da qualche manager. Tutti gli spazi devono poter essere gestiti da questa singola entità, giusto? In realtà, questo tipo di paradigma non è necessario tutto il tempo.
Nel motore SEL, gli spazi sono costruiti da una posizione e possono essere cercati per nome con un dizionario, ma potrebbe essere preferibile che la maggior parte dei progetti lasci semplicemente che gli spazi siano gestiti caso per caso. Spesso, ha senso creare uno spazio all'interno di uno script casuale, tenerlo premuto per un po 'e quindi rilasciare lo spazio. Altre volte viene creato uno spazio e rimane in memoria l'intera durata del runtime del gioco.
Una buona raccomandazione sarebbe semplicemente lasciare che l'utente assegni uno spazio e lo liberi a volontà. Sarebbe probabilmente utile utilizzare un singolo allocatore per questo comportamento. Tuttavia, la gestione dell'istanza spaziale stessa, come trovata attraverso l'esperienza, potrebbe non essere la cosa più preoccupante; lasciarlo all'utente.
Nota: quando uno spazio viene distrutto, dovrebbe ripulire tutti gli oggetti all'interno! Non confondere la mancanza di un manager con una mancanza di gestione della durata.
I componenti sono spesso registrati con i rispettivi sistemi in un motore basato su componenti. Tuttavia, con gli spazi questo diventa inutile. Invece, ogni spazio dovrebbe contenere ciò che viene chiamato a sottospazio. Un sottospazio può essere molto banale da implementare, ad esempio, come vettore di oggetti componenti. L'idea è di contenere semplicemente vari tipi di contenitori componenti all'interno di ogni spazio. Ogni volta che un componente viene costruito, richiede lo spazio con cui deve essere associato e si registra con il sottospazio.
Uno spazio non deve necessariamente avere ogni singolo tipo di sottospazio in sé. Ad esempio, uno spazio del menu non avrà probabilmente bisogno di alcuna simulazione fisica e quindi potrebbe non avere un'intera istanza di un mondo fisico che rappresenta un sottospazio fisico.
Infine, va notato che in un'architettura basata su componenti gli oggetti di gioco devono avere un handle o un puntatore allo spazio in cui risiedono. In questo modo, lo spazio diventa una parte dell'identificatore univoco dell'oggetto stesso del gioco.
Gli spazi sono estremamente semplici da implementare e offrono molti importanti vantaggi. Per praticamente ogni motore di gioco e di gioco esistente, l'aggiunta di spazi sarà positiva grazie alla facilità di implementazione. Usa gli spazi, anche per progetti molto piccoli e giochi molto piccoli!
Come risorsa, la mia implementazione degli spazi è open source per la visualizzazione pubblica all'interno del motore di gioco SEL.