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.

In this sample, you build a short scene out of storylets. You pick who plays the two roles, which room the scene is set in, and what kind of scene it is. Then you walk into the room and talk to the two characters. Every line they say comes from a node group, and the when: headers on each node decide which one runs. It’s a port of the Advanced Saliency sample from Yarn Spinner for Unity.

When you run it, you start in the setup room, next to the Explainer. Around the room are four pillars, each with a sign and a button. The signs show the current choices: “The Primary role is played by Alice”, “The Secondary role is played by Barry”, “It is set inside an Office” and “It will be an Interrogation scene”. Walk into a pillar’s button to change its choice. Talk to the Explainer to start the scene, then walk down the corridor into the scenario room, where the two characters are waiting.

The green player character and a blue character holding a coffee cup stand in the setup room. Ahead, a corridor leads to an empty walled room, and four small characters wait on a platform in the distance. The dialogue box at the bottom shows Explainer saying Hello and welcome to the Advanced Saliency demo.
The Advanced Saliency sample, with the corridor to the scenario room ahead.

Running it

Open samples/advanced_saliency/advanced_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.

The four pillars are:

PillarChangesChoices
PillarPrimaryWho plays the primary roleAlice, Barry, George, Liz
PillarSecondaryWho plays the secondary roleAlice, Barry, George, Liz
PillarRoomThe roomOffice, Pub, Church, Mansion
PillarScenarioThe scenarioInterrogation, Explore, Rescue, Date
The pillars in the setup room. Each bump moves to the next choice, and goes back to the first after the last.

The Explainer won’t start the scene if the same character is in both roles. Every time you start a scene, the scenario room is rebuilt, so you can change the choices and play again.

How it works

There are three parts to the sample:

  • The Yarn Scripts, which hold every scenario as a set of node groups.
  • A custom Variable Storage, which lets the pillars change the Yarn variables.
  • The level god, a node that builds the room when the Yarn Script tells it to.

The sample leaves the Dialogue Runner’s Saliency Strategy at the default, Random Best Least Recent. See Saliency and Storylets for how it picks.

AdvancedSaliencyNode3D
ScenarioRoomNode3D
SetupRoomNode3D
VoidNode3D
BridgeNode3D
PlayerCharacterBody3D
CameraRigNode3D
PillarPrimaryNode3D
ValueUpdaterNode
SignPlateMeshInstance3D
LabelLabel3D
ButtonBumpArea3D
PillarSecondaryNode3D
PillarRoomNode3D
PillarScenarioNode3D
AliceCharacterBody3D
AppearanceNode
DialogueInteractableNode3D
BarryCharacterBody3D
GeorgeCharacterBody3D
LizCharacterBody3D
ExplainerCharacterBody3D
DialogueInteractableNode3D
TheRoomVariableStorageNode
YarnDialogueRunner
UILayerCanvasLayer
LinePresenterControl
OptionsPresenterControl
LevelGodNode3D
The main parts of the sample scene. Barry, George and Liz are set up the same way as Alice.

ScenarioRoom, SetupRoom and Void are three copies of the same arena, and Bridge is the corridor between the first two. The script on the root node removes a wall tile from each end of the corridor, to make the doorways. The four characters start in Void, out of the way, until a scene needs them.

The choices are Yarn variables

Room.yarn declares an enum for each choice, and a variable to hold it. The enums use strings for their values:

<<enum Character>>
    <<case Alice = "Alice">>
    <<case Barry = "Barry">>
    <<case George = "George">>
    <<case Liz = "Liz">>
<<endenum>>
The Character enum, from Room.yarn.
<<enum ScenarioState>>
    <<case NotStarted>>
    <<case Started>>
    <<case Complete>>
<<endenum>>

<<declare $primary = Character.Alice>>
<<declare $secondary = Character.Barry>>
<<declare $scenario = Scenario.Interrogation>>
<<declare $Room = Room.Office>>

// the transient state variables we need to reset each run
<<declare $speak_to_primary = false>>
<<declare $speak_to_secondary = false>>
<<declare $scenario_state = .NotStarted>>
Lines 1–5 How far through the scenario the player is. This enum has no values given, so its cases are stored as numbers.
Lines 7–10 The four choices the pillars change.
Lines 13–15 Where the player is in the current scene. These are reset every time a scene starts.
The rest of the declarations, from the RoomSetup node in Room.yarn.

The Dialogue Runner’s Variable Storage is TheRoomVariableStorage, a script that extends YarnInMemoryVariableStorage. It has a getter and a setter for each variable, using GDScript enums that match the Yarn ones. A Yarn enum with string values is stored as its string, so the setters store names like "Alice":

## the character playing the primary role
func get_primary() -> Character:
    return _to_enum(get_string("$primary", CHARACTER_NAMES[0]), CHARACTER_NAMES) as Character


func set_primary(value: Character) -> void:
    set_value("$primary", CHARACTER_NAMES[value])
The primary role’s getter and setter, from the_room_variable_storage.gd.

Each pillar has a ValueUpdater for one of the four variables. When the player walks into the pillar’s ButtonBump, the ValueUpdater reads the variable through the Variable Storage, sets it to the next choice, and updates the sign. See Variables and Storage for more on custom Variable Storage.

Starting a scene

Talking to the Explainer runs the RoomSetup node. It can show the current choices, and if the roles are played by different characters, it offers to play the scenario. Choosing to play jumps to ResetAndPlay:

title: ResetAndPlay
---
<<set $speak_to_secondary = false>>
<<set $speak_to_primary = false>>
<<set $scenario_state = .NotStarted>>
<<start_level>>
===
Lines 3–5 Reset the variables that track the player’s progress, so the scenario starts from the beginning.
Line 6 A Command that builds the scenario room.
The ResetAndPlay node, from Room.yarn.

The level god adds the start_level Command in its _ready, with dialogue_runner.add_command("start_level", spawn_level). spawn_level() does four things:

  1. Removes the room it built last time, if there is one.
  2. Puts all four characters back where they started, in Void.
  3. Looks up the room’s RoomLayout in its Layouts dictionary, and moves the primary and secondary characters to the layout’s spots.
  4. Adds the layout’s room model to ScenarioRoom.

Each room has a RoomLayout resource in the layouts folder, such as Pub.tres. A layout has a Primary and a Secondary CharacterSpawn, each with a position and a rotation, and an Environment Scene for the room. See Commands for more on adding Commands.

Every character goes to Primary or Secondary

Each character’s DialogueInteractable starts the node group with the character’s name. The scenarios are written for the primary and secondary roles, not for particular characters, so Room.yarn has two short nodes for each character that jump to the right group:

title: Alice
when: $primary == .Alice
---
<<jump Primary>>
===
title: Alice
when: $secondary == .Alice
---
<<jump Secondary>>
===
Lines 2–4 If Alice plays the primary role, run the Primary node group.
Lines 7–9 If Alice plays the secondary role, run the Secondary node group.
Alice’s two nodes, from Room.yarn. Barry, George and Liz have the same pair.

A character who isn’t in either role has nothing that passes, so the DialogueInteractable doesn’t show the speaking indicator over them.

Scenarios as storylets

Each scenario is a set of nodes in the Primary and Secondary node groups. Every node checks $scenario, and most also check $scenario_state, and whether the player has talked to the other character yet. As the player talks to each character, the nodes change these variables, and different nodes start to pass.

title: Primary
when: $scenario == .Explore
when: $scenario_state == .NotStarted
---
Player: ok time to wander around aimlessly looking for stuff
{$primary}: Great idea
Player: ok, any idea where to look?
{$primary}: I think {$secondary} had some ideas
Player: gotcha

<<set $scenario_state = .Started>>
===
Lines 2–3 Only in the Explore scenario, before it has started.
Line 6 {$primary} is the name of whoever plays the primary role, so the same node works for every character.
Line 11 Start the scenario. This node stops passing, and the Primary nodes for a started Explore scenario start to pass.
The first node of the Explore scenario, from Room-Exploration.yarn.

The Rescue and Date scenarios work the same way, and so does Interrogation, which also uses line groups to change a line depending on the room.

More specific nodes win

The default strategy picks the candidate with the highest complexity, from the ones seen the fewest times. The sample uses this to give some combinations their own version of a scenario. Room-Explore-Mansion-Liz-Alice.yarn has a different Explore scenario, for when it’s set in the Mansion and Alice and Liz play the two roles:

title: Primary
when: $scenario == .Explore
when: $Room == .Mansion
when: $primary == .Alice || $primary == .Liz
when: $secondary == .Alice || $secondary == .Liz
when: $scenario_state == .NotStarted
---
Player: hey
{$primary}: Can't talk, I am too busy uncovering the Bone Secrets.
Player: that sounds cool
{$primary}: it will be, yes.
<<set $scenario_state = .Started>>
===
Lines 2–6 Five conditions, two of them with ||, so this node’s complexity is 7. The ordinary Explore node above has a complexity of 2.
The first node of the Mansion version, from Room-Explore-Mansion-Liz-Alice.yarn.

With that setup, both versions pass, and neither has been seen. The Mansion version has the higher complexity, so it wins. Every other node in the Mansion version has the same extra conditions, so they win over the ordinary Explore nodes for the rest of the scene.

Room-Interrogation.yarn does the same for one character. It adds a node to Alice’s own node group, with when: $primary == .Alice, the Interrogation conditions, and when: once. Its complexity of 4 beats the complexity of 1 on Alice’s node that jumps to Primary, so the first time Alice plays the primary role in an Interrogation, she gives a short speech of her own.

The view counts aren’t reset when a scene starts. Once you’ve played the Mansion version of Explore more times than the ordinary one, the ordinary Explore nodes have been seen fewer times. The default strategy looks at view counts before complexity, so it picks them instead.

Things to try

  • Select YarnDialogueRunner, and under Advanced, set Saliency Strategy to Best. The Best strategy ignores view counts, so the Mansion version of Explore plays every time you choose it.

  • Add a node to the Date scenario for one room, such as:

    title: Secondary
    when: $scenario == .Date
    when: $scenario_state == .NotStarted
    when: $Room == .Pub
    ---
    {$secondary}: Fancy seeing you two in here.
    ===
    A new node for Room-Date.yarn.

    It has one more condition than the ordinary Secondary node for a Date that hasn’t started. In a Date in the Pub, the first time you talk to the secondary character, they say the new line.

Next step Yarn Spinner+ and Add-Ons What Yarn Spinner+ for Godot (GDScript) includes, and how to get it.