basic

  • can comment //

tags

  • can tag via # - following string content can be read by the game.
    • use case: trigger different art based on emotion, e.g. # surly
  • delimit multi tags with more #
  • can mark ink with audio filenames
  • tag can be written above or at end of the line, and all tags are in _inkStory.currentTags

choices

  • *
  • w/o further flow instr, choice flows into next line of text
  • put choice text in square brackets if you don’t want it to be printed
    • * [hello]

knots

  • fundamental structural unit of content
  • 2+ equals signs, like === top_knot ===
    • name must be single word
  • knots don’t run automatically, call them
    • -> top_knot
  • need ->END to signal end of flow

diverts

  • -> direct to a knot name to tell story where to go
    • seamless, can happen mid-sentence too

glue

  • default - line-breaks before every new line of content
  • change this with <>

branching + joining

are done with knots and diverts

  • can use diverts to loop content

includes, stitches

  • knots can be subdivided into stitches
    • single equals sign =
=== the_orient_express ===
= in_first_class
	...
= in_third_class
	...
= in_the_guards_van
	...
= missed_the_train
	...
  • knot can represent a scene, and stitches for events within the scene
  • stitches have unique names
*	[Travel in third class]
	-> the_orient_express.in_third_class
  • first stitch is default when diverting to a knot with stitches

  • can also have non-stitch content in a knot w stitches, but need to manually divert into a stitch after that

  • combine script files via INCLUDE

varying choices

  • default - choices can only be chosen once, so if you loop, chosen options will disappear
    • fallback choice - choice without text

sticky choices

    • bullet for choices that don’t disappear
  • fallbacks can be sticky

conditional choices

*	{ not visit_paris } 	[Go to Paris] -> visit_paris
+ 	{ visit_paris 	 } 		[Return to Paris] -> visit_paris

*	{ visit_paris.met_estelle } [ Telephone Mme Estelle ] -> phone_estelle
  • can check if a piece of content has been seen
  • note that knot_name test is true if any stitch inside has been seen
  • conditionals don’t override once-only option behavior - need sticky
  • can use multiple conditionals
  • ink supports && and || syntax too
    • recommended to use not tho instead of !
  • these are read counts not booleans - can check count

variable text - alternatives

sequence

  • set of alternatives tracking how many times its been seen - each time shows next element; when it runs out of content, shows final
{I bought a coffee with my five-pound note.|I bought a second coffee for my friend.|I didn't have enough money to buy any more coffee.}

cycles

  • loop, don’t end It was {&Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday} today.

can use ~ for shuffle to randomize output

conditional text

{met_person: i remember seeing them here}

functions***

TURNS_SINCE(->knot)

  • number of player inputs since a given knot/stitch was seen useful function
=== function came_from(-> x)
	~ returns TURNS_SINCE(x) == 0

weave

  • simplify story flows with always-forward direction

gather

  • -
  • all options gather at this same ending
  • flow is guaranteed to start at top and ‘fall’ to the bottom

nested flow

  • add sub choices
- 	"Well, Poirot? Murder or suicide?"
	*	"Murder!"
	 	"And who did it?"
		* * 	"Detective-Inspector Japp!"
		* * 	"Captain Hastings!"
		* * 	"Myself!"
	* 	"Suicide!"
	-	Mrs. Christie lowered her manuscript a moment. The rest of the writing group sat, open-mouthed.
  • gather points can be nested too
  • options separate paths, gathers bring them back together
  • unlimited nesting levels

weave tracking

  • lines in a weave dont have addr, can’t be diverted to
  • to remember what a player has seen, we can add labels with (label_name) to anything
    • important for options later that are only unlocked based on the player having seen specific content
=== meet_guard ===
The guard frowns at you.

* 	(greet) [Greet him]
	'Greetings.'
*	(get_out) 'Get out of my way[.'],' you tell the guard.

- 	'Hmm,' replies the guard.

*	{greet} 	'Having a nice day?' // only if you greeted him

* 	'Hmm?'[] you reply.

*	{get_out} [Shove him aside] 	 // only if you threatened him
	You shove him sharply. He stares in reply, and draws his sword!
	-> fight_guard 			// this route diverts out of the weave

-	'Mff,' the guard replies, and then offers you a paper bag. 'Toffee?'
=== fight_guard ===
...
= throw_something
*	(rock) [Throw rock at guard] -> throw
* 	(sand) [Throw sand at guard] -> throw

= throw
You hurl {throw_something.rock:a rock|a handful of sand} at the guard.

vars and logic

  • global variables can be set and read from anywhere
  • VAR - initial value determines type of variable
  • can store diverts as variables
  • global vars can be touched from runtime too
  • ink layer is good for storing gameplay vars - no save/load issues + the story can react to current values
  • ~ says a line will do logic
=== set_some_variables ===
	~ knows_about_wager = true
	~ x = (x * x) - (y * y) + c
	~ y = 2 * x * y
  • a string defined in ink will always be a string, even if it has ink?

knots do logic but they aren’t quite functions, which have call stack and ret vals

  • CONST

function

  • cannot have stitches, diverts, choices
  • can call functions, include printed content, return val of any type, recurse
  • declare a knot to be one == function hi ==
  • can be called inline

external functions

  • call C# functions in game

visual novel strategies

  • plan out high-level story flow via diagram or twine first
  • track key decisions with variables
  • branch endings with conditions

running ink with Unity

https://github.com/inkle/ink/blob/master/Documentation/RunningYourInk.md

  • create a wrapper MonoBehavior for ink Story
using Ink.Runtime;

public class Script : MonoBehaviour {

	// Set this file to your compiled json asset
	public TextAsset inkAsset;

	// The ink story that we're wrapping
	Story _inkStory;
	
	void Awake()
	{
	    _inkStory = new Story(inkAsset.text);
	}
	
	while (_inkStory.canContinue) {
	    Debug.Log (_inkStory.Continue ());
		// if no more content, check for choices
		if( _inkStory.currentChoices.Count > 0 )
	    {
	        for (int i = 0; i < _inkStory.currentChoices.Count; ++i) {
	            Choice choice = _inkStory.currentChoices [i];
	            Debug.Log("Choice " + (i + 1) + ". " + choice.text);
	        }
	    }

	    //...and when the player provides input:

        _inkStory.ChooseChoiceIndex (index);

    //And now you're ready to return to step 1, and present content again.
	}
  • get ink tags to trigger other behavior

  • can make knot tags at the very top of a knot

  • tags can have vars like {character}: Hello there! #{character}_greeting.jpg

  • can save and reload story state via json

  • use unity error handling to avoid ink errors throwing exceptions

_inkStory = new Story(inkAsset.text);

_inkStory.onError += (msg, type) => {
    if( type == Ink.ErrorType.Warning )
        Debug.LogWarning(msg);
    else
        Debug.LogError(msg);
};

other useful runtime funcs

_inkStory.ChoosePathString("myKnotName"); _inkStory.ChoosePathString("myKnotName.theStitchWithin");

  • note, can’t reference labels like this, only knots and stitches

_inkStory.variablesState["player_health"] = 100 int health = (int) _inkStory.variablesState["player_health"]

_inkStory.state.VisitCountAtPathString("...");

variable observers

  • register function to be called whenever a story variable changes
    • enables state of ink vars to be reflected directly in UI
_inkStory.ObserveVariable ("health", (string varName, object newValue) => {
    SetHealthInUI((int)newValue);
});

external functions

  • define game functions callable from ink
    EXTERNAL playSound(soundName)
_inkStory.BindExternalFunction ("playSound", (string name) => {
	_audioController.Play(name);
});

besides this, can also just use text to write instructions to the game and define game-side custom parser

tunnels??

threads??

lists

  • list of states for state tracking
  • like an in/out nameboard
    • names in parentheses are in the initial state of a list
    • LIST here = Jane, (Pat), Joe
  • lists provide
    • flags: each entry is event, use += to mark event as occured, test with ?
    • state machine: each entry is state, use = to set, ++ and — to step forwrd or back, test with == or >
    • properties: each list reps a property