Ad un certo punto un motore DEVE specializzarsi e conoscere cose sul gioco. Vado su una tangente qui.
Prendi risorse in un RTS. Un gioco può avere Credits
e Crystal
un altro Metal
ePotatoes
Dovresti usare i concetti OO correttamente e andare per max. code-riutilizzo. È chiaro che Resource
qui esiste un concetto di .
Quindi decidiamo che le risorse hanno il seguente:
- Un hook nel loop principale per incrementare / decrementare se stessi
- Un modo per ottenere l'importo corrente (restituisce un
int
)
- Un modo per sottrarre / aggiungere arbitrariamente (giocatori che trasferiscono risorse, acquisti ....)
Nota che questa nozione di a Resource
potrebbe rappresentare uccisioni o punti in una partita! Non è molto potente.
Ora pensiamo a un gioco. Possiamo in qualche modo avere valuta trattando in penny e aggiungendo un punto decimale all'output. Ciò che non possiamo fare sono risorse "istantanee". Come dire "generazione della rete elettrica"
Diciamo che aggiungi una InstantResource
classe con metodi simili. Ora stai (iniziando a) inquinare il tuo motore con risorse.
Il problema
Facciamo di nuovo l'esempio RTS. Supponiamo che qualunque cosa doni qualcuno Crystal
ad un altro giocatore. Vuoi fare qualcosa del tipo:
if(transfer.target == engine.getPlayerId()) {
engine.hud.addIncoming("You got "+transfer.quantity+" of "+
engine.resourceDictionary.getNameOf(transfer.resourceId)+
" from "+engine.getPlayer(transfer.source).name);
}
engine.getPlayer(transfer.target).getResourceById(transfer.resourceId).add(transfer.quantity)
engine.getPlayer(transfer.source).getResourceById(transfer.resourceId).add(-transfer.quantity)
Tuttavia, questo è davvero piuttosto disordinato. È uno scopo generale, ma disordinato. Già se impone un resourceDictionary
che significa che ora le tue risorse devono avere nomi! E lo è per giocatore, quindi non puoi più avere risorse di squadra.
Questa è un'astrazione "troppo" (non un brillante esempio, lo ammetto) invece dovresti colpire un punto in cui accetti che il tuo gioco ha giocatori e cristalli, quindi puoi semplicemente avere (per esempio)
engine.getPlayer(transfer.target).crystal().receiveDonation(transfer)
engine.getPlayer(transfer.source).crystal().sendDonation(transfer)
Con una classe Player
e una classe in CurrentPlayer
cui CurrentPlayer
l' crystal
oggetto mostrerà automaticamente le cose sull'HUD per il trasferimento / invio di donazioni.
Questo inquina il motore con il cristallo, la donazione del cristallo, i messaggi sull'HUD per i giocatori attuali e tutto il resto. È più veloce e più facile da leggere / scrivere / mantenere (che è più importante, in quanto non è significativamente più veloce)
Osservazioni finali
Il caso delle risorse non è eccezionale. Spero che tu possa ancora vedere il punto però. Semmai ho dimostrato che "le risorse non appartengono al motore" in quanto ciò di cui un gioco specifico ha bisogno e ciò che è applicabile a tutte le nozioni di risorse sono cose MOLTO diverse. Quello che troverai di solito sono 3 (o 4) "livelli"
- Il "Core" - questa è la definizione da manuale del motore, è un grafico di scena con hook di eventi, si occupa di shader e pacchetti di rete e una nozione astratta di giocatori
- Il "GameCore" - Questo è piuttosto generico per il tipo di gioco, ma non per tutti i giochi, ad esempio risorse in RTS o munizioni in FPS. La logica del gioco inizia a filtrare qui. Questo è dove la nostra precedente nozione di risorse sarebbe. Abbiamo aggiunto queste cose che hanno senso per la maggior parte delle risorse RTS.
- "GameLogic" MOLTO specifico per il gioco reale in corso di realizzazione. Troverai variabili con nomi come
creature
o ship
o squad
. Usando l' ereditarietà otterrai classi che si estendono su tutti e 3 i livelli (ad esempio Crystal
è un Resource
che è un GameLoopEventListener
dire)
- Le "risorse" sono inutili per qualsiasi altro gioco. Prendiamo ad esempio gli script AI combinati in Half Life 2, non verranno utilizzati in un RTS con lo stesso motore.
Realizzare un nuovo gioco da un vecchio motore
Questo è MOLTO comune. La fase 1 è quella di strappare i livelli 3 e 4 (e 2 se il gioco è di tipo TOTALMENTE diverso) Supponiamo di fare un RTS da un vecchio RTS. Abbiamo ancora risorse, non solo cristallo e roba del genere - quindi le classi di base nei livelli 2 e 1 hanno ancora senso, tutto quel cristallo a cui si fa riferimento in 3 e 4 può essere scartato. Quindi lo facciamo. Possiamo comunque verificarlo come riferimento per ciò che vogliamo fare.
Inquinamento nel livello 1
Questo può succedere. Astrazione e prestazioni sono nemici. UE4 ad esempio fornisce molti casi ottimizzati di composizione (quindi se vuoi X e Y qualcuno ha scritto codice che fa X e Y insieme molto velocemente - sa che sta facendo entrambi) e di conseguenza è DAVVERO piuttosto grande. Questo non è male ma richiede tempo. Il livello 1 deciderà cose come "come si passano i dati agli shader" e come si animano le cose. Farlo nel modo migliore per il tuo progetto è SEMPRE buono. Prova a pianificare il futuro, riutilizzare il codice è tuo amico, ereditare dove ha senso.
Livelli di classificazione
ULTIMAMENTE (lo prometto) non aver paura dei livelli. Engine è un termine arcaico dei vecchi tempi delle pipeline a funzione fissa in cui i motori funzionavano praticamente allo stesso modo graficamente (e di conseguenza aveva molto in comune) la pipeline programmabile ha ribaltato questo concetto e come tale "strato 1" è stato inquinato con qualunque effetto gli sviluppatori volessero ottenere. L'intelligenza artificiale era la caratteristica distintiva (a causa della miriade di approcci) dei motori, ora è AI e grafica.
Il tuo codice non dovrebbe essere archiviato in questi livelli. Anche il famoso motore Unreal ha MOLTE versioni diverse ognuna specifica per un gioco diverso. Esistono pochi file (diversi dalle strutture dati simili forse) che sarebbero rimasti invariati. Questo va bene! Se vuoi creare un nuovo gioco da un altro, ci vorranno più di 30 minuti. La chiave è pianificare, sapere quali bit copiare e incollare e cosa lasciarsi alle spalle.