Skip to content

Ink Scripting Guide

Ink is a powerful scripting language for writing interactive narratives. This guide covers how to use Ink for Encounters stories.

Ink Basics

What is Ink?

Ink is a narrative scripting language created by Inkle Studios. It allows you to write branching stories with:

  • Linear narrative sequences
  • Player choices
  • Variables and state tracking
  • Conditional logic
  • Functions and reusable content

For comprehensive Ink documentation, see the official Ink guide.

Encounters-Specific Features

Conversation State Management

Track where the player is in the conversation:

ink
VAR conversation_state = "initial"

-> start

== start ==
Initial messages here
~ conversation_state = "waiting_for_response"
-> conversation_choices

== conversation_choices ==
* [Choice 1]
  ~ conversation_state = "responded_to_choice_1"
{{ ... }}
  -> response_1

Cross-Conversation Variables

Variables can be shared between conversations for complex stories:

ink
// In mum.ink
== on_police_called ==
I'm calling the police now!
~ police_contacted = true
-> END

// In alex.ink
== check_police_status ==
{police_contacted:
  I heard mum called the police.
- else:
  Should we call the police?
}
-> END

Triggered Events

Create knots that other conversations can trigger:

ink
== on_mum_police_contacted ==
Mum just called the police. #typing:3s #delay:4s
They said they're opening a missing person case now.
~ conversation_state = "reacted_to_police"
-> END

This knot can be triggered by events in other conversations.

Sequences

Show different content on repeated visits:

ink
== repeated_question ==
{
- First time asking
- Second time asking
- Third time asking
- They keep asking...
}
-> END

Conditional Text

Inline conditions within messages:

ink
{police_contacted: The police are already involved. | Maybe we should call the police.}

Example: Complete Conversation

Here's a complete example showing best practices:

ink
// alex.ink - Conversation with Alex about missing Sarah

// State variables
VAR police_contacted = false
VAR alex_knows_player = false
VAR club_info_shared = false
VAR conversation_state = "initial"

-> start

== start ==
// Initial messages that appear when conversation opens
Like my new avatar? #initial #read #image:alex-avatar.jpg
Have you seen this funny video? #link:www.youtube.com/watch?v=dQw4w9WgXcQ #initial #read
Looks great! And lol that video 😂 #me #initial #read
Hey Sarah, have you seen my keys? #initial #read
I think they're on the kitchen counter #me #initial #read
Sarah? You were supposed to meet me an hour ago. Where are you? #initial
This is really weird... you always text back immediately. #initial

// Transition to interactive mode
~ conversation_state = "waiting_for_response"
-> conversation_choices

== conversation_choices ==
// Player's available choices
* [Hi, I found Sarah's phone and saw your messages]
  -> found_phone_response
* [Where was Sarah last seen?]
  -> last_seen_response
* {alex_knows_player and not club_info_shared} [Tell me more about the club]
  -> club_details_response
* {not police_contacted} [We should call the police]
  -> police_response
-> END

== found_phone_response ==
Wait... who is this? #typing:2s
You're texting from Sarah's phone but you're not Sarah. #typing:1s
Did you find her phone? Oh god, something's happened to her hasn't it? #typing:3s
~ alex_knows_player = true
~ conversation_state = "phone_explained"
-> END

== last_seen_response ==
She was at Club Neon last night. #typing:2s
Said she was meeting some new friends from work. #typing:2s
I offered to pick her up but she said she had a ride. #typing:1s
That was around 11 PM.
~ conversation_state = "shared_info"
-> END

== club_details_response ==
Club Neon is on King Street in the city centre. #typing:2s
It's pretty new, opened about a month ago. #typing:2s
Sarah said her colleagues Emma and Jake were going to meet her there.
~ club_info_shared = true
~ conversation_state = "shared_club_info"
-> END

== police_response ==
Yes, you're right. I should have called them already. #typing:2s
But they always say to wait 24 hours... #typing:1s
It's been about 18 hours now.
~ conversation_state = "police_discussion"
-> END

// Event triggered from another conversation
== on_mum_police_contacted ==
Mum just called the police. #typing:3s #delay:4s
They said they're opening a missing person case now.
~ police_contacted = true
~ conversation_state = "reacted_to_police"
-> END

Best Practices

Writing Style

Natural Dialogue

Write messages as real people would text - use contractions, casual language, and emojis where appropriate.

Good:

ink
Hey! Where are you? 😊
I'm getting worried...

Avoid:

ink
Hello. I am inquiring about your current location.
I am experiencing feelings of concern.

Pacing

  • Use timing tags to create realistic conversation flow
  • Don't overwhelm the player with too many messages at once
  • Build tension with delays and typing indicators
  • Vary timing - quick responses for urgent moments, longer delays for thinking
ink
Let me check something... #typing:3s #delay:2s
Oh no. #typing:1s #delay:3s
You need to see this. #image:evidence.jpg #typing:2s

Timing Guidelines:

  • Short messages: 1-2 seconds typing
  • Medium messages: 2-4 seconds typing
  • Long messages: 4-6 seconds typing
  • Very long messages: 1-2 minutes typing
  • Quick pause: 1-3 seconds delay
  • Emotional pauses: 3-10 seconds delay
  • Short activity: 1-5 minutes delay (driving, searching, etc.)
  • Longer activity: 10-60 minutes delay (meetings, investigations)
  • Major time jumps: 1+ hours delay (next day, later that evening)

Choice Design

  • Offer 2-4 choices at a time (avoid overwhelming)
  • Make choices meaningful - they should affect the story
  • Use clear language - player should understand what each choice means
  • Vary choice types - questions, actions, emotional responses

State Management

  • Track important decisions with variables
  • Use descriptive state names: "police_contacted" not "state1"
  • Update state consistently when story progresses

Testing

  • Test all branches - make sure every choice works
  • Check timing - ensure delays feel natural
  • Verify conditions - conditional choices should appear correctly
  • Test cross-conversation events if you use them

Common Patterns

Delayed Revelation

ink
I need to tell you something... #typing:3s #delay:2s
It's about Sarah. #typing:2s #delay:3s
She's been lying to us. #typing:2s

Information Gathering

ink
* [What time did she leave?]
  ~ time_asked = true
  Around 11 PM, I think.
  -> more_questions
* [Who was she with?]
  ~ companions_asked = true
  Some people from work.
  -> more_questions
  
== more_questions ==
* {time_asked and companions_asked} [That's all I need to know]
  -> END
* {not time_asked} [What time did she leave?]
  -> time_response

Emotional Progression

ink
VAR trust_level = 0

* [I want to help find Sarah]
  ~ trust_level = trust_level + 1
  Thank you... I really appreciate that.
  
* [Tell me everything you know]
  {trust_level > 2:
    Okay, I trust you. Here's what happened...
  - else:
    I don't know if I should tell you everything yet.
  }

Debugging Tips

Common Errors

Missing -> END:

ink
== my_knot ==
Some text here
// ERROR: No ending!

Fix:

ink
== my_knot ==
Some text here
-> END

Undefined knot:

ink
-> non_existent_knot  // ERROR: Knot doesn't exist

Syntax errors in conditions:

ink
{variable = true}  // ERROR: Use == for comparison
{variable == true}  // CORRECT

Testing Your Ink

Build your story to check for errors:

bash
npm run build:story

The build script will show compilation errors with line numbers.

Quick Reference: All Encounters Tags

Here's a complete list of all available tags for quick reference:

TagDescriptionExample
#initialMessage exists before player joinsHey! #initial
#readMark message as already readHi there #initial #read
#meMessage from the playerThanks! #me #initial #read
#systemSystem message (no sender)User joined #system
#typing:XShow typing indicator (s/m/h)Let me check... #typing:3s
#delay:XWait before showing message (s/m/h)I found it! #delay:5s
#image:filenameDisplay an imageLook at this! #image:photo.jpg
#audio:filenameDisplay audio playerListen #audio:voice.wav
#video:filenameDisplay video playerWatch this #video:clip.mp4
#gallery:idUnlock and display gallery itemFound this #gallery:photo-001
#link:urlCreate a clickable linkCheck this out #link:example.com
#contact:idSpecify sender in group chatHello everyone #contact:alex
#text_input:promptRequest text input from player# text_input:Enter name
#attach_image:promptRequest image selection# attach_image:Select photo
#notify:conv:knotTrigger knot in another conversationDone! #notify:alex:on_event
#unlockConversation:id[:delay]Unlock a conversation#unlockConversation:group:10s
#unlockContact:id[:delay]Unlock a contact#unlockContact:detective:5s
#unlockNews:id[:delay]Unlock a news article#unlockNews:breaking:2m
#unlockNote:id[:delay]Unlock a note#unlockNote:clue:30s

Tag Compatibility

Can be combined:

  • #initial + #read + #me - Show player's past messages
  • #typing:Xs + #delay:Xs - Realistic message timing
  • #image:file + #typing:Xs - Image with typing indicator
  • #delay:Xs + #unlock:id - Unlock conversation after delay
  • #notify:conv:knot + #typing:Xs - Trigger with timing

Group chat specific:

  • #contact:id - Only use in group conversations

Cross-conversation:

  • #notify:conv:knot - Trigger events in other conversations
  • #unlock:conv-id - Make new conversations available

Visual Studio Code with Ink Extension

The best way to write Ink scripts is using Visual Studio Code with the Ink extension:

Why VS Code + Ink Extension?

  • Syntax highlighting for .ink files
  • Auto-completion for Ink keywords
  • Real-time error detection
  • Jump to knot definitions
  • Outline view of your story structure

Installation:

  1. Download Visual Studio Code (free)
  2. Install VS Code
  3. Open VS Code
  4. Click Extensions icon (left sidebar) or press Ctrl+Shift+X / Cmd+Shift+X
  5. Search for "Ink" by inkle
  6. Click Install

Using the Extension:

  • Open any .ink file and it will automatically highlight syntax
  • Hover over knots to see previews
  • Use Ctrl+Click / Cmd+Click on knot names to jump to them
  • View story structure in the Outline panel

Other Resources

Next Steps

Released under the MIT License.