Node Internals
This sample reads a compiled Yarn node from C#. Before a node runs, it finds every Command in the node, and works out which assets those Commands need, so they can be loaded ahead of time.
Nothing is loaded for real. The Commands log messages to Unity’s Console window, and the first time a Command sees an asset, it waits a second to stand in for a slow load. When you run the sample, Capsley explains what’s going on, then asks whether to run the next node with the “preload” or without it. With the preload, the Commands finish instantly. Without it, each one pauses for a second.
Running it
In the Project window, open the Node Internals sample’s folder, open the Internals scene, and press Play. Keep the Console window open, because that’s where the results appear.
- Move with W, A, S and D, or the arrow keys.
- Walk up to Capsley and press Space to talk.
- Press Space, or click the continue button, to continue.
You can talk to Capsley again after the conversation ends. The explanation at the start only appears the first time, because it’s in a <<once>> block.
How it works
Capsley is the Explanation Character GameObject. Its Dialogue Interactable runs the Start node when you talk to it.
The Command Asset Preloader component, on Command Demo Preloader, adds five Commands:
| Command | What it does |
|---|---|
<<preload_command_assets>> | Finds the assets the commands node needs, and marks them as loaded. |
<<clear_preload>> | Forgets everything that’s been marked as loaded. |
<<set_background>> | Stands in for showing a background. |
<<set_music>> | Stands in for playing music. |
<<character_avatar>> | Stands in for showing a character’s picture. |
<<preload_command_assets>> needs the component’s settings, so the component registers it with the Dialogue Runner when the scene starts, by calling AddCommandHandler(). The other four are static methods marked with the [YarnCommand] attribute, so Yarn Spinner registers them for you. See Commands.
The component’s Dialogue Reference, set in the Inspector, names the node it looks through: the commands node in the Internals Yarn Project.
The Yarn Script
At the end of the Start node, the player picks whether to preload, and then the dialogue jumps to the commands node:
-> launch with the "preload"
<<preload_command_assets>>
-> launch without the "preload"
<<clear_preload>>
<<jump commands>>
===
title: commands
---
Capsley: this node has a lot of commands in it that are intended to simulate a bunch of commands that need to load assets and resources
<<set_background castle>>
<<set_music "cool beats to slay dragons to">>
<<character_avatar Glenn>>
<<character_avatar Liz>>
<<character_avatar EvilDave>>
<<character_avatar RegularDave>>
Capsley: aaaaaaand donecommands node now, and mark its assets as loaded.Finding the Commands
<<preload_command_assets>> gets the compiled node from the Yarn Project, goes through its instructions one at a time, and keeps the ones that run a Command:
private void PreloadAssets()
{
if (dialogueReference == null)
{
return;
}
if (dialogueReference.project == null)
{
return;
}
if (dialogueReference.nodeName == null)
{
return;
}
if (dialogueReference.project.Program.Nodes == null)
{
return;
}
if (!dialogueReference.project.Program.Nodes.TryGetValue(dialogueReference.nodeName, out var node))
{
return;
}
foreach (var instruction in node.Instructions)
{
if (instruction.InstructionTypeCase != Instruction.InstructionTypeOneofCase.RunCommand)
{
continue;
}
var elements = DialogueRunner.SplitCommandText(instruction.RunCommand.CommandText).ToArray();
if (elements.Length != 2)
{
continue;
}
switch (elements[0])
{
case "set_background":
backgrounds.Add(elements[1]);
break;
case "set_music":
music.Add(elements[1]);
break;
case "character_avatar":
avatars.Add(elements[1]);
break;
}
}
}Program has every node in the project, by name. This gets the node called commands.set_music "cool beats to slay dragons to" becomes two parts, because the quotes keep the name together.This finds every Command in the node, wherever it is. A Command inside an <<if>> or an option is found even if the dialogue never reaches it.
The Commands that use the assets
<<set_background>>, <<set_music>> and <<character_avatar>> all work the same way, each with the set for its kind of asset:
[YarnCommand("set_background")]
public static async YarnTask SetBackground(string backgroundAsset)
{
if (backgrounds.Add(backgroundAsset))
{
Debug.LogWarning($"{backgroundAsset} is not already \"loaded\", pretending to do that now");
await YarnTask.Delay(1000);
}
else
{
Debug.Log($"{backgroundAsset} has already been \"loaded\"");
}
Debug.Log($"\"setting\" the background to be: {backgroundAsset}");
}Add() returns true if the asset wasn’t in the set yet. Log a warning, and wait a second to stand in for loading it.Because the method returns a YarnTask, the dialogue waits for it before going on. If you chose to launch without the preload, the six Commands take six seconds between them, and the Console shows a warning for each asset. With the preload, each one logs that it has already been “loaded”, and Capsley’s next line appears immediately.
Capsley’s last line has a [link] on “CommandAssetPreloader”. The Link Markup Handler on Markup Processors opens CommandAssetPreloader.cs when it’s clicked, in the Unity Editor.
Things to try
- Select
Command Demo Preloaderand change the node in Dialogue Reference toStart, then launch with the preload. None of the six assets are found, because their Commands are incommands, so every one of them pauses. - Add
<<character_avatar Glenn>>a second time in thecommandsnode. Without the preload, the second one doesn’t pause, because the first one has already “loaded” Glenn.