Kodowanie różnych stanów w grach przygodowych


12

Planuję grę przygodową i nie mogę ustalić, w jaki sposób można wdrożyć zachowanie poziomu w zależności od postępu fabuły.

Moja gra dla jednego gracza ma ogromny świat, w którym gracz musi wchodzić w interakcje z ludźmi w mieście w różnych momentach gry. Jednak w zależności od przebiegu historii graczowi zostaną przedstawione różne rzeczy, np. Przywódca Gildii zmieni lokalizacje z rynku na różne lokalizacje w mieście; Drzwi były odblokowywane tylko o określonych porach dnia po zakończeniu określonej procedury; Różne zdarzenia odcięcia / wyzwalacza mają miejsce dopiero po osiągnięciu określonego kamienia milowego.

Naiwnie pomyślałem na początku o użyciu instrukcji switch {}, aby zdecydować, co NPC powinien powiedzieć lub w którym można go znaleźć, i umożliwić celom misji interakcję tylko po sprawdzeniu stanu globalnej zmiennej stanu gry. Ale zdałem sobie sprawę, że szybko napotkam wiele różnych stanów gry i skrzynek przełączników, aby zmienić zachowanie obiektu. Ta instrukcja przełączania byłaby również bardzo trudna do debugowania i myślę, że może być również trudna w użyciu w edytorze poziomów.

Pomyślałem więc, że zamiast mieć jeden obiekt z wieloma stanami, może powinienem mieć wiele instancji tego samego obiektu z jednym stanem. W ten sposób, jeśli użyję czegoś w rodzaju edytora poziomów, mogę umieścić instancję NPC we wszystkich różnych lokalizacjach, w których kiedykolwiek mógłby się pojawić, a także instancję dla każdego stanu konwersacji, który ma. Ale to oznacza, że ​​wokół poziomu będzie unosiło się wiele nieaktywnych, niewidzialnych obiektów gry, co może być kłopotem z pamięcią lub po prostu trudne do zobaczenia w edytorze poziomów, nie wiem.

Lub po prostu utwórz identyczny, ale osobny poziom dla każdego stanu gry. Jest to najczystszy i wolny od błędów sposób robienia rzeczy, ale wydaje się, że to ogromna ręczna praca, upewniająca się, że każda wersja poziomu jest naprawdę identyczna.

Wszystkie moje metody wydają się tak nieefektywne, więc podsumowując moje pytanie, czy istnieje lepszy lub ustandaryzowany sposób na wdrożenie zachowania poziomu w zależności od stanu rozwoju historii?

PS: Nie mam jeszcze edytora poziomów - myślę o użyciu czegoś takiego jak JME SDK lub zrobieniu własnego.

Odpowiedzi:


9

Myślę, że w tym przypadku potrzebujesz wzoru wzorca stanu . Zamiast mieć wiele instancji każdego obiektu gry, utwórz jedną instancję, ale obuduj jej zachowanie w osobnej klasie. Utwórz wiele klas, po jednej dla każdego możliwego zachowania i nadaj wszystkim klasom ten sam interfejs. Skojarz jeden z obiektem gry (stan początkowy), a gdy warunki się zmienią (kamień milowy zostanie osiągnięty, minie pora dnia itp.) Zmienisz stan tego obiektu (tj. Powiążesz go z innym obiektem w zależności od logiki gry) i w razie potrzeby zaktualizuj jego właściwości.

Jeden przykład tego, jak wyglądałby interfejs stanu (w całości złożony - aby zilustrować poziom kontroli, jaki daje ten schemat):

interface NPCState {
    Scene whereAmI(NPC o);
    String saySomething(NPC o);
}

I dwie klasy implementujące:

class Busy implements NPCState {
    Scene whereAmI(NPC o) {
        return o.getWorkScene();
    }
    String saySomething(NPC o) {
        return "Can't talk now, I'm busy!";
    }
}

class Available implements NPCState {
    Scene whereAmI(NPC o) {
        return TAVERN;
    }
    String saySomething(NPC o) {
        String[] choices = o.getRandomChat();
        return choices[RANDOM.getInt(choices.length)];
    }
}

I przełączanie stanów:

// The time of day passed from "afternoon" to "evening"
NPCState available = new Available();
for ( NPC o : list ) {
    Scene oldScene = o.state.whereAmI(o);
    o.state = available;
    Scene newScene = o.state.whereAmI(o);
    moveGameObject(o, oldScene, newScene);
    ...

Ważne postacie niezależne mogą mieć swoje własne stany, logika wyboru stanu może być bardziej dostosowywana, a ty możesz mieć różne stany dla różnych aspektów gry (w tym przykładzie użyłem jednej klasy do podania zarówno lokalizacji, jak i czatu, ale możesz rozdzielić i wykonaj wiele kombinacji).

Działa to również dobrze z edytorami poziomów: możesz mieć proste pole kombi do przełączania stanu „globalnego” poziomu, a następnie dodawać i zmieniać położenie obiektów gry tak, aby były wyświetlane w tym stanie. Silnik gry byłby odpowiedzialny tylko za „dodanie” tego obiektu do sceny, gdy ma on prawidłowy stan - ale jego parametry byłyby nadal edytowalne w przyjazny dla użytkownika sposób.

(Oświadczenie: Mam niewielkie doświadczenie w pracy z edytorami gier, więc mogę śmiało powiedzieć o tym, jak działają profesjonalni edytorzy; ale moja uwaga na temat Wzorca stanu nadal się utrzymuje, organizowanie kodu w ten sposób powinno być czyste, łatwe w utrzymaniu i nie marnować systemu zasoby.)


wiesz, możesz połączyć ten wzorzec projektowania stanu z opisaną przeze mnie tablicą asocjacyjną. Możesz kodować obiekty stanu w sposób opisany tutaj, a następnie wybierać między różnymi obiektami stanu za pomocą tablicy asocjacyjnej, jak zasugerowałem.
jhocking

Zgadzam się, dobrze jest też oddzielić grę od silnika, a logika gry na sztywno wzmacnia sprzężenie między nimi (zmniejszając możliwość ponownego użycia). Jest jednak pewien kompromis, ponieważ w zależności od złożoności zamierzonego zachowania próbowanie „miękkiego kodu” wszystko może powodować niepotrzebne zakłócenia . W takim przypadku pożądane może być podejście mieszane (tj. Mieć „ogólną” logikę przejścia stanu, ale pozwalającą również na włączenie niestandardowego kodu)
mgibsonbr

Jak rozumiem, istnieje mapowanie jeden na jednego między NPCState i GameState. Następnie umieściłem NPC w tablicy i iterowałem ją, przypisując nowy NPCState, gdy zaobserwowana zostanie zmiana stanu gry. NPCState musi być w stanie wiedzieć, jak obsłużyć każdy wysłany do niego NPC różny, więc zasadniczo NPCState zawiera zachowanie wszystkich NPC dla danego stanu? Podoba mi się to, że wszystkie zachowania są przechowywane w jednym NPCState, co mapuje czysto na implementację edytora gier, ale to trochę sprawia, że ​​NPCState jest dość ogromny.
Cardin

Och, chyba źle zrozumiałem twój ans. Trochę go zmieniłem, aby uwzględnić obserwatorów. Jest to więc jeden stan różnicowy NPC dla każdego różnicowego NPC, z wyjątkiem super-ogólnych, takich jak Crowd NPC, które mogą współdzielić stan. Dla każdego stanu gry NPC zarejestruje się wraz ze swoim stanem NPC u obserwatora. W związku z tym Obserwator będzie dokładnie wiedział, który NPC jest zarejestrowany, aby zmienić zachowanie w danym stanie gry i po prostu iterować. A po stronie edytora gier, edytor gier musi tylko przekazać Observerowi sygnał, aby zmienić stan całego poziomu.
Cardin

1
Tak, to jest pomysł! Ważne postacie niezależne będą miały wiele stanów, a przejście stanu będzie zależeć głównie od ukończonych kamieni milowych. Generyczni NPC mogą czasami reagować na kamienie milowe, a nawet ich wybrany stan zależy od wewnętrznej właściwości (powiedzmy, że wszyscy NPC mają domyślny stan początkowy, a kiedy rozmawiasz z jednym z nich po raz pierwszy, przedstawia się, a następnie normalny cykl przełączania stanu).
mgibsonbr

2

Wybory, które rozważę, albo spowodują, że poszczególne obiekty zareagują na różne gamestaty, albo podniosą różne poziomy w różnych gamestatach. Wybór między tymi dwoma zależy od tego, co dokładnie staram się zrobić w grze (jakie są różne stany? W jaki sposób gra będzie przechodzić między stanami? Itp.)

Tak czy inaczej, nie zrobiłbym tego przez zakodowanie stanów w kodzie gry. Zamiast masowej instrukcji przełączania w obiektach NPC, zamiast zachowań NPC załadowanych do tablicy asocjacyjnej z pliku danych, a następnie skorzystam z tej tablicy asocjacyjnej, aby uruchomić inne zachowanie dla powiązanych stanów, coś takiego:

if (state in behaviors) {
  behaviors[state]();
}

Czy ten plik danych byłby rodzajem języka skryptowego? Myślę, że zwykły plik danych tekstowych może nie wystarczyć do opisania zachowania. W każdym razie masz rację, że powinien być ładowany dynamicznie. Nie mogę do końca myśleć o użyciu edytora gier do generowania poprawnego kodu Java, zdecydowanie trzeba go trochę przeanalizować.
Cardin

1
Cóż, taka była moja pierwotna myśl, ale po obejrzeniu odpowiedzi mgibsonbr zdałem sobie sprawę, że możesz zakodować różne zwierzęta jako osobne klasy, a następnie w pliku danych po prostu powiedz, które klasy zachowania pasują do którego stanu. Załaduj te dane do tablicy asocjacyjnej w czasie wykonywania.
jhocking

Och .. Tak, to zdecydowanie prostsze! : D W porównaniu ze scenariuszem osadzania czegoś takiego jak Lua haha ​​..
Cardin

2

Co powiesz na użycie wzorca obserwatora do szukania zmian kamienia milowego? Jeśli nastąpi zmiana, pewna klasa rozpozna to i obsłuży na przykład zmianę, którą należy wprowadzić w NPC.

Zamiast wspomnianego wzorca projektowania stanu użyłbym wzorca strategii.

Jeśli NPC nie ma sposobu na interakcję z postacią i m pozycji, w których mógłby być, istnieje maksymalnie (m * n) +1 klas, które musisz zaprojektować. Używając wzorca strategii skończyłbyś z n + m + 1 klasami, ale te strategie mogą być również używane przez innych NPC.

Mogłaby więc istnieć klasa zajmująca się kamieniami milowymi oraz klasy, które obserwują tę klasę i radzą sobie zarówno z NP, jak i wrogami lub czymkolwiek, co powinno zostać zmienione. Jeśli obserwatorzy zostaną zaktualizowani, zdecydują, czy muszą coś zmienić w instancjach, którymi rządzą. Na przykład klasa NPC w konstruktorze poinformowałaby menedżera NPC, kiedy musi zostać zaktualizowany i co należy zaktualizować ...


Wzór Observer wydaje się interesujący. Myślę, że czysto pozostawiłoby to całą odpowiedzialność u NPC, aby zarejestrować się u obserwatora państwowego. Wzór strategii przypomina zachowanie wyzwalacza i sztucznej inteligencji Unity Engine, które służy do dzielenia się zachowaniem między różnymi obiektami gry (tak myślę). Brzmi wykonalnie. Nie jestem pewien, jakie są za / przeciw, ale to, że Unity również używa tej samej metody, jest nieco uspokajające haha ​​..
Cardin

Właśnie użyłem tych dwóch wzorów kilka razy, więc nie jestem w stanie powiedzieć ci o wadach: - / Ale myślę, że to miłe w przypadku pojedynczej odpowiedzialności i dostępności, aby przetestować każdą strategię :) Może to być mylące, jeśli twój używając strategii w wielu różnych klasach i chcesz znaleźć każdą klasę, która z niej korzysta.
TOAOGG

0

Wszystkie podane podejścia są ważne. To zależy od sytuacji, w jakiej się znajdujesz w danym momencie. Wiele przygód lub MMO używa ich kombinacji.

Na przykład, jeśli kluczowe wydarzenie zmienia dużą część poziomu (np. Komornik sprząta mieszkanie i wszyscy w nim zostają aresztowani), zwykle łatwiej jest zastąpić cały pokój drugim pokojem, który wygląda podobnie.

OTOH, jeśli postacie chodzą po mapie i robią różne rzeczy w różnych miejscach, często masz jednego aktora, który obraca się przez różne obiekty zachowań (np. Chodź prosto / żadnych rozmów vs. stań tutaj / rozmowa o śmierci Mitcha), co może obejmować „ukryty”, jeśli ich cel został spełniony.

To powiedziawszy, zwykle posiadanie duplikatów obiektu tworzonego ręcznie nie powinno powodować żadnych problemów. Ile obiektów możesz stworzyć? Jeśli możesz stworzyć więcej obiektów, niż gra może zapętlić, spójrz na ich „ukrytą” właściwość i pomiń, silnik jest zbyt wolny. Więc nie martwiłbym się tym zbytnio. W rzeczywistości robi to wiele gier online. Niektóre postacie lub przedmioty są zawsze dostępne, ale nie są wyświetlane postaciom, które nie mają odpowiedniej misji.

Możesz nawet łączyć podejścia: mieć dwoje drzwi w swoim budynku. Jeden prowadzi do mieszkania „przed komornikiem”, drugi do mieszkania po. Gdy wejdziesz do korytarza, pokazany zostanie tylko ten, który dotyczy postępów w historii. W ten sposób możesz mieć ogólny mechanizm dla „przedmiotu jest widoczny w bieżącym punkcie historii” i drzwi z jednym miejscem docelowym. Alternatywnie możesz zrobić bardziej skomplikowane drzwi, które mogą mieć zachowania, które można wymienić, a jednym z nich jest „idź do pełnego mieszkania”, a drugie „idź do pustego mieszkania”. Może to wydawać się bezsensowne, jeśli tak naprawdę zmienia się tylko przeznaczenie drzwi, ale jeśli zmienia się również ich wygląd (np. Duży zamek przed drzwiami, który najpierw trzeba złamać),

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.