¿Cómo implemento el diálogo de ramificación en javascript?


11

Estoy haciendo un tipo de juego de novela visual muy básico en JavaScript. Soy un principiante, así que solo hago esto por diversión y aprendizaje, y debido a una mala planificación, me he encontrado con un pequeño problema cuando llegas a una rama en el diálogo.

Actualmente mantengo el script para el juego en una variable de cadena y divido cada escena con una etiqueta como "# ~" en arreglos más pequeños para que el script del juego se vea así:

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

Esto funciona muy bien para cosas lineales, pero ¿cómo debo manejar una rama en el árbol de diálogo? Este método parece muy complicado, ya que tendría que saber exactamente cuánto dura cada ruta y, si alguna vez necesito cambiar algo, sería un dolor de cabeza.

¿Cómo puedo hacer esto de una manera más simple? Estoy tratando de mantener JavaScript vainilla ya que me gustaría que el juego funcione con Web Run Time.


Este video puede darle algunas ideas: youtube.com/watch?v=XM2t5H7kY6Y
JCM

Recientemente tuve que desarrollar algo para esto usando Node, y opté por una estructura de archivo de texto muy básica. Puede ver el código resultante y el formato de texto en: github.com/scottbw/dialoguejs. El código es GPL, no dude en usarlo. Estoy seguro de que no será difícil adaptarse para un juego que no sea Node JS: reemplace la parte "fs.load ()" con Ajax.
Scott Wilson el

Echa un vistazo a Ink , un lenguaje de guiones de historia ramificado, desarrollado por Inkle Studio . Existen diversas integraciones programáticas de Ink (Java, Javascript, C #) y muchos recursos de terceros . La tinta también se ha utilizado en muchos juegos comerciales. Finalmente, hay un editor de escritorio, Inky , que puede verificar la sintaxis y "reproducir" sus diálogos de ramificación.
Big Rich el

Respuestas:


8

La respuesta de Philipp ya muestra la dirección correcta. Simplemente creo que la estructura de datos es innecesariamente detallada. Los textos más cortos serían más fáciles de escribir y leer.

Incluso si los textos más cortos harían que el algoritmo sea un poco más complejo, vale la pena hacerlo, porque solo escribe el algoritmo una vez, pero la mayor parte de su tiempo se dedicará a escribir y mantener la historia. Por lo tanto, optimice para facilitar la parte que pasará más tiempo haciendo.

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

Algunas explicaciones para este diseño:

Toda la historia está escrita en una matriz. No tiene que proporcionar números, la sintaxis de la matriz los proporciona automáticamente: el primer elemento tiene el índice 0, el siguiente tiene el índice 1, etc.

En la mayoría de los casos, no es necesario escribir el número del siguiente paso. Supongo que la mayoría de las líneas de texto no son ramas. Hagamos que "el siguiente paso sea el siguiente elemento" sea una suposición predeterminada, y solo tomemos notas cuando no sea así.

Para saltos, use etiquetas , no números. Luego, si luego agrega o elimina algunas líneas, se conservará la lógica de la historia y no tendrá que ajustar los números.

Encuentre un compromiso razonable entre claridad y brevedad. Por ejemplo, sugiero escribir "m" en lugar de "mensaje", ya que ese será el comando utilizado con más frecuencia, por lo que acortarlo hará que el texto sea más legible. Pero no hay necesidad de acortar las palabras clave restantes. (Sin embargo, haga lo que desee. Lo importante es que sea más legible para usted . Alternativamente, puede admitir "m" y "mensaje" como palabras clave válidas).

El algoritmo para el juego debería ser algo como esto:

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

    }
  }
}

Por cierto, estas ideas fueron inspiradas por Ren'Py , que no es exactamente lo que quieres (ni JavaScript, ni web), pero de todos modos podría darte algunas ideas geniales.


Gracias por la explicación en profundidad, no sabía que las matrices podían funcionar de la manera en que usted y Philipp mostraron, pensé que solo podían contener cadenas o números.
El cartógrafo silencioso

1
He estado tratando de implementar su solución y funciona bastante bien, aunque creo que en algunos lugares ({ label: "like_yes"; m: "I am happy you like my game!"; next: "like_end" },)debería tener un ',' no un ';'. Además, ¿cómo se llama exactamente la cosa en las llaves? ¿Es eso un objeto dentro de la matriz? si quisiera más información sobre cómo usar esto, ¿qué buscaría?
El cartógrafo silencioso

Si, {...}es un objeto. En JavaScript, el objeto es una matriz asociativa de valor clave (con alguna funcionalidad adicional, no utilizada en este ejemplo), similar a la matriz en PHP o Mapa en Java. Para obtener más información, consulte los artículos de Wikipedia sobre JavaScript y ECMAScript, y la documentación vinculada desde allí, especialmente la documentación oficial de ECMAScript.
Viliam Búr

1
Por cierto, tenga en cuenta que la estructura de datos que recomienda aquí es básicamente JSON. Personalmente, recomendaría ir hasta JSON (en su mayoría agregue llaves y un "árbol": alrededor de todo; como {"árbol": [etc]}) y luego puede almacenar sus árboles de diálogo en archivos externos. Poner sus datos en archivos externos que carga su juego es mucho más flexible y modular (por lo tanto, ese enfoque es una mejor práctica).
jhocking

5

Le recomendaría que cree una serie de eventos de diálogo. Cada evento es un objeto que contiene el texto que dice el NPC y una serie de posibles respuestas del jugador, que a su vez son objetos con un texto de respuesta y el índice del evento que sigue a esta respuesta.

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

Deberías usar un enfoque diferente. JavaScript admite matrices y objetos, entonces, ¿por qué no usar uno por entrada, ahorrándote toda la división y haciendo que el texto real sea más fácil de editar / leer?

Si lo desea, puede echar un vistazo a algunos prototipos que he hecho durante unas horas para # 1gam . La fuente es gratuita para usarse bajo GPLv3 (estoy perfectamente bien si no te apegas a la GPL, si solo la estás usando como inspiración. Pero avísame una vez que tu juego haya terminado). Simplemente no esperes una escritura increíble ni nada de eso. ;)

Para dar una breve explicación sobre cómo funciona el código, ignorando las cosas de animación CSS y cosas por el estilo:

  • var data esencialmente contiene toda la historia con todas las opciones posibles, etc.
  • Cada "ubicación" (o página / entrada) se identifica mediante una ID. La primera ID de la lista es start, la segunda cwait, etc.
  • Cada ubicación contiene dos elementos obligatorios: un título y el texto real. Los enlaces para las decisiones se escriben en un marcado simple que toma la forma [target location:display text].
  • Toda la "magia" está sucediendo en el interior navigate(): esta función hace que los enlaces de marcado sean clicky. Es un poco más largo, porque también estoy manejando algo de texto estático para puntos muertos allí. La parte importante son las dos primeras llamadas a replace().
  • Las últimas entradas opcionales definen nuevos colores de fondo para combinar, lo que respalda el estado general del juego.
  • En lugar de definir estos colores, también podría agregar enlaces que apuntan a otras ubicaciones (tenga en cuenta que mi código no maneja esto; es solo una idea para demostrar esto):

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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.