Making of: A New Christmas Carol (Part 2)

Our Adventures in Twine

Behind the Screens:
In this new series of blogs by the National Videogame Museum, we want to give you the inside track into how we build some of our award-winning programmes. In this first post, our Curator, Lex Roberts takes you through how they built our special Christmas workshop!


This is Part 2 of our A New Christmas Carol project making of guide. See Part 1 for an introduction to the project.

If you haven’t played the game, and you’re not very familiar with Twine, it might be a good idea to try playing it first, this section contains spoilers! And may require some familiarity with Twine.


Remember! Twine can be very simple, but with this post we’re going to dive into some of its advanced functionality.  If you’re new to Twine, you don’t need to use any of these functions to write your first game!

Twine comes in many flavours all with different purposes and for different abilities and stories. You can read more about Twine story formats here. Each format is slightly different and makes some things easier to do in one or the other. The default Twine format is called Harlowe, it’s simple to use, and difficult to go wrong. Having been avid Twine fans for many years, we’re most comfortable in SugarCube, which is an older style from an older version of Twine – it’s easier to customise and add in JavaScript code. However, as our workshops would be written with Harlowe, we decided it was time to update our skills with this new way of writing stories!

Once we got over the new way of doing things, Harlowe was enjoyable to use, and with a little effort we were still able to convince it to do the things we wanted!  

Named Hooks

A ‘hook‘ in Twine is any text that appears in [square brackets]. We used named hooks A LOT. Named hooks let you tag pieces of text and then write interactions for them, or style them differently from the rest of the story. We used named hooks for all of the quotes used throughout the story, and styled them to look more like the text you’d find in a book. 

example code showing named hooks. |page>[story text here]

Named hooks also came in handy when used with the (click:) macro. 

The cellar-door flew open with a booming sound, and then he heard the noise much louder, on the floors below; then coming up the stairs; then coming straight towards his [door.]<door|

(click: ?door)["It’s humbug still!" said Scrooge. "I won’t believe it"]

Here, when you click the text door with the <door| label, the text "It's humbug still…" will appear below. 

Things can quickly get complicated with hooks. In the introduction we used hidden hooks and the (link-reveal:) macro to slowly show a longer text as you click each sentence. 

Twine screenshot showing more complex hidden hooks and the link reveal macro

Using Timed Passages

Here’s one of the simpler uses of the (live:) macro that we used to add delays to passages, and build the suspense!

  • 2 seconds after loading the passage "but then…" will appear
  • 2 seconds after that "Jacob Marley's ghost appears!" will appear in a large spooky font! 
(live: 2s)[(stop:)but then...
(live: 2s)[(stop:)
$s[[[[<h2>Jacob Marley's ghost appears!</h2>|Marley 3]]]<ghostText|]
]]

There’s a lot of brackets going in this section. The (live:) macro will update the rest of the [hook] every x number of seconds, here it’s set to 2 seconds. 


Each (live:) statement is immediately followed by a (stop:) so these will happen after 2 seconds, but by using the stop macro they won’t repeat again. 

$s is a variable, this variable is set at the top of the passage with (set: $s to (text-style: "smear")) we’re using it just to make the code a little easier to read, and so we could play with all the text-style effects easily without worrying about messing up those brackets! 

It took a bit of getting used to dealing with all those square brackets. In this case we have the first bracket for the (text-style:) macro, the one to contain the whole line in the <ghostText| named hook, then 2 for the normal passage link! 

<h2> is normal website HTML for Header 2, making the text BIGGER and bolder than the rest of the passage. 

Then at the end of the passage, we need closing square brackets for all of it! Including the text-style and the two live macros! 

Sound

We added our sounds and played them using JavaScript functions. The audio files were mp3 files that we hosted on the server along with the Twine webpage.

In our Story JavaScript passage we added some JavaScript code. The Story JavaScript passage is a special passage found in the menu.

twine menu showing where to find story javascript passage
How to edit the Story JavaScript Passage
// we created an object that could be accessed from anywhere called audioObj.
window.audioObj = {}; 
// create a HTML audio element 
var audio= document.createElement('audio');
// set the source or 'src' to the location of the mp3 file. 
audio.src= 'chain.mp3';
// set loop to false as it only needs to play once.
audio.loop = false;
// add our HTML audio element to our object 
window.audioObj["chain"] = audio;

//do it again for the bang sound effect.
audio= document.createElement('audio');
audio.src= 'bang.mp3';
audio.loop = false;
window.audioObj["bang"] = audio;

Then these audio files could be played anywhere by adding a line of JavaScript to the passage.

{<script>window.audioArray["bang"].play();</script>}

The curly brackets {} around the script tag ensure the line doesn’t take up space on your webpage. For example:

hello

World

will have a line between hello and world, but

hello {

}world

Will appear as 'hello world'

Variables and JavaScript

Harlowe isn’t so easy to use with JavaScript as SugarCube is, we had to change things around until we found a happy medium that didn’t break things. For example – you can’t change Harlowe variables using JavaScript or your story will lose its place and refreshing the page will take you back to the wrong place! 

Fortunately, as well as Twine variables you can also use global JavaScript variables in your Twine macros:

<script>
// when the chains rattling has played once we added this variable.
window.chainPlayed = 1;
</script>

Then in our main passage

Scrooge hears the rattling of chains...
(event: when chainPlayed == 1)[
{<script>window.audioArray["bang"].play();</script>}
|page>[The cellar-door flew open ...

So when the audio file finishes playing, chainPlayed will be set to 1, the (event:) macro will be triggered and so Twine will play a BANG! sound effect and display the page text: "The cellar-door flew open.." 

Linking it all together

There were several ways we could have linked the stories together, including manually copy-pasting the new passages into the original game. We decided to spend some time implementing a more robust way that we could reuse easily in future projects. 

We wanted to get a list of html files (the submitted stories) and make a list of them as links in our main game.

To do this we used a little bit of PHP code to look at all the folders, make a list of games and their titles, and make that available in a format that our Twine game could understand (JSON – JavaScript Object Notation, an easy to use format for transferring data from one place to another).

In this project there are 3 types of story, these would be stored in 3 separate folders on the server. The PHP script reads all the files in the past folder, and stores the name and URL of each one in a list. Then it does the same with the present folder, and the future folder. 

It returns some JSON with a list of titles and urls a bit like this. 

  • Past:
    • name : "my past story", URL : "stories/past/my past story.html
    • name : "another story", URL : "stories/past/another story.html 
  • Present:
    • name : "my present story", URL : "stories/present/my presentstory.html
  • Future:
    • name : "my future story", URL : "stories/future/my future story.html

Here is the code in our Story JavaScript passage that fetches that list and stores it in a JavaScript variable called window.stories.

if(!window.loaded) //if the data hasn't been loaded yet
{
  // get the data from the loader.php webpage and store it in window.stories.
  $.get( "loader.php", function(data){ 
	window.stories = JSON.parse(data);
	window.loaded = true;
	console.log("stories found!");
  }).fail(function(){
  	alert("loading extra stories failed.");
  });
}

And then finally, in the story passages we need to list out those names and URLs. This could have been done in JavaScript too, but there is a Twine macro that does just the job.

(for: each _item, ...stories["past"])[
(link:_item["name"])[(gotoURL:_item["url"])]

The JavaScript variable stories contains stories["past"] which is a list of all the stories relating to the Ghost of Christmas Past. The (for:) macro will go through each _item in the stories["past"] list and create a (link:) with its name and when you click it the (gotoURL:) macro will take you to the associated URL.

As the submitted stories are completely separate twine games, we needed a way to get back to the original story AND at the right passage. We can’t rely on the browser to do that work for us – what if someone sends their friends as family a link to their own submission? Their browser won’t know where in the story to take them back to after that ghost and they’d end up at the title. 

For this final piece of the puzzle we used URL query parameters – a little bit of information that appears after a question mark in your URL.

Each participant was asked to add one line of code to the end of their game. This is the code for stories about the Ghost of Christmas Present.

(link:"Scrooge woke up")[(gotoURL:"../../?story=present")]

Just like in the previous section, this is a link with the text “Scrooge woke up” and when you click it, it takes you to the URL "../../?story=present" 

"../.." takes us from the submitted story webpage, back to the main game webpage. "?story=present" is our URL query parameter. 

Then we can add some more code to our Story Javascript so that if the URL has this extra data, it loads up a different passage in Twine. 

if (!window.harlowe){
//this sets the Twine 'Engine' to a variable so we can use its functions later on
	window.harlowe.Engine = Engine; }

//When the document is ready 
$(document).ready(function(){	

// get just the text after the question mark in the URL
var queryString = window.location.search;
// use built in Javascript methods to read this text
var params = new URLSearchParams(queryString);
// get the 'past', 'present' or 'future' variable out of the query string
var story = params.get('story');

if(story == "past")
{
	//Engine.goToPassage is a special Twine function that takes us to any passage.
	window.harlowe.Engine.goToPassage("Ghost of Christmas Present");

}
else if (story == "present")
{
	//where to return to
	window.harlowe.Engine.goToPassage("Ghost of Christmas Future");
}
else if(story == "future")
{
	//where to return to
	window.harlowe.Engine.goToPassage("Christmas Day");
	
}
});

This counts as a ‘hack’ as you’re not supposed to access the Harlowe ‘Engine’ from JavaScript and there’s no guarantee that it won’t stop working in a later version of Twine if the developers change something. But it was simpler than the alternative – mixing JavaScript and Twine to get the URL parameter in JavaScript and then use Twine (if:) and (goto:) macros to work out which passage to go to! 

And that is the end of our overview of all the tips and tricks we used to create A New Christmas Carol! Twine provides endless possibilities, it can be customized, twisted, and turned into almost anything you can dream of. You can mix Twine with other tools as well, check out Superlunary ep. 1 in our Lab exhibition, which combines Twine, Bitsy and Flicksy

During these strange times where schools, museums, clubs and other organisations are looking for new and exciting ways to engage with their audiences digitally and safely from home, Twine is a completely brilliant resource. We certainly hope to use a lot of the new things we’ve learnt again in the future!