Rozważ grę karcianą, taką jak Hearthstone .
Istnieją setki kart, które robią wiele różnych rzeczy, z których niektóre są unikalne nawet dla jednej karty! Na przykład, istnieje karta (zwana Nozdormu), która zmniejsza turę gracza do zaledwie 15 sekund!
Kiedy masz tak wiele potencjalnych efektów, jak możesz uniknąć magicznych liczb i jednorazowych kontroli w całym kodzie? Jak można uniknąć metody „Check_Nozdormu_In_Play” w klasie PlayerTurnTime? Jak można zorganizować kod w taki sposób, aby dodając jeszcze więcej efektów, nie trzeba było refaktoryzować podstawowych systemów, aby obsługiwały rzeczy, których nigdy wcześniej nie obsługiwały?
architecture
software-engineering
scripting
Sable Dreamer
źródło
źródło
Odpowiedzi:
Czy sprawdziłeś systemy komponentów encji i strategie przesyłania komunikatów o zdarzeniach?
Efekty statusu powinny być jakimś składnikiem, który może zastosować swoje trwałe efekty w metodzie OnCreate (), wygasić ich efekty w OnRemoved () i subskrybować komunikaty o zdarzeniach gry, aby zastosować efekty, które występują jako reakcja na coś, co się dzieje.
Jeśli efekt jest trwale warunkowy (trwa przez X zwojów, ale ma zastosowanie tylko w pewnych okolicznościach), może być konieczne sprawdzenie tych warunków na różnych etapach.
Następnie upewnij się, że twoja gra również nie ma domyślnych magicznych liczb. Upewnij się, że wszystko, co można zmienić, jest zmienną opartą na danych, a nie zakodowanymi domyślnymi wartościami domyślnymi ze zmiennymi używanymi dla wyjątków.
W ten sposób nigdy nie zakładasz, jaka będzie długość skrętu. Jest to zawsze stale sprawdzana zmienna, którą można zmienić dowolnym efektem i być może cofnąć później przez efekt po wygaśnięciu. Nigdy nie sprawdzasz wyjątków przed ustawieniem domyślnej liczby magicznej.
źródło
RobStone jest na dobrej drodze, ale chciałem to rozwinąć, ponieważ właśnie to zrobiłem, kiedy napisałem Dungeon Ho !, Roguelike, który miał bardzo złożony system efektów dla broni i zaklęć.
Każda karta powinna mieć dołączony zestaw efektów, zdefiniowany w taki sposób, aby wskazywał, jaki jest efekt, do czego jest skierowany, jak i na jak długo. Na przykład efekt „obrażeń przeciwnika” może wyglądać mniej więcej tak;
Następnie, gdy zadziała efekt, przygotuj ogólną procedurę obsługi efektu. Jak idiota użyłem ogromnej instrukcji case / switch:
Ale o wiele lepszy i bardziej modułowy sposób to zrobić poprzez polimorfizm. Utwórz klasę Effect, która otacza wszystkie te dane, utwórz podklasę dla każdego rodzaju efektu, a następnie poproś tę klasę o zastąpienie metody onExecute () specyficznej dla tej klasy.
Mielibyśmy więc podstawową klasę Effect, a następnie klasę DamageEffect z metodą onExecute (), więc w naszym kodzie przetwarzania po prostu poszliśmy;
Sposobem radzenia sobie ze świadomością tego, co jest w grze, jest utworzenie Vector / Array / link list / etc. aktywnych efektów (typu Efekt, klasa podstawowa) dołączonych do dowolnego obiektu (w tym pola gry / „gry”), więc zamiast konieczności sprawdzania, czy dany efekt jest w grze, wystarczy przejrzeć wszystkie efekty przypisane do obiekty i pozwól im się wykonać. Jeśli efekt nie jest dołączony do obiektu, nie jest odtwarzany.
źródło
Przedstawię garść sugestii. Niektóre z nich są ze sobą sprzeczne. Ale może niektóre są przydatne.
Rozważ listy kontra flagi
Możesz iterować po całym świecie i sprawdzać flagę na każdym elemencie, aby zdecydować, czy wykonać flagę. Lub możesz zachować listę tylko tych przedmiotów, które powinny zrobić flagę.
Rozważ listy i wyliczenia
Możesz dodawać pola boolowskie do swojej klasy przedmiotów, isAThis i isAThat. Lub możesz mieć listę ciągów znaków lub elementów wyliczających, takich jak {„isAThis”, „isAThat”} lub {IS_A_THIS, IS_A_THAT}. W ten sposób możesz dodawać nowe w wyliczeniach (lub stałych ciągów) bez dodawania pól. Nie dlatego, że dodawanie pól jest naprawdę złe…
Rozważ wskaźniki funkcji
Zamiast listy flag lub wyliczeń, może mieć listę akcji do wykonania dla tego elementu w różnych kontekstach. (Entity-ish…)
Rozważ obiekty
Niektóre osoby preferują podejścia oparte na danych, skryptach lub elementach składowych. Ale warto też wziąć pod uwagę staromodne hierarchie obiektów. Klasa podstawowa musi zaakceptować akcje, takie jak „zagraj tę kartę w fazie F” lub cokolwiek innego. Następnie każdy rodzaj karty może zastąpić i odpowiednio zareagować. Prawdopodobnie istnieje także obiekt gracza i obiekt gry, więc gra może wykonywać takie czynności, jak (player-> isAllowedToPlay ()) {wykonaj grę…}.
Rozważ możliwość debugowania
Kiedyś dobrą cechą stosu flag jest to, że możesz sprawdzić i wydrukować stan każdego przedmiotu w ten sam sposób. Jeśli stan jest reprezentowany przez różne typy lub pakiety komponentów, wskaźniki funkcji lub znajdowanie się na różnych listach, może nie wystarczyć samo spojrzenie na pola elementu. To wszystko kompromisy.
Ostatecznie refaktoryzacja: rozważ testy jednostkowe
Bez względu na to, jak bardzo uogólniasz swoją architekturę, będziesz w stanie wyobrazić sobie rzeczy, których nie obejmuje. Następnie będziesz musiał dokonać refaktoryzacji. Może trochę, może dużo.
Sposobem na zwiększenie bezpieczeństwa jest szereg testów jednostkowych. W ten sposób możesz być pewny, że nawet jeśli przestawiłeś rzeczy pod spodem (może o wiele! Każdy test jednostkowy wygląda ogólnie tak:
Jak widać, utrzymanie stabilnych połączeń API najwyższego poziomu w grze (lub odtwarzaczu, karcie i c) jest kluczem do strategii testów jednostkowych.
źródło
Zamiast myśleć o każdej karcie osobno, zacznij myśleć w kategoriach kategorii efektów, a karty zawierają jedną lub więcej z tych kategorii. Na przykład, aby obliczyć czas w rundzie, możesz przejrzeć wszystkie karty w grze i sprawdzić kategorię „manipuluj czasem trwania tury” każdej karty zawierającej tę kategorię. Każda karta następnie zwiększa lub zastępuje czas trwania tury, zgodnie z ustalonymi regułami.
Zasadniczo jest to system mini-komponentowy, w którym każdy obiekt „karciany” jest po prostu pojemnikiem na kilka elementów efektów.
źródło