DEV Community

Em Lazer-Walker
Em Lazer-Walker

Posted on • Edited on

A Modern Developer's Workflow For Twine

I love Twine! Whether you're trying to prototype a larger work or make something on your own, it's such a powerful and easy-to-use tool to make hypertext-based narrative games.

That said, a common complaint I've heard from most people I've talked to who use it seriously is how readily its workflows fall apart at scale.

A visual graph editor is a fantastic approach for small projects, but gets unmanageable quickly on larger projects. Additionally, the way the Twine 2 editor handles files means using using tools like version control can be difficult, and merging changes from multiple collaborators can be nearly impossible.

But there's a solution! I'm going to spend the next few minutes walking you through my Twine development workflow. There are three important parts of it I want to talk about:

  1. Plain text files. I use VS Code to write my games, rather than using the visual Twine editor.
  2. Modern version control, storing my games in git on GitHub.
  3. Automatic publishing. Every time I push a new version of my game to GitHub, it's instantly playable via GitHub Actions and GitHub Pages.

Let's step through the tools I use, and how you can get set up with a similar toolchain!

Writing in a Text Editor

Why is it valuable to be able to write Twine games as text files instead of as nodes in a visual graph?

It scales better. When your game grows to be tens of thousands of words, navigating Twine's node-based visual editor can be a pain. Having your entire game be in a single text file, that you can manipulate and browse however you'd like, is far easier for even medium-sized projects. And that's even before considering that being able to split your script up into multiple files, which can greatly reduce the cognitive load for larger projects.

It allows for reuse. Have some macros or other bits of scripting you'd like to reuse between passages, or across multiple game projects? Being able to copy/paste text in an IDE is a lot easier than managing it in the visual editor.

It gives you access to better writing tools. I'm more comfortable writing in the same text editor I use for other programming and writing tasks than I am in Twine's text boxes. It also means I can use the tools they provide to make my life easier!

VS Code has extensions to add syntax highlighting for both Harlowe and Sugarcube. More than that, access to its entire IDE ecosystem means I can pull in tools to help with creative prose writing. This means basic things like spell check and an omnipresent word counter, but it can also mean more powerful tools to do things like warn me if I'm using subtly sexist/racist/ableist language or even spark my creativity by collaborating with an AI!

It enables more robust versioning and collaboration. More on this later, but writing my game in a text file means it's stored in a human-readable text file, which is what enables all of the other great tools and techniques I'll be talking about next.

This all sounds great! To get all of these benefits, we can use a special programming language called Twee!

What is Twee?

In the olden days of Twine 1, there were two officially-supported ways to make games: using the Twine visual editor, or by writing code in a scripting language called twee that could be compiled by an official CLI tool, also called twee.

(A fun historical sidenote: even though the Twine's visual editor is the more popular tool, the twee CLI predates it by 3 years!)

Twee code is conceptually the same as a Twine graph, with different blocks of text in a file referring to different passages.

:: Start
This is the first passage in a Twine game!

[[This is a link|Next Passage]]


:: Next Passage
The player just clicked a link to get here!

Enter fullscreen mode Exit fullscreen mode

When Twine 2 came out, support for the twee language was officially killed, and the only officially supported path was to use the Twine 2 visual editor and its greatly-expanded support for story formats.

How do you use Twee with Twine 2?

When Twine 2 wasn't accompanied by a "Twee 2", the community stepped up, and a number of third-party twee CLI tools emerged. The twee language needed to adapt, though, since Twine 2 handles story formats in a vastly different way from Twine 1.

What follows is a bit of a technical explanation of the development of modern Twee tools. I think it's interesting, but if you want to skip over it, the main practical takeaway is that I use the Tweego CLI tool to write a newer version of Twee that's called Twee 3.

Twine 2 Story Formats: A Technical Explanation

To understand why we can't just use the old twee tool with Twine 2, we need to understand how Twine 2 story formats work.

Internally, Twine 2 stores your work as an XML document. When you click the "publish" button in the Twine 2 editor, that XML document is passed to the selected "story format", which is essentially an HTML template. A story format will typically embed JS within that template to parse and modify the Twine story data as appropriate to display it as a playable game.

This is why/how different story formats present vastly different authoring syntax: as far as Twine the engine is concerned, a passage's text is just an arbitrary text blob (except insofar as it parses links to draw lines in the visual graph editor), and it's then up to the story format to decide how to parse a passage to provide narrative functionality.

If you're curious to see a "minimum viable story format", I maintain a story format called Twison that converts Twine story data XML into JSON, with a few bits of computation and data-munging meant to make the JSON easier to consume if you're integrating it into your own game engine.

This all means a story format is essential to actually going from a script to a playable game! It isn't enough for a hypothetical CLI tool to just take your twee code and bundle it up into the same XML format that Twine 2 uses internally, it also needs to then pass that XML to a story format and generate an HTML file from that interaction.

So... is there or isn't there a Twee 2?

The last few years have been a tumultuous time for people who would want to write Twee. After quite some time of different people building out different competing Twine 2-compatible twee compilers, there is now a formal language specification for Twee 3, maintained by the Interactive Fiction Technology Foundation (IFTF).

It's designed to be a superset of the original twee language (retroactively known as Twee 1), and to be fairly easy to convert between twee code and the internal format used by the Twine 2 visual editor.

If you're interested in the history and politics of how we got here, this oral history is a great overview.

There are multiple functioning Twee 3 compilers, but I personally use Tweego. I'm sure others are great as well, but Tweego works well, is actively maintained, and is easy to get support for in the official Twine Discord.

How to use Tweego

If you're comfortable using CLI tools, Tweego is quite easy to use. After downloading the correct binary from the website, you can call it directly to simply compile a .twee file into a compiled .html file you can play in a browser:

$ /path/to/tweego -o example.html example.twee
Enter fullscreen mode Exit fullscreen mode

Here's the sample code from earlier updated to Twee 3 and with some metadata:

::StoryData
{
    "ifid": "F2277A49-95C9-4B14-AE66-62526089F861",
    "format": "Harlowe",
    "format-version": "3.1.0",
    "start": "Start"
}

::StoryTitle
My test story!

:: Start
This is the first passage in a Twine game!

[[This is a link|Next Passage]]


:: Next Passage
The player just clicked a link to get here!

Enter fullscreen mode Exit fullscreen mode

That ifid is a random unique identifier for a game. If you try to compile a Twee file without including that, tweego will automatically generate one for you.

Similarly, tweego has a ton of other options and flags you can pass in, that you can see by running tweego --help. For the options that do things like specify a story format, I'd highly recommend just specifying that in a metadata block like I have above.

Also worth calling out is the --watch option. If you run tweego -o example.html example.twee --watch, it will start up a server that watches for file changes and then recompiles. If you have a text editor open in one window and a web browser open in another one pointed to your compiled output, this is a great way to quickly test changes!

But I want to use the visual editor!

If you have a reason to use the Twine 2 visual editor for something, you can also use it with Tweego. You can take the .html file output by Tweego and import it directly into Twine 2. When you're done, you can convert back from a .html file produced by Twine 2 into Twee by using the -d flag (e.g. tweego -o example.twee example.html -d).

As an aside: the Twee language includes import functionality that lets you spread your game across multiple files and then join them at compilation time. That can be a really powerful technique for managing larger games, or reusing macros across projects, but that sort of workflow can make jumping back and forth with the visual editor trickier. See the tweego docs for more info.

Version Control

As mentioned, one of the coolest parts about writing Twine games in plain text files is how much easier they are to version.

If you've ever tried to revisit previous versions of a Twine game you've made, or tried to collaborate with other writers, you know how difficult this can be when you're operating purely on .html files! Whether you're using git or just storing .html files on a server somewhere, having to import and export files that aren't particularly human readable is a major pain.

In the past, I've often given up on trying to fix merge conflicts with other writers, and just manually copy-pasted changes into the Twine editor by hand. That's frustrating, and avoidable by storing everything in Twee files instead!

I'm not going to walk through how I use git and GitHub, but I will say one important thing that I do is not store my compiled .html files in git at all. Rather, I'm going to set up a build process so that GitHub is responsible for automatically compiling my .twee files into .html files. This means we can keep the git repository clean and readable!

Automatically building on GitHub

The concepts of CI and CD (continuous integration and continuous delivery, respectively) are very popular in non-game software development. The high-level idea is that it shouldn't require a lot of manual work to deploy a new version of your software.

As soon as you push up new code to your version control server, it should be responsible for making sure things aren't broken and then compiling it, deploying it, or whatever else might need to be done to get your code into the hands of users.

This might seem foreign, or perhaps overkill, if you're just used to the flow of writing a game, getting an HTML file, and uploading that to something like https://itch.io.

However, GitHub Actions are a lightweight free service we can use to easily set up a deployment pipeline! In the previous section, I mentioned I don't store the compiled HTML files in my git repos for Twine/Twee games. Instead, GitHub Actions handles everything.

Every time I push a new version of a Twine game to GitHub, a GitHub Action runs that uses Tweego to compile my game, and then publishes it to GitHub Pages. The end result is I don't need to think about how to publish my game, or worry if I've forgotten to deploy the latest version or not: whatever version of my Twee code I can read on GitHub, that's the version players are playing!

Getting this set up with your own Twine/Twee project is easy. Let's walk through it!

Add the story format to git

When your Twee specifies that you're using a story format like Harlowe or Sugarcube, Tweego can find the correct story format because the version of Tweego you've downloaded from the Tweego website includes a half-dozen standard ones. The way we'll be installing Tweego on GitHub Actions won't have access to those.

Within your git directory, create a folder called storyformats. Go into wherever you've downloaded Tweego, and move the appropriate story format(s) from its storyformats directory into the one you've just created. Commit and push that to git.

This is also generally a good thing for maintaining your game in the future! If you come back to this in five years, it's possible this specific version of the story format you're using might not still be available, and tracking it down might be hard; including the exact story format bundle within your git repo will help ensure (although not guarantee) your ability to edit and compile your game.

Getting Started with GitHub Actions

To set up a GitHub Action, all you need to do is add a new file into your git repo.

GitHub Actions are based on "workflows", which are configuration files. If you add a file called .github/workflows/build.yml (or any .yml file inside that directory), it will read that config and try to use it.

That file should look like this:

name: Build

on:
  push:
    branches:
      - master

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1

      - name: Use Go 1.13
        uses: actions/setup-go@v1
        with:
          go-version: 1.13.x

      - name: build game
        run: |
          go get github.com/tmedwards/tweego
          export PATH=$PATH:$(go env GOPATH)/bin
          tweego YOUR_TWEE_FILE.twee -o dist/index.html

      - name: Deploy to Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_branch: gh-pages
          publish_dir: ./dist
Enter fullscreen mode Exit fullscreen mode

Be sure to swap out YOUR_TWEE_FILE.twee for the actual filename, and change any other tweego settings you might need to. If you're not sure what you're doing, you probably want to leave the output file as dist/index.html.

This script uses GitHub Pages to host your games. It's a free hosting service for static sites such as Twine games that's integrated right into GitHub. It's totally free, and can scale to support any amount of traffic. I think it's absolutely the best and easiest way to host small websites like Twine games that don't require any sort of backend server services.

If you don't want to use GH Pages to host your game, you'll want to replace the last "Deploy" step with whatever you're using instead.

Testing your GitHub Action

If you make a new commit and push it to your game's master branch on GitHub, after a few minutes it should be live on the web! By default, it should be available at https://[your-github-username].github.com/[repo-name], although it's also possible to configure GitHub Pages to work with a custom domain name.

The GitHub Action can take a few minutes to compile and deploy, so be patient! You can also click through to the "Actions" tab in your repository and see the build as it progresses.

For those who are interested, let's walk through what this config file is doing:

name: Build
Enter fullscreen mode Exit fullscreen mode

This just names the workflow. It can be anything you want; it'll show up in the Actions UI.

on:
  push:
    branches:
      - master
Enter fullscreen mode Exit fullscreen mode

This indicates the series of steps that follow will execute whenever someone pushes code to the master branch.

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
Enter fullscreen mode Exit fullscreen mode

Now we've started to define the task itself. Specifically, it runs on Linux, although that doesn't really matter to us.

Conceptually, a workflow is made up of a number of steps. A step can either be some code we manually write, or it can be a preset collection of actions provided by the community.

- uses: actions/checkout@v1
Enter fullscreen mode Exit fullscreen mode

This checks out the latest version of our code

- name: Use Go 1.13
  uses: actions/setup-go@v1
  with:
    go-version: 1.13.x
Enter fullscreen mode Exit fullscreen mode

Tweego is written in the programming language Go. We'll be compiling Tweego's code from scratch, which means we need a Go compiler. This gives us a working environment for Go code, and lets us specify which version of Go we want.

- name: build game
    run: |
      go get github.com/tmedwards/tweego
      export PATH=$PATH:$(go env GOPATH)/bin
      tweego YOUR_TWEE_FILE.twee -o dist/index.html
Enter fullscreen mode Exit fullscreen mode

This is a custom script! The first go get line downloads and compiles the Tweego tool itself. The next line does some fiddly environment setup you don't particularly need to worry about (modifying our PATH so we can just call the tweego binary without specifying a full filepath). Finally, we run tweego itself.

- name: Deploy
  uses: peaceiris/actions-gh-pages@v3
  env:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    publish_branch: gh-pages
    publish_dir: ./dist
Enter fullscreen mode Exit fullscreen mode

At this point, we have an HTML file in a directory called dist. This is a third-party action created by another GitHub user that deploys code straight to GitHub Pages. This config uses an automatically-generated access token (so it has permissions to commit/deploy), and specifies that we want to take all of the files in the dist directory and publish them to the gh-pages branch.

...and that's it!

And with all of that, we should be good to go!

As someone used to working with more programer-focused tools, I've found this workflow to make it WAY easier and more pleasant to work on games with Twine. Hopefully it's helpful to you too!

If this is interesting to you, you might also be interested in PlayFab-Twine, my tool to easily and automatically add free analytics to your Twine games. The GitHub repo for that site is also a great example of a Twine project developed using this workflow!

Drop me a note if you're using any of this stuff, I'd love to hear from you!

Top comments (13)

Collapse
 
fullmonkeypython profile image
S

Thank you for this tutorial ! It's a one of a kind. So helpful!

I used your workflow in this repo and adapted it after reading the comments as well --thank you all for those, especially tsaftaridis and Robert Lim. I see no more deprecation warnings for now.

I am also compiling a whole folder instead of a single .twee file.

Hoping this helps and/or saves time for others before things crash and burn again in node deprecation and javascript dependency hells :

name: Build

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Use Go 1.13
        uses: actions/setup-go@v5.0.1
        with:
          go-version: 1.13.x

      - uses: jaxxstorm/action-install-gh-release@v1.12.0 
        with:
          repo: tmedwards/tweego
          chmod: 0775
      - run : tweego story -o index.html

      - name: Deploy to Pages
        uses: peaceiris/actions-gh-pages@v4
        with:
          github_token: ${{ secrets.GH_PAGES_DEPLOY_TOKEN }}
          publish_branch: gh-pages
          publish_dir: ./
Enter fullscreen mode Exit fullscreen mode

Thank you again Em!

Collapse
 
tsaftaridis profile image
tsaftaridis

Thank you for putting this together! Confirming still works Aug 10th.

Collapse
 
burmashave profile image
burmashave

Thanks for this. Some notes from my experience following it:

  • The sample code wouldn't compile without a "StoryTitle" passage ("error: Special passage "StoryTitle" not found.")

  • I'm also using VS Code, and I really wanted syntax highlighting. The only extension I could find was "Twee2 tw2 Syntax," which supposedly accommodates files with either the .tw or .tw2 extension; I tried both, but only .tw2 worked. And since Tweego's docs say the 'official' extensions are .tw and .twee, I'm not sure if using .tw2 will cause headaches later

  • philome.la appears to be 'read-only' now, so might want to remove the reference to that

  • PlayFab-Twine sounded interesting: is it possible to get it working with formats other than Harlowe?

Collapse
 
lazerwalker profile image
Em Lazer-Walker • Edited

Thanks for this!

  • Just added a StoryTitle passage to the sample, and removed philome.la (as sad as it makes me!)

  • I'm also using the "Twee2 tw2 Syntax" extension. It's been working fine for me with ".twee" files without any configuration. Also worth noting that, even if it's not picking up .tw files, you can click the language name in the bottom-right corner of your VS Code window and manually select "Twee" to enable syntax highlighting for any file regardless of extension.

  • Building something like PlayFab-Twine depends on hooking directly into the internals of a story format. It's definitely possible to get it working with formats other than Harlowe, but it's a non-trivial amount of work. If there was a lot of interest, I'd consider making a new version for Snowman or Sugarcube.

Collapse
 
gamewise profile image
Gamewise

I'm definitely interested in a sugarcube version of PlayFab-Twine! It sounds amazing.
And thanks heaps for this informative blog post. I read it about 5 times before I could remotely understand it...but finally got tweego working yay! Next step Github actions!

Btw how did you get that AI collaboration working in VS??! Sounds so great

Collapse
 
katafrakt profile image
Paweł Świątkowski

Interesting... I have always thought that graphic editor is the only point of using Twine. If you don't use it, you may as well use Ink or ChoiceScript (license aside). But now maybe I'll try to bear with Twee and try to build something in it.

Collapse
 
lazerwalker profile image
Em Lazer-Walker

A huge benefit of Twine/Twee over other choice-based IF tools is that it can produce fully-playable games by itself, rather than you needing to e.g. embed Ink within a Unity game to produce something that others can play. There are plenty of ways to produce narrative games in Unity without needing to write code (tools like Fungus come to mind) but that’s all still a lot more complex than being able to go directly from a Twine node graph or Twee script to playable HTML.

Being able to embed raw HTML/JS/CSS into Twine games also gives a great on-ramp to more custom behavior. As someone without much/any code experience, it’s easy to start by copy/pasting a few macros made by the community and slowly work your way up to adding more complex functionality, in a way that more closely resembles the accessibility of the Geocities-era web than modern game dev.

Collapse
 
bobslim profile image
Robert Lim • Edited

Heya people,

I found it a little easier to use the compiled binary from github rather than compiling using go. I ran into a couple of issues with the dependencies there, and I wasn't sure how to proceed as a non-go-user.

Replace this:

- name: build game
  run: |
      go get github.com/tmedwards/tweego
      export PATH=$PATH:$(go env GOPATH)/bin
      tweego YOUR_TWEE_FILE.twee -o dist/index.html
Enter fullscreen mode Exit fullscreen mode

with this:

- uses: jaxxstorm/action-install-gh-release@v1.10.0 
  with:
      repo: tmedwards/tweego
      chmod: 0775
- run tweego YOUR_TWEE_FILE.twee -o dist/index.html
Enter fullscreen mode Exit fullscreen mode

for extra credit, I also managed my build scripts in a package.json. This lets the build server and my local dev environment use the same scripts. You still have to install tweego for yourself (this file is easiest)

"scripts": {
    "twee": "tweego --output src/pages/game.html twine",
    "dev:twee": "npm run twee -- --watch",
}
Enter fullscreen mode Exit fullscreen mode

If not included in tweego, remember to include your chosen storyformat in the repo in .storyformat/formatname-1

Collapse
 
kyrathasoft profile image
William.Miller • Edited

I've tried setting this up with my github using the OP's Yaml file contents, and the edits suggested by tsaftaridis; not having success

I'm trying to publish to page:
kyrathasoft.github.io/Stories/

using the following contents in my build.yml file...

`name: Build

on:
push:
branches:
- master

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1

  - name: Use Go 1.13
    uses: actions/setup-go@v1
    with:
      go-version: 1.13.x

  - name: build game
    run: |
      go get github.com/tmedwards/tweego
      export PATH=$PATH:$(go env GOPATH)/bin
      tweego Kyrathaba.twee -o dist/index.html

  - name: Deploy to Pages
    uses: peaceiris/actions-gh-pages@v3
    with:
      github_token: ${{ secrets.GITHUB_TOKEN }}
      publish_branch: gh-pages
      publish_dir: ./dist`
Enter fullscreen mode Exit fullscreen mode
Collapse
 
tsaftaridis profile image
tsaftaridis

Thank you for the detailed explanations!

Build game fails, however, I can't see if I am doing anything wrong...

Anyone have the same problem?

Run go get github.com/tmedwards/tweego
go get github.com/tmedwards/tweego
export PATH=$PATH:$(go env GOPATH)/bin
tweego testcase.twee -o dist/index.html
shell: /usr/bin/bash -e {0}
env:
GOROOT: /opt/hostedtoolcache/go/1.13.15/x64
warning: path dist/index.html: lstat dist/index.html: no such file or directory
error: Starting passage "Start" not found.
Error: Process completed with exit code 1.

Collapse
 
tsaftaridis profile image
tsaftaridis • Edited

For anyone else using this guide, I got it to deploy to github pages by making the following changes in the config yaml file (workflow):

tweego YOUR_TWEE_FILE.twee -o dist/index.html to
tweego YOUR_TWEE_FILE.twee -o index.html

AND

publish_dir: ./dist
publish_dir: ./

Disclaimer: I am very much a newbie. Don't know if this breaks anything really important down the line.

Collapse
 
kyrathasoft profile image
William.Miller

I've not been able to find a copy of the very most recent Harlowe 3 format, the one used by Twine 2.6 desktop. Anyone know how I can get it? The best I could get Tweego to do was compile against, like, Harlowe 3.1

Collapse
 
coreydwillis profile image
Corey Willis

Thank you so much for this article! I was getting frustrated with using the Twine app to work on my game and this process is exactly what I was looking for!