Pardon our dust while we move our docs here. The Godot (GDScript) docs are on this site, and the Yarn language, Unity, Godot (C#) and Unreal docs are still at docs.yarnspinner.dev.

Four characters stand in a room, and what each of them says depends on the day and the time of day. Several pieces of content can often run at once, and Yarn Spinner picks one. Alice, Barry and Liz each have a node group, and George has a line group.

When you run it, the sign at the back of the room says “It is Monday morning.” A switch on the floor in front of the sign moves time on. Walk into it and the sign changes to Monday evening, then Tuesday morning, and so on. Talk to the characters before and after, and they say different things.

A walled arena with a checked floor. Light blue, dark blue, yellow and orange pill-shaped characters stand along the back wall, either side of an orange floor switch below a sign reading It is Monday morning. The green player character stands in the middle, and a blue character holding a coffee cup is in the front right.
The Basic Saliency sample.

Running it

Open samples/basic-saliency/basic-saliency.tscn and press Run Current Scene.

KeyWhat it does
W, A, S, D or the arrow keysMove
ETalk to the character you’re next to
Enter, Space or a left clickGo to the next line
The controls.

Walk into the orange switch at the back of the room to move time on. Each time, the time goes from morning to evening, or from evening to the next morning. After Wednesday evening, it goes back to Monday morning.

The blue character in the front right is Capsley, who explains the sample. Capsley’s node has when: once, so after you’ve talked to Capsley once, the speaking indicator doesn’t appear over Capsley again.

How it works

The day and the time are two Yarn variables, $day and $time. Every node in the characters’ node groups has when: headers that check them. Each time you talk to a character, the Dialogue Runner works out which of that character’s nodes can run, and its saliency strategy picks one. The sample leaves the Dialogue Runner’s Saliency Strategy at the default, Random Best Least Recent. See Saliency and Storylets for how the strategies pick.

BasicSaliencyNode3D
BasicArenaNode3D
CameraRigNode3D
VariableStorageBasicSaliencyVariableStorage
YarnDialogueRunner
button-floor-squareNode3D
columnNode3D
TimeTriggerArea3D
CollisionShape3D
Label3D
GeorgeCharacterBody3D
DialogueInteractableNode3D
AliceCharacterBody3D
DialogueInteractableNode3D
LizCharacterBody3D
DialogueInteractableNode3D
BarryCharacterBody3D
DialogueInteractableNode3D
PlayerCharacterCharacterBody3D
CapsleyCharacterBody3D
DialogueInteractableNode3D
UILayerCanvasLayer
LinePresenterControl
OptionsPresenterControl
HintLabel
The main parts of the sample scene. Each character has a DialogueInteractable, which starts the node group named after the character.

The day and the time

The Yarn Script declares two enums, and a variable for each:

<<enum Day>>
    <<case Monday>>
    <<case Tuesday>>
    <<case Wednesday>>
<<endenum>>

<<enum TimeOfDay>>
    <<case Morning>>
    <<case Evening>>
<<endenum>>

/// the current day
<<declare $day = Day.Monday>>
/// the current time
<<declare $time = TimeOfDay.Morning>>
Lines 1–5 The days the sample uses.
Lines 7–10 The two times of day.
Line 13 The current day. It starts on Monday.
Line 15 The current time. It starts in the morning.
Part of the Setup node in BasicSaliency.yarn.

Nothing runs the Setup node. Declarations apply to the whole Yarn Project, so it never has to to run.

In a condition, .Monday is short for Day.Monday. Yarn Spinner works out the enum from the variable it’s compared with.

Node groups

Alice has a node group with eight nodes. Some depend on the day, and some on the day and the time. On Wednesday evening, two of them can run:

title: Alice
when: $day == .Wednesday
when: $time == .Evening
when: once
---
Player: Hello
Alice: the week is half over, what a relief.
===
title: Alice
when: $day == .Wednesday
when: $time == .Evening
---
Player: Hello
Alice: Probably should go to bed, it is Wednesday evening after all.
===
Lines 2–4 Two conditions and once, so this node’s complexity is 3. It can only run once.
Lines 10–11 The same two conditions without once, so this node’s complexity is 2.
Alice’s two Wednesday evening nodes, from BasicSaliency.yarn.

The default strategy first looks for the candidates seen the fewest times, then picks the one with the highest complexity. The first time you talk to Alice on a Wednesday evening, neither node has been seen, so the once node wins. After that it stops passing, and Alice says the other line.

The other characters show other ways of writing node groups:

  • Barry has three nodes for the morning and three for the evening, each with one condition. The default strategy picks the one seen the fewest times, so Barry works through all three before he repeats one.
  • Liz’s nodes don’t depend on the day or the time. Three of them have when: always, and one has when: once and when: always. The once node has the highest complexity, so it’s the first thing Liz says.
  • Alice has two nodes for Tuesday evening and two for Wednesday morning, with the same conditions. When nodes tie like this, she takes turns between them.

Each character’s DialogueInteractable starts the node group with the character’s name, such as Alice. Before showing the speaking indicator, it checks that something in the group can run, with has_salient_content(). See Looking at a node group from GDScript.

A line group

George’s node is a single node with a line group in it. Each line starts with =>, and some have a condition:

title: George
---
=> George: oh hi
=> George: another Monday, I hate Mondays <<if $day == .Monday>>
=> George: I suppose I should make breakfast <<if $time == .Morning>>
=> George: Weird how tomorrow is Monday, right? <<if $day == .Wednesday && $time == .Evening>>
    Player: did you want the devs to have to write out even more sample content?
    George: it would be nice, yes
    Player: well they aren't gonna do that
    George: boo
=> George: I wonder if I am in any other samples
===
George’s node, from BasicSaliency.yarn.

The Dialogue Runner picks one line from the group with the same strategy as the node groups. The lines with no condition can always be picked. The Wednesday evening line has two conditions joined with &&, so it has the highest complexity, and the first time you talk to George on a Wednesday evening, he says it. The indented lines under it only run when that line is picked.

Changing the time from GDScript

The Dialogue Runner’s Variable Storage is a BasicSaliencyVariableStorage, a script that extends YarnInMemoryVariableStorage. It adds functions for reading and changing the day and the time, so that game code doesn’t need to know the variable names:

enum Day { MONDAY = 0, TUESDAY = 1, WEDNESDAY = 2 }
enum TimeOfDay { MORNING = 0, EVENING = 1 }


## the current day
func get_day() -> Day:
    return int(get_float("$day", 0.0)) as Day


func set_day(value: Day) -> void:
    set_value("$day", float(value))
Lines 1–2 GDScript copies of the Yarn enums. Yarn stores each case as a number, starting from 0, in the order the cases are declared.
Lines 6–7 Read $day as a number and turn it into a Day.
Lines 10–11 Store a Day in $day as a number.
Part of basic_saliency_variable_storage.gd.

The switch is an Area3D called TimeTrigger, with time_advancer.gd on it. Its body_entered signal is connected to _on_body_entered() in the scene. When the player walks into it, it moves time on through the Variable Storage, and updates the sign:

func _on_body_entered(body: Node3D) -> void:
    if body.is_in_group(&"player"):
        _advance_time()
        _update_label()


func _advance_time() -> void:
    if variable_store == null:
        push_warning("TimeAdvancer: variable_store is not set")
        return

    # Morning steps to evening; evening wraps to the next day's morning,
    # with Wednesday looping back to Monday.
    if variable_store.get_time() == TimeOfDay.MORNING:
        variable_store.set_time(TimeOfDay.EVENING)
        return

    variable_store.set_time(TimeOfDay.MORNING)
    match variable_store.get_day():
        Day.MONDAY:
            variable_store.set_day(Day.TUESDAY)
        Day.TUESDAY:
            variable_store.set_day(Day.WEDNESDAY)
        Day.WEDNESDAY:
            variable_store.set_day(Day.MONDAY)
Lines 1–4 Only the player moves time on.
Lines 14–16 Morning becomes evening.
Lines 18–25 Evening becomes the next morning, and the day moves on.
Part of time_advancer.gd.

The next time you talk to someone, the when: headers are checked against the new values. For more on custom Variable Storage, see Variables and Storage.

Things to try

  • Select YarnDialogueRunner, and under Advanced, set Saliency Strategy to Best. Run the scene and talk to Barry a few times. The Best strategy doesn’t count how many times each node has been seen, so Barry says the same line every time.
  • Add another node to Barry’s node group with two conditions, such as when: $time == .Morning and when: $day == .Tuesday. It has a higher complexity than his other morning nodes, so it’s the first thing he says on a Tuesday morning.
  • Add a line to George’s line group with <<if $time == .Evening>> at the end, and talk to him in the evening.
Next step Custom Saliency A custom saliency strategy that makes some content more likely than the rest.