11
\$\begingroup\$

I'm making a very basic visual novel type of game in JavaScript. I'm a beginner, so I'm just doing this for fun and learning, and due to bad planning I have run into a bit of a problem when you get to a branch in dialogue.

Currently I hold the script for the game in a string variable and I break up each scene with a tag such as "#~" into smaller arrays so that the game script looks like this:

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

This works great for linear stuff, but how should I handle a branch in the dialogue tree? This method seems very complicated, as I would have to know exactly how long each path is and then if I ever needed to change anything it would be a headache.

How can I do this in a simpler way? I'm trying to stick to vanilla JavaScript as I'd like the game to work with Web Run Time.

\$\endgroup\$
3
  • \$\begingroup\$ This video can give you some ideas: youtube.com/watch?v=XM2t5H7kY6Y \$\endgroup\$ Commented Apr 22, 2013 at 3:24
  • \$\begingroup\$ I recently had to develop something for this using Node, and opted for a very basic text file structure. You can see the resulting code and text format at: github.com/scottbw/dialoguejs The code is GPL, feel free to use it. I'm sure it won't be hard to adapt for a non-Node JS game - replace the "fs.load()" part with Ajax. \$\endgroup\$ Commented Nov 28, 2013 at 13:20
  • \$\begingroup\$ Check out Ink, a branching story scripting language, developed by Inkle Studio. There are various programmatic Ink integrations (Java, Javascript, C#) and many 3rd party resources. Ink has been used in many commercial games, too. Finally, there's a desktop editor, Inky that can syntax check and 'play' your branching dialogues. \$\endgroup\$
    – Big Rich
    Commented Nov 1, 2019 at 10:26

3 Answers 3

10
\$\begingroup\$

Philipp's answer already shows the right direction. I just think the data structure is needlessly verbose. Shorter texts would be easier to write and read.

Even if shorter texts would make the algorithm a bit more complex, that's worth doing, because you only write the algorithm once, but most of your time will be spent writing and maintaining the story. Therefore optimize for making easier the part you will spend most time doing.

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

Some explanations for this design:

The whole story is written in one array. You don't have to provide numbers, they are provided automatically by the array syntax: the first item has index 0, the next one has index 1, etc.

In most cases, it is not necessary to write the number of the following step. I assume that most lines of text are not branches. Let's make "the next step is the following item" a default assumption, and only make notes when it is otherwise.

For jumps, use labels, not numbers. Then, if you later add or remove a few lines, the logic of the story will be preserved, and you don't have to adjust the numbers.

Find a reasonable compromise between clarity and shortness. For example, I suggest to write "m" instead of "message", because that will be the most frequently used command ever, so making it short will make the text more legible. But there is no need to shorten the remaining keywords. (However, do as you wish. The important thing is to make it most legible for you. Alternatively, you could support both "m" and "message" as valid keywords.)

The algorithm for the game should be something like this:

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

    }
  }
}

By the way, these ideas were inspired by Ren'Py, which is not exactly what you want (not JavaScript, not web), but could give you some cool ideas anyway.

\$\endgroup\$
4
  • \$\begingroup\$ Thank you for the in depth explanation, I was not aware that arrays could work the way you and Philipp showed, I thought they could only hold Strings or numbers. \$\endgroup\$ Commented Apr 23, 2013 at 1:51
  • 1
    \$\begingroup\$ I've been trying to implement your solution and it works fairly well, although I think in some places ({ label: "like_yes"; m: "I am happy you like my game!"; next: "like_end" },) should have a ',' not a ';'. Also what exactly is the thing in the curly braces called? Is that an object within the array? if I wanted more info on how to use this what would I search for? \$\endgroup\$ Commented Apr 27, 2013 at 21:08
  • \$\begingroup\$ Yes, {...} is an object. In JavaScript, object is a key-value associative array (with some extra functionality, not used in this example), similar to array in PHP, or Map in Java. For more info, see Wikipedia articles on JavaScript and ECMAScript, and the documentation linked from there, especially the official ECMAScript documentation. \$\endgroup\$ Commented Apr 29, 2013 at 14:07
  • 1
    \$\begingroup\$ btw note that the data structure he recommends here is basically JSON. Personally I'd recommend going all the way to JSON (mostly add curly brackets and a "tree": around the entire thing; like {"tree":[etc]} ) and then you can store your dialogue trees in external files. Putting your data into external files that your game loads is much more flexible and modular (thus why that approach is a best-practice). \$\endgroup\$
    – jhocking
    Commented Nov 28, 2013 at 15:28
5
\$\begingroup\$

I would recommend you to create an array of dialog events. Each event is an object containing the text the NPC says and an array of possible player responses, which in turn are objects with a response text and the index of the event which follows on this response.

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

...
\$\endgroup\$
2
\$\begingroup\$

You should use a different approach. JavaScript supports arrays and objects, so why not using one per entry, saving you all the splitting and also making the actual text easier to edit/read?

If you want, you can have a look at some prototype I've made during a few hours for #1gam. The source is free to be used under GPLv3 (I'm perfectly fine if you don't stick to the GPL, if you're just using it for inspiration. But let me know once your game is finished.). Just don't expect awesome writing or anything like that. ;)

To give a short explanation on how the code works, ignoring the CSS animation things and stuff like that:

  • var data essentially contains the whole story with all possible choices, etc.
  • Every "location" (or page/entry) is identified by an ID. The first ID in the list is start, the second cwait, etc.
  • Every location contains two mandatory elements: A caption as well as the actual text. Links for decisions are written in some simple markup taking the form [target location:display text].
  • The whole "magic" is Happening inside navigate(): This function makes the Markup links clicky. It's a bit longer, because I'm also handling some static text for dead Ends there. The important part are the first two calls to replace().
  • The optional last entries define new background colors to blend to, supporting the overall mood of the game.
  • Instead of defining these colors you could as well add links pointing to other locations (note this is not handled by my code; it's just some idea to demonstrate this):

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

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .