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, characters talk to each other or call out to the player, and their lines float above their heads. The player can keep walking around the whole time. Each line stays up for a time based on its length, then the next one appears, with no button to press.

When you run it, you’re in a 3D level made of several small rooms, then a market. Each room has Capsley, who explains one way to use background chatter when you talk to them, and one or two characters who show it. The market at the end puts the same ideas together: bored guards, gossiping townspeople, a fishmonger, someone wandering between the stalls, and a town crier.

A green pill-shaped player character on a chequered floor between grey walls. Other coloured pill-shaped characters stand further along the corridor and in the rooms on each side. The controls hint is at the top of the screen.
The first room of the Background Chatter sample.

Running it

Open samples/background-chatter/background-chatter.tscn and press Run Current Scene.

  • Move with W, A, S and D, or the arrow keys.
  • When a character has something to say to you, an indicator appears over them. Press E to talk to them.
  • In a conversation, press Enter or Space, or click, to continue. Click an option to choose it.
  • Walk near characters to hear them chatter. You don’t need to press anything.

You can’t move while you’re in a conversation, but you can while background chatter is playing.

How it works

There’s one Dialogue Runner for the conversations the player starts, called PrimaryDialogueRunner. It uses the usual Line Presenter and Options Presenter. Every background conversation has its own Dialogue Runner, so it can run alongside the others and the primary one.

BackgroundChatterNode3D
PlayerCharacterCharacterBody3D
ChatterNPCNode
BackgroundChatterACharacterBody3D
ChatterNPCNode
BackgroundChatterBCharacterBody3D
ChatterNPCNode
CapsleyCharacterBody3D
DialogueInteractableNode3D
PrimaryDialogueRunnerYarnDialogueRunner
ChatterGroupManagerNode
Demo_BackgroundConversationNode3D
RunnerYarnDialogueRunner
BackgroundChatterViewNode
Demo_CallAndResponseNode3D
RunnerYarnDialogueRunner
BackgroundChatterViewNode
UILayerCanvasLayer
LinePresenterYarnLinePresenter
OptionsPresenterYarnOptionsPresenter
HintLabel
Part of the scene. Each node under ChatterGroupManager is one background conversation, with its own Dialogue Runner and Presenter.

All the Dialogue Runners use the same Yarn Project, BackgroundChatter.yarnproject. None of them has a Variable Storage set, so each one makes its own. A variable that a background conversation changes is only changed for that conversation’s Dialogue Runner.

Lines above heads

Each background Dialogue Runner has one Presenter, BackgroundChatterView. It’s an instance of scenes/background_chatter_view.tscn, which holds the label it shows, and its script is scripts/background_chatter_view.gd. It’s a Presenter you write yourself. It doesn’t show options, because nobody chooses them.

Every character who can speak in the background has a ChatterNPC node. Its Speaker Name is the name used in the Yarn Script. The player’s is Player, so Player: Who, me? floats above the player. When a line runs, the Presenter finds the ChatterNPC whose Speaker Name matches the line’s character name. Then it shows the text without the name, in its label, which follows a point Chatter Height above that character’s position.

The line stays on screen for 75 milliseconds per character, or 1.5 seconds if that’s longer. Then the label hides, and after another half a second the Presenter returns, so the Dialogue Runner moves on. These are the Presenter’s Milliseconds per Character, Min Duration and Delay After Lines settings. If the conversation is stopped, the Presenter hides the line at once.

Starting and stopping conversations

Each node under ChatterGroupManager has the ChatterGroup script, from scripts/chatter_group.gd. It says which Yarn node to run, and when:

SettingWhat it does
Chatter NodeThe node or node group to run.
TargetsThe characters taking part. The group moves itself to the middle of them every frame, so its start and stop radius go wherever they go.
Start RadiusThe player has to be this close for the conversation to start.
Stop RadiusIf the player gets this far away while it’s running, the conversation reacts as Out of Range Behaviour says.
Out of Range BehaviourDO_NOTHING lets it carry on. STOP stops it. STOP_AND_RUN_NODE stops it, then runs Out of Range Node.
Start Immediately on EnterStart as soon as the player is in range, instead of after a random delay.
Interrupted by PrimaryStop this conversation when the primary Dialogue Runner starts.
SaliencyWhich saliency strategy the conversation’s Dialogue Runner uses to pick lines and nodes.
The settings on each ChatterGroup.

With Show Ranges in Editor on, which it is by default, each group shows its start radius as a green sphere and its stop radius as a red one in the editor.

ChatterGroupManager runs a loop for each group. It waits a random time between its Min Delay and Max Delay, which are 2 and 4 seconds in this scene, or a single frame if the group has Start Immediately on Enter on. Then, if the player is within the start radius and the group’s conversation isn’t already running, it starts the conversation and watches the player’s distance until it ends. A group that starts on enter waits for the player to walk out of range before it can start again, so it doesn’t repeat immediately.

When the player talks to someone, the primary Dialogue Runner emits dialogue_started, and the manager stops every group that has Interrupted by Primary on:

func _interrupt_all_chatter() -> void:
	for group in _groups:
		if group.interrupted_by_primary:
			group.interrupt()


func interrupt() -> void:
	if dialogue_runner != null and dialogue_runner.is_running():
		await dialogue_runner.stop_dialogue()
Line 1 Connected to the primary Dialogue Runner’s dialogue_started signal.
Lines 3–4 Groups with Interrupted by Primary off keep talking.
Lines 7–9 In ChatterGroup. Stop the group’s own Dialogue Runner. Its Presenter hides the line that was showing.
Part of chatter_group_manager.gd, and the interrupt function from chatter_group.gd.

While the primary dialogue is running, the manager doesn’t start those groups either. They start again by themselves once it ends and the player is in range. See Async and Cancellation for what stopping a Dialogue Runner does.

The conversations

Each room, and each part of the market, shows a different way to write background chatter:

GroupSpeakersWhat it shows
Demo_BackgroundConversationBackgroundChatterA and BA plain conversation, written as ordinary lines.
Demo_CallAndResponse and CallAndResponseCallAndResponseA and B, BoredGuard1 and 2Two line groups, so each conversation is a random question and a random answer.
Demo_InterruptionInterruptionA and BTalking to either character starts a conversation on the primary Dialogue Runner, which stops the chatter.
Demo_WalkAway and InterruptableBackgroundChatWalkAway, the Fishmonger and the playerThe player takes part. The conversation starts on enter, and walking away runs a different node.
Demo_Ongoing and OngoingBackgroundConversationOngoingA and B, Gossip1 and 2A node group that plays the next part of the conversation each time.
RandomBackgroundChatTownsperson1 and 2A node group of three conversations, picked at random.
RoamingCharacterWandererA character who walks a path while they chatter. The group’s only target is the Wanderer, so its range moves with them.
TownCrierTownCrierA line group with conditions on its lines.
The background conversations in the sample.

Line groups and node groups are the same features you’d use in any dialogue. The Dialogue Runner picks one item from the group each time, using its saliency strategy. See Saliency and Storylets.

The bored guards in the market use two line groups. Every answer makes sense after every question:

title: CallAndResponse
tags:
---
=> BoredGuard1: Can't believe I'm doing the night shift again.
=> BoredGuard1: Once took down a whole squad, single handed.
=> BoredGuard1: You hear that?
=> BoredGuard1: Ever visit the blue quarter after midnight? Wild times.
=> BoredGuard1: I hear the king has a new advisor. <<if $king_new_advisor>>
=> BoredGuard1: Can't wait for them to issue us new shoes.
=> BoredGuard1: My shield's getting rusty.

=> BoredGuard2: Yep.
=> BoredGuard2: Sure.
=> BoredGuard2: Yeah.
=> BoredGuard2: Mmm.
=> BoredGuard2: Uh-huh.
=> BoredGuard2: What?
=> BoredGuard2: Odd.
=> BoredGuard2: Don't care.
===
Lines 4–10 The first line group. One of these lines runs, said by the first guard.
Line 8 This line can only be picked when $king_new_advisor is true. It’s declared in TownCrier.yarn, and starts false.
Lines 12–19 The second line group, for the other guard’s reply.
CallAndResponse.yarn.

The gossips are a node group, with a variable that counts how far through the story they are. Each time the player comes near, the next part plays:

title: OngoingBackgroundConversation
when: $gossip == 0
---
/// How far through the gossip background conversation we are.
<<declare $gossip = 0>>

// Update the variable before running any lines in case we walk away and this
// background chatter is interrupted
<<set $gossip += 1>>

Gossip1: Did you hear about Barbara?
Gossip2: About her new sword?
Gossip1: Wait, new sword?
Gossip2: Yes! I saw it when I was over for tea yesterday!
===
title: OngoingBackgroundConversation
when: $gossip == 1
---
<<set $gossip += 1>>

Gossip1: So she had her sword just.. hanging about?
Gossip2: Yes, casual as you like!
Gossip2: I said, "Barbara, that's rather sharp to be leaving out!"
Gossip1: And she took it figuratively?
Gossip2: She took it figuratively!
Gossip2: "Oh, thank you, I'm very proud of it!"
===
Line 2 This node can only run when $gossip is 0, so it’s the first part.
Line 9 Move the counter on before any lines run. If the player walks away part way through, they hear the next part next time, instead of starting this one again.
Line 17 The second part. A third part, for $gossip == 2, follows it in the file.
The first two parts of OngoingBackgroundConversation.yarn.

Demo_Ongoing in _Explanation.yarn does the opposite. It adds 1 to $ongoing_counter after its last line, so the player has to hear a whole part before the next one plays. Its last node has when: always, and plays once all the other parts have been heard.

The Fishmonger’s conversation includes the player. Its group has Start Immediately on Enter on, and its Out of Range Behaviour is STOP_AND_RUN_NODE, with Out of Range Node set to InterruptableBackgroundChat_WalkAway:

title: InterruptableBackgroundChat
---
// This conversation runs in the background, but the player character is
// involved. If the player walks away from the NPC while it's ongoing, it will
// stop and the 'walk away' node will play.
<<once>>
Fishmonger: You there! Interested in some fresh fish?
<<else>>
=> Fishmonger: Still got those fish!
=> Fishmonger: Nice fresh fish!
=> Fishmonger: Delicious fish!
    Fishmonger: Del-fish-ous, I'd say! <<once>>
<<endonce>>

=> Player: Are they any good?
=> Player: How fresh are they?
=> Player: Are they still cold?

=> Fishmonger: They're the finest around!
=> Fishmonger: Fresh off the cart, barely nine days out of the ocean!
=> Fishmonger: They're the choicest of fish!
=> Fishmonger: You've never had anything like them!

===
title: InterruptableBackgroundChat_WalkAway
---
// The conversation started, but the player walked away in the middle of it.

=> Player: Maybe some other time.
=> Player: Not now.
=> Player: Not into fish.
===
Lines 6–13 The first time, the Fishmonger calls out to the player. After that, one of three greetings is picked.
Line 12 A line that only runs once, the first time “Delicious fish!” is picked.
Lines 15–17 The player’s reply, shown above the player.
Lines 25–32 Run on the same Dialogue Runner when the player walks out of the stop radius part way through.
InterruptableBackgroundChat.yarn.

The Town Crier and the Wanderer can be talked to as well as heard. Each has a DialogueInteractable that starts a node on the primary Dialogue Runner: TownCrierTalkTo and RoamingCharacterTalkTo. RoamingCharacterTalkTo uses the <<pause_path_movement Wanderer>> and <<resume_path_movement Wanderer>> Commands to stop the Wanderer walking while they talk, then send them on their way.

Things to try

  • On the TownCrier group under ChatterGroupManager, turn off Interrupted by Primary. Talk to the Town Crier, and their announcements keep floating above them during the conversation.
  • In TownCrier.yarn, change <<declare $dragon_dead = false>> to <<declare $dragon_dead = true>>. The Town Crier announces that the dragon is slain, and tells you so when you ask for the news. Nothing in the sample sets $dragon_dead, so this is the only way to hear those lines.
  • On ChatterGroupManager, lower Min Delay and Max Delay. The conversations that wait for a delay start again sooner.
Next step Voice Over Recorded voice clips for each line, in more than one language.