Come posso implementare il dialogo di branching in javascript?


11

Sto realizzando un tipo di gioco visual novel molto semplice in JavaScript. Sono un principiante, quindi lo sto facendo solo per divertimento e apprendimento, e a causa di una cattiva pianificazione ho riscontrato un po 'di problemi quando si arriva a una succursale nel dialogo.

Attualmente tengo lo script per il gioco in una variabile stringa e suddivido ogni scena con un tag come "# ~" in array più piccoli in modo che lo script del gioco sia simile al seguente:

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

Funziona benissimo per le cose lineari, ma come dovrei gestire un ramo nella finestra di dialogo? Questo metodo sembra molto complicato, poiché dovrei sapere esattamente quanto è lungo ogni percorso e quindi se mai avessi bisogno di cambiare qualcosa, sarebbe un mal di testa.

Come posso farlo in un modo più semplice? Sto cercando di attenermi a JavaScript vaniglia poiché mi piacerebbe che il gioco funzionasse con Web Run Time.


Questo video può darti alcune idee: youtube.com/watch?v=XM2t5H7kY6Y
JCM

Di recente ho dovuto sviluppare qualcosa per questo usando Node e ho optato per una struttura di file di testo molto semplice. Puoi vedere il codice e il formato di testo risultanti su: github.com/scottbw/dialoguejs Il codice è GPL, sentiti libero di usarlo. Sono sicuro che non sarà difficile adattarsi a un gioco JS non Node - sostituisci la parte "fs.load ()" con Ajax.
Scott Wilson,

Dai un'occhiata a Ink , un linguaggio di scripting per storie ramificate, sviluppato da Inkle Studio . Esistono varie integrazioni di inchiostro programmatiche (Java, Javascript, C #) e molte risorse di terze parti . L'inchiostro è stato utilizzato anche in molti giochi commerciali. Infine, c'è un editor desktop, Inky , che può controllare la sintassi e "riprodurre" i dialoghi di diramazione.
Big Rich

Risposte:


8

La risposta di Philipp mostra già la giusta direzione. Penso solo che la struttura dei dati sia inutilmente dettagliata. Testi più brevi sarebbero più facili da scrivere e leggere.

Anche se testi più brevi renderebbero l'algoritmo un po 'più complesso, vale la pena farlo, perché scrivi l'algoritmo solo una volta, ma la maggior parte del tuo tempo sarà speso a scrivere e mantenere la storia. Pertanto ottimizza per rendere più semplice la parte che trascorrerai più tempo a fare.

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" }
];

Alcune spiegazioni per questo disegno:

L'intera storia è scritta in una matrice. Non è necessario fornire numeri, sono forniti automaticamente dalla sintassi dell'array: il primo elemento ha indice 0, il successivo ha indice 1, ecc.

Nella maggior parte dei casi, non è necessario scrivere il numero del passaggio seguente. Presumo che la maggior parte delle righe di testo non siano rami. Facciamo che "il passaggio successivo sia l'elemento seguente" un presupposto predefinito e prendiamo appunti solo quando è diversamente.

Per i salti, utilizzare le etichette , non i numeri. Quindi, se successivamente aggiungi o rimuovi alcune righe, la logica della trama verrà preservata e non dovrai modificare i numeri.

Trova un ragionevole compromesso tra chiarezza e brevità. Ad esempio, suggerisco di scrivere "m" invece di "messaggio", perché sarà il comando più usato di sempre, quindi accorciandolo renderà il testo più leggibile. Ma non è necessario abbreviare le parole chiave rimanenti. (Tuttavia, fai come desideri. L'importante è renderlo più leggibile per te . In alternativa, potresti supportare sia "m" che "messaggio" come parole chiave valide.)

L'algoritmo per il gioco dovrebbe essere qualcosa del genere:

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

    }
  }
}

A proposito, queste idee sono state ispirate da Ren'Py , che non è esattamente quello che vuoi (non JavaScript, non web), ma potrebbe darti comunque delle idee interessanti.


Grazie per la spiegazione approfondita, non ero consapevole del fatto che le matrici potessero funzionare come tu e Philipp avete mostrato, pensavo che potessero contenere solo stringhe o numeri.
The Silent Cartographer,

1
Ho cercato di implementare la tua soluzione e funziona abbastanza bene, anche se penso che in alcuni punti ({ label: "like_yes"; m: "I am happy you like my game!"; next: "like_end" },)dovrebbe avere un ',' non un ';'. Inoltre, come si chiama esattamente la cosa tra le parentesi graffe? È un oggetto all'interno dell'array? se volessi maggiori informazioni su come utilizzare questo cosa avrei cercato?
The Silent Cartographer:

Sì, {...}è un oggetto. In JavaScript, l'oggetto è un array associativo di valore-chiave (con alcune funzionalità extra, non utilizzato in questo esempio), simile all'array in PHP o Map in Java. Per ulteriori informazioni, consultare gli articoli di Wikipedia su JavaScript e ECMAScript e la documentazione collegata da lì, in particolare la documentazione ufficiale ECMAScript.
Viliam Búr,

1
a proposito, la struttura dei dati che raccomanda qui è fondamentalmente JSON. Personalmente consiglierei di andare fino a JSON (per lo più aggiungi parentesi graffe e un "albero": intorno all'intera cosa; come {"albero": [etc]}) e quindi puoi memorizzare i tuoi alberi di dialogo in file esterni. Mettere i tuoi dati in file esterni caricati dal tuo gioco è molto più flessibile e modulare (quindi perché questo approccio è una buona pratica).
jhocking del

5

Ti consiglierei di creare una serie di eventi di dialogo. Ogni evento è un oggetto contenente il testo che dice l'NPC e una serie di possibili risposte del giocatore, che a loro volta sono oggetti con un testo di risposta e l'indice dell'evento che segue questa risposta.

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",

...

2

Dovresti usare un approccio diverso. JavaScript supporta array e oggetti, quindi perché non usarne uno per voce, risparmiando tutta la suddivisione e rendendo anche il testo effettivo più facile da modificare / leggere?

Se vuoi, puoi dare un'occhiata ad un prototipo che ho realizzato in poche ore per # 1gam . La fonte può essere utilizzata gratuitamente con GPLv3 (sto benissimo se non ti attieni alla GPL, se la stai solo usando come fonte di ispirazione. Ma fammi sapere una volta che il gioco è finito.). Non aspettarti una scrittura fantastica o qualcosa del genere. ;)

Per dare una breve spiegazione su come funziona il codice, ignorando le cose dell'animazione CSS e cose del genere:

  • var data contiene essenzialmente l'intera storia con tutte le possibili scelte, ecc.
  • Ogni "posizione" (o pagina / voce) è identificata da un ID. Il primo ID nell'elenco è start, il secondo cwait, ecc.
  • Ogni posizione contiene due elementi obbligatori: una didascalia e il testo effettivo. I collegamenti per le decisioni sono scritti in alcuni semplici markup che prendono la forma [target location:display text].
  • Tutta la "magia" sta accadendo dentro navigate(): questa funzione rende i link Markup cliccabili. È un po 'più lungo, perché sto anche gestendo del testo statico per Dead Ends lì. La parte importante sono le prime due chiamate a replace().
  • Le ultime voci opzionali definiscono nuovi colori di sfondo da fondere, a sostegno dell'umore generale del gioco.
  • Invece di definire questi colori potresti anche aggiungere collegamenti che puntano ad altre posizioni (nota che questo non è gestito dal mio codice; è solo una idea per dimostrarlo):

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.