Skip to content

Dialogue

Dialogue files and their runner, Yarn Spinner import, and the DialogueBox node.

Files: dialogue.odin, dialogue_box.odin, yarn.odin.

Dialogue: conversations as data, played by a runner and shown by a DialogueBox node (dialogue_box.odin).

A dialogue (an asset of kind “Dialogue”, a .dialogue.xml file; Yarn Spinner’s .yarn files convert into one: yarn.odin) is a set of named nodes, each a list of steps:

Step Does
Line someone says something: speaker, text ({$gold} puts a variable in)
Options the player chooses: each option’s text and the node it goes next to
Jump goes on in another node
Set sets a variable: var="$gold" value="$gold - 5"
Command tells the game something (<<shake 2>>): the box’s onCommand event; wait 1 pauses
Stop ends the conversation

Every step (and option) can have a condition (if="$gold >= 5 and not visited(\"Shop\")"): it runs (or shows) only when true. A node that runs out of steps ends the conversation.

<dialogue formatVersion="13" start="Start">
<node name="Start" x="0" y="0">
<line speaker="Guide" text="Welcome! You have {$gold} gold."/>
<options>
<option text="Buy a sword" next="Buy" if="$gold >= 5"/>
<option text="Goodbye" next="End"/>
</options>
</node>
<node name="Buy" x="240" y="0">
<set var="$gold" value="$gold - 5"/>
<command text="give sword"/>
<line speaker="Guide" text="Here you go."/>
</node>
</dialogue>

Expressions are Yarn’s: numbers, “strings”, true / false, $variables (unset: 0), + - * / %, == != < <= > >= (also is eq neq lt lte gt gte), and or not (also && || !), parentheses, and the functions visited("Node"), visited_count("Node"), random(), random_range(a, b), dice(sides).

A DialogueRunner plays one: Dialogue_Start, then Dialogue_Continue after each line and Dialogue_Choose at options; its state says what to show. Variables (DialogueVariables) are a game’s (Game.dialogueVariables: every DialogueBox shares them, so $met_guide set in one conversation is true in the next) or a runner’s own; Dialogue_SetVariable / GetVariable, or DialogueVariables_Set / Get from scripts.

DialogueBox nodes show conversations (dialogue.odin): a box at the bottom of the screen with the speaker’s name, the line typed out a letter at a time, and the options to choose from.

  • Playing: dialogue (a .dialogue.xml asset) from startNode (its own start when “”), when the scene starts (autostart) or when a script calls DialogueBox_Start. Variables are the game’s (Game.dialogueVariables), shared by every box and kept between conversations.
  • The player: the game’s ui_accept action (or Space / Enter / a click) shows the whole line while it types, then goes on; at options, ui_up / ui_down (or the arrows) pick and ui_accept chooses, or a click on one.
  • Commands (<<shake 2>>): onCommand (data: the command’s text, text := data.(string)), then it goes on, unless a listener calls DialogueBox_Hold (then DialogueBox_Continue when the game is done). wait 1.5 waits that long by itself.
  • Events: onLine (data: the line’s text), onCommand, onFinished (the conversation ended). It hides itself when a conversation ends (hideWhenDone).

It draws in screen space at its position (a UI node: put it on a screen layer, or under no camera), settings.size across, its text wrapped inside.

Yarn Spinner files (.yarn, yarnspinner.dev, the language version 2) converted into a dialogue (dialogue.odin), which the engine plays and the editor edits:

  • each node (title: and position: headers, a body between --- and ===) becomes a node;
  • Speaker: text lines become lines (#tags dropped, {$x} kept), -> option groups become options: each option’s indented body becomes a node of its own (Title.1, Title.2…), and what follows the group a continuation node (Title.after1) the option bodies jump to;
  • <<if>> / <<elseif>> / <<else>> / <<endif>> become conditions on the steps inside them, <<if ...>> after a line or an option that one’s own condition;
  • <<jump X>>, <<set $x to expr>>, <<stop>> become jump, set and stop steps; <&lt;declare $x = expr>> a set on the node’s first visit; any other command (<&lt;wait 1>>, <&lt;shake 2>>) a command step.

Not converted (reported): <<once>>, line groups (=>), smart variables, enums, functions defined by the game other than the built-in ones (they evaluate as unknown), node groups. yarnSpeaker reads Name: text; a colon later in the text isn’t a speaker.

DialogueStepKind :: enum {
Line,
Options,
Jump,
Set,
Command,
Stop,
}

What a step does

enum. Source: dialogue.odin:61

DialogueOption :: struct {
text: string,
// The node it goes to ("": the conversation ends)
target: string,
// Shown only when true ("": always)
condition: string,
}

A choice of an Options step

struct. Source: dialogue.odin:71

DialogueStep :: struct {
kind: DialogueStepKind,
// Runs only when true ("": always)
condition: string,
// Line: who says it ("": nobody: narration) and what
speaker, text: string,
// Options: the choices
options: [dynamic]DialogueOption,
// Jump: the node to go on in
target: string,
// Set: the variable ("$gold") and the expression it's set to
variable, expression: string,
// Command: its text ("shake 2")
command: string,
}

One step of a node (the fields its kind uses)

struct. Source: dialogue.odin:80

DialogueNode :: struct {
name: string,
// Where editors draw it in the graph
position: [2]f32,
steps: [dynamic]DialogueStep,
}

A node: its steps, run in order

struct. Source: dialogue.odin:97

DialogueData :: struct {
// The node conversations start in ("": the first)
start: string,
nodes: [dynamic]DialogueNode,
}

A dialogue (ParseDialogue; free with DialogueData_Destroy)

struct. Source: dialogue.odin:105

DialogueValue :: union {
f64,
bool,
string,
}

A value of an expression or variable

union. Source: dialogue.odin:112

DialogueState :: enum {
// Not started, or finished
Idle,
// A line shows: `Dialogue_Continue` after it
Line,
// Options show: `Dialogue_Choose`
Options,
// A command came: the game does it, then `Dialogue_Continue`
Command,
// It ended (a Stop, a node out of steps, an option to nowhere)
Finished,
}

What a runner is waiting for

enum. Source: dialogue.odin:119

DialogueShownOption :: struct {
text: string,
// Its index in the step (Dialogue_Choose takes the shown index)
index: int,
}

An option as shown (its condition held)

struct. Source: dialogue.odin:133

DialogueVariables :: struct {
values: map[string]DialogueValue,
visits: map[string]int,
allocator: mem.Allocator,
}

Dialogue variables (“$gold”) and how often each node was entered (by name)

struct. Source: dialogue.odin:140

DialogueRunner :: struct {
data: ^DialogueData,
state: DialogueState,
// Where it is: a node's index and the next step in it
node, step: int,
// What shows now (its strings live until the next Continue or Choose)
speaker, text: string,
options: [dynamic]DialogueShownOption,
// The command waiting (State Command)
command: string,
// Its variables and node visits: shared (a game's, `Game.dialogueVariables`, which every
// DialogueBox uses), or nil for its own (`own`)
shared: ^DialogueVariables,
own: DialogueVariables,
// The Options step showing (for Choose)
optionsStep: int,
allocator: mem.Allocator,
}

Plays a dialogue

struct. Source: dialogue.odin:147

DialogueBoxSettings :: struct {
// The box's size (`boxWidth`, `boxHeight`)
size: [2]f32,
// Letters typed a second (`typeSpeed`; 0: the whole line at once)
typeSpeed: f32,
// Text size, and the font ("" for the default; `fontSize`, `font`)
fontSize: f32,
font: string,
// Space inside the box's edge (`boxPadding`)
padding: f32,
// Colors: the box, the text, the speaker's name, the option picked (`boxColor`, `textColor`,
// `nameColor`, `pickColor`)
background: sdl.Color,
textColor: sdl.Color,
nameColor: sdl.Color,
pickColor: sdl.Color,
// Starts its dialogue when the scene starts (`autostart`)
autostart: bool,
// Hides when a conversation ends, shows when one starts (`hideWhenDone`)
hideWhenDone: bool,
}

A DialogueBox’s look and behaviour (scene files’ attributes)

struct. Source: dialogue_box.odin:31

DialogueBox :: struct {
using node: Node,
settings: DialogueBoxSettings,
// The dialogue asset and the node it starts in (node arena)
dialogue: string,
startNode: string,
runner: DialogueRunner,
// Letters of the line shown so far, and the time since the line began
typed: f32,
// The option highlighted
pick: int,
// A command's wait (seconds left), and a listener holding it
waiting: f64,
held: bool,
started: bool,
// A line began: it says so on onLine
onLine: EventSubject `event:"A line shows (its speaker and text are the box's runner's)" data:"text := data.(string)"`,
onCommand: EventSubject `event:"A command ran (<<shake 2>>): do it; DialogueBox_Hold to make it wait" data:"text := data.(string)"`,
onFinished: EventSubject `event:"The conversation ended"`,
}

Shows a conversation

struct. Source: dialogue_box.odin:70

ParseDialogue :: proc(text: string, allocator := context.allocator) -> (DialogueData, ProjectError)

Reads a dialogue file.

Inputs

  • text: The file’s contents
  • allocator: For the dialogue’s strings and lists

Returns

procedure. Source: dialogue.odin:176

FormatDialogue :: proc(data: DialogueData, allocator := context.allocator) -> string

Writes a dialogue file.

Returns

  • The text (in allocator)

procedure. Source: dialogue.odin:242

DialogueData_Clone :: proc(data: DialogueData, allocator := context.allocator) -> DialogueData

A copy of a dialogue (in allocator)

procedure. Source: dialogue.odin:296

DialogueStep_Clone :: proc(s: DialogueStep, allocator := context.allocator) -> DialogueStep

A copy of a step (in allocator)

procedure. Source: dialogue.odin:307

DialogueStep_Destroy :: proc(s: ^DialogueStep, allocator := context.allocator)

Frees a step’s strings

procedure. Source: dialogue.odin:323

DialogueData_Destroy :: proc(data: ^DialogueData, allocator := context.allocator)

Frees what ParseDialogue or DialogueData_Clone made

procedure. Source: dialogue.odin:341

DialogueData_Equal :: proc(a, b: DialogueData) -> bool

Whether two dialogues are the same

procedure. Source: dialogue.odin:353

Dialogue_FindNode :: proc(data: ^DialogueData, name: string) -> int

A node’s index by name (-1 when there’s none)

procedure. Source: dialogue.odin:373

Dialogue_Start :: proc(runner: ^DialogueRunner, data: ^DialogueData, node := "") -> bool

Starts a conversation: in node (the dialogue’s start node when “”), running until the first line, options, command or its end.

Inputs

  • runner: The runner (its variables kept)
  • data: The dialogue (kept by pointer: keep it alive while it plays)
  • node: The node to start in

Returns

  • false when there’s no such node (the runner is Finished)

procedure. Source: dialogue.odin:391

Dialogue_Continue :: proc(runner: ^DialogueRunner) -> bool

Goes on after a line or a command, until the next line, options, command or the end.

Returns

  • false when the conversation is over (the runner is Finished)

procedure. Source: dialogue.odin:410

Dialogue_Choose :: proc(runner: ^DialogueRunner, shown: int) -> bool

Chooses one of the options showing (its index among runner.options) and goes on in its node.

Returns

  • false when it ended there (an option to nowhere), or there are no options showing

procedure. Source: dialogue.odin:421

Dialogue_Stop :: proc(runner: ^DialogueRunner)

Ends a conversation now

procedure. Source: dialogue.odin:435

Dialogue_SetVariable :: proc(runner: ^DialogueRunner, name: string, value: DialogueValue)

Sets a runner’s dialogue variable (“$gold”; a string is copied)

procedure. Source: dialogue.odin:441

Dialogue_GetVariable :: proc(runner: ^DialogueRunner, name: string) -> DialogueValue

A runner’s dialogue variable (nil when unset)

procedure. Source: dialogue.odin:446

DialogueVariables_Set :: proc(vars: ^DialogueVariables, name: string, value: DialogueValue)

Sets a dialogue variable (“$gold”; a string is copied).

Example

// Given: game: ^e.Game
e.DialogueVariables_Set(&game.dialogueVariables, "$gold", f64(12))

procedure. Source: dialogue.odin:457

DialogueVariables_Get :: proc(vars: ^DialogueVariables, name: string) -> DialogueValue

A dialogue variable (nil when unset)

procedure. Source: dialogue.odin:472

DialogueVariables_Destroy :: proc(vars: ^DialogueVariables)

Frees dialogue variables

procedure. Source: dialogue.odin:477

DialogueRunner_Destroy :: proc(runner: ^DialogueRunner)

Frees a runner’s variables and lists (not its dialogue)

procedure. Source: dialogue.odin:489

Dialogue_Evaluate :: proc(runner: ^DialogueRunner, expression: string) -> (DialogueValue, bool)

Evaluates an expression with a runner’s variables (nil runner: no variables).

Returns

  • The value, and false (with value nil) when it doesn’t parse

procedure. Source: dialogue.odin:505

DialogueValue_Truthy :: proc(v: DialogueValue) -> bool

Whether a value counts as true (nonzero numbers, non-empty strings)

procedure. Source: dialogue.odin:514

DialogueValue_Text :: proc(v: DialogueValue) -> string

A value as text (“5”, “true”, “hello”; temp allocated)

procedure. Source: dialogue.odin:524

Dialogue_Interpolate :: proc(runner: ^DialogueRunner, text: string) -> string

A line’s text with {expression} parts replaced by their values (“You have {$gold} gold”).

Returns

  • The text (temp allocated)

procedure. Source: dialogue.odin:541

CreateDialogueBox :: proc(scene: ^Scene, dialogue: string, startNode := "", settings := DEFAULT_DIALOGUE_BOX_SETTINGS) -> ^DialogueBox

Makes a DialogueBox for a dialogue asset (path relative to the project)

procedure. Source: dialogue_box.odin:92

DialogueBox_Start :: proc(box: ^DialogueBox, node := "") -> bool

Starts a conversation in the box (and shows it).

Inputs

  • box: The box
  • node: The dialogue node to start in (“”: the box’s startNode, else the dialogue’s start)

Returns

  • false when the dialogue or the node isn’t there (logged)

procedure. Source: dialogue_box.odin:126

DialogueBox_Hold :: proc(box: ^DialogueBox)

Makes a command wait: call from an onCommand listener, then DialogueBox_Continue

procedure. Source: dialogue_box.odin:143

DialogueBox_Continue :: proc(box: ^DialogueBox)

Goes on after a held command (or skips the line showing)

procedure. Source: dialogue_box.odin:148

DialogueBox_IsPlaying :: proc(box: ^DialogueBox) -> bool

Whether a conversation is playing in the box

procedure. Source: dialogue_box.odin:157

Game_LoadDialogue :: proc(game: ^Game, path: string) -> (^DialogueData, bool)

A dialogue, read once per game (Game.dialogues): the file relative to the project folder.

Returns

  • The dialogue (the game’s; nil when it can’t be read, logged), and whether it was read

procedure. Source: dialogue_box.odin:168

Yarn_Convert :: proc(text: string, allocator := context.allocator) -> (data: DialogueData, warnings: []string, ok: bool)

Converts a Yarn file into a dialogue.

Inputs

  • text: The .yarn file’s contents
  • allocator: For the dialogue

Returns

  • The dialogue (free with DialogueData_Destroy), what wasn’t converted (temp allocated), and false when it has no nodes

procedure. Source: yarn.odin:38

DIALOGUE_EXTENSION :: ".dialogue.xml"

A dialogue file’s extension

constant. Source: dialogue.odin:58

DEFAULT_DIALOGUE_BOX_SETTINGS :: DialogueBoxSettings{
size = {600, 140},
typeSpeed = 40,
fontSize = 20,
padding = 14,
background = {16, 18, 28, 230},
textColor = {240, 240, 240, 255},
nameColor = {255, 210, 110, 255},
pickColor = {120, 200, 255, 255},
autostart = true,
hideWhenDone = true,
}

A dialogue box’s settings when none are given (CreateDialogueBox, scene files): 600 x 140, 40 characters a second, size 20 text on a dark panel; it starts when the scene does and hides when the conversation ends

constant. Source: dialogue_box.odin:56