Próbuję napisać krótką „grę”, w której gracz chodzi i walczy z potworami, ale nie mam pojęcia, jak sobie z tym poradzić.
Powiedzmy na przykład, że mam „Wojownika” i „Trolla”. Jak walczą ze sobą? Wiem, że mogę coś zrobić
Conan = Warrior.new();
CaveTroll = Troll.new();
Conan.attack(CaveTroll);
CaveTroll.attack(Conan);
Ale która część gry kontroluje potwora? Czy po prostu trzymam powyższą sekwencję w pętli, dopóki jedna z nich nie umrze? Czy też gra „silnik” musi mieć część, która dotyczy konkretnie walki? Czy jest to aspekt sztucznej inteligencji Trolla, który musi dbać o swoje działania?
Ponadto, kto / co określa działania podejmowane przez potwora? Może Troll może uderzać, kopać, gryźć, rzucać zaklęcia, pić mikstury, używać magicznego przedmiotu. Czy silnik gry określa, jakie działania podejmuje Troll, czy też jest czymś, czym zarządza klasa Troll?
Przepraszam, nie mogę być bardziej szczegółowy, ale potrzebuję wskazówek, w jakim kierunku pójść w tym kierunku.
Odpowiedzi:
Wyobrażam sobie sekwencję bitew jako minigrę w twojej grze. Tiki aktualizacji (lub tiki zwrotne) są kierowane do komponentu obsługującego te zdarzenia. Takie podejście obejmuje logikę sekwencji bitew w osobnej klasie, pozostawiając główną pętlę gry swobodną w przechodzeniu między stanami gry.
Klasa sekwencji bitew wyglądałaby następująco:
Twój Troll i Wojownik dziedziczą po wspólnej nadklasie zwanej Istotą. W ramach HandleTurn atakująca istota może się poruszać. Jest to odpowiednik rutyny sztucznej inteligencji AI.
Metoda walki decyduje o tym, co jednostka zrobi. Pamiętaj, że nie musi to angażować przeciwnej istoty, takiej jak wypicie mikstury lub ucieczka.
Aktualizacja: Aby wesprzeć wiele potworów i drużynę gracza, wprowadzasz klasę grupową:
Klasa Group zastąpi wszystkie wystąpienia Entity w klasie BattleSequence. Wybieranie i atakowanie będzie obsługiwane przez samą klasę Istoty, więc AI może wziąć pod uwagę całą grupę przy wyborze najlepszego sposobu działania.
źródło
Miałbym dedykowany obiekt walki, który zarządza walką. Zawierałby pełny stan walki, w tym takie rzeczy jak lista postaci graczy, lista wrogów, aktualna tura, teren bitwy i tak dalej. Walka może wtedy mieć metodę aktualizacji, która zarządza logiką bitwy. Umieszczenie kodu walki w prostej pętli nie jest dobrym pomysłem, ponieważ skończyłby się naprawdę szybko. Zwykle masz trochę czasu i różne etapy bitwy.
Jeśli chodzi o podejmowane działania, z pewnością możesz sprawić, że będzie losowy, ale nie ma sensu, aby potwór z pełnym HP rzucił zaklęcie leczące. Opłaca się mieć podstawową logikę określania, które działanie należy podjąć. Na przykład niektóre akcje mogą mieć wyższy priorytet niż inne (np. Kopnięcia trolla w 30% przypadków), a także inne warunki, dzięki którym bitwy będą ciekawsze (np. Gdy troll HP ma mniej niż 10% pełnego HP, jest 20% szansa na rzucenie zaklęcia leczącego, w przeciwnym razie szansa wynosi 1%). To może być tak złożone, jak chcesz.
Myślę, że klasa potworów powinna poradzić sobie z wyborem akcji do wykonania, obiekt bitwy prosi potwora o akcję, a potwór dokonuje wyboru, a następnie przystępuje do jego zastosowania. Jednym z pomysłów jest posiadanie obiektu strategii, który podłączasz do potworów i który wybiera z listy możliwych akcji potworów na podstawie priorytetów, kategorii i warunków przypisanych do każdej akcji bitwy. Następnie możesz mieć na przykład klasę OffensiveStrategy, która ma pierwszeństwo przed atakami umiejętności defensywnych, oraz inną CautiousStrategy, która ma większe szanse na wyleczenie. Boss może być w stanie dynamicznie zmieniać strategię w oparciu o jej obecny stan.
Ostatnia rzecz. Możesz chcieć mieć zarówno postacie graczy, jak i potwory dziedziczące po tej samej klasie, być instancjami tej samej klasy (na przykład aktor lub walczący), lub udostępnić wspólny obiekt, który zawiera wspólną funkcjonalność. Zmniejsza to duplikację kodu, a także pozwoli ci mieć po swojej stronie kontrolowanych przez AI NPC, którzy mogą wdrożyć te same strategie, które już zakodowałeś dla potworów.
źródło
Tak, musisz mieć specjalną część w silniku, która obsługuje walkę.
Nie wiem, jak dokładnie walczysz, ale założę, że gracze wędrują po świecie gry, spotykają się z potworami, a bitwa trwa w czasie rzeczywistym. Jeśli tak, to troll musi znać otoczenie w określonym obszarze, może określić, jak daleko troll widzi coś przed sobą (troll sobie z tym radzi).
Jeśli chodzi o AI, myślę, że silnik musi sobie z tym poradzić, więc powiedzmy, że masz więcej niż jednego wroga, który może zrobić to samo (ugryzienie), możesz po prostu przypisać AI innemu potworowi i gotowe!
źródło
Twój gracz i twój troll są niczym innym jak zestawami danych, które nazywamy modelem danych opisującym twój świat. Życie, ekwipunek, możliwości ataku, a nawet ich znajomość świata - wszystko opiera się na modelu danych.
Zachowaj jeden główny obiekt modelu, który przechowuje wszystkie dane opisujące Twój świat. Będzie zawierał ogólne informacje o świecie, takie jak trudność, parametry fizyki itp. Będzie także zawierał listę / tablicę danych określonych bytów , jak to opisano powyżej. Ten główny model może składać się z wielu podobiektów w celu opisania twojego świata. Nigdzie w twoim modelu nie powinieneś mieć żadnych funkcji sterujących logiką gry lub logiką wyświetlania; gettery są jedynym wyjątkiem i byłyby używane tylko w celu umożliwienia łatwiejszego pobierania danych z modelu (jeśli członkowie publiczni jeszcze tego nie zrobią).
Następnie utwórz funkcje w jednej lub więcej klasach „kontrolerów”; możesz napisać je wszystkie jako funkcje pomocnicze w swojej klasie głównej, chociaż po pewnym czasie może to nieco wzrosnąć. Będą one nazywane każdą aktualizacją, aby działać na danych podmiotów w różnych celach (ruch, atak itp.). Trzymanie tych funkcji poza klasą encji jest bardziej zasobooszczędne, a gdy wiesz, co opisuje twoją encję, automatycznie wiesz, jakie funkcje muszą na nią działać.
Ostatnia uwaga jest taka, że przydatne jest także oddzielenie logiki wyświetlania od logiki gry. Logika wyświetlania brzmiałaby: „Gdzie narysować to na ekranie i w jakim kolorze?” vs. logika gry jest tym, co zarysowałem w powyższym pseudokodzie.
(Nota dewelopera: Podczas korzystania z klas, to luźno podąża za funkcjonalnym podejściem programistycznym, które uznaje wszystkie metody za idealnie bezstanowe, pozwalając na czysty model danych i podejście do przetwarzania, które minimalizuje błędy spowodowane stanem zatrzymanym. FP jest ostatecznym MVC, ponieważ osiąga MVC cel wyraźnego rozdzielenia obaw. Zobacz to pytanie ).
źródło