Skip to content
A framework for creating visual novels
JavaScript
Find file
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.
dist
src update to 2.3
.gitattributes
.gitignore
README.md
bower.json
gruntfile.js
package.json
test.html

README.md

NovelScript.js

Creating a visual novel is hard if you don't have the right framework. Most of the frameworks I have found are incredibly opinionated and don't allow for extendability.

What did you really need?

Here's a list of things you really need.

  1. A couple of elements inside of a target element:
    • A Canvas (so you can draw some images)
    • An invisible container for images for use with ctx.drawImage(imgELement, ...)
  2. An image indexing function
  3. A custom Event Engine that has default events
    • Overridable default behavior
    • A few standard events
  4. Expose the native DOM API in a useful way
  5. Plugins using a documented Object Composition API

This library covers all of these things and provides a couple of opinionated plugins to actually create your Visual Novel API.

Getting Started

var myNovel = NovelScript(options);

It's really this easy to create a custom Visual Novel. Any plugins that you have included on your page will automatically generate the required objects you need to tell your story!

Required Options

options.images

"images": {
    "imageID": {//put image attributes for dom api here
        "class":"",
        "style": { //image style attributes go here
            "maxWidth":"400px",
            "maxHeight":"600px"
        }
    },
    "imageID2": {
    }
}

Make sure that you pass every object and put the unique DOM id for that image into the hash.

options.audio

"audio": {
    "audioID": {//once again put values for attributes of the _AUDIO ELEMENT HERE_ here
        // this is necesarry for the 'sounds:loaded' event and if you want to auto load the audio
        "preload": "auto",
        "loop": "loop" // probably a good idea for background music
    }
}

Feel free to set audio to false if it isn't necessary for your novel.

options.target

Should be a string. If an element is found with the specified id, it will inject the dom elements into the target. Otherwise everything will be placed inside the document.body.

options.draw

This can be set to false if you want to override the default draw functionality of NovelScript.

options.canvas

"canvas": {
    "width": "800px",
    "height": "600px",
    "id": "",
    "class": ""
}

Any of the default properties of the canvas go in this hash.

Any plugin can ask for default options, make sure to read their documentation to get a good gist on how to configure your novel.

Extending NovelScript?

The Wiki will have a bunch of tutorials on how to extend NovelScript.

An Element of Storage

Every NovelScript has a canvas, a canvas ctx, an invisible div containing images called the imageRepo, and an invisible div containing all the audio elements for playing called the soundRepo.

Relevant Parameters

options.target @String {Optional}

The target will be appended to the document.body by default, however, if a target is specified, it will grab the element with the specified id.

console.log(myNovel);
/* {
    'canvas': document.createElement('canvas'),
    'ctx': canvas.getContext('2d'),
    'imageRepo': document.createElement('div'),
    'soundRepo': document.createElement('div')
} */

Both repos are set by default to 'display: none;', but you can modify their style using the style API provided by your browser environment.

Events API

myNovel.on

@parameters [
    event @String {Required}, 
    handler @Function {Required}
]

This method adds a handler to the event namespace provided. The event doesn't have to exist for the handler to exist in the event namespace.

myNovel.once

@parameters [
    event @String {Required}, 
    handler @Function {Required}
]

This method provides a handler to be handled once before it is disposed.

myNovel.off

@parameters [
    event @String {Required}, 
    handler @Function {Required}
]

This method takes the prior handler and removes it from the event namespace.

myNovel.trigger

@parameters [
    event @String {Required}, 
    params @Array... {Optional [See event definitions]}
]

This particular method shoots all the functions to the stack to be handled asynchronously using setTimeout with the remaining parameters passed to the function calls. Please see the event definitions for information on what parameters are required for each event.

myNovel.triggerSynchronous

@parameters [
    event @String {Required}, 
    params @Array... {Optional [See event definitions]}
]

This particular method uses Function.prototype.apply to call each function in the specified namespace synchronously with the remaining parameters passed to the function calls. Please see the event definitions for information on what parameters are required for each event.

myNovel.default

@parameters [
    event @String {Required}, 
    handler @Function {Required}
]

Some functions must be called every time and are default behavior for events. In this case, pass a handler to overwrite any default functionality that NovelScript provides to you if desired functionality is different than provided.

Note: the default event will always trigger last in the queue. Do not override a native/documented event that comes with NovelScript unless you know what you are doing.

Drawing Images

The native DOM API for drawing images is nice, but sometimes there are a few use cases where the parameters are pretty straight forward.

Provided Functions

drawActor(img, x, y, width, height)

@parameters [
    img @ImageElement {Required}, 
    x @Number {Optional} default: 0,
    y @Number {Optional} default: 0, 
    width @Number {Optional} default: img.width, 
    height @Number {Optional} default: img.height
]

These parameters are very self explanatory. You are responsible for drawing the image onto the canvas yourself (unless your plugin will automatically render your actors for you.)

drawBg(img)

@parameters [
    img @ImageElement {Required}
]

The background is rendered at (0,0) and has the width/height of the canvas.

getStoredImages(id)

@parameters [
    id @String {Optional} default: (undefined)
]

This will either return an image, or every image if an id is specified.

myNovel.loadImages(images)

@parameters[
    images @Object {Required}
]

This method is responsible for injecting the image elements into the dom and setting up the elements to fire 'images:loaded' and 'image:load(:id)'. These events are fired when the images are loaded and are ready to use.

Notes on image drawing

Chances are you wanted to use this particular function to draw the image onto your canvas. If this is not desired behavior, feel free to override it yourself using the provided snippet using Composer.js.

Example of overriding drawActor:

This method directly overwrites the prototype directly and will be added before any of the closures are added.

NovelScript.motif({
    drawActor: function (image, x, y, width, height) {
        //You now have say on what gets drawn on the canvas using this 
    }
});

What if this isn't a prototypical method and should have some private variables outside of it's scope? Do this instead:

function yourScopeFunction (options) {
    'use strict';
    var self = this; // is your NovelScript object before it becomes NovelScript

    function loadImages (options) {
        //do some magic here
    }

    Composer.remix(self, {
        //expose your functions in this object literal
        'loadImages': loadImages
    });
}

var myEngineFactory = Composer.symphony(yourScopeFunction, NovelScript);

var myNovel = myEngineFactory();

//or...
NovelScript.phrase(function (options) {
    var self = this, privateVariable; //to functions declared here.
    this.loadImages = function loadImages(/*parameters*/) {
        return self; //load your images here and return self to maintain chainability
    };

    Composer.remix(self, {
        //expose your functions in this object literal
        'loadImages': loadImages
    });
});

Every function provided by NovelScript will check to see if the NovelScript prototype has been modified and be un-obtrusive on your function definitions. Otherwise the functions are simply mixed in.

Note: You cannot access properties that have not been created yet when using Composer.symphony if it is placed before NovelScript in the symphony chain.

Playing Audio

myNovel.play(id) myNovel.stop(id) myNovel.pause(id)

@parameters [
    id @String {Required}
]

Will play, stop, or pause the song specified by the ID.

myNovel.loadSounds(sounds)

@parameters[
    sounds @Object {Required}
]

This method is responsible for injecting the audio elements into the dom and setting up the elements to fire 'sounds:loaded' and 'audio:load(:id)'. These events are fired when the sounds are loaded and are ready to use.

Default Events

Image

image:load(:id) image:error(:id)

@parameters [
    img @ImageElement {Required}
]

If any named image items were added to the options.images namespace of the options hash, each specified item will be injected into the dom into the imageRepo. Upon loading, the captured element.onload event will trigger the image:load and image:load:id event synchronously and respectively passing the image element as the only parameter. If any native error occurs, the element.onerror event will trigger image:error and image:error:id synchronously and respectively. Note: its id was changed to match the id specified in the options.images hash.

images:loaded

@parameters [
    ImageElementHash @Object {Required}
]

Once every image is loaded, this event triggers with the image hash.s

Audio

audio:load(:id) audio:error(:id)

@parameters [
    audio @AudioElement {Required}
]

If any named audio items were added to the options.audio namespace of the options hash, each specified item will be injected into the dom into the soundRepo. Upon loading, the captured element.onload event will trigger the audio:load and audio:load:id event synchronously and respectively passing the audio element as the only parameter. If any native error occurs, the element.onerror event will trigger audio:error and audio:error:id synchronously and respectively. Note: its id was changed to match the id specified in the options.audio hash.

sounds:loaded

@parameters [
    AudioElementHash @Object {Required}
]

Calls when every Audio Element is loaded. With an object that contains every audio element referenced by its id specified in the options.audio hash.

audio:play(:id) audio:stop(:id) audio:pause(:id)

@parameters [
    audio @AudioElement {Required}
]

All of these events will be fired whenever play, stop, or pause is called on an audio element successfully. Note that the :id version will ALWAYS FIRE FIRST and both will fire asynchrnously.

Pages

page:next page:previous

@parameters [
]

These are the recommended event namespaces and they bother trigger page:change by default. You must handle these events and change the pages. These events must also be triggered from keyboard or button events. Again: You are responsible for handling and triggering these events. The NovelScript.currentPage property must be set before the event finishes firing every event handler.

page:change

@parameters [
    page @Page {Required}
]

If there is any code that should be handled before the dirty event is triggered, it must go here.

dirty

@parameters [
]

This event can be fired asynchronously, though it is recomended to be fired synchronously to prevent any lag the user must experience when experiencing your novel.

This event is an event that should be fired when the state of the screen needs to be refreshed to change the state of the NovelScript. It will fire the following events synchronously in this order. It is highly recomended that these portions of your application be seperated in closures and handled/labeled for modularity and clarity using the default event handler. If any aditional functions must be added they can be included using the NovelScript.on method.

  1. page:preDraw
  2. bg:draw
  3. actors:draw
  4. textbox:draw
  5. text:draw
  6. page:drawComplete

Note: If you wish to trigger events from these namespaces from a dirty, the events are suggested to be called using the NovelScript.triggerSynchronous method. This is because the lag between the events will be noticable and cause a choppy draw whenever the screen becomes updated. This is useful for button APIs that need to redraw the state of the novel whenever something happens.

The only event worth mentioning (that isn't self explainatory) is the actors:draw event.

actors:draw

@parameters [
]

This event is responsible for synchronously dispatching the drawing of your actors using the provided myNovel.ctx. You can acheive this by activating an event handler called actor:draw and pass to the event handler the actor object. If this is the method of choice, handle the drawing of an actor using myNovel.drawActor inside of a custom NovelScript.default actor:draw event. This is highly recommended but not required.

Extending NovelScript

Your plugin should look like this (see the Composer.js API for more details on handling Composer Objects):

~function (window, document, Composer, NovelScript) {
    'use strict';
    function Extension(options) {
        this; //NovelScript object
        options; //you can change options here (stuff has already begun loading anyway)
    }
    NovelScript.phrase(Extension);
    //expose another window variable here if you want, instruct your users to use Composer.symphony to use your extension 
} (window, document, Composer, NovelScript);
Something went wrong with that request. Please try again.