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 C# code share. Yarn Scripts set variables with <<set>>, check them in <<if>>, and show them in lines with {$visitor_name}. Your game’s C# code reads and changes 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 C# code<br>SetValue, TryGetValue,<br>AddChangeListener"]
save["Save file in<br>Application.persistentDataPath"]
yarn <-->|read and change| storage
game <-->|read and change| storage
storage <-.->|save and load| save<<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>>Where variables are kept
Each Dialogue Runner has a Variable Storage. If you leave that setting empty, the Dialogue Runner adds an In Memory Variable Storage to its GameObject when the game runs. It keeps variables in memory while the game runs, and they’re lost when the game closes, unless you save them.
The Dialogue System prefab from already has an In Memory Variable Storage, next to its Dialogue Runner, and the Dialogue Runner’s Variable Storage is set to it:
To use a different Variable Storage, add it as a component to a GameObject in your scene, and drag that GameObject into the Dialogue Runner’s Variable Storage field.
When something asks for a variable’s value, the Variable Storage looks in three places, in this order:
- the value it’s storing, if the variable has been set,
- the variable’s expression, if it’s a smart variable,
- the default value from the variable’s
<<declare>>in Yarn.
Reading a variable before it’s been set gives you its declared default.
Reading and changing variables from C#
Get the Variable Storage from the Dialogue Runner, and use the variable’s name, including the $:
var storage = dialogueRunner.VariableStorage;
storage.SetValue("$visitor_name", "Sam");
storage.TryGetValue<float>("$trust", out var trust);
storage.TryGetValue<bool>("$lamp_fixed", out var lampFixed);
storage.TryGetValue<string>("$visitor_name", out var visitor);$.float, bool or string. A variable that hasn’t been set gives its default from <<declare>>.TryGetValue<T>() returns true if it found the variable as that type, and puts the value in its out parameter. If the variable doesn’t exist, or has a different type, it returns false. Contains() tells you whether a variable has been set in the Variable Storage. It returns false for a variable that’s still at its declared default.
Yarn variables can only hold numbers, text, and true or false, so SetValue() takes a float, a string or a bool. Yarn numbers are kept as float, so read them with TryGetValue<float>(). The In Memory Variable Storage throws an exception if a variable’s name doesn’t start with $.
To give a variable a value the dialogue can use from its first line, set it before you start the dialogue. Start Automatically waits one frame before it starts, so a value you set in your own Start() method is ready in time.
Reacting when a variable changes
To keep something on screen up to date, like a counter, call AddChangeListener<T>() with the variable’s name and a method. Your method gets the new value, as that type, whenever the variable changes. That includes changes made by <<set>> in Yarn and changes made by your own code.
IDisposable trustListener;
void Start()
{
var storage = dialogueRunner.VariableStorage;
trustListener = storage.AddChangeListener<float>("$trust", OnTrustChanged);
storage.TryGetValue<float>("$trust", out var trust);
OnTrustChanged(trust);
}
void OnDestroy()
{
trustListener?.Dispose();
}
void OnTrustChanged(float trust)
{
trustLabel.text = $"Trust: {trust}";
}AddChangeListener() returns an IDisposable. Keep it, so you can stop listening.OnTrustChanged with the new value whenever $trust changes. The type in angle brackets has to match the variable’s type, so a number is a float.float.If the type in angle brackets doesn’t match the variable’s type, AddChangeListener() throws an exception.
To hear about every variable, call AddChangeListener() with a method that takes the variable’s name and its new value: storage.AddChangeListener((name, value) => Debug.Log($"{name} is now {value}"));. It also returns an IDisposable.
Smart variables don’t change by being set, so you can’t listen to them. AddChangeListener() throws an exception if you try.
Saving and loading
The Dialogue Runner can save every variable in its Variable Storage to a file in Unity’s persistent data path, and load them back:
dialogueRunner.SaveStateToPersistentStorage("dialogue_save.json");
dialogueRunner.LoadStateFromPersistentStorage("dialogue_save.json");Both return true if they worked. Loading replaces all the current variables with the ones in the file.
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 StartDialogue() after loading.
To save Yarn variables as part of your game’s own save file, use GetAllVariables(), which returns every variable that’s been set, in three dictionaries: one for numbers, one for text and one for true or false. Put them back with SetAllVariables():
var (floats, strings, bools) = storage.GetAllVariables();
storage.SetAllVariables(floats, strings, bools);SetAllVariables() clears the Variable Storage first. To keep the variables that are already there, pass false as a fourth argument. To remove every variable, call Clear().
Seeing variables while you test
The In Memory Variable Storage can show you every variable while the game runs.
In the Editor, select the GameObject with the In Memory Variable Storage, and unfold Debug Variables in the Inspector. In Play Mode, it lists every variable, like $trust = 2 (Single).
To show the same list in the game:
- Add a TextMeshPro text object to your scene, such as .
- Drag it into the In Memory Variable Storage’s Debug Text View field.
Both only list variables that have been set, not ones still at their declared default.
Typed access with a generated class
Yarn Spinner can generate a Variable Storage class with a property for each variable, so you don’t have to type variable names as text. $trust becomes Trust, $visitor_name becomes VisitorName, and each property has the matching C# type. See Generating a typed variables class.
Add the generated class to a GameObject in your scene, and set it as the Dialogue Runner’s Variable Storage. By default it’s based on the In Memory Variable Storage, so it works the same way. Then:
var variables = (YarnVariables)dialogueRunner.VariableStorage;
variables.VisitorName = "Sam";
if (variables.Trust >= 2)
{
OpenTheDoor();
}Writing your own Variable Storage
To keep Yarn variables somewhere else, write a class that extends VariableStorageBehaviour. This one keeps them in a dictionary of save data, so they’re saved and loaded along with the rest of your game:
using System.Collections.Generic;
using Yarn;
using Yarn.Unity;
public class SaveDataVariableStorage : VariableStorageBehaviour
{
public Dictionary<string, object> saveData = new Dictionary<string, object>();
public override void SetValue(string variableName, string stringValue) => Store(variableName, stringValue);
public override void SetValue(string variableName, float floatValue) => Store(variableName, floatValue);
public override void SetValue(string variableName, bool boolValue) => Store(variableName, boolValue);
void Store<T>(string variableName, T value)
{
saveData[variableName] = value;
NotifyVariableChanged(variableName, value);
}
public override bool TryGetValue<T>(string variableName, out T result)
{
switch (GetVariableKind(variableName))
{
case VariableKind.Stored:
if (saveData.TryGetValue(variableName, out var value) && value is T typedValue)
{
result = typedValue;
return true;
}
return Program.TryGetInitialValue(variableName, out result);
case VariableKind.Smart:
return SmartVariableEvaluator.TryGetSmartVariable(variableName, out result);
default:
result = default;
return false;
}
}
public override void Clear() => saveData.Clear();
public override bool Contains(string variableName) => saveData.ContainsKey(variableName);
public override (Dictionary<string, float> FloatVariables, Dictionary<string, string> StringVariables, Dictionary<string, bool> BoolVariables) GetAllVariables()
{
var floats = new Dictionary<string, float>();
var strings = new Dictionary<string, string>();
var bools = new Dictionary<string, bool>();
foreach (var pair in saveData)
{
if (pair.Value is float f) floats[pair.Key] = f;
if (pair.Value is string s) strings[pair.Key] = s;
if (pair.Value is bool b) bools[pair.Key] = b;
}
return (floats, strings, bools);
}
public override void SetAllVariables(Dictionary<string, float> floats, Dictionary<string, string> strings, Dictionary<string, bool> bools, bool clear = true)
{
if (clear) Clear();
foreach (var pair in floats) SetValue(pair.Key, pair.Value);
foreach (var pair in strings) SetValue(pair.Key, pair.Value);
foreach (var pair in bools) SetValue(pair.Key, pair.Value);
}
}VariableStorageBehaviour. It’s a MonoBehaviour, so you add it to a GameObject.SetValue is called whenever a variable is set, from Yarn or from your code. There’s one for each type a Yarn variable can have.NotifyVariableChanged. Without it, change listeners never hear about the new value.TryGetValue returns a variable’s value. GetVariableKind says whether it’s a stored variable or a smart variable. A stored variable that hasn’t been set falls back to its declared default from Program, and a smart variable is worked out by SmartVariableEvaluator. The Dialogue Runner sets both of these for you.Clear removes every variable.Contains says whether a variable has been set.GetAllVariables returns every variable that’s been set, sorted by type. Saving uses it.SetAllVariables puts variables back, clearing the old ones first unless clear is false. Loading uses it.To use it, add SaveDataVariableStorage to a GameObject in your scene and set it as the Dialogue Runner’s Variable Storage.
A Variable Storage needs those eight methods: the three SetValue methods, TryGetValue, Clear, Contains, GetAllVariables and SetAllVariables. Change listeners, and the Dialogue Runner’s saving and loading, are built on them.
An example
Before the dialogue starts, this script sets $visitor_name to “Sam”. Rosa greets the player by name, and the player’s choice raises $trust, which a label in the corner shows. 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>>
===using System;
using TMPro;
using UnityEngine;
using Yarn.Unity;
public class VariablesDemo : MonoBehaviour
{
[SerializeField] DialogueRunner dialogueRunner;
[SerializeField] TextMeshProUGUI trustLabel;
[SerializeField] TextMeshProUGUI savedLabel;
[SerializeField] Light lampLight;
IDisposable trustListener;
IDisposable lampListener;
void Start()
{
var storage = dialogueRunner.VariableStorage;
storage.SetValue("$visitor_name", "Sam");
trustListener = storage.AddChangeListener<float>("$trust", OnTrustChanged);
storage.TryGetValue<float>("$trust", out var trust);
OnTrustChanged(trust);
lampListener = storage.AddChangeListener<bool>("$lamp_fixed", OnLampFixedChanged);
dialogueRunner.onDialogueComplete.AddListener(OnDialogueComplete);
dialogueRunner.StartDialogue("VariablesDemo");
}
void OnDestroy()
{
trustListener?.Dispose();
lampListener?.Dispose();
}
void OnTrustChanged(float trust)
{
trustLabel.text = $"Trust: {trust}";
}
void OnLampFixedChanged(bool lampFixed)
{
lampLight.enabled = lampFixed;
}
void OnDialogueComplete()
{
if (dialogueRunner.SaveStateToPersistentStorage("variables_demo.json"))
{
savedLabel.text = "Saved to " + Application.persistentDataPath;
}
}
}$visitor_name before the dialogue starts, so Rosa’s first line uses it.$lamp_fixed changes.$trust.$lamp_fixed, and turns the lamp’s light on when it’s true.