Variable Storage
Variables form the foundation of any programming language, and Yarn Spinner is no exception. While Yarn Spinner manages variables differently than you might be accustomed to, understanding this system becomes increasingly important as your games grow in complexity.
This guide explores Yarn Spinner's variable storage system in two parts: first examining how to get the most from the provided system, then diving into more advanced implementation of custom variable storage solutions.
What We'll Be Covering
Using the variable storage system from your Unity code
Reading and writing variables via C#
Generated variable storage wrappers
Using the in-memory variables storage system
Making your own Variable Storage systems
Yarn Variables
Yarn Spinner deliberately limits variable types to strings, numbers, booleans, and enumerations (which themselves are wrappers around the first three types). At its core, Yarn Spinner isn't actually concerned with variables themselves, but rather with values. When encountering a variable, all Yarn Spinner needs is a way to replace that variable with its corresponding value.
This creates an interesting challenge: while computers handle changing values with ease, humans struggle to track unnamed values. Our brains naturally organize information through naming conventions. Variables bridge this gap by giving changeable values names that we can easily reference and understand.
This is where the Variable Storage system comes into play. It handles the translation between human-friendly named variables and computer-friendly values. The system performs this crucial mapping function, while most of Yarn Spinner simply asks "what's the value of $gold
?" without concerning itself with how that value is stored or managed.
Using the Variable Storage
Every game has unique requirements for variable handling. Since Yarn Spinner can't anticipate your specific storage methods, it uses a common interface to get and set variables. This interface connects to your game through the Dialogue Runner.
The Dialogue Runner includes a VariableStorage
property that anything with a reference to the runner can access. When using a custom variable storage system, setting this property configures Yarn Spinner to use your implementation. If you don't provide a variable storage system, Yarn Spinner creates a temporary one automatically.
For beginners, we recommend letting Yarn Spinner handle variable storage temporarily. As your project grows, you'll likely want to implement a more permanent, game-specific storage solution.
Accessing Yarn Variables in your Code
While using variables within Yarn scripts is straightforward, accessing them from C# requires a few additional steps. Because the variable storage system is designed to be replaceable, Yarn Spinner uses the same approaches we'll explore here to interact with variables.
Reading Yarn Variables
To read a Yarn variable in C#, you'll need a reference to your game's dialogue runner. Through this reference, you can access the variable storage system:
The variable storage's TryGetValue
method returns a boolean indicating whether the variable was found. A true
result means a variable with the specified name and type was located and stored in the output parameter. A false
result indicates failure to find a matching variable, so check your spelling and requested type when troubleshooting.
Writing Yarn Variables
Similarly, setting variables uses the dialogue runner's variable storage as the entry point:
Generated Wrapper
While the methods above work effectively, they're not particularly convenient. Yarn Spinner v3 introduces a more elegant solution: a generated wrapper around your variable storage that provides direct property access to each variable.
For example, if your Yarn script includes:
The generator would create:
This approach eliminates the need to write wrapper code manually. It also handles enumerations, generating C# versions of any enums declared in your Yarn scripts. For example:
Becomes:
The system also preserves any custom backing values you define for your enums.
Making a generated storage wrapper
To generate a variable storage wrapper, start with your Yarn Project:
In the Inspector, check the
Generate Variables Source File
boxEnter a class name
Specify a namespace
Select the parent class for your variable storage
Click the
Apply
button
This creates a new C# file containing your wrapper class. Remember to connect this variable storage to your dialogue runner before using it.
In-Memory Variable Storage
Yarn Spinner's built-in in-memory variable storage works well for development projects and smaller games. It wraps a standard dictionary and is automatically created if you don't provide your own storage system.
Its primary limitation is ephemerality—variables only exist as long as they remain in memory. When a scene unloads or the game closes, any unsaved changes are lost. The system does support persistence through the dialogue runner's SaveStateToPersistentStorage
and LoadStateFromPersistentStorage
methods, but you must call these explicitly at appropriate times.
Loading performs this process in reverse, reading the JSON string and injecting the recovered variables back into storage. Remember that players can access and potentially delete save files, so your code should handle missing files gracefully.
While the in-memory storage with built-in saving/loading works adequately for smaller projects, it typically won't scale to meet the needs of larger games. For those situations, you'll want to implement a custom variable storage solution.
Making your own Variable Storage System
Larger games typically already have established systems for managing game state and handling save/load functionality. Rather than maintaining separate systems for Yarn variables, why not integrate them into your existing architecture? This provides a single source of truth for all game data, simplifying development and maintenance.
To make your existing state system compatible with Yarn Spinner, you'll need to implement a VariableStorageBehaviour
subclass. This MonoBehaviour implements interfaces that allow Yarn Spinner to interact with your storage system.
You'll need to implement eight key methods, starting with these four for individual variable operations:
void SetValue(string variableName, string stringValue)
Associate the string stringValue
with the variable named variableName
void SetValue(string variableName, float floatValue)
Associate the float floatValue
with the variable named variableName
void SetValue(string variableName, bool boolValue)
Associate the boolean boolValue
with the variable named variableName
bool TryGetValue<T>(string variableName, out T result)
Retrieve the variable named variableName
as a type T
and store it into the result
out parameter. Return true
if this was possible or false
otherwise
These handle most variable storage interactions. You'll also need two utility methods:
void Clear()
Delete all variables from the variable storage
bool Contains(string variableName)
Return true
if there is a variable named variableName
in the variable store
Finally, implement two bulk storage and retrieval methods used by the built-in persistence functions and editor utilities:
void SetAllVariables(Dictionary<string, float> floats, Dictionary<string, string> strings, Dictionary<string, bool> bools, bool clear = true)
Is a bulk setter for all variables. The three dictionary parameters represent each of the floats
, strings
, and bools
needed to be saved. The clear
parameter indicates if the variable storage should clear itself before applying the bulk set.
(Dictionary<string, float> FloatVariables, Dictionary<string, string> StringVariables, Dictionary<string, bool> BoolVariables) GetAllVariables()
Returns all variables in the store. Returns them as a 3-tuple of dictionaries, each dictionary should contain all variables in the store of it's type, with the types being float
, string
, bool
.
Smart Variables
Yarn Spinner v3 introduces Smart Variables, which function like computed properties in C#. Their values are determined by running Yarn code rather than direct variable lookup.
When implementing your own variable storage, you don't need to handle smart variables directly. The VariableStorageBehaviour
base class already provides this functionality, which your subclass inherits automatically. We strongly recommend against overriding this behavior unless you fully understand the implementation details.
Example
Let's integrate Yarn variables into an existing game state system with some interesting complexities. Our example system has two key characteristics that make integration challenging:
It stores the complete history of variable changes rather than just current values, perhaps for supporting time-rewind mechanics or achievement tracking.
It uses indexing instead of key storage to minimize save file size. Keys can consume significant storage space—in our example with variables like
$knows_fred = true
,$gold_coins = 2
, and$player_name = "Alice"
, keys represent approximately 60% of the data. In games with thousands of variables, the key-to-value ratio can reach 10:1. Our system avoids storing keys altogether while still handling key-based lookups.
Our existing system stores game state in an array of IConvertible
Lists, adding a new entry to the appropriate list whenever a value changes and returning the last entry when a value is requested.
Here's our existing system:
Let's examine key methods, starting with initialization:
This creates a perfect hash function for our keys and initializes the state array. Next, let's see how it adds values:
After validating the key, this appends the new value to the list for that index. Finally, here's value retrieval:
After validating the key, this returns a copy of all values for that variable.
Conforming to VariableStorageBehaviour
Now let's adapt this system to work with Yarn Spinner by implementing VariableStorageBehaviour
:
Besides changing the base class to VariableStorageBehaviour
, we've added a YarnProject
reference for accessing default values and implemented the required methods. Let's modify the Initialise
method first:
We now include Yarn variable names in our key list and initialize them with their default values. Next, let's implement the SetValue
methods:
Since we already have a method for adding values by name, implementation is straightforward. Now for TryGetValue
:
While we could have used the existing TryGetValues
method and just returned the last element, this implementation shows a more direct approach. It validates the key, retrieves the latest value, and returns it after type checking.
The Contains
method presents an interesting challenge since we don't store keys directly:
This leverages a characteristic of our perfect hash function: some array slots remain null by default because creating a minimal perfect hash can be challenging. Variables that don't exist will hash to empty slots, allowing us to use a null check to determine existence.
Finally, let's implement the bulk operations. First, SetAllVariables
:
This simply iterates through each variable collection and adds values individually. While not the most efficient approach, it's adequate for this infrequently-called method.
Lastly, GetAllVariables
:
Since we don't store keys directly, we use the project's InitialValues
to iterate through known Yarn variables, retrieve their current values, and sort them by type into the appropriate dictionaries.
And we're done!
With these additions, we've successfully adapted our existing game state system to serve as a Yarn Spinner variable storage provider. The integration required minimal additional code, mostly consisting of adapters that call into our existing methods. This demonstrates how straightforward it can be to integrate Yarn Spinner with your existing architecture.
For the complete example code, see LINK.
Last updated
Was this helpful?