Commands
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 300 0>>
<<wait 1>>
Rosa: Here, you'll need this.
<<give_item "brass key" 1>>
===When the dialogue reaches a Command, the Dialogue Runner calls the Blueprint function or C++ code you’ve connected to it. Once that has finished, the dialogue carries on.
sequenceDiagram
participant R as Dialogue Runner
participant A as BP_Lighthouse
participant Rosa as Rosa (BP_Character)
Note over R: #60;#60;play_sound door_creak#62;#62;
R->>A: play_sound("door_creak")
A-->>R: returns
Note over R: Shows "Oh! You gave me a fright."
Note over R: #60;#60;move Rosa 300 0#62;#62;
R->>Rosa: move(300, 0)
Rosa-->>R: returns true
Note over Rosa: Rosa moves for 1 second
Rosa->>R: Complete Blocking Command
Note over R: Carries on with #60;#60;wait 1#62;#62;Writing a Command
There are several ways to connect a Command to your game. When the dialogue reaches a Command, the Dialogue Runner looks for it in this order:
- the built-in Commands,
<<wait>>and<<stop>>, - Commands you’ve added by name, with the Register Command Handler node or
AddCommandHandler()in C++ (see Adding Commands by name), - a function with the same name as the Command, on one of the Dialogue Runner’s Command Handler Objects,
- a function with the same name as the Command, on the Actor named by the Command’s first argument, or on one of that Actor’s components.
The last two need no registering beyond the function itself, and the Dialogue Runner converts each argument to the type of the function’s parameter. They work the same way for Blueprint functions and C++ UFUNCTIONs.
Commands on a Command Handler Object
The Dialogue Runner has an array called Command Handler Objects, in the Commands part of its Yarn Spinner section. When the dialogue reaches a Command, the Dialogue Runner looks at each object in the array, in order, for a function whose name is exactly the Command’s name, including its case. A function called play_sound is <<play_sound>>.
To make <<play_sound>> a function on the Actor that has your Dialogue Runner:
- Open the Actor’s Blueprint, for example
BP_Lighthouse. - Add a function called
play_sound, with one input: Sound Name, a String. - Make the function play the sound. For example, if Sound Name is
door_creak, call Play Sound 2D with your door sound. - In the Event Graph, after Event BeginPlay, drag in the Yarn Dialogue Runner component, get its Command Handler Objects, and connect an Add node with Self as the item.
From C++, add the object to the array yourself: DialogueRunner->CommandHandlerObjects.Add(this);. The function has to be a UFUNCTION, and its name has to match the Command’s name exactly, so a C++ function for <<play_sound>> is called play_sound.
<<play_sound door_creak>>Commands on an Actor in the Level
If no Command Handler Object has a matching function, the Dialogue Runner treats the Command’s first argument as the name of an Actor. It looks for that Actor in the Level, and calls the function named after the Command on it, or on one of its components that has it. The rest of the arguments are passed to the function.
For <<move Rosa 300 0>>, the Dialogue Runner finds the Actor called Rosa and calls its move function with 300 and 0:
- Open
BP_Character, and add a function calledmove, with two inputs: X and Y, both Float. - Add an output called
ReturnValue, a Boolean, and set it to true on the Return Node. That makes the dialogue wait, as described in Making the dialogue wait. - In the function, call a custom event that does the moving, like Start Move, passing X and Y. A Blueprint function can’t contain latent nodes like Move Component To or Delay, so the moving happens in the event.
- In Start Move, use Move Component To on the root component, with Over Time set to 1. From its Completed pin, call Complete Blocking Command on the Dialogue Runner.
<<move Rosa 300 0>>If every character in your game uses the same Blueprint, each one gets a <<move>> Command, and the first argument says which character moves. If there’s no Actor with that name, the Command isn’t handled (see When a Command isn’t found). If there is, but neither it nor its components have the function, the Dialogue Runner also logs a warning saying so.
BP_Character_C_1, not the label you gave it. For Commands that have to work in a packaged game, use a Command Handler Object or add the Command by name.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>>.
For a function on a Command Handler Object or an Actor, each argument is converted to the type of the function’s parameter:
| Parameter type | What you write in Yarn |
|---|---|
| String | Any text. |
| Integer, Float | A number, like 3 or 0.5. |
| Boolean | true or false. |
| Vector, Rotator and other structs | The struct in Unreal’s text format, like (X=100,Y=200,Z=0) for a Vector. Put it in double quotes if it has spaces. |
If there are fewer arguments than parameters, the rest of the parameters get their type’s default, like 0, an empty String or false. Extra arguments are ignored. If an argument can’t be converted, the function is still called, with the parameter left at its default. Turn on the Dialogue Runner’s Verbose Logging to see a warning in the Output Log when that happens.
Commands you add by name get every argument as text, in an array of Strings, and convert them yourself.
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 function returns. To make it wait until something has finished, like an animation or a sound, make it a blocking Command. The dialogue then stays where it is until you call Complete Blocking Command on the Dialogue Runner.
- For a function on a Command Handler Object or an Actor, return true from an output called
ReturnValue, a Boolean. In C++, return abool. Returning false, or having no output, means the dialogue carries on straight away. - For a Command added by name, use the Register Blocking Command Handler node, or
AddBlockingCommandHandler()in C++. The dialogue always waits for these.
DialogueRunner->AddBlockingCommandHandler(TEXT("wave"), [this](const TArray<FString>& Parameters)
{
PlayWaveAnimation();
GetWorldTimerManager().SetTimer(WaveTimer, [this]()
{
DialogueRunner->CompleteBlockingCommand();
}, 1.5f, false);
});You can call Complete Blocking Command from inside the handler, if the Command turns out to have nothing to wait for. If no blocking Command is waiting, it logs a warning and does nothing.
For more on waiting in Commands, see Async and Cancellation.
Adding Commands by name
You can add a Command to a Dialogue Runner yourself, with any name, and connect it to a Blueprint event or a C++ lambda. The handler gets the Command’s arguments as an array of Strings.
In Blueprints:
- After Event BeginPlay, add a Register Command Handler node.
- Connect your Dialogue Runner to Dialogue Runner, and type the Command’s name in Yarn, like
give_item, into Command Name. - Drag from the Handler pin and add a custom event. It has one input, Parameters, an array of Strings.
- In the event, read the arguments with Get: index 0 is the first argument. Convert numbers with String To Integer or String To Float.
In C++, call AddCommandHandler() on the Dialogue Runner. To use the plugin from your game’s C++ code, add "YarnSpinner" to your module’s dependencies in its .Build.cs file.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "GameCommands.generated.h"
class UYarnDialogueRunner;
UCLASS()
class AGameCommands : public AActor
{
GENERATED_BODY()
protected:
virtual void BeginPlay() override;
private:
UPROPERTY()
TObjectPtr<UYarnDialogueRunner> DialogueRunner;
TMap<FString, int32> Inventory;
};#include "GameCommands.h"
#include "YarnDialogueRunner.h"
void AGameCommands::BeginPlay()
{
Super::BeginPlay();
DialogueRunner = FindComponentByClass<UYarnDialogueRunner>();
DialogueRunner->AddCommandHandler(TEXT("give_item"), [this](const TArray<FString>& Parameters)
{
if (Parameters.Num() < 2)
{
return;
}
const FString& ItemName = Parameters[0];
const int32 Count = FCString::Atoi(*Parameters[1]);
Inventory.FindOrAdd(ItemName) += Count;
});
DialogueRunner->StartDialogue(TEXT("CommandsExample"));
}<< and >>.<<give_item "brass key" 1>> passes brass key and 1. Convert the count to a number, and add it to the inventory.To remove a Command, use the Unregister Command Handler node, or call DialogueRunner->RemoveCommandHandler(TEXT("give_item")).
If you add a Command with a name that’s already been added, the new one replaces the old one. You can’t replace wait or stop: the Dialogue Runner logs a warning, and the handler is never called.
Commands marked with YarnCommand
In C++, you can mark a UFUNCTION with YarnCommand metadata, and add every marked function on an object at once with Register Commands From Object. The function takes the Command’s arguments as an array of Strings:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "AudioCommands.generated.h"
UCLASS()
class AAudioCommands : public AActor
{
GENERATED_BODY()
protected:
virtual void BeginPlay() override;
UFUNCTION(meta = (YarnCommand = "play_sound"))
void PlaySound(const TArray<FString>& Parameters);
};The YarnCommand metadata gives the Command’s name in Yarn. If you leave the name empty, the function’s name is used. The function has to take exactly one parameter, a const TArray<FString>&.
#include "AudioCommands.h"
#include "YarnCommandLibrary.h"
#include "YarnDialogueRunner.h"
void AAudioCommands::BeginPlay()
{
Super::BeginPlay();
UYarnDialogueRunner* DialogueRunner = FindComponentByClass<UYarnDialogueRunner>();
UYarnCommandLibrary::RegisterCommandsFromObject(DialogueRunner, this);
}
void AAudioCommands::PlaySound(const TArray<FString>& Parameters)
{
if (Parameters.Num() > 0)
{
UE_LOG(LogTemp, Log, TEXT("Playing %s"), *Parameters[0]);
}
}YarnCommand or YarnFunction.<<play_sound door_creak>> calls this with door_creak as the first element of Parameters.To remove them all again, use Unregister Commands From Object.
UFUNCTION metadata isn’t included in a packaged game. Instead, the editor saves the marked functions it finds in your project’s Config/DefaultGame.ini, in the Yarn Spinner Command Registry section of Project Settings, and a packaged game reads them from there. The editor updates it when your C++ or Blueprints change. If you change YarnCommand metadata and package your game without opening the editor in between, open the editor once, or reimport a Yarn Project, so the saved list is up to date.
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. With no number, it waits one second. 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 like YarnDialogueRunner: Unhandled command 'play_sound' in the Output Log, and carries on with the dialogue.
To handle those Commands yourself, bind to the Dialogue Runner’s On Unhandled Command event. It passes the whole Command as text in Command Text, like play_sound door_creak. The error is still logged. See Dialogue Runner for its other events.
To make the dialogue wait while you handle an unknown Command, turn off the Dialogue Runner’s Continue On Unhandled Command, in the Behaviour part of its Yarn Spinner section. The dialogue then stays where it is until you call Continue on the Dialogue Runner.
DialogueRunner->OnUnhandledCommand.AddDynamic(this, &AGameCommands::HandleUnhandledCommand);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 a .ysls.json file that the plugin writes next to each .yarnproject file.
The plugin writes the file when a Yarn Project is imported or reimported, after a Blueprint compiles, and after C++ code is reloaded. To write it yourself, right-click a Yarn Project in the Content Browser and choose Generate YSLS File.
The file includes:
- C++
UFUNCTIONs marked withYarnCommandorYarnFunction, with their parameters and their doc comment, - Register Command Handler, Register Blocking Command Handler and Register Function Handler nodes in your Blueprints, when the name is typed into the node rather than connected from another node. Only the name is included.
Commands added with AddCommandHandler() in C++, and functions found on Command Handler Objects or Actors, aren’t included. To have a C++ Command suggested, mark it with YarnCommand and add it with RegisterCommandsFromObject().
For a setup that uses Commands and Functions together, see Putting It Together.