Pracuję nad systemem komponentów encji w C ++, który, mam nadzieję, będzie podążał za Artemisem (http://piemaster.net/2011/07/entity-component-artemis/), ponieważ komponenty to głównie torby danych, a to Systemy zawierające logikę. Mam nadzieję, że wykorzystam podejście skoncentrowane na danych i zbuduję fajne narzędzia do obsługi treści.
Jednak jeden garb, z którym się spotykam, to jak pobrać jakiś ciąg identyfikatora lub identyfikator GUID z pliku danych i użyć go do skonstruowania komponentu dla encji. Oczywiście mógłbym mieć tylko jedną dużą funkcję parsowania:
Component* ParseComponentType(const std::string &typeName)
{
if (typeName == "RenderComponent") {
return new RenderComponent();
}
else if (typeName == "TransformComponent") {
return new TransformComponent();
}
else {
return NULL:
}
}
Ale to naprawdę brzydkie. Zamierzam często dodawać i modyfikować komponenty i mam nadzieję, że zbuduję coś w rodzaju ScriptedComponentComponent, tak aby można było zaimplementować komponent i system w Lua do celów prototypowania. Chciałbym móc napisać klasę dziedziczącą po pewnej BaseComponent
klasie, być może wrzucić kilka makr, aby wszystko działało, a następnie mieć klasę dostępną do tworzenia instancji w czasie wykonywania.
W języku C # i Javie byłoby to dość proste, ponieważ masz ładne interfejsy API refleksji do wyszukiwania klas i konstruktorów. Ale robię to w C ++, ponieważ chcę zwiększyć swoją biegłość w tym języku.
Jak to się dzieje w C ++? Czytałem o włączaniu RTTI, ale wygląda na to, że większość ludzi jest tego nieufna, szczególnie w sytuacji, gdy potrzebuję jej tylko dla podzbioru typów obiektów. Jeśli potrzebuję niestandardowego systemu RTTI, to gdzie mogę przejść, aby rozpocząć naukę pisania?
źródło
Odpowiedzi:
Komentarz:
Implementacja Artemis jest interesująca. Wymyśliłem podobne rozwiązanie, tyle że nazwałem swoje komponenty „Atrybutami” i „Zachowaniami”. Takie podejście do rozdzielania typów komponentów działało dla mnie bardzo dobrze.
Jeśli chodzi o rozwiązanie:
kod jest łatwy w użyciu, ale implementacja może być trudna do wykonania, jeśli nie masz doświadczenia z C ++. Więc...
Pożądany interfejs
To, co zrobiłem, to mieć centralne repozytorium wszystkich komponentów. Każdy typ komponentu jest mapowany na określony ciąg znaków (który reprezentuje nazwę komponentu). Oto jak korzystasz z systemu:
Implementacja
Wdrożenie nie jest takie złe, ale wciąż dość skomplikowane; wymaga pewnej wiedzy na temat szablonów i wskaźników funkcji.
Uwaga: Joe Wreschnig poczynił kilka dobrych uwag w komentarzach, głównie na temat tego, jak moja poprzednia implementacja przyjęła zbyt wiele założeń dotyczących tego, jak dobry jest kompilator w optymalizacji kodu; problem nie był szkodliwy, imo, ale również mnie zaskoczył. Zauważyłem również, że poprzednie
COMPONENT_REGISTER
makro nie działało z szablonami.Zmieniłem kod i teraz wszystkie te problemy powinny zostać naprawione. Makro współpracuje z szablonami i rozwiązano problemy, które podniósł Joe: teraz kompilatorom znacznie łatwiej jest zoptymalizować niepotrzebny kod.
składnik / składnik. h
składnik / szczegół. h
component / component.cpp
Rozszerzanie z Luą
Powinienem zauważyć, że przy odrobinie pracy (nie jest to bardzo trudne), można to wykorzystać do płynnej pracy ze składnikami zdefiniowanymi w C ++ lub Lua, bez konieczności myślenia o tym.
źródło
shared_ptr
, ale twoja rada jest nadal dobra.Wydaje się, że to, czego chcesz, to fabryka.
http://en.wikipedia.org/wiki/Factory_method_pattern
To, co możesz zrobić, to zarejestrować w fabryce różne komponenty pod jaką nazwą one odpowiadają, a następnie masz mapę identyfikatora łańcucha do podpisu metody konstruktora w celu wygenerowania komponentów.
źródło
Component
klas, dzwoniącComponentSubclass::RegisterWithFactory()
, prawda? Czy istnieje sposób, aby to ustawić, zrób to bardziej dynamicznie i automagicznie? Przepływ pracy, którego szukam, to 1. Napisz klasę, patrząc tylko na odpowiedni nagłówek i plik cpp 2. Ponownie skompiluj grę 3. Edytor poziomu początkowego i nowa klasa komponentów jest dostępna do użycia.Przez jakiś czas pracowałem z projektem Paula Manty z wybranej odpowiedzi i ostatecznie doszedłem do bardziej ogólnej i zwięzłej implementacji fabryki poniżej, którą chętnie podzielę się z każdym, kto przyjdzie na to pytanie w przyszłości. W tym przykładzie każdy obiekt fabryki pochodzi od
Object
klasy podstawowej:Statyczna klasa fabryczna jest następująca:
Makro do rejestrowania podtypu
Object
jest następujące:Teraz użycie jest następujące:
Pojemność wielu identyfikatorów ciągów dla podtypu była przydatna w mojej aplikacji, ale ograniczenie do jednego identyfikatora dla każdego podtypu byłoby dość proste.
Mam nadzieję, że było to przydatne!
źródło
Opierając się na odpowiedzi @TimStraubinger , zbudowałem klasę fabryczną przy użyciu standardów C ++ 14, które mogą przechowywać elementy pochodne z dowolną liczbą argumentów . Mój przykład, w przeciwieństwie do Tima, bierze tylko jedną nazwę / klawisz na funkcję. Podobnie jak Tim, każda przechowywana klasa pochodzi z klasy Base , a moja nazywana jest Base .
Base.h
EX_Factory.h
main.cpp
Wydajność
Mam nadzieję, że pomoże to osobom potrzebującym użyć projektu fabryki, który nie wymaga konstruktora tożsamości do pracy. To było fajne projektowanie, więc mam nadzieję, że pomoże ludziom potrzebującym większej elastyczności w projektach fabrycznych .
źródło