Making a Visual Novel with Unity (1/5) - Introduction to Ink

If you like narrative games you might have heard of Inkle Studios. They released Sorcery, 80 days and, most recently, Pendragon. You haven’t? I recommend you check them out.

What’s interesting is that their games are developed with ink, a scripting language those guys made to help them develop text games more easily. The best part is, it’s open source and free to use!

Ink allows you to write stories witch branching narrative, choices and dialogues very easily. It comes with Inky - an editor, where you can write stories using Ink, debug them and run in the same window to test your choices etc. There is also Inklewriter, which offers a simple interface to let you create those stories without needing to get deep into the scripting magic. To be honest, I haven’t used it. If your only goal is to write and share some interactive fiction, you might want to give it a go. However, if you’re planning to build an actual story driven game with Unity, read on.

Working with Ink

Inkle offers an extremely comprehensive documentation with examples on how to use Ink and all of its features. There is also an example game where they show those features in action. However, if you’re like me, you may find it a little bit daunting to go through all that text and try to make sense of it. That’s why I prepared this small example to show you the basics. Truth to be told, when making my own game, these basics covered 90% of what I needed.

If you want to follow along, download Inky and let's get started!

Knots

In Ink, the story is divided into knots. Think of them as scenes within your story. Each scene can have a dialogue, a paragraph of text, some choices etc. It’s up to you how you want to structure your story. What I found works best is to break into another knot when we get to a choice, a scene change or when there is a context change (like the player needing to perform an action). How do knots look, you ask? Very good question, you mark them like so:

=== knot_title ===
Here goes knot content

Simple as that!

Now, I said that a story is built from multiple knots so you will need a way to move between them. You do it like so:

-> knot_title

This is also used to tell ink, which knot is the entry point for your story, so make sure you add this line at the top of your script (with the correct title). This is very important, as ink wouldn’t know where to start otherwise!

Now, let's see an example script. Please feel free to paste the following into your Inky to see it in action.

-> start_knot // this tells ink where to start the story

=== start_knot ===
Hello from the start knot!
Now we'll go to knot 2!
-> knot_2

=== knot_2 ===
Hello from knot 2!
-> END // this marks the end of the story

You’ll see knot_2 ends with -> END line. This is to mark where the story ends. You want to make sure all of your knots have an ending, whether it's moving into the next knot or ending the story (Inky will remind you of that too).

Okay, so let’s get to that exciting bit now, the choices. In your game, you’re very likely to want to give a player a decision to make. Depending on what they choose, we want to branch off into a different knot. How is that done then?

*** Choice 1
[Continuation for choice 1]

*** Choice 2
[Continuation for choice 2]

Each choice is marked with *** and what follows it will only be visible after the player chooses that option. Let’s add that into our example:

-> start_knot // this tells ink where to start the story

=== start_knot ===
Hello from the start knot!
Now we'll go to knot 2!

-> knot_2

=== knot_2 ===
Hello from knot 2!
Time for a personality test.
Red pill or blue pill?
*** Red pill
My god, how brave!
-> END // this marks the end of the story
*** Blue pill
Bold move, my friend
-> END // this marks the end of the story

Now, you see each choice has its own continuation and we’ve performed our first branching! You can test your story in Inky, on the right hand side section of the window.

Testing in Inky

You can imagine it can get quite difficult to read and (and write) if you add a lot of text for each choice. This is why, as mentioned before, I like to split into further knots after each decision to make it easier to handle. Let’s change our example:

-> start_knot // this tells ink where to start the story

=== start_knot ===
Hello from the start knot!
Now we'll go to knot 2!
-> knot_2

=== knot_2 ===
Hello from knot 2!
Time for a personality test.
Red pill or blue pill?
*** Red pill
-> red_pill
*** Blue pill
-> blue_pill

=== red_pill ===
My god, how brave!
-> END

=== blue_pill
Bold move, my friend
-> END

Now you see that each option has its own knot. From the player’s point of view, nothing has changed. However, doing things this way will prove very useful when localising your game (you never know when you might need that!) and even just to make it more readable and easy to work with.

Choices matter

We’ve learned how to branch our story based on different choices, but in many cases you’ll still want to share some same parts of the story afterwards. Otherwise it could get out of hands very quickly. If you want to do that, you just create a new knot and point into it from your choice specific knots.

-> start_knot

=== start_knot ===
Hello from the start knot!
Now we'll go to knot 2!
-> knot_2

=== knot_2 ===
Hello from knot 2!
Time for a personality test.
Red pill or blue pill?
*** Red pill
-> red_pill
*** Blue pill
-> blue_pill

=== red_pill ===
My god, how brave!
-> continue_conversation

=== blue_pill
Bold move, my friend
-> continue_conversation

=== continue_conversation
Alright. You have answered my question.
-> END

Simple as that! These choices are meant to matter though. For example, what if we want to make a comment based on what the player has done? The most simple way is to check if we’ve visited a specific knot or not (hehe!). This is how to make that check:

{ knot_name: [further story content] }

Do you want to check if we’ve NOT visited a knot?

{ not knot_name: [further story content] }

By using this pattern, you can check for many different conditions and define the flow of the story accordingly. Let’s see it in action:

-> start_knot

=== start_knot ===
Hello from the start knot!
Now we'll go to knot 2!
-> knot_2

=== knot_2 ===
Hello from knot 2!
Time for a personality test.
Red pill or blue pill?
*** Red pill
-> red_pill
*** Blue pill
-> blue_pill

=== red_pill ===
My god, how brave!
-> continue_conversation

=== blue_pill
Bold move, my friend
-> continue_conversation

=== continue_conversation
Alright. You have answered my question.
{ red_pill:
You chose the red pill. But I'm still not sure I can trust you
}
{ not red_pill: -> no_red_pill_comment}
->END

=== no_red_pill_comment ===
You didn't choose the red pill. I'm not sure I can trust you.
-> END

You'll notice I’ve used both approaches of typing the text directly after the check and of pointing to another knot. It's up to you how you want to structure your file and what's more convenient for you. Just bear in mind using knots can be a huge help later on.

Testing in Inky

Variables and conditions

If you’re familiar with programming, you might have thought the way to check if we’ve seen a knot is similar to ‘if-statements’. That’s exactly what it is and it can be used to check various different conditions! Before we do that though, let’s talk about variables.

In your story you might want to keep track of some things like player name, health points, relationship levels between people, experience points etc. You can do that in ink by using variables. Some example ones can look like so:

VAR PlayerName = “John”

VAR ChoseRedPill = false

VAR HealthPoints = 50

As you can see, we can have various types of variables. They can be numbers, text, or booleans (true/false values). After defining a variable, you can access it anywhere in your story, whether it's to display it, update it or use it to make another decision. Let’s add those variables into our game.

// Global variables
VAR PlayerName = "John"
VAR ChoseRedPill = false
VAR HealthPoints = 50

-> start_knot

=== start_knot ===
Hello, {PlayerName}! This is the starting knot! // display variable value
Now, we'll go to knot 2!
-> knot_2

=== knot_2 ===
Hello from knot 2!
Time for a personality test.
Red pill or blue pill?
*** Red pill
~ChoseRedPill = true // update variable value
-> red_pill
*** Blue pill
~HealthPoints -= 20 // update variable value
-> blue_pill

=== red_pill ===
My god, how brave!
-> continue_conversation

=== blue_pill
Bold move, my friend
-> continue_conversation

=== continue_conversation
{ HealthPoints < 50: You seem quite weak. I wonder why...}
Alright. You have answered my question.
{ ChoseRedPill:
You chose the red pill. But I'm still not sure I can trust you
}
{not red_pill: -> no_red_pill_comment}
->END

=== no_red_pill_comment ===
You didn't choose the red pill. I'm not sure I can trust you.
-> END

As you can see, to display a variable within the text, use ~VariableName

To update a variable, use ~VariableName = [new value]. In case of numbers you can use += or -= to increase or decrease the value by a certain amount (e.g. ~ HealthPoints -= 30).

To check for a condition using variables, follow the same pattern as before, { condition : result }. For example:

{ ChoseRedPill:
You chose the red pill. But I'm still not sure I can trust you
}

Or

{ HealthPoints < 50: You seem quite weak. I wonder why...}

This will make the line appear only when HealthPoints value is lower than 50.

Testing with Inky

Wrapping up

That’s it for the basics! There is a lot more to cover in regards to Ink, but I’m confident you can start writing great interactive stories or prototypes of your games with just the methods we’ve covered here. However, if you’re interested in learning more Ink magic, check out the official documentation. It’s amazing how much you can do with it!

Next time, we’ll learn how to connect Ink to our Unity project.

Happy coding!

More in this series