Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Learn about Dialogue Presenters, which present dialogue content to the user in Yarn Spinner for Unity.
A Dialogue Presenter is a component that receives content from a Dialogue Runner, and presents it to the player. Dialogue Views are how the player sees your game's lines of dialogue, and how they select choices in the dialogue.
If you used an earlier version of Yarn Spinner, you may be familiar with Dialogue Views. Dialogue Presenters are the same thing, but renamed. Innovation, baby.
A Dialogue Runner can have multiple Dialogue Presenters. For example, in most situations, you'll have a Dialogue Presenter that's designed to display lines of dialogue:
...and another that's in charge of displaying options to the player:
If you want a custom Dialogue Presenter that can display Night in the Woods-style speech bubbles, or a Mass Effect style dialogue wheel, then check out our premium .
They're a great way to support the project, and get some fancy dialogue views into your game. ❤️
Because every game's needs are different, a Dialogue Presenter is designed to be extremely customisable, and you can create your own Dialogue Presenters to suit the needs of your game.
Because there are common patterns of how games work with dialogue, Yarn Spinner for Unity comes with some pre-built Dialogue Presenters that handle common use cases:
Line Presenter is a Dialogue Presenter that displays a single line of dialogue in a text box that's inside a canvas, and shows a button that the user can click to proceed.
Options Presenter is a Dialogue Presenter that displays a collection of options in a list.


Learn about the Unity components that you use when working with Yarn Spinner for Unity.
Yarn Spinner for Unity is made up of a number of components. The most important of these are the Dialogue Runner, which loads and runs your scripts, and the Dialogue Views that show content to your player.
In this section, you'll learn about how to work with each of these.
Learn about the Dialogue Runner, which runs the contents of your Yarn Scripts and delivers lines, options and commands to your game.
The Dialogue Runner is the bridge between the dialogue that you've written in your Yarn Spinner Scripts and the other components of your game. It's a component that's responsible for loading, running and managing the contents of a Yarn Project, and for delivering the content of your Yarn Spinner Scripts to the other parts of your game, such as your user interface.
You can easily add a Dialogue Runner to your scene as part of a prefab that we supply named Dialogue System.
Adding a Dialogue System is the first step in adding Yarn Spinner-powered dialogue to a Scene in Unity.
To use a Dialogue System, you add it to a game object in your scene, connect it to Dialogue Presenters, and provide it with a Yarn Project to run.
With the Yarn Spinner for Unity installed in your Unity project, you can add a Dialogue System to your Unity Scene by choosing the GameObject menu -> Yarn Spinner -> Dialogue System or by right-clicking in the Hierarchy and choosing Yarn Spinner -> Dialogue System.
With the Dialogue System added to the Scene, you'll find it in the Hierarchy:
We'll discuss the other components and GameObjects that are provided inside our Dialogue System shortly, as the Component you need to understand first is the Dialogue Runner itself.
If you select the Dialogue System in the Hierarchy and look at the Inspector, you'll find the parameters for the Dialogue Runner.
To function, the Dialogue Runner needs one primary thing: a Yarn Project.
When you want to start running the dialogue in your game, you call the Dialogue Runner's StartDialogue method. When you do this, the Dialogue Runner will begin delivering lines, options and commands to its Dialogue Views.
You can also tell it to start automatically by choosing the relevant checkbox in the Inspector.
The Dialogue Runner is designed to work with other components of Yarn Spinner for Unity:
The contents of your dialogue are delivered to your .
The values of are stored and retrieved using the Dialogue Presenter's .
Content that users should see, including the text in their current language, voice over clips, and other assets, are retrieved using the Dialogue Runner's .
Yarn Project
The that this Dialogue Runner is running.
Variable Storage
The to store and retrieve variable data from. If you do not set this, the Dialogue Runner will create an for you at runtime.
Line Provider

The to use to get user-facing content for each line. If you do not set this, the Dialogue Runner will create a for you at runtime.
Dialogue Presenters
The to send lines, options and commands to.
Start Automatically
If this is turned on, the Dialogue Runner will start running the node named Start Node when the scene starts. If this is not turned on, you will need to call to start running.
Start Node
If Start Automatically is turned on, the Dialogue Runner will start running this node when the scene starts. (If your Yarn Project does not contain a node with this name, an error will be reported.)
Run Selected Options as Lines
If this is turned on, when the user chooses an option, the Dialogue Runner will run the selected option as if it were a Line.
Verbose Logging
If this is turned on, the Dialogue Runner will log information about the state of each line to the Console as it's run.
Allow Option Fallthrough
If every option is , should the dialogue runner fall through to the next piece of content?
On Node Start
A Unity Event that's fired when the Dialogue Runner begins running a new node. This may be fired multiple times during a dialogue run.
On Node Complete
A Unity Event that's fired when the Dialogue Runner reaches the end of a node. This may be fired multiple times during a dialogue run.
On Dialogue Start
A Unity Event that's fired when the Dialogue Runner starts running dialogue.
On Dialogue Complete
A Unity Event that's fired when the Dialogue Runner stops running dialogue.
On Unhandled Command
A Unity Event that's fired when a Command is encountered. This will only be called if no other part of the system has already handled the command, such as command handlers registered via or .


Every game's data storage requirements are different. For this reason, Yarn Spinner is designed to make it straightforward to create your own custom component for managing how Yarn scripts store and load variables in ways that work with the other parts of your game.
Custom Variable Storage components are subclasses of the abstract class VariableStorageBehaviour. To implement your own, you need to implement the following methods:
public bool TryGetValue<T>(string variableName, out T result);
public void SetValue(string variableName, string stringValue);
public void SetValue(string variableName, float floatValue);
public void SetValue(string variableName, bool boolValue);
public void Clear();
public bool Contains(string variableName);Learn about the Line Advancer, a component that can signal to a Dialogue Presenter that the user wants to proceed to the next piece of content.
A Line Advancer listens for user input and sends requests to a Dialogue Runner to advance the presentation of the current line, either by asking a dialogue runner to hurry up its delivery, advance to the next line, or cancel the entire dialogue session.
A Line Advancer is generally used to implement a 'press spacebar to continue/skip' mechanic.
To use a Line Advancer, create a new game object, and attach a Line Advancer component to it using the Add Component button.
You can control what specific input the component is looking for by changing the Continue Action Type setting:
If you set the Input Mode to Key Code, you can select a key on the keyboard that will continue to the next line on press, or hurry up.
If you set the Input Mode to Input Actions, you can create an Action from an input device (such as from a keyboard, gamepad, or other method).
The Built-in Localised Line Provider is a Line Provider that fetches localized text or audio assets (AudioClip) for a line of dialogue, given the user's language.
The Built-in Localised Line Provider will automatically use Addressable Assets, if the Addressables package is installed in your Unity project and the Yarn Project is configured to use Addressable Assets.
Text Language Code
Line Providers are components that are responsible for taking the Line objects that the Dialogue Runner produces, and fetches the appropriate localised content for that line. Line Providers produce LocalizedLine objects, which are sent to the Dialogue Runner's Dialogue Presenters.
When a Yarn Spinner Script runs, the Dialogue Runner produces Line objects. These objects contain information about the line, but not the text of the line itself. This is because it's the responsibility of the game to load the user-facing parts of the line, including the text of the line in the player's current language setting, as well as any other assets that may be needed to present the line, such as audio files for voiceover.
Yarn Spinner comes with two built-in types of line providers:
Built-In Localised Line Provider is a Line Provider that uses Yarn Spinner's built-in localisation system.
is a Line Provider that fetches the text and any localised assets from .
Learn about Options Presenter, a Dialogue Presenter that shows options in a list.
An Options Presenter is a that displays options in a list, using Unity UI. When the Dialogue Runner encounters a set of options in your Yarn script, the Options Presenter will display them, wait for the user to select one of them, and then sends that choice back to the Dialogue Runner.
When this view receives options from the Dialogue Runner, it creates an instance of the Option Item you specify in the Option View Prefab property, and adds it as a child.
The language that the Built-in Localised Line Provider should use to fetch localised text for.
Audio Language
The language that the Built-in Localised Line Provider should use to fetch localised audio clips for.

Input Mode
The type of input that this line advancer responds to.
Hurry Up Line Key Code or Action (available depending on choice of Input Mode)
Tell the advance to hurry up.
Next Line Key Code or Action (available depending on choice of Input Mode)
Force the next line.
Cancel Dialogue Key Code (available depending on choice of Input Mode)
Cancel dialogue.
Runner
The Dialogue Runner that will receive requests to advance or cancel content
Multi Advance Is Cancel
Does repeatedly requesting a line advance cancel the line?
Advance Count (available if Multi Advance is Cancel is chosen)

The number of times that a line advance occurs before the current line is cancelled.
Canvas Group
The Canvas Group that the Options List View will control. The Canvas Group will be made active when the Options List View is displaying options, and inactive when not displaying options.
Option View Prefab
A prefab containing an Option View. The Options List View will create an instance of this prefab for each option that needs to be displayed.
Shows Last Line
If this is turned on, the Options Presenter will show the text of the last line that ran before options appeared. This can be useful when you want to give context to a collection of options.
Last Line Text
A TextMeshPro Text object that will display the text of the last line that appeared before options appeared. This field only appears when Shows Last Line is enabled.
Last Line Container


Unity Localised Line Provider is a Line Provider that fetches localized text and assets for a line of dialogue from a String Table and optionally from an Asset Table, based on the project's current localization settings.
Strings Table
The String Table Collection containing localised line text. See to learn how to populate it with your project's dialogue.
Assets Table
Variable Storage components are responsible for storing and retrieving the values of variables in your Yarn scripts. When a Yarn script needs to get the value of a variable, it asks the Variable Storage for it; when a Yarn script sets the value of a variable, the Variable Storage is given the value.
Each game has different requirements for how variables are stored, which means that Yarn Spinner doesn't make any assumptions how the information is actually stored on disk. Instead, you can create your own custom Variable Storage script that implements the methods that Yarn Spinner needs.
If you don't connect a Variable Storage to your Dialogue Runner, it will create an In-Memory Variable Storage when the game starts, and use that.
The game object that contains the Last Line Text. This object is set to active when options run and a last line is available, and is set to inactive when an option is selected.
Last Line Character Name Text
A TextMeshPro Text object that will display the character name found in the last line, if one is available.
Last Line Character Name Container
The game object that contains the Last Line Text. This object is set to active when options run, a last line is available, and the last line has a character name. It is set to inactive when an option is selected.
Show Unavailable Options
If this is turned on, then any options whose line condition has failed will still appear to the user, but they won't be selectable. If this is off, then these options will not appear at all.
Fade UI
If this is turned on, the alpha value of the Canvas Group will be animated up and down when options appear, creating a fade-in and fade-out effect.
Fade Up Duration
The amount of time that the Canvas Group will take to fade up when options appear, if Fade UI is turned on.
Fade Down Duration
The amount of time that the Canvas Group will take to fade down when an option is selected, if Fade UI is turned on.
(Optional) The Asset Table Collection containing localised assets. If an Asset Table is provided, then the Unity Localised Line Provider will fetch localised assets for each line, based on the line's ID.
The In-Memory Variable Storage component is a Variable Storage component that stores all variables in memory. These variables are erased when the game stops.
Debug Text View
A Unity UI Text object that will display a summary of the variables that have been stored in this component. If this is not set, this property will not be used.
You can use this property to display a debug summary of your variables at run-time in your games.
Debug Variables
This area of the Inspector shows a summary of the variables. This works similarly to the Debug Text View property, but the summary is only ever shown in the Editor, and it doesn't require any setup.
Yarn Spinner 3 transitions from the callbacks and coroutines approach of versions 1 and 2 to an asynchronous programming model. This guide explores what this means for your development, how to use these new features, and important considerations when implementing async code.
Basics of asynchronous programming and async and await keywords
Supported awaiters
Creating your own async code
How to cancel awaited code
Using completion sources
Asynchronous programming isn't drastically different from traditional approaches. In many ways, it's a refinement of the coroutine pattern with some syntax changes and additional capabilities.
At its core, async programming provides a way to write code that performs operations asynchronously. This means your code doesn't block program execution when encountering long-running tasks (such as those spanning multiple frames).
To illustrate this concept, let's use a simple analogy: imagine you are a computer making a cup of tea. Your process might look like this:
Fill the kettle
Boil the kettle
Add tea to the cup
Add the water to the cup
In non-async code, each step blocks execution - while waiting for the water to boil, you'd be frozen, unable to respond to other inputs or perform other tasks. In async code, these steps remain non-blocking. You can respond to other events while still monitoring the kettle. You'll still wait for the water to boil before proceeding to the next step (you're not performing tasks in parallel), but you won't block the entire system during that wait.
As a developer, you decide which code segments should be async and which should execute synchronously. Ideally, this distinction shouldn't complicate your code's readability or logical flow. That's the essence of async programming.
The primary distinction between asynchronous programming and traditional approaches is the introduction of new keywords. The most significant is await, which tells C# to pause execution at that point until the specified asynchronous operation completes, then resume from there.
You can use await with any method flagged as asynchronous or any method returning an awaiter (more on these shortly). This brings us to the async keyword, which must be added to method signatures that contain awaiting calls. Unity will report errors if you try to await code in a method without the async keyword, or if you include async in a method that doesn't await anything.
These keywords represent the main syntactic differences. While there's considerable complexity enabling asynchronicity under the hood, you rarely need to concern yourself with those details. Simply await asynchronous code and add asyncto methods that perform awaiting operations.
This approach brings an additional benefit: it makes asynchronous code easily identifiable. With a quick glance at keywords and method signatures, you can understand which code executes asynchronously and which code awaits other operations, resulting in cleaner, more maintainable code.
You might wonder, "Can't we accomplish all this with coroutines?" It's a valid question. Coroutines have served Unity developers well for years and are widely understood. However, they have several limitations that make async programming a superior alternative in many cases.
First, coroutines cannot return values. While workarounds exist, they're exactly that - workarounds. Async methods can naturally return values without additional complexity.
Second, coroutines lack cancellation context. To stop a coroutine, you need external management code to maintain references and handle cancellation, which becomes increasingly complex with nested coroutines. Moreover, coroutines receive no notification when cancelled, forcing external code to handle cleanup. Async code addresses this through cancellation tokens, which provide cancellation signals allowing code to clean up after itself and propagate cancellation to other async operations it calls. Similarly, exceptions thrown in coroutines can't be caught by the initiating code, whereas async code enables centralized error handling.
Coroutines are also tied to MonoBehaviours. While often convenient, this can force unnecessary Unity dependencies in code that otherwise wouldn't need them, such as networking components.
Additionally, coroutines run on the main thread. This is typically appropriate, but makes offloading work to other threads cumbersome. Async code supports multi-threading more elegantly (though for heavily threaded operations, the Jobs system remains preferable).
Finally, coroutines don't clearly signal their intent in method signatures and call sites. They return IEnumerator, giving no indication they represent long-running operations. The difference between StartCoroutine(MyCoroutine()) and MyCoroutine() isn't immediately obvious, and editor-mode coroutines require a completely different approach. Async code uses consistent syntax across all contexts with clear signaling of intent.
While coroutines do work and have some minor advantages, their quirks often lead to more complex, harder-to-maintain code compared to async alternatives.
The underlying infrastructure for asynchronous programming extends beyond this guide's scope, but different systems provide the necessary components to support awaiting operations. You can think of these as containers for awaited work that can be queried, cancelled, or ignored. Awaiters notify when their work completes, allowing code execution to continue.
Yarn Spinner supports three types of awaiters:
We've implemented a system that detects and uses the most appropriate awaiter for your project. We prioritize UniTask if installed, falling back to Awaitables, and finally to Tasks if no other options exist.
This behavior is encapsulated in our YarnTask awaiter. YarnTask wraps one of the above awaiters and provides conversion methods between different awaiter types, making it easier to use Yarn Spinner with your preferred awaiter system. In most cases, you won't need to think about this - simply using await with the appropriate code segments should work seamlessly.
While these three awaiter types are similar, each has distinct characteristics worth noting.
Tasks are part of C# rather than Unity, meaning they lack integration with Unity's run loop and GameObject lifecycle. Without careful management, this can lead to unexpected behaviors like GameObject movement after exiting play mode. Tasks also have the highest memory and performance overhead of the three options (though still relatively modest). However, they offer the most extensive API with numerous convenience methods and flexibility - a trade-off for being the most generic option. Tasks are recommended only as a last resort when better alternatives aren't available.
Awaitables are a relatively recent Unity addition. They provide Task-like functionality for common asynchronous Unity operations with awareness of the run loop and GameObject lifecycle. They're more lightweight but less flexible than Tasks. One important caveat: cancelled Awaitables throw exceptions that must be caught - a necessity of their implementation rather than a design flaw, but still a notable difference from Tasks. We recommend Awaitables over Tasks, and most newer Unity versions support them.
UniTask is a third-party asynchronous library that combines the tight engine integration and lightweight footprint of Awaitables with many quality-of-life features from Tasks. Like Awaitables, UniTask throws exceptions when awaited code is cancelled, requiring exception handling. It's our recommended approach for asynchronous programming in Unity.
As mentioned earlier, we've created a YarnTask wrapper awaiter. This isn't intended to replace any of the above options, but rather to provide a convenience layer supporting as many developers and Unity versions as possible.
Let's explore creating basic async code by converting a coroutine to an async approach. We'll use a simple example of moving a cube from one position to another over time.
First, here's how you might implement this as a coroutine:
You would start it like this:
Now, let's convert this to an async implementation:
And to start it:
Next, let's add cancellation support - after all, what good is a moving cube if its movement can't be stopped?
Now we can clean up when cancelled - in this case, jumping to the end position, though cleanup could mean many different things depending on your implementation.
To use this cancellation feature:
Each MonoBehaviour includes a token that's cancelled when the MonoBehaviour receives the Destroy call. However, this isn't particularly useful here, since we're more interested in cancelling movement before destruction. Let's create a new cancellation token linked to the destroy token that we can cancel independently:
Now we have a token we can cancel whenever needed, but because it's linked to the destroy cancellation token, destruction will still cascade cancellation downward. This nesting can be as deep as required, with cancellation propagating through the entire chain with a single call.
We should also handle any exceptions that might be thrown:
To test cancellation, we can trigger it in response to user input:
You can safely call cancel multiple times without negative consequences, as the tokens handle this gracefully. With that, we've created a fully async and cancellable cube movement system!
Moving cubes is useful, but we're still performing tasks without communication. Let's return a value from our async method. Imagine creating a custom dialogue option selector that rolls a die and selects the option corresponding to the result:
Then in our RunOptionsAsync method, we await the roll and use the result to select an option:
Notice we're using a generic form of YarnTask, where the generic type specifies the return type: YarnTask<int> for the dice roller and YarnTask<DialogueOption> for the dialogue option selection. We don't need to explicitly return a YarnTask<T> (though we can) - C# is smart enough to infer the appropriate return type.
The final major async component is completion sources. These are particularly useful for converting non-asynchronous code, especially UI with callbacks like buttons, to work with async patterns.
A completion source allows external objects to set a completion result, which other code can await like any other async task. For example:
In your custom presenter code, you might use this like:
Now the line will wait for the user to press the button before continuing.
Completion sources can also return values. This is how the default options presenter works - each option item receives the same completion source, and when selected, it sets the completion source with the chosen option:
The selected value is then returned to the dialogue system:
You now have a foundational understanding of async programming and how it's implemented in Yarn Spinner. This knowledge is essential for creating custom dialogue presenters and other interactive components in your Yarn Spinner projects.
Integrating Text Animator with Yarn Spinner 3.
This guide covers the integration of Text Animator with Yarn Spinner 3's default Line Presenter. While we strongly recommend creating custom Dialogue Presenters for precise control over your game's presentation, this guide will help you get the basic integration working quickly.
This guide is written for Text Animator version two, but most of the information here also applies to version three of Text Animator. We also have a which has direct support for Text Animator, for both version two and three, and is already pre-configured to work with the default and custom presenters.
Expand the Dialogue System prefab in your hierarchy and navigate to the Text game object within the Line Presenter ( Dialogue System → Canvas → Line Presenter → Text ).
Add the following components to the Text game object:
Drink the tea
IEnumerator MoveCube(float duration, Vector3 goal)
{
float accumulator = 0;
Vector3 start = this.transform.position;
while (accumulator < duration)
{
this.transform.position = Vector3.Lerp(start, goal, accumulator / duration);
yield return null;
accumulator += Time.deltaTime;
}
}void Start()
{
StartCoroutine(MoveCube(1, new Vector3(10, 0, 0)));
}async YarnTask MoveCube(float duration, Vector3 goal)
{
float accumulator = 0;
Vector3 start = this.transform.position;
while (accumulator < duration)
{
this.transform.position = Vector3.Lerp(start, goal, accumulator / duration);
await YarnTask.Yield();
accumulator += Time.deltaTime;
}
}async void Start()
{
await MoveCube(1, new Vector3(10, 0, 0));
}async YarnTask MoveCube(float duration, Vector3 goal, CancellationToken token = default)
{
float accumulator = 0;
Vector3 start = this.transform.position;
while (accumulator < duration && !token.IsCancellationRequested)
{
this.transform.position = Vector3.Lerp(start, goal, accumulator / duration);
await YarnTask.Yield();
accumulator += Time.deltaTime;
}
this.transform.position = goal;
}async void Start()
{
await MoveCube(5, new Vector3(10, 0, 0), this.destroyCancellationToken);
}CancellationTokenSource cancellationTokenSource;
async void Start()
{
cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(this.destroyCancellationToken);
await MoveCube(5, new Vector3(10, 0, 0), cancellationTokenSource.Token);
}async void Start()
{
cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(this.destroyCancellationToken);
try
{
await MoveCube(5, new Vector3(10, 0, 0), cancellationTokenSource.Token);
}
catch (System.Exception ex)
{
Debug.LogException(ex);
}
}void Update()
{
// if you release tab the cube movement is cancelled
if (Input.GetKeyUp(KeyCode.Tab))
{
cancellationTokenSource.Cancel();
}
}async YarnTask<int> RollDice(int totalNumberOfOptions, CancellationToken cancellationToken)
{
// animate in the dice
await FadeUpDiceAnimation(token);
// generate a random number
int number = UnityEngine.Random(0, count);
// animate the dice rolling to that number
await AnimateRoll(number, token);
// animate away the dice
await FadeDownDiceAnimation(token);
// returning the value
return number;
}public override async YarnTask<DialogueOption> RunOptionsAsync(DialogueOption[] dialogueOptions, CancellationToken cancellationToken)
{
int roll = await RollDice(dialogueOptions.Count, cancellationToken);
return dialogueOptions[roll];
}public class ButtonWaiter : MonoBehaviour
{
public YarnTaskCompletionSource buttonCompletion;
public Button button;
async void Start()
{
button.onClick.AddListener(() =>
{
buttonCompletion.TrySetResult();
});
}
}public override async YarnTask RunLineAsync(LocalizedLine line, LineCancellationToken token)
{
// configuring
var completionSource = new YarnTaskCompletionSource();
buttonWaiter.buttonCompletion = completionSource;
// fade in the UI, including our button
await FadeInUI();
// start waiting on the button
await buttonWaiter.Task;
// fade down the UI
await FadeOutUI();
}OnOptionSelected.TrySetResult(this.Option);// Wait for a selection to be made, or for the task to be completed.
var completedTask = await selectedOptionCompletionSource.Task;
// finally we return the selected option
return completedTask;Add a component
On the TextAnimator_TMP component, ensure that Typewriter Starts Automatically is enabled.
Select the Line Presenter game object (Dialogue System → Canvas → Line Presenter) and modify the following settings:
Disable Fade UI
Typewriter Style Instant
Run your Yarn script to see the Text Animator effects in action.
When using Text Animator, the Line Advancer can detect when a line has finished displaying but has not been dismissed via action markup. This changes the default "quick advance" behavior where rapidly pressing the advance button would skip the line entirely.
To restore quick-skip functionality:
Enable Multi Advance is Cancel on the Line Advancer component
Set Advance Count to 2
This configuration allows players to press the advance button twice in quick succession to skip the current line, similar to the original behavior.
For production games, consider developing your own custom presenter to achieve the exact behaviour and appearance your game requires.
Action Markup Compatibility
Due to architectural considerations in our markup system design, Action Markup and Text Animator cannot currently be used together.
Workaround: If you need both Text Animator effects and inline events, use Text Animator's event system to achieve similar functionality.
This limitation is resolved in the Text Animator paid add-on.


Learn how to create Dialogue Presenters that are designed for the specific needs of your game.
The Line Presenter and Options Presenter are handy for many situations. But your game might need to show lines and options in a particular way. When that's the case, you can write your own custom Dialogue Presenter. This gives you full control over how lines and options appear.
To make a Dialogue Presenter, you first create a subclass of the DialoguePresenterBase class. Then, you add this subclass as a component to a game object in your scene. After that, you can add this game object to the Dialogue Presenters list on your scene's Dialogue Runner.
By itself, an empty subclass of DialoguePresenterBase won't do much. You need to implement specific methods to make it show lines and options.
To get how custom Dialogue Presenters work, it helps to understand how the Dialogue Runner handles content. Yarn Spinner scripts use three types of content: lines, options, and commands. Only the first two, lines and options, need to be shown directly to the player.
When the Dialogue Runner finds lines or options, it first figures out the exact content the player needs to see. Once it has this, it sends the content to each of its Dialogue Presenters.
Your scene can have several Dialogue Presenters. And they can all do different things. For instance, you might have one Dialogue Presenter for lines, another for options, and a third for playing voice-over audio.
In compiled Yarn Spinner Scripts, Lines and Options are represented by line IDs. A line ID is a unique code for the text of a line or an option. When Yarn Spinner needs to show a line or option, it asks its Line Provider for a LocalizedLine object. This object holds the text for the line or option in the player's current language.
If you're showing a group of options, each option in that group has its own LocalizedLine.
Once a LocalizedLine is ready, the Dialogue Runner has everything it needs to show content. What happens next depends on whether it's showing a line or an option.
When Yarn Spinner comes across a line of dialogue, it calls the RunLineAsync method on every Dialogue Presenter. This method gets two things: first, the LocalizedLine from the Line Provider, and second, a LineCancellationToken. The Dialogue Presenter uses this token to know the line's status. Specifically, it checks if the player wants to speed up the line's display or move to the next line.
In Dialogue Presenters, a line is considered "presented" when the player has seen the whole line and is ready for the next one. What this means in practice varies. For example, a Dialogue Presenter playing voice-over audio might finish when all the audio has played. Another one that shows text gradually might finish when all text is visible and the UI has faded out.
The Dialogue Runner waits until all Dialogue Presenters say they've finished showing the line. Then, it moves to the next bit of dialogue.
The RunLineAsync method is asynchronous. This means a Dialogue Presenter signals it's done and ready for the next line by returning from the method.
If your game needs to pause dialogue until the player does something (like press a button), your Dialogue Presenter can do this. It simply doesn't return from the RunLineAsync method until the line cancellation token signals to advance. Because the Dialogue Runner waits for all presenters, the dialogue will pause until your presenter says to go on.
Line presentation usually isn't instant. It typically happens over a short period. So, players might want to speed this up or skip it entirely. Dialogue Presenters need to be ready for the player to ask for a line to hurry up or be skipped at any moment during presentation.
The line's state is held in the LineCancellationToken given during the RunLineAsync method. This token wraps two other Cancellation Tokens: one for advancing to the next line, and one for making the current line hurry. These tokens are linked. So, if the "next line" token is flagged, the "hurry up" token will be too. But it doesn't work the other way around.
Most times, you won't need to access these tokens directly. You can check the line's status using handy properties. IsHurryUpRequested on the token tells your custom Dialogue Presenter if the player wants the display to go faster. And IsNextLineRequested tells you if your Dialogue Presenter should skip the current line completely.
Any part of line presentation that isn't instant should use these properties. This helps decide if they should speed up or be skipped. The same LineCancellationToken is shared by all Dialogue Presenters. So, if the line's status changes, all presenters see it at the same time. Each Dialogue Presenter decides what "hurrying up" means for it; it's a signal to finish the presentation quickly.
For example, a voice-over presenter might fade out audio quickly or cut it off. A text-revealing presenter might show all remaining text at once or very rapidly. Advancing a line is a more direct signal that the player wants the dialogue to move on. You shouldn't ignore IsNextLineRequested when it's true. Presenters should clean up and finish showing the line as soon as possible if the player has asked to advance.
You can change the LineCancellationToken's state using the Dialogue Runner. It has two methods for this. RequestHurryUpLine sets the token so IsHurryUpRequested becomes true. RequestNextLine sets the IsNextLineRequested flag to true. Since the LineCancellationToken wraps two linked tokens, these methods also cancel those internal tokens. Requesting the next line also means requesting the line to hurry up. You can safely request a line advance or hurry up multiple times; it won't cause any issues.
Options are a bit different from lines. They need some kind of player input before the dialogue can go on. The Dialogue Runner needs to know which option was picked.
To manage options, Dialogue Presenters implement the RunOptionsAsync method. This method gets an array of DialogueOption objects and a cancellation token. Each DialogueOption object is an option that can be shown to the player.
When this method is called, the Dialogue Presenter uses the info in the DialogueOption objects to show choices to the player. Then it waits for player input. Once it knows which option was selected, the method should return that chosen option. The Dialogue Runner then uses this to make the selection.
If your presenter doesn't need to handle options, it can return a null value instead.
The Dialogue Runner will ignore any nulls it gets back from presenters here. The first non-null DialogueOption it receives is treated as the selected one.
When the Dialogue Runner sends options to its Dialogue Presenters, it expects only one of them to return a non-null option. If none of them return an option, the Dialogue Runner will never know what was picked. And it will wait forever, and ever, and ever.
If more than one presenter returns an option, the Dialogue Runner uses the first one it gets and ignores the others. After getting a non-null option, the Dialogue Runner cancels the cancellation token given in the method. This tells any presenters that haven't returned yet that an option has been selected. So, they can stop waiting for a selection.
To get the tags on a line, you use the Metadata property on the LocalizedLine objects you receive. Your code decides what to do with these tags.
Learn about Line Presenter, a Dialogue Presenter that shows lines of text.
A Line Presenter is a Dialogue Presenter that displays a single line of dialogue inside a Unity UI canvas. When the Dialogue Runner encounters a line in your Yarn Script, the Line Presenter will display it, wait for the user to indicate they're done reading it, and then dismiss it.
If a line contains a character's name at the start, a Line Presenter can be configured to show the name in a separate text view to the line text itself. If the Character Name Text property is connected to a TextMeshPro Text object, then the character's name will appear in this object.
If you don't attach a Text object to the Character Name Text property, you can choose to either show the character name as part of the line (that is, in the Line Text view), or don't show it all.
A Line Presenter can be configured to use visual effects when presenting lines.
You can choose to have the Line Presenter fade in when a line appears, and fade out when the line is dismissed.
You can choose to have the text of the line appear, one letter at a time, with a "typewriter" effect.
The Dialogue Runner will automatically proceed to the next piece of content once all Dialogue Presenters have reported that they've finished with a line.
If the 'Auto Advance' option on a Line Presenter is turned on, then the Line Presenter will signal that it's done with a line as soon as all visual effects have finished.
If 'Auto Advance' is turned off, then the Line Presenter will not signal that it's done when the effects have finished, and the line's delivery will stop.
To make the Line Presenter finish up, you can call RequestHurryUpLine on the Dialogue Runner. This will not end the current line, but will send a signal to all Line Presenters that it should finish displaying its content quickly. To move onto the next line, you can call the method RequestNextLine on the Dialogue Runner.
The supplied Line Presenter has an arrow button at the bottom. Clicking this will call RequestNextLine on the Dialogue Runner:
Canvas Group
The Canvas Group that the Line Presenter will control. The Canvas Group will be made active when the Line Presenter is displaying a line, and inactive when not displaying a line.
Line Text
A TextMeshPro Text object that the text of the line will be displayed in.
Character: Shows Name In Line

If this is turned on, lines that contain a character's name will display the name in the Line Text section. If it is turned off, character names will not be shown in the Line Text.
Character: Name Field
A TextMeshPro Text object that will display the name of the character currently speaking the line.
Character: Character Name Container
A game object that will be made active when a line contains a character name, and inactive when it doesn't. The Name Field should be a child of this object.
Fade: Fade UI
If this is turned on, the Line Presenter will fade the opacity of the Canvas Group from 0% to 100% opacity when lines appear, and fade back to 0% when lines are dismissed.
Fade: Fade Up Duration
The duration of the Fade effect when fading a new line in, in seconds. If this is zero, the line will appear immediately.
Fade: Fade Down Duration
The duration of the Fade effect when fading a line out, in seconds. If this is zero, the line will disappear immediately.
Auto Advance
If this is turned off, the Line Presenter will finish presenting the line, and then wait. This is useful for games where the user has control over the timing of lines of dialogue. If this is turned on, the Line Presenter will signal to the Dialogue Runner that it's done showing the line once all animations are complete.
Delay before Advancing
If Auto Advance is turned on, the Line Presenter will wait this many seconds after all animations are complete before signalling that it's done showing the line. This option is only available when Auto Advance is turned on.
Typewriter Style
Controls the way that the typewriter will present the text of the line.
Instant: The text will appear all at once.
By Letter: The text will appear at a fixed rate of letters per second. You can configure the speed by adjusting the Letters Per Second rate that appears when the typewriter is in this style.
By Word: The text will appear one word at a time, at a fixed rate of words per second. You can configure the speed by adjusting the Words Per Second rate that appears when the typewriter is in this style.
Custom: You can provide a game object that has a component that implements the interface, for full control over how the typewriter works.
Event Handlers
A list of ActionMarkupHandler objects that will be notified when certain events (like the typewriter starting, characters appearing, and the typewriter finishing) occur.


This guide teaches you how to use the Variable Storage system.
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.
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 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.
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.
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:
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 box
Enter a class name
Specify a namespace
Select the parent class for your variable storage
This creates a new C# file containing your wrapper class. Remember to connect this variable storage to your dialogue runner before using it.
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.
The saving process collects all variables from storage, converts them to a JSON string, and writes this data to a file in Unity's persistent data folder. The specific location , but provides a safe space for save data.
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.
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:
These handle most variable storage interactions. You'll also need two utility methods:
Finally, implement two bulk storage and retrieval methods used by the built-in persistence functions and editor utilities:
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.
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.
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 .
Click the Apply button
// getting the dialogue runner
var runner = FindObjectOfType<Yarn.Unity.DialogueRunner>();
if (runner == null)
{
Debug.LogWarning("Was unable to find a dialogue runner");
return;
}
// attempting to find a float called $gold
if (runner.VariableStorage.TryGetValue<float>("$gold", out var gold))
{
// we found the variable
// it's value has been stored into the gold parameter
// we can now use the gold variable
if (gold > 100)
{
Debug.Log("they are rich, unlock the Player Is Rich cheevo!");
}
}
else
{
// we failed to find $gold
Debug.LogWarning("Was unable to find a number value for $gold");
}// getting the dialogue runner
var runner = FindObjectOfType<Yarn.Unity.DialogueRunner>();
if (runner == null)
{
Debug.LogWarning("Was unable to find a dialogue runner");
return;
}
// modifying the value of the players gold, they now have 25 gold
runner.VariableStorage.SetValue("$gold", 25);/// the number of gold coins the player has
<<declare $gold = 0>>/// <summary>
/// the number of gold coins the player has
/// </summary>
public float Gold
{
get
{
if (this.TryGetValue<float>("$gold", out var gold))
{
return gold;
}
return 0;
}
set => this.SetValue<float>("$gold", value);
}<<enum TimeOfDay>>
<<case Morning>>
<<case Evening>>
<<endenum>>public enum TimeOfDay
{
/// <summary>
/// Morning
/// </summary>
Morning = 0,
/// <summary>
/// Evening
/// </summary>
Evening = 1,
}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)
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
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.
public class GameStateManager : Monobehaviour
{
private List<IConvertible>[] gameState;
private MinPerfectHash hashFunction;
public void Initialise(string[] keys) { ... }
public bool TryGetValues<T>(string variableName, out T[] result) { ... }
public T[] ValuesAt<T>(int index) { ... }
public void AddValue(string variableName, IConvertible value) {}
public void AddValueAt(int index, IConvertible value) {}
public void Rollback(string variableName) { ... }
public void RollbackAt(int index) { ... }
public void Clear() { ... }
}public void Initialise(string[] keys)
{
// generate a new hash function for the specific list of keys
var keyHashGenerator = new VariableHashKeySource(keys);
hashFunction = MinPerfectHash.Create(keyHashGenerator, 1);
// now we make the values array of the size of the hash function
gameState = new List<IConvertible>[hashFunction.N];
}public void AddValue(string variableName, IConvertible value)
{
// if we don't have a hash function we can't find the index for where to add the new value
if (hashFunction == null)
{
throw new InvalidOperationException();
}
var index = hashFunction.IndexOf(variableName);
var values = gameState[index];
// if we have no list at this index that means that the key is invalid
if (values == null)
{
throw new ArgumentException();
}
// finally we can now add the new value to the list
values.Add(value);
gameState[index] = values;
}public bool TryGetValues<T>(string variableName, out T[] result)
{
if (hashFunction == null)
{
result = default;
return false;
}
var index = hashFunction.IndexOf(variableName);
if (gameState[index] == null)
{
result = default;
return false;
}
// adding all the elements to an array, letting you see the variable history
var extant = gameState[index];
T[] values = new T[extant.Count];
for (int i = 0; i < extant.Count; i++)
{
values[i] = (T)extant[i];
}
result = values;
return true;
}public class GameStateManager : Yarn.Unity.VariableStorageBehaviour
{
private List<IConvertible>[] gameState;
private MinPerfectHash hashFunction;
public Yarn.Unity.YarnProject project;
private List<IConvertible>[] gameState;
private MinPerfectHash hashFunction;
public void Initialise(string[] keys) { ... }
public bool TryGetValues<T>(string variableName, out T[] result) { ... }
public T[] ValuesAt<T>(int index) { ... }
public void AddValue(string variableName, IConvertible value) {}
public void AddValueAt(int index, IConvertible value) {}
public void Rollback(string variableName) { ... }
public void RollbackAt(int index) { ... }
public override void Clear() { ... }
public override void SetValue(string variableName, string stringValue) { ... }
public override void SetValue(string variableName, float floatValue) { ... }
public override void SetValue(string variableName, bool boolValue) { ... }
public override bool TryGetValue<T>(string variableName, out T result) { ... }
public override bool Contains(string variableName) { ... }
public override void SetAllVariables(Dictionary<string, float> floats, Dictionary<string, string> strings, Dictionary<string, bool> bools, bool clear = true) { ... }
public override (Dictionary<string, float> FloatVariables, Dictionary<string, string> StringVariables, Dictionary<string, bool> BoolVariables) GetAllVariables() { ... }
}public void Initialise(string[] keys)
{
// if we don't have a project we will have to abort initialisation
if (project == null)
{
Debug.LogError("Unable to initialise variable storage as there is no Yarn Project set");
return;
}
// getting the initial values from the project
// and merging that with the rest of the game keys
var initialValues = project.InitialValues;
List<string> yarnKeys = new();
yarnKeys.AddRange(keys);
yarnKeys.AddRange(initialValues.Keys);
// generate a new hash function for the specific list of keys
var keyHashGenerator = new VariableHashKeySource(yarnKeys.ToArray());
hashFunction = MinPerfectHash.Create(keyHashGenerator, 1);
// now we make the values array of the size of the hash function
gameState = new List<IConvertible>[hashFunction.N];
// now we can add into the array the default yarn values
foreach (var pair in initialValues)
{
uint index = hashFunction.IndexOf(pair.Key);
gameState[index] = new List<IConvertible>()
{
pair.Value,
};
}
}public override void SetValue(string variableName, string stringValue)
{
AddValue(variableName, stringValue);
}
public override void SetValue(string variableName, float floatValue)
{
AddValue(variableName, floatValue);
}
public override void SetValue(string variableName, bool boolValue)
{
AddValue(variableName, boolValue);
}public override bool TryGetValue<T>(string variableName, out T result)
{
// if we don't have a hash function we can't find the index
if (hashFunction == null)
{
result = default;
return false;
}
// getting the index of the variable name
var index = hashFunction.IndexOf(variableName);
var values = gameState[index];
// if we have no value at that index we also can't return it
if (values == null)
{
result = default;
return false;
}
// grabbing the last element
var value = values[^1];
// checking it is actually of type T
if (!typeof(T).IsAssignableFrom(value.GetType()))
{
result = default;
return false;
}
// returning it
result = (T)value;
return true;
}public override bool Contains(string variableName)
{
// if we don't have a hash function we can't see if we have a value for that key
if (hashFunction == null)
{
throw new InvalidOperationException();
}
var index = hashFunction.IndexOf(variableName);
var values = gameState[index];
return values == null;
}public override void SetAllVariables(Dictionary<string, float> floats, Dictionary<string, string> strings, Dictionary<string, bool> bools, bool clear = true)
{
if (hashFunction == null)
{
throw new InvalidOperationException();
}
foreach (var pair in floats)
{
AddValue(pair.Key, pair.Value);
}
foreach (var pair in bools)
{
AddValue(pair.Key, pair.Value);
}
foreach (var pair in strings)
{
AddValue(pair.Key, pair.Value);
}
}public override (Dictionary<string, float> FloatVariables, Dictionary<string, string> StringVariables, Dictionary<string, bool> BoolVariables) GetAllVariables()
{
if (hashFunction == null)
{
throw new InvalidOperationException();
}
if (project == null)
{
throw new InvalidOperationException();
}
Dictionary<string, float> allFloats = new();
Dictionary<string, string> allStrings = new();
Dictionary<string, bool> allBools = new();
foreach (var key in project.InitialValues.Keys)
{
var index = hashFunction.IndexOf(key);
var values = gameState[index];
// if we have no list at this index that means that the key is invalid
if (values == null)
{
continue;
}
var value = values[^1];
if (value is bool v)
{
allBools[key] = v;
continue;
}
if (value is float f)
{
allFloats[key] = f;
continue;
}
if (value is string s)
{
allStrings[key] = s;
continue;
}
}
return (allFloats, allStrings, allBools);
}Retrieve the variable named variableName as a type T and store it into the resultout parameter. Return true if this was possible or false otherwise
