Personalmente raccomando di mantenere la funzione draw fuori dalla stessa classe Object. Consiglio anche di mantenere la posizione / coordinate degli oggetti fuori dall'oggetto stesso.
Tale metodo draw () si occuperà dell'API di rendering di basso livello di OpenGL, OpenGL ES, Direct3D, il livello di wrapping su tali API o un'API di motori. È possibile che sia necessario passare da uno all'altro (ad esempio, se si desidera supportare OpenGL + OpenGL ES + Direct3D.
Quel GameObject dovrebbe contenere solo le informazioni di base sul suo aspetto visivo come una mesh o forse un pacchetto più grande che include input di shader, stato di animazione e così via.
Inoltre vorrai una pipeline grafica flessibile. Cosa succede se si desidera ordinare oggetti in base alla loro distanza dalla fotocamera. O il loro tipo di materiale. Cosa succede se vuoi disegnare un oggetto "selezionato" di un colore diverso. Che dire se invece di eseguire il rendering in modo così semplice come si chiama una funzione di disegno su un oggetto, invece lo inserisce in un elenco di comandi di azioni da eseguire per il rendering (potrebbe essere necessario per il threading). Puoi fare quel genere di cose con l'altro sistema ma è un PITA.
Quello che raccomando è invece di disegnare direttamente, si associano tutti gli oggetti desiderati a un'altra struttura di dati. Tale associazione deve solo avere un riferimento alla posizione degli oggetti e alle informazioni di rendering.
I tuoi livelli / blocchi / aree / mappe / hub / mondo intero / qualunque cosa ricevano un indice spaziale, questo contiene gli oggetti e li restituisce sulla base di query coordinate e potrebbe essere un semplice elenco o qualcosa come un Octree. Potrebbe anche essere un involucro di qualcosa implementato da un motore fisico di terze parti come una scena fisica. Ti permette di fare cose come "Esegui query su tutti gli oggetti che sono nella vista della videocamera con un po 'di spazio in più intorno a loro" o per giochi più semplici in cui puoi semplicemente eseguire il rendering di tutto afferrare l'intero elenco.
Gli indici spaziali non devono contenere le informazioni sul posizionamento effettivo. Funzionano immagazzinando oggetti in strutture ad albero in relazione alla posizione di altri oggetti. Possono essere considerati come una sorta di cache con perdita che consente una rapida ricerca di un oggetto in base alla sua posizione. Non è necessario duplicare le coordinate X, Y, Z effettive. Detto questo, se lo volessi mantenere potresti farlo
In effetti i tuoi oggetti di gioco non devono nemmeno contenere le informazioni sulla loro posizione. Ad esempio, un oggetto che non è stato messo in un livello non dovrebbe avere coordinate x, y, z, che non ha senso. Puoi contenerlo nell'indice speciale. Se è necessario cercare le coordinate dell'oggetto in base al suo riferimento effettivo, sarà necessario disporre di un'associazione tra l'oggetto e il grafico della scena (i grafici delle scene servono per restituire gli oggetti in base alle coordinate ma sono lenti nel restituire le coordinate in base agli oggetti) .
Quando aggiungi un oggetto a un livello. Farà quanto segue:
1) Creare una struttura di posizione:
class Location {
float x, y, z; // Or a special Coordinates class, or a vec3 or whatever.
SpacialIndex& spacialIndex; // Note this could be the area/level/map/whatever here
};
Questo potrebbe anche essere un riferimento a un oggetto in un motore fisico di terze parti. Oppure potrebbe essere un coordinate di offset con un riferimento a un'altra posizione (per una telecamera di localizzazione o un oggetto o un esempio allegato). Con il polimorfismo potrebbe dipendere dal fatto che si tratti di un oggetto statico o dinamico. Mantenendo un riferimento all'indice spaziale qui quando le coordinate vengono aggiornate, anche l'indice spaziale può essere.
Se si è preoccupati dell'allocazione dinamica della memoria, utilizzare un pool di memoria.
2) Un legame / collegamento tra l'oggetto, la sua posizione e il grafico della scena.
typedef std::pair<Object, Location> SpacialBinding.
3) Il legame viene aggiunto all'indice spaziale all'interno del livello nel punto appropriato.
Quando ti stai preparando per il rendering.
1) Ottieni la videocamera (sarà solo un altro oggetto, tranne per il fatto che la posizione traccerà il personaggio dei giocatori e il tuo renderer avrà un riferimento speciale ad esso, infatti è tutto ciò di cui ha davvero bisogno).
2) Ottieni il SpacialBinding della videocamera.
3) Ottieni l'indice spaziale dalla rilegatura.
4) Interroga gli oggetti che sono (possibilmente) visibili alla telecamera.
5A) È necessario che le informazioni visive siano elaborate. Trame caricate sulla GPU e così via. Questo sarebbe meglio farlo in anticipo (come ad esempio sul carico di livello) ma forse potrebbe essere fatto in fase di esecuzione (per un mondo aperto, potresti caricare roba quando ti stai avvicinando a un pezzo ma dovrebbe comunque essere fatto in anticipo).
5B) Se lo si desidera, creare un albero di rendering memorizzato nella cache, se si desidera ordinare in profondità / materiale o tenere traccia degli oggetti vicini, potrebbe essere visibile in un secondo momento. Altrimenti puoi semplicemente interrogare l'indice spaziale ogni volta che dipenderà dai tuoi requisiti di gioco / prestazioni.
Il tuo renderer avrà probabilmente bisogno di un oggetto RenderBinding che si collegherà tra l'Oggetto, le coordinate
class RenderBinding {
Object& object;
RenderInformation& renderInfo;
Location& location // This could just be a coordinates class.
}
Quindi quando esegui il rendering, esegui solo l'elenco.
Ho usato i riferimenti sopra ma potrebbero essere puntatori intelligenti, puntatori non elaborati, handle di oggetti e così via.
MODIFICARE:
class Game {
weak_ptr<Camera> camera;
Level level1;
void init() {
Camera camera(75.0_deg, 1.025_ratio, 1000_meters);
auto template_player = loadObject("Player.json")
auto player = level1.addObject(move(player), Position(1.0, 2.0, 3.0));
level1.addObject(move(camera), getRelativePosition(player));
auto template_bad_guy = loadObject("BadGuy.json")
level1.addObject(template_bad_guy, {10, 10, 20});
level1.addObject(template_bad_guy, {10, 30, 20});
level1.addObject(move(template_bad_guy), {50, 30, 20});
}
void render() {
camera->getFrustrum();
auto level = camera->getLocation()->getLevel();
auto object = level.getVisible(camera);
for(object : objects) {
render(objects);
}
}
void render(Object& object) {
auto ri = object.getRenderInfo();
renderVBO(ri.getVBO());
}
Object loadObject(string file) {
Object object;
// Load file from disk and set the properties
// Upload mesh data, textures to GPU. Load shaders whatever.
object.setHitPoints(// values from file);
object.setRenderInfo(// data from 3D api);
}
}
class Level {
Octree octree;
vector<ObjectPtr> objects;
// NOTE: If your level is mesh based there might also be a BSP here. Or a hightmap for an openworld
// There could also be a physics scene here.
ObjectPtr addObject(Object&& object, Position& pos) {
Location location(pos, level, object);
objects.emplace_back(object);
object->setLocation(location)
return octree.addObject(location);
}
vector<Object> getVisible(Camera& camera) {
auto f = camera.getFtrustrum();
return octree.getObjectsInFrustrum(f);
}
void updatePosition(LocationPtr l) {
octree->updatePosition(l);
}
}
class Octree {
OctreeNode root_node;
ObjectPtr add(Location&& object) {
return root_node.add(location);
}
vector<ObjectPtr> getObjectsInRadius(const vec3& position, const float& radius) { // pass to root_node };
vector<ObjectPtr> getObjectsinFrustrum(const FrustrumShape frustrum;) {//...}
void updatePosition(LocationPtr* l) {
// Walk up from l.octree_node until you reach the new place
// Check if objects are colliding
// l.object.CollidedWith(other)
}
}
class Object {
Location location;
RenderInfo render_info;
Properties object_props;
Position getPosition() { return getLocation().position; }
Location getLocation() { return location; }
void collidedWith(ObjectPtr other) {
// if other.isPickup() && object.needs(other.pickupType()) pick it up, play sound whatever
}
}
class Location {
Position position;
LevelPtr level;
ObjectPtr object;
OctreeNote octree_node;
setPosition(Position position) {
position = position;
level.updatePosition(this);
}
}
class Position {
vec3 coordinates;
vec3 rotation;
}
class RenderInfo {
AnimationState anim;
}
class RenderInfo_OpenGL : public RenderInfo {
GLuint vbo_object;
GLuint texture_object;
GLuint shader_object;
}
class Camera: public Object {
Degrees fov;
Ratio aspect;
Meters draw_distance;
Frustrum getFrustrum() {
// Use above to make a skewed frustum box
}
}
Quanto a rendere le cose "consapevoli" l'una dell'altra. Questo è il rilevamento delle collisioni. Sarebbe probabilmente implementato nell'ottobre. Dovresti fornire qualche richiamata nel tuo oggetto principale. Questa roba è gestita al meglio da un motore fisico adeguato come Bullet. In tal caso, sostituisci Octree con PhysicsScene e Position con un link a qualcosa come CollisionMesh.getPosition ().