Writing interactive stories with Ink in Unity

Why I chose Ink as one of the core technologies for my game.

In this article I will tell you why I put my own engine "on the shelf" and adopted Inkle Studio’s Ink scripting language for interactive fiction and their Unity integration plugin for my latest game Dust and Salt.

Intro

Exactly one year ago I released my first app ever – the short story about the young Korean warrior Kim Gee Soo investigating a series of mysterious deaths in the picturesque village of Yo Soo. It’s an adaptation of the interactive novel Hwarang and Kumiho by Bulgarian author Leidrin Sweever. Adapting a gamebook can be achieved in various ways and back then I chose building my own engine.

The old engine

You know how gamebooks are divided in sections or paragraphs, and you have to choose which page to turn to continue reading your story? At the beginning I wanted to transfer these stories to the digital world by the letter. I wanted to preserve the same sections and choices. This strict way of thinking naturally led me to place everything in a graph, each section or paragraph represented by a node, and each choice by a line. I decided to describe this graph with something robust in the programming world: an XML schema.

After all, what are interactive stories aside from complex flow charts? Boy, was I wrong.

At about the same time Inkle, the creators of the successful 80 days and the Sorcery! series, opened their scripting narrative engine to the world, making it open source.

My first game was easy to build and quick to release. When I began my second app - the interactive horror story Transomnia - I had to make a choice: use Inkle’s ink language or my own. My own XML schema did the job for the time being so naturally I decided to continue with it. I thought I didn’t need another big overkill tool and could just go with the flow.

Later, when I had to implement a dice throwing system I began to struggle. When I wanted loops in the story, like visiting the same section multiple times experiencing slightly different choices, I struggled even more.

At the end I managed to make my way and I implemented everything I needed.

All great but the time for making the technology stack choice for my first premium title was then upon me.

The game changer

In the last year I learned a lot about games in general, and especially about interactive stories from a creator’s perspective.

I dug deep and analyzed the various aspects of interactive stories. One important conclusion on how to make the reader involved and play along led me to these major points which I tried to use in my adaptations: making use of small chunks of text, add choices for the effect of choosing only, and minor branching.

This new paradigm made me go back and revisit my strict philosophy. I just couldn’t cope with the new requirements with my old engine, so I made the switch to Ink.

Here is my list of top 7 basic Ink features that made it my core technology choice.

1. Syntax

Let's begin with something small in first sight but important in the long run - syntax complexity. Working with stories means working mostly with text. When your own markup gets in the way of reading and editing something is wrong.

The following snippet shows how I described each section from the gamebook I adapted using XML. The <number> tag acts as my unique identifier. <text> is the body of the section and <link> is the choice providded to the user.

The <event> tag is used as a reference to an outside function with the appropriate name and parameters.

<episode> <number>5</number> <text>Just before you reach the first intersection, the ground quakes with a terrible rumble. You quickly press against the wall of the nearest house.</text> <event> <action>AlterHP</action> <parameters> <parameter>-2</parameter> </parameters> </event> <link goto="3"> <text>Continue.</text> </link> </episode>

In Ink you have === knots === , something similar to the concept of sections or paragraphs in classic Fighting Fantasy gamebooks. Knots divert to other knots with this symbol: -> , something that resembles the programming GoTo statement. Then you have * Choice text choices, {Hello world!} print operator, and conditions (if statements) {condition == true: do this | else do that} . Of course, these are the absoulte basics.

The same snippet as the above one in Ink would look like this:

=== five === Just before you reach the first intersection, the ground quakes with a terrible rumble. You quickly press against the wall of the nearest house. ~ alterStats(health, -2) * [Continue.] ->3

In the example above, I have used XML to describe which method is called from within my C# code and what parameters are passed to it. The business logic behind this is in the game classes which are separate from my XML story file. They look like this:

... public static void AlterHP(string value){ hp += Int32.Parse (value); } ...

When writing with Ink this logic can be directly incorporated in the files of your ink story:

VAR hp = 10 === function alter(ref stats, value) === ~ stats = stats + value

While in my C# case it keeps business logic separated from the text, things quickly get messy if you add more complex logic. (Did you notice the passing of the function parameter by its reference? Yeap. It can do that too. )

Let’s see another example:

<episode> <number>7</number> <text>… The lighter lights up the pink body of one of the “crawlers” and before you can react it’s already next to you.</text> <link goto="39"> <requirement> <action>CheckItem</action> <parameters> <parameter>knife</parameter> <parameter>1</parameter> </parameters> </requirement> <text>You use a kitchen knife.</text> </link> <link goto="39"> <requirement> <action>CheckGunEmpty</action> <parameters> <parameter>1</parameter> </parameters> </requirement> <event> <action>Shoot</action> <parameters> <parameter>1</parameter> </parameters> </event> <text>You use a gun with at least one bullet.</text> </link> <link goto="10"> <requirement> <action>CheckTwoItems</action> <parameters> <parameter>knife</parameter> <parameter>gun</parameter> <parameter>0</parameter> </parameters> </requirement> <text>You fail to defend and the “crawler” stings the claws of its tentacles deep into your chest.</text> </link> </episode>

Things begin to pile up! In Ink, the same example would look like so:

=== seven === … The lighter lights up the pink body of one of the “crawlers” and before you can react it’s already next to you. * {inventory has knife} You use a kitchen knife. * {inventory has gun} {ammo > 0} You use a gun with at least one bullet. ~ alter(ammo, -1) * {inventory hasnt knife} {inventory hasnt gun or ammo <= 0} You fail to defend and the “crawler” stings the claws of its tentacles deep into your chest. ->END

Efficient and clean! As a programmer I cannot be happier with the simplicity of solving this so easily which would be a complex scenario for my previous markup.

2. Minor story variations

But here is where Ink truly shines, by taking the same example and twisting it just a tiny bit:

… * {inventory has gun} {ammo > 0} You use a gun {ammo == 1: with your last bullet|loaded with the bullets you have}. ~ alter(ammo, -1) …

The syntax I’ve added would produce these 2 variations of the sentence:

You use a gun with your last bullet. (In case you have only 1 bullet left.)

You use a gun loaded with the bullets you have. (In case you have more than 1 bullet left.)

It didn’t cost me much overhead to instantly edit the text and add some flavor. Because it's easy, it's readable. It's human. Dealing with text rather than complex markup is after all what we strive for. These are the kinds of little perks that enable you to unlock your creativity. How would you describe this little spaghetti code in a flow chart? How many variations will you add until you give up modifying your chart or graph and just proceed to writing the story you want? Of course you should trace your scenes and document your way in your story, just as any good writer would urge you to do.

3. Dialogues

One of the strongest sides of Ink is the ease with which you can write dialogues. In an interactive book it’s utterly expensive to create a dynamic dialogue and, in the end, it’s just not worth it. In the digital world, the volume restrictions are gone.

"Release the prince! Surrender and your life will be spared!" * "We’re not who you take us for." The man with the mustache looks befuddled. His grip, however, remains firm on his sword. ** "Who are you looking for?" ** "I am Wayward of the Nameless People and these are my warriors." ** "We come in peace to meet with the Zin of Outremer." * "What prince?" * "Who are you?" - Your words does not seem to please the man with the mustache, so he takes out his sword and points it to you. "Last chance. Where is the prince?"

So far in the previous examples we’ve kept using choices that link to other places. However, this would require us to uniquely name every destination in the story which generally discourages minor branching and imitates the printed gamebook format.

One of the most powerful features of Ink are the so called weaves. Your story splits with choices that help your narrative and gathers again at the "-" symbol. You can even nest choices to further drill down one path before merging again with the main flow.

4. Loops

One of my favorite Ink features are the loops. Reusable chunks of content.

Your hand freezes, holding the pint at your lips. - (vaingloria_conversation) * "Dead? How did he die?"[] you ponder. "They say it was a rogue arrow in a hunting accident. However, the circumstances of his death are not very clear" * "What happens when the leader dies?"[] you ask. "When the leader of the Nameless People dies, or steps aside due to illness or old age, a new Clansmoot is held," Naan explains. * {loop} "We don't have any more time to lose[".]," you say. ->done - (loop) { -> vaingloria _conversation | -> vaingloria _conversation | } - (done) Abbas puts down the jug reluctantly, wipes his mouth and says: "It is a pity for the good wine."

This is a relatively simple example that showcases the powerful concept.

Key aspects are the gather points of the weaves that we can label:

- (vaingloria_conversation)

- (loop)

- (done)

That way, when marked, they serve both as anchors which you can divert to as well as flags you can check if the reader has been there; or even as counters to check how many times the player has read it.

The conversation would go on and on until you hit the exit line. The exit choice is hidden until at least one other choice is made. This is particularly good when you want the reader to obtain some information before giving him/her the ability to "skip".

5. Complex logic with functions and variables

Yes, you’ve read correctly. Functions and variables are available when using Ink. There were glimpses of these in my Game Logic examples that I will now enclose in more detail.

Let’s say you have a skill system. You could describe your skills using LIST notation in Ink like this:

LIST Skills = strength, agility, science, speech, insight

Lists are very nifty for tracking state. That way I can check if the player has a particular skill like so:

{Skills has strength}

I can even check if the player has a combination of Skills, like so:

{Skills has (strength, science)}

What if I need to know how many skills the player has mastered? Easy, just use one of the basic queries available:

{LIST_COUNT(Skills)}

I also need a function that I can use when the player masters a new skill. Its purpose would be to store the skill the player knows and generate a proper output.

=== function learn_new_skill(name) === ~ Skills += name ~ return "You've mastered a new skill! {name}!"

Now all I have to do is to add anywhere in my text:

{learn_new_skill(strength)}

Making condition checks against the skills variable in my story would be as easy as:

{Skills has strength: Your muscles pop out like ship ropes. Your face is covered in sweat. Just when you start wondering if you made a mistake accepting this challenge, he groans and his hand weakens. You slam it against the table and relax, exhausted but pleased with the victory. - else: You resist for a while, but at the end, you cannot hold on and you surrender. Your hand thumps against the table, brought down by his unstoppable strength. }

In a similar fashion, you can describe an inventory system, or a state machine to track how much you have pissed the local lord.

6. All of this in its own scripting IDE

Writing and updating your story as I did in an XML format (or JSON, or whatever format you come up with) requires an IDE. Yes, as a programmer you might use your favorite IDE because you are used to complex syntax but what if you need writers? What if you need editors? Getting these people on board with an IDE is an overkill to your scope. Is copying and pasting your scripts to MS Word so that they read it a solution? I’ve tried that and while it works to some extent it doesn’t scale well. You constantly update your source, track changes, leave comments and maintain multiple documents. Just more and more overhead.

This is where Ink shines for me once again. Inkle have created an editor for Ink called Inky. It has syntax highlights, comments, TODOs, find + replace, jump to sections, variable tracking and more.

However, the best feature that blew my mind is the live-action runtime execution with HTML/JS just as you type it!

Your screen is split in two, on the right side you see your writings in Ink coming to live with choices you can click and test on the fly.

When the core of my game is text and story, as soon as I am done with it, I can send over the files to testers that can give me feedback even before I’ve finished the actual Unity prototype and interface.

I cannot overstate how much help this has been to me and my project.

7. Multi-platform support

Ink has a plugin that integrates with Unity very nicely. I can safely put Ink in charge of taking care of the story state and use Unity to add visuals, UI, and more interactivity:

It’s also open source so fans and contributors have made experimental builds that run on various other platforms, such as Web (with Javascript)!

If you can’t find your platform but you are advanced, you can also write a parser of your own, based on their docs.

Conclusion

I have only scratched the surface of the powerhouse that Ink is. There are many more features that you will discover for yourself and your projects.

Once again, special thanks to Inkle Studios for creating and open-sourcing Ink - you guys rock!

Learn everything about Ink from its main web page: https://www.inklestudios.com/ink/

You can download the Inky editor here: https://github.com/inkle/inky

Check the official documentation on Writing with ink: https://github.com/inkle/ink/blob/master/Documentation/WritingWithInk.md