Narrator
The Ink language parser and runtime implementation in Lua
Install / Use
/learn @astrochili/NarratorREADME

Narrator
Overview
The Ink language parser and runtime implementation in Lua.
Ink is a powerful narrative scripting language. You can find more information about how to write Ink scripts here. There is also Inky editor with useful features to test and debug Ink scripts.
Narrator allows to convert raw Ink scripts to the book (a lua table) and play it as story.
- 📖 A book is a passive model on the shelf like a game level.
- ✨ A story is a runtime state of the book reading like a game process.
Quick example
local narrator = require('narrator.narrator')
-- Parse a book from the Ink file.
local book = narrator.parse_file('stories.game')
-- Init a story from the book
local story = narrator.init_story(book)
-- Begin the story
story:begin()
while story:can_continue() do
-- Get current paragraphs to output
local paragraphs = story:continue()
for _, paragraph in ipairs(paragraphs) do
local text = paragraph.text
-- You can handle tags as you like, but we attach them to text here.
if paragraph.tags then
text = text .. ' #' .. table.concat(paragraph.tags, ' #')
end
-- Output text to the player
print(text)
end
-- If there is no choice it seems like the game is over
if not story:can_choose() then break end
-- Get available choices and output them to the player
local choices = story:get_choices()
for i, choice in ipairs(choices) do
print(i .. ') ' .. choice.text)
end
-- Read the choice from the player input
local answer = tonumber(io.read())
-- Send answer to the story to generate new paragraphs
story:choose(answer)
end
Alternatives
- defold-ink — The Ink language runtime implementation in Lua based on parsing compiled JSON files.
Showcase
- Cat's Day — A short card game about one furry.
- Rare Pets — A merge game for mobile about pets that become what they eat.
- Sensual Hunting (NSFW) — An adult only game where all the navigation and dialogs made with this library.
- The Secret Laboratory — A short card game about the labaratory director.
Features
Supported
- [x] Comments: singleline, multiline, todo's
- [x] Tags: global tags, knot tags, stitch tags, paragraph tags
- [x] Paths and sections: inclusions, knots, stitches, labels
- [x] Choices: suppressing and mixing, labels, conditions, sticky and fallback choices, tags
- [x] Branching: diversions, glues, gathers, nesting
- [x] Tunnels
- [x] Alternatives: sequences, cycles, once-only, shuffles, empty steps, nesting
- [x] Multiline alternatives: all the same + shuffle options
- [x] Conditions: logical operations, string queries, if and else statements, nesting
- [x] Multiline conditions: all the same + elseif statements, switches, nesting
- [x] Variables: assignments, constants, global variables, temporary variables, visits, lists
- [x] Lists: logical operations, multivalued lists, multi-list lists, all the queries, work with numbers
- [x] Game queries: all the queries without
TURNS()andTURNS_SINCE() - [x] State: saving and loading
- [x] Integration: external functions, variables observing, jumping
- [x] Migration: the ability to implement the migration of player's saves after the book update
- [x] Internal functions
Unsupported
- [ ] Threads
- [ ] Divert target as variable type
- [ ] Assigning string evaluations to variables
- [ ] Multiple parallel flows
Also there is a list of known limitations on the issues page.
Installation
Common case (Löve, pure Lua, etc.)
Download the latest release archive and require the narrator module.
local narrator = require('narrator.narrator')
Narrator requires lpeg as dependency to parse Ink content. You can install it with luarocks.
$ luarocks install lpeg
In fact, you don't need lpeg in the release, but you need it locally to parse Ink content and generate lua versions of books to play in your game. Use parsing in development only, prefer already parsed and stored books in production.
Defold
Add links to the zip-archives of the latest versions of narrator and defold-lpeg to your Defold project as dependencies.
https://github.com/astrochili/narrator/archive/master.zip
https://github.com/astrochili/defold-lpeg/archive/master.zip
Then you can require the narrator module.
local narrator = require('narrator.narrator')
Documentation
narrator.parse_file(path, params)
Parses the Ink file at path with all the inclusions and returns a book instance. Path notations 'stories/game.ink', 'stories/game' and 'stories.game' are valid.
You can save a parsed book to the lua file with the same path by passing { save = true } as params table. By default, the params table is { save = false }.
-- Parse a Ink file at path 'stories/game.ink'
local book = narrator.parse_file('stories.game')
-- Parse a Ink file at path 'stories/game.ink'
-- and save the book at path 'stories/game.lua'
local book = narrator.parse_file('stories.game', { save = true })
Reading and saving files required io so if you can't work with files by this way use narrator.parse_content().
narrator.parse_content(content, inclusions)
Parses the string with Ink content and returns a book instance. The inclusions param is optional and can be used to pass an array of strings with Ink content of inclusions.
local content = 'Content of a root Ink file'
local inclusions = {
'Content of an included Ink file',
'Content of another included Ink file'
}
-- Parse a string with Ink content
local book = narrator.parse_content(content)
-- Parse a string with Ink content and inclusions
local book = narrator.parse_content(content, inclusions)
Content parsing is useful when you should manage files by your engine environment and don't want to use io module. For example, in Defold, you may want to load ink files as custom resources with sys.load_resource().
narrator.init_story(book)
Inits a story instance from the book. This is aclual to use in production. For example, just load a book with require() and pass it to this function.
-- Require a parsed and saved before book
local book = require('stories.game')
-- Init a story instance
local story = narrator.init_story(book)
story:begin()
Begins the story. Generates the first chunk of paragraphs and choices.
story:can_continue()
Returns a boolean, does the story have paragraphs to output or not.
while story:can_continue()
Related Skills
node-connect
343.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
90.0kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
343.1kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
343.1kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
