Tworzę odgórną grę 2D i chcę mieć wiele różnych rodzajów ataków. Chciałbym, aby ataki były bardzo elastyczne i łączone tak, jak działa The Binding of Isaac. Oto lista wszystkich przedmiotów kolekcjonerskich w grze . Aby znaleźć dobry przykład, spójrzmy na przedmiot Spoon Bender .
Spoon Bender daje Izaakowi możliwość strzelania do łez naprowadzających.
Jeśli spojrzysz na sekcję „synergie”, zobaczysz, że można ją łączyć z innymi przedmiotami kolekcjonerskimi, aby uzyskać ciekawe, ale intuicyjne efekty. Na przykład, jeśli łączy się z Wewnętrznym okiem , „pozwoli Izaakowi na oddanie wielu strzałów naprowadzających jednocześnie”. Ma to sens, ponieważ Wewnętrzne oko
Daje Izaakowi potrójny strzał
Jaka jest dobra architektura do projektowania takich rzeczy? Oto rozwiązanie brutalnej siły:
if not spoon bender and not the inner eye then ...
if spoon bender and not the inner eye then ...
if not spoon bender and the inner eye then ...
if spoon bender and the inner eye then ...
Ale to wymknie się spod kontroli bardzo szybko. Jaki jest lepszy sposób zaprojektowania takiego systemu?
źródło
Odpowiedzi:
Zupełnie nie musisz ręcznie kombinować kodów. Zamiast tego możesz skupić się na właściwościach, które daje każdy element. Na przykład, pozycja A ustawia
Projectile=Fireball,Targetting=Homing
. Pozycja B zestawyFireMode=ArcShot,Count=3
.ArcShot
Logika jest odpowiedzialny za wysyłanieCount
liczbęProjectile
elementów w łuk.Te dwa elementy można łączyć z dowolnymi innymi elementami, które dowolnie modyfikują te (lub inne) właściwości. Jeśli dodasz nowy typ pocisku, będzie on automatycznie działał
ArcShot
, a jeśli dodasz nowy tryb strzelania, będzie on automatycznie działał zFireball
pociskami. PodobnieTargetting
jest właściwością, która ustawia kontroler dla pocisków podczasFireMode
tworzenia pocisków, dzięki czemu można je łatwo i trywialnie łączyć w dowolną kombinację, jeśli ma to sens.Możesz także ustawić zależności właściwości i tym podobne. Na przykład
ArcShot
wymaga posiadania dostawcyProjectile
(który może być tylko domyślny). Możesz ustawić priorytety, aby mieć dwa aktywne elementy, z których oba dostarczająProjectile
kod, wiedział, którego użyć. Możesz też podać interfejs użytkownika, aby użytkownik mógł wybrać typ pocisku, którego chcesz użyć, lub po prostu zażądać od gracza wyposażyć przedmioty o wysokim priorytecie, których nie chce, lub użyć najnowszego przedmiotu itp. Możesz dodatkowo dopuścić system niezgodności , np. takie, że dwa przedmioty, które tylko oba modyfikują,Projectile
nie mogą być jednocześnie wyposażone.Zasadniczo, jeśli to możliwe, preferuj podejście oparte na danych (lub deklaratywne ) nad podejściem proceduralnym (duży bałagan jeśli-inaczej), jeśli chodzi o przedmioty i takie w twojej grze. Ogólna logika wyższego poziomu, którą można konfigurować za pomocą prostych danych, jest znacznie lepsza niż zakodowane na stałe listy reguł o specjalnej obudowie.
źródło
Jeśli używasz języka OOP, brzmi to jak dobre miejsce do zastosowania Wzorca Dekoratora . Jeśli chcesz zmodyfikować sposób ataku, udekoruj go odpowiednim rozszerzeniem.
Surowy c ++ Przykład:
Ta metoda byłaby najlepsza, jeśli masz bardzo dużą liczbę ataków i potrzebujesz, aby wszystkie zachowywały się mniej więcej w ten sam sposób. Jeśli chcesz znacząco zmienić sposób, w jaki atak odbywa się za pomocą modyfikatora (np. Nowa animacja z modyfikatorem), ta metoda nie jest dla Ciebie.
źródło
Attack
metodę agregowanego obiektu.TripleAttack
Klasa nie powinna wiedzieć oTearAttack
klasie. Gdyby to była prawda, doprowadziłoby to do tylu bólów głowy, coelse-if
blok. Oznacza to, że wszelkie animacje łez muszą znajdować się wewnątrzTearAttackBehaviour
obiektu. Ten obiekt nie wie (i nie powinien) wiedzieć, że został ozdobiony przezTripleAttack
przedmiot. W rezultacie 3 animacje łez przebiegają niezależnie, ponieważ są niezależne.Jako fan Binding of Isaac również zastanawiałem się, jak poradzić sobie z czymś takim. System w grze jest wystarczająco solidny, gdy pojawiające się zachowania powstają w wyniku kombinacji efektów (ten, który przychodzi mi do głowy, to uzyskanie lustra, łyżki do gięcia i niektórych wzmacniaczy zasięgu, które powodują zawirowanie, naprowadzanie ściany łez wokół Izaaka w stylu Magneto ). Sama ich liczba sprawiłaby, że blok „jeśli” byłby niepraktyczny.
Doszedłem do wniosku, że Izaak i jego łzy są dwoma bytami znajdującymi się w centrum ogromnej ramy Component-Entity . Istoty mają pewne podstawowe statystyki (prędkość ruchu, życie, zasięg, obrażenia, duszek itp.), A każdy składnik przyniesie ze sobą modyfikator statystyk i czasownik.
W kodzie Izaak i jego łzy mieliby listę zawierającą elementy interfejsu. Izaak miałby listę rzeczy, które subskrybują interfejs IsaacMutatora, a jego łzy tearMutator. IsaacMutator miałby funkcje modyfikujące zdrowie, szybkość, zasięg, wygląd i niektóre specjalne czasowniki Izaaka. TearMutator byłby podobny. Raz na pętlę gry Izaak przechodził przez wszystkich swoich Izaaków, a także wszystkie żywe łzy. Przykładowy angielski brzmi następująco:
i tak dalej. Ponieważ typy są addytywne, możesz układać w stosy, dodawać i usuwać zawartość swoich serc.
źródło
Myślę, że twoja droga działa najlepiej. Każdy z tych elementów daje warunek, jeśli użyte razem dają inny warunek, wówczas efektywnie potrzebujesz wszystkich 3 możliwych warunków.
Możesz także przejść do tego, tworząc nowy typ definicji, gdy oba elementy są obecne, ale tak naprawdę dodaje to splotu:
źródło