inkle ink Documentation

repository·master·Indexed 26 days ago

https://github.com/inkle/ink

A scripting language for writing interactive narratives. Includes guides on using the Inky editor, integrating with Unity, using the inklecate command-line compiler, and implementing the C# runtime engine. Provides technical details on the Ink pipeline architecture, the StringParser, and the ink JSON runtime format.

Tokens
19.5K
Snippets
60
Records
98
Agent score
89%

What's inside ink

  1. Understand the Ink pipeline architecture

    master

    The Ink pipeline consists of three primary stages:

    1. Ink parser: Interprets Ink text files into a hierarchy of Parsed.Object objects.
    2. Runtime code generation: Converts the parsed hierarchy into a lightweight, JSON-based format optimized for the engine.
    3. Runtime engine: Executes the generated runtime code, iterating through content and managing control flow.
  2. Redirect return from a Tunnel

    master

    While tunnels typically return to the caller using ->->, you can force a tunnel to return to a specific different location instead of the original caller. This is useful for handling state changes (like death or game-over) that occur within a subroutine.

    Warning: Use this sparingly as it can make story flow difficult to track.

  3. Nest options using multiple asterisks

    master

    You can create sub-choices (nested options) within an existing choice by using multiple asterisks (*) instead of one. This allows a specific choice to trigger a follow-up sequence of questions or actions before returning to the main flow via a gather.

    • * represents a top-level option.
    • * * represents a sub-option (nested one level deep).
    • * * * represents a sub-sub-option, and so on.
    - "Well, Poirot? Murder or suicide?"
        * "Murder!"
            "And who did it?"
            * * "Detective-Inspector Japp!"
            * * "Captain Hastings!"
            * * "Myself!"
        * "Suicide!"
            "Really, Poirot? Are you quite sure?"
            * * "Quite sure."
            * * "It is perfectly obvious."
    - Mrs. Christie lowered her manuscript a moment.
  4. End a thread explicitly with `-> DONE`

    master

    A side-thread ends when it runs out of flow to process. If a thread has no content to offer (e.g., no conversation is available), you must mark the end explicitly using -> DONE.

    If you do not use -> DONE at the end of a thread that has no more content, the compiler may generate a warning about an unfinished story thread.

    Note:

    • You do not need -> DONE after an option has been chosen; once an option is selected, the thread becomes the normal story flow.
    • Do not use -> END to end a thread; -> END terminates the entire story flow.
    == thread_example ==
    I had a headache; threading is hard to get your head around.
    <- conversation
    <- walking
    -> DONE
  5. Manage choice persistence with sticky and fallback choices

    master

    By default, every choice (*) in ink can only be chosen once. If a player encounters a loop, they may run out of options.

    • Sticky Choices: Use a plus sign (+) instead of an asterisk to create a 'sticky' choice that remains available every time the player reaches that point.
    • Fallback Choices: A choice without text (e.g., * -> destination) is a 'fallback choice'. It is never displayed to the player but is automatically selected if no other valid options exist. This prevents 'out of content' runtime errors.
    • Default Content Fallbacks: You can create a default choice that includes text using the * -> syntax.
    === find_help ===
    
    You search desperately for a friendly face in the crowd.
    * The woman in the hat[?] pushes you roughly aside. -> find_help
    * The man with the briefcase[?] looks disgusted as you stumble past him. -> find_help
    * -> 
        But it is too late: you collapse onto the station platform. This is the end.
        -> END
    
    === homers_couch ===
    + [Eat another donut]
        You eat another donut. -> homers_couch
    * [Get off the couch]
        You struggle up off the couch to go to compose epic poetry.
        -> END
  6. Use Lists for Flags, State Machines, and Properties

    master

    Ink provides LIST construction to manage complex states. You can implement three distinct patterns:

    1. Flags

    Treat each list entry as an event that has occurred.

    • Use += to mark an event as having occurred.
    • Test presence using ? (is this entry in the list?) or !? (is this entry NOT in the list?).

    2. State Machines

    Treat each list entry as a sequential state.

    • Use = to set the state.
    • Use ++ to step forward to the next state or -- to step backward.
    • Test using == (equality) or > (is the current state after this one?).

    3. Properties

    Treat the list as a set of possible values for a property. To change a property, remove the old state and add the new one.

    • Use -= to remove a state.
    • Use += to add a state.
    • Use LIST_ALL(ListName) to clear all possible values from a variable assigned to a list.
    // Flags Example
    LIST GameEvents = foundSword, openedCasket, metGorgon
    { GameEvents ? openedCasket }
    { GameEvents ? (foundSword, metGorgon) }
    ~ GameEvents += metGorgon
    
    // State Machine Example
    LIST PancakeState = ingredients_gathered, batter_mix, pan_hot, pancakes_tossed, ready_to_eat
    { PancakeState == batter_mix }
    { PancakeState < ready_to_eat }
    ~ PancakeState++
    
    // Properties Example
    LIST OnOffState = on, off
    LIST ChargeState = uncharged, charging, charged
    VAR PhoneState = (off, uncharged)
    
    * {PhoneState !? uncharged } [Plug in phone]
        ~ PhoneState -= LIST_ALL(ChargeState)
        ~ PhoneState += charging
        You plug the phone into charge.
    * { PhoneState ? (on, charged) } [ Call my mother ]
  7. Divert to an option

    master

    You can divert to an option. The divert goes to the output of having chosen that choice, as though the choice had been chosen.

    Note behavior:

    • The content printed will ignore square-bracketed text [...].
    • If the option is marked as once-only, it will be marked as used up.
    - (opts)
        * [Pull a face]
            You pull a face, and the soldier comes at you! -> shove
    
        * (shove) [Shove the guard aside] You shove the guard to one side, but he comes back swinging.
    
        * {shove} [Grapple and fight] -> fight_the_guard
    
        - -> opts
  8. Implement incremental knowledge chains in Ink

    master

    You can model complex, evolving knowledge using LIST and VAR to create 'knowledge chains'. In these chains, each new fact supersedes the previous one. Using a custom function with LIST_RANGE and LIST_MIN, you can implement a reach function that automatically adds all intermediate states in a chain when a specific state is achieved. This allows for sophisticated logic like checking if a player is 'between' two specific facts using a custom between(x, y) function.

    // System: Incremental knowledge.
    // Each list is a chain of facts. Each fact supersedes the fact before 
    
    VAR knowledgeState = ()
    
    === function reached (x) 
       ~ return knowledgeState ? x 
    
    === function between(x, y) 
       ~ return knowledgeState? x && not (knowledgeState ^ y) 
    
    === function reach(statesToSet) 
       ~ temp x = pop(statesToSet) 
       { 
       - not x: 
          ~ return false 
    
       - not reached(x):
          ~ temp chain = LIST_ALL(x) 
          ~ temp statesGained = LIST_RANGE(chain, LIST_MIN(chain), x) 
          ~ knowledgeState += statesGained 
          ~ reach (statesToSet) 
          ~ return true 
    
        - else:
          ~ return false || reach(statesToSet) 
        } 
  9. Nest gather points for complex sub-scenes

    master

    Just like options, gather points can be nested. This allows you to create entire 'sub-weaves' within a larger story branch. If a player enters a nested branch, they will experience a sequence of choices and gathers that are contained within that specific path before the flow returns to the parent gather point.

    - "Well, Poirot? Murder or suicide?"
        * "Murder!"
            "And who did it?"
            * * "Detective-Inspector Japp!"
            * * "Captain Hastings!"
            - - "You must be joking!"
            * * "Mon ami, I am deadly serious."
            * * "If only..."
        * "Suicide!"
            "Really, Poirot? Are you quite sure?"
            * * "Quite sure."
            * * "It is perfectly obvious."
    - Mrs. Christie lowered her manuscript a moment.
  10. Use Alternatives for variable text

    master

    You can make text vary at the moment it is printed using alternatives. Alternatives are written inside {...} curly brackets, with elements separated by | symbols.

    Supported types of alternatives:

    • Sequences (default): Tracks how many times it has been seen and shows the next element. When content runs out, it repeats the final element.
    • Cycles (&): Similar to sequences, but loops the content.
    • Once-only (!): Similar to sequences, but displays nothing once the content runs out.
    • Shuffles (~): Produces randomized output.

    Alternatives can contain blank elements, be nested, include divert statements (->), and be used inside choice text.

    Caveat: You cannot start an option's text with { as it looks like a conditional. To use an alternative at the start of an option, escape the whitespace with a backslash \ .

  11. Build Ink from source

    master

    Build Requirements

    Building with command-line

    To build the project using the dotnet CLI:

    1. Navigate to the specific project directory (e.g., cd inklecate).
    2. Run the build command:
      dotnet build -c Release
    3. To produce a self-contained executable for a specific platform, use dotnet publish with a Runtime Identifier (RID):
      dotnet publish -r win-x64 -c Release --self-contained

    Recommended RIDs:

    • win-x64
    • linux-x64
    • osx-x64