Questo consiglio non è molto specifico per il rendering ma dovrebbe aiutare a creare un sistema che mantenga le cose in gran parte separate. Innanzitutto, cerca di mantenere separati i dati "GameObject" dalle informazioni sulla posizione.
Vale la pena notare che le semplici informazioni sulla posizione XYZ potrebbero non essere così semplici. Se si utilizza un motore fisico, i dati di posizione potrebbero essere memorizzati all'interno del motore di terze parti. Dovresti o sincronizzarti tra loro (il che implicherebbe un sacco di inutili copie di memoria) o interrogare le informazioni direttamente dal motore. Ma non tutti gli oggetti necessitano di fisica, alcuni saranno riparati in posizione, quindi un semplice set di galleggianti funziona bene lì. Alcuni potrebbero anche essere collegati ad altri oggetti, quindi la loro posizione è in realtà un offset di un'altra posizione. In una configurazione avanzata potresti avere la posizione memorizzata solo sulla GPU l'unica volta che sarebbe necessario lato computer è per lo scripting, l'archiviazione e la replica di rete. Quindi avrai probabilmente diverse scelte possibili per i tuoi dati posizionali. Qui ha senso usare l'eredità.
Piuttosto che un oggetto che possiede la sua posizione, tale oggetto dovrebbe essere esso stesso posseduto da una struttura di dati di indicizzazione. Ad esempio, un "Livello" potrebbe avere un Octree, o forse una "scena" di un motore fisico. Quando si desidera eseguire il rendering (o impostare una scena di rendering), si richiede alla struttura speciale oggetti visibili alla telecamera.
Questo aiuta anche a dare una buona gestione della memoria. In questo modo un oggetto che non si trova in un'area non ha nemmeno una posizione che ha senso piuttosto che restituire 0,0 coords o i coords che aveva quando era l'ultimo in un'area.
Se non conservi più le coordinate nell'oggetto, invece di object.getX () finiresti per avere level.getX (oggetto). Il problema è che la ricerca dell'oggetto nel livello sarà probabilmente un'operazione lenta poiché dovrà guardare attraverso tutti i suoi oggetti e corrispondere a quello richiesto.
Per evitarlo probabilmente creerei una classe speciale di "link". Uno che si lega tra un livello e un oggetto. Lo chiamo "Posizione". Ciò conterrebbe le coordinate xyz, nonché l'handle al livello e un handle all'oggetto. Questa classe di collegamento verrebbe memorizzata nella struttura / livello spaziale e l'oggetto avrebbe un debole riferimento ad essa (se il livello / posizione viene distrutto, la rifrazione degli oggetti deve essere aggiornata su null. Potrebbe valere la pena avere effettivamente la classe Location 'possiede' l'oggetto, in questo modo se un livello viene eliminato, così è la struttura dell'indice speciale, le posizioni che contiene e i suoi oggetti.
typedef std::tuple<Level, Object, PositionXYZ> Location;
Ora le informazioni sulla posizione sono memorizzate in un'unica posizione. Non duplicato tra l'oggetto, la struttura di indicizzazione spaziale, il renderer e così via.
Strutture di dati spaziali come Octrees spesso non hanno nemmeno bisogno di avere le coordinate degli oggetti che memorizzano. La posizione viene memorizzata nella posizione relativa dei nodi nella struttura stessa (potrebbe essere considerata una sorta di compressione con perdita di dati, sacrificando l'accuratezza per tempi di ricerca rapidi). Con l'oggetto location nell'ottobre, al termine della query vengono trovate le coordinate effettive al suo interno.
O se stai usando un motore fisico per gestire le posizioni dei tuoi oggetti o una miscela tra i due, la classe Location dovrebbe gestirla in modo trasparente mantenendo tutto il tuo codice in un unico posto.
Un altro vantaggio è ora la posizione e il riferimento al livello è memorizzato nella stessa posizione. È possibile implementare object.TeleportTo (other_object) e farlo funzionare su più livelli. Allo stesso modo, la ricerca del percorso dell'IA potrebbe seguire qualcosa in un'area diversa.
Per quanto riguarda il rendering. Il rendering può avere un'associazione simile alla posizione. Tranne che avrebbe il materiale specifico per il rendering lì dentro. Probabilmente non è necessario che "Oggetto" o "Livello" siano memorizzati in questa struttura. L'oggetto potrebbe essere utile se stai cercando di fare qualcosa come la selezione del colore o il rendering di una hitbar che galleggia sopra di esso e così via, ma per il resto il renderer si preoccupa solo della mesh e simili. RenderableStuff sarebbe una Mesh, potrebbe anche avere delimitatori e così via.
typedef std::pair<RenderableStuff, PositionXYZ> RenderThing;
renderer.render(level, camera);
renderer: object = level.getVisibleObjects(camera);
level: physics.getObjectsInArea(physics.getCameraFrustrum(camera));
for(object in objects) {
//This could be depth sorted, meshes could be broken up and sorted by material for batch rendering or whatever
rendering_que.addObjectToRender(object);
}
Potresti non aver bisogno di farlo ogni frame, puoi assicurarti di prendere una regione più grande di quella attualmente mostrata dalla fotocamera. Memorizzalo nella cache, traccia i movimenti degli oggetti per vedere se il riquadro di selezione rientra nel raggio d'azione, traccia i movimenti della fotocamera e così via. Ma non iniziare a fare casini con quel tipo di cose fino a quando non le hai confrontate.
Lo stesso motore fisico potrebbe avere un'astrazione simile, dal momento che non ha bisogno nemmeno dei dati Oggetto, ma solo della mesh di collisione e delle proprietà fisiche.
Tutti i dati del tuo oggetto principale dovrebbero contenere il nome della mesh utilizzata dall'oggetto. Il motore di gioco può quindi andare avanti e caricarlo in qualsiasi formato preferisca senza sovraccaricare la classe di oggetti con un mucchio di cose specifiche per il rendering (che potrebbero essere specifiche per l'API di rendering, ovvero DirectX vs OpenGL).
Mantiene anche diversi componenti separati. Questo rende facile fare cose come sostituire il tuo motore fisico poiché quella roba è per lo più autonoma in un'unica posizione. Rende inoltre molto più semplice l'iterazione. Puoi testare cose come le query di fisica senza dover configurare alcun oggetto falso reale poiché tutto ciò di cui hai bisogno è la classe Location. Puoi anche ottimizzare le cose più facilmente. Rende più ovvio quali query è necessario eseguire su quali classi e singole posizioni per ottimizzarle (ad esempio il livello sopra.
m_renderable
membro. In questo modo, puoi separare meglio la tua logica. Non imporre la "interfaccia" di rendering su oggetti generici che hanno anche fisica, ai e quant'altro. Successivamente, è possibile gestire i renderable separatamente. È necessario un livello di astrazione sulle chiamate di funzione OpenGL per disaccoppiare ancora di più le cose. Quindi, non aspettarti che un buon motore abbia chiamate API GL all'interno delle sue varie implementazioni renderizzabili. Questo è tutto, in poche parole.