Będę mówić z trochę doświadczenia, od sztywnego projektu OO do projektu Entity-Component-System (ECS).
Jakiś czas temu byłem taki jak ty , miałem wiele różnych rzeczy o podobnych właściwościach i budowałem różne obiekty i próbowałem użyć dziedziczenia, aby je rozwiązać. Bardzo mądra osoba powiedziała mi , że tego nie rób, a zamiast tego użyj Entity-Component-System.
Teraz ECS to wielka koncepcja i ciężko jest ją dobrze zrozumieć. Jest w to dużo pracy, właściwe budowanie jednostek, komponentów i systemów. Jednak zanim to zrobimy, musimy zdefiniować warunki.
- Podmiot : to jest rzecz , gracz, zwierzę, NPC, cokolwiek . To coś, co wymaga dołączonych do niego komponentów.
- Składnik : w twoim przypadku jest to atrybut lub właściwość , taka jak „Imię” lub „Wiek” lub „Rodzice”.
- System : taka jest logika komponentu lub zachowania . Zazwyczaj budujesz jeden system na komponent, ale nie zawsze jest to możliwe. Ponadto czasami systemy muszą wpływać na inne systemy.
Oto, gdzie bym poszedł z tym:
Przede wszystkim utwórz postać ID
dla swoich postaci. int
, Guid
Cokolwiek chcesz. To jest „jednostka”.
Po drugie, zacznij myśleć o różnych zachowaniach. Takie rzeczy jak „Drzewo genealogiczne” - takie zachowanie. Zamiast modelować to jako atrybuty encji, zbuduj system, który przechowuje wszystkie te informacje . System może następnie zdecydować, co z tym zrobić.
Podobnie chcemy zbudować system dla „Czy postać żyje czy nie żyje?” Jest to jeden z najważniejszych systemów w twoim projekcie, ponieważ wpływa na wszystkie pozostałe. Niektóre systemy mogą usuwać „martwe” znaki (takie jak system „sprite”), inne mogą wewnętrznie zmieniać układ, aby lepiej obsługiwać nowy status.
Na przykład zbudujesz system „Sprite”, „Rysowanie” lub „Rendering”. Ten system będzie odpowiedzialny za określenie, z jakiego duszka postać ma być wyświetlana i jak ją wyświetlić. Następnie, gdy postać umiera, usuń ją.
Dodatkowo system „AI”, który może powiedzieć postaci, co ma robić, gdzie iść itp. Powinno to oddziaływać z wieloma innymi systemami i podejmować na ich podstawie decyzje. Znów martwe postacie prawdopodobnie można usunąć z tego systemu, ponieważ tak naprawdę już nic nie robią.
Twój system „Imię” i „Drzewo genealogiczne” prawdopodobnie powinny zachować postać (żywą lub martwą) w pamięci. Ten system musi przywołać te informacje, niezależnie od stanu postaci. (Jim jest nadal Jimem, nawet po tym, jak go pochowaliśmy).
Daje to również korzyść ze zmiany, gdy system reaguje wydajniej: system ma swój własny zegar. Niektóre systemy muszą strzelać szybko, niektóre nie. W tym miejscu zaczynamy wchodzić w to, co sprawia, że gra działa sprawnie. Nie musimy ponownie obliczać pogody co milisekundę, prawdopodobnie możemy to robić co około 5 godzin.
Daje to również bardziej kreatywną dźwignię: możesz zbudować system „Pathfinder”, który może obsłużyć obliczenia ścieżki od A do B, i może aktualizować w razie potrzeby, pozwalając systemowi Ruchu powiedzieć „gdzie muszę idź następny?" Możemy teraz w pełni rozdzielić te obawy i skuteczniej je uzasadniać. Ruch nie musi znaleźć ścieżki, musi cię tylko tam zabrać.
Będziesz chciał odsłonić niektóre części systemu na zewnątrz. W twoim Pathfinder
systemie prawdopodobnie będziesz chciał Vector2 NextPosition(int entity)
. W ten sposób możesz przechowywać te elementy w ściśle kontrolowanych tablicach lub listach. Możesz użyć mniejszych struct
typów, które pomogą ci utrzymać komponenty w mniejszych, ciągłych blokach pamięci, co może znacznie przyspieszyć aktualizacje systemu . (Zwłaszcza jeśli zewnętrzne wpływy na system są minimalne, teraz trzeba tylko dbać o jego stan wewnętrzny, np Name
.)
Ale i nie mogę tego wystarczająco podkreślić, teraz an Entity
jest po prostu ID
kafelkami, obiektami itp. Jeśli istota nie należy do systemu, system nie będzie tego śledził. Oznacza to, że możemy tworzyć nasze obiekty „Drzewo”, przechowywać je w systemach Sprite
i Movement
(drzewa się nie poruszają, ale mają one komponent „Pozycja”) i trzymać je z dala od innych systemów. Nie potrzebujemy już specjalnej listy dla drzew, ponieważ renderowanie drzewa nie różni się niczym od postaci oprócz papierowego obijania. (Które Sprite
system może kontrolować, lub Paperdoll
system może kontrolować.) Teraz NextPosition
możemy nieco przepisać: Vector2? NextPosition(int entity)
i może zwrócić null
pozycję dla podmiotów, na których mu nie zależy. Stosujemy to również do naszych NameSystem.GetName(int entity)
, zwraca null
za drzewa i skały.
Zakończę to, ale tutaj chodzi o to, aby dać ci trochę informacji na temat ECS i jak naprawdę możesz go wykorzystać, aby uzyskać lepszy projekt swojej gry. Możesz zwiększyć wydajność, oddzielić niezwiązane elementy i zachować porządek. (To również dobrze łączy się z funkcjonalnymi językami / konfiguracjami, takimi jak F # i LINQ, które gorąco polecam sprawdzenie F #, jeśli jeszcze tego nie zrobiłeś, bardzo dobrze łączy się z C #, gdy używasz ich w połączeniu.)