A Command is a line in a Yarn Script that tells your game to do something. It’s written inside << and >>, with the Command’s name first and any arguments after it:
title: CommandsExample
---
<<play_sound door_creak>>
Rosa: Oh! You gave me a fright.
<<move Rosa 700 400>>
<<wait 1>>
Rosa: Here, you'll need this.
<<give_item "brass key" 1>>
===When the dialogue reaches a Command, the Dialogue Runner calls the GDScript method you’ve connected to it. Once the method has finished, the dialogue carries on.
sequenceDiagram
participant R as Dialogue Runner
participant A as Audio code
participant Rosa as Rosa (Character.gd)
Note over R: #60;#60;play_sound door_creak#62;#62;
R->>A: _yarn_command_play_sound("door_creak")
A-->>R: returns
Note over R: Shows "Oh! You gave me a fright."
Note over R: #60;#60;move Rosa 700 400#62;#62;
R->>Rosa: _yarn_command_move(700, 400)
Rosa-->>R: returns a Signal
Note over Rosa: Rosa moves for 1 second
Rosa-->>R: the Signal is emitted
Note over R: Carries on with #60;#60;wait 1#62;#62;Writing a Command
The quickest way to make a Command is to give a method a name that starts with _yarn_command_. The rest of the name is the Command’s name in Yarn, so _yarn_command_play_sound is <<play_sound>>.
When the Dialogue Runner starts, it looks for these methods in:
- scripts with a
class_name, anywhere in your project, - scripts attached to nodes in the running scene,
- autoloads.
If nodes are added to the scene later, it searches again the first time it reaches a Command it doesn’t know.
There are two kinds of Command, depending on whether the method is static.
Commands that aren’t tied to a node
A static method becomes a Command you can use anywhere:
static func _yarn_command_play_sound(sound_name: String) -> void:
AudioManager.play(sound_name)<<play_sound door_creak>>A method in an autoload works the same way, static or not. Autoloads suit Commands that the whole game uses.
Commands on a node
A method that isn’t static belongs to a node. In Yarn, the first argument is the name of the node to call it on:
extends Node2D
func _yarn_command_move(x: float, y: float) -> Signal:
var tween := create_tween()
tween.tween_property(self, "position", Vector2(x, y), 1.0)
return tween.finishedstatic, so it’s a Command on a node. In Yarn, the first argument, Rosa, says which node to call it on. x and y come from the other two arguments, converted to float.finished signal, so the dialogue waits until the move is done.<<move Rosa 700 400>>If every character in your game uses the same script, each one gets a <<move>> Command, and the first argument says which character moves. The Dialogue Runner looks for a node with that name anywhere in the running game. If it can’t find one, or the node doesn’t have the script, it logs an error and the dialogue carries on.
Arguments
Arguments are separated by spaces. To pass an argument with a space in it, put it in double quotes: <<give_item "brass key" 1>>.
Each argument is converted to the type of the method’s parameter:
| Parameter type | What you write in Yarn |
|---|---|
String | Any text. |
int, float | A number, like 3 or 0.5. |
bool | true or false. Writing the parameter’s name means true: for a parameter called wait, <<fade wait>> passes true. |
Vector2, Vector3 | Numbers separated by commas, with no spaces, like 4,2. |
Color | A hex code like #ff8800, or a colour name like orange. |
A node type, like Node2D | The name of a node in the scene. The Dialogue Runner finds it and passes it in. If there’s no such node, it passes null and logs a warning. |
A parameter without a type gets the argument as a String. Parameters with default values can be left out.
If there are too few or too many arguments, or one can’t be converted, the Dialogue Runner logs an error saying which one, doesn’t call the method, and carries on with the dialogue.
To use a variable’s value as an argument, put it in curly braces: <<give_item {$reward} 1>>.
Making the dialogue wait
The dialogue carries on as soon as your method returns. To make it wait until something has finished, like an animation or a sound, do one of these:
- Return a
Signal. The dialogue waits until it’s emitted. ThemoveCommand above returns its tween’sfinishedsignal, so the next line only appears once Rosa has arrived. - Use
awaitin the method. The dialogue waits until the method has finished.
func _yarn_command_wave() -> void:
$AnimationPlayer.play("wave")
await $AnimationPlayer.animation_finishedTo wait with a timer, use YarnAsync.wait(self, seconds) rather than get_tree().create_timer(). It stops counting while the game is paused. See Async and Cancellation.
Adding Commands from code
You can add a Command to a Dialogue Runner yourself, with any name and any method:
@export var dialogue_runner: YarnDialogueRunner
func _ready():
dialogue_runner.add_command("give_item", _give_item)
func _give_item(item_name: String, count: int) -> void:
Inventory.add(item_name, count)<<give_item "brass key" 1>> passes a String and an int.Inventory stands in for however your game keeps track of items.Pass a named method, like _give_item, rather than a lambda. The Dialogue Runner converts arguments using the method’s parameter types, and a lambda has none, so it gets every argument as a String.
To remove a Command, call dialogue_runner.remove_command("give_item").
To add a Command to every Dialogue Runner in your game, call YarnSpinner.register_command() with the same arguments. YarnSpinner is an autoload that the addon adds when it’s enabled. Each Dialogue Runner picks up these Commands when it starts, so register them early, for example in your own autoload’s _ready.
Where the Dialogue Runner looks
The Dialogue Runner’s Auto-Discovery settings control how it finds _yarn_command_ methods:
| Setting | Default | What it does |
|---|---|---|
| Auto Discover Commands | On | Finds _yarn_command_ and _yarn_function_ methods by itself. Turn it off to only use Commands you add yourself. |
| Discovery Root | empty | The node to look under for scripts attached to nodes. When it’s empty, the Dialogue Runner looks through the whole running scene. |
Scripts with a class_name, and autoloads, are found wherever they are. A script without a class_name is only found if it’s attached to a node under the Discovery Root.
Two Commands can’t have the same name. If a second method uses a name that’s already taken, the Dialogue Runner logs an error and ignores it.
Built-in Commands
Two Commands are always available:
| Command | What it does |
|---|---|
<<wait 2>> | Waits for the number of seconds you give it before the dialogue carries on. It stops counting while the game is paused. |
<<stop>> | Ends the dialogue immediately. |
When a Command isn’t found
If the dialogue reaches a Command that isn’t connected to anything, the Dialogue Runner logs an error and carries on with the dialogue.
To handle those Commands yourself, connect to the Dialogue Runner’s command_unhandled signal. It passes the whole Command as text, like play_sound door_creak, and the error isn’t logged. The dialogue then waits until you call dialogue_runner.signal_content_complete(), so call it once you’ve dealt with the Command.
Before it runs any Command, the Dialogue Runner emits command_received with the Command’s name and its arguments. Use it to log Commands, or to react to them in more than one place.
Autocomplete in VS Code
The Yarn Spinner extension for VS Code can suggest your Commands as you type, and warn you about ones it doesn’t know. It reads them from the project’s .ysls.json file, which the addon writes for you. See The .ysls.json file.
The demo scene
This scene uses each kind of Command. Rosa hears the door creak, walks over to the lamp and lights it, and gives the player a key. The panel in the top left lists each Command as the dialogue reaches it.
title: CommandsDemo
---
<<play_sound door_creak>>
Rosa: Oh! You gave me a fright.
<<move Rosa 640 430>>
Rosa: Let me get the lamp going again.
<<light_lamp>>
Rosa: There. Much better.
Rosa: You'll need this to get up the stairs.
<<give_item "brass key">>
Rosa: Up you go!
===extends Node2D
@export var dialogue_runner: YarnDialogueRunner
@export var call_log: RichTextLabel
@export var inventory_label: Label
@export var door: Control
@export var lamp: ColorRect
@export var glow: ColorRect
var _inventory: Array[String] = []
func _ready():
dialogue_runner.add_command("play_sound", _play_sound)
dialogue_runner.add_command("light_lamp", _light_lamp)
dialogue_runner.add_command("give_item", _give_item)
dialogue_runner.command_received.connect(_log_command)
_update_inventory()
func _log_command(command_name: String, args: Array) -> void:
var text := command_name
for arg in args:
var value := str(arg)
text += " " + ('"%s"' % value if " " in value else value)
call_log.append_text("[color=#e6a15a]Command[/color] <<%s>>\n" % text)
# Commands
func _play_sound(sound_name: String) -> void:
var label := Label.new()
label.text = "*creak!*" if sound_name == "door_creak" else "*" + sound_name + "*"
label.add_theme_font_size_override("font_size", 32)
label.position = door.position + Vector2(-10, -50)
add_child(label)
var tween := create_tween()
tween.tween_property(label, "position:y", label.position.y - 60, 1.2)
tween.parallel().tween_property(label, "modulate:a", 0.0, 1.2)
tween.tween_callback(label.queue_free)
func _light_lamp() -> Signal:
var tween := create_tween()
tween.tween_property(lamp, "color", Color(1.0, 0.85, 0.35), 0.8)
tween.parallel().tween_property(glow, "modulate:a", 1.0, 0.8)
return tween.finished
func _give_item(item_name: String) -> void:
_inventory.append(item_name)
_update_inventory()
func _update_inventory() -> void:
var text := "Inventory\n"
if _inventory.is_empty():
text += "(empty)"
for item in _inventory:
text += "• " + item + "\n"
inventory_label.text = text<<give_item>> adds to.<<move>> isn’t here: it’s on Rosa’s script, and the Dialogue Runner finds it by itself._log_command for every Command, before it runs.<<play_sound>>. There’s no real sound: it shows “creak!” by the door, then floats it up and fades it out. It returns nothing, so the dialogue doesn’t wait.<<light_lamp>>. It returns the tween’s finished signal, so the dialogue waits for the lamp.<<give_item>>. It adds the item and redraws the inventory.extends Node2D
func _yarn_command_move(x: float, y: float) -> Signal:
var tween := create_tween()
tween.tween_property(self, "position", Vector2(x, y), 1.0)
return tween.finished