Osobiście zalecam trzymanie funkcji rysowania poza samą klasą Object. Polecam nawet trzymanie lokalizacji / współrzędnych obiektów poza samym obiektem.
Ta metoda draw () będzie zajmować się API renderowania niskiego poziomu OpenGL, OpenGL ES, Direct3D, warstwą owijania na tych interfejsach API lub interfejsem API silników. Może być tak, że musisz przełączać się między nimi (jeśli na przykład chcesz obsługiwać OpenGL + OpenGL ES + Direct3D).
GameObject powinien po prostu zawierać podstawowe informacje o jego wyglądzie, takie jak siatka lub może większy pakiet zawierający dane cieniowania, stan animacji i tak dalej.
Będziesz także potrzebować elastycznego potoku graficznego. Co się stanie, jeśli chcesz zamówić obiekty na podstawie ich odległości od kamery. Lub ich rodzaj materiału. Co się stanie, jeśli chcesz narysować „wybrany” obiekt innym kolorem. Co jeśli zamiast faktycznie renderować tak soo, jak wywołujesz funkcję rysowania na obiekcie, zamiast tego umieszcza ją na liście poleceń, które ma wykonać render (może być potrzebny do wątkowania). Możesz zrobić coś takiego z innym systemem, ale jest to PITA.
To, co zalecam, to zamiast bezpośredniego rysowania, wiążesz wszystkie obiekty, które chcesz, z inną strukturą danych. Wiązanie to naprawdę musi mieć tylko odwołanie do położenia obiektów i informacji o renderowaniu.
Twoje poziomy / fragmenty / obszary / mapy / huby / cały świat / cokolwiek dostanie indeks przestrzenny, zawiera obiekty i zwraca je na podstawie zapytań o współrzędne i może być prostą listą lub czymś w rodzaju Octree. Może to być także opakowanie czegoś zaimplementowanego przez silnik fizyki innej firmy jako scena fizyki. Pozwala ci robić rzeczy takie jak „Zapytaj wszystkie obiekty, które są w polu widzenia kamery z jakimś dodatkowym obszarem wokół nich”, lub dla prostszych gier, w których możesz po prostu renderować wszystko, chwytając całą listę.
Indeksy przestrzenne nie muszą zawierać rzeczywistych informacji o położeniu. Działają, przechowując obiekty w strukturach drzewnych w stosunku do położenia innych obiektów. Mogą być traktowane jako rodzaj stratnej pamięci podręcznej, która umożliwia szybkie wyszukiwanie obiektu na podstawie jego położenia. Nie ma potrzeby kopiowania rzeczywistych współrzędnych X, Y, Z. Powiedziawszy, że możesz, jeśli chcesz zachować
W rzeczywistości twoje obiekty gry nie muszą nawet zawierać własnych informacji o lokalizacji. Na przykład obiekt, który nie został umieszczony na poziomie, nie powinien mieć współrzędnych x, y, z, co nie ma sensu. Możesz to zawrzeć w specjalnym indeksie. Jeśli chcesz sprawdzić współrzędne obiektu na podstawie jego rzeczywistego odwołania, będziesz chciał powiązać obiekt z wykresem sceny (wykresy sceny służą do zwracania obiektów opartych na współrzędnych, ale powolne są zwracanie współrzędnych na podstawie obiektów) .
Po dodaniu obiektu do poziomu. Będzie wykonywać następujące czynności:
1) Utwórz strukturę lokalizacji:
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
};
Może to być również odniesienie do obiektu w silnikach fizyki innych firm. Lub może to być współrzędna przesunięcia z odniesieniem do innej lokalizacji (dla kamery śledzącej lub dołączonego obiektu lub przykładu). W przypadku polimorfizmu może to zależeć od tego, czy jest to obiekt statyczny czy dynamiczny. Zachowując tutaj odniesienie do indeksu przestrzennego, gdy współrzędne są aktualizowane, indeks przestrzenny może być również.
Jeśli martwisz się dynamicznym przydzielaniem pamięci, użyj puli pamięci.
2) Powiązanie / powiązanie między twoim obiektem, jego lokalizacją i wykresem sceny.
typedef std::pair<Object, Location> SpacialBinding.
3) Wiązanie jest dodawane do indeksu przestrzennego wewnątrz poziomu w odpowiednim punkcie.
Kiedy przygotowujesz się do renderowania.
1) Zdobądź kamerę (będzie to po prostu inny obiekt, z wyjątkiem tego, że jego lokalizacja będzie śledzić postać gracza, a Twój renderer będzie miał do niego specjalne odniesienie, w rzeczywistości to wszystko, czego naprawdę potrzebuje).
2) Pobierz funkcję SpacialBinding kamery.
3) Pobierz indeks przestrzenny z wiązania.
4) Zapytaj obiekty, które są (prawdopodobnie) widoczne dla kamery.
5A) Musisz przetwarzać informacje wizualne. Tekstury przesłane do GPU i tak dalej. Najlepiej byłoby to zrobić z wyprzedzeniem (na przykład na poziomie obciążenia), ale być może można to zrobić w czasie wykonywania (w otwartym świecie można ładować rzeczy, gdy zbliżasz się do porcji, ale nadal powinno to być zrobione z wyprzedzeniem).
5B) Opcjonalnie zbuduj buforowane drzewo renderowania, jeśli chcesz uporządkować głębokość / materiał lub śledzić pobliskie obiekty, które mogą być widoczne później. W przeciwnym razie możesz po prostu zapytać o indeks przestrzenny za każdym razem, gdy będzie on zależał od wymagań gry / wydajności.
Twój renderer prawdopodobnie będzie potrzebował obiektu RenderBinding, który połączy obiekt, współrzędne
class RenderBinding {
Object& object;
RenderInformation& renderInfo;
Location& location // This could just be a coordinates class.
}
Następnie podczas renderowania wystarczy uruchomić listę.
Użyłem powyższych referencji, ale mogą to być inteligentne wskaźniki, surowe wskaźniki, uchwyty obiektów i tak dalej.
EDYTOWAĆ:
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
}
}
Co do uczynienia siebie „świadomymi” siebie. To wykrywanie kolizji. Prawdopodobnie zostanie wdrożony w Oktree. Będziesz musiał podać zwrotny w swoim głównym obiekcie. Najlepiej radzić sobie z takim silnikiem fizyki, jak Bullet. W takim przypadku wystarczy zamienić Octree na PhysicsScene i Position na link do czegoś takiego jak CollisionMesh.getPosition ().