Variables and Storage

Yarn variables, like $trust or $visitor_name, hold values that your dialogue can check and change. They’re kept in a Variable Storage, which your Yarn Scripts and your game share. Yarn Scripts set variables with <<set>>, check them in <<if>>, and show them in lines with {$visitor_name}. Your game’s Blueprints and C++ read and change the same variables, and can be told when they change.

A variable set by one side is seen by the other. Your game can set $visitor_name before a conversation, and the dialogue uses it. The dialogue can set $lamp_fixed, and your game lights the lamp.

flowchart LR
  yarn["Your Yarn Scripts<br>#60;#60;set#62;#62;, #60;#60;if#62;#62;, {$variable}"]
  storage[("Variable Storage<br>$trust = 2<br>$visitor_name = Sam")]
  game["Your Blueprints and C++<br>Set Number Variable, Get Variable As Number,<br>Add Number Change Listener"]
  save["Save slot in<br>Saved/SaveGames"]
  yarn <-->|read and change| storage
  game <-->|read and change| storage
  storage <-.->|save and load| save
Your Yarn Scripts and your Blueprints and C++ both read and change the variables in the Variable Storage. They can be saved to a save slot and loaded back.
<<declare $trust = 0>>
<<declare $lamp_fixed = false>>
<<declare $visitor_name = "stranger">>
Rosa: Hello, {$visitor_name}.
<<set $trust to $trust + 2>>
<<if $trust >= 2>>
    Rosa: Thanks for the help.
<<endif>>
Three variables, declared with a default value, then set and checked.

Where variables are kept

Each Dialogue Runner has a Variable Storage. If you leave that setting empty, the Dialogue Runner makes a Yarn In Memory Variable Storage when it begins play. It keeps variables in memory while the game runs, and they’re lost when the game closes, unless you save them.

When something asks for a variable’s value, the Yarn In Memory Variable Storage looks in three places, in this order:

  1. the value it’s storing, if the variable has been set,
  2. the variable’s expression, if it’s a smart variable,
  3. the default value from the variable’s <<declare>> in Yarn.

Reading a variable before it’s been set gives you its declared default.

To use a different Variable Storage, like your own, add it as a component on the Dialogue Runner’s Actor and set the Dialogue Runner’s Variable Storage to it before the Dialogue Runner begins play. In Blueprints, do this in the Actor’s Construction Script: drag in the Yarn Dialogue Runner, and call Set Variable Storage with your Variable Storage component. In C++, do it in your Actor’s PostInitializeComponents():

void AGameDialogueActor::PostInitializeComponents()
{
	Super::PostInitializeComponents();

	UYarnDialogueRunner* DialogueRunner = FindComponentByClass<UYarnDialogueRunner>();
	DialogueRunner->VariableStorage = FindComponentByClass<USaveDataVariableStorage>();
}
Setting the Dialogue Runner’s Variable Storage before it begins play. USaveDataVariableStorage is the Variable Storage from Writing your own Variable Storage, below.

Reading and changing variables from Blueprints and C++

The Yarn Spinner nodes for variables take the Dialogue Runner and the variable’s name, including the $. They’re under Yarn Spinner > Variables when you right-click in a Blueprint graph:

NodeWhat it does
Set Number Variable, Set String Variable, Set Bool VariableSets a variable to the Value you give it.
Get Variable As Number, Get Variable As BoolPuts the variable’s value in Out Value. Return Value is true if the variable was found and has that type.
Get Variable As StringPuts the variable’s value in Out Value as text, whatever its type. Return Value is true if the variable was found.
Has VariableTrue if the variable has a value, including a declared default.
Get All Variable Names, Get All VariablesThe names, or names and values, of every variable that’s been set.
The Yarn Spinner nodes for reading and changing variables.
PLACEHOLDER — Screenshot needed
A Blueprint graph. Event BeginPlay connects to Set String Variable, with the Yarn Dialogue Runner component in Dialogue Runner, $visitor_name in Variable Name and Sam in Value. Beside it, a Get Variable As Number node reads $trust, with its Out Value and Return Value pins showing.

The same functions are in UYarnBlueprintLibrary, so you can call them from C++:

#include "YarnBlueprintLibrary.h"

UYarnBlueprintLibrary::SetStringVariable(DialogueRunner, TEXT("$visitor_name"), TEXT("Sam"));

float Trust = 0.0f;
bool LampFixed = false;
FString Visitor;
UYarnBlueprintLibrary::GetVariableAsNumber(DialogueRunner, TEXT("$trust"), Trust);
UYarnBlueprintLibrary::GetVariableAsBool(DialogueRunner, TEXT("$lamp_fixed"), LampFixed);
UYarnBlueprintLibrary::GetVariableAsString(DialogueRunner, TEXT("$visitor_name"), Visitor);
Line 3 Set a variable. Use its whole name, including the $.
Lines 5–10 Read variables as a float, bool or FString. A variable that hasn’t been set gives its default from <<declare>>. Each returns true if it found the variable.
Reading and changing Yarn variables from C++.

You can also use the Variable Storage directly, through the IYarnVariableStorage interface. Its functions are Blueprint events, so in C++ call them through the interface’s Execute_ functions, not on the interface itself:

UObject* Storage = DialogueRunner->VariableStorage.GetObject();

IYarnVariableStorage::Execute_SetNumber(Storage, TEXT("$trust"), 2.0f);

FYarnValue Trust;
if (IYarnVariableStorage::Execute_TryGetValue(Storage, TEXT("$trust"), Trust))
{
	float TrustValue = Trust.ConvertToNumber();
}
Reading and changing a variable through the Dialogue Runner’s Variable Storage.

Yarn variables can only hold numbers, text, and true or false. Yarn numbers are kept as float, so a whole number you set comes back as a float. A variable’s name has to start with $. If it doesn’t, the Yarn In Memory Variable Storage logs a warning and doesn’t store it.

The Dialogue Runner makes its default Variable Storage when it begins play, so variables can’t be set before then. To give a variable a value the dialogue can use from its first line, turn off the Dialogue Runner’s Auto Start, set the variable in Event BeginPlay or BeginPlay(), and then call Start Dialogue.

Reacting when a variable changes

The Yarn In Memory Variable Storage can tell you when a variable changes. That includes changes made by <<set>> in Yarn and changes made by your own Blueprints or C++.

To keep something on screen up to date, like a counter, add a change listener with Add Number Change Listener, Add String Change Listener or Add Bool Change Listener. Your event gets the variable’s name and its new value, as that type:

  1. After Event BeginPlay, get the Yarn Dialogue Runner’s Variable Storage, and cast it to Yarn In Memory Variable Storage.
  2. Call Add Number Change Listener on it, with Variable Name set to $trust.
  3. Drag from the Callback pin and add a custom event. It has two inputs: Variable Name and New Value, a Float. Update your counter from New Value.
  4. Save the node’s Return Value, a Yarn Variable Listener Handle, in a variable. In Event End Play, pass it to Remove Change Listener.
PLACEHOLDER — Screenshot needed
A Blueprint graph. Event BeginPlay gets Variable Storage from the Yarn Dialogue Runner, casts it to Yarn In Memory Variable Storage, and calls Add Number Change Listener with $trust. Its Callback pin connects to a custom event called On Trust Changed, which sets a text block to “Trust: " followed by New Value. Its Return Value is stored in a variable called Trust Listener.

In C++, bind the callback to a UFUNCTION:

UYarnInMemoryVariableStorage* Storage = Cast<UYarnInMemoryVariableStorage>(DialogueRunner->VariableStorage.GetObject());

FOnYarnNumberVariableChanged Callback;
Callback.BindDynamic(this, &AVariablesDemo::OnTrustChanged);
TrustListener = Storage->AddNumberChangeListener(TEXT("$trust"), Callback);

float Trust = 0.0f;
UYarnBlueprintLibrary::GetVariableAsNumber(DialogueRunner, TEXT("$trust"), Trust);
OnTrustChanged(TEXT("$trust"), Trust);
Line 1 Change listeners are on the Yarn In Memory Variable Storage, so cast the Dialogue Runner’s Variable Storage to it.
Lines 3–5 Call OnTrustChanged with the new value whenever $trust changes. Keep the handle, so you can stop listening with Storage->RemoveChangeListener(TrustListener).
Lines 7–9 Show the starting value. Adding a listener doesn’t call your function until the value changes.
Updating a counter whenever $trust changes. TrustListener is an FYarnVariableListenerHandle, and OnTrustChanged is a UFUNCTION taking a const FString& and a float.

A Number listener only hears about number values, a String listener about text, and a Bool listener about true or false.

To hear about every variable, bind to the Yarn In Memory Variable Storage’s On Variable Changed event. It passes Variable Name and New Value, a Yarn Value. In C++, that’s Storage->OnVariableChanged.AddDynamic().

Smart variables don’t change by being set, so change listeners and On Variable Changed never hear about them.

Saving and loading

Yarn Spinner can save every variable in a Dialogue Runner’s Variable Storage to a save slot, using Unreal’s save game system, and load them back. The nodes are under Yarn Spinner > Persistence:

NodeWhat it does
Save Variables To SlotSaves every variable that’s been set to the slot.
Load Variables From SlotClears the Variable Storage, and loads the variables from the slot.
Does Variable Slot ExistTrue if the slot has been saved to.
Delete Variable SlotDeletes the slot.
The Yarn Spinner nodes for saving and loading variables.

Each takes a Slot Name, YarnVariables unless you change it, and a User Index, 0 unless you change it. Use a different slot name for each save slot, like YarnSave_Slot1 and YarnSave_Slot2. The slots are saved in your project’s Saved/SaveGames folder while you work in the editor.

UYarnBlueprintLibrary::SaveVariablesToSlot(DialogueRunner, TEXT("YarnSave_Slot1"), 0);
UYarnBlueprintLibrary::LoadVariablesFromSlot(DialogueRunner, TEXT("YarnSave_Slot1"), 0);
Saving and loading Yarn variables from C++.

Both return true if they worked. Loading sets each variable, so change listeners hear about the loaded values. The save includes the values Yarn Spinner uses to track which nodes have been run, so visited() gives the same answers after loading.

Saving and loading only work with a Yarn In Memory Variable Storage, or a class based on it. With any other Variable Storage, both return false.

Only the variables are saved, not where the dialogue is up to. To pick a conversation up again, save the name of the node you want to start from, and pass it to Start Dialogue after loading.

To save Yarn variables as part of your game’s own save file, call Get All Variables on the Yarn In Memory Variable Storage. It gives you three maps of every variable that’s been set: Out Floats for numbers, Out Strings for text and Out Bools for true or false. Put them back with Set All Variables, which clears the Variable Storage first unless you untick Clear First. To remove every variable, call Clear.

Seeing variables while you test

The Yarn In Memory Variable Storage’s Get Debug String node returns every variable, one per line, like $trust = 2 (Number). Pass it to Print String to see it on screen and in the Output Log.

It only lists variables that have been set, not ones still at their declared default.

Writing your own Variable Storage

To keep Yarn variables somewhere else, write a C++ class that implements the IYarnVariableStorage interface. This one is a component that keeps them in a map, marked SaveGame so that your game’s own save system can write it along with everything else:

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "YarnVariableStorage.h"
#include "SaveDataVariableStorage.generated.h"

UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class USaveDataVariableStorage : public UActorComponent, public IYarnVariableStorage
{
	GENERATED_BODY()

public:
	UPROPERTY(SaveGame)
	TMap<FString, FYarnValue> YarnVariables;

	virtual void SetString_Implementation(const FString& VariableName, const FString& Value) override;
	virtual void SetNumber_Implementation(const FString& VariableName, float Value) override;
	virtual void SetBool_Implementation(const FString& VariableName, bool Value) override;
	virtual void SetValue_Implementation(const FString& VariableName, const FYarnValue& Value) override;
	virtual bool TryGetValue_Implementation(const FString& VariableName, FYarnValue& OutValue) override;
	virtual bool Contains_Implementation(const FString& VariableName) override;
	virtual void Clear_Implementation() override;
	virtual void SetYarnProject(UYarnProject* Project) override;
	virtual IYarnSmartVariableEvaluator* GetSmartVariableEvaluator() const override;
	virtual void SetSmartVariableEvaluator(IYarnSmartVariableEvaluator* Evaluator, UObject* EvaluatorOwner) override;

private:
	UPROPERTY()
	TObjectPtr<UYarnProject> YarnProject;

	IYarnSmartVariableEvaluator* SmartVariableEvaluator = nullptr;
};
Lines 8–9 A component that implements IYarnVariableStorage, so you can add it to an Actor.
Lines 14–15 Where the variables are kept. SaveGame marks the map for Unreal’s save game serialisation.
Lines 17–23 The interface’s Blueprint events, implemented in C++.
Lines 24–26 The Dialogue Runner calls these when it begins play, to give the storage its Yarn Project and the evaluator for smart variables.
SaveDataVariableStorage.h. A Variable Storage that keeps Yarn variables with the game’s save data.
#include "SaveDataVariableStorage.h"
#include "YarnSmartVariables.h"

void USaveDataVariableStorage::SetString_Implementation(const FString& VariableName, const FString& Value)
{
	SetValue_Implementation(VariableName, FYarnValue(Value));
}

void USaveDataVariableStorage::SetNumber_Implementation(const FString& VariableName, float Value)
{
	SetValue_Implementation(VariableName, FYarnValue(Value));
}

void USaveDataVariableStorage::SetBool_Implementation(const FString& VariableName, bool Value)
{
	SetValue_Implementation(VariableName, FYarnValue(Value));
}

void USaveDataVariableStorage::SetValue_Implementation(const FString& VariableName, const FYarnValue& Value)
{
	YarnVariables.Add(VariableName, Value);
}

bool USaveDataVariableStorage::TryGetValue_Implementation(const FString& VariableName, FYarnValue& OutValue)
{
	if (const FYarnValue* Stored = YarnVariables.Find(VariableName))
	{
		OutValue = *Stored;
		return true;
	}
	if (SmartVariableEvaluator && SmartVariableEvaluator->TryGetSmartVariable(VariableName, OutValue))
	{
		return true;
	}
	return YarnProject && YarnProject->TryGetInitialValue(VariableName, OutValue);
}

bool USaveDataVariableStorage::Contains_Implementation(const FString& VariableName)
{
	return YarnVariables.Contains(VariableName);
}

void USaveDataVariableStorage::Clear_Implementation()
{
	YarnVariables.Empty();
}

void USaveDataVariableStorage::SetYarnProject(UYarnProject* Project)
{
	YarnProject = Project;
}

IYarnSmartVariableEvaluator* USaveDataVariableStorage::GetSmartVariableEvaluator() const
{
	return SmartVariableEvaluator;
}

void USaveDataVariableStorage::SetSmartVariableEvaluator(IYarnSmartVariableEvaluator* Evaluator, UObject* EvaluatorOwner)
{
	SmartVariableEvaluator = Evaluator;
}
Lines 4–17 The typed setters all store a Yarn Value.
Lines 19–22 SetValue is called whenever the Yarn Script sets a variable with <<set>>, and by the Set … Variable nodes.
Lines 24–36 TryGetValue returns a variable’s value: the stored value if it’s been set, then a smart variable’s value, then the declared default from the Yarn Project.
Lines 38–46 Contains says whether a variable has been set, and Clear removes every variable.
Lines 48–61 Keep the Yarn Project and the smart variable evaluator the Dialogue Runner passes in.
SaveDataVariableStorage.cpp.

To use it, add a Save Data Variable Storage component to the Dialogue Runner’s Actor, and set it as the Dialogue Runner’s Variable Storage, as in Where variables are kept.

A Variable Storage needs those ten functions. Change listeners, Get All Variables and the Save Variables To Slot and Load Variables From Slot nodes only work with the Yarn In Memory Variable Storage. If you need them, base your class on UYarnInMemoryVariableStorage instead, and override the functions you want to change.

An example

Before the dialogue starts, this Actor sets $visitor_name to “Sam”. Rosa greets the player by name, and the player’s choice raises $trust, which is shown on screen. Setting $lamp_fixed lights the lamp, and the variables are saved when the dialogue ends.

title: VariablesDemo
---
<<declare $trust = 0>>
<<declare $lamp_fixed = false>>
<<declare $visitor_name = "stranger">>
Rosa: Hello, {$visitor_name}. What brings you here?
-> I can help with the lamp.
    <<set $trust to $trust + 2>>
    Rosa: That's kind of you.
-> Just looking around.
    <<set $trust to $trust + 1>>
    Rosa: Mind the stairs, then.
Rosa: Let me get the lamp going.
<<set $lamp_fixed to true>>
<<if $trust >= 2>>
    Rosa: Thanks for the help, {$visitor_name}.
<<else>>
    Rosa: See you around, {$visitor_name}.
<<endif>>
===
The Yarn Script.
BP_VariablesDemo (Self)
DefaultSceneRoot Scene Component
LampLight Point Light
YarnDialogueRunner Yarn Dialogue Runner
YarnLinePresenter Yarn Line Presenter
YarnOptionsPresenter Yarn Options Presenter
An Actor made up for this example, based on the AVariablesDemo class below. The Dialogue Runner has Auto Start turned off, and its Variable Storage is left empty, so it makes a Yarn In Memory Variable Storage.
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "YarnVariableStorage.h"
#include "VariablesDemo.generated.h"

class UYarnDialogueRunner;
class UPointLightComponent;

UCLASS()
class AVariablesDemo : public AActor
{
	GENERATED_BODY()

protected:
	virtual void BeginPlay() override;
	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

private:
	UPROPERTY()
	TObjectPtr<UYarnDialogueRunner> DialogueRunner;

	UPROPERTY()
	TObjectPtr<UYarnInMemoryVariableStorage> Storage;

	UPROPERTY()
	TObjectPtr<UPointLightComponent> LampLight;

	FYarnVariableListenerHandle TrustListener;
	FYarnVariableListenerHandle LampListener;

	UFUNCTION()
	void OnTrustChanged(const FString& VariableName, float NewValue);

	UFUNCTION()
	void OnLampFixedChanged(const FString& VariableName, bool NewValue);

	UFUNCTION()
	void OnDialogueComplete();
};
Lines 30–31 The two change listeners, kept so they can be removed.
Lines 33–40 The callbacks have to be UFUNCTIONs, because change listeners and Dialogue Runner events are dynamic delegates.
VariablesDemo.h.
#include "VariablesDemo.h"
#include "Components/PointLightComponent.h"
#include "Engine/Engine.h"
#include "YarnBlueprintLibrary.h"
#include "YarnDialogueRunner.h"

void AVariablesDemo::BeginPlay()
{
	Super::BeginPlay();

	DialogueRunner = FindComponentByClass<UYarnDialogueRunner>();
	LampLight = FindComponentByClass<UPointLightComponent>();
	Storage = Cast<UYarnInMemoryVariableStorage>(DialogueRunner->VariableStorage.GetObject());

	UYarnBlueprintLibrary::SetStringVariable(DialogueRunner, TEXT("$visitor_name"), TEXT("Sam"));

	FOnYarnNumberVariableChanged TrustCallback;
	TrustCallback.BindDynamic(this, &AVariablesDemo::OnTrustChanged);
	TrustListener = Storage->AddNumberChangeListener(TEXT("$trust"), TrustCallback);

	float Trust = 0.0f;
	UYarnBlueprintLibrary::GetVariableAsNumber(DialogueRunner, TEXT("$trust"), Trust);
	OnTrustChanged(TEXT("$trust"), Trust);

	FOnYarnBoolVariableChanged LampCallback;
	LampCallback.BindDynamic(this, &AVariablesDemo::OnLampFixedChanged);
	LampListener = Storage->AddBoolChangeListener(TEXT("$lamp_fixed"), LampCallback);

	DialogueRunner->OnDialogueComplete.AddDynamic(this, &AVariablesDemo::OnDialogueComplete);
	DialogueRunner->StartDialogue(TEXT("VariablesDemo"));
}

void AVariablesDemo::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	if (Storage)
	{
		Storage->RemoveChangeListener(TrustListener);
		Storage->RemoveChangeListener(LampListener);
	}
	Super::EndPlay(EndPlayReason);
}

void AVariablesDemo::OnTrustChanged(const FString& VariableName, float NewValue)
{
	if (GEngine)
	{
		GEngine->AddOnScreenDebugMessage(1, 30.0f, FColor::White, FString::Printf(TEXT("Trust: %.0f"), NewValue));
	}
}

void AVariablesDemo::OnLampFixedChanged(const FString& VariableName, bool NewValue)
{
	LampLight->SetIntensity(NewValue ? 5000.0f : 0.0f);
}

void AVariablesDemo::OnDialogueComplete()
{
	UYarnBlueprintLibrary::SaveVariablesToSlot(DialogueRunner, TEXT("VariablesDemo"), 0);
}
Lines 11–13 Find the Dialogue Runner and the lamp’s light, and get the Yarn In Memory Variable Storage the Dialogue Runner made when it began play.
Line 15 Set $visitor_name before the dialogue starts, so Rosa’s first line uses it.
Lines 17–23 Keep the trust counter up to date, and show the starting value.
Lines 25–27 Light the lamp when $lamp_fixed changes.
Lines 29–30 Save when the dialogue ends, then start it. Auto Start is off, so the dialogue starts here, after $visitor_name is set.
Lines 33–41 Remove both listeners when the Actor leaves play.
Lines 43–49 Gets the new value of $trust, and shows it on screen.
Lines 51–54 Gets the new value of $lamp_fixed, and turns the lamp’s light on when it’s true.
Lines 56–59 Save every variable to a save slot called VariablesDemo.
VariablesDemo.cpp.
Next step Smart Variables Variables that are worked out from other variables each time they're read.