• Register
Post feature Report RSS Devlog #3: Getting Ink

Orkward, the space ork dating game, was made with Ink, a scripting language by Inkle. This technical deep-dive is all about how Ink is used, and why.

Posted by on

At the time of writing, Orkward is coming up to 6000 words of richly interconnected interactive fiction. It's written using Ink, a scripting language by Inkle which drives their own games - including Pendragon, Heaven's Vault and 80 Days.

In this devlog, I'll be talking about Ink, how I use it, and what I think of it. It'll start off at quite a conceptual level, but get gradually more technical as we go.

Orkward

The first version of Orkward was a text-only game, pure interactive fiction. It was driven from an Ink script and a CSS stylesheet. It looked like this:

Orkward's text Menu

The beauty of Ink is that you can quickly export a fully playable browser-based interactive fiction game. And that's great!

Even better, Ink also provides an API which you can plug into your own engine. It supports JavaScript and Unity today, and we're hoping for Unreal support in the not-too-distant future (this seems likely as Ink just received a grant from Epic).

The current version of the game embeds Ink's 'native' JavaScript renderer and runtime into my own JavaScript (React) 'frontend'. I can control when and where the text display and read/write internal game state at my leisure. This means I can plug-in my own art assets and UI overlays on top of the story, giving me loads of control.

Since version 0.5, Orkward has replaced the pure-text menu with a visual gallery and UI overlay, giving the player a richer, more visual way to drive the story. Here's how it looks as of 0.7:

Orkward's image-based gallery menu

In a later devlog I'll probably talk more about the JavaScript frontend (unless I think it's too boring, which could easily happen). For now, we're just going to focus on the texty bit produced by Ink.

Playing with Ink

The basic idea of Ink is to print prose on the screen, interspersed with options or choices which let the player choose where to go next. Once the game is finished, you should be able to read back over all the text and have a consistent, plain-text narrative.

In other words, the pages are designed to be continuous. This is a very different model to the paged output of Twine or Ren'Py.

In Orkward, you might see a choice like this (choice in yellow text):

A screenshot of a text-choice shown in Ink

And when you click on a choice, the options are removed and replaced with the next text, so if you select 'down the grog', you'll see this:

A screenshot of the previous choice resolved into text

Seamless. Lovely.

But it doesn't have to be seamless - that's just the default presentation. Ink is a runtime and has an API which I can call to read/write variable state, generate text, loop through choices, and so on. The HTML renderer I'm using is just a bit of very-readable JavaScript which I can modify. So I can customise the output as I like, highlighting player options or marking out 'sections' to imply player choice.

Writing with Ink

Ink is a scripting language is which puts text first. There's no fiddly GUI - the idea really is that you just write out your story in prose, as natural as possible and without having to use the mouse. The dedication and focus that gives you is just lovely.

Inkle provide an editor called Inky, which is the easiest way to get started. Or there's a range of plugins to Visual Studio Code.

If you're familiar with Twine, it's quite different because you don't have the pinboard with nodes that visualise the story. I have to say, I don't miss this at all. It's quite nice to be able to visualise the story, but things quickly get very hard to follow. And it's fiddly. I spend as much time moving nodes around as I do acually writing. For a time back in Twine 1, I actually wrote the serialised format directly and imported it back into the tool to test it.

If you're familiar with Ren'Py, then Ink is pretty similar. Ink is a much thinner engine - it doesn't give you all the fancy UI stuff that Ren'Py gives you, and makes fewer assumptions about presentation. I think Ink is a more elegant and powerful scripting language - although for some people, Ren'Py will be a better fit.

There is a bit of syntax to learn. Like markdown, it's a mixture of arbitrary and intuitive symbols scattered across your text to drive the formatting. But once you learn that syntax - and it's its simplest, it's really very simple - it becomes very quick to put something together.

Simple Syntax

Here's the basic format of an ink script, taken from Inky (the Ink editor):

Some Ink script, shown in Inky


This defines a named section of story (or a stitch, in Ink parlance), called gather_armor. Other sections are referenced in lines 187 and 189 via diverts, which are those little arrows. When the story gets to that line, it'll go/jump/divert to that section.

The asterisks on lines 186 and 188 define choices that the player can pick. Everything on the same line as the asterisk will be displayed as a single choice (so choices are in effect single-line). When the player picks a choice, Ink will play all content between that choice and the next choice or divert.

So in the code above, selecting the first choice will trigger a divert to the death_by_manners section (bad news for the player!), and the second will trigger divert to main_branch. Instead of diverts, we could put some inline text. Typically we'd indent this text to show that it's "inside" an option block.

The square brackets on line 188 alter the line before and after selection. This is a little bit complicated, but basically it cuts the line into three parts around the brackets. The bit before the open bracket will always be shown. The bit in between the brackets will only be shown before the option is clicked, and the bit after the closing bracket will only be shown after the option was clicked, replacing the bit in the middle.

So before the option is clicked, Ink prints:

  • "Fine."

And afterwards, it prints:

  • "Fine," you say.

This is often useful because the text of a choice, without the context of other choices around it, often doesn't read well when you read the prose back.

Indenting is immaterial, by the way, and only used for formatting. Ink mostly ignores whitespace, apart from line breaks because some syntax is line-based (for example, choices (*) and code (~) read the whole line).

Here's how that section looks in-game:

The previous Ink source rendered in-game


Fancy Syntax

Ink is a really rich language, full of powerful features. Here are just a few neat tricks I've used in Orkward.

Alternatives

A very common use-case in interactive fiction is to show cycling or randomised output, typically when you get back to root menu. Like a shopkeeper. The first time you meet the shopkeeper, you need to print a description of them. But the second time, you just need a simple line to introduce the shop menu.

Alternatives are a great way to do this. Here's a drinking game loop with Chompa (slightly simplified - this will actually loop forever):

Syntax for alternative outputs, shown in Inky


This creates a section (stitch) called drink_up, which we jump (divert) back to on every choice.

The { cycle: } stuff is the alternative loop. Every line starting with - is an alternative. cycle is a keyword which tells ink to cycle through these options one by one. The <> syntax is 'glue', which basically ignores the line breaks. So every time we enter the drink_up section, we show a different line of text.

Here's what the output looks like in the Inky editor:

An example of rendered alternative outputs, shown in Inky


Another twist on alternatives is to shuffle the output, meaning you can get more varied, natural dialog. This scene, in which the player tries to defuse an orky bomb, uses the single-line syntax, where shuffle is denoted by '~' and each alternative is denoted by the '|':

Ink source for shuffled alternative text with in-line syntax, shown in Inky

This produces a random output like "Try the red one" each time

The 'alternative' syntax really shows what a dedicated scripted language can do when it really understand its domain. I love it!

Tags

Ink allows me to add arbitrary 'tags' to any line. These are a bit like comments, in that the player won't see them. But they are seen by the runtime engine. Tags allow me to broadcast messages to the game's frontend from within the story. You can use this to change visual styles, synchronise art assets, play music or sound. I use tags to broadcast a return to the main menu screen (hiding the story and showing the gallery menu), trigger an ending or game over screen, and unlock trophies.

Here's an example of unlocking a trophy by out-drinking Chompa:

Ink source showing a trophy tag, captured from Inky


The first line sets the trophy variable by incrementing a number. The second line is the tag, where 'trophy' is the tag name and 'bottoms_up' is the variable name (minus a prefix).

When the frontend sees this tag, if the variable count is 1 (ie, the first time a trophy is triggered), we'll show a little pop-up to tell the player they're just unlocked a trophy. Neat!

Weaves

Finally, here's a neat trick using a thing called a weave. This is a bit tricky and maybe only makes sense when you've used Ink and got a feel for how it works.

The Ink I've shown so far uses diverts at the end of each option to go to the next section - which is a good proxy for how Twine works.

But that can mean you have very fragmented script, with little sense of structure and flow. Ideally you want the start of the story at the top of your script, and the bottom at the end, and the rest flowing in a broadly chronological order (as much as possible!). And you can do this with stiches and diverts.

Weaves are a neat trick which helps you reduce this fragmentation and have longer sequences of prose. It's denoted by a dash (yes,a dash can mean a lot of things in Ink!) and it will 'gather' or 'catch' the story here unless otherwise diverted.

Here's an example. This section of Orkward lets you pick a name for your knife:

Syntax for a weave, shown in Inky


It presents the player with three options ("Dis is Clara/Stabba/Julius") and, whenever one is picked, it sets an internal variable which we can use later.

The weave is the dash after the choices, on line 251. It basically acts as a section break for Ink, telling it not to associate line 251 or anything after it with the previous section. It also tells Ink where to go next after any previous sections (unless they explicitly divert).

That's a bit complicated, isn't it? Basically, after each choice, unless there's a divert, we jump to the next weave. So all three options above end up in the same place.

This gets really useful when you have branches which all end up in the same place, because the start and the end are in the right place in the file so you get a natural structure. It's a bit hard to show this in a screenshot, but here's a very brief example (you might imagine that each choice has a lot more content before joining up at the weave at the end):

An example of intented choices, gathered with a weave, from Inky


This generally works better with shorter sections, but it's a really useful trick.

Other stuff

There's a lot more I haven't talked about. Stitches can be scoped a bit into knots (think sections and subsections) and accessed by paths (-> section1.subsectionb ).You can pass parametersto stitches and knots. You can even pass diverts through. You can define functions to compute values. I haven't really talked about variables but you can see them in this blog. You can nest choices and weaves, which quickly becomes useful when inlining text.

It's a really rich language which has helped Inkle ship some very complex interactive storytelling experiences.

A Few Stains

Working with Ink has been pretty great, but I do have a couple of criticisms.

Firstly, there's a lot of jargon involved. Ink documentation talks about knots and weaves and diverts and tunnels. Now, there's a pretty good reason for this because Ink introduces a lot of concepts which are pretty unique to it. But mostly I think it's a bit of a barrier to learning and makes the langauge look harder than it really is. You might argue that some characters get a bit over-used too - dashes, in particular, can mean very different things in subtly different contexts. You get used to all this and it's clear enough, but you do need to wrap your head around it.

In the end, it's a programming language and so it does take a bit of comprehension.

Secondly, the tools available leave a little to be desired. Inkle's Inky editor is basically really really good. It runs the game alongside your script and instantly updates - which is just brilliant for editing while you play through.

A screenshot of the Inky editor

But it does have shortcomings. It's only really good for editing a single file - if you're working with several files, it quickly becomes hard work. I can't really use it for Orkward - I have too many files, and because the structure of the game is non-standard, I often need to see thinga rendered in-game.

I get the sense that the community use Visual Studio Code for a lot of Inking. And I'm using it, too, because it's a great tool for JavaScript development and vital to the frontend. There are few plugins for syntax support and stuff, but they too have many shortcomings. It's great for large projects with mutiple files, but I really miss the preview/live-reload modes.

Ink has just recently received investment from Epic, so I'm really hoping to see a better range of tools (and maybe documentation) coming out over the next year to make it easier and better to work with.

Ok I'm bored and want to get back to game dev please

So to round up, Ink is:

  1. An elegant scripting language which reminds me very much of markdown
  2. A runtime which exports to Unity and JavaScript and is ideal for embedding into a bigger game.

Mostly, I picked up ink for this project just for the sake of trying it out. I've wanted an excuse for years, so I'm pleased Orkward came along and gave me a chance to get stuck in. The runtime API means I can deeply integrate with whichever game engine I'm using, while the text-first approach to scripting the narrative means I can stay glued to the keyboard and iterate very quickly.

I've really enjoyed my time with Ink and would absolutely use it again to drive any text-heavy storytelling in future games.

In the next devlog, I'll be joined by a very special guest to talk about narrative design in Orkward. See you then!

Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: