Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
If you're new to Yarn Spinner, or want a refresher on getting started or navigating the documentation, then this is the place to be.
Yarn Spinner is a set of friendly tools for writing interactive narratives and games.
Yarn Spinner is easy for writers to use, but also has powerful features for programmers, game designers, narrative directors, producers, and more.
Yarn Spinner is used in thousands of amazing games, including Rift of the Necrodancer, Night in the Woods, A Short Hike, Lost in Random, Dredge, Frog Detective, Button City, Escape Academy, Baladins, and Unbeatable.
Join our friendly community Discord before you start working with Yarn Spinner. It's the best place to keep up to date, get support and advice, and share what you're working on
Yarn Spinner includes a narrative scripting language, Yarn, that allows you to write your game narratives, quests, and logic in a friendly format. The Beginner's Guide will start you off on your path to learning Yarn Spinner.
Once you're comfortable writing Yarn Scripts, you can integrate Yarn Scripts with a game engine, such as Unity, Godot, or Unreal. Buy one of our useful add-ons, too, if you need it!
We'd love to hear about your game, and show it off when we talk about awesome Yarn Spinner-powered games. And please, don't forget to include a credit for Yarn Spinner!
... use it to power your game too!
Yarn Spinner 3 came out on 16 May 2025! Celebrate!
Yarn Spinner 3 is out! It's the culmination of 10 years of Yarn Spinner development, taking inspiration from how people use Yarn Spinner in wildly successful and amazing games like DREDGE, Lil' Guardsman, A Short Hike, and Demonschool.
For the development of Yarn Spinner 3, stewardship of Yarn Spinner moved from our original game development studio, Secret Lab, to a whole new entity focused solely on Yarn Spinner development. It's now our primary job!
Yarn Spinner is developed in the open on GitHub, and can be purchased from the Yarn Spinner Itch.io Store or the Unity Asset Store.
Yarn Spinner 3 introduces so many improvements and new things, and makes writing complex interactive narratives easier than ever.
Get started with Yarn Spinner 3 via the Beginner's Guide!
... use it to power your game too!
New? Start Here!
Scripting Fundamentals
Advanced Scripting
Yarn Spinner for Unity
Unity Add-Ons
Community Discord
Show us your game
Crediting Yarn Spinner
FAQ
Story Solver
Yarn Spinner for Godot
Yarn Spinner for Unreal
Learn about the glorious history of Yarn Spinner.
Yarn Spinner is built by Yarn Spinner Pty. Ltd., an Australian company based in beautiful Lutruwita/Tasmania, Palawa/Tasmanian Aboriginal country, in Australia.
Yarn Spinner Pty. Ltd. was founded by the team that created Yarn Spinner—Jon Manning, Tim Nugent, and Paris Buttfield-Addison—all colleagues and contributors at Secret Lab, a game development studio, known for Night in the Woods, I Feel Fine, Leonardo's Moon Ship, the Australian ABC Play School games, and more.
Yarn Spinner is now maintained by the Yarn Spinner Pty. Ltd. team, including:
You can find Yarn Spinner on Bluesky too!
v0.9 was released in October 2015, the first public release;
v1.0 was released in January 2020;
v2.0 was released in December 2021;
v2.1 was released in February 2022;
v2.2 was released in April 2022;
v2.3 was released in July 2023;
v2.4 was released in November 2023;
v2.5 was released in December 2024.
v3.0 was released in May 2025.
Yarn Spinner Pty. Ltd. is a tools development company located in Hobart, Australia. Yarn Spinner designs and builds the open source Yarn Spinner project, and offers consulting, integration, and development services for custom Yarn Spinner integrations, features, and more.
Yarn Spinner's core team are also the founders and long-term team collaborators of Secret Lab Pty. Ltd.
Secret Lab Pty. Ltd. is an independent games and creative technology studio located in Hobart, Australia.
Secret Lab has been enthusiastically building video games, apps, and technology to showcase culture, history, arts, and narrative experiences since 2008, and is the longest running games studio in Tasmania.
Secret Lab is proud to be the original creators of Yarn Spinner, and is best known for the BAFTA- and IGF-winning Night in the Woods, and is currently working on the interactive narrative game, I Feel Fine (written by Ryan North), and the adventure-puzzle game, Leonardo's Moon Ship (written by writer of Pixar's Ratatouille, Jim Capobianco).
You can hire Secret Lab to build your video games, interactive experiences, fancy apps, and more.
Get started with Yarn Spinner Scripting by working through the fundamentals in detail.
If you're here, you've worked through the , and setup the , and you're ready to dive into the details of writing dialogue with Yarn Spinner.
This section of the documentation will take you through the scripting fundamentals:
combining Nodes, Lines, Options, and Jumps for simple interactive narratives
using Detours, Variables, and Yarn Spinner's Flow Control features for more complex interactions
the basics of Commands and Functions in Yarn Spinner
running options Once, and Line Groups for flexibility and control
Once you've completed it, you can move to .
Learn about reusing the same line in multiple places, using shadow lines.
In Yarn Spinner Scripts, shadow lines let you reuse the same line in multiple places, without having to create duplicate copies.
Shadow lines are marked using the #shadow:
hashtag. When you use the #shadow:
tag, you specify the line ID of another line, which is called the source line. The source line must have an explicit #line:
hashtag to identify it.
Here’s a simple example of using shadow lines:
The script contains six lines, but only 5 string table entries will be created, because the line “I should go” is shadowed.
Shadow lines are required to have the same text in the Yarn script as their source line, but are allowed to have different hashtags (in addition to the #shadow:
and #line:
hashtags).
There are two important kinds of files you'll use when working with Yarn Spinner for Godot:
are files that link your Yarn Scripts together, and are used by the Dialogue Runner.
are files that contain your written dialogue.
Yarn Spinner for Godot is the set of components and scripts that make Yarn Spinner work inside a Godot project.
In this section, you’ll learn how to install, set up, and work with Yarn Spinner for Godot.
Just getting started with Yarn Spinner for Godot? Start with in your project.
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.
title: Tavern
---
Ava: Hello, barkeep!
Guy: Hi there, how can I help?
Ava: I should go. #line:departure
===
title: Kitchen
---
Ava: Greetings, chef!
Guy: What are you doing back here?
Ava: I should go. #shadow:departure
===
Welcome to Yarn Spinner! On this page you'll learn how to get started.
Welcome to Yarn Spinner! We're thrilled you're here.
This section will equip you with all the knowledge you need to write Yarn Spinner Scripts. Work through the following steps, one-by-one:
Get comfortable with Yarn Spinner Scripting and write Yarn Spinner Scripts using our online, browser based tool, Try Yarn Spinner. Our Beginner's Guide takes you through the very basics.
Once you're comfortable with the basics of Yarn Spinner and Try Yarn Spinner, you'll move to using Yarn Spinner for Visual Studio Code to write narratives, and learn the fundamentals of Yarn Spinner Scripting.
And, finally, check out the advanced features of Yarn Spinner for Visual Studio Code.
Once you've finished here in the Writing Yarn Scripts section, you will progress to the First Steps of using Yarn Spinner for Game Engines.
Dive into the Advanced features of Yarn Spinner Scripting.
If you're here, you've worked through the Beginner's Guide, setup the Yarn Spinner Editor, and learned the Scripting Fundamentals of Yarn Spinner.
Now it's time to look at some more advanced features, including:
Node Groups, Storylets, and Saliency, for complex, responsive storytelling with Yarn Spinner
Tags and Metadata, to give you flexibility on how your lines are handled in a game engine
Markup, for providing hints on how narrative text should be displayed, as well as running commands in a game engine within lines
Shadow Lines, for marking when a line is an identical duplicate of another line
Writing Together, using the Live Share Extension for Visual Studio Code.
Once you've completed it, you're ready to use Yarn Spinner with a game engine, such as Unity
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.
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.
There's a lot of new features for storylets and saliency in Yarn Spinner. Learn about them!
Storylets and Saliency are a new feature of Yarn Spinner 3 that has quite a few different pieces to making it work. Because of this we have multiple samples dedicated to demonstrating these.
The samples are not about the concept of storylets and saliency, if you are new to this and wondering what this all means, then we highly recommend our Storylets and Saliency Primer.
The samples assume you are comfortable now with both the concepts of storylets and salient content and writing them in Yarn Spinner.
There are three samples around using storylets and saliency in Yarn Spinner for Unity:
Basic Storylets and Saliency goes over the basics of using storylets in your games
Custom Saliency Strategies looks at how you can create custom saliency strategies
Advanced Saliency demonstrates using storylets and interpolation together to build entire dynamic stories and events.
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 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 Presenters 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.
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 creating enums, which allow you to create variables that are constrained to a specific set of values.
In Yarn Spinner, enums let you create variables whose value is constrained to a pre-defined list of possibilities.
To define an enum you must provide a name, and some cases for it. Here's a new enum called Food
with the cases Apple
, Orange
, and Pear
:
<<enum Food>>
<<case Apple>>
<<case Orange>>
<<case Pear>>
<<endenum>>
Once you've created an enum, you can use it just like any other variable:
// Declare a new variable with the default value Food.Apple
<<declare $favouriteFood = Food.Apple>>
// You can set $favouriteFood to the 'apple', 'orange' or 'pear'
// cases, but nothing else!
<<set $favouriteFood to Food.Orange>>
// You can use enums in if statements, like any other type of value:
<<if $favouriteFood == Food.Apple>>
I love apples!
<<endif>>
// You can even skip the name of the enum if Yarn Spinner can
// figure it out from context!
<<set $favouriteFood = .Pear>>
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
(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.
Learn about Yarn scripts, which are the assets that contain the dialogue you write.
A Yarn script is a text file containing your dialogue.
To create a new Yarn script in Godot, follow these steps:
Open the Project menu, and choose Tools > YarnSpinner -> Yarn Script.
Choose a directory and filename for the new Yarn script in the dialog that appears.
The new file that you've just created will contain a single node, which has the same name as the file.
You can edit .yarn scripts with the text editor of your choice. To open your editor from within Godot, ensure that you have associated .yarn files on your computer with your desired editor. Then, right click a .yarn script in the Filesystem panel and click Edit in External Program. When you save your changes and return to Godot, it will be re-compiled.
If you're totally new to Yarn Spinner, this is the place to start.
When you first start learning Yarn, you can use , our introductory, browser- based tool for writing Yarn Spinner Scripts. No installation necessary!
Copy and paste, or write, this Yarn Spinner script into Try Yarn Spinner, and hit the Run button in the top-right.
This is a node. A node must always start with a header containing a single key and value pair to set the node's title. The key is title
and the value is Start
. They are separated by a :
. Node titles must start with a letter, and can only contain letters, numbers and underscores.
The header ends with a line containing only ---
and the body of the node follows. The body is where the lines are kept.
There is a single line here: Narrator: Hi, I'm the narrator for the documentation!
A line can contain whatever you want, and doesn't have to begin with a character name.
Finally, the node ends with a line containing only ===
.
In the left-hand pane of Try Yarn Spinner, add a new node by copy and pasting, or writing, the following snippet of Yarn Spinner script. Place it below the previous node.
You may want to Run the Yarn Spinner Script to see what happens. You'll probably notice that nothing has changed!
That's because there is currently no path to these nodes from the Start node. To add a path, you'll need to use the jump command.
Update the contents of the Start
node, and add a jump command, like this:
All commands in Yarn Spinner are surrounded by <<
and >>
. A jump command contains the jump
keyword, and then the title of the node you want to jump to.
So, this particular jump command will jump to the node titled Adventure
.
Use the Run button in the top right-hand corner of Try Yarn Spinner to run your Yarn Spinner Script again. Your story is now slightly more interesting!
You'll notice that when the line containing the jump command arrives, the story continues in the node that was jumped to so you'll now also see the line Narrator: We're going to go on an adventure!
Our story isn't particularly amazing, but we still have another node that's not being used...
We're going to introduce one final concept in this Beginner's Guide: Options.
Update your Adventure node to look like the following:
The lines the start with ->
are known as options. Options provide a choice to the player and always begin with a ->
.
All options at the same level, in the same node, are presented at the same time. So the options we added here will be presented together. Lines indented after an option will only be run if that option is chosen.
Any lines that are not options but occur after options will only be run if one of the options does not jump away to another node.
We just wrote a simple Yarn Spinner script consisting of one node (titled Start
), with one line inside that node, delivering a greeting from a narrator.
Then we added two more nodes, a jump command, and some options, with jumps occuring depending on the option chosen.
And that's the very basics of Yarn Spinner Scripting! Dive into the of Writing Yarn Scripts next.
Learn about the Yarn Spinner Editor, our official extension for Visual Studio Code.
Back in the , you learned the basics of Yarn Spinner Scripting by using our online tool, Try Yarn Spinner.
While Try Yarn Spinner is great for the basics, and for experimenting, when you start creating more complex narratives, you need a great editor! It's time to meet Yarn Spinner for Visual Studio Code.
Yarn Spinner for Visual Studio Code is an extension for the free text editor, Visual Studio Code ("VS Code").
Yarn Spinner for Visual Studio Code gives you syntax highlighting for Yarn Spinner Scripts, as well as a graph view that displays the nodes, and relationships between the nodes, and a whole bunch of other useful features that we'll be exploring as we learn the to write Yarn Spinner Scripts using Visual Studio Code.
If you've never used to VS Code before, for your operating system and platform before continuing.
Don't be too alarmed by the AI-powered nonsense Microsoft will try and sell you. You can disable most of the AI "features" via the Command Palette by pressing Shift + Command + P (Mac) or Ctrl + Shift + P (Windows/Linux), or choosing the View menu -> Command Palette..., and typing Chat: Hide Copilot
and hitting Return/Enter:
Once you've got VS Code installed on your system, you'll need to install Yarn Spinner for Visual Studio Code. To do this:
That's all you need to do to install the extension! You're ready to .
Preview your dialogue within the Yarn Spinner for Visual Studio Code Extension.
You can run your Yarn Spinner Scripts inside the Visual Studio Code extension, without having to import it into a game or run it via a game engine.
This means that you can write content for your game even if the game isn't yet ready to have dialogue added to it. It's also useful for quickly checking to see if your Yarn script works the way that you want it to before testing it in your game.
To preview your dialogue in Visual Studio Code, open the Command Palette by pressing Command-Shift-P
(Control-Shift-P
on Windows), and type "Preview Dialogue". Your current Yarn project will open to the side, and you can begin playing through your script.
While playing through your dialogue in preview mode, there are several features that can be useful to test out a script.
Changing the starting node: By default, the dialogue preview will start playing from a node named "Start", if one is present in your Yarn files. If you want to play from a different node, open the list of nodes and choose the node you want to start from.
Changing whether lines are shown one at a time, or all at once: By default, the dialogue preview shows each line and command one and a time. You can change this to delivering everything all at once by opening the Settings menu and changing the setting to "Show Lines All At Once".
Showing the contents of variables: You can see the current contents of all in your script by opening the Settings menu and choosing "Show Variables". A list of variables will appear, and as you play through your script, they'll update when your script changes their contents.
Changing whether 'unavailable' lines are shown: Options can be depending on whether or not a test has passed. By default, options that fail this test are not shown in the dialogue preview at all; to change this to showing all options (including ones that the user can't choose), open the Settings menu and choose "Show Unavailable Options".
Restarting the script: When you click the Restart button, the dialogue preview will restart from the currently selected starting node.
You can export a stand-alone HTML file containing a runnable version of your Yarn script, which you can send to other people for them to play. This can be particularly useful for writers who want to get feedback on their scripts before working with other team members to integrate their content.
To export your script, click the Export button, and choose where to save the file. The file can then be sent anywhere you like, and can be opened in all major web browsers.
Learn about detour and return, which let you temporarily move to another node, then return.
In addition to using the to move between nodes, you can also use a detour
command.
A detour
command looks very simlar to jump: it takes a single parameter with the title of the node you want to move to, but unlike the jump command the detour command will return to the node that called it afterwards.
The detour command works much like the jump command, except it will return to the node it detoured from after that node is done. Here’s an example of it in action:
If the player replies No?
to the guard’s question, Yarn Spinner will detour to the node titled Guard_Backstory
and run its contents.
When the end of the Guard_Backstory
node is reached, Yarn Spinner will return to the node titled Guard
, and resume from just after the detour
statement.
When you detour
into a node, Yarn Spinner runs the content from that node just as if you’d used a jump
statement. When you reach the end of the node, or reach a return
command, Yarn Spinner will return to just after the detour
command. A return command looks like <<return>>
. There are no parameters.
You can return early from a detoured node by using the return
command. Doing so will return to just after the detour
command as though the end of the node had been reached, for example:
When you detour into a node, that node can itself detour into other nodes. If a detoured node uses a jump
command to run another node, the return stack is cleared. If you detour
into a node, and that node jumps
to another node, Yarn Spinner won’t return to your original detour
site.
When you use <<detour>>
command, they'll be shown in the Graph View in Yarn Spinner for Visual Studio Code as an line with an arrow at each end, from the node that's being detoured to the node that's being detoured to:
Next up, learn about in Yarn Spinner Scripts.
Learn about once statements, which let you specify content that only runs once.
Sometimes it's useful to create lines of dialogue that can only ever be displayed once.
In Yarn Spinner Scripts, you can use a once
statement to denote content that can only be run one single time. When the script reaches a once
statement, it checks to see if it’s run before. If it has, it skips over it! Magic.
There are two main ways you can use a once
statement, which we'll explore below.
<<once>>
and <<endonce>>
:You can also use an <<else>>
clause within the <<once>>
statement, which will be run if the relevant <<once>>
content has already been seen:
You can also add an if
to the once
to run content a single time, but only when a certain condition is true. In all other cases, it will be skipped (or the else
content will be run, if there is any):
<<once>>
to a line, or options:If you add once
(or once if
) to a line, that line will only appear once, and will be skipped over every other time it’s encountered:
Similarly, if you add it to an option, that option will only be selectable once, and will be marked as unavailable after it’s been selected.
once
statements are really useful when you want to show long, detailed content the first time it’s encountered, but you don’t want to show it every time. This means that players don’t need to mash the ‘skip line’ button over and over when they realise that they’re starting to see a long run of lines they’ve already seen:
Learn about using node groups, which allow Yarn Spinner to choose which content to run, depending on conditions.
In Yarn Spinner, you can also create node groups. A node group is collection of nodes that share the same name that Yarn Spinner will choose from.
To create a node group, you create multiple nodes that all share the same name, and ensure that each of the nodes have at least one when:
header.
The when:
header tells Yarn Spinner about the conditions that must be met in order to run the node:
Node groups are combined into a single node that performs the appropriate checks and then runs one of the node group’s members. You start dialogue with a node group using its name. You can also use the jump
or detour
statements to run a node group from somewhere else in your Yarn scripts.
Node groups will be visualised in a box of their own in the Graph View of Yarn Spinner for Visual Studio Code:
You can add as many when:
headers to a node as you want.
Use Yarn Spinner Scripts in Unity games, using Yarn Spinner for Unity.
Yarn Spinner for Unity is the set of components and scripts that make Yarn Spinner work inside a Unity project.
In this section, you’ll learn how to install, set up, and work with Yarn Spinner for Unity.
Yarn Spinner for Unity 3 is compatible with Unity version 2022.3 and newer. If you need to use an older version of Unity, use Yarn Spinner 2.x
If you're here to get started, continue by diving into the page.
If you haven't yet read about the Yarn Spinner Scripting, then visit the pages first.
Learn how to install the Speech Bubbles for Yarn Spinner Package.
You can purchase Speech Bubbles for Yarn Spinner from the or the .
Once you've purchased it, download the package from the store. It will be in the form of a .unitypackage
file. To install the package, open the Unity project that you want to add it to, and open the the Assets menu -> Import Package -> Custom Package...
You'll then be able to select the .unitypackage
file for Speech Bubbles for Yarn Spinner on your file system, and click Open. This will present the Import Unity Package window:
Click the Import button, and Speech Bubbles for Yarn Spinner will be imported into your project.
The Speech Bubbles add-on comes has two packages inside the unitypackage, one for the speech bubbles themselves, and one for the Yarn Spinner Samples. The samples contain necessary resources for the two speech bubble examples but if you already have the you do not need to add them again.
Learn about using line groups, which allow Yarn Spinner to choose which content to run, depending on conditions.
To give your dialogue more life and variety, you can also provide lines in a line group. When lines are in a line group, Yarn Spinner will choose one of them for you.
Line groups are collections of lines that begin with a =>
symbol:
In this example, the Captain will say "Navigator, fire the glitter torpedoes! That'll confuse the enemy ships!
", and then Yarn Spinner will choose one of the subsequent lines to respond with.
You can attach conditions to lines in a line group, to ensure that they only run when it’s appropriate to do so. Conditions can be any true or false expression, and can also be combined with the once
keyword to ensure that a line can only run once:
A line in a line group can also have additional lines belonging to it, which will run if the item is selected.
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.
A quick look at the examples that ship with Speech Bubbles for Yarn Spinner.
Speech Bubbles for Yarn Spinner ships with two example scenes, showcasing the flexibility of the Speech Bubbles.
The 2D Sidescroller Example showcases the flexibility of the Speech Bubble in a variety of contexts, including both the Formal Bubble, and the Casual Bubble. Once you've installed the package, find this example in Speech Bubbles for Yarn Spinner/Examples/2D Sidescroller
.
The 3D Top-Down Example showcases the Speech Bubbles in a 3D environment, with a variety of small customisations. Once you've installed the package, find this example in Speech Bubbles for Yarn Spinner/Examples/3D Top-Down
.
This page shows you how to install Yarn Spinner for Godot, the Godot integration for running Yarn and Yarn Spinner scripts in your Godot-based games.
Download a from the , or clone the repository somewhere.
Locate the addons/
directory in your new local copy of Yarn Spinner for Godot:
Put a copy of this directory into your new Godot project, either by dragging the folder in your file manager (e.g. Finder or Explorer) into the folder of the Godot project, or by dragging from your file manager into the FileSystem dock of your Godot project:
Next, choose the Project menu -> Tools -> C# -> Create C# solution. This will create a C# project for you. We have to do this to trigger the creation of the .csproj
file, which is necessary to let Godot know about the Yarn Spinner plugin.
Next, open the project folder in Visual Studio Code. In the sidebar of VS Code, the .csproj
file and add the following line to it, inside the <Project>
</Project>
tags, but not inside an <ItemGroup>
or <PropertyGroup>:
Your brand new project should look something like this in VSCode:
Save the tweaked .csproj
file and return to Godot, everything is almost ready to go. Click the Build button in the very top right-hand corner of the Godot window. This will trigger a build of the C# solution for the project, which is required to make Godot aware of Yarn Spinner for Godot.
Once the build is complete, open the Project menu -> Project Settings, change to the Plugins tab, and tick the enabled box next to the Yarn Spinner for Godot plugin:
With that, you're ready to go!
Dialogue Wheel for Yarn Spinner ships with two example scenes, showcasing the flexibiltiy of the Dialogue Wheel.
This example shows off the Automatic-Layout Dialogue Wheel, with an ever-increasing number of options being displayed. Once you've installed the package, find this example in Dialogue Wheel for Yarn Spinner/Examples/Automatic Layout Example
.
This example shows the Image Wheel in action, with a very simple third-person sci-fi game. Once you've installed the package, find this example in Dialogue Wheel for Yarn Spinner/Examples/Image Wheel Example
.
A brief history of Yarn Spinner for Unity.
v0.9 was released in October 2015, the first public release;
v1.0 was released in January 2020;
v2.0 was released in December 2021;
v2.1 was released in February 2022;
v2.2 was released in April 2022;
v2.3 was released in July 2023;
v2.4 was released in November 2023;
v2.5 was released in December 2024.
v3.0 was released in May 2025.
Yarn Spinner 3 came out in May 2025, adding new features to run content only once, enumerations, smart variables, and storylets and saliency!
Yarn Spinner 2 debuted in July 2020, adding markup support, dialogue views, and lots lots more!
After being developed for Night in the Woods, and staying in early-release for several years, Yarn Spinner 1 was released in January 2020!
Learn about using smart variables that determine their value at run-time.
Smart variables in Yarn Spinner are a powerful way to improve your dialogue and narrative flows. Unlike regular variables that only change when explicitly set, smart variables recalculate their value every time they're accessed, based on the expression that defines them.
Code Readability: They make your dialogue scripts more readable by using meaningful names instead of complex conditions.
Centralised Logic: Define a condition once and use it throughout your game.
Maintenance: When game mechanics change, you only need to update the smart variable declaration, not every place it's used.
Abstraction: They hide complex game state checks behind simple, descriptive names.
Creating a smart variable in Yarn Spinner is straightforward: <<declare>>
command followed by a variable name (with the $
prefix) and assign it an rather than a static value. The expression can use , or even reference other variables.
For example: <<declare $is_powerful = $strength > 50 && $magic_ability >= 20>>
.
This smart variable will automatically update whenever the values of $strength
or $magic_ability
change, making your dialogue able to dynamically respond to the player's stats without additional code.
Smart variables shine when you have conditions that you check frequently or that combine multiple factors. They're especially useful for:
Tracking complex character states
Monitoring game world conditions
Representing player achievements or quest status
Creating dynamic dialogue that responds to multiple game systems
By using smart variables, you make your dialogue scripts more intuitive and maintainable while keeping your game logic organised.
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 is configured to use Addressable Assets.
Learn how to install the Dialogue Wheel for Yarn Spinner Package.
You can purchase Dialogue Wheel for Yarn Spinner from the or the .
Once you've purchased it, download the package from the store. It will be in the form of a .unitypackage
file. To install the package, open the Unity project that you want to add it to, and open the the Assets menu -> Import Package -> Custom Package...
You'll then be able to select the .unitypackage
file for Dialogue Wheel for Yarn Spinner on your file system, and click Open. This will present the Import Unity Package window:
Click the Import button, and Dialogue Wheel for Yarn Spinner will be imported into your project.
The Dialogue Wheel add-on comes has two packages inside the unitypackage, one for the wheels themselves, and one for the Yarn Spinner Samples. The samples contain necessary resources for the two wheel examples but if you already have the you do not need to add them again.
Next, check out the guides on , or .
The guide and documentation for the paid Yarn Spinner for Unity add-on, Speech Bubbles for Yarn Spinner.
This Unity package provides two prefabs: Formal Bubble and Casual Bubble. The package requires Yarn Spinner for Unity.
This add-on is not part of the open source Yarn Spinner package, and can be or via the .
Speech Bubbles for Yarn Spinner provides a Casual Bubble and a Formal Bubble prefab. Both are customisable, powerful, and extremely flexible:
customise fonts, colours, and styles of bubbles
optional character nameplate
flexible anchor point for bubble stem
lock bubbles to camera, or not, your choice
works for 2D or 3D games
This guide provides documentation on using the bubbles and both prefabs.
The oldest supported version of Unity for the Speech Bubbles is 2022.3.
The design of the Speech Bubbles are slightly different to how most custom dialogue presenters, or even the default presenters, work. At first glance it might not be obvious as to what each piece does, that is what this section is for.
The Bubble Dialogue View is the subclass and is what talks to the for the relevant events in the story. The Bubble Dialogue View manages all the various bubbles that will need to be shown on screen, in particular it controls when to create and destroy them and gives them the content they need to show. The Bubble Dialogue View is a essentially a middle-man between the actual game objects shown on screen and Yarn Spinner.
The Bubble and Bubble Content are the two classes that control where and what is shown on the screen. The Bubble manages positioning information, determining where to best place the canvas rectangle. What a Bubble appears like, so it's shape, text, colours, and so on are determined by it's Bubble Content.
The Bubble Content represents a single piece of content. So each line that needs to be shown is a piece of content, the collection of options is another piece of content. The Bubble then shows this piece of content, and when a new piece of content comes in removes the old and shows the new. The Bubble gets given this Bubble Content to show by it's Bubble Dialogue View. The specific information contained within a Bubble Content changes depending on what needs to be shown to the player, the most common information is the line.
The reason for this split is because the way a bubble determines where to place itself is not significantly impacted by it's content. Only the size of the text inside the content changes this, the rest is the same no matter what. This means a great deal of code could be extracted and kept generic from the rest of the presentation. This means making visually unique bubbles is now easier by subclassing the Bubble Content, all the positioning code can be reused for every visually different bubble.
The last class that operates in a different manner from the Yarn Spinner norm is the Bubble Input. When the user wants to hurry up or skip lines in Yarn Spinner this is handled by the , Speech Bubbles however are a bit different in this respect. Because they can only show a single option at a time they need to have a way to also support changing between which option is currently being shown by the bubble, this is what the Bubble Input does.
The Line Advancer is still a part of the equation, as hurrying up lines is still something people will want to do with Speech Bubbles, however it is no longer directly configured by you in the editor. Instead Bubble Input has a reference to the Line Advancer and it will set the appropriate values. This is just to avoid having to change two objects at once, however if you do want to have this behaviour the Bubble Input has a toggle called Independent Advancer Configuration
where if set means the Bubble Input won't attempt to change the configuration of the Line Advancer.
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.
Debug Text View
A RichTextLabel node 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.
<<declare $player_is_friends_with_sam = $sam_relationship_score > 50>>
<<if $player_is_friends_with_sam>>
Sam: Hey buddy! Good to see you again.
<<else>>
Sam: Oh, it's you. What do you want?
<<endif>>
<<declare $has_enough_materials = $wood >= 5 && $nails >= 10>>
<<declare $has_required_skill = $carpentry_level >= 3>>
<<declare $can_build_chair = $has_enough_materials && $has_required_skill>>
<<if $can_build_chair>>
Craftsman: You've got everything you need to build that chair now.
<<else>>
<<if !$has_enough_materials>>
Craftsman: You'll need more materials first.
<<else>>
Craftsman: Your carpentry skills aren't quite there yet.
<<endif>>
<<endif>>
<<declare $is_evening = $game_hour >= 18 && $game_hour < 22>>
<<declare $town_shops_open = !$is_holiday && $game_hour >= 9 && $game_hour < 18>>
<<if $is_evening && !$town_shops_open>>
Innkeeper: Most shops are closed now, but you're welcome to stay here for the night.
<<endif>>
Hi, I'm the narrator for the documentation!
title: Adventure
---
Narrator: We're going to go on an adventure!
===
title: Cave
---
Narrator: Let's look inside the spooky cave...
===
title: Start
---
Narrator: Hi, I'm the narrator for the documentation!
===
title: Adventure
---
Narrator: We're going to go on an adventure!
-> OK! Let's go!
<<jump Cave>>
-> I don't want to go on an adventure...
Narrator: Oh, OK then.
===
title: Guard
---
Guard: Have I told you my backstory?
-> Yes.
Guard: Oh. Well, then.
-> No?
<<detour Guard_Backstory>>
Guard: Anyway, you can't come in.
===
title: Guard_Backstory
---
Guard: It all started when I was a mere recruit.
// (five minutes of exposition omitted)
===
title: Guard
---
Guard: Have I told you my backstory?
-> Yes.
Guard: Oh. Well, then.
-> No?
<<detour Guard_Backstory>>
Guard: Anyway, you can't come in.
===
title: Guard_Backstory
---
Guard: Do you want the detailed version or the short version?
-> Detailed.
<<detour Guard_Detailed_Backstory>>
Guard: I hope you enjoyed learning all that.. Anyway...
-> Short.
Guard: Right, well, I was a recruit, then I wasn't.
===
title: Guard_Detailed_Backstory
---
Guard: It all started when I was a mere recruit.
// (five minutes of exposition omitted)
// (other stuff happens)
Guard: Want to hear more?
-> Yes.
-> No.
<<return>>
Guard: (speaks more garbage)
===
<<detour>>
command being visualised in the Graph View.title: Guard
---
Guard: You there, traveller!
Player: Who, me?
Guard: Yes! Stay off the roads after dark!
===
title: Guard
---
Guard: I hear the king has a new advisor.
===
title: Guard
---
Guard: No weapons allowed in the city!
===
Text Language Code
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.
Learn about using Commands in Yarn Spinner.
In Yarn Spinner, you can send instructions to your game through commands. Commands look like this:
<<wait 2>>
<<setsprite ShipName happy>>
<<fade_out 1.5>>
Commands are sent to your game's Dialogue Runner, just like lines and options are. Commands are not shown to the player directly; instead, they're used for things like stage directions.
Yarn Spinner comes with some built-in commands; however, to get the most usefulness out of them, you'll want to define your own custom commands that make your game do what you need to.
There are two built-in commands in Yarn Spinner: wait
, and stop
.
wait
The wait
command pauses the dialogue for a specified number of seconds, and then resumes. You can use integers (whole numbers), or decimals.
// Wait for 2 seconds
<<wait 2>>
// Wait for half a second
<<wait 0.5>>
stop
The stop
command immediately ends the dialogue, as though the game had reached the end of a node. Use this if you need to leave a conversation in the middle of an if
statement, or a shortcut option.
// Leave the dialogue now
<<stop>>
// Leave the dialogue if we don't have enough money
<<if $money < 50>>
Shopkeeper: You can't afford my pies!
<<stop>>
<<endif>>
You can create your own commands, so that your scripts can send directions to your game. For more information on how to create them in Unity games, see Creating Commands and Functions, in the Yarn Spinner for Unity section of the documentation, and equivalents for other engines.
We recommend that you only move into the Yarn Spinner for Unity documentation after learning the fundamentals of Yarn Spinner Scripting.
<<once>>
// The guard will introduce herself to the player only once.
Guard: Hail, traveller! Well met.
Guard: I am Alys, the guard!
<<endonce>>
<<once>>
Guard: Hail, traveller! Well met.
<<else>>
Guard: Welcome back.
<<endonce>>
<<once if $player_is_adventurer>>
// The guard knows the player is an adventurer, so say this line,
// but only ever once!
Guard: I used to be an adventurer like you, but then I took an arrow in the knee.
<<else>>
// Either the player is not an adventurer, or we already saw the
// 'arrow in the knee' line.
Guard: Greetings.
<<endonce>>
Guard: Who are you? <<once>> // Show this line only one time
Guard: Go on, get lost!
-> What's going on? <<once>>
Guard: The kingdom is under seige!
-> Where can I park my horse? <<once if $has_horse>>
Guard: Over by the tavern.
-> Lovely day today!
Guard: Uh huh.
-> I should go.
Guard: Please do.
<<once>>
// Show long, character-establishing lines the first time
Guard: There's nothing new to report!
Guard: I've been at this post for hours, and I'm so bored.
Guard: I can't wait for the end of my watch.
<<else>>
// Show a more condensed version all other times
Guard: Nothing to report!
<<endonce>>
title: Start
---
Captain: Navigator, fire the glitter torpedoes! That'll confuse the enemy ships!
=> Navigator: *sighs deeply* Sir, we don't have 'glitter torpedoes.' Those were in your dream last night.
=> Navigator: *eyes roll skyward* Captain, weaponizing craft supplies is not part of standard space combat protocol.
=> Navigator: *Slumps shoulders in defeat* I'll... make a note in the log that you suggested tactical glitter, sir.
===
=> Guard: Halt!
=> Guard: No entry!
=> Guard: Stop!
=> Guard: Greetings, citizen.
=> Guard: Hello, traveller.
Guard: Stay vigilant. // runs after 'Hello, traveller.'
=> Guard: Hail, adventurer! <<if $player_is_adventurer>>
=> Guard: I used to be an adventurer like you, but then I took an arrow in the knee. <<once if $player_is_adventurer>>
<Import Project="addons\YarnSpinner-Godot\YarnSpinner-Godot.props" />
addons
directory in a local copy of Yarn Spinner for Godot.addons
directory in..csproj
for your project.Learn about our premium add-ons for Yarn Spinner for Unity, giving you ready-made Dialogue Wheels and Speech Bubbles for Yarn Spinner.
Yarn Spinner is always free and open source. To help offset the costs of designing, building, and supporting Yarn Spinner, we provide a number of add-ons as paid extras to make implementing Yarn Spinner in your games easier for you.
At the moment, these include:
You can purchase both of these on the Yarn Spinner Itch.io Store or via the Unity Asset Store.
This section of the Yarn Spinner documentation provides guides for the available add-ons.
Learn how to implement a custom saliency strategy for your narratives.
Yarn Spinner 3 introduced a new system for selecting which content should be presented next, called Saliency Strategies. Whenever it's time to select the next piece of salient content from node groups or line groups, Yarn Spinner consults its saliency strategy to determine which piece should be chosen.
This guide explores an implementation provided in our Custom Saliency Strategies Sample.
While we provide several built-in saliency strategies that cover common scenarios, we can't anticipate every possible need. That's where custom strategies come in, which is the focus of this sample. We'll demonstrate how to create a new custom saliency strategy to meet specific requirements.
Creating new saliency strategies through the IContentSaliencyStrategy
interface
Reading line metadata
Reading node headers
The IContentSaliencyStrategy
interface requires implementing just two methods: QueryBestContent
and ContentWasSelected
.
QueryBestContent
: Called when Yarn Spinner asks, "If I were to run this block of content, what would be selected?"
ContentWasSelected
: Called when Yarn Spinner informs the strategy, "This specific piece has been chosen"
When Yarn Spinner needs to select a piece of salient content, it first asks its strategy what content would be selected (QueryBestContent
) and then selects it and informs the strategy of this selection (ContentWasSelected
).
You might wonder why this functionality is split into two methods, especially when the first call is often immediately followed by the second. For many saliency strategies, it won't matter - querying and selecting can effectively be the same operation, with the only required state being the list of content to analyze.
However, other strategies require maintaining state that would be affected if selection and querying were combined. For example, the built-in "Best Least Recently Viewed" strategy tracks which content it has shown previously to avoid showing the same piece of content twice in a row. Each time it selects content, it marks it as seen, which deprioritizes that specific content for future selections.
This design creates a clear separation: QueryBestContent
is non-mutating, while ContentWasSelected
can modify state. The alternative would be to prevent querying available content altogether, but this would be too limiting - it would prevent you from checking whether content is available, which might be useful for showing indicators that an NPC has something to say.
The sample includes a custom saliency strategy in WeightedSaliencySelector.cs
. This strategy assigns a custom weight to each storylet, as specified by the writer, and uses these weights to determine the probability of each storylet being shown.
Each piece of salient content is given a range proportional to its weight, and then one is chosen randomly from the combined range of all weights. In practical terms, given the following line group:
=> I am line A #weight:2
=> I am line B #weight:1
"I am line A" will be shown approximately two-thirds of the time, while "I am line B" will appear approximately one-third of the time.
When asked for the best content, the strategy first filters out any content with failing conditions. After this filtering, we're left with content where all conditions (line conditions or when headers) have evaluated to true. The next step is to determine the weighting for each piece of content.
Our weighted saliency strategy supports both line groups and node groups, which require slightly different approaches:
For node groups, we look for the weight
header in the node headers:
weightString = runner.Dialogue.GetHeaderValue(element.ContentID, WeightKey)
For line groups, we look for the weight
tag in the metadata:
var lineKey = WeightKey + ':';
foreach (var metadata in runner.YarnProject.lineMetadata.GetMetadata(element.ContentID))
{
if (metadata.StartsWith(lineKey))
{
weightString = metadata.Substring(lineKey.Length).Trim();
break;
}
}
Once we have the string values for weights, we convert them to integers. If the conversion fails, or if there isn't a weight value in the headers or metadata, we assign a default weight of one.
With the weights established, we build a list of ranges representing those weights. Finally, we generate a random number within the combined size of all ranges and use that to select which content to present.
Learn about Options Presenter, a Dialogue Presenter that shows options in a list.
An Options Presenter is a Dialogue Presenter 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.
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
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.
Quickly get started with a simple scene.
Want to use Yarn Spinner in a new scene in Unity right away? Follow these steps. If this is your first time using Yarn Spinner, or you're a bit unsure how it all works, we suggest reading the rest of the documentation first!
Install Unity 2022.3 or newer.
Create a new empty Unity project, by following the instructions in the Unity manual.
Install Yarn Spinner into the project, by following the instructions in Installation for Unity.
Add a Dialogue System to the scene:, by opening the GameObject menu and choosing Yarn Spinner -> Dialogue System.
Create a new Yarn Spinner Script, by opening the Assets menu and choosing Create -> Yarn Spinner -> Yarn Script. Name the new file HelloYarn
.
Open the new Yarn script by double-clicking it.
Select all of the text in the file, and delete it.
Copy the text below, and paste it into the file.
title: Start
---
Wow!
My first ever Yarn script in Unity!
-> Gosh!
-> Incredible!
-> I'm amazed!
Anyway, time to get writing!
===
Save the file and return to Unity.
Create a new Yarn Project that uses this script, by selecting the HelloYarn
file, and clicking the Create New Yarn Project button in the Inspector. This will create a new Yarn Project called Project
. Projects are collections of Yarn scripts that get compiled together, and can be used with a Dialogue Runner.
Make the Dialogue Runner use the Project by dragging the Project you just made into the Dialogue Runner's Yarn Project field.
Play the game by clicking the Play button at the top of the window. Your dialogue will appear!
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).
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.
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.
Learn how to trigger events using markup, inline with your dialogue.
Yarn Spinner version 3 introduces a powerful new way to trigger events during dialogue presentation. While previous versions required custom implementations for in-line actions, v3 provides the IActionMarkupHandler
interface. This system notifies handlers about key line presentation events, enabling you to respond at specific moments.
This approach offers tremendous flexibility for creating various inline events. Our sample demonstrates two practical applications: character movement and facial expression changes during dialogue presentation.
This guide explores the contents of the Inline Events Sample.
The new IActionMarkupHandler
interface
The new ActionMarkupHandler
class
Using these with the Line Presenter to trigger events
Two implementations: character movement and animation changes
The IActionMarkupHandler
interface provides a structured approach to handling in-line actions during dialogue presentation. At its core, the system works by having the Line Presenter display dialogue one character at a time, notifying handlers throughout the process. This creates multiple opportunities to trigger events at precise moments.
The interface defines methods corresponding to different stages of the line presentation lifecycle:
Initial Preparation (OnPrepareForLine
): Called immediately after the line is received but before any UI elements appear.
Display Start (OnLineDisplayBegin
): Called just before characters begin appearing, but after UI elements are visible.
Character Display (OnCharacterWillAppear
): Called each time a character in the line is about to be displayed.
Display Completion (OnLineDisplayComplete
): Called after all characters in the line have been shown.
Dismissal (OnLineWillDismiss
): Called just before the line will be dismissed.
Notably, OnCharacterWillAppear
is an asynchronous method. The line presentation system will await each Action Markup Handler's completion before continuing, allowing events of any duration to occur at any point in the dialogue.
Yarn Spinner also provides ActionMarkupHandler
, a MonoBehaviour implementation of this interface, for situations where you need component-based functionality. The Line Presenter includes a serialized field for directly connecting ActionMarkupHandler
subclasses, as demonstrated in our sample.
Our sample showcases two types of action markup: moving the player character and changing NPC facial expressions. Both are implemented as ActionMarkupHandler
subclasses and connected to the Line Presenter through its Event Processors
field.
The movement implementation resides in the MoveEvent.cs
file as an ActionMarkupHandler
subclass. Most of the functionality is contained in the OnPrepareForLine
method.
This method scans all markup in the current line, searching specifically for the move
markup tag. When found, it extracts the name
property from the marker and uses it to locate a corresponding in-scene marker with the same name. The target location and the marker position within the text are stored in a dictionary for later use.
During line presentation, the OnCharacterWillAppear
method checks if the current character position corresponds to a stored location. If so, it moves the player character to that position before continuing.
This allows for dialogue lines such as: Player: So what, just part way through I stop talking... [move name="far" /] ...and move on over?
When presentation reaches position 49, it will pause, the player character will move to the GameObject named "far", and then the dialogue will resume.
The facial expression system is implemented in EmotionEvent.cs
as another ActionMarkupHandler
subclass. The primary logic is also in the OnPrepareForLine
method.
This implementation first uses the character name from the dialogue line to find a corresponding GameObject. It then retrieves the SimpleCharacter
component (a sample-specific type, not part of Yarn Spinner core) from this GameObject. After these preparations, it processes all markup in the line, looking specifically for emotion
tags. The emotion
property value is extracted and stored for later use.
During presentation, OnCharacterWillAppear
checks if there's a cached emotion corresponding to the current position. If found, it uses this value to change the character's facial expression animation. Unlike the movement example, this change happens instantly without pausing dialogue progression.
This enables dialogue lines like: Alice: Yes... which I would have shown [emotion="angry" /] had you not interrupted me.
When presentation reaches position 32, the facial expression of the "Alice" GameObject will instantly change to whatever animation corresponds to "angry" while the dialogue continues uninterrupted.
This page shows you how to install Yarn Spinner for Unity, the Unity integration for running Yarn and Yarn Spinner scripts in your Unity-based games.
You can download and install Yarn Spinner for Unity in a variety different ways.
Yarn Spinner for Unity 3 is compatible with Unity version 2022.3 and newer. If you need to use an older version of Unity, use Yarn Spinner 2.x, which supports Unity 2021.3.
Yarn Spinner is developed in the open, and the best ways to download and install Yarn Spinner for Unity, and the ones that support us to continue developing it the most are via the and the .
First, , and add it to your cart, and complete your purchase while logged into the same Unity account you use to activate Unity.
Once you've purchased Yarn Spinner for Unity, you'll find the Add to Cart button replaced by an Open in Unity button. Click this button to launch Unity, and the Package Manager will locate your purchased package:
Once the Package Manager has located the package, you can use the Download button to fetch it:
Once Yarn Spinner for Unity has downloaded, you can use the Import button to start the process of adding it to your project:
This will trigger the Import Unity Package workflow, where you can use the Import button to add the Yarn Spinner for Unity package to your project:
And with that, you're ready to use Yarn Spinner! You might also want to download and import the Yarn Spinner for Unity Samples as a .unitypackage
, from here.
First, visit the , and click the Buy Now button, and complete the checkout process.
Once you've purchased Yarn Spinner, you'll find a Download button at the top of the page:
The download button will take you the following page, where you can download the Yarn Spinner for Unity .unitypackage
:
Once you've downloaded the .unitypackage
, with the Unity project you want to use it in open and ready to go, double click it. Unity will then allow you to import the package into your project:
As an alternative to downloading Yarn Spinner from OpenUPM, you can install Yarn Spinner by downloading the package directly from GitHub, where the project's source code is stored.
To install Yarn Spinner from GitHub, follow these instructions.
Make sure your system .
In Unity, open the Window menu, and choose Package Manager.
Click the +
button, and choose "Add package from git URL".
In the text field that appears, enter the following URL:
https://github.com/YarnSpinnerTool/YarnSpinner-Unity.git#current
Be sure to type the URL exactly as it appears in this document.
The project will download and install. This might take a moment.
You can also install the Yarn Spinner package into your project using the Package Manager window in Unity. Specifically, Yarn Spinner is available via the .
In order to follow the instructions in this section, your project needs to be using Unity 2020.1 or higher. If your project is using an earlier version of Unity, we recommend installing Yarn Spinner from Git.
Setting Up the OpenUPM Registry in Your Project
Before you can install Yarn Spinner from OpenUPM, you first need to configure your project so that it knows where to get the package from.
In Unity, open the Edit menu, and choose Project Settings.
In the list of sections at the left hand side of the window, select Package Manager.
This window is where you tell Unity about where to find packages that come from registries besides Unity's built-in one.
In the Name field, type OpenUPM
.
In the URL field, type https://package.openupm.com
.
In the Scopes field, type dev.yarnspinner
.
Click Save.
When you're done, the settings window should look like this:
You can now install Yarn Spinner itself.
Installing the Yarn Spinner package
Open the Window menu, and choose Package Manager.
In the toolbar, click Packages: In Project, and choose My Registries.
Yarn Spinner will appear in the list. Select it, and click Install.
Yarn Spinner will download and install into your project.
You can verify that everything is imported succesfully by looking for Yarn Spinner under Packages, in the Project pane.
DREDGE
A Short Hike
Lil' Guardsman
Demonschool
Little Kitty Big City
Rift of the Necrodancer
Venba
Unbeatable
Luma Island
NORCO
Night in the Woods
ENA: Dream BBQ
DREDGE
A Short Hike
Lil' Guardsman
Demonschool
Little Kitty Big City
Rift of the Necrodancer
Venba
Unbeatable
Luma Island
NORCO
Night in the Woods
ENA: Dream BBQ
Learn about localizing your Yarn Spinner scripts with Unity.
Localization is the process of translating and adapting content to a specific language, region or culture.
This section of the documentation is designed to give you an understanding of the architecture behind localising and adding voice over to Yarn Spinner for Unity projects, if you just want to add localisations or voice overs to your game, check out our Voice Over and Localisation Sample and Guide.
Yarn Spinner Scripts are written in human-readable language. This is generally a single language, and (most of the time) will be written in the language that your development team primarily speaks. The language that a Yarn project is written in is called the base language.
If you want your dialogue to be understood by people who don't speak this language, you will need to translate it.
Yarn Spinner is designed to make it easy to extract the user-facing text of your dialogue into a strings file, which can then be translated into a different language, and then loaded at run-time. You can translate your project into as many languages as you'd like, and Yarn Spinner will handle it for you automatically.
Yarn Spinner is also designed around the idea that a line of dialogue may have assets associated with it. Most commonly, this means an audio file that contains an actor performing the line, so that it can be used in your game as a voice-over. These assets are also localisable.
I just want to add voiceover in a single language. Why do I need to localise, too?
The philosophy of Yarn Spinner's approach to localisation is: if you want your dialogue to be text-only, and in a single language, you don't need to do anything at all. If you want to do anything else, you will need to set up a localisation and manage it using Yarn Spinner's processes.
We've found that most users who want to start using Yarn Spinner want to quickly get dialogue on the screen, and don't want to do lots of work to get the basics going. That's why we make the simple use-case (text only, a single language) as easy to use as we can.
However, if you're building a game that's voice acted, it makes your life significantly easier if you build your systems with localisation in mind from the start. Additionally, if you have the resources to add voice-over to your project, you should also have the resources to translate your game to other languages (even if you only have voice-overs in a single language.)
To that end, we designed it so that voiceover is intimately tied to localisation, so that you have to at least start thinking about localisation at the start of the process.
Yarn Spinner makes it easy to add multiple languages to your game. The gist is:
Select your Yarn Project in the Assets panel
Click "Export Strings as CSV" in the Inspector
Translate the exported CSV file
Import the translations back into your project
You can use either:
Built-in Localisation System: Manage translations directly through Yarn Spinner
Unity Localisation System: Integrate with Unity's Localisation package
We provide both Yarn Spinner's Built-in Localisation System and Unity's because our built-in system is easier to setup. The Unity Localisation System has more features, but requires a lot of setup.
If you're working on a large game with quite a few team members, and are using external services like Google Sheets, and have UI-text that's not powered by Yarn Spinner, then you might want to use the Unity Localisation System.
Localisation: A set of information that describes where to find text and assets for a given language.
Base Language: The language that your Yarn script files are written in.
Strings Files: A text document that contains translated versions of Yarn lines.
Line ID: A unique code that identifies a line of dialogue or an option in the original source text.
Localised Line: The text of a line of dialogue, in a particular locale.
Localised Line Asset: An asset (for example, an audio clip) that's associated for a particular line, in a particular locale. For example, an audio clip containing the voiceover for the line "Hello there", in German.
Line Provider: A component that receives line IDs from the Dialogue Runner, and fetches the localised line and localised line assets (if present) for the player's preferred locale.
To localise your Yarn scripts, you specify the 'base language' that your scripts are written in. You then add unique line ID tags to each line that identify each line. Finally, the localisation system reads your tagged lines and fills the string table for your base language. You can then add additional translations for your lines to the string tables for other languages.
Every Yarn script is associated with a base language. By default, Yarn Spinner sets the base language to that of your current locale. For example, if your computer is set to use Australian English, then Yarn Spinner will use that as the base language.
The base language of a Yarn Script is controlled by the Yarn Project that it's a part of. You can change the language of your base localisation by changing the 'Base Language' setting on a Yarn Project.
In order to match different versions of a line, you need to add a line id to each line of dialogue. A line ID is a tag that appears at the end of a line that uniquely identifies a line of dialogue in your game.
Here's an example of a line of dialogue with a line tag:
Gunther: I wanted orange! They gave me lemon-lime. #line:1a64a5
In this example, the line of dialogue has a line ID of 1a64a5
.
Yarn Spinner can automatically add line IDs to your dialogue for you. To do this, select your Yarn Project, and click 'Add Line Tags to Scripts'. Yarn Spinner will re-write all of the script files, adding a line ID to any line that doesn't already have one.
Once you've added line IDs to your Yarn scripts, they're ready to be used in your game's localisation system. You can choose between using the Unity-provided Localization package, in which case Yarn Spinner can prepare your string tables and fetch content from those tables at run-time, or our Built Yarn Spinner localisation system.
Learn about updating a Unity project to use Yarn Spinner 3 when it's already using Yarn Spinner 2.
Yarn Spinner 3 introduces primarily additive changes to its language. Significant modifications have been made to Yarn Spinner for Unity to support these new features, resulting in some breaking changes.
You only need to refer to this page if you've got a project that's already being developed in Yarn Spinner for Unity 2, and you want to transition to Yarn Spinner for Unity 3. If you're starting a new Unity project and plan to work with Yarn Spinner, you should be using Yarn Spinner 3.
This guide assists in migrating Yarn Spinner for Unity 2 projects to Yarn Spinner 3. While we provide compatibility layers to automatically manage many of the changes, some manual adjustments will be necessary. Here we'll look at the steps to ensure existing Yarn Spinner 2 games function with Yarn Spinner for Unity 3, and outline how to transition to the newer systems.
The functionality of [YarnCommand]
and [YarnFunction]
attributes is unchanged. However, they have been moved to a new namespace. To use these attributes, add using Yarn.Unity.Attributes;
to the relevant C# files.
The system for managing dialogue interruption (hurrying, advancing, cancelling) has been redesigned.
When Presenters receive dialogue events, such as requests to show lines or options, they are provided with a cancellation token. The state of this token can be checked to determine if an advance has been requested. This design centralises presentation, cancellation, and cleanup logic, which was previously distributed across multiple methods.
The Line Advancer
sample demonstrates a practical implementation of line advancement using this cancellation token approach.
Yarn Spinner 3 replaces the callback-based DialogueViewBase
with an asynchronous DialoguePresenter
. This is the new standard for handling dialogue events in your code.
While Presenters and Views have functional similarities, Presenters utilize a different API and are not directly compatible with older Views. It is highly recommended to refactor existing Views to the new Presenter model.
For an existing project that is already using the older Dialogue View system, you can continue using DialogueViewBase
by importing it from its new Yarn.Unity.Legacy
namespace. The DialogueViewBase
class has been adapted to act as a compatibility layer for the new Presenters.
If you choose to do this, you will not gain any of the features of the new Dialogue Presenter system. If wish to instead update your project to use the new Dialogue Presenter system, follow the process below.
Previously, Dialogue Views used callbacks to coordinate with the dialogue runner. Dialogue Presenters employ asynchronous functions, signalling completion by returning from the method. This simplifies logic for both the dialogue runner and custom Dialogue Presenter implementations.
While the APIs of the old Dialogue View and the new Dialogue Presenter may appear similar, their operational mechanics differ significantly.
The conversion process will vary depending on the specific functionality of your View, but the general steps are:
Change the Subclass.
Convert to the New Methods.
Delete Unnecessary Methods.
Remove the Interrupt Action.
Replace DialogueViewBase
with DialoguePresenterBase
as the base class. Since DialogueViewBase
is a subclass of DialoguePresenterBase
(for compatibility), its connection to the Dialogue Runner in Unity should generally remain intact. However, it is crucial to verify this connection after completing the conversion.
While not a direct one-to-one mapping, several Presenter methods correspond to View methods in terms of the dialogue events they handle:
Old DialogueViewBase Method
New DialoguePresenterBase Method
DialogueStarted
OnDialogueStartedAsync
RunLine
RunLineAsync
RunOptions
RunOptionsAsync
DialogueComplete
OnDialogueCompleteAsync
The old DialogueView
used callbacks and specific methods for user interruption and line completion. This functionality is now handled by cancellation tokens within the Presenter.
The following methods are no longer needed and can be removed:
InterruptLine
DismissLine
UserRequestedViewAdvancement
The requestInterrupt
action is obsolete and can be deleted.
The remaining task is to replicate the original behaviour of your Dialogue View within the new Dialogue Presenter structure. Several Samples are available, showcasing various custom Dialogue Presenters, which can provide guidance and practical examples for this process.
Any new Yarn Projects you create will be already configured correctly but if you have any existing v2 Yarn Projects these will need to be updated.
This is a single change that needs to be done on each Yarn Project.
If you open your Yarn Projects inside any text editor there will be a line inside that says "projectFileVersion": 2,
, replace this with "projectFileVersion": 3,
and save the file.
When you go back to Unity this change will be detected and the project will be automatically reimported at which stage you can now use v3 features in that project.
Learn how to use the Yarn Spinner brand in your game, and how to acknowledge your use of Yarn Spinner.
This document applies to any and all products using Yarn Spinner of any version, and might change from time to time. Please check back occasionally! If you have any questions, or want an official written answer, or written permission, or something else—even if it's weird, we love weird—please email legal AT yarnspinner.dev
Thank you for using Yarn Spinner! 💚 This page contains information on how to use the Yarn Spinner name and logo in projects that make use of Yarn Spinner, or in media coverage of Yarn Spinner.
The core of Yarn Spinner is provided to you under the terms of the MIT License. This means that if you use Yarn Spinner in a game, software, or any other work, you are required to include a copy of the MIT license in that work.
You can find a copy of Yarn Spinner's MIT license here:
You are required to include a copy of this with your game. There's no firm rules as to how you do this. We have some suggestions, though:
on a screen in your game, along with other software licenses
in a file that lists all licenses related to your game
in an accompanying PDF, or similar, electronic manual or documentation
Basically, as long as a copy of Yarn Spinner's version of the MIT license is distributed with your game, you're good to go.
In addition to including a copy of the Yarn Spinner's MIT license in your game, please include Yarn Spinner in your game's credits, such as the initial splash screen, or in your end-game credits, or somewhere else of your choosing.
To credit Yarn Spinner, we give you permission to use our logo, which is available for download below.
You may also choose to credit us in text. We suggest the following:
Dialogue powered by Yarn Spinner — https://yarnspinner.dev
You can reword this however you'd like, but we'd prefer if you kept the name 'Yarn Spinner', and the link to the website.
Yarn Spinner® is a trademark of Secret Lab Pty. Ltd., the original creators, and is licensed to Yarn Spinner Pty. Ltd, which is a spinoff company to look after the project.
For the purposes of crediting Yarn Spinner, acknowledging that you're using Yarn Spinner, or discussing, teaching, or tutorialising Yarn Spinner and other simlar uses, we give you permission to use the Yarn Spinner name and logo on packaging, promotional/advertising materials, splash screens, in publications, seminars, conference talks, and web sites, and anything else in relation to your Yarn Spinner-powered software.
Please adhere to the following if you build a non-game product or tool that supports Yarn Spinner:
The "Yarn Spinner" trademark or the word "Yarn" cannot be part of a product name (e.g. "Big Talk for Yarn Spinner") unless we have given you written permission (you would have had to email us for that).
The Yarn Spinner trademark can be used in a referential manner or alongside a referential phrase (e.g. "SimOptometrist is built with Yarn Spinner", "Aunty Edna's Grand Adventure uses Yarn Spinner", "Big Talk is compatible with Yarn Spinner", "Bobby Yarner teaches Yarn Spinner", "Learn Yarn Spinner with Dr Eyeballs");
The game, or project that you're including the Yarn Spinner trademark must, in fact, work with, use, or relate to Yarn Spinner in some way;
The use of the Yarn Spinner trademark cannot imply endorsement, sponsorship, or an inaccurate representation of the relationship between you, your game/project, and the Yarn Spinner project, team, Yarn Spinner Pty. Ltd., or Secret Lab Pty Ltd;
The use of the Yarn Spinner trademark should not show Yarn Spinner, the Yarn Spinner team, Yarn Spinner Pty. Ltd., Secret Lab Pty Ltd, or any related group or entity in an false or derogatory manner, or in association with any hate speech, or criminal or illegal activities.
If you make a product that suports Yarn Spinner, please direct your users to install Yarn Spinner via the Unity Asset Store or Itch.io in the first instance. Do not automatically install Yarn Spinner via OpenUPM or as a Unity Package from GitHub through your product or tool unless we have given you written permission to do so.
If you have any questons on this, ask the Yarn Spinner team in the Discord, tweet at us, or email us at legal AT yarnspinner.dev — we're friendly, and really just want to help.
When you use the Yarn Spinner trademark, logo, or brand to credit Yarn Spinner in your game (or other work), you should try your best to conform your use to our brand guidelines.
You may not edit, modify, distort, recolour, or change the Yarn Spinner logo unless you show us what you've done (and receive our approval), or ask our permission. We're happy for you to totally diverge from our colours and branding, if we talk about it with you, so please email us at legal AT yarnspinner.dev to chat — we're friendly, we promise!
You can download a copy of the Yarn Spinner logo for inclusion in your credits below. We explicitly give you permission to use these logos to credit Yarn Spinner in your games, in accordance with the guidelines on this page:
Learn about saliency and saliency strategies, which let you control how line groups and node groups select which content to run.
In Yarn Spinner, saliency lets you control how line groups and node groups select which content to run.
We recommend you read the Storylets and Saliency Primer before reading this page.
When a line group or node group needs to run content, it needs to make a decision about which item in the group to choose. The method for making this decision is called a saliency strategy.
Saliency strategies are provided with the following information about each item:
How many of its conditions passed (that is, the when:
clauses on a node group, or the single condition on a line group)
How many of its conditions failed
The total complexity of all of its conditions:
The always
condition has a complexity of zero.
Otherwise, the following values are added together:
If the condition is a once
condition, add 1.
If the condition has an expression, add the the total number of boolean operators (and, or, not, xor) present, plus 1.
A unique key that identifies the content.
You can either use one of Yarn Spinner's built-in saliency strategies, or create your own custom saliency strategy.
First: The first item in the group that has not failed any of its conditions is selected.
If the items of a node group are all in the same file, the ordering of the group is the order in which they appear in the file. If a node group’s nodes are split up across more than one file, the ordering of the nodes is not defined. the node that is considered ‘first’ is not defined.
Best: The items that have not failed any conditions are sorted by their total complexity score, and the first item that has the highest score is selected.
Best Least Recently Viewed: The items that have not failed any conditions are sorted by score, and then by how many times they have been selected by this strategy. If there is more than one best item remaining, the first of these is selected.
Random Best Least Recently Viewed: The items that have not failed any conditions are sorted by score, and then by how many times they have been selected by this strategy. If there is more than one best item remaining, a random one of these is selected.
If you don't set the saliency strategy anywhere yourself, the default will be Random Best Least Recently Viewed.
A saliency strategy is given a collection of possible options, and returns either one of them that should be run, or null
to indicate that none of them should run.
You can only change the saliency strategy in Try Yarn Spinner and when using Yarn Spinner in a game engine, like Unity.
When using Try Yarn Spinner, you can set the saliency strategy by calling one of the following built-in commands:
<<set_saliency first>>
<<set_saliency random>>
<<set_saliency best>>
<<set_saliency best_least_recent>>
<<set_saliency random_best_least_recent>>
When using Visual Studio Code to Preview your dialouge, you can change the active saliency strategy in the drop-down menu at the top of the Preview view:
Your C# code can create custom saliency strategies. To do so, create a type that implements the IContentSaliencyStrategy
interface, and assign it to your Dialogue
object’s ContentSaliencyStrategy
property.
IContentSaliencyStrategy
has two required methods.
ContentSaliencyOption? QueryBestContent(IEnumerable<ContentSaliencyOption> content)
determines which of a collection of options, if any, should run. It should return the best content from the available options, or null
if none of them should run. This is the main method in which you write the specific logic for your custom strategy.
Calling this method does not indicate that the content has been selected; rather, it is a query to determine what should be selected. Your implementation of this method should not change any state, and should be read-only. The content returned by this method is not guaranteed to actually be run.
void ContentWasSelected(ContentSaliencyOption content)
is called to indicate that a specific piece of content returned by a previous call to QueryBestContent
has been selected. This method should update any appropriate state to represent this fact, such as by updating the total number of times the content has been selected.
You can use a node group to check to see if any of its items can run, This can be useful when determining whether to show if a character should be marked as ready to talk, or whether any of its nodes have a special tag (for example, if a character has important information to discuss, as opposed to more general conversation.)
Learn to use options, which allow your players to choose lines of dialogue.
When you want to let the player decide what to say, you use an option. Options let you show multiple potential lines of dialogue to the player, and let the player select one.
Options are lines prefixed with a ->
. You write as many options as you'd like the player to see, and the player chooses one of them. The content of the option is like any other line of dialogue.
For example, consider the following code:
title: Start
---
Navigator: The quantum fluctuations are intensifying. We need to jump now.
Captain: But the calculations aren't complete. We could end up anywhere.
Navigator: The wormhole is collapsing. It's now or never.
Captain: Fine. Initiate jump sequence.
Navigator: Something's wrong. We're being pulled backward...
Captain: That's impossible. Unless...
Navigator: We're arriving before we left. We've become our own rescue mission.
-> Captain: Let's alter our trajectory and break this temporal loop!
-> Captain: We must complete the cycle. Our past selves depend on it.
===
In this example, the line Navigator: We're arriving before we left. We've become our own rescue mission.
will run.
The player will then be given the choice for the Captain to say either Let's alter our trajectory and break this temporal loop!
, or We must complete the cycle. Our past selves depend on it.
Options that are grouped together are delivered together, for example, consider the following updated script:
title: Start
---
Navigator: The quantum fluctuations are intensifying. We need to jump now.
Captain: But the calculations aren't complete. We could end up anywhere.
Navigator: The wormhole is collapsing. It's now or never.
Captain: Fine. Initiate jump sequence.
Navigator: Something's wrong. We're being pulled backward...
Captain: That's impossible. Unless...
Navigator: We're arriving before we left. We've become our own rescue mission.
-> Captain: Let's alter our trajectory and break this temporal loop!
-> Captain: We must complete the cycle. Our past selves depend on it.
Navigator: Ayee! We're all going to die!
-> Captain: Nonsense! Keep yourself together!
-> Captain: AHHHH! We're all going to die!
===
In this example script these two options will be delivered together:
-> Captain: Let's alter our trajectory and break this temporal loop!
-> Captain: We must complete the cycle. Our past selves depend on it.
As will these, separately:
-> Captain: Nonsense! Keep yourself together!
-> Captain: AHHHH! We're all going to die!
Options can have their own lines, which are run when the option is selected. If a different option is selected, they won't run. To write this, indent the lines that belong to an option.
In the following code, different lines will run based on which of the two shortcut options are selected.
title: Start
---
Navigator: The quantum fluctuations are intensifying. We need to jump now.
Captain: But the calculations aren't complete. We could end up anywhere.
Navigator: The wormhole is collapsing. It's now or never.
Captain: Fine. Initiate jump sequence.
Navigator: Something's wrong. We're being pulled backward...
Captain: That's impossible. Unless...
Navigator: We're arriving before we left. We've become our own rescue mission.
-> Captain: Let's alter our trajectory and break this temporal loop!
Navigator: Risky, Captain. We'd be writing ourselves out of existence.
-> Captain: We must complete the cycle. Our past selves depend on it.
Navigator: Then we're doomed to repeat this moment... forever.
===
When the player has the choice of saying either "Let's alter our trajectory and break this temporal loop!
", or "We must complete the cycle. Our past selves depend on it.
"
Depending on their choice, the Navigator will say "Risky, Captain. We'd be writing ourselves out of existence.
" or "Then we're doomed to repeat this moment... forever.". Finally, no matter what was selected, the line "Sounds good!
" will run.
You can also nest options below other options. For example, consider the following snippet of Yarn Spinner Script:
title: Start
---
Navigator: The quantum fluctuations are intensifying. We need to jump now.
Captain: But the calculations aren't complete. We could end up anywhere.
Navigator: The wormhole is collapsing. It's now or never.
Captain: Fine. Initiate jump sequence.
Navigator: Something's wrong. We're being pulled backward...
Captain: That's impossible. Unless...
Navigator: We're arriving before we left. We've become our own rescue mission.
-> Captain: Let's alter our trajectory and break this temporal loop!
Navigator: Risky, Captain. We'd be writing ourselves out of existence.
-> Captain: Damnit, Navigator! Nothing can stop me existing!
Navigator: *sigh* Very well, Captain.
-> Captain: By gods! You're right!
Navigator: But it's only solution, I fear.
-> Captain: We must complete the cycle. Our past selves depend on it.
Navigator: Then we're doomed to repeat this moment... forever.
-> Captain: If we're doomed, at least we'll be remembered as heroes.
Navigator: .. if anyone remembers us at all
-> Captain: Forever... forever... forever...
Navigator: Sir?
-> Captain: We must do it!
Navigator: As always, sir, you're right.
===
In this example script, the following options will be delivered together:
-> Captain: Let's alter our trajectory and break this temporal loop!
-> Captain: We must complete the cycle. Our past selves depend on it.
And then, depending on which one was chosen, another set of options will be delivered together.
For example, if the player chooses Captain: Let's alter our trajectory and break this temporal loop!
, then the line from the Navigator will be delivered (Navigator: Risky, Captain. We'd be writing ourselves out of existence.
) and then two options for the Captain will be provided:
-> Captain: Damnit, Navigator! Nothing can stop me existing!
-> Captain: By gods! You're right!
And if the player chooses Captain: We must complete the cycle. Our past selves depend on it.
, then the line from the Navigator will be delivered (Navigator: Then we're doomed to repeat this moment... forever.
) and then three options for the Captain will be provided:
-> Captain: If we're doomed, at least we'll be remembered as heroes.
-> Captain: Forever... forever... forever...
-> Captain: We must do it!
Learn to use the jump command to move the narrative between nodes.
The <<jump>>
command lets you move the dialogue between nodes. It is used by writing <<
then the word jump
, a space, and then the full title of the node you want to jump the narrative to, then another >>
.
To jump to a node with the title Rescue_the_Kitten
, for example, you would write the line <<jump Rescue_the_Kitten>>.
Jump commands should always be placed on their own line, indented as appropriate.
For example, consider the following conversation, which could be structured inside one node, using nested options, or split over several nodes using options and jump commands. The before version is inside a single node, and works fine, but the after version is structured across multiple nodes, and is a lot easier to make sense of.
title: Start
---
Navigator: Where to, Captain?
-> Captain: I want to go back to earth!
Navigator: Earth it is sir.
Navigator: This jump will take us 10 hours.
Navigator: Permission to jump, sir?
-> Captain: Granted, let's go!
Navigator: On it, sir.
-> Captain: Not yet. Just wait a moment.
Navigator: Standing by, sir.
-> Captain: Second star to the left!
Navigator: Can you be more specific?
-> Captain: I cannot, no.
Navigator: Right away, sir.
-> Captain: ... that one *gestures*
Navigator: Very good, sir.
Navigator: Being a Navigator sure is hard work!
===
title: Start
---
Navigator: Where to, Captain?
-> Captain: I want to go back to earth!
<<jump Earth>>
-> Captain: Second star to the left!
<<jump SecondStar>>
Navigator: Being a Navigator sure is hard work!
===
title: Earth
---
Navigator: Earth it is sir.
Navigator: This jump will take us 10 hours.
Navigator: Permission to jump, sir?
-> Captain: Granted, let's go!
Navigator: On it, sir.
-> Captain: Not yet. Just wait a moment.
Navigator: Standing by, sir.
<<jump Done>>
===
title: SecondStar
---
Navigator: Can you be more specific?
-> Captain: I cannot, no.
Navigator: Right away, sir.
-> Captain: ... that one *gestures*
Navigator: Very good, sir.
<<jump Done>>
===
title: Done
---
Navigator: Being a Navigator sure is hard work!
===
Separating dialogue segments into nodes can aid in writing neater files that are easier to edit as they grow.
When you use <<jump>>
command, they'll be shown in the Graph View in Yarn Spinner for Visual Studio Code as an line with an arrow leading to the node that's being jumped to:
<<jump>>
command being visualised in the Graph View.If you use the <<jump>>
command to jump to a node that's in a different .yarn
file, it will be visualised as a line leading to a small circle:
Next up, learn about the Jump Command's close relative, the Detour Command.
Learn how to use the Live Share Extension with Yarn Spinner for Visual Studio Code.
The primary editing experience for Yarn Spinner Scripts is our Yarn Spinner for Visual Studio Code extension. This provides the majority of features that you'd want in order to effectively write .yarn
scripts using VSCode.
This page shows you how to use Microsoft's free Live Share extension for Visual Studio Code, together with Yarn Spinner for Visual Studio Code, to collaborate live on your narrative.
.yarn
files using the Live Share extension and VIsual Studio CodeTo install Live Share for Visual Studio Code, and use it with Yarn Spinner:
Click this link to open Visual Studio Code, and jump to the Live Share Extension.
Click the install button!
If Step 2 does not work, launch Visual Studio Code and choose the View menu -> Extensions, to open the Extensions Marketplace.
Then, in the search field at the top-left of the window, search for "Live Share", and click the Install button on the extension provided by Microsoft that appears in the results.
Open your Yarn Spinner project in Visual Studio code. For example, here's I Feel Fine:
.yarn
filesWith your project open, choose Live Share in the Activity Bar on the left side of the screen, and in the resulting view that appears, choose the Share button:
You'll be prompted to sign in to either GitHub or your Microsoft account, and after this a notification will appear in the bottom of the screen letting you know a shareable link to collaborate has been copied to the clipboard:
When you receive a collaboration link and visit it, you'll be asked how you'd like to join:
If you choose to Continue in Web, you'll be taken to a web version of VS Code, which will not have the Yarn Spinner extension installed. If you click Open in Visual Studio Code, your local copy of VS Code will be installed, complete with extensions.
Once the shared workspace opens in your local copy of VS Code, you'll be in an untrusted state, which means the Yarn Spinner extension will not be providing syntax highlighting, To enable this, click Manage, in the grey bar at the top of the window, then click the Trust button in the view that appears:
The person who initially shared the workspace will have full and complete access to the features of the Yarn Spinner for Visual Studio Code extension. Everything, from syntax highlighting to the graph view will work as normal.
Those who are joining the shared session will only have access to the syntax highlighting features of the Yarn Spinner for Visual Studio Code extension, provided they go through the process of trusting the workspace, as noted earlier.
As you edit, you'll be able to see other users in the files, and their work will be briefly highlighted as they write:
.yarn
files together.For more guidance using the Live Share extension, check out the extension's official page, as well as the Live Share extension's documentation.
Learn about Line Presenter, a Dialogue Presenter that displays a single line of dialogue on a Canvas.
Line View is a Dialogue Presenter that displays a single line of dialogue with a set of components parented to a Godot Control. When the Dialogue Runner encounters a line in your Yarn Script, the Line View 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, Line View 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 RichTextLabel, then the character's name will appear in this object.
If you don't attach a RichTextLabel 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.
Line View can be configured to use visual effects when presenting lines.
You can choose to have the Line View 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 View is turned on, then the Line View 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 View will not signal that it's done when the effects have finished, and the line's delivery will stop. To make the Line View finish up, you can call the UserRequestedViewAdvancement method, which tells the Line View that the user wants to proceed. The built-in Dialogue System scene comes set up with a 'Continue' button that calls this method. You can also call this method from code.
View Control Path
This Control node will be made visible when the Line View is displaying a line, and invisible when not displaying a line.
Convert Html to Bb Code
If enabled, matched pairs of the characters '<' and >
will be replaced by [
and ]
respectively, so that you can leverage Godot's RichTextLabel's . If you need a more advanced or nuanced way to use BBCode in your yarn scripts, it's recommended to implement your own custom dialogue presenter.
Auto Advance
If this is turned on, the Line View 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 off, the Line View will signal to the Dialogue Runner that it's done showing the line once all animations are complete.
Hold Time
If Auto Advance is turned on, the Line View 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.
Line Text Path
A RichTextLabel node that the text of the line will be displayed in.
Use Fade Effect
If this is turned on, the Line View 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 In Time
The duration of the Fade effect when fading a new line in, in seconds. If this is zero, the line will appear immediately.
Fade Out Time
The duration of the Fade effect when fading a line out, in seconds. If this is zero, the line will disappear immediately.
Use Typewriter Effect
If this is turned on, the text of the line will appear one character at a time. This will take place after the Fade effect, if enabled.
On Character Typed
A signal that's emitted every time the Typewriter effect displays new text.
Typewriter Effect Speed
The number of characters per second to display when performing a Typewrite effect. Larger values means that text will appear faster.
Character Name Text
A RichTextLabel node that will display the name of the character currently speaking the line.
Show Character Name In Line View
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 at all. This option is only available when Character Name Text is empty.
Continue Button
A Control that will be made visible when the line has finished appearing. This is intended to be used for controlling the appearance of a button that the user can interact with to continue to the next line.
Learn about our Background Chatter Sample, which shows off how to have your characters talk to each other in the background, not just with a player character.
One of the best ways to make a game's world come alive is to have characters engage in conversations with each other, not just with the player character. The Background Chatter sample demonstrates how you can use Yarn Spinner to create and manage these ambient conversations.
The Dialogue Runner is the core component that manages dialogue interactions in your scene. When a node runs, the Dialogue Runner sends lines and options to its Dialogue Presenters.
The Line View and Option View presenters in the built-in prefab are designed to be modal - they take center stage, assuming they have the player's full attention. During these conversations, the player is typically not moving around or performing other actions.
Background conversations, however, are non-modal: they happen while the player continues to explore and interact with the world. The player doesn't participate in these conversations, which proceed independently.
The most effective approach for implementing background conversations in Yarn Spinner is to create separate Dialogue Runners for each conversation. This approach offers several advantages:
Each background Dialogue Runner can have dedicated Dialogue Presenters designed specifically for ambient conversations
Background Dialogue Runners operate independently from the main "primary" Dialogue Runner
You can manage multiple simultaneous background conversations as needed
All Dialogue Runners in your scene can share the same Yarn Project and Variable Storage, maintaining a consistent dialogue state.
Line Presenters for background conversations have different requirements than those used for primary player interactions. These presenters should:
Make it clear that dialogue is taking place
Remain unobtrusive enough to avoid interfering with the player's focus
Usually avoid presenting options, since background conversations don't assume active player participation
Since your game might feature multiple simultaneous conversations, we recommend creating one Dialogue Runner for each conversation in your scene, allowing them to run in parallel.
The BackgroundChatter sample demonstrates how to create an environment where the player can move freely while hearing ambient conversations in the background.
To play the sample, open the BackgroundChatter/Main.unity
scene and press play. Use the WASD keys to move around and the Space key to initiate conversations with characters.
The sample features a sequence of background conversations, each demonstrating different aspects of this system. Explanation characters (identifiable by their ties and coffee cups) stand near each demonstration. Speaking with them provides details about what each conversation demonstrates.
In this sample, each background conversation is represented by a "Chatter Group" - a spherical volume in the game world that can trigger a specific dialogue node when the player enters it. Each Chatter Group contains:
A Canvas with custom Line Presenters
Text elements that float above speaking characters
Player position tracking that cancels dialogue when the player leaves the volume
The Chatter Group Manager runs each Chatter Group on a timer, so that while the player remains within a group's volume, that group can potentially initiate dialogue.
The basic background conversation demonstrates a simple exchange between two characters. This conversation runs normally and cannot be interrupted by player actions.
The procedural conversation demonstrates using two line groups to create a dynamic back-and-forth between characters. The first line group contains only questions, while the second contains generic replies that work with any question. This approach allows you to create numerous possible conversation combinations with minimal writing effort.
The interruptable conversation demonstrates how players can interrupt characters engaged in a background conversation. While these characters participate in their ambient dialogue, they remain interactable. When the player initiates interaction, the background conversation pauses to prevent overlap with the primary dialogue. After the player's conversation concludes, the background conversation resumes.
This example shows how players can participate in what begins as a background conversation. When the player approaches the character, a conversation starts with the player as a participant. If the player leaves the Chatter Group's volume, the conversation ends and triggers an additional dialogue node. This allows the player character to deliver a parting line rather than abruptly ending the conversation.
This example demonstrates how background conversations can access variable storage and other Yarn Spinner features. When the player passes by these characters repeatedly, they progress through different parts of an ongoing conversation. This continuity is achieved by using a variable that updates each time the conversation runs.
The marketplace scene showcases background conversations in a more gameplay-oriented scenario. As you explore this area, you'll encounter various types of background conversations demonstrated elsewhere in the sample.
Learn about Yarn Spinner's built-in functions.
A function is a block of code that provides a value to your Yarn scripts, which you can use in , or store in .
In Yarn Spinner scripts, functions perform two main kinds of task:
Functions let you get values that change over time, or that depend on other values. For example, the random
function returns a different random number every time you call it.
Functions let you get data from your game back into your scripts.
You call a function inside an expression. For example:
Yarn Spinner comes with several built-in functions for you to use.
visited(string node_name)
visited
returns a boolean value of true
if the node with the title of node_name
has been entered and exited at least once before, otherwise returns false
. Will return false
if node_name
doesn't match a node in project.
visited_count(string node_name)
visted_count
returns a number value of the number of times the node with the title of node_name
has been entered and exited, otherwise returns 0
. Will return 0
if node_name
doesn't match a node in project.
format_invariant(number n)
format_invariant
returns a string representation of n
, formatted using the invariant culture. This is useful for embedding numbers in commands, where the command expects the number to be formatted using the invariant culture. For example, <<give_gold {$gold}>>
, which might end up as give_gold 4,51
in German, but give_gold 4.51
in English, can now be <<give_gold {format_invariant($gold)}>>
, which will always be give_gold 4.51
.
random()
random
returns a random number between 0 and 1 each time you call it.
random_range(number a, number b)
random_range
returns a random number between a
and b
, inclusive.
dice(number sides)
dice
returns a random integer between 1 and sides
, inclusive.
For example, dice(6)
returns a number between 1 and 6, just like rolling a six-sided die.
min(number a, number b)
min
compares a
and b
, and returns the smaller of the two.
max(number a, number b)
max
compares a
and b
, and returns the larger of the two.
round(number n)
round
rounds n
to the nearest integer.
round_places(number n, number places)
round_places
rounds n
to the nearest number with places
decimal points.
floor(number n)
floor
rounds n
down to the nearest integer, towards negative infinity.
ceil(number n)
ceil
rounds n
up to the nearest integer, towards positive infinity.
inc(number n)
inc
rounds n
up to the nearest integer. If n
is already an integer, inc
returns n+1
.
dec(number n)
dec
rounds n
down to the nearest integer. If n
is already an integer, dec
returns n-1
.
decimal(number n)
decimal
returns the decimal portion of n
. This will always be a number between 0 and 1. For example, decimal(4.51)
will return 0.51
.
int(number n)
int
rounds n
down to the nearest integer, towards zero.
We recommend that you only move into the Yarn Spinner for Unity documentation after learning the fundamentals of Yarn Spinner Scripting.
You can create your own commands, so that your scripts can send directions to your game. For more information on how to create them in Unity games, see , in the Yarn Spinner for Unity section of the documentation, and equivalents for other engines.
Functions are not intended to be a way for you to send instructions to your game. For that purpose, you should use .
In particular, functions are not guaranteed to be called in the same order as they appear in your code, or even be called at all if Yarn Spinner believes the result can be cached.
As much as possible, custom functions should be , and have no side effects besides returning a value based on parameters.
Learn about nodes and lines in Yarn Spinner scripts.
Yarn Spinner Scripts are built up out of nodes. Nodes are where you put your dialogue. You can have as many nodes as you want in a file.
Each node has, at the very minimum, a collection of headers, and a body. All nodes have at least one header, which is the title. The title is the name of the node, and the body contains the Yarn script that contains your game's dialogue.
Nodes are used to separate out parts of the story, and make it easier to manage longer stories and branching.
The title of a node is important, because your game uses node titles to tell Yarn Spinner which node to start running. You also use the title of a node when you want to jump to another node. Node titles are not shown to the player.
Node titles must start with a letter, and can contain letters, numbers and underscores. Node names cannot contain a .
(period).
For example, FirstNode
, First_Node
and Node1
are valid, but First Node
, First.Node
and 1stNode
are not.
The ---
marker indicates where the body begins. After this point, you can put all of your Yarn script.
The ===
marker indicates where the node ends; after this point, you can begin another node.
The body of a node (that is, the content between the node start marker ---
and the node end marker ===
) is made up of three different kinds of content: lines, commands, and options.
When you write Yarn Spinner dialogue, just about every line of text that you write in a node is a line. When a node is run, it runs each line, one at a time, and sends it to your game.
A line of dialogue is just the thing you want some entity or character to say, usually beginning with the name of the entity speaking.
For example, consider the following Yarn Spinner script snippet from Night in the Woods:
When this code is run in the game, it looks like this:
Yarn Spinner sends each of these lines, one at a time, to the game. The game is responsible for taking the text, and presenting it to the player; in the case of Night in the Woods, this means drawing the speech bubble, animating each letter in, and waiting for the user to press a key to advance to the next line.
Lines of dialogue can contain just about any text, except for some special characters that Yarn Spinner uses to add extra information to a line.
If there is a set of characters without spaces before a colon (:) at the beginning of the line, Yarn Spinner will mark that as the name of the character. This information will then be passed to your game, so that you can change the way that lines are shown based on the character who's saying them. For example:
Nodes can also have a color:
and a group:
in their header, which specifies what colour the bar at the top a node is shown in, and the group they're gathered in, in the Graph View of the :
You can also set nodes to be Sticky Notes by adding style: note
to the node header. This will render the nodes differently in the Graph View of the . The color:
of the node will be respected too, if one is set, otherwise the Sticky Note will default to yellow:
You can learn more about in the section.
Learn about Line Presenter, a Dialogue Presenter that shows lines of text.
A Line Presenter is a 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:
Quick Start Guide
After following the instructions to , in your Godot project, click the Instantiate Child Scene button:
And navigate into the addons/YarnSpinner-Godot/Scenes
folder of your project, and choose the DefaultDialogueSystem.tscn
file as the scene to instantiate:
Then, right click the new scene in the Scene dock, and check the "Editable Children" option. This will allow you to view all of the components that make up the default dialogue system, and set options on them in the inspector dock.
Your Scene dock will look like this showing a node hierarchy that's entirely based on the DefaultDialogueSystem.tscn
scene that you instantiated:
Next, create a new Yarn Project using the menu Tools > YarnSpinner >Create Yarn Project:
Then choose a directory to save your new YarnProject in. For example, you can save it to the root of your project. Name the new Yarn Project FirstProject.yarnproject
:
Next, create a new Yarn script (a file with a .yarn
extension) by using the menu Tools > YarnSpinner >Create Yarn Script. In the resulting "Create a new Yarn Script" window, set the File name to MyStory.yarn
, and click the Save button::
It may take a moment, but Godot will import your new .yarn
file, and it will appear in the FileSystem dock. When it's appeared, double-click on the Yarn Project, FirstProject.yarnproject
in the FileSystem dock and look to the Inspector, making sure that res://MyStory.yarn
is in the list of Source Scripts, which are the Yarn scripts that compromise the new project:
Next, open the MyStory.yarn
file in VS Code, and add the following Yarn script to it, before saving it and returning to Godot:
Select the DialogueRunner
node in the Scene dock, and look to the Inspector. Selecting "Editable Children" earlier in the guide is what will allow you to see the DialogueRunner
node and edit its options. Assign the Yarn Project you created to the DialogueRunner
by dragging the FirstProject.tres
Yarn Project from the FileSystem dock into the Yarn Project slot of the Inspector:
Finally, enter Start
as the Start Node, and tick the box next to Starts Automatically:
Save your scene as Demo.tscn
, and run the game. At this point, you can play your project, and step through the dialogue in the default Yarn Spinner for Godot Line Presenter and Options Presenter:
With that, we've reached the end of our beginner's guide. You're ready go forth and build games with Yarn Spinner! You're also equipped to work with the rest of the documentations here! Don't forget to to chat with other Yarn Spinner users, the Yarn Spinner team, seek help, and share your work.
Learn about Yarn Projects, which group your scripts together for use in a Dialogue Runner.
A Yarn Project is a file that links multiple together. Yarn projects are how Dialogue Runners work with your content.
To create a new Yarn Project, follow these steps:
Open the Project menu, and choose Tools -> YarnSpinner -> Yarn Project.
Godot will open a dialogue where you can choose the directory your Yarn Project will be saved, and its filename. Choose a name and directory, and press the Save button.
On their own, a Yarn Project doesn't do anything. In order to be useful, you need to add Yarn scripts to it.
Yarn Projects include all Yarn Scripts that the project finds in the Source Files directory. By default, that means all Yarn Scripts in the same directory as the Yarn Project, and all of that directory's children.
When you add a Yarn Script to the same folder as a Yarn Project, it will automatically be included in the Yarn Project. When you make changes to the script, the Yarn Project will automatically be re-imported.
You can change the locations that a Yarn Project looks for Yarn Scripts by modifying the Source Files setting. Each entry in the Source Files setting is a search pattern.
You can add as many entries to the Source Files field as you like. If a file is matched by multiple patterns, it will only be included once.
A Yarn script can be included in more than one Yarn Project.
When you write a Yarn script, you write it in a specific human language. This is referred to as the 'base' language of the script. It's called the base language because it's the one you start with, and the one you translate into other languages.
You can set the base language of a Yarn Project in the Inspector by changing the Base Language setting.
If you want to translate your scripts into another language, you add a new locale code to your Yarn Project. To learn about this process, see .
Yarn Projects are used by Dialogue Runners. When a Dialogue Runner is told to start running dialogue, it reads it from the Yarn Project it's been provided.
Learn about tags and metadata, for adding additional context to lines in Yarn Spinner Scripts
A tag is a piece of information that you can add to content in Yarn Spinner that adds additional context or detail about that content.
There are two places you can add tags in Yarn scripts: you can add them to nodes, and you can add them to lines.
Tags aren't shown to the user; instead, they're used by your game, or by Yarn Spinner itself.
Tags can be added to the end of lines and options. Tags on lines start with a hash symbol (#
), and cannot contain spaces. You can add as many tags as you like to line, but they must all be on the same line in the script.
Here's an example of a line with two tags:
Tags that you add to a line can be accessed from your game. The way that you access them depends on your game engine. For example, to access them in a Unity game, you use the LocalizedLine.Metadata property.
Some tags are used by Yarn Spinner itself, while all others are used by your own code, so it's up to you what content they should have, and how to handle them.
Certain tags are used internally by Yarn Spinner, or are added for you if they don't exist.
#lastline
The Yarn Spinner compiler adds a #lastline
tag to every line that comes just before a set of options.
For example, the following excerpt:
is treated as though it had been written as:
In a Unity game, you can use this tag in a custom to be notified ahead of time when the player is about to be shown options.
#line
The #line
tag uniquely identifies a single line or option across all of your game's dialogue. This is used to identify lines for localisation. Every line's #line
tag must be unique. If a line or option doesn't have a #line
tag, it will be automatically added for you.
Here's an example of some Yarn script with #line
tags:
For more details (including what the tag should look like), see .
Nodes can also have tags, which you can use to add labels that describe the node.
Node tags they work a bit differently than line tags: they are defined in the header with the tags
key, and they don't have to begin with a hash symbol (#
).
Here's an example of a node with two tags:
You can access the tags on a node via the Dialogue object's method.
The metadata of a line is only composed of tags. Because of this, you may find that the Yarn Spinner code and documentation refer to line tags and line metadata interchangeably.
Nodes can have other metadata in their headers. This metadata isn't exposed through the API, which means it's mostly used to store additional information for whoever is writing the Yarn dialogue or for editors to make use of.
However, currently there is one header that defines specific behavior within the Yarn Spinner compiler: the tracking
header.
tracking
headerNodes can track whether they have already been visited during the game. For this to work, the Yarn Spinner compiler needs to add some special code to the node. To avoid creating this code for nodes that don't need it, the compiler only adds this code if it finds a call to the visited()
function with the node name in it.
In some cases, you may need the compiler to add this special code to a node even if no corresponding visited()
call exists. To direct the compiler to do this, include the tracking
header with the value of always
:
Additionally, using a value of never
instructs the compiler to never add this special code to the node. If you use the visited
function with a node set to never use tracking, it will always return false
.
For more information on visit tracking, see the documentation for .
Tags and metadata may seem very complicated at first, and their uses may not be clear. The following example use cases explain how they can be used in your game. Keep in mind that this is not an exhaustive list of use cases.
Yarn provides to let you change attributes for specific parts of a line. In case most of your attributes apply to entire lines (for example, the color of a line), it may be easier to just use tags instead.
The #lastline
tag can be used to display the last line of dialogue along with any options. This is handled within your code by checking if a line has the #lastline
tag, and if it does, storing it before continuing with the execution of the Yarn dialogue.
Since metadata isn't shown directly to the player, you can use metadata for any internal workflows or tooling. For example, instead of tracking lines that need to be revised outside the Yarn files (which could lead to syncing problems), you could add line tags (such as #needsrevision) to the appropriate lines directly in the Yarn files, and process these lines as part of an internal tool or workflow. The Unity integration automatically generates a CSV file with all lines that contain metadata, making this super easy!
As referenced before, the Yarn Spinner integration for Unity uses line tags to link localised dialogue lines. This is better explained in the section, when using Yarn Spinner for Unity.
Aside from that, every piece of metadata can be used by translators and adapters to help them understand how the text is being used, thus leading to better localised text.
Some games may require that certain lines of dialogue are displayed somewhere other than the dialogue window (for example, as flavor text for an item description, or in an item that acts as a log). Instead of manually duplicating these lines (which adds overhead during development and localisation), tags can be used along with code that checks for the tags and duplicates the lines while the game is running.
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.
// Inside an if statement:
<<if dice(6) == 6>>
You rolled a six!
<<endif>>
// Inside a line:
Gambler: My lucky number is {random_range(1,10)}!
Homer: Hi, I'd like to order a tire balancing. #tone:sarcastic #duplicate
Hello there.
-> Hi!
-> What's up?
Hello there. #lastline
-> Hi!
-> What's up?
Mechanic: You're in orbit of Jupiter, at a rest station along the main tourism lines. There's a meteorite headed towards here that'll completely destroy this station in three days. #line:4c49c5
Mechanic: And you're a wayfinding robot bolted to the floor of said Jupiter Tourist Station. #line:5b6256
-> Bolted to the floor?! #line:f65d07
Mechanic: Yeah, like all tourist helper bots. #line:1b159b
-> Three days?! #line:40eaf7
Mechanic: More or less. I wouldn't make any long-term plans. #line:3a6c94
title: Train_Dialogue
tags: #camera2 background:conductor_cabin
---
Why did you stop the train?
Now we won't arrive in time at the next stop!
===
title: Node_Name
tracking: always
---
I know how many times you've been here.
===
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.
Auto Advance
If this is turned on, 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 off, the Line Presenter will signal to the Dialogue Runner that it's done showing the line once all animations are complete.
Hold Time
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.
Line Text
A TextMeshPro Text object that the text of the line will be displayed in.
Use Fade Effect
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 In Time
The duration of the Fade effect when fading a new line in, in seconds. If this is zero, the line will appear immediately.
Fade Out Time
The duration of the Fade effect when fading a line out, in seconds. If this is zero, the line will disappear immediately.
Use Typewriter Effect
If this is turned on, the text of the line will appear one character at a time. This will take place after the Fade effect, if enabled.
On Character Typed
A Unity Event that's called every time the Typewriter effect displays new text.
Typewriter Effect Speed
The number of characters per second to display when performing a Typewrite effect. Larger values means that text will appear faster.
Character Name Text
A TextMeshPro Text object that will display the name of the character currently speaking the line.
Show Character Name In Line Presenter
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 at all. This option is only available when Character Name Text is empty.
Continue Button
A game object that will be made active when the line has finished appearing. This is intended to be used for controlling the appearance of a button that the user can interact with to continue to the next line.
*
any filename
"*.yarn
" will find "One.yarn" and "Two.yarn".
**/*
any path, including subdirectories
"**/*.yarn
" will find "One.yarn" and "Subfolder/Two.yarn".
..
the parent folder
"../*.yarn
" will find "One.yarn" in the parent folder.
Re-Compile Scripts in Project
Manually trigger all of your .yarn scripts to be compiled.
Add Line Tags to Scripts
When you click this button, any line of dialogue in the Source Scripts list that doesn't have a #line:
tag will have one added. See Adding Localizations for more information.
Update Localizations
When you click this button, all .csv
strings files that are configured in the Localization CSVs list will be updated with any lines that have been added, modified or deleted since the strings file was created.
See Adding Localizations for more information.
Source Scripts
The list of places that this Yarn Project looks for Yarn Scripts.
Base Language
The locale code for the language that the Yarn Scripts are written in.
Localization CSVs
A mapping of locale codes to CSV file paths, for storing localized content for your dialogue.
Export Strings and Metadata as CSV
When you click this button, all of the lines in the Yarn Scripts that this project uses will be written to a .csv
file, which can be translated to other languages. A CSV file listing any metadata associated with each line will also be generated alongside the strings CSV file. See Adding Localizations for more information.
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 write a script that implements the DialoguePresenterBase
interface, and add your script to a node in your scene. You can then add this node to the Dialogue Presenters list in the inspector of your scene's Dialogue Runner.
By itself, an empty script implementing the DialoguePresenterBase
interface will not do anything useful. To make it display lines and options, you'll need to implement certain methods.
To understand how to create a custom Dialogue Presenter, it's useful to understand how the Dialogue Runner works with content.
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.
Two example scenes are provided with Yarn Spinner for Godot: addons/YarnSpinner-Godot/Scenes/DefaultDialogueSystem.tscn
and addons/YarnSpinner-Godot/Scenes/RoundedDialogueSystem.tscn
.
Many projects ultimately end up needing to write their own custom Dialogue Presenter scripts to achieve the desired level of control over line and option presentation, but when first starting to integrate Yarn Spinner with your Godot game, it can be useful to start with the provided example presenters and modify their appearance and layout. These .tscn
files provide a pre-configured Line Presenter, DialogueRunner, and Options Presenter.
To create your modified version, start by navigating to the .tscn
file that you would like to base your presenters on in addons/YarnSpinner-Godot/Scenes/
. In this example, we'll use the RoundedDialogueSystem. Right click RoundedDialogueSystem.tscn and select the option "Move/Duplicate to...". Choose the directory in your project that you would like to save your dialogue system to, then click the 'Copy' button.
Be careful not to select the 'Move' button on this step. If you do accidentally move the file instead of copying it, you can move it back by following the same process, selecting addons/YarnSpinner-Godot/Scenes/
as the destination directory.
You now have a duplicate of the original .tscn
file. You can rename it to anything you like. Double click your duplicated file to edit it.
Before modifying any styles in your copy of the dialogue system, make sure to right click the StyleBox resource and select 'Make Unique' or 'Save As...'. If you select 'Save As...', chose a directory outside of the addons/ directory to save your modified style. If you don't do this step, any modifications that you make to the styles will be saved to the .tres files in Yarn Spinner for Godot's directory, meaning you will lose these changes the next time you update the plugin.
Keep an eye on the addons/YarnSpinner-Godot directory as you customize your presenters using version control to ensure the .tres and .tscn files are not being modified.
You can now edit the Controls in your new scene to arrange and style them to your liking. The Controls that your scene contains, such as Panels, RichTextLabels, VBoxContainers, and others, are built-in Godot UI components, so you use the same techniques that you would for any other Godot UI component to re-style them.
One way to customize the appearance of your scene is by adding or modifying StyleBox resources on the various components (visible in the inspector for each node under Theme Overrides > Styles). You can also modify the anchor points of the Controls to change their position relative to the screen's edges and center.
It's recommended to become familiar with the fundamentals of working with Godot UI components before attempting to customize your dialogue system.
Mae: Well, this is great.
Mae: I mean I didn't expect a party or anything
Mae: but I figured *someone* would be here.
Mae: ...
Mae: Welcome home, Mae.
This is a line of dialogue, without a character name.
Speaker: This is another line of dialogue said by a character called "Speaker".
Navigator: The quantum fluctuations are intensifying. We need to jump now.
Captain: But the calculations aren't complete. We could end up anywhere.
Navigator: The wormhole is collapsing. It's now or never.
Captain: Fine. Initiate jump sequence.
Navigator: Something's wrong. We're being pulled backward...
Captain: That's impossible. Unless...
Navigator: We're arriving before we left. We've become our own rescue mission.
title: Start
tags:
---
Narrator: Oh, hello!
-> Hi, where am I?
Narrator: You're in Godot!
-> Oh.
<<jump Oh>>
-> How did I get here?
<<jump Godot>>
===
title: Oh
---
Narrator: Yeah, fun, right?
===
title: Godot
---
Narrator: Someone read the Beginner's Guide!
===
DefaultDialogueSystem.tscn
.DefaultDialogueSystem
instantiated into your scene.DialogueRunner
.DialogueRunner
to start automatically.Learn how to use logic and flow control in your Yarn Spinner Scripts.
So far, you've learned how to use Nodes and Lines, Options, the Jump Command, the Detour Command, and Variables to write Yarn Spinner Scripts.
Because it's actually a full programming language of its own, Yarn Spinner also supports flow control.
Flow control takes several forms, including if statements, and conditional options.
if
statementsIn addition to storing information, variables are useful for controlling what's lines of dialogue are presented to the player. To do this, you can use if
statements.
An if
statement allows you to control whether a collection of content is shown or not.
When you write an if
statement, you provide an expression, which is checked; if that expression evaluates to a true
value, then all of the content in between the <<if>>
and <<endif>>
statements are run.
For example, consider the following Yarn Spinner Script:
<<set $gold_amount to 5>>
Player: I'd like to buy a pie!
<<if $gold_amount < 10>>
Baker: Well, you can't afford one!
<<endif>>
This script will:
set a variable, $gold_amount
, to 5;
show the line Player: I'd like to buy a pie!
use an if statement to see if $gold_amount
is less than 10
, and if that is the case, which it will be in this example, show the line Baker: Well, you can't afford one!
elseif
and else
You can use the elseif
and else
statements to handle different situations in an if
statement.
An elseif
statement has an expression that gets checked if the if
statement, or any previous elseif
statements, don't run.
An else
statement doesn't have an expression, and runs if the if
and any elseif
don't run.
For example, consider the following Yarn Spinner Script:
<<set $gold_amount to 5>>
Player: I'd like to buy a pie!
<<if $gold_amount < 10>>
Baker: Well, you can't afford one!
<<elseif $gold_amount < 15>>
Baker: You can almost afford one!
<<else>>
Baker: You can afford a pie!
<<endif>>
This script will:
set a variable, $gold_amount
, to 5;
show the line Player: I'd like to buy a pie!
use an if statement to see if $gold_amount
is less than 10
, and if that is the case, show the line Baker: Well, you can't afford one!
otherwise use a elseif
statement to see if $gold_amount
is less than 15
, and if that is the case, show the line Baker: You can almost afford one!
otherwise use a else
statement to provide a fallback, which will show the line Baker: You can afford a pie!
end the if
statement block with an endif
statement.
You can have as many elseif
statements as you want inside an if
statement block, and you can use whatever you want inside, for example:
<<set $gold_amount to 5>>
<<set $reputation to 10>>
<<if $gold_amount >= 5 and $reputation >= 8>>
// The player is both rich and popular
Merchant: You're rich enough and popular enough for me to serve you!
<<elseif $gold_amount >= 10 or $reputation >= 10>>
// The player is either rich enough or popular enough to serve
Merchant: I wouldn't normally, but I'll serve you!
<<else>>
// The player is dirt
Merchant: You're neither rich enough nor important enough for me to serve!
<<endif>>
In this example:
there are two variables, $gold_amount
for tracking the player's currency, and $reputation
for their reputation in town
an if
statement first checks if their $gold_amount
is greater than or equal to 5
and whether their reputation is greater than or equal to 8
, if both of these are true, then the line Merchant: You're rich enough and popular enough for me to serve you!
is shown
otherwise the elseif statement checks if their $gold_amount is greater than or equal to 10 (making them very rich), or their $reputation is greater than or equal to 10 (making them very well regarded), and if either of these are true the line Merchant: I wouldn't normally, but I'll serve you!
is shown
if neither set of conditions is true, then the else statement means the line Merchant: You're neither rich enough nor important enough for me to serve!
will be shown
When presenting options to the player using the ->
syntax, you may want to make some options not available. You can do this by adding a condition to the option, making it a conditional option.
For example, if you have a variable that tracks your player's reputation points, called $reputation
, you might want to make certain options only available if the value of $reputation
is high enough.
Conditions on options are done by adding an if
statement to the end of the option. They look like this:
Guard: You're not allowed in!
-> Sure I am! The boss knows me! <<if $reputation > 10>>
-> Please?
-> I'll come back later.
When Yarn Spinner runs this collection of options, it will check the expression inside the if
statement. If the expression is false
, then the option will be marked as unavailable.
In addition to Yarn Spinner's own built-in localisation system, your game can also use the Unity Localization package.
Both the Unity Localization and Built-In Localisation approaches are very similar to one another, but there are some caveats and extra steps to make them play together.
Watch a video where Yarn Spinner developer Jon Manning walks you through using Yarn Spinner with Unity's Localisation package:
Before doing anything with Yarn Spinner, you will need to set up your Unity project to use the Unity Localization system. To install and set up Unity Localization, follow the instructions on the Unity Localization package's documentation.
Install the Localisation package
Create a new Localization Settings in the Project Settings - Localization screen:
Create at lease one Locale in the Project Settings - Localization view.
Create a String Table Collection via Window menu -> Asset Management -> Localization Tables.
Check "Use Unity Localisation System" in your Yarn Project, and assign the String Table, and click Apply.
Verify that the String Table (viewable by Window menu -> Asset Management -> Localization Tables) contains your Yarn Spinner Script's lines.
In the Inspector for your Dialogue Runner, click the "Add Unity Localized Line Provider" button:
This will add a component to your GameObject. Find it, and assign your String Table Collection to the Strings Table field:
Don't manually update the Strings Table Collection for your base language. For example, if you're writing your Yarn Spinner Scripts in English, don't modify the English column. That column is managed and updated by Yarn Spinner. You can safetly modify the other columns (which is how you get your translations in). We recommend having a separate Strings Table Collection for your non-Yarn Spinner strings.
Once you have followed these instructions, your project should now:
Have the Unity Localization package installed
Created and configured one or more Locales for your project
Created a string table collection.
With these done you should now have your project set up correctly, and have a string table collection for your locales with no entries inside. Yarn Spinner will fill this string table with content that it extracts from your Yarn Scripts.
To fill a string table with content from a Yarn project, follow these steps:
Select the Yarn Project, and go to its Inspector.
Enable the Use Unity Localisation System setting.
Set the Base Language
to your desired language. This must be ensure its one of the locales that you have configured for your project.
In the String Table Collection
field, add the String Table Collection that you want to populate with line content.
Click Apply.
You can check that the string table has been filled with content by opening the Window menu, and choosing Asset Management -> Localization Tables. You can then view the contents of your string table. The Key of each string will be the #line
ID from the Yarn files.
When the Yarn Project importer adds your lines into the string table, it uses the Base language field you set in the Inspector to determine which locale in your String Table Collection should have the lines added into.
If your project doesn't have a Locale which matches your Base Language, Yarn Spinner will attempt to find an appropriate Locale to use. To ensure that the importer uses the correct Locale, be sure to specify it in the Inspector.
When a Yarn script is run, the Dialogue Runner receives line IDs from the Yarn Project, and must determine what localised content should be shown to the player, using a line provider. In order for the Dialogue Runner to fetch localised data from the Unity string table, you use a Unity Localised Line Provider.
To configure it, all that needs to be done is hook your string table collection up to the Strings field of the Unity Localised Line Provider.
During gameplay, the Unity Localised Line Provider will fetch content from your string table depending on the game's current locale setting. You can control this at run-time by using the locale selector at the top-right corner of your Game View.
In addition to localising the strings that make up your lines, you can also localise assets that go with each line, such as voice-over audio, or custom objects that store other localised data.
To localise assets in Unity Localisation, you create and populate an Asset Table. Yarn Spinner doesn't automatically populate Asset Tables for you like it does String Tables, because Yarn Spinner doesn't manage your assets like it does with your lines.
Instead, you can create an Asset Table that contains assets with the same key as your lines. For example, if you have a line in your Yarn script that has the line ID "line:tom-1
", then the string table will have an entry with the key line:tom-1
. To create a voice-over asset to go with this line, you can create an asset table that also contains an entry with the key line:tom-1
, and maps to an audio file.
The Unity Localised Line Provider will automatically match String Table entries and Asset Table entries if they have the same key, and then deliver them to your Dialogue Views for use. To do this, ensure that your Unity Localised Line Provider has an Asset Table configured in the Inspector.
Because both Yarn Spinner and Unity use the same marker for their string interpolation and manipulation ({
and }
). You can't use the Unity Localization smart strings in Yarn Spinner content.
Learn how to use the Speech Bubbles, from the Speech Bubbles for Yarn Spinner Add-On Package.
The Speech Bubbles Add-On provides a flexible speech bubble system with a variety of possible customisations. Here, you'll learn how to implement it in a Unity project by looking at a basic little 3D game. We'll be using prefabs and assets from the Yarn Spinner Samples package to speed this up, but nothing we are using from there is necessary, it's just faster than building all the infrastructure out ourself.
To use Speech Bubbles for Yarn Spinner, you'll need to create a new Unity project and install the Yarn Spinner package, and then install the Speech Bubbles for Yarn Spinner package.
For this guide, we'll be making a simple 3D game that will look like this:
Our environment and the characters will all be using the exact same Sample Package assets we used to build out the examples for the Speech Bubbles in addition to all the samples in the Samples package.
First we want to have the environment to wander around in, so from the Packages/Yarn Spinner Samples/Prefabs
folder grab the Basic Arena prefab out and drop it into the scene.
Our script, which we've yet to write, will have two characters, the player and one NPC so we need them next. From the Packages/Yarn Spinner Samples/Prefabs
folder grab the Player prefab and place them somewhere in the arena. From the same folder grab the NPC prefab and place it somewhere else in the arena. We also want our NPC to be coloured orange, or else the story won't make any sense.
Select the NPC and rename them to be called "Orange", then in the Inspector find the Character Appearance
component and select the Orange button.
Orange will change to now be coloured the same as their namesake. Once that is done we should have our world built out.
The player can move about so we will want the camera to track them, luckily the code for that is also already done in the sample assets. From the Packages/Yarn Spinner Samples/Prefabs
folder and drag the Camera Rig prefab into the scene. This includes a camera so we can select the existing Main Camera from the hierarchy and delete it. For the camera rig to follow the player we need to let it know that that is it's target. To do this select the Camera Rig and in the Inspector drag the Player from the Hierarchy into the Target field.
Now our camera is following the player, you can test this by starting the game and walking the player around the scene.
Time to make the dialogue happen, first we will make our project by creating a new Yarn Project asset by going Create -> Yarn Spinner -> Yarn Project
and we'll name it Demo.yarnproject
. For our script we'll also make that now Create -> Yarn Spinner -> Yarn Script
and name that Demo.yarn
. Open Demo.yarn
and add the following dialogue:
With this magnum opus of a story done let's get it into our demo game.
In the Hierarchy right click and add a new Yarn Spinner -> Dialogue System
to the scene. Select the Dialogue System and in it's Inspector set the Yarn Project field to be our Demo.yarnproject
we just made.
Last piece of the puzzle is to make it so that Orange can trigger dialogue when interacted with. Select out Orange NPC in the scene and in it's Inspector connect our Dialogue System into it's Dialogue Runner field, our Demo.yarnproject
project into it's Dialogue field and from the node dropdown select the Orange
node.
Now if you run the scene once again you'll see it all work, but it's just using the default presenters, and not the speech bubbles. Everything we've done up until now is just getting a basic scene up and running so that we can add the bubbles, so let's do that now.
Bubbles are a bit different from most presenters, they need to both be told about the dialogue events, so must participate in the dialogue system but also need to understand the characters they belong two so they can anchor the actual individual bubble with that character. These two pieces need to be done before they'll work.
First up we will need to modify the dialogue system. Expand out the Dialogue System and it's Canvas. Select the Line Presenter and Options Presenter and delete both of them, we won't need them as the Speech Bubbles will handle both roles. Add a new empty game object to the Canvas and name it "Bubble Dialogue View".
In the Inspector for the Bubble Dialogue View add a new Bubble Dialogue View component to the game object. This is our actual Speech Bubbles presenter component but it will need some configuration. In the Bubble Prefab field add the Casual Bubble from the Prefabs folder in the Speech Bubbles install. In the Bubble Canvas field add the Canvas from the Hierarchy into this field.
As discussed in the Speech Bubbles main page the way Line Advancer works with the bubbles is a bit different than other presenters. We will need to add a Bubble Input system in addition to the Line Advancer otherwise bubbles won't know how to handle their options.
Select the Line Advancer from the Dialogue System. In it's Inspector add a new Bubble Input component to the Line Advancer game object. Now to configure it!
Add the Line Advancer from the Hierarchy into the Bubble Input's Line Advancer field. Add the Bubble Dialogue View from the Hierarchy into the View field. Add the Dialogue System from the Hierarchy into the Runner field. Finally, configure the input however you wish, we just left ours as keycodes.
The last step is to make it so the Dialogue Runner is actually aware of these changes, otherwise we are not gonna have a fun time.
Select the Dialogue System and in it's Inspector delete all the elements inside the Dialogue Presenters field. Set the size of that field to be two. Into the new slots drag the Bubble Dialogue View and the Line Advancer into these slots, respectively.
Now we need to anchor the bubbles to their characters.
Because the bubbles don't know the world position of the character they are associated with we need to give them that information. We do that via a Character Bubble Anchor, which is little more than a transform the Bubble Dialogue View knows about so it can map that anchors world position into a canvas screen position. Let's add them now.
Select the Player and add a new empty game object to the Player, name it Anchor. Add a Character Bubble Anchor component to the anchor game object. In the Inspector set the Character Name field to be Player
, this is used later so that the Bubble Dialogue View can use the current line's speaker name to work out which anchor to use. Leave the rest of the values as is.
Do the same with Orange, add a new game object and call it Anchor, add a Character Bubble Anchor to it, and set the Character Name to be Orange
.
The transform value set on the anchor, or the transform of the anchor itself if one isn't set, will be the position that is used to anchor the bubble. So make sure on both the Player and Orange to position the anchor somewhere that feels right to you. To help with this a small green ball gizmo will be drawn at the anchor location, so using that we placed the anchor point slightly above the characters heads, but you can put it anywhere you want.
With that, you can play your project and see the Speech Bubbles in action:
Next check our provided Speech Bubble Examples which show off using the bubbles in 2D as well as 3D and how you can create custom bubble content for individual characters.
This page covers what you need to know to use the internal localisation system built into Yarn Spinner. This supports both the localisation of the text, so the lines themselves, and your assets needed for them.
Watch a video where Yarn Spinner developer Jon Manning walks you through using the Built-In Localisation System:
When you want to prepare a Yarn Project for an additional language, you add a new Localisation in the Yarn Project.
Localisations are how you tell Yarn Spinner where to find the localised lines, and the localised line assets, for a given language.
To create a new Localisation, open the Localisations list in the Yarn Project's Inspector, and click the + button.
Localisations have the following properties:
Language ID
The language for this localisation.
Strings File
A Text Asset containing the translated lines for this Yarn Project's scripts. See for information on how to create these assets.
Assets Folder
A folder containing the localised assets for this localisation.
After you've set up a localisation, you can translate your dialogue into that localisation's language. To do this, you generate a strings file.
A strings file is a text-based spreadsheet, in comma-separated value form, that contains a translated version of your dialogue. Yarn Spinner can generate a strings file for you, based on the line IDs in the dialogue.
To create a strings file, select a Yarn Project, and click the "Export Strings and Metadata as CSV" button. Unity will ask where you want to save the strings file (the metadata file will have the same name as the strings file, but with a "-metadata" appended to it).
A strings file has the following structure:
language
The language code for this line.
When you export a strings file, this will be the Yarn project's base language.
id
The line ID for this line.
text
The text of the line, in the language indicated by the language
column.
file
The file that the line was originally found in.
node
The node that the line was originally found in.
lineNumber
The line number of the file that the line was originally found in.
lock
A unique value that Yarn Spinner uses to detect if the line has been modified since the strings file was generated. Don't modify or delete this value.
comment
A note indicating the intent and tone of the line. This can be useful for translators who may not have the same background or context for how the line should be delivered.
Once you've exported a strings file, you can translate it into another language: for each row in the database, change the language
column to the new language you're translating into, and the text
column to the translated text of the line.
Only the language
and text
columns should be modified by the translator. Don't modify the others; in particular, if you modify the value in the id
column, Yarn Spinner won't be able to link the translated line to the original version.
The metadata file contains the id
, file
, node
, and lineNumber
columns (which have the same values as in the strings file). Additionally, it contains a metadata
column with all the metadata of a line. Only lines that contain metadata will be present in this file. For more information on metadata, see Tags and Metadata.
Once you have a strings file that's been translated into your target language, you can add it to your Localisation. To do this, drag and drop the translated strings file into the Strings File property of your localisation, and click Apply.
Localised line assets are assets that are associated with a particular line, in a particular localisation. The most common example of this is voice-over lines, which are audio assets that are associated with each line.
Line Providers are responsible for fetching the appropriate assets for a given line and language. For example, the Builtin Localisation Line Provider fetches assets from a Yarn Project, and provides them to voice-over dialogue views.
The specific localised line, and localised line assets, that a line provider fetches depends on which language they have been configured to fetch.
The Text Line Provider has a single language option, which controls which language the line will appear in.
The Audio Line Provider has two language options: the language of the text, and the language of the audio files that are retrieved. This means that you can configure it to provide text in one language, and audio in another.
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 Dialogue Presenters.
The values of variables are stored and retrieved using the Dialogue Presenter's Variable Storage.
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 Line Provider.
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.
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 Complete
A Unity Event that's fired when the Dialogue Runner stops running dialogue.
On 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 .
Learn about the samples we provide for Yarn Spinner for Unity.
Yarn Spinner for Unity ships with a range of samples covering common patterns for things you might want to do in your game.
To install the Yarn Spinner for Unity Samples, you'll need to have a Unity project with Yarn Spinner for Unity installed, so head over to first.
Once Yarn Spinner for Unity is installed, you can install the Yarn Spinner for Unity Samples Package.
The best way to install the samples is to use the Samples button on the inspector of any Dialogue Runner, Yarn Project, or Yarn Script, or by choosing the Window menu -> Yarn Spinner -> and clicking Install Samples Package.
Clicking on this will work out the best way to install the samples depending on how you installed Yarn Spinner itself.
While the best way to install samples is the above, depending on your environment and device setup this might not be possible. In those circumstances you will need to manually install the samples, which is what this section is about.
How you manually install the Samples depends on how you installed Yarn Spinner:
The Yarn Spinner Samples are another Unity Asset Store asset.
This package is free but does have a dependancy on the main Yarn Spinner for Unity asset.
Open your browser and navigate to
Click on the "Add to My Assets" button.
Your browser should now offer a popup asking if you want to open the samples in Unity.
Click on the Open in Unity button.
The asset store will now bounce you back to Unity and start installing the samples.
The Yarn Spinner Samples are available on Itch as part of the Yarn Spinner for Unity asset.
Open your browser and navigation to https://yarnspinner.itch.io/yarn-spinner
At the top of the page you should see a little section telling you you own the asset and offering a "Download" button.
Click on the "Download" button and it will take you to the Download page.
Click on the "Download" button for the second bundle and save it some where convenient.
Open the downloaded unitypackage
and import the package into your Unity project.
Inside Unity open the package manager from the menu Windows -> Package Manager
In the top left corner press the +
button and from the dropdown that appears select Install package from Git URL
In the textfield that appears enter: https://github.com/YarnSpinnerTool/YarnSpinner-Unity-Samples.git
Unity will now install the samples project after a little while. Once it is done you can now browse and install the samples directly from the package manager.
Select the samples package in the middle column and click on the Samples tab in the main window.
And just like that you can now explore the samples!
Inside your browser navigate to the samples site: https://github.com/YarnSpinnerTool/YarnSpinner-Unity-Samples
Click on the Code button and from the dropdown select the Download Zip option
Save this zip somewhere useful and unzip it
This folder now contains the samples package and it's time to add it to your Unity project.
Back inside Unity open the package manager from the menu Windows -> Package Manager
In the top left corner press the +
button and from the dropdown that appears select Install package from disk
Navigate to where you downloaded and unzipped the samples and select the package.json
file inside that folder
Unity will now install the samples project after a little while. Once it is done you can now browse and install the samples directly from the package manager.
select the samples package in the middle column and click on the Samples tab in the main window.
And just like that you can now install and explore the samples!
Once the Yarn Spinner Samples package is installed in your project, you can install each individual Sample by opening the Unity Package Manager through the Window menu -> Package Manaing, locating the Yarn Spinner Samples package, and switching to the Samples tab:
To work with each individual Yarn Spinner for Unity Sample, click the Import button next to the Sample that you want to install, inside the Unity Package Manager. For example, to add the sample to the project, click its Import button:
You'll find the Sample in the Project pane:
You can open the Unity Scene (in this case Main
) to explore the sample.
- a small scene in which a character explains Yarn Spinner's inbuilt samples and what each of the others include.
- walks through the various major features of Yarn Spinner.
- demonstrates the creation of pools of lines or nodes which can be drawn from based on current game state to deliver dynamic, contextual content.
- demonstrates basic customisation of dialogue views with a custom font, view background, and continue button texture.
- demonstrates more elaborate customisation of dialogue views such that they are styled like a text message conversation on a phone screen. This includes how to add lines or options to the screen when a new line is received, as opposed to the default view which replaces the previous line or options.
- demonstrates creation of custom options view and behaviour such that the player is given only a limited amount of time to choose once dialogue options are presented.
- demonstrates localising a dialogue view
- demonstrates the use of multiple Dialogue Runners to allow different types of NPC background conversations simultaneously with each other or during primary dialogue.
- demonstrates the use of Action Markup to insert command-like triggers in the middle of dialogue line delivery.
- demonstrates the use of Replacement Markup to insert text styling or dynamic content into dialogue lines as they are presented.
- demonstrates the creation of a custom way to score and choose between content in node or line groups at runtime.
- demonstrates the use of node groups, line groups, and dynamic line content together to make a fully dynamic scene.
This example project demonstrates making a simple dialogue-based game when beginning with only an empty Unity scene.
Display Yarn dialogue in a Unity scene
Allow a player to select between options to respond
Add some static visuals
Yarn Spinner installed in Unity:
Yarn Spinner set up in a text editor:
Open a new Unity 3D project. Ensure Yarn Spinner has been added to the project in the Package Manager as per the .
If the sample empty scene is not visible, you'll need to open it. In the Project Window where project files are displayed, navigate to Assets > Scenes and select SampleScene.unity.
Yarn Spinner for Unity comes with a pre-made UI layer and accompanying utility scripts to handle displaying lines and presenting options from Yarn files. Add one by opening the GameObject menu, and choosing Yarn Spinner > Dialogue System.
When the Dialogue System in the scene is selected, the Inspector will display the Yarn Project it is expecting line from. Here, a Yarn Project is a kind of linking file that groups Yarn script files together.
To make one, navigate to a sensible place for the file to live (such as a new folder Assets > Dialogue) and right-click the Project Window pane to select Create > Yarn Spinner > Yarn Project.
Select the scene's Dialogue System again and drag the new Yarn Project into the labelled slot in the Inspector.
Now the Yarn Project needs one or more Yarn Scripts to get dialogue from. Just like with the Yarn Project, navigate to the desired file location and select Create > Yarn Spinner > Yarn Script. Name the new script Start, and place it in the same folder as the Yarn Project. This will make the Yarn Script be included in the Yarn Project.
By default, a new Yarn Script begins with a single node that has the same name as the file. It will contain a single line of placeholder dialogue.
To set this dialogue to run automatically when the game starts, select the Dialogue System again and in the Dialogue Runner section of the inspector check Start Automatically. The default start node name is called Start, which matches the node created earlier.
Now, pressing the ▶️ button in Unity should result in the test line being displayed in front of the empty scene world. Pressing the Continue arrow will make the UI disappear, as it has reached the end of the script.
So it's time for the actual writing part. Here, I've opened my new Yarn Script in Visual Studio Code with the Yarn Spinner Extension installed as per the . I've written a simple script about a conversation between the player and a capsule-shaped NPC named Capsley. Depending on how the player responds to their greeting, Mr Capsley will either be pleased to meet them or decide they are rude.
You can find this example script below to copy. Or if you need a refresher on how to represent your own story in Yarn, refer to the .
Once you've got a basic story, pop back into Unity and check the basics:
Once any desired visual assets have been added to the scene and the story has received any necessary fleshing out, the game is complete. If you've used this example to add dialogue to your own scene, you may skip ahead to . Otherwise, let's proceed!
For the shape example, let's add a "character" to the scene. Use Menu > GameObject > 3D Object to add a Capsule to the scene. Name him Capsley. For the camera to look at him properly, we'll want to set his transform position to x = 0, y = 1, z = -8
. You should now be looking at him up close.
All this grey in the skybox makes him difficult to distinguish though, and he doesn't look very friendly. Create a basic Materials for him by right-clicking the Project Window in the desired file location and select Create > Material. Change the colour of the Material to your preferred hue by modifying the Base map value in the Inspector.
Add the Material to each Capsley by selecting him in the Scene Heirarchy and dragging the new Material into the Materials > Element 0 in the Mesh Renderer part of the Inspector.
By creating a new material to be layered on top, we can give poor Capsley a face. Here, a PNG has been added to the Unity project and used as the Base map instead of a flat colour. By selecting Alpha clipping, it can be added to Capsley's Materials > Element 1 (you'll need to add this element) without removing his base colour.
A playable branching story game with simple static visuals.
An easy way to spice this up is to just add more dialogue or more characters. And it doesn't stop there! Yarn Spinner is perfect for allowing growing projects to remain functional throughout.
title: Start
---
This is an example of a Yarn Spinner script. Write your dialogue here!
===
title: Start
---
/// Whether Capsley like you or not. This starts true, but may change.
<<declare $capsley_likes_you = true as bool>>
/// The player's name. The player chooses this. It starts empty.
<<declare $player_name = "" as string>>
Capsley: Hello, I am Mr Capsley.
Capsley: Who are you then?
-> I'm Capsule, but my friends call me "Tic Tac". No idea why...
<<set $player_name to "Tic Tac">>
-> The name's Triquandle.
<<set $player_name to "Triquandle">>
-> Pyramid. Why - who wants to know?
<<set $player_name to "Pyramid">>
<<set $capsley_likes_you to false>>
<<if $capsley_likes_you>>
Capsley: Nice to meet you {$player_name}!
<<else>>
Capsley: No need to be so rude...
Capsley: Maybe you should be called Grumpy {$player_name}.
<<endif>>
===
Learn how to use markup in your Yarn Spinner Scripts, and respond to it in Unity by styling your narrative's text.
Yarn Spinner 3 introduced a new system for performing in-line text replacements using markers. Previously, text replacement based on markers was entirely handled by the view displaying the line. In Yarn Spinner 3, we've implemented a cleaner approach with IAttributeMarkerProcessor
and the related ReplacementMarkupHandler
.
This guide explores the implementation of this in our Replacement Markup Sample.
These components connect directly to the line provider, allowing replacements to occur before the line reaches the views. This simplifies your code architecture and creates a clearer separation of responsibilities. This sample demonstrates four different applications of replacement markup:
Built-in common replacements and marker palettes
Dynamic markup that changes with each presentation to obscure text
Named replacement for in-line content highlighting
Injecting sprites and additional text content into a line
Using Replacement Markup through IAttributeMarkerProcessor
and ReplacementMarkupHandler
Markup Palettes and the built-in replacement processors
Dynamically replacing text
Offsetting non-replacement markup attributes
TMP Sprites and adding elements to a line
The IAttributeMarkerProcessor
interface is the primary mechanism for creating replacement markup. At its core, replacement markup works by registering with the line provider to handle specific markup tags. During line presentation, when the dialogue runner requests a line from the line provider, the provider calls its registered IAttributeMarkerProcessor
instances to process the line.
Whenever a line provider encounters markup for which it has a registered processor, it calls the ProcessReplacementMarker
method on that processor. This method receives four parameters:
marker
: The marker being processed, containing position, range, and properties
childBuilder
: A StringBuilder containing the text content of all child nodes, which you'll typically modify by adding text to the beginning and end
childAttributes
: A list of markup inside your markup's children, which you can modify, offset, or even delete as part of your replacement
localeCode
: The code for the current locale in which the line will be presented
The method returns a list of LineParser.MarkupDiagnostic
objects, which identify any unresolvable errors in the markup. These diagnostics can be displayed in the editor, facilitating easier debugging of invalid markup.
For convenience, we provide an abstract class ReplacementMarkupHandler
, a MonoBehaviour that implements the IAttributeMarkerProcessor
interface. This class includes a static ReplacementMarkupHandler.NoDiagnostics
list for when your processor completes successfully without errors.
Understanding how markup processing works internally can be helpful when creating your own replacement markers. Consider this line in Yarn:
This is [b]my line[/b] with [b][i]some[/i] markup within[/b] of it.
We would expect three markers:
b
marker at position 8, with a range of 7
b
marker at position 21, with a range of 18
i
marker at position 21, with a range of 4
Yarn Spinner first models this as a tree, nesting the i
marker inside the second b
marker:
root
│
├─ "This is "
│
├─ b
│ └─ "my line"
│
├─ " with "
│
├─ b
│ ├─ i
│ │ └─ "some"
│ └─ "markup within"
│
└─ " of it."
This tree structure simplifies manipulation for replacements without manual index and range adjustments. If markup is incorrectly nested, we can rewrite the tree to maintain a valid structure while preserving the same ranges.
To flatten this tree while performing replacements, we traverse it depth-first. When we encounter a markup node with a registered replacement handler, we process it and continue to the next sibling. This ensures that when a replacement processor is called, all of its children have already been processed.
A key benefit of this approach is that each replacement processor can assume its indices start at 0. Since all children have been processed, and the root node handles final assembly and position offsetting, you don't need to know your exact position in the tree. You can trust that your children have been handled, and your siblings' positions will be managed later during final assembly.
As we traverse back up the tree, each completed branch has its siblings' text and markup attributes merged, creating a progressively flattened line.
Once the root node is processed, all replacements are complete and the tree structure becomes irrelevant. The root node simply appends all its children's text into a single line, merges all markup into a single list, and delivers it to the dialogue runner for presentation.
The sample scene includes four different examples, each NPC demonstrating a different aspect of replacement markup.
This example showcases the two built-in markup replacement systems - the only ones in this sample that don't require custom code. It uses markup palettes for basic line styling and the style replacement system for TextMeshPro (TMP) styles.
The Markup Palette provides a straightforward way to add styling to lines. It's a scriptable object that connects a marker name to styling information, allowing you to write Yarn like: Player: Hello, I want this to be [fancy]important looking[/fancy].
Which translates to TMP tags: Player: Hello, I want this to be <color=#00ff00><b>important looking</b></color>.
Markup Palettes support common styles like color, bold, and italics, or you can define custom start and end tags, including offset information for child attributes. You can create your own palettes from Assets -> Create -> Yarn Spinner -> Markup Palette
. The dialogue system prefab includes common tags like [b]
for bold, [u]
for underline, [i]
for italics, and [s]
for strikethrough.
The code that processes markup palettes is in PaletteMarkerProcessor.cs
. It works by creating the appropriate TMP rich text tags based on the palette, prepending opening tags to the childBuilder
parameter, and appending closing tags at the end.
The other built-in replacer handles TMP styles. This adds support for any styles defined in a TMP stylesheet. Since we don't know style names in advance, the Yarn syntax looks like: Player: Hello, I want this to be [style=h1]important looking[/style].
The code in StyleMarkerProcessor.cs
translates this into appropriate <style>
TMP tags.
This example demonstrates using markup with variables to dynamically obscure text. Each time you talk to the character, the amount of obscured text decreases until you can understand the entire line. The obscurity level changes via a Yarn variable interpolated into the markup:
Bob: [obscurity = {$obscurity}]Why hello there, it's nice to meet you friend.[/obscurity]
The implementation is in ObscurityMarkupProcessor.cs
, which randomly replaces characters with punctuation symbols based on the obscurity level. The processor retrieves the current level from the marker:
marker.TryGetProperty("obscurity", out int value)
In this sample, we defined four obscurity levels: fully obscured, two-thirds obscured, one-quarter obscured, and no obscuration. The code checks if the value is 0
, 1
, or 2
, with any other value resulting in no obscuration.
The actual obscuring happens in the Obscure
method, which:
Collects the index of every non-whitespace character
Shuffles these indices (so different characters are revealed as obscurity decreases)
Uses the obscurity factor to determine which characters to replace with random punctuation
This example demonstrates replacement markup where the replacement is based on the child text rather than the markup itself. The processor reads the child text inside a marker and uses it to determine what color to apply to that section.
The design philosophy is that the writer has already included the context for highlighting - the character's name. This allows writing:
Player: Hey there [name]Alice[/name], what up?
Instead of needing to specify:
Player: Hey there [name=alice]Alice[/name], what up?
Of course, there are cases where an explicit property is needed:
Player: I think [name=alice]she's[/name] just being nice [name]Bob[/name]!
The implementation is in EntityColourer.cs
. The processor handles both cases by first checking for an explicit property:
marker.TryGetProperty("name", out string value)
If none exists, it uses the child text content:
childBuilder.ToString().ToLower()
With the name determined, it performs a lookup in a name-to-color mapping and applies TMP color tags:
childBuilder.Insert(0, $"<color=#{UnityEngine.ColorUtility.ToHtmlStringRGBA(entity.colour)}><b>");
childBuilder.Append("</b></color>");
This example shows how to add new text and elements (specifically sprites) to a line. Marked regions are flagged as a specific type, gaining both a sprite and color when processed:
Liz: well it is called a [fire]flame[/fire]thrower so I think you can work that one out yourself.
The implementation in SpriteReplacmentMarkerProcessor.cs
is quite straightforward. It defines format strings for TMP rich tags:
var start = "<b>[<color=#{0}><sprite=\"effects\" name=\"{1}\">";
var end = "</color>]</b>";
It then uses a switch statement on the marker name to fill in the format strings:
case "fire":
childBuilder.Insert(0, string.Format(start, ColorUtility.ToHtmlStringRGB(debuff), "fire"));
childBuilder.Append(end);
break;
Since this adds visible elements at the start of the markup text (the sprite and [
character), the processor must offset any attributes within that markup:
for (int i = 0; i < childAttributes.Count; i++)
{
childAttributes[i] = childAttributes[i].Shift(2);
}
This shifts attributes down by two positions to accommodate the added characters.
Learn how to use the Automatic-Layout Dialogue Wheel, from the Dialogue Wheel for Yarn Spinner Add-On Package.
The Automatic-Layout Dialogue Wheel provides a dialogue wheel with a simple graphical appearance, and can—theoretically—support as many options as you'd like, automatically adjusting to display them.
The Automatic-Layout Dialogue Wheel works by diving up a circle into a number of regions. The number of regions is determined on-the-fly by the number of options that need to be displayed, the size of each region is determined by diving up the available space on the circle evenly by the number of options, so if you have three options each region will be 120 degrees wide. Each region then gets given a single option to show and that option is placed within the centre edge of the region, coiling around circle in a counter-clockwise fashion. The wheel can also have a deadzone set which is a section of the circle where options can't appear, this is mostly to ensure wheels near the bottom of the screen don't have their options cut off, but can be used for other effects. The deadzone is determined by a start and end angle, anything within this range is a valid spot for options. Anything outside of it, is not. The range inside the example is a start of -30 and and end of 210 degrees, meaning a wedge at the bottom is not available for options. The deadzone angles, the coiling direction, and angle offset of each region can be controlled and customised.
To use the Automatic-Layout Dialogue Wheel , and the .
Then, create a new Dialogue System in your Hierarchy:
Then, in the Project pane, create a new Yarn Project asset:
And create a new Yarn Script to use:
Open the Yarn script to write your story. Then save the Yarn script and return to Unity.
We've provided an initial sample story here, if you want to test things out.
With the Dialogue Runner selected in the Hierarchy, drag the Yarn Project that you created from the Project pane into the Yarn Project slot in the Dialogue System's Inspector:
Next, locate the Automatic-Layout Dialogue Wheel prefab, supplied with this add-on, and drag it from the Project pane, so it's below the Canvas in the Hierarchy:
Right-click on the Options Presenter in the Hierarchy, and choose Delete. You won't need that presenter, as you'll be displaying a wheel, instead of a list. To make the Dialogue System aware of the Automatic-Layout Dialogue Wheel, select it (the Dialogue System) in the Hierarchy, and drag the Automatic-Layout Dialogue View from the Hierarchy into the Element 2 slot of the Dialogue Presenters section in the Inspector:
Click on the Start Automatically toggle on the Dialogue Runner Inspector and from the Start Node drop down pick Start
.
If you save your scene and run it, your Automatic-Layout Dialogue Wheel should now be working!
There are three commands which let you adjust how the wheel works during dialogue. To see these in action check out the .
<<set_layout_direction wheel-name direction>>
has two parameters, the name of the wheel game object, and the direction. This determines which way around the wheel options should be placed, either clockwise or counter-clockwise. Allowed values for the direction
parameter to place options clockwise include: clockwise
, sunwise
, cw
. Allowed values for the direction
parameter to place options counter-clockwise include: counterclockwise
, counter-clockwise
, widdershins
, ccw
.
<<set_option_zone wheel-name start-angle end-angle>>
has three parameters, the name of the wheel game object, and two angles for the start and end region. If you want to have the entire circle you would set start-angle
to 0
and end-angle
to 360
, if you only wanted the top half of the wheel you would set them to 0
and 180
respectively. For the wheel in the example the wheel can place options from -30 to 210 degrees, leaving a wedge at the bottom where options can't be placed.
The wheel divides up it's available space (so 360 degrees unless a deadzone is set) evenly based on how many options there are. If we have 180 degrees available to place options and we have three options that means each option gets 60 degrees to be placed and will be placed in the centre of that 60 degree wedge. Meaning we'd have one option at 30 degrees (top right), another at 90 (center), and the third at 150 degrees (top left). You can offset the angle to change this behaviour.
<<set_offset_angle wheel-name angle>>
has two parameters, the name of the wheel game object, and the offset angle. Changing this will rotate the option regions by the angle
given.
To customise the supplied wheel, select the Wheel Graphic in the Hierarchy:
And look to the Inspector:
Here, on the Wheel Graphic component, you can customise a variety of things, such as the material, color, inner radius, and density of the wheel. For example, if you set the Density field to 6, as we did above, your wheel will become a hexagon:
To customise the way each option is displayed around the wheel, select the Automatic Wheel Option View under Automatic-Layout Dialogue Wheel prefab:
And look to the Inspector:
You can tweak some of the fields here to customise the options that are displayed around the wheel. Specifically, you might want to customise the following.
On the Automatic Wheel Option View:
Normal Color — the color of each option that's not currently highlighted or disabled
Disabled Color — the color of each option that's displayed, but cannot be chosen
Highlighted Color — the color of the option that's currently selected (but not yet chosen)
Text/Graphic Highlight Mode — whether each option is immediately highlighted when it's selected, or whether they crossfade between
Crossfade Duration — the duration of the crossfade, if crossfading
On the Wheel Option Graphic:
Material — the Material used for circle graphic displayed near each option
Width — the size of the circle graphic displayed near each option
For example, if you wanted to highlight selected options in green, and crossfade between them, you could set the following:
Which would result in something like this:
The Inspector for the wheel has an in-editor visualiser to help you see where options will end up placed around the circle based on your settings. Here you can control the number of regions being previewed, see the effect of changing the start and end angles for the deadzone, and change the region offset angles.
If we wanted the entire wheel to be in use we could change the start and end angles.
If we were curious to see the regions if we had five options.
Or perhaps we wanted the first one at the top, so change the offset angle for the regions.
And that's everything you need to know to use the Automatic-Layout Dialogue Wheel! Review the for more.
title: Start
---
Narrator: What brings to the pool?
-> Cleaning
I have come to clean the pool.
Narrator: Ah, just as I thought.
-> I'm a pool cleaner
Narrator: I know.
<<jump End>>
-> I was actually lying.
Narrator: Oh, I see.
<<jump End>>
-> Treasure
I am looking for the lost treasure of... the pool.
Narrator: There is no treasure in the pool.
-> WHAT!?
WHAT?! WHY NOT? I WAS TOLD THERE WAS TREASURE HERE!
Narrator: Nope.
<<jump End>>
-> Oh, okay.
Oh, well, I guess I'll go.
Narrator: OK, bye!
<<jump End>>
-> I know.
I know, I just wanted a swim.
Narrator: In you get, then!
<<jump End>>
-> No reason
I have a fetish for pool cleaning equipment.
Narrator: Whatever floats your boat...
-> Thanks.
Narrator: Uh uh.
<<jump End>>
-> Commerce
I'd like to buy a pool.
Narrator: Well, it's not for sale.
Narrator: Go away.
<<jump End>>
-> Swimming
I'm here to go for a swim.
Narrator: Well, you can't.
<<jump End>>
===
title: End
---
Narrator: Anyway...
Narrator: Have a nice day!
<<stop>>
===
Learn the principles behind our storylets and saliency features.
When you do things right, people won't be sure you've done anything at all. - Futurama
Storylets and saliency represent an evolution in interactive storytelling techniques. To understand their significance, let's examine how narrative approaches have developed over time.
The earliest approach to storytelling was linear. Stories followed a single, author-defined path with each moment crafted to lead directly to the next.
The next evolution introduced branching, allowing stories to have multiple paths for players to explore.
Branching narratives offer remarkable flexibility. Some branches might be extensive with significant impact, while others could be minor, adding subtle flavor. Some paths might terminate, while others reconnect with the main storyline. Despite this flexibility, each step remains author-crafted, with branches fixed in the writer's intended order.
Storylets represent the next step in this evolution. They break the rigid connections between narrative elements, creating a more dynamic flow. Theoretically, a story could move from any storylet to any other storylet, creating a potential connection from every block to every other block.
If narrative chunks are disconnected, how do they form a coherent experience? This is where saliency comes in—it's the mechanism that links story pieces together.
Saliency determines which storylet is most relevant (or "salient") at any given moment. Instead of an author-defined sequence, saliency selects the ordering dynamically. For example, if you have two storylets—one about entering any room in an apartment, and another specifically about entering the bedroom—when the player is in the bedroom, the more specific storylet would be more salient.
Interestingly, linear and branching narratives can be viewed as salient stories composed of storylets, where the most salient storylet is defined by the author's explicit connections. This perspective highlights a major strength of storylets: they can incorporate and blend previous structural approaches. This gives you precise control when needed while maintaining flexibility for dynamic narrative progression.
The storylet is the fundamental unit of this narrative system, but how large or small should a storylet be? The answer depends on your specific narrative needs.
Storylets typically exist at three levels of granularity:
Collection Level: The largest unit—a vignette, conversation, or paragraph. At this level, your story assembles by connecting self-contained moments. These moments generally function independently, so their precise order may be flexible, or they might be designed to flow naturally with a subset of other moments.
Line Level: Here, a storylet represents a single line of dialogue. Conversations build by assembling individual lines, which then form larger narrative moments. This approach is particularly effective for creating dynamic NPC barks in games.
Sub-Line Level: The smallest unit, where storylets are fragments within a single line. These pieces combine to form complete lines of dialogue, which then assemble into conversations.
Many games blend these approaches, often mixing storylet techniques with traditional linear or branching content. Rather than focusing exclusively on storylet size, consider what type of narrative experience you want to create and determine what level of storylet granularity will best support that vision.
How do we determine which storylet is "best" to present next? The approach varies based on your narrative goals.
The most common approach uses conditions on each storylet—boolean expressions like "are we in the bedroom?" or "does the player have more than 25 gold coins?" These conditions filter available storylets and help select among the remaining options. When multiple storylets are valid, you select the one with the highest "quality"—typically the one that satisfies the most conditions.
Another approach tags storylets with various attributes. When selecting the next storylet, you filter based on relevant tags. The filtering criteria might come directly from player choices or from systems guiding the narrative.
Some approaches aim to guide the narrative toward specific outcomes:
Pathfinding: Storylets exist on a graph, and the system attempts to progress from the current storylet toward a specific destination, influenced by player choices along the way.
Drama Management: A system with narrative goals selects storylets to work toward those goals. For example, Façade selects storylets to maximize conversational drama. Pathfinding can be viewed as drama management where the goal is reaching a specific point in the narrative graph.
Two less complex but still useful approaches include:
Player Selection: Present all available storylets and let the player choose.
Random Selection: Choose randomly from available storylets.
These methods often incorporate knockout or deprioritization systems to prevent repetition. They're also effective for resolving ambiguity when multiple storylets have equal saliency.
Storylets serve various narrative functions in games:
Perhaps the most common use is creating contextual NPC comments. Skyrim's infamous "arrow to the knee" line exemplifies this—triggered when the player has cleared a dungeon, a condition most players eventually satisfy.
Storylets create specific moments that add life to the game world without connecting directly to the main plot. Mass Effect's elevator conversations, filtered based on your current squad members, demonstrate this approach.
Storylets can create highly specific dialogue responding to gameplay moments, making NPCs seem more aware and reactive. Left 4 Dead's combat and downtime barks showcase this, as do Ellie's contextual reactions in The Last of Us.
In games without predetermined stories, storylets create meaningful moments and side-narratives. Civilization VII uses storylets for historical and alternative historical events, triggered by factors like civilization type, leaders, and current player actions.
Database-driven games use storylets to let players explore narrative space freely. Her Story exemplifies this approach, with self-contained story chunks that both stand alone and contribute to an overall narrative.
Some games build entire narratives from storylets. Mask of a Rose and Wildermyth assemble stories entirely from storylets based on player actions, allowing players to create their own unique narratives.
Yarn Spinner supports two primary levels of storylet granularity: line-based and node-based. Both integrate seamlessly with traditional branching and linear Yarn scripts.
Line Groups implement line-level storylets. They visually resemble option groups but use =>
instead of ->
. Each line within a group represents a potential storylet, but only one will be selected.
Barry: Oh is that so?
=> Alice: Yep.
=> Alice: Of course it is!
=> Alice: Why would you think otherwise?
The dialogue system receives the Barry line, then one selected line from Alice. The player might see:
Barry: Oh is that so?
Alice: Of course it is!
The system doesn't indicate that the Alice line came from a storylet selection—it's simply presented as the next line of dialogue.
We can add conditions to modify selection:
Barry: Oh is that so?
=> Alice: Yep.
=> Alice: Of course it is! <<if $barry_suspicion > 3>>
=> Alice: Why would you think otherwise? <<if $barry_suspicion > 5>>
=> Alice: I am not talking without my lawyer <<if $barry_suspicion > 5 && $knows_barry_is_cop>>
Assuming high Barry suspicion and knowledge that he's a cop, the dialogue might present:
Barry: Oh is that so?
Alice: I am not talking without my lawyer
The final line is selected as the most salient option, even though multiple options satisfy their conditions.
Like option groups, line groups support nested content that only appears if that specific line is chosen:
Barry: Oh is that so?
=> Alice: Yep.
Barry: lol lmao, thanks nerd.
=> Alice: Of course it is! <<if $barry_suspicion > 3>>
=> Alice: Why would you think otherwise? <<if $barry_suspicion > 5>>
=> Alice: I am not talking without my lawyer <<if $barry_suspicion > 5 && $knows_barry_is_cop>>
Barry: Why would you need a lawyer?
Alice: *cool silence*
Any Yarn dialogue or commands can be nested, including other storylets, jumps, and detours.
Node groups allow larger storylet chunks to be associated. They look like standard nodes but require a when
header:
title: Barry
---
Barry: Oh is that so?
<<jump Alice>>
===
title: Alice
when: always
---
Alice: Yep.
Barry: lol lmao, thanks nerd.
===
title: Alice
when: $barry_suspicion > 3
---
Alice: Of course it is!
===
title: Alice
when: $barry_suspicion > 5
---
Alice: Why would you think otherwise?
===
title: Alice
when: $barry_suspicion > 5
when: $knows_barry_is_cop
---
Alice: I am not talking without my lawyer
Barry: Why would you need a lawyer?
Alice: *cool silence*
===
When <<jump Alice>>
executes, it jumps to the most salient node in the "Alice" group. If Barry is highly suspicious and Alice knows he's a cop, the system selects the last node.
For this simple example, node groups require more writing than line groups for the same effect. However, for larger conversations with significant variability, node groups become more advantageous.
Like line groups, node groups can contain any valid Yarn content, including jumps, detours, and line groups.
The dialogue runner consults a saliency strategy when presenting a storylet, whether from a line group or node group. While you can create custom strategies to implement any approach, Yarn Spinner provides several built-in quality-based strategies:
Best (select the most salient)
Best Least Recently Viewed (prioritize salient content that hasn't been seen recently)
Random Best Least Recently Viewed (like above, with some randomization)
Random (completely random selection)
All built-in strategies first filter out storylets with failing conditions, though custom strategies can include failing conditions if desired.
The recommended default is Random Best Least Recently Viewed, which balances selection of the highest-saliency content while preventing repetition.
Learn how to use Yarn Spinner for Visual Studio Code as your Yarn editor.
Now that you've got Visual Studio Code and Yarn Spinner for Visual Studio Code installed, it's time to learn how to use it to write Yarn Spinner Scripts.
Yarn Spinner for Visual Studio Code is designed to work with a folder, not single files.
You should always work on a project-by-project basis with folders containing your .yarn
file or files in it, even if your narrative only has one .yarn
file, put it in a folder of its own.
If you're working on Yarn Spinner Scripts that are part of a game—for example a Unity game—you're likely to have a folder for your narrative/dialogue within the project structure.
If you're using VS Code as your C# editing environment with Unity, you can reach your Yarn Spinner scripts by choosing the Assets menu -> Open C# Project from within Unity, and then navigating to the appropriate folder within VS Code to work with your Yarn Spinner Scripts.
If you're not using VS Code as your C# editing environment with Unity, you can still open the Narrative folder with VS Code to work with your Yarn Spinner Scripts.
For this example, we'll assume you have a folder that you want to use to work on your story. The folder in our example is called YSDocsDemo
. Inside this folder, we've made a single Yarn Spinner Script named Chat.yarn
.
You can also open an empty folder, and make the Yarn Spinner Scripts within VS Code.
If we open VS Code, the default screen will have an Open button right in the middle.
Click this button, or choose the File menu -> Open Folder..., and then open the folder containing your .yarn
file:
When the folder opens, you'll see the sidebar of VS Code change to reflect the contents of the folder. You can click on a .yarn
file to open it in the text editor:
You can also use the File menu to make a new file inside the folder:
VS Code will then ask you to name your new file:
With a .yarn
file open in VS Code, you can verify that the Yarn Spinner for Visual Studio Code Extension is active by looking in the bottom right-hand corner of the screen, and locating the words "Yarn Spinner":
You can use the text editing view to work with .yarn
, and to write your narratives. The Yarn Spinner for Visual Studio Code extension provides all sorts of features to make this process easier.
For example, if you hold the Command key (on macOS) or the Control key (on Windows or Linux) and hover over names of nodes in, for example, `<<jump>>
statements, you'll be able click on them to move the editor view to the Yarn that represents that node:
You'll also be offered autocomplete suggestions based on node names that exist in your project. For example, if you create a new <<jump>>
statement, you'll be able to pick from your nodes:
If your Yarn Spinner Scripts also use variables, which you'll learn about shortly, Yarn Spinner for VS Code will help out as well. For example, when you <<declare>>
a new variable, you can add a comment with three /
in front of it to provide a description of the variable:
Then, when you use the variable, you can hover over it in VS Code for a reminder of its purpose (and its default value):
Variable names will also autocomplete when you try and use them, and errors will be show if there are type isues. So, if you <<declare>>
a variable to be a certain type, for example a boolean
:
... and then attempt to use that variable in a way that would produce an error. For example, by attempting to assign a number to it, then Yarn Spinner for Visual Studio Code will show an error:
You'll also be able to see documentation comments from commands defined in your game's C# source code:
Nodes can be spread across as many .yarn
files in a single folder as you like.
While Yarn Spinner is a text based language, our Yarn Spinner for Visual Studio Code extension provides a handy Graph View.
You can open the Graph View for whichever .yarn
file you're currently working with by clicking the Graph View button in the top right-hand corner:
You might notice that, when you first look at the Graph View for a .yarn
file, all the nodes appeared stacked on each other, like this:
To make sense of things, and better understand the <<jump>>
use between nodes, you can rearrange the nodes by clicking and dragging to wherever you want them:
Any time the <<jump>>
command is used, it will be visualised as a line with an arrow, leading to the note that's being jumped to. If you <<jump>>
to a node in another .yarn
file, it will be visualised as a line leading to a small circle:
The position of the nodes will be stored in each node's header:
You can use the Add Node button, found at the top of the Graph View, to add new nodes. New nodes will appear in the Graph View, and in the text editor:
Double-clicking a node in the Graph View will jump to that node in the Text View:
If you have a lot of nodes, you can use the Jump to Node menu, in the top right-hand corner of the Graph View, to jump the Graph View to a specific node:
At any point you can also click Show in Graph View, found above each node in the Text View to jump the Graph View to it:
You can add some additional metadata to the headers of each node to customise your Graph View, for ease of understanding the relationships between areas of your script.
For example, if you add the color
field to the header, you can colour-code your nodes:
The color
field works like any other header element, and goes below the title
and above the ---
:
You can also group your nodes by adding the group
field to your node headers. For example, if you add group: Main_Options
to the header of the Volcanos
, Dogs
, and Trees
nodes, you'd end up with this:
You can also use the Add Sticky Note button, found at the top of the Graph View, to add Sticky Notes to your Yarn Spinner Scripts:
Sticky Notes are just like normal nodes, but styled to stand out, so you can use them to leave notes for yourself, or your team, to add context to your narratives:
Because Sticky Nodes are just normal nodes with an extra header field (they become a Sticky Note when you add style: note
to their header), you can use the color:
field to set their colour. For example, the notes in the above screenshot are created with the following Yarn Spinner Script:
The VS Code Command Palette has a number of useful Yarn Spinner features as well. Summon the Command Palette by pressing Shift + Command + P (Mac) or Ctrl + Shift + P (Windows/Linux), or choosing the View menu -> Command Palette..., and type "Yarn Spinner" to filter the available commands to those provided by the Yarn Spinner for Visual Studio Code Extension:
From here you can Preview Dialogue, which will allow you to play through your narrative, right inside Visual Studio Code. To learn more about this, read .
The Export Dialogue as HTML... option will export a self-contained playable version of your narrative as an HTML file, which is otherwise the same as the experience your get when previewing.
The Export Dialogue as Graph... option will allow you to export a .dot
file of your graph. To learn about .dot
files, check out the GraphViz documentation:
And finally, the Export Dialogue as Recording Spreadsheet... option will allow you to export a spreadsheet, which can be useful for voice actors recording dialogue.
Learn about storing data using variables in Yarn Spinner Scripts.
Sometimes it makes sense for the options presented or the outcomes of selecting different options to vary based on other things the player has done or said up until this point. This requires the use of logic and variables, which we'll discuss in this section.
Yarn Spinner Script is a full programming language, which means it has support for writing code that let you control how the dialogue in your game works. In this section, you'll learn how to use variables to control your dialogue.
Variables store information. Variables can store one of three types of information: numbers, strings, and booleans.
Every variable has a name. In Yarn Spinner, all variable names start with a dollar sign ($
).
Declaring a variable means telling Yarn Spinner that a variable exists, what it's meant to be used for, and what initial value it has.
You should always declare a variable before you first use it.
To declare a variable, you use the <<declare>>
command:
If you use a variable without declaring it, Yarn Spinner will try to figure out what type it should have based on how it's being used in your scripts, as well as what initial value it should have - zero for numbers, false for booleans, and blank text for strings. When a variable is not declared, we call that an implicit declaration.
If you declare a variable, you can make sure that the type of the variable is what you intend it to be. Declaring a variable also lets you control what the variable's initial value is, and lets you add descriptive comments that explain the purpose of the variable to other people (or to your future self!)
You may find it useful to create a special node (for example, titled Setup
, or similar) that is used solely to <<declare>>
all the variables your narrative uses. You don't need to run this node for the declarations to take effect.
You put information into a variable by using the <<set>>
command. For example, the following Yarn Spinner Script puts a string, "Hello, Yarn!"
, into a variable called $greeting
:
As with node titles, variable names must not contain spaces. While they can contain a range of different characters the first character must be a letter. In general your variables will be made up of only letters, numbers and underscores.
Variables in Yarn Spinner can store one of three types of information: numbers, strings, and booleans:
Each variable can only store one type of value. Variables can change their value at any time, but they can never change their type.
For example, the following Yarn Spinner Script will work:
This works because while the value of each of the variable changes, the type doesn't change.
However, the following Yarn Spinner Script will not work:
This will not work because when they are declared, $myCoolNumber
is set to a number, and $myFantasticString
is set to a string, so they can only ever store that type of information.
Variables cannot be empty. All variables are required to have a value.
You can work with the values stored in variables.
For example, numbers can be multiplied, strings can be combined, and boolean values can have logical operations (like and and or) applied to them. When values are used together like this, it's called an expression:
An expression needs to be a single type. You can't work with values of different types in a single expression.
For example, the following code will not work:
Yarn Spinner provides built-in functions for converting between certain types:
The string()
function converts values of any type into a string.
The number()
function converts values of any type into a number (if it can be interpreted as one.)
The bool()
function converts values of any type into a boolean value (if it can be interpreted as one.)
These functions work within Yarn Spinner Scripts. They all work by passing in, between the (
and )
, a value of any time, either directly or by referring to another variable, and they return the same value, converted to the appropriate type.
For example, consider the following:
In this snippet, we declare the variable $aNumber
and assign it the default value of 42
, so $aNumber
will always store a number, and we declare the variable $aString
and assign it the default value of "This is a string"
, so $aString
will always store a string.
Then, we use the <<set>>
command to upate the value stored in $aString
, and assign it the value of $aNumber
(which is a number, and can't be assigned directly to a string) converted to a string using the string()
function. Thus, $aString
will then contain the string "42"
.
Yarn Spinner supports the following logical operators. Most of these have multiple ways being written:
Equality: eq
or is
or ==
Inequality: neq
or !
Greater than: gt
or >
Less than: lt
or <
Less than or equal to: lte
or <=
Greater than or equal to: gte
or >=
Boolean 'or'': or
or ||
Boolean 'xor': xor
or ^
Boolean 'not': not
or !
Boolean 'and': and
or &&
Yarn Spinner also supports the following maths operators:
Addition: +
Subtraction: -
Multiplication: *
Division: /
Truncating Remainder Division: %
Brackets: (
to open the brackets and )
to close them.
Yarn Spinner follows a fairly standard order of operations, and falls back to using left to right when operators are of equivalent priority.
The order of operations is as follows:
Brackets
Boolean Negation
Multiplication, Division, and Truncating Remainder Division
Addition, Subtraction
Less than or equals, Greater than or equals, Less than, Greater than
Equality, Inequality
Boolean AND, Boolean OR, Boolean XOR
To show the contents of a variable, you put it inside braces ({ }
) inside a line. The value of that variable will appear in its place. For example:
Learn how to visually theme one of our provided Dialogue Presenters.
The default Dialogue Presenters that come with Yarn Spinner have their own specific look, but they're designed to be re-themed without requiring any custom code.
The Theming Default Presenters sample and guide demonstrates how to modify the built-in line and options presenters to use a sliced sprite and different font for a complete visual overhaul—all without writing any code.
Modifying existing dialogue presenters
Slicing sprites via the sprite editor
Importing custom fonts into TextMeshPro
The nightmare hellscape that is Unity UI
This guide walks you through the creation of a project we provide, completed, as a sample.
If you haven't already installed Yarn Spinner, follow the instructions at . Once installed, we'll start by building out a basic scene:
Make a new scene in Unity.
From Samples/Shared Assets/Prefabs
drag the Basic Arena into the scene
From Samples/Shared Assets/Prefabs
add a Camera Rig into the scene
From Samples/Shared Assets/Prefabs
drag a Player prefab into the scene
From Samples/Shared Assets/Prefabs
drag an NPC prefab into the scene and rename it to be Alice
Add a default dialogue system to the scene, in the Hierarchy right click Yarn Spinner -> Dialogue Runner
Create a new Yarn Spinner project Assets -> Create -> Yarn Spinner -> Yarn Project
and name it Rethemed Dialogue
Create a new Yarn script Assets -> Create -> Yarn Spinner -> Yarn Script
and name it Alice
Delete the previous camera called Main Camera
Replace the contents of the Alice Yarn script with the following:
This is a very simple node with just a few lines of dialogue and a single option. All we need is to show off the main features of lines and options, so feel free to change it, but for now this is sufficient.
Select the Camera Rig and in the inspector drag the Player into the target field
Select Alice and in the Inspector set the Dialogue field to use the new Rethemed Dialogue
project
Select Alice and in the Inspector select the Alice node in the dropdown
Select Alice and in the Dialogue Runner field drag in the Dialogue System object from the hierarchy
Select the Dialogue System and in the Inspector set the Yarn Project to use the Rethemed Dialogue
project
Arguably the easiest but most impactful change you can make is to use a different font. This will immediately make the UI look different with minimal effort. Selecting a font is a very important stylistic choice for your game, as it will influence everything about how you design the rest of your UI.
For this sample, we've chosen a pixel font that works well with our desired aesthetic. At this stage, we're just adding the font to the project; we'll connect it to the various presenters later.
Grab the PixelifySans font from LINK TO FONT
Add the font into the Project assets
Go to Window -> TextMeshPro -> Font Asset Creator
Drag the PixelifySans-Bold
font into the Source Font field
Click Generate Font Asset
Click Save
and save the TMP version of the font into your project
Repeat these steps for the PixelifySans-Regular
font
With this done, our fonts are ready to be used. We'll return to them when we start making layout changes, but first we also need some sprites.
Our retheming will rely on a sprite sheet that we'll slice. Let's set that up now. We'll need the Sprite Editor for slicing, which you might already have installed.
Open the Package Manager by going to Window -> Package Manager
In the side bar select the Unity Registry
option
Wait for Unity to populate the manager
Find the package called 2D Sprite
Install it
Add the sprite sheet into the project from link to sprite sheet
Select the sprite sheet and in the Inspector change it's Texture Type to be Sprite (2D and UI)
Set the sprite mode to be Multiple
Set the Pixels per Unit to be 20
Set the Filter Mode to be Point (no filter)
Click Apply
Select the sprite sheet and in the Inspector click on the Open Sprite Editor
button
Now we need to slice the sprite sheet into individual sprites and define the regions. Without doing this, the sprites would stretch in undesirable ways and look distorted. We need to identify and slice eight different sprites, which is somewhat tedious to describe, but thankfully the Sprite Editor makes the process relatively straightforward.
In the top bar of the editor, in the Slice dropdown configure it to be an Automatic
slice with a Center
pivot and click the Slice
button
This does most of the work for us, using the transparent space in the sprite sheet to identify the eight different sprites. Now that we have the individual sprites identified, we need to define which regions can stretch and which cannot. Without this step, the sprites will scale oddly.
Select the left-topmost sprite
Name it Button-Filled-Up
Set it's L, R, T, and B to all be 7
Select the sprite on the right of the Button-Filled-Up
sprite
Name it Button-Empty-Up
Set it's L, R, T, and B to all be 7
Select the sprite on the right of the Button-Empty-Up
sprite
Name it Button-Continue-Up
Set it's L, R, T, and B to all be 7
Select the sprite on the right of the Button-Continue-Up
sprite
Name it Option-Selected
Select the sprite below the Button-Filled-Up
sprite
Name it Button-Filled-Down
Set it's L, R, T, and B to all be 7
Select the sprite on the right of the Button-Filled-Down
sprite
Name it Button-Empty-Down
Set it's L, R, T, and B to all be 7
Select the sprite on the right of the Button-Empty-Down
sprite
Name it Button-Continue-Down
Select the bottom-leftmost sprite
Name it Background
Set it's L, R, T, and B to all be 16
Click the Apply button
Now all our sprites are neatly sliced and ready to use. This was quite a bit of slicing, but fortunately most sprites use the same slice settings. If you don't want to do all this work or made a mistake, you can grab the pre-sliced version from the Sample folder. What we've accomplished here is defining which parts of each sprite can stretch and which parts should remain fixed.
Next, we'll modify the Line Presenter to use our custom sprites instead of the default visuals.
Select the Dialogue System prefab in the Hierarchy
Expand out the Canvas -> Line View
and select the Background
gameobject
In the Image
component of the Inspector change the Source Image to be our freshly sliced Background
sprite
In the Image
component of the Inspector change the Color to be fully opaque white
In the Image
component of the Inspector change the Image Type dropdown to be Sliced
In the Hierarchy select the Line View game object
In the Vertical Layout Group component set the Left and Right padding to be 40
, the Top to be 24
, The bottom to be 115
and set the Spacing to be 20
Now we need to make the line presenter use our custom font.
Select the Text Mesh Pro field inside of Dialogue System -> Canvas -> Line View -> Character Name
In the Inspector on the Text Mesh Pro component change the Font Asset field to point to the Pixelify-Sans font
Set the font size to be 48
Select the Text Mesh Pro field inside of Dialogue System -> Canvas -> Line View -> Text
In the Inspector on the Text Mesh Pro component change the Font Asset field to point to the Pixelify-Sans font
Set the font size to be 50
Expand the Extra Settings section and find the Margins
Set the margins to be Left 20
, Top 0
, Right 20
, and Bottom 0
With that, our line presenter is finished! We can test it to see how it looks in action.
We're halfway there! Now we need to apply similar changes to our options presenter. This has a slight additional step—we'll need to make a prefab for our option items—but otherwise the process is similar to what we did for the line presenter. Let's start by creating the options item prefab, which requires the most work.
Right click on the Options Presenter Dialogue Systemm -> Canvas -> Options View
and add a new UI -> Text - TextMeshPro
child game object
Rename it to be Option Item
Select the Option Item and in the Inspector find the Text Mesh Pro component
Change the Font Asset to point to Pixelify-Sans font
Set the Font Size to be 50
Expand out the Extra Settings
Set the margins to be Left 30
, and Top, Right and Bottom to all be 0
Add a new Image child game object to the Option Item
Rename it to be called Selection Indicator
Make it so it is a full height stretch but with a fixed width of 20
Select the Options Item in the Hierarchy
Add an Options Item component onto the game object
In the Text field drag the Option Item game object in
In the Selection Image field drag in the Selection Indicator game object
Expand out the Selected section of the inspector
Set the Selected sprite to be the Option-Selected
sprite
Drag the Option Item game object out of the Hierarchy into the Assets to make it a prefab
Delete the existing Option Item game object, it has served it's purpose
With that done, our final steps are to modify the Options Presenter to use our new Option Item.
Select the Options View game object in the Hierarchy
In the Inspector find the Options View Prefab field inside the Options Presenter component
Replace the existing prefab with our Option Item prefab we made above
Select the Background child game object of the option presenter
In the Inspector find the Image component
Set the Source Image to be our Background
sprite
Set the Color to be fully opaque white
Set the Image Type to be Sliced
Select the Last Line child game object
In the Inspector on the Text Mesh Pro component change the Font Asset field to point to the Pixelify-Sans font
Change the Font Size to be 48
Set the Font to be using Bold
Expand the Extra Settings and change the Margins to be Left, Right and Top to be 0
, and set Bottom to be 14
And with that, our Options Presenter is also rethemed!
With these straightforward changes, we've completely transformed the look of the default dialogue presenters without writing any code. Hopefully, you now feel confident in your ability to customize the visuals of built-in presenters. Take your newly styled dialogue system for a spin and see how it enhances your game's visual identity!
Learn the various components and assets used in getting Yarn Spinner up and running Unity scenes.
This guide will walk you through setting up Yarn Spinner in your Unity project and getting a basic dialogue system running. It assumes you have created a new Unity project and .
To organise your dialogue, you'll need to create a Yarn Project.
A Yarn Project is a special file that groups related Yarn Scripts together. It's essential for using dialogue in your game.
To create a Yarn Project:
In the Unity Editor, open the Assets menu -> Yarn Spinner -> and choose Yarn Project
Name your new Yarn Project file (e.g., MyGame
)
The Yarn Project will appear in your Assets folder
Yarn Projects include all Yarn Scripts in the same directory by default. You can modify the Source Files setting to include scripts from different locations.
You can take a look at the Inspector of your Yarn Spinner Project to get a better understanding of what it's looking for:
You'll notice that it's including all .yarn
scripts in the same folder as it, or in folders below/inside it. You can change this, or add specific Yarn Spinner Scripts if you'd like. You can also use this Inspector to change the default language for your Yarn Spinner Project, add additional localisations, and export your strings for translation.
For now, you don't need to touch anything here.
Now, let's create a Yarn Script to write your dialogue:
Open the Assets menu -> Yarn Spinner > and choose Yarn Script
Name your script (e.g., Introduction
). This will create a file named Introduction.yarn
Double-click the script to open it in the editor
Here's a simple example of dialogue written in Yarn:
Save your changes. The script will be automatically included in your Yarn Project if it's in the same folder.
When writing dialogue, remember:
Each node begins with title:
followed by the node name
The node's content starts after ---
and ends with ===
Options are created using ->
at the start of a line
Commands use <<command>>
syntax
To use your dialogue in-game, you need to add a Dialogue System to your scene:
In your scene hierarchy, right-click and choose Yarn Spinner -> Dialogue System
With the Dialogue System selected in the Hierarchy, locate its Dialogue Runner component in the Inspector.
Drag your Yarn Project from the Assets view into the "Yarn Project" field in the Dialogue Runner:
If you want dialogue to start automatically, check "Start Automatically" and set your starting node (often titled Start
, by convention).
The Dialogue System prefab comes with several components:
Dialogue Runner: The core component that runs your dialogue
Line Presenter: Displays text dialogue to the player
Options Presenter: Shows choices for the player to select
Line Advancer: Allows the player to progress through dialogue
Markup Processor: Handles replacement .
Dialogue Presenters are components that display dialogue content to the player. There are a few different varieties. You can have multiple Dialogue Presenters in your scene, each handling different aspects of dialogue presentation. You'll often have a Line Presenter, to show regular lines, and an Options Presenter, to show options.
A Line Presenter shows lines of dialogue. The default Line Presenter looks like this:
This default Line Presenter has some configuration options, including:
Text appearance and positioning
Character name display
Typewriter effect speed
Fading effects
Auto-advance settings
You can learn about them by selecting the Line Presenter in the Hierarchy:
And then looking at its Inspector:
The Options Presenter displays for the player to select. The default Options Presenter looks like this:
You can configure:
Option appearance and positioning
List layout
Whether to show unavailable options
The Line Advancer allows players to progress dialogue using input. You can configure it by:
Selecting your Line Presenter in the hierarchy
Finding the associated Line Advancer component
Configuring the input method (keycode, button, etc.)
Variable Storage components keep track of variables in your dialogue:
By default, Yarn Spinner uses In-Memory Variable Storage (variables are lost when the game ends)
For persistent variables, create a custom Variable Storage that connects to your game's save system. You can learn to do this in our Guide.
To debug variables during development, use the Debug Text View property of the In-Memory Variable Storage component. By creating a in your Hierarchy, and assigning it to the Debut Text View field of the In Memory Variable Storage component attached to the Dialogue System, you can monitor variables in your game view for debug purposes:
Line Providers fetch the content for each line of dialogue:
Text Line Provider: Provides just the text of dialogue lines
Audio Line Provider: Provides text and associated audio clips
Unity Localised Line Provider: Works with Unity's Localization system
If you don't set a Line Provider, the system will create a Text Line Provider automatically. These components are all configured in the Dialogue Runner:
To test your dialogue system:
Make sure you have a Yarn Project asset and a Yarn Script asset, and that the Yarn Script is appropriately included with the Yarn Project:
Add a Dialogue System to the scene, and assign the Yarn Project to it in the Inspector for the Dialogue Runner component attached to the Dialogue System:
Also set the the Dialogue Runner to Start Automatically, and run the appropriate Yarn node:
Press Play in the Unity Editor. Dialogue should begin!
If you didn't want dialogue to start automatically, you can trigger it by calling the StartDialogue()
method on your Dialogue Runner. For example, you might trigger dialogue when a player presses a button near an NPC.
While testing, use the Unity Console to check for any errors in your Yarn scripts:
Check if your Yarn Project is assigned to the Dialogue Runner
Verify that your Yarn Script contains a node with the name specified in "Start Node"
Check that you've ticked Start Automatically on the Dialogue Runner
Make sure there are no compilation errors in your Yarn Scripts
Ensure the Line Presenter component is properly configured
Check that the Canvas Group and Text components are correctly assigned
For debugging, use the In-Memory Variable Storage's "Debug Text View" to see variable values
Make sure variables are declared with the correct type
Learn about the terminology and assets used to work with Yarn Spinner Scripts in Unity.
Yarn Spinner Scripts are just like any other kind of asset you might work with in your Unity Projects: they live in your Assets/
folder.
When you're building a project in Unity that use Yarn Spinner, there are two main kinds of files (which become Assets, in Unity terminology) you'll use when working with Yarn Spinner for Unity:
Yarn Scripts—.yarn
files that contain your written dialogue.
Yarn Projects—a special file that groups a set of Yarn Scripts together.
In this document, we'll learn how these pieces fit together in Unity.
A Yarn Spinner Script is a text file containing your dialogue. You spent a lot of time writing .yarn
files, which are Yarn Spinner Scripts, using Yarn Spinner for Visual Studio Code in the section of the documentation.
There are two main methods of getting your Yarn Spinner Scripts into your Unity project:
you can move the .yarn
Yarn Scripts into the Assets/
folder of your project, using your computer's file manager or terminal;
you can can create new .yarn
Yarn Scripts inside Unity.
To create a new Yarn script in Unity, follow these steps:
Open the Assets menu, and choose Yarn Spinner -> Yarn Script.
Unity will create a new file. Type in a name for the file, and press return.
The new file that you've just created will contain a single , which has the same name as the file.
To edit a Yarn script, double-click it in Unity. The file will open in your editor. When you save your changes and return to Unity, it will be re-compiled. Learn about editing Yarn Spinner Scripts using the .
A Yarn Project is a file that links multiple Yarn Spinner Scripts together.
To create a new Yarn Project, follow these steps:
Open the Assets menu, and choose Yarn Spinner -> Yarn Project.
Unity will create a new file. Type in a name for the file, and press return.
On their own, a Yarn Project doesn't do anything. In order to be useful, you need to add Yarn scripts to it.
Yarn Projects include all Yarn Scripts that the project finds in the Source Files directory. By default, that means all Yarn Scripts in the same directory as the Yarn Project, and all of that directory's children.
When you add a Yarn Script to the same folder as a Yarn Project, it will automatically be included in the Yarn Project. When you make changes to the script, the Yarn Project will automatically be re-imported.
You can change the locations that a Yarn Project looks for Yarn Scripts by modifying the Source Files setting. Each entry in the Source Files setting is a search pattern.
You can add as many entries to the Source Files field as you like. If a file is matched by multiple patterns, it will only be included once.
A Yarn script can be included in more than one Yarn Project.
You can create a new Yarn Project from a script. To do this, follow these steps:
Select the Yarn script in the Project pane.
In the Inspector, click the Create New Yarn Project button.
Clicking this button does two things:
A new Yarn Project will be created next to the Yarn script.
The new Yarn Project will include the Yarn script you created it from in its list of source scripts.
A Yarn Project's inspector shows information about every that are used in the Yarn scripts. This section of the Inspector shows the name, type, description, and default value of each variable.
The Inspector will show information about every variable in the project. If you use a declare
statement to declare a variable, you can control the initial value of a variable, as well as its description.
If you don't declare a variable, Yarn Spinner will attempt to figure the variable's type out based on how it's used, and won't be able to provide a description.
When you write a Yarn script, you write it in a specific human language. This is referred to as the 'base' language of the script. It's called the base language because it's the one you start with, and the one you translate into other languages.
You can set the base language of a Yarn Project in the Inspector by changing the Base Language setting.
If you want to translate your scripts into another language, or if you want to associate each line with assets (like voice over audio clips), you create a new Localisation. To learn about this process, see .
Yarn Projects are used by Dialogue Runners. When a Dialogue Runner is told to start running dialogue, it reads it from the Yarn Project it's been provided.
/// The name of the player.
<<declare $playerName = "Reginald the Wizard">>
/// The number of gold pieces that the player has.
<<declare $gold = 42>>
/// Is the door to the dungeon unlocked?
<<declare $doorUnlocked = false>>
<<set $greeting to "Hello, Yarn!">>
Number
Any whole or decimal number
1
, 2.5
, 3468900
, -500
String
Any sequence of letters, numbers and other characters, enclosed in quotes.
"Hello
", "✓
", "A whole sentence.
"
Boolean
Either the value true or the value false.
true
, false
// Set some initial values in some variables
<<declare $myCoolNumber = 7>>
<<declare $myFantasticString = "wow, text!">>
// Now change them!
<<set $myCoolNumber to 8>>
<<set $myFantasticString to "incredible!">>
// Set some initial values in some variables
<<declare $myCoolNumber = 7>>
<<declare $myFantasticString = "wow, text!">>
// This will NOT work, because you can't change types!
<<set $myCoolNumber to "eight">>
<<set $myFantasticString to 42>>
// Stores 3 inside $numberOfSidesInATriangle
<<set $numberOfSidesInATriangle = 2 + 1>>
// Store 4 inside $numberOfSidesInASquare
<<set $numberOfSidesInASquare = $numberOfSidesInATriangle + 1>>
// This will NOT work, because you can't add a string and a number:
<<set $broken = "hello" + 1>>
<<declare $aNumber = 42>>
<<declare $aString = "This is my string.">>
<<set $aString = string($aNumber)>>
<<set $variableName to "a string value">>
The value of variableName is {$variableName}.
The value of variableName is a string value.
title: Alice
---
Player: Hello
Alice: Hello, this is a sample showing off retheming the base dialogue presenters
-> Neat
Alice: right?
-> Dull
Alice: rude!
===
Markup lets you add attributes to text that is delivered in lines.
Markup allows you to add attributes into your text, like [a]hello[/a]
. These attributes can be used by your game to do things like change the formatting of the text, add animations, and more.
When text is parsed, the tags are removed from the text, and you receive information about the range of the plain text that the attributes apply to.
Attributes apply to ranges of text:
Oh, [wave]hello[/wave] there!
Yarn Spinner will take this text, and produce two things: the plain text, and a collection of attributes. The plain text is the text without any markers; in this example it will be:
Oh, hello there!
Attributes represent ranges of the plain text that have additional information. They contain a position, a length, and their name, as well as their properties.
In this example, a single attribute will be generated, with a position of 4, a length of 5, and a name of "wave".
Attributes are opened like [this]
, and closed like [/this]
.
Attributes can overlap:
You can put multiple attributes inside each other. For example:
Oh, [wave]hello [bounce]there![/bounce][/wave]
You can close an attribute in any order you like. For example, this has the same meaning as the previous example:
Oh, [wave]hello [bounce]there![/wave][/bounce]
Attributes can self-close:
[wave/]
A self-closing attribute has a length of zero.
The marker [/]
is the close-all marker. It closes all currently open attributes. For example:
[wave][bounce]Hello![/]
Attributes can have properties:
[wave size=2]Wavy![/wave]
This attribute 'wave' has a property called 'size', which has an integer value of 2.
Attributes can have short-hand properies, like so:
[wave=2]Wavy![/wave]
This is the same as saying this:
[wave wave=2]Wavy![/wave]
This attribute 'wave' has a property called 'wave', which has an integer value of 2. The name of the attribute is taken from the first property.
Properties can be any of the following types:
Integers
Floats
'true' or 'false'
Strings
Single words without quote marks are parsed as strings. For example, the two following lines are identical:
[mood=angry]Grr![/mood]
[mood="angry"]Grr![/mood]
If a self-closing attribute has white-space before it, or it's at the start of the line, then it will trim a single whitespace after it. This means that the following text produces a plain text of "A B":
A [wave/] B
You may want to show text containing the [
and ]
characters to your player. To prevent the markup parser from treating as special characters, you can escape them. Text that has been escaped will be treated as plain text, and will not be interpreted by the parser.
There are two ways to escape your markup: escaping single characters, and using the nomarkup
attribute.
[
and ]
charactersIf you need to escape a single square bracket character, put a backslash \
in front of it:
Here's some square brackets, just for you: \[ \]
This will appear to the player as:
Here's some square brackets, just for you: [ ]
The backslash will not appear in the text.
nomarkup
AttributeIf you want to escape a longer run of text, or if you have many square brackets, escaping a single character at a time can be cumbersome. In these cases, you may want to escape an entire region of text, using the nomarkup
attribute. This attribute makes the parser ignore any markup characters inside it.
If you want to include characters like [
and ]
, wrap them in the nomarkup
attribute:
[nomarkup]Here's a big ol' [ bunch of ] characters, filled [[]] with square [[] brackets![/nomarkup]
This will appear as:
Here's a big ol' [ bunch of ] characters, filled [[]] with square [[] brackets!
character
AttributeThe character
attribute is used to mark the part of the line that identifies the character that's speaking.
Yarn Spinner will attempt to add this character for you, by looking for character names in lines that look like this:
CharacterA: Hello!
CharacterB: Oh, hi!
The markup parser will mark everything from the start of the line up to the first :
(and any trailing whitespace after it) with the character
attribute. This attribute has a property, name
, which contains the text from the start of the line up to the :
. If a :
isn't present, or a character
attribute has been added in markup, it won't be added.
This means that the example above is treated the same as this:
[character name="CharacterA"]CharacterA: [/character]Hello!
[character name="CharacterB"]CharacterB: [/character]Oh hi!
You can use this to trim out the character names from lines in your game.
Certain attributes in Yarn Spinner's markup are replacement markers, which Yarn Spinner uses to insert or replace text based on the value of a variable.
There are three built-in replacement markers:
The select
marker uses the value of a variable to choose an outcome.
The plural
marker uses the value of a number to decide on the plural class for that number.
The ordinal
marker uses the value of a number to decide on the ordinal class for that number.
All three of these markers have a property called value
, and use this to decide what text should be used in the line.
You can also make your own custom replacement markers.
select
The select
marker is the simplest of the built-in replacement markers. It takes the value of the value
property, and uses that to choose a replacement.
It's especially useful for when you need to insert a gendered pronoun in a line:
// In this example, the $gender variable is a string that
// contains either "m", "f", or "nb".
I think [select value={$gender} m="he" f="she" nb="they" /] will be there!
// Depending on the value of $gender, this line can appear
// as one of these possible options:
I think he will be there!
// or:
I think she will be there!
// or:
I think they will be there!
plural
and ordinal
The plural
and ordinal
markers take a number in its value
property, and use that to determine the plural or ordinal number class of that value.
Different languages have different rules for how numbers are pluralised.
In many languages, the term you use to refer to a thing depends on the the number of that thing. This is known as a plural class: in English, you can have one apple, but many apples, and you have have one mouse, but many mice.
However, the rules vary significantly across different languages. English has two: "single", and "other". However, for example, Polish has multiple.
In English, you say "one apple, two apples, five apples".
In Polish, you say "jedno jabłko, dwa jabłka, pięć jabłek".
Notice how the Polish word for "apple", "jabłko", takes multiple forms as the number changes, whereas it takes two forms in English.
In Yarn Spinner, individual lines are replaced depending on the user's locale, but the logic surround them is not. This means that, if you want to be able to translate your game into multiple languages, you can't write Yarn code like this:
<<if $apple_count == 1>>
You have one apple!
<<else>>
You have {$apple_count} apples!
<<endif>>
If you did it this way, the logic would only work for languages that have the same rules for plurals as English. (There are several of them that do, but far more that don't.)
Complicating this further, there are two main kinds of plural classes: cardinal plural classes, and ordinal plural classes.
Cardinal plural classes are the kind we just saw (for example, "one apple, two apples").
Ordinal plural classes refer to the positioning of a thing; in English, ordinal numbers are things like "1st, 2nd, 3rd."
As with cardinal plural classes, different languages have different ordinal plural classes.
plural
and ordinal
Yarn Spinner is able to take a number and the user's current locale, and determine the correct cardinal or ordinal plural class of that number, for that locale. You can then use the plural class to decide on what text to show.
plural
and ordinal
have a property called value
, just like select
. They then have a property for each of the current locale's plural classes. These can be:
one
two
few
many
other
The two markers differ based on what kind of plural class they work with:
plural
selects a number's cardinal plural class.
ordinal
selects a number's ordinal plural class.
Not every language uses every category; for example, English only uses "one" and "other" for cardinal plural classes.
For each of these properties, you provide the text that should appear.
For example:
PieMaker: Hey, look! [plural value={$pie_count} one="A pie" other="Some pies" /]!
// This will appear as either:
PieMaker: Hey, look! A pie!
// or:
PieMaker: Hey, look! Some pies!
You can include the actual value in the resulting text by using the %
character. This character will be replaced with the value provided to the value
property:
PieMaker: I just baked [plural value={$pie_count} one="a pie" other="% pies" /]!
// This will appear as, for example:
PieMaker: I just baked a pie!"
// or:
PieMaker: I just baked 4 pies!"
The ordinal
marker works similarly, but uses the ordinal plural class:
Runner: The race is over! I came in [ordinal value={$race_position} one="%st" two="%nd" few="%rd" other="%th" /] place!
// This will appear as, for example:
Runner: The race is over! I came in 1st place!
// or:
Runner: The race is over! I came in 23rd place!
You can also use markup to trigger events in your game from Yarn Spinner Scripts. To learn about this, check out the Inline Events Sample Guide.
title: NodeName
color: purple
---
===
title: Note_Evil
style: note
position: 521,336
---
TODO: Add more evil options in here.
===
title: Note_Love
style: note
color: red
position: -128,171
---
I don't like the fact that the witch is totally unaware of what love is. Can we give them some more lines here?
===
.yarn
files..yarn
file to edit..yarn
extension.///
comment syntax to describe a variable.<<declare>>
to declare a boolean.boolean
like an integer
.title: Start
---
Player: Hello there!
NPC: Oh, hello! How can I help you today?
-> I need information.
NPC: What would you like to know?
-> I'm just browsing.
NPC: Feel free to look around!
-> Actually, I should go.
NPC: No problem. Come back anytime!
<<jump End>>
===
title: End
---
Player: Thanks for your help!
NPC: You're welcome! Have a nice day!
===
Start
.*
any filename
"*.yarn
" will find "One.yarn" and "Two.yarn".
**/*
any path, including subdirectories
"**/*.yarn
" will find "One.yarn" and "Subfolder/Two.yarn".
..
the parent folder
"../*.yarn
" will find "One.yarn" in the parent folder.
Source Scripts
The list of places that this Yarn Project looks for Yarn Scripts.
Base Language
The language that the Yarn Scripts are written in.
Localisations
A mapping of languages to string tables and associated assets.
This list will only appear if the project is not using the Unity Localisation system. See Adding Localizations and Assets to Projects for more information.
Use Addressable Assets
If this is turned on, the Yarn Project will be set up to tell other parts of the game that localised assets like audio files should be fetched using the Addressable Assets system.
This checkbox will only appear if the Addressable Assets package is installed in your project, and if the project is not using the Unity Localisation System.
Use Unity Localisation System
If this is turned on, the Yarn Project will use the Unity Localisation System to store line data in.
This checkbox will only appear if the Localisation package is installed in your project.
Unity Localisation String Table
The String Table Collection that the Yarn Project uses. When the project is imported or reimported, this String Table will be filled with line content that comes from the project's Yarn Scripts.
This field will only appear if project is using the Unity Localisation system.
Export Strings as CSV
When you click this button, all of the lines in the Yarn Scripts that this project uses will be written to a .csv
file, which can be translated to other languages. See Adding Localizations and Assets to Projects for more information.
Update Existing Strings Files
When you click this button, all .csv
strings files that are configured in the Languages to Source Assets list will be updated with any lines that have been added, modified or deleted since the strings file was created.
This checkbox will only appear if the project is not usin the Unity Localisation system. See Adding Localizations and Assets to Projects for more information.
Add Line Tags to Scripts
When you click this button, any line of dialogue in the Source Scripts list that doesn't have a #line:
tag will have one added. See Adding Localizations and Assets to Projects for more information.
Learn about the Feature Tour Sample, which shows off many of the different capabilities of Yarn Spinner.
The Feature Tour Sample is an ideal starting point for newcomers to Yarn Spinner. It presents a series of conversations along a corridor, each demonstrating a different capability of Yarn Spinner.
Are you ready to navigate this narrative gauntlet?
and
and
and
Storylets
A Dialogue Interactible component and node-character associations
The feature tour consists of a long corridor with multiple rooms. Each room contains one or more interactive characters, with each character showcasing a specific Yarn Spinner feature. While this sample doesn't demonstrate every Yarn Spinner capability, it covers the most commonly used ones.
At the heart of this feature tour is a class, Dialogue Interactible. This component is attached to each interactive character and associates a specific dialogue node with that character. If you're curious about how a particular feature works, you can identify which node contains that feature through this component.
For example, if you want to see which node provides dialogue for a specific character, select that character in the scene and examine the Dialogue Interactible component.
In this example, we can see the node is Room4_Variables_2
. Since all dialogue in this sample is contained in a single file (Tour.yarn
), you simply need to open that file and locate the node to understand its implementation.
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
Add milk to the cup
Drink the tea
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 async
to 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:
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;
}
}
You would start it like this:
void Start()
{
StartCoroutine(MoveCube(1, new Vector3(10, 0, 0)));
}
Now, let's convert this to an async implementation:
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;
}
}
And to start it:
async void Start()
{
await MoveCube(1, new Vector3(10, 0, 0));
}
Next, let's add cancellation support - after all, what good is a moving cube if its movement can't be stopped?
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;
}
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:
async void Start()
{
await MoveCube(5, new Vector3(10, 0, 0), this.destroyCancellationToken);
}
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:
CancellationTokenSource cancellationTokenSource;
async void Start()
{
cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(this.destroyCancellationToken);
await MoveCube(5, new Vector3(10, 0, 0), cancellationTokenSource.Token);
}
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:
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);
}
}
To test cancellation, we can trigger it in response to user input:
void Update()
{
// if you release tab the cube movement is cancelled
if (Input.GetKeyUp(KeyCode.Tab))
{
cancellationTokenSource.Cancel();
}
}
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:
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;
}
Then in our RunOptionsAsync
method, we await the roll and use the result to select an option:
public override async YarnTask<DialogueOption> RunOptionsAsync(DialogueOption[] dialogueOptions, CancellationToken cancellationToken)
{
int roll = await RollDice(dialogueOptions.Count, cancellationToken);
return dialogueOptions[roll];
}
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:
public class ButtonWaiter : MonoBehaviour
{
public YarnTaskCompletionSource buttonCompletion;
public Button button;
async void Start()
{
button.onClick.AddListener(() =>
{
buttonCompletion.TrySetResult();
});
}
}
In your custom presenter code, you might use this like:
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();
}
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:
OnOptionSelected.TrySetResult(this.Option);
The selected value is then returned to the dialogue system:
// 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;
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.
You can define your own commands, which allow the scripts you write in Yarn Spinner to control parts of the game that you've built.
In Unity, there are two ways to add new commands to Yarn Spinner: automatically, via the YarnCommand
attribute, or manually, using the DialogueRunner
's AddCommandHandler
method.
YarnCommand
attributeThe YarnCommand
attribute lets you expose methods on a MonoBehaviour
to Yarn Spinner.
When you add the YarnCommand
attribute to a method, you specify what name the command should have in Yarn scripts. You can then use that name as a command.
If the method is not static
, you call it with the name of the game object you want the command to run on.
For example, if you have a script called CharacterMovement
that has a method Leap
, you can add a YarnCommand
attribute to it to make it available to your Yarn scripts:
public class CharacterMovement : MonoBehaviour {
[YarnCommand("leap")]
public void Leap() {
Debug.Log($"{name} is leaping!");
}
}
If you save this in a file called CharacterMovement.cs
, create a new game object called MyCharacter
, and attach the CharacterMovement
script to that game object, you can run this code in your Yarn scripts like this:
<<leap MyCharacter>>
// will print "MyCharacter is leaping!" in the console
If the method is static, you call it directly, without providing a game object name. For example:
// Note that we aren't subclassing MonoBehaviour here;
// static commands can be on any class.
public class FadeCamera {
[YarnCommand("fade_camera")]
public static void FadeCamera() {
Debug.Log("Fading the camera!");
}
}
If you save this in a file called FadeCamera.cs
, you can run this code in your Yarn scripts like this:
<<fade_camera>>
// will print "Fading the camera!" in the console
You can also use methods that take parameters. Yarn Spinner will take the parameters that you provide, and convert them to the appropriate type.
Methods that are used with YarnCommand
may take the following kinds of parameters:
string
Passed directly to the function.
int
Parsed as an integer using .
float
Parsed as an integer using .
bool
The strings "true" and "false" are converted to their respective boolean values, true
and false
. Additionally, the name of the parameter is interpreted as true
.
GameObject
Yarn Spinner will search all active scenes for a game object with the given name. If one is found, that game object will be passed as the parameter; otherwise, null
will be passed.
Component
(or its subclasses)
Yarn Spinner will search all active scenes for a game object with the given name, and then attempt to find a component of the parameter's type on that game object or its children. If one is found, that component will be passed as the parameter; otherwise, null
will be passed.
Method parameters may be optional.
For example, consider this method:
[YarnCommand("walk")]
public void Walk(GameObject destination, bool dancing = false) {
var position = destination.transform.position;
// If the second parameter is used in the command,
// and it's "true" or "dancing", use a dance
// animation
if (dancing) {
// set animation to a dance
} else {
// set animation to a regular walk
}
// walk the character to 'position'
}
This command could be called like this:
<<walk MyCharacter StageLeft>> // walk to the position of the object named 'StageLeft'
<<walk MyOtherCharacter StageRight dancing>> // walk to StageRight, while dancing
You can see a list of commands that you've registered with the YarnCommand
attribute by opening the Window menu and choosing Yarn Spinner -> Commands.
This will show a list of every command that you can call in your Yarn script, what parameters they take, and which parameters are optional.
You can also add new commands directly to a Dialogue Runner, using the AddCommandHandler
method.
AddCommandHandler
takes two parameters: the name of the command as it should be used in Yarn Spinner, and a method to call when the function is run.
If you want to add a command using AddCommandHandler
that takes parameters, you must list the types of those parameters.
For example, to create a command that makes the main camera look at an object, create a new C# script in Unity with the following code:
public class CustomCommands : MonoBehaviour {
// Drag and drop your Dialogue Runner into this variable.
public DialogueRunner dialogueRunner;
public void Awake() {
// Create a new command called 'camera_look', which looks at a target.
// Note how we're listing 'GameObject' as the parameter type.
dialogueRunner.AddCommandHandler<GameObject>(
"camera_look", // the name of the command
CameraLookAtTarget // the method to run
);
}
// The method that gets called when '<<camera_look>>' is run.
private void CameraLookAtTarget(GameObject target) {
if (target == null) {
debug.Log("Can't find the target!");
}
// Make the main camera look at this target
Camera.main.transform.LookAt(target.transform);
}
}
Add this script to any game object, and it will register the camera_look
in the Dialogue Runner you attach.
You can then call this method like this:
<<camera_look LeftMarker>> // make the camera look at an object named LeftMarker
We provide two different means of handling commands in Yarn Spinner: the AddCommandHandler
method and the YarnCommand
attribute. Both of these provide effectively the same functionality, and under-the-hood the YarnCommand
attribute is even a wrapper around the AddCommandHandler
call. So if there are two different ways to achieve the same thing when should you use each one?
The YarnCommand
attribute allows you to tag specific methods as being a command, Yarn Spinner will then automatically handle the binding and connection of the the command in text to the method call in C#.
AddCommandHandler
method allows you to manually connect a method in C# to a command in Yarn, letting you set the name of the command and which method it connect to, giving you the control over the binding.
Most of the time, we feel that the YarnCommand
attribute is the better option, because it is easier to use, and maps well to how we find most people use commands - that is, calling specific methods on specific GameObjects.
This convenience, however, does come at a cost of flexibility, because your YarnCommands
either need to be on static methods, or follow specific calling conventions, which may not be what you need or want.
The YarnCommand
attribute works best in our opinion when your commands are calling into specific GameObjects in your scene, which means that it works very well for moving, animating, or changing characters and items in a scene.
For larger gameplay changing moments, such as loading new scenes, moving between dialogue and the rest of your game, or for more global events like saving the game or unlocking an achievement, the AddCommandHandler
method is better.
Coroutines can be commands. If you register a command, either using the YarnCommand
attribute, or the AddCommandHandler
method, and the method you're using it with is a coroutine (that is, it returns IEnumerator
, and yields
objects like WaitForSeconds
), Yarn Spinner will pause execution of your dialogue when the command is called.
Additionally, if your method returns a Coroutine
object, Yarn Spinner will wait for that coroutine to complete. You can create and return a Coroutine
by using the StartCoroutine
method.
For example, here's how you'd write your own custom implementation of <<wait>>
. (You don't have to do this in your own games, because <<wait>>
is already added for you, but this example shows you how you'd do it yourself.)
public class CustomWaitCommand : MonoBehaviour {
[YarnCommand("custom_wait")]
static IEnumerator CustomWait() {
// Wait for 1 second
yield return new WaitForSeconds(1.0);
// Because this method returns IEnumerator, it's a coroutine.
// Yarn Spinner will wait until onComplete is called.
}
}
This new method can be called like this:
<<custom_wait>> // Waits for one second, then continues running
Functions are units of code that Yarn scripts can call to receive a value.
In addition to the built-in functions that come with Yarn Spinner, you can create your own.
To create a function, you use the YarnFunction
attribute, or the AddFunction
method on a Dialogue Runner. These work very similarly to commands, but with two important distinctions:
Functions must return a value.
Functions are required to be static
.
For example, here's a custom function that adds two numbers together:
public class AdderFunction {
[YarnFunction("add_numbers")]
public static int AddNumbers(int first, int second)
{
return first + second;
}
}
When this code has been added to your project, you can use it in any expression, like an if
statement, or inside a line:
<<if add_numbers(1,1) == 2>>
One plus one is {add_numbers(1, 1)}
<<endif>>
Yarn functions can return the following types of values:
string
int
float
bool
Extend Yarn Spinner by creating custom commands to connect dialogue with your game mechanics:
Custom Commands
Create a command that your Yarn scripts can call:
[YarnCommand("give_item")]
public void GiveItem(string itemName, int quantity = 1)
{
// Your code to give the player an item
Debug.Log($"Giving {quantity} {itemName}(s) to the player");
}
Then use it in your Yarn script:
NPC: Here, take this potion.
<<give_item "health_potion">>
Custom Dialogue Presenters
For unique presentation styles, create custom Dialogue Presenters by subclassing DialoguePresenterBase
:
public class CustomDialoguePresenter : DialoguePresenterBase
{
public override void RunLine(LocalizedLine dialogueLine, Action onDialogueLineFinished)
{
// Your custom line presentation code
Debug.Log($"Presenting line: {dialogueLine.TextWithoutCharacterName}");
// Call this when the line presentation is complete
onDialogueLineFinished();
}
}
The guide and documentation for the paid add-on, Dialogue Wheel for Yarn Spinner.
This Unity package provides two prefabs: Image Dialogue Wheel and Automatic-Layout Dialogue Wheel. The package requires Yarn Spinner for Unity.
This add-on is not part of the open source Yarn Spinner package, and can be purchased from the Yarn Spinner Itch.io Store or via the Unity Asset Store.
Dialogue Wheel for Yarn Spinner provides an Automatic-Layout Dialogue Wheel and a Image Dialogue Wheel prefab. Both are customisable, powerful, and extremely flexible:
customise fonts, colours, and styles of wheels
enable and disable segments (Six-Segment Wheel)
specify specific segments for specific dialogue (Six-Segment Wheel)
theoretically unlimited options on the wheel (Automatic-Layout Dialogue Wheel)
works for 2D or 3D games
This guide provides documentation on using both prefabs.
The oldest supported version of Unity for the Dialogue Wheel is 2022.3.
The Image Wheel provides a dialogue wheel with a light scif-fi appearance, and support for up to six segments, and the ability to specify exactly which of the segment positions is used for an option in your Yarn scripts.
To learn how to use the Image Wheel, read this guide.
The Automatic-Layout Dialogue Wheel provides a dialogue wheel with a simple graphical appearance, and can—theoretically—support as many options as you'd like, automatically adjusting to display them. You cannot force an option to appear in a specific place on the wheel.
To learn how to use the Automatic-Layout Dialogue Wheel, read this guide.
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.
Unity Localised Line provider is a Line Provider that fetches the text and any localised assets from Unity's Localization system.
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.
The Dialogue Runner is the bridge between the dialogue that you've written in your Yarn 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 scripts to the other parts of your game, such as your user interface.
Setting up a Dialogue Runner is the first step in adding dialogue to your game. To use a Dialogue Runner, you add the DialogueRunner script to a node in your scene, connect it to Dialogue Presenters, and provide it with a Yarn Project to run.
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 Presenters.
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 Godot:
The contents of your dialogue are delivered to your Dialogue Presenters.
The values of variables are stored and retrieved using the Dialogue Presenter's Variable Storage.
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 Line Provider.
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. Add nodes that have scripts implementing the DialoguePresenterBase interface to this list in order for them to handle your dialogue lines and options. If a node that is added to this list does not implement DialoguePresenterBase, it will not function as a view, and an error explaining this will be logged to the output console.
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 StartDialogue 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.
onNodeStart
A signal that's emitted when the Dialogue Runner begins running a new node. This may be fired multiple times during a dialogue run.
onNodeComplete
A signal that's emitted when the Dialogue Runner reaches the end of a node. This may be fired multiple times during a dialogue run.
onDialogueComplete
A signal that's emitted when the Dialogue Runner stops running dialogue.
onCommand
A signal that's emitted 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 YarnCommand or AddCommandHandler.
Learn about Options Presenter, a Dialogue Presenter that shows options in a list.
An Options Presenter is a Dialogue Presenter 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.
Presenter Control
The Canvas Group that the Options List View will control. The control will be made visible when the Options List View is displaying options, and inactive when not displaying options.
Option Parent
The node that options will be parented to. You can use a BoxContainer to automatically lay out your options. This node should ideally be a descendent of the Presenter Control so that it will be shown and hidden at the right times.
Option Item Prefab
A packed scene containing an Option View. The Options List View will create an instance of this packed scene for each option that needs to be displayed.
Last Line Text
A RichTextLabel node that will display the text of the last line that appeared before options appeared. If this is not set, or no line has run before options are shown, then this property will not be used.
Last Line Container
The CanvasItem that contains the Last Line Text. This object is set to visible when options run, a last line is available, and the last line has a character name. It is hidden when an option is selected. This field is optional, and will default to the same node as LastLineText if it is not specified.
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.
Pallete
An optional MarkupPallete resource. Used to represent a collection of marker names and colours.
Use Fade Effect
If this is turned on, the alpha component of the Presenter Control's modulate color 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.
Learn how to build and use an advanced saliency system when you use Yarn Spinner for Unity.
There have been several guides and samples about saliency and storylets in Yarn Spinner 3, so why not add one more!?
While previous samples have deliberately limited the amount of dynamic content, the Advanced Saliency Sample explores what happens when you want highly dynamic content within the already dynamic nature of storylets themselves.
Dynamically building up Yarn content
Dynamic selection of Yarn content
Writing highly dynamic storylets
Changing a scene on the fly in response to Yarn value changes
Challenges in writing content for these types of games
Unlike other samples, we need to provide some context before diving into how everything works. This sample demonstrates building a room where the characters, scenario, and even object layout are fully dynamic based on values set in Yarn. This approach is ideal for games where you have a team of characters accompanying the player or side-quests featuring previously encountered characters and locations, making the world feel more interconnected and responsive to player choices.
The structure for this sample includes four different scenarios: interrogation, exploration, rescue, or a date. Each scenario has two main non-player characters (primary and secondary), who can be any of four NPCs: Alice, Barry, George, or Liz. These scenarios can take place in one of four rooms: an office, pub, church, or mansion.
Even in this limited sample, there are up to 192 possible combinations - far too many to write individually for just four different scenarios. The situation becomes worse when you consider that each scenario likely needs multiple nodes, creating an enormous writing burden.
As you add more scenarios, characters, and locations, the combinatorial explosion quickly outpaces even the fastest writers. To address this, we template the scenarios and use interpolation to inject different values into these templates. While replaying the same scenario repeatedly might reveal patterns, in a typical playthrough where each scenario is seen only once or twice, the game appears fully responsive to the player's actions.
This approach works particularly well for games with varied side quests that need to adapt to the player's situation. Games like Watch Dogs: Legion and Weird West use similar techniques to populate their worlds with highly reactive missions. While you could apply this approach to a main storyline, it's less common for a game's primary path since it sacrifices some of the control that's valuable for telling a specific story.
Using interpolation and templated scenarios offers two additional benefits:
You can still write highly specific scenarios alongside templated ones. For instance, you could craft detailed content for Alice and Liz exploring a mansion without writing all 191 other combinations.
It facilitates easier scaling, allowing different writers to handle specific scenarios without interfering with others' work. More niche combinations can be assigned to writers who best understand those characters, enabling more straightforward assignment and scaling of writing tasks.
The Advanced Saliency Sample consists of several components: an initial configuration area where you can speak with Capsley for an explanation and interact with four buttons to configure the room, and a main room where the scenarios play out.
Each button updates a specific Yarn variable. These variables control who the primary character is ($primary
), who the secondary character is ($secondary
), what scenario will be played out ($scenario
), and what room it will take place in ($room
).
Walking onto a button triggers an update to the relevant variable that the button controls. This allows you to configure the scenario details, participants, and location.
In the distance lies "the void," where characters not needed for the current scenario wait. While these characters could be instantiated from prefabs, we originally designed the sample differently, requiring all characters to exist in the scene simultaneously. Although we later changed this approach, we found the notion of characters waiting on a distant island amusing, so we kept it.
The level god (LevelGod.cs
) handles loading room props and placing characters. It manages a collection of room layouts (defined as scriptable objects) that specify prop placement, level content, and character positions. When it's time to run a scenario, the level god checks the variable storage, reads the current scenario, and loads the appropriate room layout. It then positions characters according to the layout specifications.
In an actual game, room geometry would likely be part of the general level design rather than defined in a configuration file. However, you would still need configuration for different scenarios, such as character placement and required props. You'd need some version of a "level god" to interpret this configuration and handle runtime setup.
Capsley is responsible for triggering the scenario. Speaking with Capsley runs the final setup code needed to load the scenario, after which the primary and secondary characters take over.
This sample contains more dialogue and uses significantly more interpolation than typical Yarn scripts. There's one main file (room.yarn
) that sets up variables, contains Capsley's dialogue, and all bouncer nodes. Each scenario has its own file (room-Date.yarn
,room-Exploration.yarn
,room-Rescue.yarn
,room-Interrogation.yarn
), and there's an additional file demonstrating how to implement specific variable combinations (room-Explore-Mansion-Liz-Alice.yarn
) alongside more generic ones.
We've organized the dialogue into two main node groups: Primary
and Secondary
, corresponding to the specific characters in those roles. These are accessed via bouncer nodes that apply across the entire sample rather than on a per-scenario basis. For example, if Alice is the Primary character, her bouncer node would be:
Perhaps the most noticeable difference from typical Yarn files is how speaker names are handled. Usually in Yarn, you define your speaker with their name followed by a colon, but here the speaker names are interpolated values. This addresses the issue of not knowing which character will speak a line, only that it will be either the primary or secondary character.
When this Yarn code runs, the names of characters in the $primary
and $secondary
roles are interpolated into the lines. If Alice is Primary and Liz is Secondary, this would be equivalent to:
This works because the enums have been given string backing values matching their character names.
With two node groups (Primary
and Secondary
), we need substantial filtering to prevent inappropriate storylets from appearing. The scenario serves as the main filter for node groups. To maintain clarity, each scenario has its own file, and every node in that file begins with the same filtering when clause. Additional clauses then track progression through the scenario. For example, the first node in the interrogation scenario when talking to the primary character has this header:
This isn't the only possible approach, but it's the most straightforward without requiring extensive custom code or limiting expressiveness. These combinations can become arbitrarily complex to accommodate highly specific situations:
These when clauses specify a scenario that's an exploration in a mansion with Alice and Liz as the main characters, where the scenario has started but the player hasn't spoken to the secondary character yet. While highly specific, breaking it into smaller pieces makes each condition manageable and understandable.
Unlike most samples intended as starting points for implementing Yarn Spinner features, this sample has limitations. Although it demonstrates a working approach to advanced, dynamic story saliency, it's more restricted than what you'd want in a full game. These limitations primarily exist to keep the sample approachable as a starting point.
As you use storylets more extensively, their structure will increasingly reflect your game's needs. Even this relatively simple sample mirrors the structure of its scene: the story is told by two characters following a consistent progression through the scenario because that's all the sample supports. While this sample provides a good starting point for thinking about these concepts, you'll need to make decisions about storylet structure, information access, and flow in your own implementation.
The progression options here are deliberately limited to keep the sample manageable. Progression follows a single path, which would be too restrictive for a full game. Each scenario in a real game would need its own progression system and a way to easily query its state, likely through a custom function.
The writing approach used for storylets and character name interpolation works adequately but can become tedious in larger projects. Writing {$primary}:
and {$secondary}:
at the start of lines is functional but potentially frustrating over time. In a larger project, you might want to use a dedicated line provider or custom view code to simplify this, allowing for something like:
While not a major improvement, this simplification adds up as your game scales.
Related to this is localization, which becomes considerably more challenging with heavy use of interpolation. The most important consideration is ensuring interpolated values undergo localization. In English, structures like The {$room} is on fire
work without issue, but in languages where articles must agree with nouns, separating them causes problems. You'll likely need to process values through a localization function before injecting them rather than using direct interpolation.
Finally, we've overlooked how storylets are selected. A real game would need some form of drama manager to read the game state and configure scenarios appropriately. In this sample, we've simplified this with four buttons that act as a drama manager - understandable but not viable for a shipping game. Similarly, we use bouncer nodes to avoid rewriting dialogue interaction code, but in a larger project, it would make more sense to update each character to load the appropriate node group directly rather than bouncing through empty nodes.
title: Alice
when: $primary == .Alice
---
<<jump Primary>>
===
{$primary}: date time!
Player: Date Time!
{$secondary}: DATE TIME!
{$primary}: why are you here?!
Alice: date time!
Player: Date Time!
Liz: DATE TIME!
Alice: why are you here?!
title: Primary
when: $scenario == .Interogation
when: $scenario_state == .NotStarted
title: Primary
when: $scenario == .Explore
when: $Room == .Mansion
when: $primary == .Alice || $primary == .Liz
when: $secondary == .Alice || $secondary == .Liz
when: $scenario_state == .Started
when: $speak_to_secondary == false
A: date time!
Player: Date Time!
B: DATE TIME!
A: why are you here?!
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:
// 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");
}
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:
// 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);
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 number of gold coins the player has
<<declare $gold = 0>>
The generator would create:
/// <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);
}
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:
<<enum TimeOfDay>>
<<case Morning>>
<<case Evening>>
<<endenum>>
Becomes:
public enum TimeOfDay
{
/// <summary>
/// Morning
/// </summary>
Morning = 0,
/// <summary>
/// Evening
/// </summary>
Evening = 1,
}
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
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.
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 varies by operating system, 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:
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
.
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:
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() { ... }
}
Let's examine key methods, starting with initialization:
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];
}
This creates a perfect hash function for our keys and initializes the state array. Next, let's see how it adds values:
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;
}
After validating the key, this appends the new value to the list for that index. Finally, here's value retrieval:
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;
}
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
:
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() { ... }
}
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:
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,
};
}
}
We now include Yarn variable names in our key list and initialize them with their default values. Next, let's implement the SetValue
methods:
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);
}
Since we already have a method for adding values by name, implementation is straightforward. Now for TryGetValue
:
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;
}
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:
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;
}
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
:
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);
}
}
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
:
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);
}
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 the sample project.
Learn how to use the Image Dialogue Wheel, from the Dialogue Wheel for Yarn Spinner Add-On Package.
The Image Dialogue Wheel provides a dialogue wheel with a light sci-fi appearance, and can display up to six responses for your dialogue. You can specify exactly which segment of the wheel each response is located.
To use the Image Dialogue Wheel make sure your Unity project has the Yarn Spinner package installed, and the install the Dialogue Wheel for Yarn Spinner package.
Then, create a new Dialogue System in your Hierarchy:
Make a folder to store your Narrative in the Project pane (ours is named Narrative
), and then inside that folder, create a new Yarn Project asset:
Similarly, also in the Narrative
folder, create a new Yarn Script to use:
Name both the Yarn Project and the Yarn Script something appropriate. Open the Yarn script to write your story. Then save the Yarn script and return to Unity.
We've provided an initial sample story here, if you want to test things out.
With the Dialogue Runner selected in the Hierarchy, drag the Yarn Project that you created from the Project pane into the Yarn Project slot in the Dialogue System's Inspector:
Next, locate the Image Dialogue Wheel prefab, supplied with this add-on, and drag it from the Project pane, so it's below the Canvas in the Hierarchy:
Right-click on the Options Presenter in the Hierarchy, and choose Delete. You won't need that view, as you'll be displaying a wheel instead of a list.
To make the Dialogue System aware of the Image Dialogue Wheel, select it (the Dialogue System) in the Hierarchy, and drag the Image Dialogue Wheel from the Hierarchy into the Element 2 slot of the Dialogue Views section in the Inspector:
Finally we want to set our story to start automatically, click on the Start Automatically toggle on the Dialogue Runner Inspector and from the Start Node drop down pick Start
. If you save your scene and run it, your Image Dialogue Wheel should now be working!
You can specify which position on the wheel your dialogue appears using tags in your Yarn scripts. Specifically, you can add tags to each set of options to specify where in the wheel the option should be placed.
For example, the following Yarn script:
title: Start
---
Narrator: Show me the positions on the Six-Segment Wheel?
-> Left Top #lt
-> Left Middle #lm
-> Left Bottom #lb
-> Right Top #rt
-> Right Middle #rm
-> Right Bottom #rb
===
Results in this:
If you select the Image Dialogue Wheel Prefab in the Hierarchy (under Dialogue System), you can look to the Inspector to customise these tags, among other options:
You can also use the Yarn Command <<set-opt>>
before each group of options in your Yarn scripts to specify how many options (limited to a maximum of three on either side) should appear in each column (left or right).
For example, the following Yarn Script:
title: Start
---
Narrator: Show some options?
-> I'm an option!
-> I'm another option!
-> I'm yet another option!
-> Me too!
===
Will result in this:
Review the provided Image Dialogue Wheel Example for more information, or check out the guide on Using Auto-Layout Dialogue Wheel.
Learn to add voice and localisation to your Yarn Spinner-powered projects.
Dialogue in games often extends beyond simple text on screen. Many projects incorporate voice acting alongside written dialogue, and sometimes even more complex assets. This raises important questions about handling localisation—as the number of assets per line increases, so does the complexity of ensuring everything works together seamlessly.
This sample demonstrates Yarn Spinner's approach to localisation, asset association, and audio playback in a cohesive system.
The scene features a character floating in space, conversing with a disembodied voice. The main character's dialogue includes synchronized audio that drives lip movement animations. Unlike most samples, this one includes two identical scenes that differ only in their localisation implementation: one uses Yarn Spinner's built-in localisation system, while the other leverages Unity's Localisation package.
Associating and playing audio with dialogue lines
Implementing custom line assets with lip-sync data and audio
Understanding the Built-in and Unity Localisation line providers
Localising both text and associated assets
The line provider is the component responsible for managing assets associated with dialogue. It supplies the content of each line—both text and related assets—to the various presenters in your game. Any object that implements the ILineProvider
interface can serve as a line provider, and the dialogue runner consults this provider when it's time to present a line.
The dialogue runner itself doesn't know the actual content of lines—it only knows which line to run via the line's ID. When a line needs to be displayed, the line provider retrieves the content from the strings table, parses any markup, and collects any associated assets. If you need custom modifications or behaviors that apply to multiple lines, creating a custom line provider is generally the most effective approach.
Yarn Spinner includes two line providers out of the box:
The built-in localised line provider (BuiltinLocalisedLineProvider
) for projects without specific localisation workflows
The Unity localised line provider (UnityLocalisedLineProvider
) for projects using Unity's localisation system
While these providers function differently internally, they present the same interface to Yarn Spinner, so you can generally use whichever best fits your workflow without worrying about the implementation details.
When the dialogue runner passes a line to your presenters, the LocalizedLine
includes an optional Asset
property that can contain any Unity Object (or null). Asset association is based on line ID, connecting relevant resources to specific dialogue lines.
Built In Localisation
The built-in localisation system requires two components for each language:
String Table: Essential for displaying lines. The base localisation (typically English) is automatically configured.
Assets Folder (optional): Contains resources for that localisation.
The system searches the assets folder and matches assets to line IDs in the project. For this sample, we have a folder named LipSync-en
containing the lip-sync data for English localisation.
CALLOUT: This isn't a lip-sync sample so for now they know it's a scriptable object that conforms to the IAssetProvider
interface. It has a list of visemes and timeframes for when to show that viseme. It also has a reference to the audio file that this lip-sync event is connected to. For more details check out LipSyncedVoiceLine.cs
.
The Yarn Project has this folder connected as the Assets
folder for the English locale:
This connection ensures that when a presenter requests an asset (as the voice over presenter does), it receives the lip-sync asset with the same name as the line ID.
Unity Localisation
The Unity Localisation approach differs significantly. Instead of referencing files on disk, it uses tables to manage content. The sample scene includes preconfigured string and asset tables, which the Unity Localisation Line Provider references.
Rather than maintaining a list of locales, the project simply references a table collection:
Similarly, the Unity Localisation Line Provider doesn't configure the current language—it simply references the necessary string and asset tables:
The base locale's string table column automatically populates with values from the Yarn files. For additional languages, you'll need to add content to the table manually, typically using one of Unity's importers. This differs from the built-in localisation, where you manually associate a string table in the project.
Asset association also works differently with Unity Localisation. Since asset tables handle this functionality, Yarn Spinner provides a wizard accessible via Window -> Yarn Spinner -> Add Assets To Table Collection
:
This wizard automatically detects locales from global settings. You'll need to:
Connect the asset table to the Asset Table Collection
field
Set the asset type
Drag asset folders for each locale into the appropriate slots
The system still matches assets to lines based on filename-to-line-ID mapping. After running the wizard, you can verify the configuration by examining the asset table:
At runtime, the Unity Localisation line provider uses these tables to find both text content and associated assets (like lip-sync data) for each line. The presenters receive complete, ready-to-use lines without needing to understand how the provider works.
IAssetProvider
The IAssetProvider
interface addresses the challenge of handling unknown asset types. Default presenters (including the voice over presenter) are designed to work both independently and in combination, so they can't make assumptions about what kinds of assets they'll receive.
While most often the asset will be an audio file, many situations require something more complex. In this sample, we use a custom LipSyncedVoiceLine
scriptable object that implements IAssetProvider
. This object contains both an audio file and viseme timing data.
Through the IAssetProvider
interface, different presenters can request specific asset types in a consistent way without worrying about implementation details. Conforming to this interface requires implementing just two methods: TryGetAsset<T>
and GetAssetsOfType<T>
.
If you're not using default presenters and don't need interoperability between assets and views, you can use whatever asset types you prefer. However, if you want compatibility between custom and default presenters, implementing IAssetProvider
is highly recommended.
The remaining components of the sample are more straightforward, as the localisation and line providers handle most of the complexity. Two presenters are particularly relevant:
This presenter requires an audio asset to function. It first checks if the line has an associated asset, and whether that asset is audio. If so, it plays the audio directly.
If the asset isn't audio but implements IAssetProvider
(as in this sample), the presenter requests an audio asset from the provider and then plays it. This demonstrates the primary purpose of the IAssetProvider
interface—allowing different types of data to be bundled into a single container.
This component has a more straightforward implementation. Since we know the asset type in this sample, we can skip some checks and directly request lip-sync timing data from the IAssetProvider
. With this data, the component modifies the character's face to match the appropriate visemes at the correct times during audio playback.
Learn how to make options that timeout after a period of inactivity from the user.
This sample demonstrates how to create a custom dialogue presenter where dialogue options include a timeout bar. When options are presented, the timeout bar gradually shrinks. If the player doesn't select an option before the timer expires, the system automatically selects one for them.
The sample supports three variations of this functionality:
A hidden option that gets selected when time runs out
A visible option designated as the default that gets selected when time runs out
The currently highlighted option gets selected when time runs out
For simplicity, this guide focuses on implementing the first approach. To explore the other methods, check out the full sample.
Custom Dialogue Presenters
Using Metadata to control views
The nightmare hellscape that is Unity UI
If you want to see a finished version of this go to PATH TO SAMPLE.
If you haven't already install Yarn Spinner link to install guide. Once installed we will start by building out a basic scene.
Make a new scene in Unity.
From Samples/Shared Assets/Prefabs
drag the Basic Arena into the scene
From Samples/Shared Assets/Prefabs
add a Camera Rig into the scene
From Samples/Shared Assets/Prefabs
drag a Player prefab into the scene
From Samples/Shared Assets/Prefabs
drag an NPC prefab into the scene and rename it to be Alice
Add a default dialogue system to the scene, in the Hierarchy right click Yarn Spinner -> Dialogue Runner
Create a new Yarn Spinner project Assets -> Create -> Yarn Spinner -> Yarn Project
and name it Timeout Dialogues
Create a new Yarn script Assets -> Create -> Yarn Spinner -> Yarn Script
and name it Alice
Delete the previous camera called Main Camera
Replace the contents of the Alice Yarn script with the following:
title: Alice
---
Alice: Hello, this is the fallback timeout sample
-> Option 1?
Alice: option 1 was selected
-> Option 2?
Alice: option 2 was selected
-> Option 3? #fallback
Alice: option 3 was selected despite not being visible
===
This script contains a single node with three options. Only the first two will be visible to the player. The third option (marked with the #fallback
metadata) will be hidden but selected automatically if the timer expires before the player makes a choice.
Select the Camera Rig and in the inspector drag the Player into the target field
Select Alice and in the Inspector set the Dialogue field to use the new Timeout Dialogues
project
Select Alice and in the Inspector select the Alice node in the dropdown
Select Alice and in the Dialogue Runner field drag in the Dialogue System object from the hierarchy
Select the Dialogue System and in the Inspector set the Yarn Project to use the Timeout Dialogues
project
At this point, you can talk to Alice and go through the dialogue, but the timeout functionality isn't implemented yet. Now we'll create our custom presenter by building a UI bar that shrinks over time, then develop a custom dialogue presenter that uses it.
First, we'll create the UI elements for our timer by modifying the existing canvas from the Dialogue System prefab.
Right click on the Dialogue System in the hierarchy and choose Prefab -> Unpack Completely
This will disconnect this from being a prefab but will otherwise leave it in the state we want.
Expand the Dialogue System out in the hierarchy so we can see all the individual components we are interested in the children and grandchildren of the Option View gameobject.
Select the Last Line game object and delete it. While we could handle also showing the last line it would just add more logic to our view while not actually showing off the main feature of this sample. So instead we'll just drop it.
Add a new empty child gameobject to the Options View and name it timer container
, this will be what holds our timer bar. By default it will have inherited a slew of position and size properties from it's parent's vertical layout group, which is mostly what we want, but we do need it to have a specific height.
In the inspector add a new Layout Element component to the timer container.
Tick the Preferred Height field and set it to have a height of 30
. Now this element will tell it's parent layout group that it is going to be 30 units high which is perfect.
Add a new empty gameobject to the timer container and name it timer bar
.
Give the bar an image component.
Change the bars rectangle widget to be centre-aligned + vertical stretch.
Set the width to be whatever you like the look of, we found that 1480 units was a nice value.
This bar will shrink as the time to select an option runs out.
Our UI work is now complete. Next, we'll write the code to make the bar shrink over time.
Create a new monobehaviour script and name it TimeoutBar.cs
Open up the new file and replace the imports with the following:
using System.Threading;
using UnityEngine;
using Yarn.Unity;
Add the following fields to the class:
[SerializeField] RectTransform bar;
private float originalSize = 0f;
These fields represent the bar UI element and its original size, which we'll use to reset it between option sets.
Replace the Start
method with the following:
void Start()
{
if (bar != null)
{
originalSize = bar.sizeDelta.x;
}
}
When Unity initializes the component, we store the default bar size in originalSize
for later reference.
Add the following new method:
public void ResetBar()
{
if (bar != null)
{
bar.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, originalSize);
}
}
This method resets the bar to its original size. There are several ways to accomplish this, but using SetSizeWithCurrentAnchors
is straightforward and lets Unity handle the rectangle's final dimensions.
Add the following new method:
public async YarnTask Shrink(float duration, CancellationToken cancellationToken)
{
if (bar == null)
{
return;
}
float accumulator = 0;
var currentSize = bar.sizeDelta.x;
while (accumulator < duration && !cancellationToken.IsCancellationRequested)
{
accumulator += Time.deltaTime;
var newSize = Mathf.Lerp(currentSize, 0, accumulator / duration);
bar.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, newSize);
await YarnTask.Yield();
}
bar.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 0);
}
This method gradually shrinks the bar over the specified duration. It first verifies that the bar exists, then enters a loop that continues until either the duration elapses or cancellation is requested. Each iteration updates the bar size using linear interpolation from its current size toward zero. After the loop, we ensure the bar is fully shrunk regardless of how it exited the loop.
Add that class as a component to our bar
Drag the bar gameobject into the Bar field in the TimeoutBar inspector and now the bar is done.
To see a complete version of this check out path to the file in the samples.
Now we'll create the custom dialogue presenter subclass. Since this involves substantial code, we'll approach it in sections.
Create a new presenter subclass Assets -> Create -> Yarn Spinner -> Create -> Dialogue View Script
and name it TimeoutOptionsView
. This creates a stubbed out subclass with all the necessary methods for a custom view.
Replace the imports with the following:
using System.Threading;
using System.Collections.Generic;
using UnityEngine;
using Yarn.Unity;
Add the following fields to the class:
[SerializeField] CanvasGroup canvasGroup;
[SerializeField] OptionItem optionViewPrefab;
[SerializeField] TimeoutBar timedBar;
private List<OptionItem> optionViews = new List<OptionItem>();
These fields reference the UI elements we need to manage: the canvas group for fade effects, the option item prefab, the timer bar, and a list to cache option items. Other than timedBar
, these are similar to what you'd find in the default options presenter.
Add the following fields to the class:
public float autoSelectDuration = 10f;
public float fadeUpDuration = 0.25f;
public float fadeDownDuration = 0.1f;
These fields control the timing aspects: how long the player has to select an option before timeout, and the duration of fade-in and fade-out animations.
Add the following field to the class:
private const string HiddenFallback = "fallback";
This constant identifies the option that should be selected when the timer expires. We'll use it later when examining option metadata.
Replace OnDialogueStartedAsync
with the following:
public override YarnTask OnDialogueStartedAsync()
{
if (canvasGroup != null)
{
canvasGroup.alpha = 0;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
}
return YarnTask.CompletedTask;
}
Replace OnDialogueCompleteAsync
with the following:
public override YarnTask OnDialogueCompleteAsync()
{
if (canvasGroup != null)
{
canvasGroup.alpha = 0;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
}
return YarnTask.CompletedTask;
}
These methods are called by the dialogue runner at the beginning and end of dialogue. Both hide and disable the options UI, then return a completed task.
Replace RunLineAsync
method with the following:
public override YarnTask RunLineAsync(LocalizedLine line, LineCancellationToken token)
{
return YarnTask.CompletedTask;
}
Since this presenter only handles options, we can return immediately when asked to run a normal line.
Replace Start
with the following:
void Start()
{
if (canvasGroup != null)
{
canvasGroup.alpha = 0;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
}
}
Similar to the dialogue event handlers, we initialize the UI as hidden and non-interactive.
Finally add a new internal enum to the class:
enum TimeOutOptionType
{
None, HiddenFallback,
}
This enum identifies which timeout behavior we're using. The default is None
, meaning no timeout is needed for the current option set.
Now we'll implement the RunOptionsAsync
method, starting with determining which timeout option to use and validating the option group.
Inside the RunOptionsAsync
method add the following code:
int hasTimeOutMetadata = 0;
TimeOutOptionType defaultOptionType = TimeOutOptionType.None;
DialogueOption defaultOption = null;
These variables will track whether we need a timeout and which option should be selected if timeout occurs.
Add the following code:
// we run through all the options quickly to see if there are any that are configured for timeouts
foreach (var option in dialogueOptions)
{
foreach (var metadata in option.Line.Metadata)
{
if (metadata == HiddenFallback)
{
// if the hidden fallback option has failed it's condition then we don't want it to impact the rest of the option group
if (!option.IsAvailable)
{
continue;
}
defaultOptionType = TimeOutOptionType.HiddenFallback;
defaultOption = option;
hasTimeOutMetadata += 1;
break;
}
}
}
This code examines all the options to find any with the fallback
metadata tag. If it finds one that's available, it marks that option as the hidden fallback to be selected when the timer expires. We also count how many tagged options we've seen for validation purposes.
Add the following code:
// now we do some error checking
// it is possible there are multiple options flagged as the fallback which we don't want
if (defaultOptionType == TimeOutOptionType.HiddenFallback)
{
// this means we need to have only found one default tagged line
if (hasTimeOutMetadata != 1)
{
Debug.LogError("Encountered more than one option with timeout tags");
// we return
return await DialogueRunner.NoOptionSelected;
}
// and we need to have the defaultOption value be not null
if (defaultOption == null)
{
Debug.LogError("Encountered have an option tagged as a fallback but have no option value set.");
// we return
return await DialogueRunner.NoOptionSelected;
}
}
This validation ensures that if we're using a hidden fallback, we have exactly one option tagged as such, and we've successfully identified which option it is. If either condition fails, we log an error and tell the dialogue runner that no option was selected.
Next, we'll set up the infrastructure to handle completion and cancellation. These mechanisms will be provided to the option items later.
Add the following code to create our completion source:
// A completion source that represents the selected option.
YarnTaskCompletionSource<DialogueOption> selectedOptionCompletionSource = new YarnTaskCompletionSource<DialogueOption>();
Add the following code to create our cancellation source:
// A cancellation token source that becomes cancelled when any option item is selected, or when this entire option view is cancelled
var completionCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
These sources work together to handle the two possible outcomes: the player selects an option, or the dialogue is cancelled. Now we'll implement the cancellation handling:
Add the following encapsulated method:
async YarnTask CancelSourceWhenDialogueCancelled()
{
await YarnTask.WaitUntilCanceled(completionCancellationSource.Token);
if (cancellationToken.IsCancellationRequested == true)
{
// The overall cancellation token was fired, not just our internal 'something was selected' cancellation token.
// This means that the dialogue view has been informed that any value it returns will not be used.
// Set a 'null' result on our completion source so that that we can get out of here as quickly as possible.
selectedOptionCompletionSource.TrySetResult(null);
}
}
// Start waiting
CancelSourceWhenDialogueCancelled().Forget();
This task monitors for dialogue cancellation and ensures proper cleanup by setting a null result on the completion source if needed. This connects the two sources: if dialogue is cancelled, this function sets the completion value on selectedOptionCompletionSource
.
Now we'll create and configure the option items to display to the user:
Add the following code to instantiate new option items as needed:
if (optionViews.Count < dialogueOptions.Length)
{
// we want to create as many new options as the difference between the current cached ones we have and the size of the option group
var newViews = dialogueOptions.Length - optionViews.Count;
for (int i = 0; i < newViews; i++)
{
var option = CreateNewOptionView();
optionViews.Add(option);
}
}
This creates just enough option item prefab instances to handle the current option group, reusing any we've already created. The CreateNewOptionView
method will be implemented later.
Add the following code to configure the option items:
// configuring all the dialogue items
for (int i = 0; i < dialogueOptions.Length; i++)
{
var optionView = optionViews[i];
var option = dialogueOptions[i];
if (option.IsAvailable == false)
{
// option is unavailable, skip it
continue;
}
// if we are set to have a hidden fallback option
// and that option is THIS option we are configuring the view for
// we want to skip over it, it will be visually represented by the timer bar
if (defaultOptionType == TimeOutOptionType.HiddenFallback && defaultOption != null && option.DialogueOptionID == defaultOption.DialogueOptionID)
{
continue;
}
optionView.gameObject.SetActive(true);
optionView.Option = option;
optionView.OnOptionSelected = selectedOptionCompletionSource;
optionView.completionToken = completionCancellationSource.Token;
}
// There is a bug that can happen where multiple options can be flagged as highlighted at once
// so if one is already selected then we use that, otherwise we highlight the first non-disabled option
int optionIndexToSelect = -1;
for (int i = 0; i < optionViews.Count; i++)
{
var view = optionViews[i];
if (!view.isActiveAndEnabled)
{
continue;
}
if (view.IsHighlighted)
{
optionIndexToSelect = i;
break;
}
// ok at this point the view is enabled
// but not highlighted
// so if we haven't already decreed we have found one to select
// we select this one
if (optionIndexToSelect == -1)
{
optionIndexToSelect = i;
}
}
if (optionIndexToSelect > -1)
{
optionViews[optionIndexToSelect].Select();
}
This code configures each option view with its corresponding option data, skipping any that are unavailable or marked as hidden fallbacks. It then selects the first valid option to be highlighted initially.
Add the following code to enable or disable the timer bar:
// now we add in the timer bar if necessary or turn it off if it isn't needed
if (defaultOptionType == TimeOutOptionType.None)
{
if (timedBar != null)
{
timedBar.gameObject.SetActive(false);
}
}
else
{
// we always want it at the bottom regardless of how many option item views there are
if (timedBar != null)
{
timedBar.gameObject.SetActive(true);
timedBar.ResetBar();
timedBar.transform.parent.SetAsLastSibling();
}
}
This activates the timer bar only if we're using a timeout option, resetting it to full size and ensuring it appears at the bottom of the option list.
Now we'll display the UI to the player:
Add the following code to fade in the UI:
// fade up the UI now
await Effects.FadeAlphaAsync(canvasGroup, 0, 1, fadeUpDuration, cancellationToken);
// allow interactivity and wait for an option to be selected
if (canvasGroup != null)
{
canvasGroup.interactable = true;
canvasGroup.blocksRaycasts = true;
}
This fades in the UI and enables user interaction once the fade is complete.
Add the following code to start the timeout bar if needed:
// now we kick off the timer bar if needed
if (defaultOptionType == TimeOutOptionType.HiddenFallback)
{
BeginDefaultSelectTimeout(selectedOptionCompletionSource, defaultOption, completionCancellationSource.Token).Forget();
}
If we're using a hidden fallback option, this starts the timer that will eventually select it. The BeginDefaultSelectTimeout
method will be implemented later.
Add the following wait code:
// Wait for a selection to be made, or for the task to be completed.
var completedTask = await selectedOptionCompletionSource.Task;
This suspends execution until the player selects an option, the timer expires, or dialogue is cancelled.
After a selection is made, we need to clean up and exit:
Add the following code:
completionCancellationSource.Cancel();
// now one of the option items has been selected so we do cleanup
if (canvasGroup != null)
{
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
}
This cancels the completion source to prevent further selections and disables UI interaction.
Add the following code to fade out the UI:
// fade down
await Effects.FadeAlphaAsync(canvasGroup, 1, 0, fadeDownDuration, cancellationToken);
// disabling ALL the options views now
foreach (var optionView in optionViews)
{
optionView.gameObject.SetActive(false);
}
This fades out the UI and hides all option views, though they remain cached for future use.
Add the following code to return the selected option:
// if we are cancelled we still need to return but we don't want to have a selection, so we return no selected option
if (cancellationToken.IsCancellationRequested)
{
return await DialogueRunner.NoOptionSelected;
}
// finally we return the selected option
return completedTask;
This returns the selected option to the dialogue runner, or indicates that no option was selected if dialogue was cancelled.
Now we need to implement the methods we've referenced but haven't defined yet:
Add the following new method:
private OptionItem CreateNewOptionView()
{
var optionView = Instantiate(optionViewPrefab);
var targetTransform = canvasGroup != null ? canvasGroup.transform : this.transform;
if (optionView == null)
{
throw new System.InvalidOperationException($"Can't create new option view: {nameof(optionView)} is null");
}
optionView.transform.SetParent(targetTransform.transform, false);
optionView.transform.SetAsLastSibling();
optionView.gameObject.SetActive(false);
return optionView;
}
This creates a new option item instance, adds it to the canvas group, positions it at the end of the list, and disables it by default.
Add the BeginDefaultSelectTimeout
method:
internal async YarnTask BeginDefaultSelectTimeout(YarnTaskCompletionSource<DialogueOption> selectedOptionCompletionSource, DialogueOption option, CancellationToken cancellationToken)
{
if (timedBar == null)
{
return;
}
await timedBar.Shrink(autoSelectDuration, cancellationToken);
if (!cancellationToken.IsCancellationRequested)
{
selectedOptionCompletionSource.TrySetResult(option);
}
}
This method initiates the timer bar shrinking animation. When complete (if not cancelled), it sets the designated fallback option as the selected choice.
Finally, let's hook everything up in Unity:
Move back over to Unity and select the Options View in the hierarchy.
Add the TimeoutOptionsView.cs
script as a new component to the Option View gameobject.
Delete the Options Presenter
component.
Select the Options View gameobject and in the Canvas Group field drag the Options View into this field.
From Packages/Yarn Spinner/Prefabs
folder drag the Option Item prefab into the Option View Prefab field.
Drag the timer gameobject into the Timed Bar field.
Pick a duration for the Auto Select, Fade Up, and Fade Down durations.
Now we need to tell the dialogue runner about our custom presenter:
Select the Dialogue System game object in the hierarchy.
Remove the old (now missing) Options Presenter from the Dialogue Views field.
Add the new timeout options view into it's place.
Take it for a spin!
Congratulations! You've created a custom options presenter that supports timed dialogue choices. When the timer expires, the system automatically selects a predefined fallback option, adding a sense of urgency to dialogue interactions.
Learn about the Basic Storylets and Saliency Sample, which shows off the fundamentals of Yarn Spinner's saliency systems.
Yarn Spinner 3 introduced storylets, a powerful new way to select which content should be presented to players.
Storylets allow you to break your narrative into modular chunks, with a saliency strategy determining which chunk should run next based on game state and other factors.
Yarn Spinner provides two different approaches to storylets, Node Groups and Line Groups, along with several Saliency strategies to fit your specific needs.
This guide focuses on getting you comfortable with the basics of storylets in Yarn Spinner.
Writing node group storylets
Writing line group storylets
Calling salient content
Interacting with generated variable storage systems
Our Basic Storylets and Saliency Sample demonstrates various things you can do with storylets in Yarn Spinner:
Content for three in-game days (Monday, Tuesday, and Wednesday)
Two time periods each day (morning and evening)
A time advancement mechanic in the center of the scene
Several characters showcasing different approaches to storylets
The time advancer controls what day and time the NPCs respond to. Each time you press the button, time advances one step through the sequence:
Monday morning → Monday evening → Tuesday morning → and so on.
When reaching Wednesday evening, it wraps back to Monday morning
The implementation is straightforward: when you collide with the trigger, it calls the AdvanceTime()
method and updates the plinth label via UpdateLabel()
.
Both methods work by reading and modifying Yarn variables ($day
and $time
) through a generated variable storage class.
Yarn Spinner allows you to generate a variable storage class that provides convenient wrappers around your declared variables. The system automatically regenerates this file whenever you change your Yarn files or project settings.
This creates two properties on the class—Day
and Time
—which you can access directly in your code. The Yarn Spinner-generated code:
Handles all standard variable storage lookup operations
Performs type checking automatically
Significantly reduces the amount of code you need to write
Generates enums matching the types defined in the Setup
node
Each NPC in the scene demonstrates a different aspect of storylets through their DialogueInteractible
component.
This component determines which node runs when the player interacts with the character.
The component pulls available nodes directly from the attached Yarn project. When triggered, it simply tells the DialogueRunner to run the selected node—just like if you wrote <<jump npc_name>>
in your Yarn Spinner Script.
Each NPC runs a single entry node, and the storylets system handles the rest. But how do storylets actually work in practice?
While storylet implementation depends heavily on your specific narrative needs, our sample focuses on a common use case: NPCs making contextual comments based on the current day and time.
This example functions as a "barks" demo—short, reactive dialogue lines—which provides an excellent introduction to storylets and salient content. Each NPC demonstrates a different approach to storylets, all defined in the BasicSaliency.yarn
file.
George demonstrates the simplest approach using Line Groups.
Each line in a block that starts with =>
represents one potential candidate for selection in that line group. Yarn Spinner chooses only a single line to present each time the line group is reached.
George's dialogue includes:
Two generic lines with no conditions (can run anytime)
Three conditional lines that filter based on the current day
One line with nested content that runs if that line is selected
For example, the line => another Monday, I hate Mondays <<if $day == .Monday>>
will only be shown if the current day is Monday.
Liz demonstrates Node Groups with the "always" condition.
A node group consists of multiple nodes sharing the same title
. When selecting which node to run, Yarn Spinner examines the when
headers to determine what should be shown.
Liz's nodes all use the when: always
header, indicating they're always valid choices. This special header is particularly useful for fallback content.
One of Liz's nodes uses another special header, when: once
, which tells Yarn Spinner this node should only be shown once. After being selected, it will never appear again—perfect for character introductions or one-time events.
Barry also uses node groups, but with conditional expressions in the when
headers.
Each of Barry's nodes checks the time of day using expressions like when: $time == .Morning
or when: $time == .Evening
. This means that every time you talk to Barry, half of his potential dialogue nodes are filtered out based on the current time.
This approach lets you easily filter nodes to present only content appropriate to the current game state.
Alice demonstrates the most specific approach with multiple when
conditions.
Each of her nodes contains multiple when
clauses, creating highly specific combinations of day and time conditions. This gives Alice the most contextually appropriate responses but requires the most content creation.
This highlights an important tradeoff: greater specificity requires more storylets. However, this is also a strength of the system. You can:
Use fallback storylets (like Liz's) for situations that don't need specific responses
Omit storylets for combinations where characters have nothing important to say
Create a more precisely woven narrative landscape with less effort than traditional approaches
Most NPCs in our sample use node groups, meaning their nodes share the same title. While this would normally cause an error in Yarn Spinner, it's intentional with storylets.
Any nodes with the same name become part of the same node group. When that group is requested to run, Yarn Spinner selects one node to present.
Important requirements for node groups:
Each node in the group must share the same title
Every node must have at least one when
header to be recognized as part of a group
If you don't have specific conditions, use when: always
as a fallback
Learn more about Node Groups.
With multiple potential storylets available, how does Yarn Spinner decide which one to show?
This is handled by the saliency strategy, which selects the most appropriate content while resolving ambiguity.
The default strategy (used in this sample) is "Random Best Least Recently Seen," which:
Removes any storylets with failing conditions
Counts the conditions on each viable storylet to determine its "complexity"
Selects the storylet with the highest complexity (most conditions)
If multiple storylets tie for complexity, deprioritizes any that have been recently seen
If there's still a tie, makes a random selection
This approach typically provides the best experience in most narrative situations, balancing specificity with variety. Yarn Spinner includes other built-in strategies, and you can create custom strategies for precise control over content selection.
Storylets provide a flexible and powerful way to organize narrative content in Yarn Spinner. By breaking your story into modular chunks and using saliency strategies to select the most appropriate content, you can create dynamic, responsive narratives that adapt to your game state.
Whether you're implementing character barks, branching dialogues, or more complex narrative systems, storylets offer a streamlined approach to creating engaging, reactive stories in your games.
Learn how to make a Phone Chat view and explore our Phone Chat Sample.
The Phone Chat sample demonstrates how to create a Dialogue Presenter that shows conversations in a scrolling view, similar to how messages on a phone look.
Lines of dialogue are shown as bubbles in the scrolling view, and are kept on screen after they're delivered, allowing the user to look up and see previous messages. Additionally, as lines appear, they're shown with a "typing" indicator.
In this tutorial, you'll build a Dialogue Presenter that looks like this:
You can find a completed version of this project as part of our Samples.
We'll start by creating a new empty Unity project, and adding Yarn Spinner.
Open Unity Hub.
Click New Project, and create a new project. In this tutorial, we'll use the Universal 3D template, though the template itself doesn't really matter.
Follow the steps in Installation for Unity to install Yarn Spinner in your project.
Next, we'll download the assets needed by this tutorial.
Download the assets for this tutorial, and import them into your project.
We're ready to start creating our scene!
Now that we've created the project, we'll begin by laying out the UI. We'll start from an empty scene, and build a canvas that will present the Phone Chat system.
Open the File menu, and choose New Scene. Select the Empty template.
Save the new scene somewhere in your project.
In our empty scene, we'll start by creating the camera that you view the scene with.
Create a new camera by opening the GameObject menu and choosing Camera.
Select the new camera, and in the Inspector, right click the Transform, and choose Reset.
Next, we'll create the canvas that the Phone Chat Presenter appears in.
Open the GameObject menu, and choose UI -> Canvas.
Select the new canvas in the Hierarchy, and find the Canvas Scaler component.
Set its UI Scale Mode to "Scale with Screen Size".
Set its Reference Resolution to 1920 by 1080.
We're now ready to start building the UI itself. The most important part of a scrolling chat view is the scroll view, so let's create that!
Open the GameObject menu, and choose UI -> Scroll View.
Select the newly created Scroll View in the Hierarchy.
In the Inspector, find the Transform component.
Set the Pos X and Pos Y values to 0,0.
Set the Width to 600, and the Height to 900.
Our scroll view won't need any scroll-bars, so we'll get rid of them now. We'll also set up the scroll view so that it only scrolls vertically.
Select the Scroll View object in the Hierarchy.
In the Inspector, find the Scroll Rect component.
Select the Horizontal Scrollbar field, and press Backspace to clear it. Do the same thing for the Vertical Scrollbar field.
Turn off the Horizontal checkbox, so that it doesn't scroll horizontally.
Delete the Scrollbar Horizontal and Scrollbar Vertical objects from the scene.
Next, we'll set up the scroll view to use our background image.
In the Inspector, find the Image component.
Change the Color property to fully opaque white.
Change the Source Image property to the BG-Lightmode
sprite.
Because we removed the scroll bars, we'll need to make the viewport of the scroll view fill the available area:
Select the Viewport object in the Hierarchy.
In the Transform component, set the Left, Right, Top and Bottom values all to 0.
We're almost done setting up the scroll view. The next thing to do is to make the content in the scroll view appear at the bottom of the view. We'll do this by setting up the Anchors of the Content view so that it's pinned to the bottom of its parent. This means that as bubbles get added to the Content object, the content object will grow upwards (rather than downwards).
Select the Scroll View -> Viewport -> Content object in the Hierarchy.
Find the Transform component in the Inspector.
Set the Anchors -> Min to X: 0, Y: 0.
Set the Anchors -> Max to X: 1, Y: 0.
Set the Pivot to X: 0.5, Y: 0.
We'll now make all of the content in the Scroll View automatically laid out in a vertical list.
With the Content object still selected, click Add Component at the bottom of the Inspector.
Search for "Vertical Layout Group", and add one to the object.
Set the new Vertical Layout Group's Spacing to -10.
Set its Child Alignment to Lower Center.
Check the Control Child Size -> Width and Height boxes.
Check the Child Force Expand -> Width box, and uncheck the Height box.
This will cause the items in the list to fill the entire width of the list. The height of each of the elements will be determined by the items themselves, using a component we'll add later.
We'll now also make the content update its own total size based on its contents.
Click the Add Component button at the bottom of the Inspector.
Search for "Content Size Fitter" and add one to the object.
Set the Horizontal Fit of the new Content Size Fitter to Unconstrained.
Set the Vertical Fit to Preferred Size.
Next, we'll create the bubble itself.
The goals for the bubble are that the text fits in the available width, and it takes up as much height as it needs given that width. We also want to show a speech bubble image behind the text, and we want that image to only be as wide as the text - it shouldn't take up the full width of the chat view.
This mimics the way that conversations work in popular chat apps: the speech bubble graphic doesn't take up the full width of the row that it exists in.
Select the Content object in the Hierarchy.
Open the GameObject menu, and choose Create Empty Child.
Name the newly created object "Message Bubble".
Because it's a child of the Vertical Layout Group we added earlier, it will automatically take up the full width of the list. We'll also want the bubble to have the correct height, based on the amount of text it has, but because we haven't set up the text component yet, that isn't available. In the meantime, we'll manually set up the bubble to have a fixed height, and we'll replace this later.
Select the Message Bubble object.
Add a new Layout Element component to the object.
Check the Min Height box, and set its value to 200.
We'll now add an object that manages the width of the bubble graphic:
Create a new empty child game object of the Message Bubble.
Name the new object "Message Content".
Add a Layout Element component to it, and set its Min Width to 300.
Add a Content Size Fitter component to it, and set its Horizontal Fit to Min Size.
Next, we'll make the bubble be right-aligned inside its content, and fill the entire height of the row:
With the Message Content object selected, go to the Transform component.
Set the Anchors -> Min to X: 1, Y: 0.
Set the Anchors -> Max to X: 1, Y: 1.
Set the Pivot to X: 1, Y: 0.5.
Set Pos X, Top and Bottom all to 0.
Now we'll add the bubble background image itself. We'll add a new object, set up its anchors so that they fill the parent object, and then add the image:
Add a new empty child object of Message Content.
Name the new object "Background".
In the Transform, set the Anchors -> Min to X: 0, Y:0.
Set the Anchors -> Max to X: 1, Y:1.
Set the Left, Top, Right and Bottom values all to 0.
Add a new Image component.
Set the Source Image of the new component to Bubble Green.
Your view should now look like this:
Now we'll add the text component, which will show the text of the image. The text object's anchors will be set up so that they're pinned to all for edges of the container, making it fill up the parent object.
Add a new empty child object of Message Content. Name it "Text".
Set its Anchors -> Min to X:0, Y:0.
Set its Anchors -> Max to X:1, Y:1.
Set its Left, Top, Right, Bottom all to 0.
Add a TextMeshPro - Text (UI) component to the object.
Set the Font to Montserrat-Regular. (This is one of the assets that you downloaded when you began this tutorial.)
Set the Text to something temporary, like "This is a test".
Scroll down to Extra Settings, and click it to expand it.
Set the Margins to:
Left: 55
Top: 30
Right: 55
Bottom: 35
The bubble should now look like this:
The bubble now has the right graphics, but its size could be improved. We'll now add some code that makes the various parts of the bubble correctly sized. To get the best result, we need to think about the overall layout here:
The Vertical Layout group is controlling the width of the Message Bubble element, and we want to make each Message Bubble control its own height.
Inside the Message Bubble, we also want the visible bubble to be as wide as it needs to be to show the text, but no wider than that (that is, there should be a gap on the side).
To make this work, we'll create a component that calculates the required size of the text box, and use it in two places:
The Message Bubble will use it to calculate its height (with its width being driven by the Vertical Layout Group on Content).
The Message Content will use it to calculate its width (with its height inherited from its parent).
Open the Assets menu, and choose Create -> MonoBehaviour Script.
Name the new script "UseSizeOfText".
Double-click the new script to open it in your text editor.
Delete the contents of the file and replace it with this:
using UnityEngine;
using UnityEngine.UI;
using TMPro;
[ExecuteAlways]
[RequireComponent(typeof(RectTransform))]
public class UseSizeOfText : MonoBehaviour, ILayoutElement
{
private TMP_Text Text => GetComponentInChildren<TMP_Text>();
private RectTransform RectTransform => GetComponent<RectTransform>();
float ILayoutElement.preferredHeight => minHeight;
float ILayoutElement.preferredWidth => minWidth;
float ILayoutElement.flexibleWidth => 0f;
float ILayoutElement.flexibleHeight => 0f;
public float minWidth { get; private set; } = 0f;
public float minHeight { get; private set; } = 0f;
int ILayoutElement.layoutPriority => 0;
[SerializeField] float minimumWidth = 250f;
[SerializeField] float minimumHeight = 30f;
void ILayoutElement.CalculateLayoutInputHorizontal() { }
void ILayoutElement.CalculateLayoutInputVertical() { }
}
This code implements a basic layout element, which is something that the layout system can ask questions of about what size it wants to be. - we'll now add code to it to make it derive its size from the text element.
Add the following method to the UseSizeOfText
class:
private void UpdateLayout(TMP_TextInfo info)
{
if (info == null || info.textComponent == null || string.IsNullOrEmpty(info.textComponent.text))
{
minHeight = minimumHeight;
minWidth = minimumWidth;
return;
}
// Calculate the maximum width available to us by getting our
// parent's width
var parentWidth = RectTransform.parent.GetComponent<RectTransform>().rect.width;
// Get the left and right margins of the text component
var xMargin = info.textComponent.margin.x + info.textComponent.margin.z;
// Get the total width available for drawing text
var insetSize = parentWidth - xMargin;
// Compute the rectangle we'd need to draw the text in, given our
// available width and an (effectively) unlimited amount of vertical
// space
var size = info.textComponent.GetPreferredValues(info.textComponent.text, insetSize, float.MaxValue);
// Our minimum width and height are now based on this (we add a
// slight padding to the width)
minHeight = Mathf.Max(minimumHeight, size.y);
minWidth = Mathf.Max(minimumWidth, size.x + 5);
// Now that we know our minimum width and height, ask the layout
// system to rebuild our layout
LayoutRebuilder.MarkLayoutForRebuild(RectTransform);
}
This method takes information describing the text in a TextMeshPro element, calculates the smallest size it needs to be given its constraints, and asks the layout system to rebuild the layout.
Lastly, we need the layout to update whenever the text changes. To do this, we'll make use of the OnPreRenderText
event, which is called immediately before the text in a TextMeshPro component is rendered as a mesh. When this happens, it's likely that our required size has changed, so we tell the layout to update.
Add the following methods to UseSizeOfText
:
protected void OnValidate() => UpdateLayout(null);
protected void OnEnable()
{
if (Text != null)
{
Text.OnPreRenderText += UpdateLayout;
UpdateLayout(Text.textInfo);
}
}
protected void OnDisable()
{
if (Text != null)
{
Text.OnPreRenderText -= UpdateLayout;
}
}
Now that we've built a component that notifies the layout system of the necessary size of the text, we'll use it in our setup.
Select the Message Content object in the Hierarchy.
Remove the Layout Element component from it.
Add a new Use Size Of Text component.
The bubble will resize to fit the width of the text. We now need to make the row take the correct height.
Select the Message Bubble object in the Hierarchy.
Remove the Layout Element component from it.
Add a new Use Size Of Text component.
Your bubble should now look like this:
When you're using a chat app, you see a typing indicator while the person on the other end is typing. Let's set up our chat bubbles so that they can show an animation for a short duration before revealing the text, as though the messages was being "typed out".
The way that the typing animation will look will be three dots, which bounce up and down in sequence.
Our first step will be to set up an object inside the Message Bubble prefab to act as our typing indicator. This object will act as a container for the three dots, so it needs to fill the entire parent object.
Create a new empty game object as a child of Message Content. Name it "Typing Indicator".
In the Inspector for the Transform component, set its Anchor Min to X:0, Y:0.
Set its Anchor Max to X:1, Y:1.
Set its Top, Left, Right, and Bottom all to 0.
Let's now add the dots themselves!
Add three new empty game objects as children of Typing Indicator. Name them Dot 1
, Dot 2
, Dot 3
.
Select all three of the Dot objects you just created.
Add an Image component to all of them.
Drag and drop the Dot sprite from the downloaded assets into the Source Image slot.
Click on the Color value, and change the Alpha value to 128, to make it semi-transparent.
Set the Pos Y for all of them to 0.
Set the Width and Height for all of them to 20.
Now that we've set up all of their common properties, we'll lay each of them out.
Select Dot 1, and set its Pos X to -25.
Select Dot 2, and set its Pos X to 0.
Select Dot 3, and set its Pos X to 25.
Finally, we'll set them up so that they bounce up and down while visible.
Open the Assets menu, and choose Create -> Animation -> Animation Clip.
Name the new animation clip "Typing".
Select the new animation clip, and in the Inspector, turn on Loop Time.
Drag and drop this new clip onto the Typing Indicator object.
This will create a new Animator Controller that uses this clip, and then add an Animation component to the Loading Indicator that uses the controller.
Next, we'll set up the animation itself.
Open the Window menu, and choose Animation -> Animation. (You may want to dock the tab in your Unity editor window, if it isn't already.)
Select the Loading Indicator object.
The Animator window should be showing the Typing animation. You can check by looking at the dropdown menu at the top-left of the pane.
In the Animator window, Click Add Property.
Choose Dot 1 -> Rect Transform -> Anchored Position.
Repeat this process for Dot 2 and Dot 3.
By default, the newly created channels will have a keyframe at frame 0, and another at frame 60.
Select the keyframe at frame 60 and press Ctrl-C.
Move the playhead to frame 30 and press Ctrl-V, to make a copy of it.
We'll now create a new keyframe that represents the high point of each dot's bounce.
Move the playhead to frame 15.
Click the Record button at the top left of the Animation pane.
In the Scene view, drag all three Dot objects up so that their Pos Y is 25.
Click the Record button again to leave recording mode.
Click the Preview Play button in the Animation window and watch as they all bounce up together.
Now that the dots are moving, we'll adjust the timing so that they bounce up in sequence - first one, then two, then three.
Select all of the keyframes for Dot 2 and shift them 30 frames to the right.
Select all of the keyframes for Dot 3 and shift them 60 frames to the right.
Click the Preview Play button again and watch as they bounce up in sequence.
Looking good!
We're done with our basic bubble. Let's now create prefabs for it.
Drag the Message Bubble object from the Hierarchy into the Project tab, creating a Prefab.
This will be our base prefab. We'll create two variants of it: one for each of our two characters.
Delete the Message Bubble object from the scene.
Select the Message Bubble prefab you just created.
Open the Assets menu, and choose Create -> Prefab Variant.
Name the new prefab Message Bubble A.
Repeat the above two steps, creating a new prefab variant called Message Bubble B.
Drag Message Bubble A into the Content object in the hierarchy, creating an instance of it in the scene.
Drag Message Bubble B into the Content object as well.
In our phone chat system, we want Message Bubble A to use a green graphic and be right-aligned, and Message Bubble B to use a blue graphic and be left-aligned. Message Bubble A is already what it needs to be, so we just need to make some changes to Message Bubble B. The only things we need to change are the anchors - Bubble A is pinned to the right side, and we'll need to adjust it so that it's pinned to the left.
Select the Message Content object in Bubble B.
Change its Anchors Min to X: 0, Y:0.
Change its Anchors Max to X: 0, Y:1.
Change its Pivot to X: 0, Y:0.5.
Change its Pos X to 0.
The bubble will now be aligned to the left of the box, but its background image is pointing the wrong way.
Select the Background object in Message Bubble B.
In the Transform component, set its Scale X to -1.
The bubble will now be facing the right way. Lastly, we'll make the bubble use a different colour image.
With the Background object still selected, change the Source Image in its Image component to Bubble Blue.
The two bubbles should now look like this:
We're all done with setting up these bubbles for displaying text, though we'll return to them later at the very end of this tutorial. Let's apply these changes to the prefab overrides.
Select the Message Bubble B object
In the Inspector, click Overrides, and then click Apply All.
This will apply these changes to Bubble B prefab variant (without modifying the A variant or the base prefab).
Delete the Message Bubble A and Message Bubble B objects from the scene.
Now that we've set up the prefabs for the line views, let's set up the UI for options. When the Dialogue Presenter needs to show options, we'll create a list of buttons that contain the text of each option.
We'll start by creating the object that will contain all of the buttons.
Create a new empty object under Content, and call it Options Container.
Add a Vertical Layout Component to it.
Set its Padding Top to 8, and Padding Bottom to 16.
Set its Spacing to 16.
Turn on Control Child Size Width, and Child Force Expand Width.
This will create a new item in the scrolling list that is itself a vertical list. Now that we've built the layout for the options, let's create the button itself.
Create a new empty object under Options Container, and call it Option Button.
In the Transform component in its Inspector, set its Height to 60.
Now we'll add the image for the button. The button image itself will need to fill the entire parent, so we'll set up the anchors to do that.
Add a new empty object under Option Button, and call it Background.
Set its Anchor Min to X: 0, Y: 0
Set its Anchor Max to X: 1, Y: 1.
Set its Top and Bottom to 0, and its Left and Right to 20.
Add an Image component.
Set the Images component's Source Image to Option-NotSelected.
Next, the component that shows the text of the option. Again, this will fill the parent.
Add a new empty object under Option Button, and call it Text.
Set its Anchor Min to X: 0, Y: 0
Set its Anchor Max to X: 1, Y: 1
Set its Top, Bottom, Left and Right all to 0.
Add a TextMeshPro - Text (UI) component to the object.
Set its Vertex Color to black.
Set its Alignment to Centered Middle.
Set its font to Montserrat-Regular.
Click Extra Settings to expand it.
Set its Left and Right margins to 30.
We'll now configure the Text component to try to fit more text on a single line, where needed.
Set Text Wrapping Mode to No Wrap, and set Overflow to Ellipsis.
Turn on Auto Size, and set its Min Size to 24, its Max to 36, and its WD% to 10.
This will cause it to condense the font slightly if it needs the space, and to shrink the font size if it still needs more space. If it still can't fit the text, it will truncate it with an ellipsis (...) rather than overflow onto a new line. With these settings, an option's text can be about 40 characters long before it truncates.
Finally, we'll add a Button component, which will do two things: it'll make the button change its sprite as the user interacts with it, and it will also (eventually) tell the Dialogue Presenter that the user has chosen this option when clicked or tapped. We'll set up the Button component to change the sprite drawn in the Background object based on the current interaction state of the button.
With the Option Button selected, Add a Button component.
Set the Transition to Sprite Swap.
Drag and drop the Background object into the Target Graphic field.
Set Highlighted Sprite to Option-Selected.
Set Pressed Sprite to Option-Pressed.
Set Selected Sprite to Option-Selected.
Your button should now look like this:
We're all done setting up the visuals of the button. The last step is to turn it into a prefab so that we can instantiate copies of it later!
Drag and drop the Option Button into the Project pane to create a prefab.
Delete the original Option Button object from the scene.
We're all set to start wiring this UI up to a Dialogue Presenter!
We'll start from the bottom up - we'll create scripts that manage the contents of the bubbles and the option buttons, and then create the Dialogue Presenter to work with these scripts.
Let's start with the code for the bubbles. This will be a simple script that can be told to show some text, or show a typing indicator.
Open the Assets menu, and choose Create -> MonoBehaviour Script.
Name the new script "ChatDialoguePresenterBubble".
Double-click the new script to open it, and replace its contents with the following code.
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class ChatDialoguePresenterBubble : MonoBehaviour
{
[SerializeField] GameObject typingIndicator;
private TMP_Text TextView => GetComponentInChildren<TMP_Text>();
public bool HasIndicator => typingIndicator != null;
public void ShowTyping()
{
if (typingIndicator != null)
{
typingIndicator.SetActive(true);
}
if (TextView != null)
{
TextView.text = string.Empty;
}
}
public void ShowText(string text)
{
if (typingIndicator != null)
{
typingIndicator.SetActive(false);
}
if (TextView != null)
{
TextView.text = text;
}
}
}
Now we just need to add this script to our bubbles. Because our Message Bubble A and Message Bubble B prefabs are variants, all we need to do is just add it to the base prefab, and it will appear on all of them.
Select the Message Bubble prefab in the Project pane.
Click Open at the top right of the Inspector to open the prefab for editing.
Add a ChatDialoguePresenterBubble
component to the Message Bubble object.
Drag and drop the Typing Indicator into the Typing Indicator field.
Close the prefab and go back to the scene.
Next, we'll create and set up the code for the Option Button. This script needs to both show text to the user, and also notify the rest of the system that the button has been pressed.
Open the Assets menu, and choose Create -> MonoBehaviour Script.
Name the new script "ChatDialoguePresenterOptionsButton".
Double-click the new script to open it, and replace its contents with the following code.
using System;
using UnityEngine;
using TMPro;
public class ChatDialoguePresenterOptionsButton : MonoBehaviour
{
private TMP_Text TextView => GetComponentInChildren<TMP_Text>();
public string Text
{
get => (TextView != null) ? TextView.text : string.Empty;
set { if (TextView != null) { TextView.text = value; } }
}
public Action OnClick { get; internal set; }
public void OnClicked()
{
OnClick?.Invoke();
}
}
Now that the code's been written, we'll add it to the Option Button prefab.
Select the Option Button prefab in the Project pane.
Click Open at the top right of the Inspector to open the prefab for editing.
Add a Chat Dialogue Presenter Options Button component to the Option Button object.
In the Button component, add a new entry to the On Click event.
Drag and drop the Option Button object from the Hierarchy into the new event's object field.
Change the method from 'No Function' to 'ChatDialoguePresenterOptionsButton -> OnClicked()'.
Close the prefab and go back to the scene.
With this, our bubble and button prefabs are ready for use with the Dialogue Presenter!
We're now finally ready to create the Dialogue Presenter itself!
Open the Create menu, and choose Yarn Spinner -> Dialogue Presenter Script.
Name the new script "ChatDialoguePresenter".
Double click the new script to open it in your text editor.
The default Dialogue Presenter template script contains an empty implementation of all of the necessary methods needed to be a Dialogue Presenter. We'll start adding functionality to it, piece by piece.
Add the following variables to the ChatDialoguePresenter
class.
[Header("Prefabs")]
[SerializeField] SerializableDictionary<string, ChatDialoguePresenterBubble> characters = new();
[Space, SerializeField] ChatDialoguePresenterBubble defaultBubblePrefab = null;
[SerializeField] ChatDialoguePresenterOptionsButton optionsButtonPrefab;
[Header("Containers")]
[SerializeField] RectTransform bubbleContainer;
[SerializeField] RectTransform optionsContainer;
[Header("Timing")]
[SerializeField] float delayAfterLine = 1f;
[SerializeField] float minimumTypingDelay = 0.5f;
[SerializeField] float maximumTypingDelay = 3f;
[SerializeField] float typingDelayPerCharacter = 0.05f;
[SerializeField] bool showTypingIndicators = true;
Our code doesn't need to take any special action when dialogue starts and ends, so we'll replace them with simple methods that just return immediately.
Replace the OnDialogueStartedAsync
and OnDialogueCompleteAsync
methods with the following code:
public override YarnTask OnDialogueStartedAsync() => YarnTask.CompletedTask;
public override YarnTask OnDialogueCompleteAsync() => YarnTask.CompletedTask;
Now it's time to start building the actual behaviour. We'll implement the RunLineAsync
method, which is called when the Dialogue Runner needs to show a line of dialogue to the user. This method will look at the line, figure out which bubble prefab needs to be used, and then adds a copy of that prefab to the list and makes the bubble show the right content.
Replace the RunLineAsync
method with the following code.
public override async YarnTask RunLineAsync(LocalizedLine line, LineCancellationToken token)
{
// Early out if we don't have anywhere to put our bubble
if (bubbleContainer == null)
{
Debug.LogWarning($"Can't show line '{line.Text.Text}': no bubble container");
return;
}
// Next, we figure out what prefab to use.
// We'll start with our default bubble. If we know about a specific
// bubble that the character speaking the line should use, we'll use
// that instead.
var prefab = defaultBubblePrefab;
if (line.CharacterName != null)
{
characters.TryGetValue(line.CharacterName, out prefab);
}
// If we don't have a bubble prefab at this point, we didn't have a
// default prefab, and we didn't find a prefab for the specific
// character. We can't show the line.
if (prefab == null)
{
Debug.LogWarning($"Can't show line '{line.Text.Text}': no default bubble was set");
return;
}
// Next, we need to show the bubble. If the options container is
// present, insert it immediately before the container (so that the
// options are always at the bottom of the list.) If we don't have
// an options container, just insert it at the bottom of the list.
int index;
if (optionsContainer != null)
{
index = optionsContainer.GetSiblingIndex();
}
else
{
index = bubbleContainer.childCount - 1;
}
// If we're configured to show a typing indicator in the bubbles,
// and the bubble prefab we have actually HAS a typing indicator,
// we'll create a bubble for showing it, and wait for the
// appropriate time before replacing it with the text.
if (showTypingIndicators && prefab.HasIndicator)
{
// We create a bubble and then destroy and replace it (rather
// than changing its size) to avoid a layout pop
var typingBubble = Instantiate(prefab, bubbleContainer);
typingBubble.transform.SetSiblingIndex(index);
typingBubble.ShowTyping();
// Calculate how long the typing indicator should appear for
var typingDelay = Mathf.Clamp(
line.TextWithoutCharacterName.Text.Length * typingDelayPerCharacter,
minimumTypingDelay,
maximumTypingDelay);
// Wait for the required time. If our token gets cancelled in
// the meantime, stop waiting.
await YarnTask.Delay(System.TimeSpan.FromSeconds(typingDelay), token.HurryUpToken).SuppressCancellationThrow();
// Remove the typing bubble. We'll replace it with the text
// bubble in a moment.
Destroy(typingBubble.gameObject);
}
// Create the bubble containing the text.
var bubble = Instantiate(prefab, bubbleContainer);
bubble.transform.SetSiblingIndex(index);
bubble.ShowText(line.TextWithoutCharacterName.Text);
// Now that the line is on screen, wait for the appropriate delay,
// and then return. We'll leave the speech bubble we added, so that
// it stay on screen.
await YarnTask.Delay(System.TimeSpan.FromSeconds(delayAfterLine), token.HurryUpToken).SuppressCancellationThrow();
}
With this done, our Dialogue Presenter is able to show lines of dialogue in the bubble!
We'll now create the Dialogue Runner. Yarn Spinner comes with a simple menu item that creates a basic set of Dialogue Presenter, but because we've already built our own custom UI already, we don't need to use it. Instead, we'll create one from scratch.
Create a new empty object called "Dialogue Runner".
Add a Dialogue Runner component to the object.
Add a new empty game object as a child, and call it "Phone Chat Presenter".
Add a ChatDialoguePresenter component to Phone Chat Presenter.
Drag the Message Bubble prefab into the Default Bubble Prefab field. This will be the prefab that's used whenever there's no specific prefab to use for a character.
Drag the Content object into the Bubble Container field.
While we're here, we'll also set up the prefabs that will be used for options.
Drag the Option Button prefab into the Options Button Prefab field.
Drag the Options Container object into the Options Container field.
We're ready to test! Let's create some dialogue and hook it up.
We'll start by creating a Yarn Project, and a single Yarn Script containing some sample dialogue.
Open the Assets menu, and choose Create -> Yarn Spinner -> Yarn Project.
Name the new file "Phone Chat Project".
Open the Assets menu, and choose Create -> Yarn Spinner -> Yarn Script.
Name the new file "Phone Chat".
Next, let's write some dialogue.
Open the Phone Chat script and replace its contents with the following:
title: Start
---
<<wait 0.5>>
A: hey i made this chat demo
A: it's pretty cool
B: lmao nice
===
Finally, we'll connect this project up to the Dialogue Runner we made in the previous section.
Select the Dialogue Runner object in the Hierarchy.
Drag and drop the Phone Chat Project that you just created into the Yarn Project field.
Turn on Start Automatically. (Leave the Start Node as 'Start'.)
The very last step is to add our Dialogue Presenter to the Dialogue Runner, so that it receives content and shows it to the user.
Add a new entry in the Dialogue Presenters list.
Drag in the Phone Chat Presenter object into the new field.
We're finally ready to see it all in action!
Click the Play button at the top of the Unity window. The conversation will play out!
This looks good, but there's some room for polish. The first thing to note is that all of the lines use the same bubble, and there's no way to tell who's saying what. Remember that we created two prefabs - the green one that we called 'A', and the blue one we called 'B'. Let's set it up so that lines spoken by character 'A' use our 'A' prefab, and likewise for 'B' and the 'B' prefab.
Select the Phone Chat Presenter in the Hierarchy.
In the Inspector, click the +
button next to the Characters field to add an entry to the Characters dictionary.
Set the key of the new entry to "A", and drag the Message Bubble A prefab into the Object slot.
Repeat this process, creating a new entry "B" that uses the Message Bubble B prefab.
Play the game again.
Lines from "B" will use a different-looking bubble!
We've got lines working just fine, but we don't currently have support for options. Let's fix that! We'll start by adding some actual options to our sample dialogue, and then we'll modify our Dialogue Presenter so that it knows what to do when options arrive.
Open the Phone Chat.yarn
, and replace its contents with the following:
title: Start
---
<<wait 0.5>>
A: hey i made this chat demo
A: it's pretty cool
B: lmao nice
B: does it support options
A: lemme see
-> A: yep
-> A: uh huh
-> A: think so
B: nice, i bet it also supports wrapping text over multiple lines
===
Open the ChatDialoguePresenter.cs
file, and replace the RunOptionsAsync
method with the following code:
public override async YarnTask<DialogueOption> RunOptionsAsync(DialogueOption[] dialogueOptions, CancellationToken cancellationToken)
{
// First things first: check to see if we have everything we need to show options.
if (optionsContainer == null)
{
Debug.LogWarning($"Can't show options: no bubble container");
return null;
}
if (optionsButtonPrefab == null)
{
Debug.LogWarning($"Can't show options: no bubble prefab");
return null;
}
// Clear any previous options that might still be present.
for (int i = 0; i < optionsContainer.childCount; i++)
{
Destroy(optionsContainer.GetChild(i).gameObject);
}
// Create a completion source, which allows the buttons to indicate
// that an option has been selected.
var completionSource = new YarnTaskCompletionSource<DialogueOption>();
// Show a button for each of the options.
foreach (var option in dialogueOptions)
{
// Create the button, and show the text.
var button = Instantiate(optionsButtonPrefab, optionsContainer);
button.Text = option.Line.TextWithoutCharacterName.Text;
// When the button is clicked, complete the task with the
// appropriate option.
button.OnClick = () => completionSource.TrySetResult(option);
}
// Wait until an option has been selected.
var selectedOption = await completionSource.Task;
// Clean up by destroying all of the buttons.
for (int i = 0; i < optionsContainer.childCount; i++)
{
Destroy(optionsContainer.GetChild(i).gameObject);
}
// Return the selected option.
return selectedOption;
}
One more thing before we test it out: when the user taps on an option to select it, we'll run the contents of that option as though it had been a line. (Without this, there's no record of what the player selected, which feels strange.)
Select the Dialogue Runner in the Hierarchy.
In the Dialogue Runner component's Inspector, turn on Run Selected Option As Line.
We're all set to see it in action.
Play the game. After the lines appear, option buttons will run!
We're all done with our Phone Chat Dialogue Presenter! Try writing some more complex conversations, swap out the sprites to create a new theme, create new bubbles, or use this code to create a scrolling log of messages that isn't phone themed!
Learn about the Welcome Sample, a launching point to explore some basics of Yarn Spinner for Unity.
The Welcome sample is the launching point for the Yarn Spinner samples. It doesn't cover anything in of itself, it is a quick tour of every sample.
Welcome has Capsley standing in front of a projector showing slides of each sample in the package. Each sample is clustered into one of four categories:
Basic
Presenters
Extending
Advanced