Tworzę grę, która wykorzystuje obiekty gier oparte na komponentach, i trudno mi wdrożyć sposób komunikacji każdego komponentu z jego obiektem gry. Zamiast wyjaśniać wszystko naraz, wyjaśnię każdą część odpowiedniego przykładowego kodu:
class GameObjectManager {
public:
//Updates all the game objects
void update(Time dt);
//Sends a message to all game objects
void sendMessage(Message m);
private:
//Vector of all the game objects
std::vector<GameObject> gameObjects;
//vectors of the different types of components
std::vector<InputComponent> input;
std::vector<PhysicsComponent> ai;
...
std::vector<RenderComponent> render;
}
GameObjectManager
Posiada wszystkie obiekty gry i ich komponentów. Odpowiada również za aktualizację obiektów gry. Odbywa się to poprzez aktualizację wektorów składowych w określonej kolejności. Używam wektorów zamiast tablic, więc praktycznie nie ma ograniczenia co do liczby obiektów gry, które mogą istnieć jednocześnie.
class GameObject {
public:
//Sends a message to the components in this game object
void sendMessage(Message m);
private:
//id to keep track of components in the manager
const int id;
//Pointers to components in the game object manager
std::vector<Component*> components;
}
GameObject
Klasa wie, jakie są jej składniki i może wysyłać do nich wiadomości.
class Component {
public:
//Receives messages and acts accordingly
virtual void handleMessage(Message m) = 0;
virtual void update(Time dt) = 0;
protected:
//Calls GameObject's sendMessage
void sendMessageToObject(Message m);
//Calls GameObjectManager's sendMessage
void sendMessageToWorld(Message m);
}
Component
Klasa jest czysto wirtualne, dzięki czemu zajęcia dla różnych typów elementów można wdrożyć jak obsługiwać wiadomości i aktualizacji. Jest również w stanie wysyłać wiadomości.
Teraz pojawia się problem, w jaki sposób komponenty mogą wywoływać sendMessage
funkcje w GameObject
i GameObjectManager
. Wymyśliłem dwa możliwe rozwiązania:
- Daj
Component
wskaźnik do tegoGameObject
.
Ponieważ jednak obiekty gry znajdują się w wektorze, wskaźniki mogą szybko zostać unieważnione (to samo można powiedzieć o wektorze w GameObject
, ale mam nadzieję, że rozwiązanie tego problemu może również rozwiązać ten problem). Mógłbym umieścić obiekty gry w tablicy, ale wtedy musiałbym podać dowolną liczbę dla wielkości, która z łatwością mogłaby być niepotrzebnie wysoka i marnować pamięć.
- Daj
Component
wskaźnik doGameObjectManager
.
Nie chcę jednak, aby komponenty mogły wywoływać funkcję aktualizacji menedżera. Jestem jedyną osobą pracującą nad tym projektem, ale nie chcę mieć zwyczaju pisania potencjalnie niebezpiecznego kodu.
Jak mogę rozwiązać ten problem, jednocześnie zachowując kod i pamięć podręczną w bezpieczny sposób?
Bycie „przyjaznym dla pamięci podręcznej” jest zajęciem dużych gier . Wydaje mi się to przedwczesną optymalizacją.
Jednym ze sposobów rozwiązania tego problemu bez „buforowania pamięci podręcznej” byłoby utworzenie obiektu na stercie zamiast na stosie: użyj
new
i (inteligentnych) wskaźników dla swoich obiektów. W ten sposób będziesz mógł odwoływać się do swoich obiektów, a ich odniesienie nie zostanie unieważnione.Aby uzyskać rozwiązanie bardziej przyjazne dla pamięci podręcznej, możesz samodzielnie zarządzać cofaniem / alokacją obiektów i używać uchwytów do tych obiektów.
Zasadniczo, podczas inicjalizacji programu, obiekt rezerwuje kawałek pamięci na stercie (nazwijmy go MemMan), a następnie, gdy chcesz utworzyć komponent, mówisz MemMan, że potrzebujesz komponentu o rozmiarze X, to „ Zarezerwuję go dla ciebie, utwórz uchwyt i zachowaj wewnętrznie, gdzie w jego alokacji znajduje się obiekt tego uchwytu. Zwróci uchwyt i jedyną rzeczą, którą zatrzymasz na temat obiektu, nigdy wskaźnik do jego położenia w pamięci.
Gdy potrzebujesz komponentu, poprosisz MemMan o dostęp do tego obiektu, co chętnie zrobi. Ale nie zachowuj odniesienia do tego, ponieważ ...
Jednym z zadań MemMan jest utrzymywanie obiektów blisko siebie w pamięci. Raz na kilka klatek gry możesz nakazać MemManowi zmianę kolejności obiektów w pamięci (lub może to zrobić automatycznie podczas tworzenia / usuwania obiektów). Zaktualizuje swoją mapę lokalizacji uchwyt do pamięci. Twoje uchwyty zawsze będą ważne, ale jeśli zachowałeś odniesienie do przestrzeni pamięci ( wskaźnik lub odnośnik ), znajdziesz tylko rozpacz i pustkę.
Podręczniki mówią, że ten sposób zarządzania pamięcią ma co najmniej 2 zalety:
Należy pamiętać, że sposób korzystania z MemMan i sposób wewnętrznej organizacji pamięci zależy od sposobu korzystania z komponentów. Jeśli będziesz iterował przez nie w oparciu o ich typ, będziesz chciał zachować komponenty według typu, jeśli iterujesz przez nie w oparciu o ich obiekt gry, musisz znaleźć sposób, aby upewnić się, że są blisko inny oparty na tym itp.
źródło