A Different Kind of Presenter

The Presenter on Building Your Own Presenter works a lot like the built-in ones. It shows one line at a time, and options as buttons. A Presenter only has to show a line and return when it’s done, and show options and return the one the player picked. How it does those two things is up to you.

The Presenter on this page uses the same two methods, but keeps the whole conversation on screen as a transcript, like a text adventure:

  • Nothing is cleared. Each line is added under the last, so the player can read back through the whole conversation.
  • Options are text, not buttons. They’re listed as numbered lines in the transcript, and stay there after the player has picked.
  • The player picks with the number keys. Pressing 1 picks the first option, 2 the second, and so on.
  • The choice is written into the transcript. After the player picks, the option they chose appears after a >.
PLACEHOLDER — Video needed
The Transcript Presenter running a short conversation: Rosa’s lines building up one under another, then two numbered options, then the chosen option after a greater-than sign, followed by the rest of the conversation. The player presses 1 to pick the first option.

The scene

The whole Presenter writes to one TextMesh Pro text, inside a Scroll View, so the player can scroll back through the transcript. The Presenter scrolls to the bottom each time it adds to the transcript.

Dialogue Runner Dialogue Runner
Transcript Presenter Transcript Presenter
Canvas CanvasCanvas ScalerGraphic Raycaster
Scroll View ImageScroll Rect
Viewport ImageMask
Content Vertical Layout GroupContent Size Fitter
Transcript TextMeshPro - Text (UI)
The Transcript Presenter, and the Scroll View it writes to. The Scroll View’s scrollbars are left out.
  1. Choose GameObjectUIScroll View to add a Scroll View.
  2. Select its Content. Add a Vertical Layout Group, and turn on Control Child Size for Width and Height. Add a Content Size Fitter, and set Vertical Fit to Preferred Size. The Content now grows as the transcript gets longer.
  3. Right-click Content, and choose UIText - TextMeshPro. Name it Transcript, and clear its text.
  4. Add an empty GameObject called Transcript Presenter, and add the script below to it.
  5. Set its Dialogue Runner, its Transcript to the text, and its Scroll Rect to the Scroll View.
  6. Add Transcript Presenter to the Dialogue Runner’s Dialogue Presenters list. It handles both lines and options, so it can be the only Presenter.

The script

Like the simple Presenter, this one reads the keyboard with Unity’s Input System package.

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

public class TranscriptPresenter : DialoguePresenterBase
{
    [SerializeField] DialogueRunner dialogueRunner;
    [SerializeField] TMP_Text transcript;
    [SerializeField] ScrollRect scrollRect;

    readonly List<DialogueOption> choices = new();
    YarnTaskCompletionSource<DialogueOption> chosen;

    void Update()
    {
        var keyboard = Keyboard.current;
        if (keyboard == null)
        {
            return;
        }

        if (choices.Count == 0)
        {
            if (keyboard.spaceKey.wasPressedThisFrame)
            {
                dialogueRunner.RequestNextLine();
            }
            return;
        }

        for (var i = 0; i < choices.Count && i < 9; i++)
        {
            if (keyboard[Key.Digit1 + i].wasPressedThisFrame)
            {
                chosen.TrySetResult(choices[i]);
            }
        }
    }

    public override YarnTask OnDialogueStartedAsync()
    {
        return YarnTask.CompletedTask;
    }

    public override YarnTask OnDialogueCompleteAsync()
    {
        return YarnTask.CompletedTask;
    }

    public override async YarnTask RunLineAsync(
        LocalizedLine line,
        LineCancellationToken token)
    {
        Append(line.Text.Text + "\n\n");
        await YarnTask.WaitUntilCanceled(token.NextContentToken);
    }

    public override async YarnTask<DialogueOption> RunOptionsAsync(
        DialogueOption[] dialogueOptions,
        LineCancellationToken token)
    {
        chosen = new YarnTaskCompletionSource<DialogueOption>();
        choices.Clear();
        foreach (var option in dialogueOptions)
        {
            if (!option.IsAvailable)
            {
                continue;
            }
            choices.Add(option);
            var text = option.Line.TextWithoutCharacterName.Text;
            Append(choices.Count + ". " + text + "\n");
        }

        DialogueOption result;
        using (token.NextContentToken.Register(() => chosen.TrySetResult(null)))
        {
            result = await chosen.Task;
        }

        choices.Clear();
        if (result != null)
        {
            var picked = result.Line.TextWithoutCharacterName.Text;
            Append("\n> " + picked + "\n\n");
        }
        return result;
    }

    void Append(string text)
    {
        transcript.text += text;
        Canvas.ForceUpdateCanvases();
        scrollRect.verticalNormalizedPosition = 0;
    }
}
Lines 10–12 The Dialogue Runner, the text the transcript is written to, and the Scroll View it’s in.
Line 14 While options are showing, the options in the order they’re numbered. The first number shown is choices[0], and so on. It’s empty the rest of the time.
Line 15 Holds the player’s choice once there is one, the same as in the simple Presenter.
Lines 25–32 When no options are showing, Space moves on to the next line, the same as the simple Presenter.
Lines 34–40 When options are showing, the keys 1 to 9 pick an option. Key.Digit1 to Key.Digit9 are next to each other, so adding i to Key.Digit1 gives the key for option i.
Lines 43–51 There’s nothing to set up or tidy away when dialogue starts and ends, but every Presenter needs these two methods.
Line 57 Add the line to the end of the transcript. Nothing is ever removed, so earlier lines stay on screen.
Line 58 Wait for the player to move on, then return. There’s nothing to tidy up afterwards.
Lines 65–76 Start a new choice. Then number the options the player can choose, starting at 1, and record which option each number is. Unavailable options are left out, so the numbers on screen have no gaps.
Lines 78–82 Wait for a choice, and give up if the Dialogue Runner stops the options, the same as the simple Presenter.
Lines 84–90 Stop listening for number keys, write the chosen option into the transcript after a >, and return it.
Lines 93–98 Add text to the end of the transcript, then scroll to the bottom. ForceUpdateCanvases makes Unity lay out the new text first, so the Scroll View knows how tall it is.
A Presenter that writes dialogue to a transcript, and takes options from the number keys.

What stayed the same

Everything this Presenter does differently happens inside RunLineAsync and RunOptionsAsync. What it gives back to the Dialogue Runner is the same as the simple Presenter:

  • RunLineAsync returns once the player has moved on. It makes no difference to the Dialogue Runner that the line is still on screen.
  • RunOptionsAsync returns the chosen DialogueOption from the array it was given, or null. The number the player pressed is only the option’s position on screen, and choices turns it back into the option.
  • Both give up when the token says the dialogue is moving on.
Next step Connecting to Your Game Commands, Functions and variables: how your Yarn Scripts talk to your game.