Creating an interactive fiction or a text-adventure game using Twine and sometimes Javascript.
If you don’t know Javascript or what that is, don’t worry! You’ll be able to learn along the way or even go without it at all. What we are going to explore is an open-source tool to build interactive stories – Twine (or see its website).
It allows us to make nonlinear stories. It can be presentations, fiction, or text-adventure games. In a nutshell, Twine creates an HTML file with all the content. So, it looks like a usual web page, and the tool is the constructor. You can upload such a “website” on the web or mobile platforms, i.e., anywhere that can parse HTML.
If you’re particularly interested in games / interactive fiction, see an extensive collection of them. So what sort of games are possible? All of them since we can add Javascript. However, the easiest way is to do text-adventure games. Why? You don’t need to program anything or much. You add stories and connect them in a meaningful way using links. I.e., if you choose “A”, the story “AA” will be shown, if you choose “B”, the story “BB” will be shown.
See also what you can create with the tool from their docs.
Creating such a page takes a few minutes on the Twine website(you can do that online). You can add Javascript, videos, music, images, customize styles via CSS and do the linking and playing around. It’s a great way to start. Next, I will show you a different approach if you’re used to coding in any developer’s editor and want version control.
Try online first, especially if your story/game is small and only needs a little custom functionality.
Why you may need Javascript
You can go without it and feel fine. But if you have custom stuff, you’ll use some macros and scripts. What are they? Imagine Twine as a core, but it has various engines that do things differently. It supports four such engines(story formats) to make the creation process more accessible. Each of them varies in complexity. In this article, I’ll be using SugarCube.
This story format has many built-in things you may need. For example:
- Saving a game, resuming it from a save.
- Various events to react to. E.g., when a story is rendered, started rendering, etc.
- Macros, i.e., useful built-in blocks/functions. For example, buttons, custom links, conditional rendering, setting variables, DOM manipulations, etc.
- Audio handling.
- And many other valuable things.
A Twine project in a developer way
Let’s create a simple project where we want to use custom Javascript and CSS styles, but more importantly – we want to have version control! I don’t use the tool’s online or desktop version because I can only manage stories as files and have their versions by commit.
You’ll need to install Tweego, a tool that can parse stories as files in any preferred text editor. Be aware of its limitations, though:
- When writing this article, the last update of Tweego was two years ago.
- Thus, you may not have all of the features from the supported story formats(e.g., Sugarcube).
Now you need to create a project folder:
$ mkdir twine-project
$ cd twine-project
$ git init
You can move the Tweego executable to this folder as well and add it to .gitignore
It’s up to you how to organize files now! An example structure may look like the following:
.gitignore
README.md
bin/
src/
├─ config.tw
├─ styles/
│ ├─ menu.css
│ ├─ main.css
├─ modules/
│ ├─ menu/
│ │ ├─ menu.tw
│ │ ├─ menu.js
In the bin
folder you have the Tweego executable to build the output to HTML(we’ll get to that). All the story(game)-related code is under src
folder. Tweego will put all Twine(`.tw`) files, CSS styles, Javascript scripts into one HTML. Therefore, it doesn’t matter what project structure you have.
Twine format
Now, closer to the coding: what is config.tw
? This is where your code will be in Twine format. Take a look at the specification. You may name this file whatever you want. It’s named config
for readability. There, we specify the settings for our game:
:: StoryTitle
My first game
:: StoryData
{
"ifid": <a serial number of your game>,
"format": "SugarCube",
"format-version": "2.30.0",
"start": <a name of the story that will be shown first>
}
:: StoryAuthor
<your name if you want>
<<script>>
// in case you'll need to have 3rd-party scripts
// remove this <<script>> section at all for now
importScripts(
'https://cdn.jsdelivr.net/npm/chance'
)
<</script>>
You need to generate a serial number for your game, i.e., IFID. Read more about how to do that here. But for now, you can use 0000A000-A0E0-00C0-00B0-CF000E000D0E
to skip this boring step.
format
tells Tweego which story format to use. We’ll use SugarCube
. format-version
is a version for this story format, currently supported is 2.30.0
only. However, there are newer versions(a limitation of Tweego).
start
is a story that will be shown first. Let’s create a file start.tw
with this content:
:: StartOfMyGame
This is the first screen of my game, yay!
[[Start playing]]
[[Read about the author]]
The ::
here indicates the ID of your passage(i.e., a page). It can be anything, e.g., :: start-of-my-game
or :: something like this
. Now that you have the ID, change your config.tw
to have:
"start": "StartOfMyGame"
After the passage(page) ID, you do whatever you want. In our case, we wrote, “This is the first screen of my game, yay!”, and it’ll be rendered as regular text, that’s it! The [[Start playing]]
thing is a link to another passage(page).
To build that to HTML, run Tweego(it’ll be watching for files changes):
$ ./bin/tweego -w src -o ./output/index.html
Here, we’re telling it to watch the src
folder and build an output HTML into the output
folder as index.html
. Run this command, and you’ll see the HTML output in that folder. Don’t forget to add output
to .gitignore
. Open output/index.html
in a browser and you’ll see something like this(with a more dark background color):
We create the links, but we also need to create such pages. So, we need to change the start.tw
:
:: StartOfMyGame
This is the first screen of my game, yay!
[[Start playing]]
[[Read about the author]]
:: Start playing
<<back>>
It's another page called "Start playing".
:: Read about the author
<<back>>
I'm the author. This is my page.
We’ve added two more pages, so whenever you click on, for example, “Start playing”, you’ll be redirected to the “Start playing” passage:
We see a new link here – “Back”! <<back>>
is a SugarCube macro that redirects a user to the previous passage(`StartOfMyGame`). It’s a more convenient way of doing that than storing a navigation history each time.
We might create these two new passages in the other files or create all the game passages in one file. It doesn’t matter because Tweego puts all of the files together into a single HTML file. You don’t need to care about importing something!
Adding Javascript to Twine stories
Let’s imagine we want to store some information about a player’s choices. There are two approaches:
- We may use the
<<set>>
macro. - We may use Javascript.
When using <<set>>
:
:: StartOfMyGame
This is the first screen of my game, yay!
<<link "Start playing" "StartPlaying">>
<<set $choice to "StartPlaying">>
<</link>>
<<link "Read about the author" "AboutTheAuthor">>
<<set $choice to "AboutTheAuthor">>
<</link>>
:: StartPlaying
<<back>>
It's another page called "Start playing".
The choice is <<= $choice>>
:: AboutTheAuthor
<<back>>
I'm the author. This is my page.
The choice is <<= $choice>>
A few new things here:
<<link>>
macro does the same as[[ ]]
, but it adds more customizability. In our case, we kept the link text, but indicated a different passage ID(`StartPlaying`, e.g.). Also, we can do something when a link is pressed, e.g., a<<set>>
instruction below.<<set>>
macro stores a variable.<<= $choice>>
is a macro to evaluate expressions. In our case, it’s displaying$choice
variable we set before.
We can achieve the same using Javascript(however, it seems unnecessary complicated in this example):
:: StartOfMyGame
This is the first screen of my game, yay!
<<link "Start playing" "StartPlaying">>
<<script>>
State.setVar('$choice', 'StartPlaying (Javascript)')
<</script>>
<</link>>
:: StartPlaying
<<back>>
It's another page called "Start playing".
The choice is <<= $choice>>
I removed the second passage to not duplicate the code. Here are a few new things:
- We still do the scripting inside
<<link>>
macro. But we use<<script>>
macro now. Inside of it, we use a global objectState
‘s methodsetVar
which does the same as<<set>>
in the previous example. - We still display the
$choice
variable not using Javascript, but we could find that HTML block using jQuery(which is built-in in SugarCube scripts), and then set the value of it to$choice
, but it’s unnecessary.
When you use Javascript, you have access to the story format’s APIs, so it’s more customizability. However, you may not encounter such complexity in your game.
That’s it for now! There are more things to do in a game, of course. But you have the documentation and tools to discover and learn more independently.