Jak radzić sobie z wieloma wątkami fabularnymi w grze RPG?

26

Zaprojektowałem grę RPG, która ma wiele wątków fabularnych, co oznacza, że ​​w zależności od wyboru użytkownika mogą się zdarzyć pewne rzeczy, możesz osiągnąć to samo na kilka sposobów, zakończenie może być inne i tak dalej.

Wdrożyłem prosty silnik decyzyjny, który działa dobrze, ale ma jedną wielką wadę. W momencie podjęcia decyzji na twoją decyzję ma wpływ natychmiastowa historia, co oznacza, że ​​nie możesz podjąć decyzji, która wpłynie na ciebie w dalekiej przyszłości . Dzieje się tak, ponieważ historia rozwija się jak gałąź w strukturze drzewa i zawsze musi wiedzieć, który węzeł jest następny. Pod maską decyzje są wdrażane za pomocą kolejki: każdy węzeł wie o poprzednim węźle i następnym węźle (lub jeśli jest to węzeł decyzyjny, czeka na dane wejściowe użytkownika, aby ustawić następny węzeł)

Widziałem wiele gier ze złożonymi silnikami decyzyjnymi i zastanawiam się, jak są one tworzone? Czy istnieje specjalny projekt, który sprawia, że ​​wszystko jest naprawdę łatwe? Czy ktoś zrobił coś podobnego i może dać mi wskazówkę, jak sobie z tym poradzić?

AKTUALIZACJA 1:

Ważnym aspektem jest zachowanie niezależności kodu opowieści, aby można było nim manipulować z zewnętrznego pliku. Planuję używać tego jako silnika, więc nawet możliwe wybory muszą pochodzić z zewnętrznego pliku. Kod musi być całkowicie abstrakcyjny.

Ponadto interesuje mnie rozwiązanie projektowe, dobry sposób na zrobienie tego, jak inni to zrobili lub zrobili.

Valentin Radu
źródło
1
Kiedy podejmowane są ważne decyzje, po prostu śledź je w globalnie dostępnej zmiennej (tablica tych zmiennych będzie łatwiejsza do zarządzania). Do zmiennych tych można następnie odwoływać się w późniejszych częściach programu do gry, aby odpowiednio działać lub wyświetlać różne rzeczy. Na przykład gracz decyduje się na posadzenie małego drzewa, a później to drzewo wydaje się bardzo duże - jeśli nie posadziłoby tego drzewa, to drzewo to nie byłoby w ogóle w tym samym późniejszym momencie gry.
Randolf Richardson
Tak, na początku tego właśnie potrzebowałem, ale potrzebuję tego, aby być niezależnym od kodu. Oznacza to, że można w pełni manipulować historią z zewnętrznego pliku. Muszę więc znaleźć sposób na uogólnienie tego, co właśnie powiedziałeś, i zrobić to w taki sposób, aby nie stracić kontroli nad projektem (jest sporo decyzji). Zaktualizuje pytanie. Dzięki!
Valentin Radu,
Mówiąc ściślej, nie mogę sprawdzić if (isTree)ani zachować isTreeglobalnego var, ponieważ historia może mieć wybór. Wiesz co mam na myśli? To bardziej jak silnik wyboru, który będzie obsługiwał wiele historii.
Valentin Radu,
Ma to również inny problem, powiedzmy, że jeśli użytkownik zdecyduje się zasadzić drzewo, które ustawiliśmy isTree=true, to później robi coś innego, na przykład walcząc ze szkolnym kolegą, który w zamian idzie i ściąga swoje drzewo, gdy drzewo jest jeszcze młode bo skopał mu tyłek. Teraz mamy 2 zmienne, które wpływają na istnienie drzewa isTree==true' and didFightBrat == false`. Wiesz co mam na myśli? I łańcuch może trwać wiecznie, na istnienie drzewa może mieć wpływ nieznana liczba czynników. Wiesz co mam na myśli?
Valentin Radu,
Następnie przechowuj te dane w pliku na dysku. Musisz utworzyć dwa podprogramy, aby załadować i zapisać informacje, a następnie użyć tych procedur z każdej części kodu zgodnie z potrzebami.
Randolf Richardson,

Odpowiedzi:

18

Możesz także uogólnić kolejkę na ukierunkowany wykres acykliczny (DAG). Możesz przeczytać o nich na Wikipedii. Zasadniczo każdy węzeł może mieć jeden lub więcej węzłów nadrzędnych, od których „zależy”. Cykle nie są dozwolone, tzn. Jeśli A zależy od B, B nie może zależeć od A (bezpośrednio lub poprzez dowolny pośredni łańcuch innych węzłów).

Każdy węzeł znajduje się w stanie „aktywnym” lub „nieaktywnym” i można go aktywować tylko wtedy, gdy wszyscy jego rodzice są już aktywni. Struktura wykresu (jakie są węzły i jak są one połączone) jest częścią danych gry, ale stan aktywny / nieaktywny jest częścią zapisywanych danych gracza.

W ten sposób możesz modelować takie rzeczy: kiedy sadzisz drzewo, oznaczasz zadanie „plantedTree” jako aktywne; potem, w dalszej części gry, kolejne zadanie „treeGrown” określa zarówno „plantedTree”, jak i inny węzeł (część historii) jako jego rodziców. Następnie „TreeGrown” uaktywnia się dopiero, gdy gracz dojdzie do tego momentu w historii, a także „plantedTree” jest aktywne.

Możesz dołączyć inne funkcje, takie jak węzły aktywowane, jeśli którykolwiek z ich rodziców aktywuje się, lub węzły, które są aktywowane przez jednego rodzica i dezaktywowane przez drugiego itd. Jest to dość ogólna struktura do tworzenia opowieści z wieloma współzależnymi wątkami.

Nathan Reed
źródło
Bardzo dobra odpowiedź, dziękuję. Naprawdę rozwiązuje również inne problemy, które mam, takie jak zapisywanie postępów użytkownika. To jest to, czego potrzebuję.
Valentin Radu,
@NathanReed Dlaczego to nie może być cykliczne? Bycie acyklicznym zazwyczaj nie jest cechą, ale produktem ubocznym projektu wykresu. Nie stworzyłbym tego z taką intencją. Wyobraź sobie na przykład, czy chcesz, aby twoje drzewo rozpoznawało pory roku. Są z natury cykliczne, a twoja historia może być identyczna w zależności od wyborów dostępnych w ciągu jednego sezonu.
Musi być acykliczny, ponieważ jeśli istnieje cykl, wchodzisz w nieskończoną pętlę, próbując dowiedzieć się, czy węzeł cyklu może być aktywny, ponieważ rekurencyjnie sprawdzasz wszystkich jego przodków, w tym sam węzeł. Gdybyś chciał wymodelować coś w rodzaju pór roku, nie zrobiłbym tego w kontekście tego wykresu.
Nathan Reed,
@NathanReed Ach, przepraszam, przegapiłem część rekurencyjną.
3

Z tego, co rozumiem, to, czego chcesz, to nie tylko silnik decyzyjny, ale także silnik reguł. Dla każdej decyzji wykonujesz podzbiór reguł określonych przez tę decyzję. Wykonanie tych reguł jest często zależne od stanu niektórych podmiotów, takich jak przykład drzewa.

Zasadniczo, kiedy gracz podejmuje decyzję, szukasz tej decyzji, przestrzegasz zasad, a następnie podajesz następny zestaw dostępnych decyzji, jak zwykle. Jednak reguły są dynamiczne, ponieważ niektóre z nich będą działać tylko na podstawie innych reguł, które już zostały wykonane.

Więcej na Wikipedii .

Z podtytułu Kiedy stosować silniki reguł (nacisk należy do mnie):

  • Problem jest zbyt skomplikowany dla tradycyjnego kodu.
  • Problem może nie być złożony, ale nie można znaleźć solidnego sposobu na jego zbudowanie.
  • Problem wykracza poza wszelkie oczywiste rozwiązania oparte na algorytmach.
  • Jest to złożony problem do rozwiązania. Nie ma oczywistych tradycyjnych rozwiązań lub problem nie jest w pełni zrozumiały.
  • Logika często się zmienia
  • Sama logika może być prosta, ale reguły zmieniają się dość często. W wielu organizacjach wydania oprogramowania są rzadkie, a reguły mogą pomóc zapewnić „sprawność”, która jest potrzebna i oczekiwana w racjonalnie bezpieczny sposób.
  • Eksperci domenowi i analitycy biznesowi są łatwo dostępni, ale nietechniczni.
  • Eksperci domenowi często dysponują bogatą wiedzą na temat reguł i procesów biznesowych. Zazwyczaj są nietechniczne, ale mogą być bardzo logiczne. Reguły mogą pozwolić im wyrazić logikę na własnych warunkach. Oczywiście nadal muszą myśleć krytycznie i być w stanie myśleć logicznie. Wiele osób na stanowiskach nietechnicznych nie ma przeszkolenia w zakresie logiki formalnej, więc bądź ostrożny i pracuj z nimi. Kodyfikując wiedzę biznesową w regułach, często ujawniasz dziury w sposobie, w jaki reguły biznesowe i procesy są obecnie rozumiane.

Należy zauważyć, że czasami silnik reguł najlepiej wdrażać za pomocą uproszczonego „języka” specyficznego dla domeny lub czegoś takiego jak YAML. Nie sugerowałbym XML.


źródło
1

Musisz wziąć pod uwagę, że zdarzenie nie jest oparte wyłącznie na decyzji użytkownika. Jak zauważyłeś, jakieś zdarzenie musi się dołączyć, jeśli po podjęciu zestawu sekwencji decyzji, a następnie dołączone zostanie coś innego (np. Dwa dni później).

Myślę, że potrzebujesz sposobu modelowania zdarzeń i sposobu, aby je uruchomić. Podczas gdy pierwszy jest bardziej ograniczony do konkretnego przypadku, ten drugi może być modelowany za pomocą hierarchicznej maszyny stanów (HSM), która bezpośrednio lub pośrednio wyzwala zdarzenia.

Należy pamiętać, że maszyna stanu cierpi na Klątwę wymiarowości, która jest łagodzona jedynie przez strukturę hierarchiczną. Wkrótce zrozumiesz, że musisz modelować złożone znaczenie statusu za pomocą HMS, ale także zapewnić sposób na jego zapytanie.

W tym scenariuszu masz podstawowe zdarzenia (decyzje użytkownika, czas, zmiana pogody itp.), Które są przetwarzane zarówno przez HSM, jak i przez podstawowe wywołania zwrotne zdarzeń. HSM zapewnia model „pamięci”, a wywołania zwrotne umożliwiają opisanie, w jaki sposób pamięć ta musi zostać wykorzystana do obliczenia konsekwencji sekwencji decyzji / zdarzeń zewnętrznych.

Możesz także skończyć na użyciu słownika (lub innej struktury kolekcji) HMS, po jednym dla każdego „aspektu” statusu, który musisz obliczyć. Przykładem może być użycie zdarzenia HMS i jednego dla decyzji podejmowanych przez wywołania zwrotne w celu wyzwolenia zdarzeń.

Cała ta infrastruktura służy temu, by naśladować zachowanie ludzkiego Mistrza Lochów: generalnie zapisuje w pamięci bieżącą sytuację (HMS [„zewnętrzny”]) z powodu decyzji gracza i warunków środowiskowych; gdy coś się dołącza, może podejmować decyzje na podstawie swojego zapisu mentalnego i zapisywać również status wewnętrznej strategii (HSM [„wewnętrzny”]), aby uniknąć reakcji w podobny sposób, jeśli na przykład sytuacja się pojawi.

FxIII
źródło