JavaScript Visual Novel Engine

  1. About the Engine
  2. The Big Picture
  3. The HTML file
  4. The novel file

About the Engine

In March of 2010, I wrote a visual novel (VN) to explain hypothesis testing to my Psychology Research Methods class. I used the Ren'py visual novel engine, and it worked great. Grant Paton-Simpson, the author of SOFA - Statistics Open For All liked the content, but said he would prefer if it were web-based instead of having to download an executable file.

After some experimentation, I have come up with a visual novel engine written in JavaScript. It is not meant as a replacement for Ren'py, nor is it intended to do everything that Ren'py does. I have tried to make it general enough to do a variety of simple VNs, but my main purpose was to adapt a single visual novel to the web. If it serves your purposes too, that’s great.

Although the engine was inspired by Ren'py, it is not a copy. Just as Ren'py is written in Python and “plays well” with that language, this engine was written to work in an HTML/CSS/JavaScript environment, and its notation is closely linked to that environment.

The Big Picture

From a conceptual level, a visual novel takes place in two screen areas: the tableau, where all the images appear, and the dialog area, where the characters’ speech appears. The novel is populated by actors, which can either be characters (people, animals, magical beings, etc.), text blocks (used to display text that isn’t someone’s speech in the tableau), or menu items, which let you take different paths through the novel.

The novel has a script that lets you specify which characters appear at what times, what they say, and how the menu items let the reader take a path through the novel. This is not the same as your <script> element that lets you put JavaScript into your HTML file. Speaking of that, though, let’s look at:

The HTML File

This tutorial will create a sample novel in file exampleNovel.html. It is not a particularly interesting visual novel (in fact, its plot line is non-existent); rather, it’s designed to be an example of, well, an example.

In the <head> of your document, you must include the visual novel JavaScript file, with markup like this. In this example, the JavaScript is in the same directory as the HTML file. For this tutorial, all the images the novel uses will be placed in a subdirectory named images, and audio will be placed in a subdirectory named audio. There’s no law that says you must do this, but it’s a good idea to keep from having one directory with an unmanageable number of files in it.

<script type="text/javascript" src="visual_novel.js"></script>

You will also include a <script> element that refers to the JavaScript that describes your novel. For this example, the file will be named exampleNovel.js, and it will reside in the same directory as the HTML file.

<script type="text/javascript" src="exampleNovel.js"></script>

Setting up the <body>

In the <body> of the document, you set up the tableau and dialog areas, as well as an area for the background image of the tableau. Here is a sample setup. The line numbers at the left are for the explanation that follows; don’t enter them into your markup.

<body onload="initNovel(800, 600);">
<div id="novelDiv">
    <div style="position: absolute;
        left:0; top: 0; z-index:-1">
        <img id="background0" src="images/empty.png">
    </div>
    <div style="position: absolute;
        left:0; top: 0; z-index:-1">
        <img id="background1" src="images/empty.png">
10     </div>
11     <div id="dialogDiv" style="z-index:100">
12     </div>
13 </div>
14 <div style="text-align:center; margin-top: 0.5em;">
15 <input type="button" value="Back to Beginning"
16     onclick="initNovel(800, 450)"/>
17 </div>
Line 1
When the document finishes loading, initialize the novel. The numbers in the parentheses give the width and height of the tableau in pixels. In this example, the tableau is 800 pixels wide and 450 pixels high.
Line 2
This is the tableau. Its id must be "novelDiv".
Lines 3-4
In order to allow background images to fade in or out, you must include this <div> The position, left, and top make the background image appear at the upper left of the tableau; the z-index: -1 ensures that the background image appears behind the characters.
Line 5
The first background image must have an id="background0". In this case, the initial background is a totally transparent image.
Lines 7-10
The second backgound image; this is required in order to allow a dissolve, where one image fades in as the other fades out.
Lines 11-12
This is the dialog area, whose id must be "dialogDiv". If your <div id="dialogDiv"> is inside the <div id="novelDiv"> (as it is in the sample novel, “The Question”), you must add style="z-index:100" to the dialogDiv to ensure that the dialog always appears above the characters.
Lines 14-17
These are optional. It’s just a good idea to allow readers to restart the novel. The onclick re-initializes the novel, just as you did in line 1.

Styling the novel

At this point, you can use CSS to attach styles to the tableau, dialog area, and menu items. Here is a sample. First, the tableau. The width and height must match the numbers you specified in the call to initNovel(), and the position: relative is required in order to place the image properly within in the tableau. The text-align: center on the body is necessary in order for the tableau to be centered horizontally on the screen when using Internet Explorer.

body {
    text-align: center;
}

#novelDiv {
    width: 800px;
    height: 600px;
    border: 1px solid black;
    margin-left: auto;
    margin-right: auto;
    position: relative;
    text-align: center;
}

The dialog, in this case, is within the tableau rather than below it. The padding plus the width add up to 800 pixels so that the borders coincide.

#dialogDiv {
    font-family: "Deja Vu Sans", Helvetica, Arial, sans-serif;
    font-size: 20px;
    position: absolute;
    width: 790px;
    height: 140px;
    top: 450px;
    left: 0;
    padding: 5px;
    background-color: #3d372c;
    color: white;
    text-align: left;
    filter: alpha(opacity=75);
    opacity: 0.75;
}

Finally, add styling for menu items. Of course, these sizes and colors are just an example; you may change them to reflect the look you want for your novel.

.menuItem {
    font-family: "Deja Vu Sans", Helvetica, Arial, sans-serif;
    font-size: 18px;
    background-color: #0050a0;
    color: white;
    text-align: center;
    width: 75%;
    margin: 0.5em auto;
    padding: 0.5em 3em;
    -moz-border-radius: 15px;
    border-radius: 15px;
}

.menuItem:hover {
    background-color: #ff8c00;
}

The novel file

Now, turn your attention to the exampleNovel.js file. This is where you will describe the novel’s actors and write the script that puts them through their paces.

Preloading Images

If you have a lot of fairly large images, you want them to be available as soon as your novel needs them. You can start a preloading process to have the browser retrieve the images while the rest of the page loads. Create an array with the names of the files you want to preload. Here is an example. Notice that the images/ directory isn’t in the file names; that saves some typing. (See the pictures that this example preloads.)

var preload = [
  "hills1.jpg", "hills2.jpg",
  "amazed1.png", "amazed2.png",
  "cool1.png", "cool2.png",
  "hand_waving1.png", "hand_waving2.png",
  "laughing1.png", "laughing2.png",
  "loving1.png", "loving2.png",
  "simple1.png", "simple2.png",
  "smiling1.png", "smiling2.png",
  "sad1.png", "sad2.png",
  "silence1.png", "silence2.png",
  "angry1.png"
];

Now add this code to do the actual preloading; here is where you will use the name of the path to the images directory.

var preloadObj = new Array(preload.length);
for (var i = 0; i < preload.length; i++)
{
    preloadObj[i] = new Image();
    preloadObj[i].src = "images/" + preload[i];
}

Declaring Variables

The next part of the JavaScript files declares all the variables you will need for the script itself, your characters, textblocks, and (optionally) positions that your script will use. The next line is absolutely essential; it is the variable that will hold your script, and it must be named script.

var script;

Now, reserve variables for your main characters. In this tutorial, there are two main characters named Tyler (a tourist) and Gavin (a guide), along with a narrator. There will also be two pseudo-characters: one used to display photos or diagrams, and a text block used to display text in the tableau.

The following example also declares variables used to specify the position of characters. Tyler and Gavin will appear at the lower left and lower right side of the window, and the diagram will appear in the upper center. Although you can declare multiple variables on one line in JavaScript, code is easier to read and maintain if you put one variable per line. You can use any variable names you want for your characters. It’s best if they resemble the characters’ actual names, but you can keep them short to save yourself some typing.

var tyler;
var gavin;
var n; // short for "narrator"
var photo;
var textBlock;

var leftSide;
var rightSide;
var upperCenter;

Preparing the Novel

Actual initialization of the variables takes place in a function which must be named prepareNovel. The first two lines will give the path to the sub-folders where your images and audio live. In this case, there’s no audio, so the audioPath is set to "". If you aren’t using a sub-folder for your images or audio, you should also set the path to "".

function prepareNovel()
{
    novel.imagePath = "images/"; // path to your image directory
    novel.audioPath = ""; // path to your audio directory

Continuing the function, initialize the characters. A character is initialized by giving the name and the color in which his name appears when he speaks in the dialog area. The name must be in quote marks and can include blanks. The color may be in any format that would be acceptable in CSS. Because the narrator doesn’t have a name, no name will appear when he “speaks,” so you don’t have to specify a color.

    gavin = new Character("Gavin", {color: "rgb(64, 204, 64)"});
    tyler = new Character("Tyler", {color: "#ffff00"});
    n = new Character("");

Specifying Positions

To specify a position on the screen, you give four numbers:

You may omit the last two numbers, in which case they will be presumed to be zero. Warning: this may change to a default of 0.5 in later versions, depending upon feedback from authors who use the novel engine.

An anchor tells how far to offset the top or left point of an image. Consider these three positions:

place1 = new Position(300, 400, 0, 0);
place2 = new Position(300, 400, 0.5, 0.5);
place3 = new Position(300, 400, 1, 0);

Let’s say one of your characters has an image that is 200 pixels wide and 150 pixels tall. If you display the character at place1, the upper left corner of the image will be at coordinate (300, 400) in the tableau, because the anchors are zero; there is no offset.

If you display the image at place2, the left coordinate will be shifted left by one-half the image width, and the top coordinate will be shifted up by one-half the image height. That will make the upper left corner of the image appear at (300 - (200 * 0.5), 400 - (150 * 0.5)), or (200, 325). In effect, this will put the center of the picture at (300, 400).

If you display the image at place3, the left coordinate will be shifted left the entire image width, and the top coordinate will be not change. That will make the upper left coordinate of the image appear at (100, 400), effectively putting the right edge of the picture at x-coordinate 300.

Here are the four positions that the example novel uses.

    leftSide = new Position(0, .75, 0, 1);
    rightSide = new Position(800, 450, 1, 1);
    upperCenter = new Position(0.5, 0.3, 0.5, 0.5);
    rightTop = new Position(0.9, 0.1, 1, 0);

The leftSide position will place images so that their left side is at the left of the screen and their bottom side is three-fourths of the way down the tableau.

The rightSide position will place images so that their right and bottom side are at location (800, 450), which is all the way at the right and three-fourths of the way down the tableau.

The upperCenter position will place images so that their horizontal centers are at the center of the tableau, and their vertical centers about three-tenths down the screen.

See if you can figure out where an image at rightTop would appear.

Warning: because JavaScript does not distinguish between floating point and integers, a position of (1, 1, 0, 0) will be taken to mean 100% of the width and 100% of the height of the tableau, not an absolute location one pixel to the right and one pixel below the upper left of the tableau. That means you will not be able to place an image at absolute position (1, 1).

Now that you have specified a position, you can use it to create the photo character and set its initial position. The photo doesn’t speak, so it doesn't need a name or a color.

    photo = new Character("", {position: upperCenter});

The last item to define in this example is a text block. It doesn’t really need a name either, but I put one in just as an example. If you don’t want to name your text blocks, just put in the null string "".

    textArea = new TextBlock("myText");

The Script

At last! You can finally start entering the script that tells the characters what to do. The following script, which consists of pairs of items in a JavaScript array, establishes a scene and has the narrator say something. In this script, the little arrow () means that the line is continued. The line numbers at the left are for reference.

script = [
    label, "start",
    scene, "hills1.jpg",
    n, "It&rsquo;s a beautiful day at Montgomery Hill Park,
    where we meet Tyler the tourist, who is looking for Gavin the Guide."
];
Line 2
The label "start" indicates the place where the novel begins.
Line 3
The scene command clears out any characters that might be on screen and sets the background to the filename given as the second item ("hills1.jpg" in this case).
Line 4
The first item is the character name; in this case, the narrator. The second item gives the text the character is supposed to say. The text is HTML, and can contain any valid HTML. In this example, &rsquo; gives a curly right single quote (’), which looks better at large sizes than a straight-up-and-down quote ('). (See an example)

Now add the following lines to make the characters appear and speak (they are in bold). Note that I had to add a comma at the end of line 4 in order to avoid a JavaScript error. This is the most common cause of errors when you write your script. Always remember to add a comma to the last line in the script before adding new lines!

 1script = [ 
 2    label, "start",
 3    scene, "hills1.jpg",
 4    n, "It&rsquo;s a beautiful day at Montgomery Hill Park,
      where we meet Tyler the tourist, who is looking for Gavin the Guide.",
 5    tyler, {image: "worried1.png", position: leftSide},
 6    tyler, "Hey, Gavin, where are you?"
 7    gavin, {position: rightTop, image: "simple2.png"},
 8    tyler, {image: "simple1.png"},
 9    gavin, "Up here in the hills, Tyler!",
10    tyler, "Please come down here. It&rsquo;s easier to talk with you.",
11    gavin, {position: rightSide},
12    gavin, "OK. That better?",
13    tyler, "Much better, thanks!"
14];
Line 5
This line makes the character tyler appear with the given image at the position you specify.
Line 6
When a character’s name is followed by a string, that means that it is “speech” that should appear in the dialog area.
Line 7
This line makes character gavin appear at the specified position with the given image. You can specify image and position in any order you want. Ordinarily, you would pick one order and stay with it throughout your script.
Line 8
Since only an image is specified, Tyler’s image changes, but not his position.
Lines 9-10
More dialog by the characters.
Line 11
The gavin character’s position has changed, but since no image has been specified, it stays the same as it was.

Choosing from a Menu

At this point, the story has a branch; Tyler can choose to either see dangerous or not-so-dangerous animals. Whenever you have a choice point in the story, you add a menu, as shown in the following part of the script.

 1   tyler, "Much better, thanks!",
 2    
 3    label, "menu1",
 4    menu, [
 5        "So, what would you like to see?",
 6        "Dangerous animals", [jump, "wild"],
 7        "Not-so-dangerous animals", [jump, "tame"],
 8    ],
 9
10    label, "wild",
11    scene, "hills2.jpg",
12    /* script continues... */
Line 1
Continuation of the script. The comma at the end of the line is required when you add more lines.
Line 3
Although not required, it’s a good idea to put a label on all your choice points so that you can jump back to a menu if you need to.
Line 4
A menu is contained in a JavaScript array (which is enclosed in square brackets).
Line 5
The first item in the array is the prompt for the reader. This prompt will appear, centered, in the dialog area.
Lines 6-7
Specify each choice with the text for that choice, followed by an array telling the novel engine what to do when the reader chooses it. Line 6 will make a button with the text “Dangerous animals” appear. If the reader clicks it, she will go to the section of the novel that has a label of "wild".

Similarly, line 7 makes a button with the text “Not-so-dangerous animals” appear. If the reader clicks it, she will go to the section of the novel that has a label of "tame".

In this example, there’s only one thing to do as the result of a click—a simple jump. It is possible to do more than one thing as the result of a click on a menu item. This is covered in the advanced features section of the tutorial.

Line 8
Since the script continues, remember to put a comma after the closing square bracket (the opening one was on line 4).

It is also possible to display a character’s image and have the character speak with one command. Just add a say property in addition to specifying the image. The following two sequences do the same thing:

// sequence 1:
tyler, {image: "loving1.png"},
tyler, "Aww, that’s really nice.",

// sequence 2:
tyler, {image: "loving1.png", say: "Aww, that’s really nice."},

With these simple commands, you can put together a reasonable visual novel. The full example is here. The only feature that it uses that has not been explained here is the TextBlock, which is discussed in the advanced features.