2012-12-14

How to Create Custom Macros in Twine

This post is a technical article for the Twine hypertext authoring system. I will assume a passing knowledge of Twine/Tweecode and an intermediate knowledge of Javascript. This tutorial applies to version 1.3.5 of Twine - and even further still I assume the latest betas.

There is a bug in the current v1.3.5 Sugarcane and Jonah story formats; custom macros will not work during the display of the "Start" passage. The Responsive story format does not have this problem.

I'll start with a simple macro example then explain it line by line. Put this code into it's own passage. The passage title can be anything. Give the passage the tag "script"

try {
  version.extensions['macrodemoMacro'] = { 
    major:1, minor:0, revision:0 
  };
  macros['macrodemo'] = {
    handler: function(place, macroName, params, parser) {
      new Wikifier(place, "Hello World!");
    },
    init: function() { },
  };
} catch(e) {
  throwError(place,"macrodemo Setup Error: "+e.message); 
}

Let's go through the code a piece at a time (not necessarily in written order).

try {
  ...
} catch(e) {
  throwError(place,"macrodemo Setup Error: "+e.message); 
}

This code sets up basic Javascript exception handling to let you know via a popup dialog when there is a problem in the macro code. Custom macros are inserted into the page at runtime so they can be difficult to debug. This exception handler makes things a bit easier. Technically this is optional, but trust me, it's worth adding. Just change the "macrodemo" text to the name of your macro so you can tell multiple custom macros apart.

  version.extensions['macrodemoMacro'] = { 
    major:1, minor:0, revision:0 
  };

This statement registers the macro with an extensions array. This is totally optional and appears to currently have no effect. If you are making a macro for re-use in many stories then it's a good idea to store version information so why not do it here. Maybe one day it'll be useful. I've taken the macro name and appended "Macro" just to be consistent with the way Twine's in built macros are registered.

  macros['macrodemo'] = {
  ...
  };

This creates the javascript object that holds the custom macro. The macro name in this case is "macrodemo" and you should change this to suit. The title of a macro should not contain spaces, double quotes or any characters that might confuse twine. There are two required functions and you can also add any other macro related variables inside the object.

    init: function() { },

This function is called when Twine is setting up the macro object when the story is started. This is where you should also initialise any variables because init() is called whenever the story is restarted. In many cases an empty function (as above) works just fine. There is no guarantee which order init() functions will be called, except that generally the inbuilt macros have their init() method called first.

    handler: function(place, macroName, params, parser) {
      new Wikifier(place, "Hello World!");
    },

This is the actual code that is executed when your macro is called. A macro is passed four parameters, the most important of which are "place" (an HTMLContainer where text is currently being output to) and "params" (any parameters passed to the macro). Parameters are split into an array on space characters except within "double quotes". The macroName parameter is self-explanatory. The parser gives a reference to the current Tiddlywiki parser object - which is useful only for some advanced cases; e.g. if you were writing a macro that has a corresponding end macro: see the inbuilt if, else and endif macros or my while loop macro for examples.

The example code here shows how to do simple text output which is added in place of the macro call. Most Twine macros will want to do something similar to this.

Once the macro is in place then somewhere in your Twine story go:

Custom macro: <<macrodemo>> 

Rebuild the story and you should see Hello World! Congratulations you have written your first macro.

BUG: Backslash characters do not encode / decode correctly from the passage store. Use a workaround such as: String.fromCharCode(92).

TIP: Since the latest betas support multiple source files via StoryIncludes, I like to put macros into separate text (.twee) files just to make organisation and reuse easier.

Macros are a powerful way of adding functionality to your Twine stories. The full power of Javascript is available to you. If you make a good macro then please share.

2 comments:

  1. Hi want to ask

    CSS! How does one javascriptate CSS in a twine.

    and if so.... so if I use state.history[0].variables["customvariables"] does that mean "customvariables" is the name of the variable i gave it in twine?

    Thanks so much

    ReplyDelete
    Replies
    1. The easiest way to add CSS in twine is by using a passage with the "stylesheet" tag. You can write CSS directly there.
      But going further:
      You can use standard javascript css manipulation to change css if you wish. I tend to use jQuery to provide good cross-platform capability when I do this.
      It is possible to use the <html>do stuff</html> syntax and print a new <script> dom object and/or localised style="" attributes. e.g.:
      new Wikifier(place, '<html><span class="cc1">Test1</span> <span style="color:#ff0033;">Test2</span></html>');
      The first span would use the .cc1 css (assuming you have defined it in a "stylesheet" passage. This is probably the best way of doing custom css.
      The second span would work, but you'd usually not want to do it this way; think of making like easier for your future self.

      Second question:
      Correct: $customvariables would be the name of the variable given in twine. So, if you have a twine variable called $hasdog then in js it is:
      state.history[0].variables["hasdog"]

      Delete