Comparison with Ink and Yarn Spinner
Ink and Yarn Spinner are the two other tools commonly used for interactive narrative. Here's how they compare.
This section tries to make an honest comparison, as of when this article was written, and is in no way claiming one tool is better or worse than the others. I believe there is room for diversity in software for writing interactive fiction. Loreline is just joining the party!
About Ink
Ink was created by Inkle and has been used in games like 80 Days and Heaven's Vault. Its structure is built around knots (major sections), stitches (sub-sections), and a weave system for inline branching. Flow control uses diverts (-> knot_name), and choices use * (single-use) or + (sticky). Ink has a mature C# runtime with an official Unity plugin. A JavaScript port (inkjs) is also officially supported; community C++ ports exist as well, including inkcpp (a standalone library also available as a UE plugin) and Inkpot (an UE5 plugin by The Chinese Room). Ink has no built-in localization system, so developers typically build their own tooling on top of the #tag metadata.
About Yarn Spinner
Yarn Spinner uses a screenplay-like format organized into nodes, each delimited by --- and ===. Commands use <<double angle brackets>> and options -> (v2+). It has strong Unity integration and a visual node editor for building dialogue graphs. It is primarily a C#/.NET tool; an official C++ implementation for Unreal Engine is in active development (beta as of early 2026).
Syntax comparison
Here is the same short script written in all three languages: narration, dialogue, a choice with a conditional option, and a variable update:
state
coffeesOrdered: 0
character barista
name: Alex
beat CoffeeShop
The warm aroma of coffee fills the cafe.
barista: Hi there! What can I get you?
choice
A regular coffee
barista: Coming right up!
A decaf if coffeesOrdered > 2
barista: Switching to decaf? Probably wise.
coffeesOrdered += 1
if coffeesOrdered > 3
barista: That's quite a lot of coffee today...
VAR coffeesOrdered = 0
=== CoffeeShop ===
The warm aroma of coffee fills the cafe.
Alex: Hi there! What can I get you?
+ A regular coffee
Alex: Coming right up!
+ {coffeesOrdered > 2} A decaf
Alex: Switching to decaf? Probably wise.
-
~ coffeesOrdered = coffeesOrdered + 1
{coffeesOrdered > 3:
Alex: That's quite a lot of coffee today...
}
-> DONE
title: CoffeeShop
---
<<declare $coffeesOrdered = 0>>
The warm aroma of coffee fills the cafe.
Alex: Hi there! What can I get you?
-> A regular coffee
Alex: Coming right up!
-> A decaf <<if $coffeesOrdered > 2>>
Alex: Switching to decaf? Probably wise.
<<set $coffeesOrdered = $coffeesOrdered + 1>>
<<if $coffeesOrdered > 3>>
Alex: That's quite a lot of coffee today...
<<endif>>
===
All three scripts tell the same story with comparable length but different conventions. Each language has its own way of handling declarations, choices, and branching.
Detailed comparison
| Loreline | Ink | Yarn Spinner | |
|---|---|---|---|
| Structure | beat sections with indentation; nested choices and alternatives |
Knots (major sections) + stitches (sub-sections) + weave for inline branching | Named nodes, each delimited by --- / === |
| Choices | choice keyword + indentation; inline if conditions on individual options |
* (disappears once chosen) or + (always available); the marker controls whether each option reappears; weave handles local branching |
-> options with <<if>> conditions |
| Characters | First-class: declared with typed fields, readable and writable throughout the story | Handled by the host app | Handled by the game engine |
| State | Variables live in state blocks: top-level state (shared across the whole script); state inside a beat (scoped to that beat); new state (temporary, reset each time the beat runs) |
Built-in variables: VAR (global), TEMP (local), LIST (enum-like) |
Built-in variables; declared with <<declare>> |
| Text tags | Built-in <tag> on any dialogue line; passed to the host as-is without affecting flow |
#tag appended to output text |
<<command>> blocks for game instructions |
| Alternatives | Built-in with 5 modes: sequence, cycle, once, pick, shuffle |
Sequences and shuffle via & / ~ choice markers |
Not built-in; handled through custom scripting |
| Functions | 60+ built-in functions. Custom functions can be written directly in Loreline scripts | Built-in functions for common operations; custom logic requires external functions written in C# or JavaScript | Custom logic handled through C# commands and functions registered from the host app |
| Localization | Built-in: #key tags in-script + .lor translation files per language |
Not built-in; teams typically build on #tag metadata |
CSV string table export/import |
| Targets | JS, TS, C#, C++, Java, Python, Lua, Haxe, all from a single Haxe codebase | C# official (Unity); JS official (inkjs); C++ community only (inkcpp, Inkpot) | C# / .NET official (Unity); C++ for UE in active development (beta) |
Ink and Yarn Spinner are mature, well-documented, and have been used in shipped games for years. Loreline is newer and takes a different approach: indentation-based structure, characters and state as core language features, and native compilation to multiple platforms from a single codebase. The best choice comes down to team familiarity, target engine, and which scripting style fits how you want to write.
Try Loreline
The quickest way to get started is the Playground, where you can write and run Loreline scripts directly in the browser without installing anything. To go further, the Writer's Guide covers the full language step by step.