Jak wdrożyć dialog rozgałęziający w javascript?

11

W JavaScript robię bardzo podstawowy typ powieści wizualnej . Jestem początkującym, więc robię to tylko dla zabawy i nauki, a z powodu złego planowania natknąłem się na pewien problem, kiedy wchodzisz do oddziału w dialogu.

Obecnie trzymam skrypt gry w zmiennej łańcuchowej i każdą scenę dzielę tagiem takim jak „# ~” na mniejsze tablice, aby skrypt gry wyglądał tak:

var script = "Hello World!#~How are you today?"
var gameText = script.split("#~");
//gameText[0]= Hello World!

Działa to świetnie w przypadku elementów liniowych, ale jak mam obsługiwać gałąź w drzewie dialogowym? Ta metoda wydaje się bardzo skomplikowana, ponieważ musiałbym dokładnie wiedzieć, jak długa jest każda ścieżka, a gdybym kiedykolwiek musiał coś zmienić, byłby to ból głowy.

Jak mogę to zrobić w prostszy sposób? Staram się trzymać waniliowego JavaScript, ponieważ chciałbym, aby gra współpracowała z Web Run Time.

Cichy kartograf
źródło
Ten film przedstawia kilka pomysłów: youtube.com/watch?v=XM2t5H7kY6Y
JCM
Niedawno musiałem opracować coś do tego za pomocą Node i zdecydowałem się na bardzo podstawową strukturę pliku tekstowego. Wynikowy kod i format tekstowy można zobaczyć na stronie: github.com/scottbw/dialoguejs Kod to GPL, możesz go używać. Jestem pewien, że przystosowanie się do gry JS bez węzła nie będzie trudne - zastąp część „fs.load ()” Ajax.
Scott Wilson
Sprawdź Ink , rozgałęziony język skryptowy opracowany przez Inkle Studio . Istnieje wiele programowych integracji atramentu (Java, JavaScript, C #) i wiele zasobów innych firm . Atrament był również używany w wielu grach komercyjnych. Wreszcie istnieje edytor pulpitu, Inky, który może sprawdzać składnię i „odtwarzać” dialogi rozgałęzień.
Big Rich

Odpowiedzi:

8

Odpowiedź Filipa już pokazuje właściwy kierunek. Myślę, że struktura danych jest niepotrzebnie pełna. Krótsze teksty łatwiej byłoby pisać i czytać.

Nawet jeśli krótsze teksty uczynią algorytm nieco bardziej złożonym, warto to zrobić, ponieważ algorytm pisze się tylko raz, ale większość czasu poświęcisz na napisanie i utrzymanie historii. Dlatego zoptymalizuj, aby ułatwić wykonanie części, którą poświęcisz najwięcej czasu.

var story = [
  { m: "Hi!" },
  { m: "This is my new game." },
  { question: "Do you like it?", answers: [
    { m: "yes", next: "like_yes" },
    { m: "no", next: "like_no" },
  ] },
  { label: "like_yes", m: "I am happy you like my game!", next: "like_end" },
  { label: "like_no", m: "You made me sad!", next: "like_end" },
  { label: "like_end" },
  { m: "OK, let's change the topic" }
];

Kilka wyjaśnień dla tego projektu:

Cała historia jest napisana w jednym szyku. Nie musisz podawać liczb, są one dostarczane automatycznie przez składnię tablicy: pierwszy element ma indeks 0, następny ma indeks 1 itd.

W większości przypadków nie jest konieczne wpisanie numeru następnego kroku. Zakładam, że większość wierszy tekstu nie jest gałęziami. Uczyńmy „następnym krokiem jest następujący element” domyślne założenie i robimy notatki tylko wtedy, gdy jest inaczej.

Do skoków używaj etykiet , a nie liczb. Następnie, jeśli później dodasz lub usuniesz kilka wierszy, logika historii zostanie zachowana i nie będziesz musiał dostosowywać liczb.

Znajdź rozsądny kompromis między jasnością a skrótem. Na przykład proponuję napisać „m” zamiast „wiadomość”, ponieważ będzie to najczęściej używane polecenie w historii, więc skrócenie go spowoduje, że tekst będzie bardziej czytelny. Ale nie ma potrzeby skracania pozostałych słów kluczowych. (Rób jednak, co chcesz. Ważne, aby było to dla Ciebie najbardziej czytelne . Alternatywnie możesz podać zarówno „m”, jak i „wiadomość” jako prawidłowe słowa kluczowe.)

Algorytm gry powinien wyglądać mniej więcej tak:

function execute_game() {
  var current_line = 0;
  while (current_line < story.length) {
    var current_step = story[current_line];
    if (undefined !== current_step.m) {

      display_message(current_step.m);
      if (undefined !== current_step.next) {
        current_line = find_label(current_step.next);
      } else {
        current_line = current_line + 1;
      }

    } else if (undefined !== current_step.question) {

      // display the question: current_step.question
      // display the answers: current_step.answers
      // choose an answer
      // and change current_line accordingly

    }
  }
}

Nawiasem mówiąc, te pomysły zostały zainspirowane przez Ren'Py , który nie jest dokładnie tym, czego chcesz (nie JavaScript, nie internet), ale i tak może dać ci fajne pomysły.

Viliam Búr
źródło
Dziękuję za dogłębne wyjaśnienie, nie wiedziałem, że tablice mogą działać tak, jak pokazaliście ty i Philipp, myślałem, że mogą przechowywać tylko ciągi lub liczby.
Cichy kartograf
1
Próbowałem wdrożyć twoje rozwiązanie i działa całkiem dobrze, chociaż myślę, że w niektórych miejscach ({ label: "like_yes"; m: "I am happy you like my game!"; next: "like_end" },)powinno być „,” a nie; Co dokładnie nazywa się rzecz w nawiasach klamrowych? Czy to obiekt w tablicy? gdybym chciał więcej informacji o tym, jak z tego korzystać, czego bym szukał?
Cichy kartograf
Tak, {...}jest przedmiotem. W JavaScript obiekt jest tablicą asocjacyjną klucz-wartość (z pewnymi dodatkowymi funkcjami, nieużywanymi w tym przykładzie), podobną do tablicy w PHP lub mapy w Javie. Aby uzyskać więcej informacji, zobacz artykuły w Wikipedii na temat JavaScript i ECMAScript, a także dokumentację z tym powiązaną, zwłaszcza oficjalną dokumentację ECMAScript.
Viliam Búr
1
przy okazji zauważ, że struktura danych, którą tutaj poleca, to w zasadzie JSON. Osobiście poleciłbym przejście do JSON (najczęściej dodaj nawiasy klamrowe i „drzewo”: wokół całej rzeczy; np. {„Drzewo”: [itp]}), a następnie możesz przechowywać drzewa dialogów w plikach zewnętrznych. Umieszczanie danych w zewnętrznych plikach ładowanych przez grę jest znacznie bardziej elastyczne i modułowe (dlatego to podejście jest najlepszą praktyką).
jhocking
5

Poleciłbym ci stworzyć tablicę zdarzeń dialogowych. Każde zdarzenie to obiekt zawierający tekst, który mówi NPC i tablicę możliwych odpowiedzi graczy, które z kolei są obiektami z tekstem odpowiedzi i indeksem zdarzenia, które następuje po tej odpowiedzi.

var event = []; // create empty array

// create event objects and store them in the array
event[0] = { text: "Hello, how are you?",
             options: [    { response: "Bad", next: 1 },
                           { response: "Good", next: 2 }
                      ]
           };
event[1] = { text: "Why, what's wrong?",
             options: [    { response: "My dog ran away", next: 3},
                           { response: "I broke up with my girlfriend", next: 4}
                      ]
           };
event[2] = { text: "That's nice",

...
Philipp
źródło
2

Powinieneś zastosować inne podejście. JavaScript obsługuje tablice i obiekty, więc dlaczego nie użyć jednego dla każdego wpisu, oszczędzając ci podziału, a także ułatwiając edycję / czytanie tekstu?

Jeśli chcesz, możesz rzucić okiem na prototyp , który zrobiłem w ciągu kilku godzin dla # 1gam . Źródła można używać bezpłatnie na GPLv3 (nic mi nie jest, jeśli nie trzymasz się GPL, jeśli używasz go tylko jako inspiracji. Ale daj mi znać, kiedy skończysz grę.) Po prostu nie oczekuj wspaniałego pisania ani czegoś takiego. ;)

Aby krótko wyjaśnić, jak działa kod, ignorując animację CSS i podobne rzeczy:

  • var data zasadniczo zawiera całą historię ze wszystkimi możliwymi wyborami itp.
  • Każda „lokalizacja” (lub strona / pozycja) jest identyfikowana za pomocą identyfikatora. Pierwszy identyfikator na liście to startdrugi cwaititd.
  • Każda lokalizacja zawiera dwa obowiązkowe elementy: Podpis, a także sam tekst. Łącza do decyzji zapisywane są w prosty sposób, przyjmując formę [target location:display text].
  • W środku dzieje się cała „magia” navigate(): Ta funkcja sprawia, że ​​linki znaczników są klikalne. Jest trochę dłużej, ponieważ tam też obsługuję statyczny tekst dla Dead Ends. Ważną częścią są dwa pierwsze połączenia z replace().
  • Opcjonalne ostatnie wpisy definiują nowe kolory tła, do których należy się wtopić, wspierając ogólny nastrój gry.
  • Zamiast definiować te kolory, możesz również dodać linki prowadzące do innych lokalizacji (zwróć uwagę, że mój kod tego nie obsługuje; to tylko pewien pomysł, aby to zademonstrować):

    'start': ['Waking up', 'You wake...', 'cwait:yell for help', 'cwait: wait a bit', 'clook: look around']

Mario
źródło