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
>.
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.
- Choose to add a Scroll View.
- 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.
- Right-click Content, and choose . Name it
Transcript, and clear its text. - Add an empty GameObject called
Transcript Presenter, and add the script below to it. - Set its Dialogue Runner, its Transcript to the text, and its Scroll Rect to the Scroll View.
- Add
Transcript Presenterto 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;
}
}choices[0], and so on. It’s empty the rest of the time.Key.Digit1 to Key.Digit9 are next to each other, so adding i to Key.Digit1 gives the key for option i.>, and return it.ForceUpdateCanvases makes Unity lay out the new text first, so the Scroll View knows how tall it is.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:
RunLineAsyncreturns once the player has moved on. It makes no difference to the Dialogue Runner that the line is still on screen.RunOptionsAsyncreturns the chosenDialogueOptionfrom the array it was given, ornull. The number the player pressed is only the option’s position on screen, andchoicesturns it back into the option.- Both give up when the token says the dialogue is moving on.