Ta rada nie jest tak naprawdę specyficzna dla renderowania, ale powinna pomóc w opracowaniu systemu, który oddziela rzeczy w dużej mierze od siebie. Najpierw spróbuj oddzielić dane „GameObject” od informacji o pozycji.
Warto zauważyć, że proste informacje o położeniu XYZ mogą nie być takie proste. Jeśli używasz silnika fizyki, dane pozycji mogą być przechowywane w silniku innej firmy. Trzeba będzie albo zsynchronizować między nimi (co wymagałoby dużo bezcelowego kopiowania pamięci) lub zapytać o informacje bezpośrednio z silnika. Ale nie wszystkie obiekty wymagają fizyki, niektóre zostaną ustalone na miejscu, więc prosty zestaw pływaków działa tam dobrze. Niektóre mogą być nawet przymocowane do innych obiektów, więc ich pozycja jest w rzeczywistości przesunięciem innej pozycji. W konfiguracji zaawansowanej pozycja może być przechowywana tylko na GPU, jedynym potrzebnym czasem po stronie komputera jest skrypty, przechowywanie i replikacja sieci. Prawdopodobnie będziesz mieć kilka możliwych wyborów dla swoich danych pozycyjnych. Tutaj warto zastosować dziedziczenie.
Zamiast obiektu posiadającego swoją pozycję, obiekt ten powinien być własnością struktury danych indeksujących. Na przykład „Poziom” może mieć scenę Octree, a może scenę z silnikiem fizyki. Kiedy chcesz wyrenderować (lub skonfigurować scenę renderowania), zapytaj swoją specjalną strukturę o obiekty widoczne dla kamery.
Pomaga to również zapewnić dobre zarządzanie pamięcią. W ten sposób obiekt, który tak naprawdę nie znajduje się w obszarze, nie ma nawet pozycji, która ma sens, zamiast zwracania 0,0 coordów lub coordów, które miał, kiedy był ostatni w obszarze.
Jeśli nie będziesz już utrzymywał współrzędnych w obiekcie, zamiast object.getX () uzyskasz poziom.getX (obiekt). Problem z wyszukiwaniem obiektu na poziomie będzie prawdopodobnie powolną operacją, ponieważ będzie musiał przejrzeć wszystkie obiekty i dopasować do tego, którego dotyczy zapytanie.
Aby tego uniknąć, prawdopodobnie stworzyłbym specjalną klasę „link”. Taki, który łączy poziom z obiektem. Nazywam to „Lokalizacja”. Zawierałby współrzędne xyz, a także uchwyt do poziomu i uchwyt do obiektu. Ta klasa łączy byłaby przechowywana w strukturze / poziomie przestrzennym, a obiekt miałby do niej słabe odniesienie (jeśli poziom / lokalizacja zostanie zniszczony, odwołanie do obiektów musi zostać zaktualizowane do wartości zerowej. Może być również warte posiadania klasy Lokalizacja „posiadać” obiekt, w ten sposób, jeśli poziom zostanie usunięty, podobnie jak specjalna struktura indeksu, lokalizacje, które on zawiera i jego obiekty.
typedef std::tuple<Level, Object, PositionXYZ> Location;
Teraz informacje o pozycji są przechowywane tylko w jednym miejscu. Nie powielane między obiektem, strukturą indeksowania Spacial, rendererem i tak dalej.
Przestrzenne struktury danych, takie jak Octrees, często nie muszą nawet mieć współrzędnych przechowywanych obiektów. Pozycja jest zapisywana we względnej lokalizacji węzłów w samej strukturze (można by ją traktować jako rodzaj kompresji stratnej, poświęcającej dokładność dla krótkich czasów wyszukiwania). W przypadku obiektu lokalizacji w Octree po zakończeniu zapytania znajdują się w nim rzeczywiste współrzędne.
Lub jeśli używasz silnika fizyki do zarządzania lokalizacjami twoich obiektów lub ich mieszanką, klasa Location powinna sobie z tym poradzić w sposób transparentny, zachowując cały kod w jednym miejscu.
Kolejną zaletą jest teraz pozycja, a odniesienie do poziomu jest przechowywane w tej samej lokalizacji. Możesz zaimplementować object.TeleportTo (other_object) i pozwolić mu działać na różnych poziomach. Podobnie wyszukiwanie ścieżki AI może podążać za czymś w innym obszarze.
W odniesieniu do renderowania. Twój rendering może mieć podobne powiązanie z lokalizacją. Tyle że miałby tam specyficzne renderowanie. Prawdopodobnie nie potrzebujesz „Object” ani „Level” do przechowywania w tej strukturze. Obiekt może być przydatny, jeśli próbujesz zrobić coś takiego jak wybieranie kolorów lub renderowanie nad nim pływającego paska itp., Ale w przeciwnym razie renderer dba tylko o siatkę i tym podobne. RenderableStuff byłby siatką, mógłby również zawierać ramki ograniczające i tak dalej.
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);
}
Być może nie będziesz musiał tego robić w każdej klatce, możesz upewnić się, że wybierasz większy region niż aktualnie pokazuje kamera. Buforuj go, śledź ruchy obiektów, aby sprawdzić, czy pole ograniczające znajduje się w zasięgu, śledź ruch kamery i tak dalej. Ale nie zaczynaj grzebać z tego rodzaju rzeczami, dopóki nie przetestujesz tego.
Sam silnik fizyki może mieć podobną abstrakcję, ponieważ nie potrzebuje również danych obiektu, tylko siatkę kolizji i właściwości fizyki.
Wszystkie podstawowe dane obiektu zawierałyby nazwę siatki używanej przez obiekt. Silnik gry może następnie załadować go w dowolnym formacie bez obciążania klasy obiektów mnóstwem rzeczy specyficznych do renderowania (które mogą być specyficzne dla interfejsu API renderowania, tj. DirectX vs OpenGL).
Utrzymuje także oddzielne elementy. Ułatwia to takie czynności, jak wymiana silnika fizyki, ponieważ te rzeczy są głównie samowystarczalne w jednym miejscu. Ułatwia to także nieprzytomność. Możesz testować takie rzeczy, jak zapytania fizyki, bez konieczności konfigurowania fałszywych obiektów, ponieważ wszystko, czego potrzebujesz, to klasa Location. Możesz także łatwiej zoptymalizować rzeczy. Sprawia, że bardziej oczywiste są zapytania, które należy wykonać na jakich klasach i pojedynczych lokalizacjach, aby je zoptymalizować (na przykład na powyższym poziomie .getVisibleObject to miejsce, w którym można buforować rzeczy, jeśli kamera nie porusza się zbyt wiele).
m_renderable
. W ten sposób możesz lepiej oddzielić swoją logikę. Nie wymuszaj renderowania „interfejsu” na obiektach ogólnych, które również mają fizykę, ai i tak dalej. Następnie możesz zarządzać renderowanymi osobno. Potrzebujesz warstwy abstrakcji nad wywołaniami funkcji OpenGL, aby jeszcze bardziej oddzielić rzeczy. Nie spodziewaj się więc, że dobry silnik będzie zawierał wywołania GL API w swoich różnych możliwych do renderowania implementacjach. To wszystko w mikro-pigułce.