Putting It Together

Commands let your Yarn Scripts tell your game to do something. Functions let them ask your game for a value. The scene below uses both.

In the scene, Rosa hears the door, walks over to the lamp and lights it, then gives the player a key if they don’t already have one. The inventory in the top right shows the key once she’s handed it over.

PLACEHOLDER — Video needed
The scene running. The door creaks, Rosa walks over to the lamp and it lights up, and “brass key” appears in the inventory in the top right, while Rosa’s lines appear in the Line Presenter.

The scene

Main Camera CameraAudio Listener
Directional Light Light
Game GameAudio Source
Rosa Mesh FilterMesh RendererCharacter
Lamp Light
Inventory Canvas CanvasCanvas ScalerGraphic Raycaster
Inventory TextMeshPro - Text (UI)
Dialogue System Dialogue RunnerIn Memory Variable Storage
Canvas CanvasCanvas ScalerGraphic Raycaster
Line Presenter Line PresenterCanvas Group
Options Presenter Options PresenterCanvas Group
Line Advancer Line Advancer
The scene. Game has the script that adds the Commands and Functions, and Rosa has the script with the move Command.

The Dialogue System comes from GameObjectYarn SpinnerDialogue System. Its Dialogue Runner has Start Automatically turned on, with Start Node set to CommandsFunctionsDemo. The Lamp’s Light starts with its Intensity at 0.

The Yarn Script

title: CommandsFunctionsDemo
---
<<play_sound door_creak>>
Rosa: Oh! You gave me a fright.
Rosa: The lamp's been out for {days_since_lamp_lit()} days.
<<move Rosa 3 0>>
Rosa: Let me get it going again.
<<light_lamp>>
Rosa: There. Much better.
<<if not has_item("brass key")>>
    Rosa: You'll need this to get up the stairs.
    <<give_item "brass key">>
<<endif>>
<<if has_item("brass key")>>
    Rosa: Now you've got the key. Up you go!
<<endif>>
===
Line 3 A Command. The game plays a creaking sound. It doesn’t make the dialogue wait, so Rosa’s first line appears while the sound is still playing.
Line 5 A Function inside a line. days_since_lamp_lit() returns 12, which is shown in the line.
Line 6 A Command on the GameObject called Rosa. It returns a YarnTask, so the dialogue waits until she has finished walking.
Line 8 A Command that also waits. The lamp fades on before Rosa’s next line.
Line 10 A Function in a condition. has_item("brass key") returns false, so Rosa hands over the key.
Line 12 A Command with an argument in quotes, because it has a space in it. The key appears in the inventory.
Lines 14–16 The same Function again. This time it returns true, because the player now has the key.
The node that runs in the scene.

The game code

The script on the Game GameObject adds the Commands and Functions when the scene starts:

using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using Yarn.Unity;

public class Game : MonoBehaviour
{
    [SerializeField] DialogueRunner dialogueRunner;
    [SerializeField] AudioSource audioSource;
    [SerializeField] AudioClip doorCreak;
    [SerializeField] Light lampLight;
    [SerializeField] TextMeshProUGUI inventoryLabel;

    readonly List<string> inventory = new List<string>();

    void Awake()
    {
        dialogueRunner.AddCommandHandler<string>("play_sound", PlaySound);
        dialogueRunner.AddCommandHandler("light_lamp", LightLamp);
        dialogueRunner.AddCommandHandler<string>("give_item", GiveItem);
        dialogueRunner.AddFunction<int>("days_since_lamp_lit", DaysSinceLampLit);
        dialogueRunner.AddFunction<string, bool>("has_item", HasItem);
        UpdateInventory();
    }

    void PlaySound(string soundName)
    {
        if (soundName == "door_creak")
        {
            audioSource.PlayOneShot(doorCreak);
        }
    }

    IEnumerator LightLamp()
    {
        var elapsed = 0f;
        while (elapsed < 0.8f)
        {
            elapsed += Time.deltaTime;
            lampLight.intensity = Mathf.Lerp(0f, 2f, elapsed / 0.8f);
            yield return null;
        }
    }

    void GiveItem(string itemName)
    {
        inventory.Add(itemName);
        UpdateInventory();
    }

    int DaysSinceLampLit()
    {
        return 12;
    }

    bool HasItem(string itemName)
    {
        return inventory.Contains(itemName);
    }

    void UpdateInventory()
    {
        inventoryLabel.text = "Inventory\n" + string.Join("\n", inventory);
    }
}
Lines 9–13 The Dialogue Runner and other parts of the scene, set in the Inspector.
Line 15 The inventory that <<give_item>> adds to and has_item() checks.
Lines 19–21 Three Commands, each connected to a method on this script.
Lines 22–23 Two Functions. The last type in angle brackets is what each one returns.
Lines 27–33 <<play_sound>>. It starts the sound and returns, so the dialogue doesn’t wait for it.
Lines 35–44 <<light_lamp>>. It’s a coroutine, so the dialogue waits while the lamp fades on over 0.8 seconds.
Lines 46–50 <<give_item>>. It returns nothing, so the dialogue doesn’t wait for it. UpdateInventory redraws the inventory.
Lines 52–55 days_since_lamp_lit() returns a number.
Lines 57–60 has_item() checks the same inventory that <<give_item>> adds to, so its answer changes once the key has been given.
Game.cs. It adds three Commands and two Functions to the Dialogue Runner, and keeps the inventory.

<<move>> isn’t added in Awake. It’s a method marked with [YarnCommand] in the script on Rosa, which Yarn Spinner adds to the Dialogue Runner by itself:

using UnityEngine;
using Yarn.Unity;

public class Character : MonoBehaviour
{
    [YarnCommand("move")]
    public async YarnTask Move(float x, float y)
    {
        var start = transform.position;
        var end = new Vector3(x, y, start.z);
        var elapsed = 0f;
        while (elapsed < 1f)
        {
            elapsed += Time.deltaTime;
            transform.position = Vector3.Lerp(start, end, elapsed);
            await YarnTask.Yield();
        }
    }
}
Line 4 The script goes on the character’s GameObject, which is called Rosa in the scene.
Lines 6–7 It isn’t static, so it’s a Command on a GameObject. In Yarn, the first argument, Rosa, says which GameObject to call it on. x and y come from the other two arguments, converted to float.
Lines 12–17 Move the GameObject to the new position over one second. The method is async and returns a YarnTask, so the dialogue waits until the move is done.
Character.cs, on Rosa.

What it shows

Every Command in the scene changes something you can see or hear. Two of them, <<move>> and <<light_lamp>>, make the next line wait: one is an async method and the other is a coroutine. <<play_sound>> and <<give_item>> return nothing, and the dialogue carries on without waiting.

Each Function returns a value the Yarn Script uses: days_since_lamp_lit() puts a number in a line, and has_item() picks which lines Rosa says. The second has_item() check gives a different answer from the first because <<give_item>> has changed the inventory it reads.

Next step Variables and Storage Reading and changing Yarn variables from C#, and saving and loading them.