Zwykle odbywa się to za pomocą wiadomości. Możesz znaleźć wiele szczegółów w innych pytaniach na tej stronie, takich jak tutaj lub tam .
Aby odpowiedzieć na twój konkretny przykład, sposobem jest zdefiniowanie małej Message
klasy, którą Twoje obiekty mogą przetwarzać, np .:
struct Message
{
Message(const Objt& sender, const std::string& msg)
: m_sender(&sender)
, m_msg(msg) {}
const Obj* m_sender;
std::string m_msg;
};
void Obj::Process(const Message& msg)
{
for (int i=0; i<m_components.size(); ++i)
{
// let components do some stuff with msg
m_components[i].Process(msg);
}
}
W ten sposób nie „zanieczyszczasz” Obj
swojego interfejsu klasami metodami związanymi z komponentami. Niektóre komponenty mogą przetworzyć wiadomość, niektóre mogą ją po prostu zignorować.
Możesz zacząć od wywołania tej metody bezpośrednio z innego obiektu:
Message msg(obj1, "EmitForce(5.0,0.0,0.0)");
obj2.ProcessMessage(msg);
W takim przypadku obj2
„s” Physics
wybierze wiadomość i zrobi wszystko, czego potrzebuje. Po zakończeniu będzie albo:
- Wyślij do siebie komunikat „SetPosition”, który
Position
wybierze komponent;
- Lub bezpośrednio uzyskaj dostęp do
Position
komponentu w celu modyfikacji (całkiem źle w przypadku projektu opartego wyłącznie na komponentach, ponieważ nie możesz założyć, że każdy obiekt ma Position
komponent, ale Position
komponent może być wymagany Physics
).
Zasadniczo dobrym pomysłem jest opóźnienie faktycznego przetworzenia wiadomości na aktualizację następnego komponentu. Natychmiastowe przetworzenie może oznaczać wysyłanie wiadomości do innych elementów innych obiektów, więc wysłanie tylko jednej wiadomości może szybko oznaczać nierozerwalny stos spaghetti.
Prawdopodobnie będziesz musiał później wybrać bardziej zaawansowany system: asynchroniczne kolejki komunikatów, wysyłanie wiadomości do grupy obiektów, rejestrowanie / wyrejestrowywanie poszczególnych komponentów z wiadomości itp.
Message
Klasa może być ogólna pojemnik z prostego łańcucha, jak pokazano powyżej, ale przetwarzania ciągi przy starcie nie jest naprawdę skuteczny. Możesz wybrać kontener wartości ogólnych: ciągi, liczby całkowite, liczby zmiennoprzecinkowe ... Z nazwą lub jeszcze lepszą, z identyfikatorem, aby rozróżnić różne typy wiadomości. Możesz też uzyskać klasę podstawową, która będzie pasować do konkretnych potrzeb. W twoim przypadku możesz sobie wyobrazić, EmitForceMessage
że wywodzi się Message
i dodaje pożądany wektor siły - ale uważaj, jeśli to zrobisz, koszt czasu wykonania RTTI .
dynamic_cast
może stać się wąskim gardłem, ale na razie nie będę się tym martwić. Nadal możesz to zoptymalizować później, jeśli stanie się to problemem. Identyfikatory klas oparte na CRC działają jak urok.To, co zrobiłem, aby rozwiązać problem podobny do tego, co pokazujesz, to dodanie pewnych procedur obsługi komponentów i dodanie pewnego rodzaju systemu rozwiązywania zdarzeń.
Tak więc w przypadku obiektu „Fizyka” po zainicjowaniu dodawałby się do centralnego menedżera obiektów fizyki. W pętli gry menedżerowie tego rodzaju mają swoje własne etapy aktualizacji, więc gdy ten program PhysicsManager jest aktualizowany, oblicza wszystkie interakcje fizyki i dodaje je do kolejki zdarzeń.
Po wygenerowaniu wszystkich zdarzeń możesz rozwiązać kolejkę zdarzeń, po prostu sprawdzając, co się stało i wykonując odpowiednie działania, w twoim przypadku powinno być zdarzenie, w którym obiekt A i B w jakiś sposób wchodzą w interakcję, więc wywołujesz metodę emitForceOn.
Zalety tej metody:
Cons:
Mam nadzieję, że to pomoże.
PS: Jeśli ktoś ma czystszy / lepszy sposób na rozwiązanie tego, naprawdę chciałbym to usłyszeć.
źródło
Kilka rzeczy do zapamiętania na temat tego projektu:
źródło