Only this pageAll pages
Powered by GitBook
Couldn't generate the PDF for 1223 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

Yarn Spinner 3.0

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Writing Yarn Scripts

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...

Yarn Spinner for Unity

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...

Yarn Spinner for Other Engines

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Start Here

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.

What to do next

1

Join the Yarn Spinner Discord

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

2

Read the Beginner's Guide

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.

3

Use Yarn Spinner in Unity, Godot, or Unreal

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!

4

Build a Game

Simply draw the rest of the owl.

5

Tell us about your game and Credit Yarn Spinner

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!

Yarn Spinner powers amazing games, including:

... use it to power your game too!

Yarn Spinner 3

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.

You can hire us to add features you want to Yarn Spinner, build games, or help you customise and integrate Yarn Spinner. Email us at hello AT yarnspinner.dev

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!

If you've got a project that's in development already in Unity, and you want to move from Yarn Spinner 2 to Yarn Spinner 3, visit Upgrading from Yarn Spinner 2.

Yarn Spinner powers amazing games, including:

... use it to power your game too!

Godot

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

Cover
Cover
Cover
Cover
Cover
Cover
Cover
Cover
Cover
Cover
Cover
Cover

About Yarn Spinner

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.

Hobart, Tasmania, Australia. It's pretty nice.

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:

  • Jon Manning

  • Tim Nugent

  • Paris Buttfield-Addison

  • Mars Buttfield-Addison

You can find Yarn Spinner on Bluesky too!

Yarn Spinner Timeline

  • 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.

About Yarn Spinner Pty. Ltd.

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.

Email us at hello AT yarnspinner.dev to hire us to work on your Yarn Spinner game, integration, or add features to Yarn Spinner just for you!

Yarn Spinner's core team are also the founders and long-term team collaborators of Secret Lab Pty. Ltd.

About 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.

Scripting Fundamentals

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 .

Shadow Lines

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 copies of other lines, but don’t create a duplicate entry in the string table.

This can be useful if you want to re-use an existing line in more than one place, which can be important when each line has in-game assets like voice-over recording.

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).

Importing Yarn Files

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.

Overview

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.

Yarn Spinner for Godot works with Godot 4.1 and newer. Currently, the recommended version is 4.4.x.

Variable Storage

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 have a game save system, you can use the component. This is a simple Variable Storage component that's built into Yarn Spinner.

The In-Memory Variable Storage stores everything in memory; when the game ends, all variables that have been stored are erased.

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
===
Beginner's Guide
Yarn Spinner Editor
Advanced Scripting
Yarn Projects
Yarn Scripts
installing the plugin
In-Memory Variable Storage

First Steps with Scripting

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:

1

Get comfortable with Yarn Spinner in the Beginner's Guide

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.

2

Learn the Fundamentals of Yarn Spinner Scripting

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.

3

Learn the Advanced Features of Yarn Spinner Scripting

And, finally, check out the advanced features of Yarn Spinner for Visual Studio Code.

While Yarn Spinner Scripts and using Yarn Spinner in a game engine, such as Unity, are two conceptually separate things, you will need to learn how to work with Yarn Spinner in a game engine, and the concepts do blur together. It can be tempting to jump straight to using a game engine, but we don't recommend it. Learn Yarn Spinner Scripts first.

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.

Advanced Scripting

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

Components

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

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 have a game save system, you can use the In-Memory Variable Storage component. This is a simple Variable Storage component that's built into Yarn Spinner.

The In-Memory Variable Storage stores everything in memory; when the game ends, all variables that have been stored are erased.

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.

Components

Storylets and Saliency

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.

Custom Variable Storage Components

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);

Dialogue Presenters

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.

Enums

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.

An enum (short for ‘enumeration’) is useful when you have a variable that needs to have a wider range of possible values than simply true or false, but needs to be more specific than a number or string.

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

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.

Use this Line Provider if you are using the Unity Localisation system. If you are using the Built-In Localisation system, use the Built-In Localised Provider instead.

Inspector

Property
Description

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.

Yarn Scripts

Learn about Yarn scripts, which are the assets that contain the dialogue you write.

A Yarn script is a text file containing your dialogue.

Yarn scripts need to be part of a Yarn Project in order to be used in your game.

Creating a New File

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.

Creating a new Yarn script.

The new file that you've just created will contain a single node, which has the same name as the file.

Creating a Yarn Script in Godot is exactly the same as creating a .yarn file externally (i.e. in macOS Finder or Windows Explorer), and dragging it into the directory of your Godot project.

Editing Yarn Scripts

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.

You can learn about our recommended editor, Visual Studio Code with the official Yarn Spinner Extension at: https://github.com/YarnSpinnerTool/YSDocs/blob/main/docs/getting-started/editing-with-vs-code/README.md.

Beginner's Guide

If you're totally new to Yarn Spinner, this is the place to start.

Writing Yarn with Try Yarn Spinner

When you first start learning Yarn, you can use , our introductory, browser- based tool for writing Yarn Spinner Scripts. No installation necessary!

As you learn to write dialogue and game scripts with Yarn, you'll eventually need to use our editor, which is a Visual Studio Code Extension. Before you get to that stage, the best place to experiment with, and explore Yarn Spinner, is . We strongly recommend that you learn piece by piece.

1

Visit Try Yarn Spinner at

In Yarn, everything you write is text and structured around nodes and lines.

In Try Yarn Spinner, the first node to run is always called Start, so we’ll write that now.

In your own games or narratives outside of Try Yarn Spinner, the first node can be named whatever you like.

2

Create a Start node with a line of dialogue

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 ===.

3

Play your Yarn Script

Use the Run button in the top right-hand corner of Try Yarn Spinner to run your Yarn Spinner Script. You can play through it in the right pane. There isn't much to play right now!

4

Add a two more nodes

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.

5

Add a 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.

6

Play your Yarn Spinner Script again

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...

7

Introducing Options

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.

8

Play your Yarn Spinner Script yet again

Use the Run button to once again run your script.

You'll notice that when you choose the line OK! Let's go! the action will jump to the node entitle Cave. Great, right?

What did we just do?

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.

Yarn Spinner Editor

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

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.

Visual Studio Code is a powerful, flexible, open source code editor for Windows, macOS, and Linux. It supports extensions, which allow it to perform a wide range of useful tasks. The Yarn Spinner Extension is one of these.

If you've never used to VS Code before, for your operating system and platform before continuing.

If you're already familiar with VS Code and VS Code extensions, you can just install the extension secretlab.yarn-spinner, or click and hit the install button, and you'll be ready to go.

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:

Installing Yarn Spinner for Visual Studio Code

Once you've got VS Code installed on your system, you'll need to install Yarn Spinner for Visual Studio Code. To do this:

1

Launch Visual Studio Code

2

Open the Extensions view

To do this, select the blocks symbol from the left-hand sidebar, or press Command+Shift+K on macOS, or Control+Shift+X on Windows or Linux.

3

Search for the Yarn Spinner Extension

With the Extensions view open, type "Yarn Spinner" into the search field.

4

Install the Extension

Once the results have loaded, look for the verified Yarn Spinner extension and click "Install" next to it.

That's all you need to do to install the extension! You're ready to .

Previewing Your Dialogue

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.

This is fundamentally the same preview experience you'll get if you Test your dialogue at , which is the tool you used in the

Showing the Dialogue Preview

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.

By default, the dialogue preview will begin playing from the node named "Start". If no node named "Start" exists, then the dialogue preview will begin playing from the first node in one of your files. You can choose which node to start playing from in the menu at the top-right of the preview area.

Preview Features

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.

Exporting Your Script

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.

Detour Command

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.

Using the Detour Command

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.

Using Detour with the Return Command

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:

If Yarn Spinner reaches a return comand, and it hasn’t detoured from another node, it will stop the dialogue (that is, it will behave as though you had written a stop command.)

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:

Write some Detour Commands

1

Write a simple story with several nodes.

Spread your story out over the nodes in a sensible manner.

2

Use the <<detour>> command to move between nodes in your story.

Make sure you specify the name of the node you want to jump to inside each <<detour>> command.

3

Run your story using Preview.

Play through it, and make sure the detours behave as you'd expect.

Next up, learn about in Yarn Spinner Scripts.

Once

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.

once statements are great for making sure that the player never sees certain content more than once. For example, you might want a character to never introduce themselves to the player twice.

There are two main ways you can use a once statement, which we'll explore below.

You can wrap some lines in <<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):

You can add <<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:

once statements keep the information about whether they’ve been run or not in a variable that’s stored in your Dialogue Runner’s Variable Storage, just like any other variable. The variable isn’t directly accessible from your Yarn scripts.

Node Groups

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:

Node groups are similar to in their behaviour, but give you more room to create longer passages of content. Your C# code can also check to see how many (if any) nodes can run, which is covered in the Saliency section.

You can add as many when: headers to a node as you want.

First Steps with Unity

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.

Installing Speech Bubbles

Learn how to install the Speech Bubbles for Yarn Spinner Package.

You can purchase Speech Bubbles for Yarn Spinner from the or the .

To use the Speech Bubbles for Yarn Spinner package in Unity, you'll also need to make sure you've got the Yarn Spinner for Unity package .

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.

Line Groups

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.

Line groups are great for running ‘barks’ - collections of short lines that need to run in response to an in-game event. It can be useful to think of them like Yarn Spinner’s existing options -> syntax, but instead of the player choosing which content to run, the computer picks it for you:

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.

Dialogue Presenters

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.

Speech Bubble Examples

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.

2D Sidescroller

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.

3D Top-Down

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.

Installation for Godot

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.

📦 Installation for Godot

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 Examples

Dialogue Wheel for Yarn Spinner ships with two example scenes, showcasing the flexibiltiy of the Dialogue Wheel.

Automatic-Layout Dialogue Wheel Example

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.

Image Dialogue Wheel 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.

In-Memory Variable Storage

The In-Memory Variable Storage component is a Variable Storage component that stores all variables in memory. These variables are erased when the game stops.

The In-Memory Variable Storage component is intended to be a useful tool for getting started, and to be replaced with a custom variable storage that meets your game's needs.

However, if your game has no need to save and restore game state, then this class can be used in your final game, too.

Inspector

Property
Description

Changelog

A brief history of Yarn Spinner for Unity.

Yarn Spinner Timeline

  • 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

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

Yarn Spinner 2 debuted in July 2020, adding markup support, dialogue views, and lots lots more!

Yarn Spinner 1

After being developed for Night in the Woods, and staying in early-release for several years, Yarn Spinner 1 was released in January 2020!

Smart Variables

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.

Why Use Smart Variables?

  1. Code Readability: They make your dialogue scripts more readable by using meaningful names instead of complex conditions.

  2. Centralised Logic: Define a condition once and use it throughout your game.

  3. Maintenance: When game mechanics change, you only need to update the smart variable declaration, not every place it's used.

  4. Abstraction: They hide complex game state checks behind simple, descriptive names.

Practical Examples

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.

Example 1: Character Relationships

Example 2: Item Availability

Example 3: Time-Based Events

Benefits in Complex Games

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.

In-Memory Variable Storage

The In-Memory Variable Storage component is a Variable Storage component that stores all variables in memory. These variables are erased when the game stops.

The In-Memory Variable Storage component is intended to be a useful tool for getting started, and to be replaced with a custom variable storage that meets your game's needs.

However, if your game has no need to save and restore game state, then this class can be used in your final game, too.

Inspector

Property
Description

Built-in Localised Line Provider

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.

You'll automatically use this Line Provider if you are using the system. If you are using the system, use the instead.

Inspector

Property
Description

Installing Dialogue Wheel

Learn how to install the Dialogue Wheel for Yarn Spinner Package.

You can purchase Dialogue Wheel for Yarn Spinner from the or the .

To use the Dialogue Wheel for Yarn Spinner package in Unity, you'll also need to make sure you've got the Yarn Spinner for Unity package .

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 .

Speech Bubbles

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.

Speech Bubble Architecture

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.

Bubble Dialogue View

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.

Bubbles and Bubble Content

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.

Bubble Input

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.

TLDR: you set the values for hurrying up, skipping, and cancelling lines on the Bubble Input.

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.

Unity Localization
<<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>>
expression
mathematical operations, logical comparisons


 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.
===
Try Yarn Spinner
Try Yarn Spinner
https://try.yarnspinner.dev
Fundamentals
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)
===
Jump Command
Variables
The flow of detours and returns (automatic and triggered by the return command) in this snippet.
The <<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!
===
line groups
Node groups are shown in the Graph View.
Yarn Spinner Itch Store
Unity Asset Store
installed
samples installed
Unity Add-Ons

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.

Yarn Project
Built-In Localisation
Unity Localisation
Unity Localised Line Provider
A Built-in Localied Line Provider
Yarn Spinner Itch Store
Unity Asset Store
installed
samples installed
Using Image Wheel
Using Auto-Layout Dialogue Wheel

Commands

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.

Built-in Commands

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>>

Making Your Own Commands

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" />
copy of the latest version of Yarn Spinner for Godot
GitHub repository
The addons directory in a local copy of Yarn Spinner for Godot.
The FileSystem dock in Godot, after dragging the addons directory in.
The .csproj for your project.
The Project Settings, showing the Plugins tab.
Beginner's Guide
head to their official website and install it
here
write and edit Yarn Spinner Scripts in Visual Studio Code
VS Code editing a Yarn script
Turning off the "helpful" "features".
The Yarn Spinner Extension
https://try.yarnspinner.dev
Beginner's Guide
variables
marked as 'unavailable'
Previewing dialogue in Visual Studio Code. On the left hand side, Yarn script is being edited, and on the right hand side, the same script is being played through.
Installation for Unity
First Steps with Scripting
purchased from the Yarn Spinner Itch.io Store
Unity Asset Store
presenter
Dialogue Runner
Line Advancer
Speech Bubbles for Yarn Spinner. A small blue capsule stands next to a small yellow capsule. In a speech bubble, the blue capsule says "hey man man what's your whole deal".

Unity Add-Ons

Learn about our premium add-ons for Yarn Spinner for Unity, giving you ready-made Dialogue Wheels and Speech Bubbles for Yarn Spinner.

You can purchase these add-ons at the Yarn Spinner Itch Store or via the Unity Asset Store. Purchasing Yarn Spinner, or a Yarn Spinner Add-on is the best way to help support Yarn Spinner, and the team behind it.

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:

  • Dialogue Wheel for Yarn Spinner

  • Speech Bubbles for Yarn Spinner

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.

Custom Saliency Strategies

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.

This sample focuses on creating custom saliency strategies, not on saliency itself. For more information about the concept of saliency, see Storylets and Saliency Primer, and Saliency.

What we'll be covering

  • Creating new saliency strategies through the IContentSaliencyStrategy interface

  • Reading line metadata

  • Reading node headers

IContentSaliencyStrategy

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.

Weighted Saliency

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.

You'll notice several return null statements in this method, which is entirely appropriate. It's normal and expected that sometimes there won't be any valid content to run. Returning null is how you inform Yarn Spinner that no valid content is available.

Options Presenter

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.

An Options Presenter.

An Options Presenter only displays options, and doesn't display lines. You can use an additional Dialogue Presenter to handle these, like Line Presenter or a custom Dialogue Presenter of your own. We provide a default Line Presenter and Options Presenter.

Inspector

The Inspector for an Options Presenter.
Property
Description

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.

Unity Quick Start

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!

Basic Yarn Spinner for Unity Usage

  1. Install Unity 2022.3 or newer.

  2. Create a new empty Unity project, by following the instructions in the Unity manual.

  3. Install Yarn Spinner into the project, by following the instructions in Installation for Unity.

  4. Add a Dialogue System to the scene:, by opening the GameObject menu and choosing Yarn Spinner -> Dialogue System.

  5. Create a new Yarn Spinner Script, by opening the Assets menu and choosing Create -> Yarn Spinner -> Yarn Script. Name the new file HelloYarn.

  6. Open the new Yarn script by double-clicking it.

    1. Select all of the text in the file, and delete it.

    2. 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!
===

You can learn about our recommended editor, Visual Studio Code with the Yarn Spinner for Visual Studio Code extension.

  1. Save the file and return to Unity.

  2. 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.

  3. Make the Dialogue Runner use the Project by dragging the Project you just made into the Dialogue Runner's Yarn Project field.

  4. Play the game by clicking the Play button at the top of the window. Your dialogue will appear!

Line Advancer

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.

A Line Advancer isn't a Line Presenter itself, but it's designed to work with other line presenters, to interrupt and control the flow of dialogue.

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).

If you want to use Input Actions, your project will need to be set up to use the Unity Input System.

Property
Description

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.

Inline Events

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.

What we'll be covering

  • 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 Action Markup Handler

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:

  1. Initial Preparation (OnPrepareForLine): Called immediately after the line is received but before any UI elements appear.

  2. Display Start (OnLineDisplayBegin): Called just before characters begin appearing, but after UI elements are visible.

  3. Character Display (OnCharacterWillAppear): Called each time a character in the line is about to be displayed.

  4. Display Completion (OnLineDisplayComplete): Called after all characters in the line have been shown.

  5. 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.

The 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.

Movement

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.

Emotion

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.

Installation for Unity

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 .

Install from the Unity Asset Store

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.

You can review for further guidance on working with purchased packages.

Install from Itch.io

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:

Install from GitHub

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.

Where possible, we recommend installing Yarn Spinner via a purchased .unitypackage from the or the .

To install Yarn Spinner from GitHub, follow these instructions.

  1. Make sure your system .

  2. In Unity, open the Window menu, and choose Package Manager.

  3. Click the + button, and choose "Add package from git URL".

  4. 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.

  5. The project will download and install. This might take a moment.

Install via OpenUPM

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.

Where possible, we recommend installing Yarn Spinner via a purchased .unitypackage from Itch.io or the Unity Asset Store.

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.

  1. In Unity, open the Edit menu, and choose Project Settings.

  2. 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.

  1. In the Name field, type OpenUPM.

  2. In the URL field, type https://package.openupm.com.

  3. In the Scopes field, type dev.yarnspinner.

  4. 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

  1. Open the Window menu, and choose Package Manager.

  2. In the toolbar, click Packages: In Project, and choose My Registries.

  1. 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.

Yarn Spinner is an open source project. You can directly support the Yarn Spinner Team by purchasing Yarn Spinner from or the , but it will always have a free option as well. To support the continued development of Yarn Spinner, purchase Yarn Spinner for Unity from one of the storefronts. This is the best way to directly support the Yarn Spinner team.

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

Cover
Cover
Cover
Cover
Cover
Cover
Cover
Cover
Cover
Cover
Cover
Cover

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

Cover
Cover
Cover
Cover
Cover
Cover
Cover
Cover
Cover
Cover
Cover
Cover

Localisation and Assets

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 will not translate your dialogue for you. It just makes it easy to load translated versions of your dialogue.

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.

Working with Localisation

Yarn Spinner makes it easy to add multiple languages to your game. The gist is:

  1. Select your Yarn Project in the Assets panel

  2. Click "Export Strings as CSV" in the Inspector

  3. Translate the exported CSV file

  4. 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 Terminology

  • 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.

Workflow

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.

Writing Yarn Scripts

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.

Adding Line IDs

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.

You can't generate a strings file unless all of the lines in all of the scripts in the Yarn Project have a line ID.

Using Localised Content in Games

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.

Upgrading from Yarn Spinner 2

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.

YarnCommand and YarnFunction Attributes

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.

New flow for Interrupting Dialogue

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.

DialogueViewBase is now DialoguePresenter

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.

Converting a Dialogue View to a Dialogue Presenter

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:

  1. Change the Subclass.

  2. Convert to the New Methods.

  3. Delete Unnecessary Methods.

  4. Remove the Interrupt Action.

1. Change the Subclass

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.

2. Convert to the New Methods

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

3. Delete Unnecessary Methods

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

4. Remove the Interrupt Action

The requestInterrupt action is obsolete and can be deleted.

Final Steps

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.

Upgrading Yarn Projects

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.

Yarn Spinner Itch.io Store
Unity Asset Store
visit the Unity Asset Store page for Yarn Spinner for Unity
Unity's documentation for using the Asset Store
Yarn Spinner Itch.io Store
Yarn Spinner Itch.io
Unity Asset Store
has Git (minimum version 2.14.0) installed
OpenUPM registry
Itch
Unity Asset Store

Crediting Yarn Spinner

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:

Yarn Spinner License (MIT License)
The MIT License (MIT)

Copyright (c) Yarn Spinner Pty. Ltd., Secret Lab Pty. Ltd., and Yarn Spinner contributors.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

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.

Rules for building products with Yarn Spinner support

Please adhere to the following if you build a non-game product or tool that supports Yarn Spinner:

  1. 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).

  2. 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");

  3. 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;

  4. 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;

  5. 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.

  6. 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.

Yarn Spinner logos

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:

5MB
YarnSpinner-Logos.zip
archive

Saliency

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.

Here are some examples of calculating the complexity of when: headers:

  • when: always has a complexity of 0.

  • when: $a has a complexity of 1.

  • when: $a or $b has a complexity of 2.

  • when: once has a complexity of 1.

  • when: once if $a or $b has a complexity of 3.

You can either use one of Yarn Spinner's built-in saliency strategies, or create your own custom saliency strategy.

Yarn Spinner has several built-in saliency strategies.

  • 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.

Changing the active saliency strategy in Try Yarn Spinner

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>>

Changing the active saliency strategy in Yarn Spinner for Visual Studio Code

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:

Changing the saliency strategy in Visual Studio Code.

Creating Custom Saliency Strategies

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.

Querying If Any Content Can Run

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.)

Options

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:

Demo.yarn
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:

Demo.yarn
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!
Two sets of options in the same node being displayed separately.

Options and Lines

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.

Options within Options

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!
The different groups of options, showing when they're grouped using colours.

Testing out Options

1

Add options to your tiny narrative.

Consider adding some lines that belong to the options below them, too.

2

Run your single-node narrative using Preview

Jump Command

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.

Using the Jump Command

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:

The <<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:

Write some Jump Commands

1

Write a simple story with several nodes.

Spread your story out over the nodes in a sensible manner.

2

Use the <<jump>> command to move between nodes in your story.

Make sure you specify the name of the node you want to jump to inside each <<jump>> command.

3

Run your story using Preview.

Play through it, and make sure the jumps behave as you'd expect.

Next up, learn about the Jump Command's close relative, the Detour Command.

Writing Together

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.

Live collaboration on .yarn files using the Live Share extension and VIsual Studio Code

The rest of this guide assumes you've followed the steps in Advanced Scripting, and already have the Yarn Spinner for Visual Studio Code extension up and running.

Installing Live Share

To install Live Share for Visual Studio Code, and use it with Yarn Spinner:

  1. Setup Yarn Spinner for Visual Studio Code.

  2. Click this link to open Visual Studio Code, and jump to the Live Share Extension.

  3. 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.

Inviting collaborators

Open your Yarn Spinner project in Visual Studio code. For example, here's I Feel Fine:

A project open in Visual Studio Code, with lots of .yarn files

With 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:

The Live Share button in the Activity Bar, and 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:

The notification that your workspace is being shared.

Joining a shared workspace

When you receive a collaboration link and visit it, you'll be asked how you'd like to join:

Joining a shared workspace.

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.

This guide assumes that the person you're sharing with also has the Yarn Spinner for Visual Studio Code extension installed.

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:

Trusting the shared workspace.

Writing together

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:

Editing .yarn files together.

For more help

For more guidance using the Live Share extension, check out the extension's official page, as well as the Live Share extension's documentation.

We'll be improving the capabilities of Yarn Spinner for Visual Studio Code over time, and will add features that directly support the Live Share extension. Stay tuned!

Line Presenter

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.

A Line Presenter only displays lines, and doesn't display options. You can use an additional Dialogue Presenter to handle these, like an Options Presenter or a custom [Dialogue Presenter[(custom-dialog-views.md)] of your own.

Showing the Character's Name

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.

Presenting Lines with Visual Effects

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.

Continuing to the Next Line

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.

Inspector

Property
Description

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.

Background Chatter

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 background conversations sample.

Running Multiple Dialogue Runners

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.

Designing Line Presenters for Background Conversations

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

As with all design patterns, there are exceptions. In Mass Effect 3, some background conversations between NPCs involved characters arguing over a topic. These conversations allowed the player to listen in and potentially interject, siding with one of the characters.

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

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.

Chatter Groups and ChatterGroupManager

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.

Basic Background Conversation

The basic background conversation demonstrates a simple exchange between two characters. This conversation runs normally and cannot be interrupted by player actions.

Procedural Conversation

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.

This conversation is inspired by the procedural conversations between stormtroopers in Star Wars: Outlaws, where pairs of characters would converse with one character making a statement and the second offering some form of agreement.

Interruptable Conversation

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.

Player-Participating Conversation

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.

Ongoing 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.

Marketplace Scene

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.

The marketplace scene.

Functions

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:

Built-In Functions

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.

This is different to floor, because floor rounds to negative infinity.

Custom Functions

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.

Nodes and Lines

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.

Node headers can contain any number of lines with the structure key: value. This can be used to store additional information, such as the location the conversation is taking place.

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.

Inside a 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.

Lines

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.

Write a simple story

1

Write a story inside a single node using Try Yarn Spinner.

Use VS Code to write a tiny story of 5 to 10 lines, inside one node.


Here's our story, if you couldn't think of an idea:

2

Play your story the Preview mode inside VS Code

Use the Command Pallette to Preview your tiny story.

Line Presenter

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.

A Line Presenter only displays lines, and doesn't display options. You can use an additional Dialogue Presenter to handle these, like or a custom of your own. We provide a default Line Presenter and Options Presenter.

Showing the Character's Name

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.

Presenting Lines with Visual Effects

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.

Continuing to the Next Line

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:

Inspector

Property
Description

Quick Start

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:

You could also instantiate the DefaultDialogueSystem.tscn into your scene at a lower part of the hierarchy, instead of the root node, to display dialogue using the provided default UI, instead.

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:

MyStory.yarn

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:

Next steps with Yarn Spinner for Godot

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.

Yarn Projects

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.

Creating a New Yarn Project

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.

Adding Yarn scripts to a Yarn Project

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.

Pattern
Description
Examples

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.

Managing Localizations and Assets

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.

Unless you change it to something else, Yarn Spinner will set the base language to your computer's current locale.

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 .

Using Yarn Projects with Dialogue Runners

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.

If you try to start a Dialogue Runner and it doesn't have a Yarn Project, or the Yarn Project doesn't have any Yarn scripts, or if any of the Yarn scripts contain an error, the Dialogue Runner won't be able to run.

Inspector

Property
Description

Tags and Metadata

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 in lines

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:

Accessing 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.

Special tags

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.

The #lastline tag will not be automatically added if there is any content, such as an if statement or a command, between the line and some options. In these situations, you may wish to manually add the tag yourself.

#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 .

Tags in nodes

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.

Other metadata

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.

The tracking header

Nodes 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 .

Use cases

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.

Controlling attributes for an entire line

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.

Displaying the last line of dialogue along with options

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.

Internal workflows

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!

Localisation

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.

Showing specific lines of dialogue outside a dialogue window

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.

Custom Dialogue Presenters

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.

If you just want to re-theme our provided Line Presenter and Options Presenter, head over to the Sample Guide instead.

Creating a Dialogue Presenter

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.

Presenting Lines and Options

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.

Getting Localised Content

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.

Presenting Lines

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.

Hurrying and Advancing Lines

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.

For some Dialogue Presenters, "hurry up" and "advance" might mean the same thing. Each presenter shows lines its own way. You decide how these signals work for your game.

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.

Don't cache the LineCancellationToken between lines. The Dialogue Runner creates new ones for each line. An old token won't be any use.

Presenting Options

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.

If RunOptionsAsync doesn't do any asynchronous tasks, it's neater to return YarnTask<DialogueOption>.FromResult(null). This looks a bit less tidy than return null;, but it clearly tells C# that you're intentionally not doing any async work in the method.

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.

Accessing Line Metadata

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.

Yarn Spinner automatically adds certain tags to lines. The #lastline tag is added to any line that's immediately followed by options. This lets your dialogue presenter change how it behaves when options are about to appear.

BBCode
// 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)}!
if statements
variables
Creating Commands and Functions
commands
pure functions
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.
===
Dialogue View
Adding Line IDs
GetTagsForNode
Functions
markup
Localization and Assets
Theming Default Presenters

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.

Dialogue Presenter
Options Presenter
Dialogue Presenter

*

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.

Yarn scripts
Adding Localizations
The Yarn Project inspector. The configurable properties of the Yarn Project are visible at the top, and the information about the imported project is visible at the bottom.
Creating a new Yarn project.
An example of a custom Source Files setting. In this example, the Yarn Project will use all .yarn files in the same folder and its sub-folders, as well as the file Common.yarn in the folder above it.
Updating the base language of a Yarn Project

Creating Custom Dialogue Presenters

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.

Creating a Dialogue Presenter

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.

Presenting Lines and Options

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.

Getting Localised Content

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.

For more information about setting up localization for your project using Yarn Spinner for Godot, see here

Presenting Lines

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.

Hurrying and Advancing Lines

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.

For some Dialogue Presenters, "hurry up" and "advance" might mean the same thing. Each presenter shows lines its own way. You decide how these signals work for your game.

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.

Don't cache the LineCancellationToken between lines. The Dialogue Runner creates new ones for each line. An old token won't be any use.

Presenting Options

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.

If RunOptionsAsync doesn't do any asynchronous tasks, it's neater to return YarnTask<DialogueOption>.FromResult(null). This looks a bit less tidy than return null;, but it clearly tells C# that you're intentionally not doing any async work in the method.

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.

Accessing Line Metadata

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.

Yarn Spinner automatically adds certain tags to lines. The #lastline tag is added to any line that's immediately followed by options. This lets your dialogue presenter change how it behaves when options are about to appear.

Modifying the Example Dialogue Systems

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.

The Automatic-Layout Dialogue Wheel example
The Image Wheel
A single node



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.
Yarn Spinner Editor
Yarn Spinner Editor
Writing Yarn in VS Code
Lines of dialogue running in Night in the Woods.
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!
===
install the plugin
join the Discord
The chain-link button, for instantiating a child scene.
Choosing the provided DefaultDialogueSystem.tscn.
The DefaultDialogueSystem instantiated into your scene.
Opening the menu to create a new .yarnproject file.
Selecting a location to save your .yarnproject file.
Opening the Create Yarn Script window
The Yarn Project, with the Yarn script identified as a Source Script.
The new Yarn Project resource assigned as the Yarn Project for the DialogueRunner.
Setting the Start Node, and that we want this DialogueRunner to start automatically.

Flow Control

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 statements

In 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

The expression used in an if and elseif statement must result in a boolean value (that is, true or false.) For exame,<<if 1>> isn't allowed, but <<if 1 == 1>> is.

Conditional Options

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.

Yarn Spinner always delivers every option in an option group to the game; it's up to the game to decide what to do with options that are marked as unavailable.

For example, an unavailable option might be shown to the user, but not selectable, so that the user can see that they could have been able to say that if circumstances had been different.

Unity Localisation

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.

The Built-In Localisation system is simpler, but has fewer features.

In this document, we'll refer to the 'Localization' package that Unity provides as 'Unity Localization', to reduce the chance of confusion.

Watch a video where Yarn Spinner developer Jon Manning walks you through using Yarn Spinner with Unity's Localisation package:

Getting Started

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.

For Unity Localization

  1. Install the Localisation package

  2. Create a new Localization Settings in the Project Settings - Localization screen:

Creating a Localization Settings in the Project Settings screen.
  1. Create at lease one Locale in the Project Settings - Localization view.

  2. Create a String Table Collection via Window menu -> Asset Management -> Localization Tables.

  3. Check "Use Unity Localisation System" in your Yarn Project, and assign the String Table, and click Apply.

Activating the Unity Localization system on the Yarn Project asset, and assigning a String Table.
  1. Verify that the String Table (viewable by Window menu -> Asset Management -> Localization Tables) contains your Yarn Spinner Script's lines.

  2. In the Inspector for your Dialogue Runner, click the "Add Unity Localized Line Provider" button:

  1. 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.

For Built-in Yarn Spinner Localization

Once you have followed these instructions, your project should now:

  1. Have the Unity Localization package installed

  2. Created and configured one or more Locales for your project

  3. Created a string table collection.

When localising your game's dialogue using Yarn Spinner, we recommend creating a separate string table collection for your dialogue, set apart from other localised content like button labels. It can make it a little easier to manage.

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.

Importing Default Strings

To fill a string table with content from a Yarn project, follow these steps:

  1. Select the Yarn Project, and go to its Inspector.

  2. Enable the Use Unity Localisation System setting.

  3. Set the Base Language to your desired language. This must be ensure its one of the locales that you have configured for your project.

  4. In the String Table Collection field, add the String Table Collection that you want to populate with line content.

  5. Click Apply.

A correctly configured Yarn project for using the Unity Localization package.

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.

String Table Locale Fallback

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.

Using the Strings

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.

The Dialogue Runner's Inspector will tell you when you need to use a Unity Localised Line Provider. You can click the button it provides to quickly add and configure one. You can also set one up manually, using the Add Component menu.

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.

The Locale Selector control, in the top-right corner of the Game View.

Localising Assets

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.

A Unity Localised Line Provider, configured with a String Table for line text, and an Asset Table for voice-over.

Potential Trip-ups and Caveats

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.

Using Speech Bubbles

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.

Setting up for Speech Bubbles

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 demo scene in action.

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.

Building the Basics

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.

Configuring the Orange NPC to be coloured orange.

Orange will change to now be coloured the same as their namesake. Once that is done we should have our world built out.

Our finished environment.

Camera controls

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.

Configuring the camera to track the player.

Now our camera is following the player, you can test this by starting the game and walking the player around the scene.

Creating the Dialogue and System

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:

Simple Yarn Script
title: Orange
---
Orange: Hello!
    -> Why Hello There!
        Orange: You are a bold one.
    -> Who are you?
        Orange: I'm Orange.
    -> You're orange.
        Orange: Yep.
===

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.

Hooking our Yarn Project up to the dialogue runner.

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.

Configuring Orange to participate in the dialogue.

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.

Adding Bubbles

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.

Adding Bubbles to Dialogue System

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".

Updating our dialogue system hierarchy.

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.

Configuring our speech bubbles.

Bubble Input and Line Advancer

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.

Our configured Bubble Input.

Hooking it up

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.

Letting the runner know about the Bubbles and Input.

Now we need to anchor the bubbles to their characters.

Anchoring the Bubbles

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 anchor for 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.

Our chosen position for the anchor for Orange.

Playing with Bubbles

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.

In-built Localisation

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.

The other option for localisation is to use the Unity Localization package. The Unity Localisation package has more features, but is more complex.

Watch a video where Yarn Spinner developer Jon Manning walks you through using the Built-In Localisation System:

Set Up Localisations

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.

The localisation settings for a project. The base language is English, and two localisations have been set up: one for English, and one for Russian.

Localisations have the following properties:

Property
Description

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.

Your project always includes at least one localisation, which is for the base language.

Creating a Translation

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.

You don't need to create a strings file for your base localisation, because Yarn Spinner creates that for you by reading your source Yarn scripts. Any localisation whose language ID is the same as your base language will be marked as 'Automatically included'.

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 passage of Yarn script, next to the strings file for those lines.

A strings file has the following structure:

Column
Description

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.

You can also provide the metadata file to the translator to give them more context and improve localisation accuracy.

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.

It's possible to update a strings file after you've made changes to your source scripts. For example, you might have added or removed lines, or made changes to the text.

To update a strings file, click the Update Existing Strings Files button at the bottom of the Inspector.

Yarn Spinner will update every strings file that's been added to the Localisations list: new lines will be added, removed lines will be deleted, and lines whose original text has changed since the last time the file was updated will have the text "NEEDS UPDATE" added to the end. This allows you to more easily find which lines need an updated translation.

Adding Localised Assets

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.

Selecting a Language at Run-time

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.

If a line provider is asked to retrieve content for a language that it doesn't have any assets for, it will retrieve the base language version instead.

Dialogue Runners and Systems

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 to a Unity Scene

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.

Adding a Dialogue System to the Scene.

With the Dialogue System added to the Scene, you'll find it in the Hierarchy:

A Dialogue System 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.

The Inspector, showing the Dialogue Runner component of the Dialogue System.

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.

The bare-bones minimum that a Dialogue Runner needs in order to work is a Yarn Project and at least one Dialogue Presenter.

If you don't set up a Variable Storage or a Line Provider, the Dialogue Runner will use temporary placeholders.

If your game is using the Unity Localization system, your Dialogue Runner must use a Unity Localised Line Provider.

Inspector

Property
Description

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 .

Samples

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.

From Yarn Spinner 3, the samples have been removed from the main repository and made a standalone package, so you'll need to manually install their package.

We did this because as the samples were growing they were becoming a larger and larger portion of the install, this was essentially bloating an install of Yarn Spinner while at the same time making development slower and more annoying.

The samples are a necessary element of Yarn Spinner, so instead of being treated like an afterthought they have been spun out into their own thing. Now they can be developed, improved, tested, have their own dependancies, and share a common sample codebase without needing to impact the rest of Yarn Spinner, meaning more—and better—samples than in the past.

Adding the Samples Package to a Project

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.

The samples may take a little while to install as they are quite large.

Manually Installing the Samples

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.

  1. Open your browser and navigate to

  2. 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.

  1. 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.

  1. 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.

  1. Click on the "Download" button and it will take you to the Download page.

  1. Click on the "Download" button for the second bundle and save it some where convenient.

  2. Open the downloaded unitypackage and import the package into your Unity project.

  1. Inside Unity open the package manager from the menu Windows -> Package Manager

  2. In the top left corner press the + button and from the dropdown that appears select Install package from Git URL

  1. 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.

  1. 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!

The samples are just a standard unity package so if you have a workflow around packages you can adopt the samples packages to that workflow.

  1. Inside your browser navigate to the samples site: https://github.com/YarnSpinnerTool/YarnSpinner-Unity-Samples

  2. Click on the Code button and from the dropdown select the Download Zip option

  1. 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.

  1. Back inside Unity open the package manager from the menu Windows -> Package Manager

  2. In the top left corner press the + button and from the dropdown that appears select Install package from disk

  1. 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.

  1. 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:

Accessing a Sample

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.

List of Yarn Spinner for Unity Samples

  • - 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.

Your First Yarn Spinner Game

This example project demonstrates making a simple dialogue-based game when beginning with only an empty Unity scene.

Goals

  1. Display Yarn dialogue in a Unity scene

  2. Allow a player to select between options to respond

  3. Add some static visuals

Preparation

  • Yarn Spinner installed in Unity:

  • Yarn Spinner set up in a text editor:

Instructions

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.

Creating a Runnable Script

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.

Depending on your version of Unity, a window might appear asking you to import TextMesh Pro assets. If this appears, click 'Import TMP Essentials'.

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.

The existence of Yarn Projects allows larger games with multiple dialogue systems (e.g. main story dialogue, barks, storylets) to separate into multiple projects that pass lines to different UI or systems.

This allows an extra level of organisation above separate Yarn files which are typically used to separate story scenes or parts. However, most games will need only a single 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.

Filling Out Your Script

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:

Draw the Rest of the Owl

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.

This tutorial isn't here to teach you all of Unity. If you need some guidance about aspects outside of Yarn Spinner, you can or there are lots of helpful guides around the web, on YouTube, or created by Unity themselves!

Result

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.

Installation for Unity
https://assetstore.unity.com/packages/slug/319418
Feature Tour
Welcome
Feature Tour
Basics Storylets and Saliency
Theming Default Views
Create a Phone Chat View
Make Options That Timeout
Voice Over and Localisation
Background Chatter
Inline Events
Replacement Markup
Custom Saliency Strategies
Advanced Saliency
Inspector for a Yarn Project
The download banner
The downloads page for Yarn Spinner for Unity on Itch highlighting the samples package
Installing a git url package
Perusing the samples within the package manager
Downloading the samples from GitHub
Adding a local package to the package manager
Perusing the samples within the package manager
Locating the Yarn Spinner Samples in the Unity Package Manager, after installing the Yarn Spinner Samples Package into a project.
Click the Import button next to the Sample you want to install.
Yarn Project
Variable Storage
In Memory Variable Storage
Line Provider
Builtin Localisation Line Provider
Dialogue Presenters
StartDialogue
YarnCommand
AddCommandHandler

You can learn how to add the Yarn Spinner for Samples to your project over at Samples.

You can learn how to add the Yarn Spinner for Samples to your project over at Samples.

You can learn how to add the Yarn Spinner for Samples to your project over at Samples.

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>>
===
Installation for Unity
Yarn Spinner Editor
Installation Instructions
Installation Instructions
Syntax Guide
Result
check out our books on the topic
A new Unity 3D project has been made with no additional changes
The Dialogue System has been added to the Scene
The new Yarn Project has been added to the Dialogue System's Dialogue Runner
The new Yarn Script has been added to the Yarn Project's Source Scripts
The placeholder line from the Yarn Script has been displayed in the otherwise empty game
Yarn Spinner is displaying lines, advancing lines and selecting options correctly as per the script
A capsule has been added to the Scene to act as our Capsley character
A Material has been added to Capsley in the Scene
The game is complete and playable with visuals
Creating a Translation

Replacement Markup

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.

You can learn how to add the Yarn Spinner for Samples to your project over at .

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:

  1. Built-in common replacements and marker palettes

  2. Dynamic markup that changes with each presentation to obscure text

  3. Named replacement for in-line content highlighting

  4. Injecting sprites and additional text content into a line

Line showing off custom markup palettes

What we'll be covering

  • 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 Attribute Marker Processor

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:

  1. marker: The marker being processed, containing position, range, and properties

  2. childBuilder: A StringBuilder containing the text content of all child nodes, which you'll typically modify by adding text to the beginning and end

  3. childAttributes: A list of markup inside your markup's children, which you can modify, offset, or even delete as part of your replacement

  4. 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.

How we process replacement markup

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.

When rewriting the tree to fix nested markup, we try to maintain the semantic meaning. For example, if you have misnested Yarn markup like: this [b]is[i] some [/b]malformed[/i] markup, it gets rewritten to the equivalent of this [b]is[i] some [/i][/b][i]malformed[/i] markup.

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 Samples

The sample scene includes four different examples, each NPC demonstrating a different aspect of replacement markup.

Built In Markup Replacement

Line showing off custom markup palettes

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>.

The inspector for the palette in this sample

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.

TMP styles may add visible characters to the line, which would offset child attributes' positions. The StyleMarkerProcessor can't handle this automatically because TMP doesn't provide a way to interrogate styles ahead of time. If you need to add visible characters, we recommend either creating a custom replacer or using the Palette system with a MarkerOffset to correctly shift attribute positions.

Dynamic Replacement

Line showing off partially obscured text

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:

  1. Collects the index of every non-whitespace character

  2. Shuffles these indices (so different characters are revealed as obscurity decreases)

  3. Uses the obscurity factor to determine which characters to replace with random punctuation

Named Replacement

Line showing off the characters names being coloured

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>");

Adding Elements

Line where the flame is coloured and has a sprite

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.

Speech Bubbles for Yarn Spinneritch.io
Speech Bubbles for Yarn SpinnerUnityAssetStore

Using Auto-Layout Wheel

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 reason we say theoretically as many options as you like is because even though the code doesn't care about the number of options, if you have too many things are gonna start looking weird.

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.

Using the Automatic-Layout Dialogue Wheel

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.

Initial sample story for Automatic-Layout Dialogue Wheel

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!

Automatic Layout Commands

There are three commands which let you adjust how the wheel works during dialogue. To see these in action check out the .

Layout Direction

<<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.

Deadzone Adjustment

<<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.

Region Offset

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.

Customising the Automatic-Layout Dialogue Wheel

Customising the Wheel

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:

Customising the Options

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:

Customising the Option Placement

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.

Samples
Customising the Graph View
Operators
AutomaticWheelExample.yarn
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>>
===
make sure your Unity project has the Yarn Spinner package installed
install the Dialogue Wheel for Yarn Spinner package
example
Dialogue Wheel Examples
Adding a new Dialogue System to your scene.
Creating a new Yarn Project asset in the Project pane.
Creating a new Yarn Script asset in the Project pane.
Assigning your Yarn Project to the Dialogue System.
The Automatic-Layout Dialogue View, added below the Canvas of the unpacked Dialogue System.
Adding the Automatic-Layout Dialogue Wheel to the Dialogue System.
Configuring the automatic start node.
The Wheel Graphic selected, inside the Automatic-Layout Dialogue View.
The Inspector for the Wheel Graphic.
The wheel, customised to display as a hexagon, via a modification to the Density field of the Wheel Graphic.
The Automatic Wheel Option View selected.
The Inspector for the Automatic Wheel Option View.
Tweaks to the Automatic Wheel Option View, to customise the way options are displayed around the wheel.
The default wheel settings and their preview.
The wheel now previewing two regions across the entire .
The wheel previewing five options.
The wheel with a 54 degree angle offset.

Storylets and Saliency Primer

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

Introduction

Storylets and saliency represent an evolution in interactive storytelling techniques. To understand their significance, let's examine how narrative approaches have developed over time.

A lot of the terminology used here, and in Yarn Spinner, are based on the work by Emily Short (2019), and Kreminksi and Wardrip-Fruin (2018), and these are just great resources on the topic, go read them.

The Evolution of Interactive Storytelling

Linear Narratives

The earliest approach to storytelling was linear. Stories followed a single, author-defined path with each moment crafted to lead directly to the next.

A linear story

Branching Narratives

The next evolution introduced branching, allowing stories to have multiple paths for players to explore.

A branching story

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.

If you are ever after a great breakdown of some of the most common shapes and patterns in branching narrative check out Kabo Ashwell (2015).

Storylet-Based Narratives

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.

A freeform story

Understanding Saliency

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.

To be a bit more precise about this, saliency is the way Yarn Spinner does this. There are other approaches, the simplest being "present everything and let the player select the next piece".

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.

Combining linear, branching, and storylet storytelling

You might be going "hang on, this sounds a lot like what [Classic Game] did, a game from ages ago I really enjoyed!?" and you'd be right, it is very similar. These ideas aren't new, nor was the evolution of these concepts as discrete as we've implied here.

Storylet Granularity

The fundamental particles, including the Storylet. Modified image from Wikipedia

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:

  1. 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.

  2. 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.

  3. 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.

Types of Saliency

How do we determine which storylet is "best" to present next? The approach varies based on your narrative goals.

Quality-Based Saliency

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.

Tag-Based Saliency

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.

Directed Saliency

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.

Simple Saliency Approaches

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 in Practice

Storylets serve various narrative functions in games:

NPC Barks

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.

Environmental Flavor

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.

Contextual Reactions

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.

Emergent Narrative

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.

Explorable Narratives

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.

Complete Narrative Systems

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.

Many of these games haven't talked about their approaches as using storylets, but its much easier to talk about it using the terminology already in this guide rather than use the terms the devs themselves said which are different for each game.

Storylets in Yarn Spinner

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

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

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.

You might notice that you can have as many when headers as you want on a node storylet. You might also notice the when: always header on one of the storylets, this is required with node groups to say "this one is always available".

Like line groups, node groups can contain any valid Yarn content, including jumps, detours, and line groups.

Saliency Strategies

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.

Logo

Writing Yarn in VS Code

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.

Writing Yarn Spinner Scripts with VS Code

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 .yarnfile, 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.

Opening a folder

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:

Working with Yarn in Visual Studio Code

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":

The bottom right-hand corner of Visual Studio Code window will only show "Yarn Spinner" if both the Yarn Spinner for Visual Studio Code extension is installed, and the currently active file is recognised as a .yarn file by its extension.

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.

This section of the documentation will talk about Yarn Spinner Script features, like <<jump>>, and others, that you won't have encountered yet. Review it, and then as you work through and , revisit this page and see how the features you've learned about work with the Yarn Spinner Editor.

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.

Working with nodes

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:

Customising the Graph View

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.

Colouring Nodes

For example, if you add the color field to the header, you can colour-code your nodes:

You can use red, green, blue, orange, yellow, or purple. The colours that you see may be different, depending on your VS Code theme.

The color field works like any other header element, and goes below the title and above the ---:

Grouping Nodes

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:

Sticky Notes

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:

Using the Command Palette

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.

You can also use the Command Palette to turn off all the Microsoft-provided "AI" "features by typing Chat: Hide Copilot and hitting Return/Enter.

Variables

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 Variables

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.

Setting Variables

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 and Types

Variables in Yarn Spinner can store one of three types of information: numbers, strings, and booleans:

Type
Possible Values
Examples

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.

Variables and Expressions

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".

Operators

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.

Order of operations

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:

  1. Brackets

  2. Boolean Negation

  3. Multiplication, Division, and Truncating Remainder Division

  4. Addition, Subtraction

  5. Less than or equals, Greater than or equals, Less than, Greater than

  6. Equality, Inequality

  7. Boolean AND, Boolean OR, Boolean XOR

Using Variables in Lines

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:

Write a story that uses variables

1

Create a Yarn Spinner Script that uses variables.

Create a new narrative that uses two variables that track a player name, and an amount of currency. Make sure you declare them.

2

Use the <<set>> command to update the value of the variables appropriately

Make sure you remember the types!

Theming Default Presenters

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.

What we'll be covering

  • 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.

Building the scene

If you haven't already installed Yarn Spinner, follow the instructions at . Once installed, we'll start by building out a basic scene:

  1. Make a new scene in Unity.

  2. From Samples/Shared Assets/Prefabs drag the Basic Arena into the scene

  3. From Samples/Shared Assets/Prefabs add a Camera Rig into the scene

  4. From Samples/Shared Assets/Prefabs drag a Player prefab into the scene

  5. From Samples/Shared Assets/Prefabs drag an NPC prefab into the scene and rename it to be Alice

  6. Add a default dialogue system to the scene, in the Hierarchy right click Yarn Spinner -> Dialogue Runner

  7. Create a new Yarn Spinner project Assets -> Create -> Yarn Spinner -> Yarn Project and name it Rethemed Dialogue

  8. Create a new Yarn script Assets -> Create -> Yarn Spinner -> Yarn Script and name it Alice

  9. Delete the previous camera called Main Camera

The Dialogue

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.

Configuring the scene

  1. Select the Camera Rig and in the inspector drag the Player into the target field

  2. Select Alice and in the Inspector set the Dialogue field to use the new Rethemed Dialogue project

  3. Select Alice and in the Inspector select the Alice node in the dropdown

  4. Select Alice and in the Dialogue Runner field drag in the Dialogue System object from the hierarchy

  5. Select the Dialogue System and in the Inspector set the Yarn Project to use the Rethemed Dialogue project

The Font

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.

  1. Grab the PixelifySans font from LINK TO FONT

  2. Add the font into the Project assets

  3. Go to Window -> TextMeshPro -> Font Asset Creator

  4. Drag the PixelifySans-Bold font into the Source Font field

  5. Click Generate Font Asset

  6. Click Save and save the TMP version of the font into your project

  7. 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.

The 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.

  1. Open the Package Manager by going to Window -> Package Manager

  2. In the side bar select the Unity Registry option

  3. Wait for Unity to populate the manager

  4. Find the package called 2D Sprite

  5. Install it

  1. Add the sprite sheet into the project from link to sprite sheet

  2. Select the sprite sheet and in the Inspector change it's Texture Type to be Sprite (2D and UI)

  3. Set the sprite mode to be Multiple

  4. Set the Pixels per Unit to be 20

  5. Set the Filter Mode to be Point (no filter)

  6. Click Apply

  7. 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.

  1. 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.

  1. Select the left-topmost sprite

  2. Name it Button-Filled-Up

  3. Set it's L, R, T, and B to all be 7

  4. Select the sprite on the right of the Button-Filled-Up sprite

  5. Name it Button-Empty-Up

  6. Set it's L, R, T, and B to all be 7

  7. Select the sprite on the right of the Button-Empty-Up sprite

  8. Name it Button-Continue-Up

  9. Set it's L, R, T, and B to all be 7

  10. Select the sprite on the right of the Button-Continue-Up sprite

  11. Name it Option-Selected

  12. Select the sprite below the Button-Filled-Up sprite

  13. Name it Button-Filled-Down

  14. Set it's L, R, T, and B to all be 7

  15. Select the sprite on the right of the Button-Filled-Down sprite

  16. Name it Button-Empty-Down

  17. Set it's L, R, T, and B to all be 7

  18. Select the sprite on the right of the Button-Empty-Down sprite

  19. Name it Button-Continue-Down

  20. Select the bottom-leftmost sprite

  21. Name it Background

  22. Set it's L, R, T, and B to all be 16

  23. 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.

The Line Presenter

Next, we'll modify the Line Presenter to use our custom sprites instead of the default visuals.

  1. Select the Dialogue System prefab in the Hierarchy

  2. Expand out the Canvas -> Line View and select the Background gameobject

  3. In the Image component of the Inspector change the Source Image to be our freshly sliced Background sprite

  4. In the Image component of the Inspector change the Color to be fully opaque white

  5. In the Image component of the Inspector change the Image Type dropdown to be Sliced

  6. In the Hierarchy select the Line View game object

  7. 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.

  1. Select the Text Mesh Pro field inside of Dialogue System -> Canvas -> Line View -> Character Name

  2. In the Inspector on the Text Mesh Pro component change the Font Asset field to point to the Pixelify-Sans font

  3. Set the font size to be 48

  4. Select the Text Mesh Pro field inside of Dialogue System -> Canvas -> Line View -> Text

  5. In the Inspector on the Text Mesh Pro component change the Font Asset field to point to the Pixelify-Sans font

  6. Set the font size to be 50

  7. Expand the Extra Settings section and find the Margins

  8. 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.

The Options Presenter

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.

  1. Right click on the Options Presenter Dialogue Systemm -> Canvas -> Options View and add a new UI -> Text - TextMeshPro child game object

  2. Rename it to be Option Item

  3. Select the Option Item and in the Inspector find the Text Mesh Pro component

  4. Change the Font Asset to point to Pixelify-Sans font

  5. Set the Font Size to be 50

  6. Expand out the Extra Settings

  7. Set the margins to be Left 30, and Top, Right and Bottom to all be 0

  8. Add a new Image child game object to the Option Item

  9. Rename it to be called Selection Indicator

  10. Make it so it is a full height stretch but with a fixed width of 20

  11. Select the Options Item in the Hierarchy

  12. Add an Options Item component onto the game object

  13. In the Text field drag the Option Item game object in

  14. In the Selection Image field drag in the Selection Indicator game object

  15. Expand out the Selected section of the inspector

  16. Set the Selected sprite to be the Option-Selected sprite

  17. Drag the Option Item game object out of the Hierarchy into the Assets to make it a prefab

  18. 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.

  1. Select the Options View game object in the Hierarchy

  2. In the Inspector find the Options View Prefab field inside the Options Presenter component

  3. Replace the existing prefab with our Option Item prefab we made above

  4. Select the Background child game object of the option presenter

  5. In the Inspector find the Image component

  6. Set the Source Image to be our Background sprite

  7. Set the Color to be fully opaque white

  8. Set the Image Type to be Sliced

  9. Select the Last Line child game object

  10. In the Inspector on the Text Mesh Pro component change the Font Asset field to point to the Pixelify-Sans font

  11. Change the Font Size to be 48

  12. Set the Font to be using Bold

  13. 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!

Congrats!

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!

Yarn Spinner in Unity Scenes

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 .

1

Creating a Yarn Project

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:

  1. In the Unity Editor, open the Assets menu -> Yarn Spinner -> and choose Yarn Project

  2. Name your new Yarn Project file (e.g., MyGame)

  3. 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.

Our recommended best practice is to create a Dialogue folder inside your assets, and store your Yarn Spinner Project(s) and Script(s) in it. The name of the folder is not significant, it's just useful to group your Yarn Spinner Script(s) and Project(s) together.

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.

2

Writing Dialogue in Yarn Scripts

Now, let's create a Yarn Script to write your dialogue:

  1. Open the Assets menu -> Yarn Spinner > and choose Yarn Script

  2. Name your script (e.g., Introduction). This will create a file named Introduction.yarn

  3. 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

3

Setting Up the Dialogue System in Unity

To use your dialogue in-game, you need to add a Dialogue System to your scene:

  1. In your scene hierarchy, right-click and choose Yarn Spinner -> Dialogue System

  2. With the Dialogue System selected in the Hierarchy, locate its Dialogue Runner component in the Inspector.

  1. Drag your Yarn Project from the Assets view into the "Yarn Project" field in the Dialogue Runner:

  1. 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 .

4

Understanding Dialogue Presenters

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.

Line Presenter

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:

Options Presenter

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

Line Advancer

The Line Advancer allows players to progress dialogue using input. You can configure it by:

  1. Selecting your Line Presenter in the hierarchy

  2. Finding the associated Line Advancer component

  3. Configuring the input method (keycode, button, etc.)

5

Variable Storage and Line Providers

Variable Storage

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

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:

6

Testing Your Dialogue

To test your dialogue system:

  1. 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:

  1. 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:

  1. Also set the the Dialogue Runner to Start Automatically, and run the appropriate Yarn node:

  1. 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:

Troubleshooting

Dialogue doesn't start

  • 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

Dialogue text doesn't appear

  • Ensure the Line Presenter component is properly configured

  • Check that the Canvas Group and Text components are correctly assigned

Variable issues

  • 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

Unity Projects + Yarn Spinner

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.

Yarn Spinner Scripts

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.

Yarn scripts need to be part of a in order to be used in your game. You'll learn what Yarn Projects are later on in this document.

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.

Creating a New 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.

Creating a Yarn Script in Unity is exactly the same as creating a .yarn file externally (i.e. in macOS Finder or Windows Explorer), and dragging it into the Assets folder of your Unity project, or directly into the Project pane (where the new Yarn Script we created through the process above appeared) in Unity.

Editing Yarn Scripts

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 .

Yarn Projects

A Yarn Project is a file that links multiple Yarn Spinner Scripts together.

Creating a New Yarn Project

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.

Adding Yarn scripts to a Yarn Project

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.

Pattern
Description
Examples

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.

Creating a Project from a Script

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.

Managing Variables

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.

Managing Localisations and Assets

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.

Unless you change it to something else, Yarn Spinner will set the base language to your computer's current locale.

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 .

Using Yarn Projects with Dialogue Runners

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.

If you try to start a Dialogue Runner and it doesn't have a Yarn Project, or the Yarn Project doesn't have any Yarn scripts, or if any of the Yarn scripts contain an error, the Dialogue Runner won't be able to run.

Inspector

Property
Description
/// 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!
===
Installation for Unity
Our rethemed line presenter
The package manager showing off the now installed 2D Sprite package
The Sprite Editor showing our sprite sheet
Our rethemed line presenter
Our restyled options presenter

You can learn how to add the Yarn Spinner for Samples to your project over at Samples.

Markup

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

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].

Overlapping Attributes

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]

Self-closing Attributes

Attributes can self-close:

[wave/]

A self-closing attribute has a length of zero.

The Close-All Marker

The marker [/] is the close-all marker. It closes all currently open attributes. For example:

[wave][bounce]Hello![/]

Properties

Attributes can have properties:

[wave size=2]Wavy![/wave]

This attribute 'wave' has a property called 'size', which has an integer value of 2.

Short-hand Properties

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.

Property Types

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]

Whitespace Trimming

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

If you don't want to trim whitespace, add a property trimwhitespace, set to false:

A [wave trimwhitespace=false/] B 
// (produces "A  B")

Showing Special Characters to the Player

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.

Escaping single [ and ] characters

If 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.

If you need to show a blackslash in your text, use two blackslashes:

Here's a backslash! \\

This will appear as:

Here's a backslash! \

The nomarkup Attribute

If 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!

The character Attribute

The 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.

Text Replacement Markup

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.

Plurals across different languages

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.

Pluralising with 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!

Using makers in your game

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?
===
Scripting Fundamentals
Advanced Scripting
Previewing Your Dialogue
https://graphviz.org/doc/info/lang.html
A folder on disk containing a Yarn file.
The Open button on the Visual Studio Code start screen.
Opening a folder containing .yarn files.
Choosing a .yarn file to edit.
Choose New File... from the File menu.
Name a file with a .yarn extension.
The status bar of VS Code showing that the Yarn Spinner extension is active.
Command/Control allows you to click a node name to jump to its definition.
Choosing a node to jump to with autocomplete.
Using the the special /// comment syntax to describe a variable.
Viewing a variable's description.
Using `<<declare>> to declare a boolean.
An error arising from trying to use a boolean like an integer.
Comments from C# commands in your Yarn scripts.
The Graph View button.
Nodes stacked on each other in the graph view.
Rearranging your nodes.
The position of each node is stored in the header.
The Add Node button.
Double-clicking a node in the Graph View to edit it in the Text View.
The Jump to Node menu.
Show in Graph View
Colour-coded nodes.
Grouping nodes in the Graph View.
The Command Palette
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!
===
installed the Yarn Spinner for Unity package
markup
options
Variable Storage
TextMeshPro Text Component
Creating a new Yarn project
The Inspector for a Yarn Spinner Project asset.
The Yarn Project asset assigned to the correct field.
Setting the Yarn Spinner DIalogue Runner to start automatically and run the node titled Start.
The default Line Presenter.
The Line Presenter, selected in the Hierarchy.
The Line Presenter's Inspector.
Configuring the Line Advancer.
The In Memory Variable Storage component.
The bottom of the Inspector for a Yarn Project asset, showing the Yarn Scripts it is including.
The Yarn Project asset assigned to the Dialogue Runner.
Settings for starting automatically, and running a specific node, found on the Dialogue Runner component.
Dialogue running via Yarn Spinner for Unity and the default Line Presenter and Options Presenter.
The Unity console showing that we forgot to close a Yarn Command on line 11 of the Yarn Script.

*

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.

Writing Yarn in VS Code
Yarn Project
node
Yarn Spinner Editor
variable
Adding Localizations and Assets to Projects
Creating a new Yarn script.
The Yarn Project inspector. The configurable properties of the Yarn Project are visible at the top, and the information about the imported project is visible at the bottom.
Creating a new Yarn project.
An example of a custom Source Files setting. In this example, the Yarn Project will use all .yarn files in the same folder and its sub-folders, as well as the file Common.yarn in the folder above it.
The 'Create New Yarn Project' button in the Inspector.
The list of variables in a Yarn Project.
Updating the base language of a Yarn Project

Feature Tour

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?

Features Covered

  • and

  • and

  • and

  • Storylets

  • A Dialogue Interactible component and node-character associations

Tour Structure

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.

Dialogue Interactible Component

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.

Nodes, Lines
Options
Jumps
Detours
Variables and Interpolation
Flow Control and Branching
Commands
Functions
The Feature Tour Sample.
Sally talking about variables
Sally's dialogue interactible component

You can learn how to add the Yarn Spinner for Samples to your project over at Samples.

FAQ

A FAQ for the various components overall Yarn Spinner project.

FAQ for Yarn Spinner

Is Yarn Spinner a language? A tool for Unity? An editor?

Yarn Spinner isn't a single project, but is a collection of projects. Conceptually, we think of Yarn Spinner as having Core Components, some Add-ons, and some Yarn Labs Experiments.

If you're new to Yarn Spinner, you don't necessarily need to understand the components just yet. We strongly recommend starting at First Steps with Scripting.

Core Components

These are production-ready components, with stable, established, released versions:

  • Yarn, the language you write your dialogue and narrative in: you write Yarn Spinner Scripts in Yarn.

  • Yarn Spinner for Visual Studio Code, the extension for the popular free text editor, Visual Studio Code, that gives it an understanding of the Yarn language, and helps you to write Yarn scripts, with a graph visualisation tool, and other features.

  • Yarn Spinner for Unity, the package you use to import and use your Yarn scripts in games you build in Unity.

  • Try Yarn Spinner, an online tool that allows you to write Yarn scripts and Play them in a web browser. It's useful to write basic Yarn, and test things out. It's just a website you can visit!

⭐️ To learn to use the Core Components, jump into the Beginner's Guide.

Add-ons

These are projects that supply additional features to Yarn Spinner, and exist as add-ons to the free, open source projects that comprise the bulk of Yarn Spinner:

  • Dialogue Wheel for Yarn Spinner, an add-on package for Yarn Spinner for Unity that provides Mass Effect-style dialogue wheel dialogue views.

  • Speech Bubbles for Yarn Spinner, an add-on package for Yarn Spinner for Unity that provides customisable speech bubbles as dialogue views.

⭐️ To purchase the Add-ons, visit the Yarn Spinner Itch.io Store, or the Unity Asset Store, or just read the documentation.

Yarn Labs Experiments

These are experimental projects that are likely to eventually be released into Core, but are currently in early, or experimental stages:

  • Yarn Spinner for Unreal, the package you use to import and use your Yarn scripts in games you build in Unreal.

  • Yarn Spinner for Godot, the package you use to import and use your Yarn scripts in games you build in Godot.

  • Yarn Spinner for Rust, the package that you use to import and use your Yarn scripts in games you build using the Rust-based Bevy engine.

Yarn Spinner for Unreal is moving from Yarn Labs to Core Components in 2025-2026.

Start learning

If you're new to Yarn Spinner, we recommend that your next step is First Steps with Scripting

Is Yarn Spinner free or paid? I heard it was Open Source?

Yarn Spinner is developed in the open, as open source software on GitHub. You can also install it from the source on GitHub, for free.

If you want to use Yarn Spinner for a commercial game, we recommend purchasing it from our Itch.io Store or the Unity Asset Store. You can also contact us for a custom license, or to arrange consulting, support, or hire us to add features especially for you!

FAQ for Yarn Spinner Scripts

What has changed for Yarn Spinner 3 vs. Yarn Spinner 2?

We've added a lot of new features, including Once, Detour Command, Line Groups, Node Groups, and more. Yarn Spinner has a lot of features designed for storylets and saliency, and you can learn about those by reading through the Saliency guides here.

FAQ for Yarn Spinner for Unity

Text

My text is not displaying at all?

You need to install the TextMeshPro Essential Resources. Open the Window menu -> TMP -> and choose Import TMP Essential Resources.

How do I style text? How do I make some words bold, italic, colorful, etc?

Yarn Spinner doesn't directly do text rendering, we use TextMeshPro to do the final render which has support for rich-text. However, writing TMP tags across a project is impractical so we have provided various styles and palettes via Yarn Markup Attributes. These provide a collection of common visual changes as well as offer a common interface There is a sample showing off using the built in markup to style your text as well as how you can make your own Markup to do more advanced styling on your text.

How do I animate wavy text, like in Night In The Woods?

Yarn Spinner doesn't handle text rendering. You'll need a separate wavy text system, like Text Animator.

How do I use Yarn Markup?

Markup lets you mark a range of text (words, phrases) in a generic way, for whatever use. You could use it to style text, add sentence markers, make clickable words, etc.

// Yarn script example of custom "wavy text" markup.
Oh, [wave]hello[/wave] there!

// After compiling, text will look like: "Oh, hello there!"
// And then the resulting markup data will look like:
// - name: "wave"
// - position: 4
// - length: 5

Note that YS only processes the text data. You must still code the actual markup effect yourself. See Markup.

Variables

How do I print the value of a variable in dialogue?

Wrap the variable (or any expression) in curly braces ({, }) to evaluate and output it. For more info, see Variables.

<<set $variableName to "a string value">>
The value of variableName is {$variableName}.
// This will appear as "The value of variableName is a string value."

How do I read / write Yarn variables from a C# script?

You can register to be notified when a variable changes. To do this, in C#, call AddChangeListener() on a Line Provider and provide a delegate that should run when the variable changes:

var stringListener = VarStorage.AddChangeListener("$stringVar", (string value) =>
{
    Debug.Log($"$stringVar changed to " + value);
});

When you no longer need to be notified of changes to a variable, you call Dispose() on the listener. If you don't do this, the variable storage will continue to call this delegate every time the variable changes, which you probably don't want to do.

stringListener.Dispose();

​If you're implementing your own variable storage system, you must call the NotifyVariableChanged() method every time a variable changes value, in order to notify any change listeners:

public override void SetValue(string variableName, float floatValue)
{
    // (code for updating the variable omitted)
    // Notify the change listeners that this variable changed
    NotifyVariableChanged(variableName, floatValue);
}

Alternatively, to read Yarn variables from C#, use VariableStorageBehaviour.TryGetValue<T>().

To write Yarn variables from C#, use VariableStorageBehaviour.SetValue().

Don't forget the $ when writing the variable's name!

variableStorage = GameObject.FindObjectOfType<InMemoryVariableStorage>();
float testVariable;
variableStorage.TryGetValue("$testVariable", out testVariable);
variableStorage.SetValue("$testVariable", testVariable + 1);

You should also check out our Variable Storage section, and the Custom Variable Storage Components guide.

How do I read / write C# variables from a Yarn script?

To read and write C# variables from Yarn, you must first code Yarn Functions and Commands in C#.

static int myNumber = 10;

// note: all Yarn Functions must be static
[YarnFunction("getMyNumber")]
public static int GetMyNumber() { 
    return myNumber; 
}

// Yarb Commands can be static or non-static
[YarnCommand("setMyNumber")]
public static void SetMyNumber(int newNumber) { 
    myNumber = newNumber;
}

Then call the functions and commands in Yarn:

My number is { getMyNumber() }!
<<setMyNumber 999>>
But now it's { getMyNumber() }!

How do I 'sync' variables between Yarn and C#?

See the previous answers on working with variables. But we recommend avoiding any "sync" pattern, because then you'll have to track and maintain the same data in two different places. Programmers usually prefer a "single source of truth".

Data should live in only one place. Variables should either live in Yarn or live in C#, and not in both.

How do I load and save data / variables / dialogue state? (Like for a save game system)

To save the current node, save the value of DialogueRunner.CurrentNode somewhere. Then to restore it, call DialogueRunner.StartDialogue() and pass in the saved node name.

To save variables, see DialogueRunner.SaveStateToPersistentStorage(). Then to load variables, call DialogueRunner.LoadStateFromPersistentStorage(). These methods use Unity's built-in JSON utility to serialize a dictionary of variables to the current platforms persistent data path folder.

For custom save systems, create your own variable storage by subclassing VariableStorageBehaviour and implementing its methods. Study InMemoryVariableStorage.cs as an example. For more info, see Guide: Yarn Variables and Variable Storage.

Flow Control

How do I jump to a specific node? How do I switch nodes while dialogue is running?

To jump to a node from Yarn, use <<jump (nodeName)>>. See Nodes, Lines, and Options.

To jump to a node with C#, just call DialogueRunner.StartDialogue(). While you can jump to another node while dialogue is running we recommond not doing this and calling DialogueRunner.Stop() before starting another piece of dialogue.

How do I jump to a specific line in a node?

Jumping to a specific line in a node is currently not supported. Instead, jump to the start of a node.

Interaction/UI

How do I continue dialogue with key/button press instead of clicking the continue button?

This is demonstrated by the Line Advancer component, which handles this exact scenario (among others). We recommend looking at how it handles the different ways of hurrying up or advancing lines as a starting point for your own games.

How do I show the last line of text when options are shown? How do I skip the last line of text before a set of options?

Yarn Spinner automatically adds a #lastline tag to a line when the next step is a set of options. Create a Custom Dialogue Presenter that uses YarnProject.lineMetadata.GetMetadata() to check for "lastline" and perform the behavior you want.

How do I customize dialogue display?

To display anything in Yarn Spinner, use a Dialogue Presenter component. There are two in-built presenters that demonstrate the basic functionality Line Presenter for lines of dialogue, Options Presenter for dialogue choices.

Most projects will need custom presenters. We recommend a modular architecture where each presenter handles it's own specific part of the display. An example of this is in the built in presenters the Line Presenter only handles showing lines, and the Options Presenter which only handles choices.

How do I make the Line Presenter's Typewriter pause?

You can pause the typewriter effect by using the built in pause markup:

Player: Sure would be nice if I could take a breather [pause /]  right now.

How do I play a Yarn node when I click / tap on an object?

Write input code to detect clicking / tapping, then call DialogueRunner.StartDialogue().

How do I play a Yarn node when I approach an object and press a button? (RPG-like talking to NPCs)

This implementation will vary for every game, so we purposely do not attempt to design a one-size-fits-all generic NPC system. Here's some example pseudo-code to make your own:

if (player presses SPACE)
    then find the nearest NPC
    get that NPC's dialogue node name
    call DialogueRunner.StartDialogue() with the NPC's dialogue node
    disable player movement

The samples do have a shared script called the Dialogue Interactible which show off one way to achieve this behaviour.

How do I position a speech bubble above an NPC's head, like in A Short Hike?

The math / code is a little complicated. Calculate the NPC's on-screen position, then convert this screen position to UI canvas space, and reposition the dialogue bubble. We do have a paid add-on that handles all of this (and more) for you if you'd prefer to just have it all working.

How do I implement a resizing dialogue bubble / SMS messaging interface?

This is more about Unity UI rather than Yarn Spinner. For a working example, see the Phone Chat sample.

To make a resizing dialogue bubble that automatically fits text, you will need a complex UI setup. Study the UI game objects and components in the sample scene. For more context about how it works, see this Unity UI Layout Groups explainer by Hallgrim Games.

How do I get text from a Text Input field into my Yarn story?

This mainly involves Unity UI, and assumes that your project already has a system where a player can input text like a TMPro Input Field component. If the player input needs to happen in the middle of dialogue execution then you can trigger it with a Yarn Command and wait for the player input if needed.

Once you have the player input value, you can store it in a C# variable and access it through a Yarn function, or store that value in a Yarn story variable. FAQs for how to access variables in Yarn and YarnSpinner are here.

System

How do I generate a Yarn Project at runtime? How do I load/compile Yarn scripts at runtime?

The intended workflow is to generate and compile Yarn Projects at editor time, not runtime. See Yarn Projects.

Compiling a Yarn script at run-time is more complex than it first appears, because it often interacts with the very specific needs of your game, and we can't provide a one-size-fits-all approach to it. If you want to implement run-time loading in your own game, the place to start looking is the API documentation for the Yarn.Compiler namespace. Please note that this is not something that we encourage people who are new to Yarn Spinner to do!

How many Yarn files should I have? Can my entire game be in one project or script? Or one project per scene? Is my project or file too big?

There is no real technical limit on the number of Yarn scripts or the size of Yarn Projects. You decide how to organize your data, and every project has different needs. Some factors to consider:

  • Simplicity. Putting everything into one big script file or one big project file is simpler sometimes.

  • Ease of writing. Writers may prefer to think in terms of one file per scene, one file per chapter.

  • Localization. 1 Yarn Project = 1 CSV spreadsheet per language. When translating, it is usually easier to work with fewer files, rather than fragmenting the translation across many files. As a workaround for games that need multiple Yarn Projects, you may prefer to create a single editor-only Yarn Project that's just for generating string files and translations. See Localizations and Assets.

Localization

How do I fetch any Yarn localized string in C#?

Some devs use YS to manage all in-game localized text, like UI strings. This use isn't intended, but it's possible. Manually create a Yarn.Line struct, set the line ID (see Localization), and then pass the struct into GetLocalizedLine().

Yarn.Line targetLine = new Yarn.Line();
targetLine.ID = "line:lineid"; // replace 'lineid' with the actual line ID
LocalizedLine outputLine = LineProvider.GetLocalizedLine(targetLine);
Debug.Log(outputLine.Text.Text);
Samples

I installed the samples package and there are no samples in the package manager, what gives?

For some reason when you install the samples package from the Unity Asset Store it has two entries inside the Package Manager. Only one copy of the samples package is installed, you don't have duplicates of the assets and scripts, it just appears in the list twice.

The first of these entries, under the Packages - Asset Store section has no samples, or even a samples tab. The second entry underneath Packages - Yarn Spinner Pty Ltd section has the samples. We are currently seeing if we can make this better, but for now if you select the second entry it will have the samples tab and you can install samples from there.

Other

How do I credit Yarn Spinner in my game?

Please visit the Crediting Yarn Spinner page for more information.

Are there any examples of Yarn Spinner implementations in Unity?

Yes! We ship a whole collection of Samples.

Asynchronous Programming

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.

What we'll be covering

  • Basics of asynchronous programming and async and await keywords

  • Supported awaiters

  • Creating your own async code

  • How to cancel awaited code

  • Using completion sources

Async Programming

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:

  1. Fill the kettle

  2. Boil the kettle

  3. Add tea to the cup

  4. Add the water to the cup

  5. Add milk to the cup

  6. 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.

While it's tempting to think of async code as parallel processing, they're distinct concepts. Asynchronous programming is about non-blocking execution, not parallel or multithreaded computation.

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.

async and await keywords

The primary distinction between asynchronous programming and traditional approaches is the introduction of new keywords. The most significant is await, which tells C# to pause execution at that point until the specified asynchronous operation completes, then resume from there.

You can use await with any method flagged as asynchronous or any method returning an awaiter (more on these shortly). This brings us to the async keyword, which must be added to method signatures that contain awaiting calls. Unity will report errors if you try to await code in a method without the async keyword, or if you include async in a method that doesn't await anything.

These keywords represent the main syntactic differences. While there's considerable complexity enabling asynchronicity under the hood, you rarely need to concern yourself with those details. Simply await asynchronous code and add asyncto methods that perform awaiting operations.

This approach brings an additional benefit: it makes asynchronous code easily identifiable. With a quick glance at keywords and method signatures, you can understand which code executes asynchronously and which code awaits other operations, resulting in cleaner, more maintainable code.

Async vs Coroutines

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.

Awaiters

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.

These constructs have various names across different environments: promises, futures, tasks, and awaitables, among others. We'll use "awaiters" throughout this guide, but you may encounter these other terms elsewhere.

Yarn Spinner supports three types of awaiters:

  • Tasks

  • Awaitable

  • UniTask

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.

We recommend using UniTask if possible, as it offers the best balance of features and performance among the three options.

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.

Tasks vs Awaitables vs UniTask

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.

Making async code

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.

In these examples, we use YarnTask, but feel free to adapt them to your preferred awaiter type.

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));
}

Cancellation

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!

Returning Values

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.

Completion Sources

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;

Conclusion

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.

Logo
The customised options in action.

Commands and Functions

Defining Commands

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.

The YarnCommand attribute

The 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:

Type
Note

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

If you're using Unity 2021.1 or earlier, you'll need to use the Window -> Yarn Spinner -> Update Yarn Commands menu item whenever you add, remove or change a YarnCommand-tagged method.

If you're using Unity 2021.2 or later, this is done for you automatically.

Finding Yarn Commands in your Project

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.

This feature is available in Unity 2021.2 and later.

Adding commands through code

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

YarnCommand vs AddCommandHandler

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.

Making Commands Using Coroutines

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

Defining Functions

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:

  1. Functions must return a value.

  2. 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

If you're using Unity 2021.1 or earlier, you'll need to use the Window -> Yarn Spinner -> Update Yarn Commands menu item whenever you add, remove or change a YarnFunction-tagged method.

If you're using Unity 2021.2 or later, this is done for you automatically.

Creating Custom Commands and Functions

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();
    }
    
    
}

assets menu - yarn spinner - dialogue presenter script

Convert.ChangeType
Convert.ChangeType
The Automatic-Layout Dialogue Wheel in action.

Dialogue Wheel

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.

Image Wheel

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.

The Image Wheel, showing the 6 option locations.

To learn how to use the Image Wheel, read this guide.

Automatic-Layout Dialogue Wheel

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.

The Automatic-Layout Dialogue Wheel, showing 8 options.

To learn how to use the Automatic-Layout Dialogue Wheel, read this guide.

Dialogue Wheels for Yarn Spinner. A light blue oval shape appears over a purple-lit background, with three text labels around it: "We're in space", "we should kiss", and "I should go".  "We should kiss" is highlighted.

Line Provider

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.

If you don't set up a Line Provider for a Dialogue Runner, it will automatically create a Buit-In Localised Line Provider, and configure it to use the user's current language.

Dialogue Runner

Learn about the Dialogue Runner, which runs the contents of your Yarn Scripts and delivers lines, options and commands to your game.

Dialogue Runners and Systems

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.

Dialogue Runner

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.

The bare-bones minimum that a Dialogue Runner needs in order to work is a Yarn Project and at least one Dialogue Presenter.

If you don't set up a Variable Storage or a Line Provider, the Dialogue Runner will use temporary placeholders.

Inspector

Property
Description

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.

Signals

Signal
Description

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.

Options Presenter

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.

An Options Presenter.

An Options Presenter only displays options, and doesn't display lines. You can use an additional Dialogue Presenter to handle these, like Line Presenter or a custom Dialogue Presenter of your own. We provide a default Line Presenter and Options Presenter.

Inspector

Property
Description

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.

Advanced Saliency

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.

What we'll be covering

  • 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

The Room

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:

  1. 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.

  2. 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 Sample

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.

The buttons

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.

The Void

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

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

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.

The Yarn

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:

Character Interpolation

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.

The Whens

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.

Sample Limitations

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.

Yarn Project
Variable Storage
In Memory Variable Storage
Line Provider
Text Line Provider
Dialogue Presenters
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?!
The advanced saliency sample showing off an office based scenario

You can learn how to add the Yarn Spinner for Samples to your project over at Samples.

Variable Storage

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.

What We'll Be Covering

  • Using the variable storage system from your Unity code

  • Reading and writing variables via C#

  • Generated variable storage wrappers

  • Using the in-memory variables storage system

  • Making your own Variable Storage systems

Yarn Variables

Yarn Spinner deliberately limits variable types to strings, numbers, booleans, and enumerations (which themselves are wrappers around the first three types). At its core, Yarn Spinner isn't actually concerned with variables themselves, but rather with values. When encountering a variable, all Yarn Spinner needs is a way to replace that variable with its corresponding value.

This creates an interesting challenge: while computers handle changing values with ease, humans struggle to track unnamed values. Our brains naturally organize information through naming conventions. Variables bridge this gap by giving changeable values names that we can easily reference and understand.

This is where the Variable Storage system comes into play. It handles the translation between human-friendly named variables and computer-friendly values. The system performs this crucial mapping function, while most of Yarn Spinner simply asks "what's the value of $gold?" without concerning itself with how that value is stored or managed.

Using the Variable Storage

Every game has unique requirements for variable handling. Since Yarn Spinner can't anticipate your specific storage methods, it uses a common interface to get and set variables. This interface connects to your game through the Dialogue Runner.

The Dialogue Runner includes a VariableStorage property that anything with a reference to the runner can access. When using a custom variable storage system, setting this property configures Yarn Spinner to use your implementation. If you don't provide a variable storage system, Yarn Spinner creates a temporary one automatically.

For beginners, we recommend letting Yarn Spinner handle variable storage temporarily. As your project grows, you'll likely want to implement a more permanent, game-specific storage solution.

Accessing Yarn Variables in your Code

While using variables within Yarn scripts is straightforward, accessing them from C# requires a few additional steps. Because the variable storage system is designed to be replaceable, Yarn Spinner uses the same approaches we'll explore here to interact with variables.

Reading Yarn Variables

To read a Yarn variable in C#, you'll need a reference to your game's dialogue runner. Through this reference, you can access the variable storage system:

// 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);

Generated Wrapper

While the methods above work effectively, they're not particularly convenient. Yarn Spinner v3 introduces a more elegant solution: a generated wrapper around your variable storage that provides direct property access to each variable.

For example, if your Yarn script includes:

/// the 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:

  1. In the Inspector, check the Generate Variables Source File box

  2. Enter a class name

  3. Specify a namespace

  4. Select the parent class for your variable storage

  5. Click the Apply button

A generated variable storage settings

This creates a new C# file containing your wrapper class. Remember to connect this variable storage to your dialogue runner before using it.

In-Memory Variable Storage

Yarn Spinner's built-in in-memory variable storage works well for development projects and smaller games. It wraps a standard dictionary and is automatically created if you don't provide your own storage system.

Its primary limitation is ephemerality—variables only exist as long as they remain in memory. When a scene unloads or the game closes, any unsaved changes are lost. The system does support persistence through the dialogue runner's SaveStateToPersistentStorage and LoadStateFromPersistentStorage methods, but you must call these explicitly at appropriate times.

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.

Making your own Variable Storage System

Larger games typically already have established systems for managing game state and handling save/load functionality. Rather than maintaining separate systems for Yarn variables, why not integrate them into your existing architecture? This provides a single source of truth for all game data, simplifying development and maintenance.

To make your existing state system compatible with Yarn Spinner, you'll need to implement a VariableStorageBehaviour subclass. This MonoBehaviour implements interfaces that allow Yarn Spinner to interact with your storage system.

You'll need to implement eight key methods, starting with these four for individual variable operations:

void SetValue(string variableName, string stringValue)

Associate the string stringValue with the variable named variableName

void SetValue(string variableName, float floatValue)

Associate the float floatValue with the variable named variableName

void SetValue(string variableName, bool boolValue)

Associate the boolean boolValue with the variable named variableName

bool TryGetValue<T>(string variableName, out T result)

Retrieve the variable named variableName as a type T and store it into the resultout parameter. Return true if this was possible or false otherwise

These handle most variable storage interactions. You'll also need two utility methods:

void Clear()

Delete all variables from the variable storage

bool Contains(string variableName)

Return true if there is a variable named variableName in the variable store

Finally, implement two bulk storage and retrieval methods used by the built-in persistence functions and editor utilities:

void SetAllVariables(Dictionary<string, float> floats, Dictionary<string, string> strings, Dictionary<string, bool> bools, bool clear = true)

Is a bulk setter for all variables. The three dictionary parameters represent each of the floats, strings, and bools needed to be saved. The clear parameter indicates if the variable storage should clear itself before applying the bulk set.

(Dictionary<string, float> FloatVariables, Dictionary<string, string> StringVariables, Dictionary<string, bool> BoolVariables) GetAllVariables()

Returns all variables in the store. Returns them as a 3-tuple of dictionaries, each dictionary should contain all variables in the store of it's type, with the types being float, string, bool.

Smart Variables

Yarn Spinner v3 introduces Smart Variables, which function like computed properties in C#. Their values are determined by running Yarn code rather than direct variable lookup.

When implementing your own variable storage, you don't need to handle smart variables directly. The VariableStorageBehaviour base class already provides this functionality, which your subclass inherits automatically. We strongly recommend against overriding this behavior unless you fully understand the implementation details.

Example

Let's integrate Yarn variables into an existing game state system with some interesting complexities. Our example system has two key characteristics that make integration challenging:

  1. It stores the complete history of variable changes rather than just current values, perhaps for supporting time-rewind mechanics or achievement tracking.

  2. 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.

If you're unfamiliar with perfect hashing, you might wonder how we can use indices without storing keys. Perfect hashing creates collision-free mappings from keys to array indices. Unlike standard hashing, it guarantees unique indices for each key, allowing direct array access without comparison operations.

The main limitation is that all keys must be known in advance, but since our variable set is fixed after development, this works perfectly. Our implementation uses the Hash, Displace, and Compress algorithm LINK with Laurent Dupuis's C# implementation LINK

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.

And we're done!

With these additions, we've successfully adapted our existing game state system to serve as a Yarn Spinner variable storage provider. The integration required minimal additional code, mostly consisting of adapters that call into our existing methods. This demonstrates how straightforward it can be to integrate Yarn Spinner with your existing architecture.

For the complete example code, see the sample project.

Using Image Wheel

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.

Using the Image Dialogue Wheel

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:

Adding a new Dialogue System to your scene.

If prompted, click the Install TMP Essentials button to install TeshMesh Pro (TMP).

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:

Creating a new Yarn Project asset in the Project pane.

Similarly, also in the Narrative folder, create a new Yarn Script to use:

Creating a new Yarn Script asset in the Project pane.

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.

Initial sample story for Image Dialogue Wheel
WheelDemoScript.yarn
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>>
===

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:

Assigning your Yarn Project to the Dialogue System.

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:

The Image Dialogue Wheel, added below the Canvas of the Dialogue System.

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:

Adding the Image Dialogue Wheel to the Dialogue System.

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!

The Image Dialogue Wheel in action.

Customising the Image Dialogue Wheel

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:

If you use set-opt to specify more options than the six-segment wheel can handle (i.e. a maximum of 3 options on each side), things may not work as expected

Review the provided Image Dialogue Wheel Example for more information, or check out the guide on Using Auto-Layout Dialogue Wheel.

Voice Over and Localisation

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.

You can learn how to add the Yarn Spinner for Samples to your project over at .

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.

Note this isn't a lip-sync sample, we are just using lip-sync data as a way to demonstrate multiple non-out-of-the-box assets associated with a line.

What we'll be covering

  • 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

Line Providers and the Ineffable Issue of Asset Association

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:

  1. The built-in localised line provider (BuiltinLocalisedLineProvider) for projects without specific localisation workflows

  2. 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.

You might notice both of these are based around your localisation approach and be wondering why. A line of text is intrinsically tied to localisation. Even if you aren't intending on translating or supporting more than your language, the work for a line provider to support one language is the same as supporting multiple languages. So we take a localisation first approach, even if you have only one localisation.

Asset Association

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.

If you have different needs for assets you'll have to create your own line provider which knows how to link the disparate pieces together and give it to the line as the assset for the line.

Built In Localisation

The built-in localisation system requires two components for each language:

  1. String Table: Essential for displaying lines. The base localisation (typically English) is automatically configured.

  2. 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.

Folder of lip-sync data assets

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:

The built-in localisation version of the sample project

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.

We assume you are already fairly comfortable with using Unity localisation. For information on how to get started with it if you are new to it, check out the official Unity docs.

Rather than maintaining a list of locales, the project simply references a table collection:

The Unity Localisation version of the sample project

Similarly, the Unity Localisation Line Provider doesn't configure the current language—it simply references the necessary string and asset tables:

The Unity Localisation line provider for the sample project

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:

The Unity Localisation asset associator

This wizard automatically detects locales from global settings. You'll need to:

  1. Connect the asset table to the Asset Table Collection field

  2. Set the asset type

  3. 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:

The asset table for the sample

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.

Play back

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:

Voice Over Presenter

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.

TextureLipSyncView

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.

Samples
Dialogue Wheels for Yarn SpinnerUnityAssetStore
Dialogue Wheel for Yarn Spinneritch.io

Make Options Timeout

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.

You can learn how to add the Yarn Spinner for Samples to your project over at .

What we'll be covering

  • 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.

Building the scene

If you haven't already install Yarn Spinner link to install guide. Once installed we will start by building out a basic scene.

  1. Make a new scene in Unity.

  2. From Samples/Shared Assets/Prefabs drag the Basic Arena into the scene

  3. From Samples/Shared Assets/Prefabs add a Camera Rig into the scene

  4. From Samples/Shared Assets/Prefabs drag a Player prefab into the scene

  5. From Samples/Shared Assets/Prefabs drag an NPC prefab into the scene and rename it to be Alice

  6. Add a default dialogue system to the scene, in the Hierarchy right click Yarn Spinner -> Dialogue Runner

  7. Create a new Yarn Spinner project Assets -> Create -> Yarn Spinner -> Yarn Project and name it Timeout Dialogues

  8. Create a new Yarn script Assets -> Create -> Yarn Spinner -> Yarn Script and name it Alice

  9. Delete the previous camera called Main Camera

The Dialogue

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.

Configuring the scene

  1. Select the Camera Rig and in the inspector drag the Player into the target field

  2. Select Alice and in the Inspector set the Dialogue field to use the new Timeout Dialogues project

  3. Select Alice and in the Inspector select the Alice node in the dropdown

  4. Select Alice and in the Dialogue Runner field drag in the Dialogue System object from the hierarchy

  5. Select the Dialogue System and in the Inspector set the Yarn Project to use the Timeout Dialogues project

The finished scene

Making a Custom Options Presenter

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.

Making the Bar UI

First, we'll create the UI elements for our timer by modifying the existing canvas from the Dialogue System prefab.

If you want to see what this looks like as you go, you might want to set the alpha of the canvas group to 1. Just make sure to put it back to 0 before you play the scene!

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. In the inspector add a new Layout Element component to the timer container.

  6. 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.

  7. Add a new empty gameobject to the timer container and name it timer bar.

  8. Give the bar an image component.

  9. Change the bars rectangle widget to be centre-aligned + vertical stretch.

  10. 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.

The RectTransform of our timer

Our UI work is now complete. Next, we'll write the code to make the bar shrink over time.

Finished Hierarchy for our scene
  1. Create a new monobehaviour script and name it TimeoutBar.cs

  2. Open up the new file and replace the imports with the following:

using System.Threading;
using UnityEngine;
using Yarn.Unity;
  1. 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.

  1. 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.

  1. 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.

  1. 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.

For this tutorial we are using YarnTask as our underlying awaitable infrastructure. This is because we need it to work with any of the supported awaitable structures. If you are adapting this tutorial for your own projects it would be better to use the same awaitable type everywhere.

  1. Add that class as a component to our bar

  2. Drag the bar gameobject into the Bar field in the TimeoutBar inspector and now the bar is done.

The completed timeout bar inspector

To see a complete version of this check out path to the file in the samples.

If you would like to verify that the shrinking and resetting is working you can set the alpha of the canvas group to be 1. And then call the Shrink method (give it a duration of 1, and a CancellationToken.None for the cancellation token parameter) it will over one second shrink the bar from full size to 0. Then if you call ResetBar method you will see it instantly pop back up to full size. The aasiest way to do this is in Update and direct keybind polling.

Making the custom presenter

Now we'll create the custom dialogue presenter subclass. Since this involves substantial code, we'll approach it in sections.

  1. Create a new presenter subclass Assets -> Create -> Yarn Spinner -> Create -> Dialogue View Scriptand name it TimeoutOptionsView. This creates a stubbed out subclass with all the necessary methods for a custom view.

  2. Replace the imports with the following:

using System.Threading;
using System.Collections.Generic;
using UnityEngine;
using Yarn.Unity;
  1. 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.

  1. 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.

  1. 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.

  1. Replace OnDialogueStartedAsync with the following:

public override YarnTask OnDialogueStartedAsync()
{
    if (canvasGroup != null)
    {
        canvasGroup.alpha = 0;
        canvasGroup.interactable = false;
        canvasGroup.blocksRaycasts = false;
    }

    return YarnTask.CompletedTask;
}
  1. 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.

You might notice that we aren't awaiting any work in these methods. And we've even said in the method declaration we aren't asynchronous. As such we need to return a completed nothing task, or else Unity will complain at us for having asynchronous methods that don't do anything asynchronously.

  1. 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.

  1. 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.

Determining the Timeout Type

Now we'll implement the RunOptionsAsync method, starting with determining which timeout option to use and validating the option group.

There is quite a lot of code we are about to add to the RunOptionsAsync method. We are going to be adding it piece by piece, with each section to be added below the previous one.

  1. 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.

  1. 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.

  1. 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.

Handling Cancellation and Completion

Next, we'll set up the infrastructure to handle completion and cancellation. These mechanisms will be provided to the option items later.

  1. Add the following code to create our completion source:

// A completion source that represents the selected option.
YarnTaskCompletionSource<DialogueOption> selectedOptionCompletionSource = new YarnTaskCompletionSource<DialogueOption>();
  1. 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:

  1. 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.

Creating and Configuring the Option Items

Now we'll create and configure the option items to display to the user:

  1. 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.

  1. 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.

  1. 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.

Fading it all in

Now we'll display the UI to the player:

  1. 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.

  1. 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.

  1. 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.

Fading it all out

After a selection is made, we need to clean up and exit:

  1. 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.

  1. 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.

  1. 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.

Missing Methods

Now we need to implement the methods we've referenced but haven't defined yet:

  1. 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.

  1. 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.

Drawing the rest of the owl

Finally, let's hook everything up in Unity:

  1. Move back over to Unity and select the Options View in the hierarchy.

  2. Add the TimeoutOptionsView.cs script as a new component to the Option View gameobject.

  3. Delete the Options Presenter component.

  4. Select the Options View gameobject and in the Canvas Group field drag the Options View into this field.

  5. From Packages/Yarn Spinner/Prefabs folder drag the Option Item prefab into the Option View Prefab field.

  6. Drag the timer gameobject into the Timed Bar field.

  7. Pick a duration for the Auto Select, Fade Up, and Fade Down durations.

The Inspector for our presenter

Now we need to tell the dialogue runner about our custom presenter:

  1. Select the Dialogue System game object in the hierarchy.

  2. Remove the old (now missing) Options Presenter from the Dialogue Views field.

  3. Add the new timeout options view into it's place.

Inspector for the Dialogue Runner
  1. 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.

Samples
Logo

Basic Storylets and Saliency

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.

For more on this topic we have Storylets and Saliency Primer, which explores the concept in the abstract, and a sample and guide on Custom Saliency Strategies and Advanced Saliency.

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.

What we'll be covering

  • Writing node group storylets

  • Writing line group storylets

  • Calling salient content

  • Interacting with generated variable storage systems

The Sample

The Basic Saliency Sample

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

You can learn how to add the Yarn Spinner for Samples to your project over at .

The Time Advancer

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.

You can learn more about Variable Storage, if you're curious.

Generated variable storage section of the project inspector

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

The NPCs

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 Dialogue Interactible inspector for Alice

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?

Writing Storylets in Yarn Spinner

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 and Line Groups

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, Node Groups, and When Always

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 and Conditionals

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 and Multiple Whens

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

Nodes Groups and Titles

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:

  1. Each node in the group must share the same title

  2. Every node must have at least one when header to be recognized as part of a group

  3. If you don't have specific conditions, use when: always as a fallback

Learn more about Node Groups.

The Saliency Strategy

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:

  1. Removes any storylets with failing conditions

  2. Counts the conditions on each viable storylet to determine its "complexity"

  3. Selects the storylet with the highest complexity (most conditions)

  4. If multiple storylets tie for complexity, deprioritizes any that have been recently seen

  5. 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.

Conclusion

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.

Samples

Create a Phone Chat View

Learn how to make a Phone Chat view and explore our Phone Chat Sample.

Phone Chat

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.

While this tutorial's visuals are modelled on phone messaging user interfaces, the techniques used in it are also useful for any other situation where you want to keep an on-screen record of messages.

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.

You can learn how to add the Yarn Spinner for Samples to your project over at .

Creating the Project

We'll start by creating a new empty Unity project, and adding Yarn Spinner.

  1. Open Unity Hub.

  2. 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.

  3. Follow the steps in Installation for Unity to install Yarn Spinner in your project.

Next, we'll download the assets needed by this tutorial.

  1. Download the assets for this tutorial, and import them into your project.

We're ready to start creating our scene!

Setting Up the UI

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.

  1. Open the File menu, and choose New Scene. Select the Empty template.

  2. 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.

  1. Create a new camera by opening the GameObject menu and choosing Camera.

  2. 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.

  1. Open the GameObject menu, and choose UI -> Canvas.

  2. Select the new canvas in the Hierarchy, and find the Canvas Scaler component.

  3. Set its UI Scale Mode to "Scale with Screen Size".

  4. Set its Reference Resolution to 1920 by 1080.

Setting Up the Scroll View

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!

  1. Open the GameObject menu, and choose UI -> Scroll View.

  2. Select the newly created Scroll View in the Hierarchy.

  3. In the Inspector, find the Transform component.

  4. Set the Pos X and Pos Y values to 0,0.

  5. 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.

  1. Select the Scroll View object in the Hierarchy.

  2. In the Inspector, find the Scroll Rect component.

  3. Select the Horizontal Scrollbar field, and press Backspace to clear it. Do the same thing for the Vertical Scrollbar field.

  4. Turn off the Horizontal checkbox, so that it doesn't scroll horizontally.

  5. Delete the Scrollbar Horizontal and Scrollbar Vertical objects from the scene.

Next, we'll set up the scroll view to use our background image.

  1. In the Inspector, find the Image component.

  2. Change the Color property to fully opaque white.

  3. 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:

  1. Select the Viewport object in the Hierarchy.

  2. 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).

  1. Select the Scroll View -> Viewport -> Content object in the Hierarchy.

  2. Find the Transform component in the Inspector.

  3. Set the Anchors -> Min to X: 0, Y: 0.

  4. Set the Anchors -> Max to X: 1, Y: 0.

  5. 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.

  1. With the Content object still selected, click Add Component at the bottom of the Inspector.

  2. Search for "Vertical Layout Group", and add one to the object.

  3. Set the new Vertical Layout Group's Spacing to -10.

  4. Set its Child Alignment to Lower Center.

  5. Check the Control Child Size -> Width and Height boxes.

  6. 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.

  1. Click the Add Component button at the bottom of the Inspector.

  2. Search for "Content Size Fitter" and add one to the object.

  3. Set the Horizontal Fit of the new Content Size Fitter to Unconstrained.

  4. Set the Vertical Fit to Preferred Size.

Creating the Bubble

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.

A sample conversation in Apple iMessage, showing how the bubble graphic fits around the text of each message.
  1. Select the Content object in the Hierarchy.

  2. Open the GameObject menu, and choose Create Empty Child.

  3. Name the newly created object "Message Bubble".

We'll eventually make the bubble automatically size itself, but while we're setting it up, we'll set its size manually using these Layout Element components.

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.

  1. Select the Message Bubble object.

  2. Add a new Layout Element component to the object.

  3. 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:

  1. Create a new empty child game object of the Message Bubble.

  2. Name the new object "Message Content".

  3. Add a Layout Element component to it, and set its Min Width to 300.

  4. 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:

  1. With the Message Content object selected, go to the Transform component.

  2. Set the Anchors -> Min to X: 1, Y: 0.

  3. Set the Anchors -> Max to X: 1, Y: 1.

  4. Set the Pivot to X: 1, Y: 0.5.

  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:

  1. Add a new empty child object of Message Content.

  2. Name the new object "Background".

  3. In the Transform, set the Anchors -> Min to X: 0, Y:0.

  4. Set the Anchors -> Max to X: 1, Y:1.

  5. Set the Left, Top, Right and Bottom values all to 0.

  6. Add a new Image component.

  7. Set the Source Image of the new component to Bubble Green.

Your view should now look like this:

The green speech bubble background, in the bottom-right corner of the scrolling area.

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.

  1. Add a new empty child object of Message Content. Name it "Text".

  2. Set its Anchors -> Min to X:0, Y:0.

  3. Set its Anchors -> Max to X:1, Y:1.

  4. Set its Left, Top, Right, Bottom all to 0.

  5. Add a TextMeshPro - Text (UI) component to the object.

  6. Set the Font to Montserrat-Regular. (This is one of the assets that you downloaded when you began this tutorial.)

  7. Set the Text to something temporary, like "This is a test".

  8. Scroll down to Extra Settings, and click it to expand it.

  9. Set the Margins to:

    • Left: 55

    • Top: 30

    • Right: 55

    • Bottom: 35

The bubble should now look like this:

The bubble with the text 'This is a test' contained with it, and with margins that keep the text inside the bubble.

Making The Bubble Automatically Size Itself

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).

  1. Open the Assets menu, and choose Create -> MonoBehaviour Script.

  2. Name the new script "UseSizeOfText".

  3. Double-click the new script to open it in your text editor.

  4. 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.

  1. 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.

  1. 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.

  1. Select the Message Content object in the Hierarchy.

  2. Remove the Layout Element component from it.

  3. 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.

  1. Select the Message Bubble object in the Hierarchy.

  2. Remove the Layout Element component from it.

  3. Add a new Use Size Of Text component.

Your bubble should now look like this:

A laid-out bubble.

Adding the Typing Indicator

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.

The typing indicator

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.

  1. Create a new empty game object as a child of Message Content. Name it "Typing Indicator".

  2. In the Inspector for the Transform component, set its Anchor Min to X:0, Y:0.

  3. Set its Anchor Max to X:1, Y:1.

  4. Set its Top, Left, Right, and Bottom all to 0.

Let's now add the dots themselves!

  1. Add three new empty game objects as children of Typing Indicator. Name them Dot 1, Dot 2, Dot 3.

  2. Select all three of the Dot objects you just created.

  3. Add an Image component to all of them.

  4. Drag and drop the Dot sprite from the downloaded assets into the Source Image slot.

  5. Click on the Color value, and change the Alpha value to 128, to make it semi-transparent.

  6. Set the Pos Y for all of them to 0.

  7. 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.

  1. Select Dot 1, and set its Pos X to -25.

  2. Select Dot 2, and set its Pos X to 0.

  3. 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.

  1. Open the Assets menu, and choose Create -> Animation -> Animation Clip.

  2. Name the new animation clip "Typing".

  3. Select the new animation clip, and in the Inspector, turn on Loop Time.

  4. 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.

  1. 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.)

  2. 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.

  1. In the Animator window, Click Add Property.

  2. Choose Dot 1 -> Rect Transform -> Anchored Position.

  3. 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.

  1. Select the keyframe at frame 60 and press Ctrl-C.

  2. 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.

  1. Move the playhead to frame 15.

  2. Click the Record button at the top left of the Animation pane.

  3. In the Scene view, drag all three Dot objects up so that their Pos Y is 25.

  4. Click the Record button again to leave recording mode.

  5. 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.

  1. Select all of the keyframes for Dot 2 and shift them 30 frames to the right.

  2. Select all of the keyframes for Dot 3 and shift them 60 frames to the right.

  3. Click the Preview Play button again and watch as they bounce up in sequence.

Looking good!

Creating Bubble Prefabs

We're done with our basic bubble. Let's now create prefabs for it.

  1. 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.

  1. Delete the Message Bubble object from the scene.

  2. Select the Message Bubble prefab you just created.

  3. Open the Assets menu, and choose Create -> Prefab Variant.

  4. Name the new prefab Message Bubble A.

  5. Repeat the above two steps, creating a new prefab variant called Message Bubble B.

  6. Drag Message Bubble A into the Content object in the hierarchy, creating an instance of it in the scene.

  7. 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.

  1. Select the Message Content object in Bubble B.

  2. Change its Anchors Min to X: 0, Y:0.

  3. Change its Anchors Max to X: 0, Y:1.

  4. Change its Pivot to X: 0, Y:0.5.

  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.

  1. Select the Background object in Message Bubble B.

  2. 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.

  1. 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:

The two flavours of bubbles.

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.

  1. Select the Message Bubble B object

  2. 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).

  1. Delete the Message Bubble A and Message Bubble B objects from the scene.

Creating Option Buttons

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.

  1. Create a new empty object under Content, and call it Options Container.

  2. Add a Vertical Layout Component to it.

  3. Set its Padding Top to 8, and Padding Bottom to 16.

  4. Set its Spacing to 16.

  5. 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.

  1. Create a new empty object under Options Container, and call it Option Button.

  2. 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.

  1. Add a new empty object under Option Button, and call it Background.

  2. Set its Anchor Min to X: 0, Y: 0

  3. Set its Anchor Max to X: 1, Y: 1.

  4. Set its Top and Bottom to 0, and its Left and Right to 20.

  5. Add an Image component.

  6. 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.

  1. Add a new empty object under Option Button, and call it Text.

  2. Set its Anchor Min to X: 0, Y: 0

  3. Set its Anchor Max to X: 1, Y: 1

  4. Set its Top, Bottom, Left and Right all to 0.

  5. Add a TextMeshPro - Text (UI) component to the object.

  6. Set its Vertex Color to black.

  7. Set its Alignment to Centered Middle.

  8. Set its font to Montserrat-Regular.

  9. Click Extra Settings to expand it.

  10. 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.

  1. Set Text Wrapping Mode to No Wrap, and set Overflow to Ellipsis.

  2. 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.

  1. With the Option Button selected, Add a Button component.

  2. Set the Transition to Sprite Swap.

  3. Drag and drop the Background object into the Target Graphic field.

  4. Set Highlighted Sprite to Option-Selected.

  5. Set Pressed Sprite to Option-Pressed.

  6. Set Selected Sprite to Option-Selected.

Your button should now look like this:

The option button

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!

  1. Drag and drop the Option Button into the Project pane to create a prefab.

  2. Delete the original Option Button object from the scene.

Writing the Code for Bubbles and Buttons

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.

  1. Open the Assets menu, and choose Create -> MonoBehaviour Script.

  2. Name the new script "ChatDialoguePresenterBubble".

  3. 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.

  1. Select the Message Bubble prefab in the Project pane.

  2. Click Open at the top right of the Inspector to open the prefab for editing.

  3. Add a ChatDialoguePresenterBubble component to the Message Bubble object.

  4. Drag and drop the Typing Indicator into the Typing Indicator field.

  5. 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.

  1. Open the Assets menu, and choose Create -> MonoBehaviour Script.

  2. Name the new script "ChatDialoguePresenterOptionsButton".

  3. 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.

  1. Select the Option Button prefab in the Project pane.

  2. Click Open at the top right of the Inspector to open the prefab for editing.

  3. Add a Chat Dialogue Presenter Options Button component to the Option Button object.

  4. In the Button component, add a new entry to the On Click event.

  5. Drag and drop the Option Button object from the Hierarchy into the new event's object field.

  6. Change the method from 'No Function' to 'ChatDialoguePresenterOptionsButton -> OnClicked()'.

  7. Close the prefab and go back to the scene.

With this, our bubble and button prefabs are ready for use with the Dialogue Presenter!

Writing the Dialogue Presenter Code

We're now finally ready to create the Dialogue Presenter itself!

  1. Open the Create menu, and choose Yarn Spinner -> Dialogue Presenter Script.

  2. Name the new script "ChatDialoguePresenter".

  3. 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.

  1. 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.

  1. 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.

  1. 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!

Setting Up The Dialogue Runner

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.

  1. Create a new empty object called "Dialogue Runner".

  2. Add a Dialogue Runner component to the object.

  3. Add a new empty game object as a child, and call it "Phone Chat Presenter".

  4. Add a ChatDialoguePresenter component to Phone Chat Presenter.

  5. 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.

  6. 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.

  1. Drag the Option Button prefab into the Options Button Prefab field.

  2. Drag the Options Container object into the Options Container field.

We're ready to test! Let's create some dialogue and hook it up.

Creating Test Dialogue

We'll start by creating a Yarn Project, and a single Yarn Script containing some sample dialogue.

  1. Open the Assets menu, and choose Create -> Yarn Spinner -> Yarn Project.

  2. Name the new file "Phone Chat Project".

  3. Open the Assets menu, and choose Create -> Yarn Spinner -> Yarn Script.

  4. Name the new file "Phone Chat".

Next, let's write some dialogue.

  1. 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.

  1. Select the Dialogue Runner object in the Hierarchy.

  2. Drag and drop the Phone Chat Project that you just created into the Yarn Project field.

  3. 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.

  1. Add a new entry in the Dialogue Presenters list.

  2. Drag in the Phone Chat Presenter object into the new field.

We're finally ready to see it all in action!

  1. Click the Play button at the top of the Unity window. The conversation will play out!

Configuring Character Bubbles

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.

  1. Select the Phone Chat Presenter in the Hierarchy.

  2. In the Inspector, click the + button next to the Characters field to add an entry to the Characters dictionary.

  3. Set the key of the new entry to "A", and drag the Message Bubble A prefab into the Object slot.

  4. Repeat this process, creating a new entry "B" that uses the Message Bubble B prefab.

  5. Play the game again.

Lines from "B" will use a different-looking bubble!

Adding support for options

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.

  1. 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
===
  1. 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.)

  1. Select the Dialogue Runner in the Hierarchy.

  2. In the Dialogue Runner component's Inspector, turn on Run Selected Option As Line.

We're all set to see it in action.

  1. Play the game. After the lines appear, option buttons will run!

Wrapping Up

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!

Logo
Samples

Welcome

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.

While this isn't intended to be a sample you might be interested in checking out how the projector works as a custom dialogue presenter.

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

The Welcome Sample

You can learn how to add the Yarn Spinner for Samples to your project over at Samples.