Pardon our dust while we move our docs here. The Godot (GDScript) docs are on this site, and the Yarn language, Unity, Godot (C#) and Unreal docs are still at docs.yarnspinner.dev.

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 functions, 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 Transcript Presenter running the Lighthouse conversation. The player pressed 1 to pick the first option.

The scene

The whole Presenter uses one RichTextLabel. Turn on its Scroll Following property, so it scrolls down as the transcript grows.

GameNode2D
YarnDialogueRunner
TranscriptPresenter
CanvasLayer
TranscriptRichTextLabel
The Transcript Presenter, and the RichTextLabel it writes to.
  1. Add a Node called TranscriptPresenter, and attach the script below to it.
  2. Set its Transcript property to the RichTextLabel.
  3. Add TranscriptPresenter to the Dialogue Runner’s Presenters list. It handles both lines and options, so it can be the only Presenter.

The script

class_name TranscriptPresenter
extends YarnDialoguePresenter

signal _option_chosen(index: int)

@export var transcript: RichTextLabel

var _choices: Array[int] = []


func _unhandled_input(event: InputEvent) -> void:
	if _choices.is_empty():
		if event.is_action_pressed("ui_accept"):
			dialogue_runner.request_next_line()
		return

	if event is InputEventKey and event.pressed:
		var number: int = event.keycode - KEY_1
		if number >= 0 and number < _choices.size():
			_option_chosen.emit(_choices[number])


func run_line(
		line: YarnLine,
		token: YarnCancellationToken = null
) -> void:
	transcript.append_text(line.text + "\n\n")
	await token.wait_for_next_content()


func run_options(
		options: Array[YarnOption],
		token: YarnCancellationToken = null
) -> int:
	_choices.clear()
	for i in options.size():
		if not options[i].is_available:
			continue
		_choices.append(i)
		var number := str(_choices.size()) + ". "
		transcript.append_text(number + options[i].text + "\n")

	var give_up := _option_chosen.emit.bind(-1)
	token.next_content_requested.connect(give_up)
	var index: int = await _option_chosen
	token.next_content_requested.disconnect(give_up)

	_choices.clear()
	if index >= 0:
		var picked := options[index].text
		transcript.append_text("\n> " + picked + "\n\n")
	return index
Line 6 The RichTextLabel the transcript is written to.
Line 8 While options are showing, the index of each numbered option. The first number shown is _choices[0], and so on. It’s empty the rest of the time.
Lines 12–15 When no options are showing, ui_accept moves on to the next line, the same as the simple Presenter.
Lines 17–20 When options are showing, the keys 1 to 9 pick an option. KEY_1 to KEY_9 are next to each other, so subtracting KEY_1 turns the key into 0 to 8.
Line 27 Add the line to the end of the transcript. Nothing is ever removed, so earlier lines stay on screen.
Line 28 Wait for the player to move on, then return. There’s nothing to tidy up afterwards.
Lines 35–41 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 43–46 Wait for a choice, and give up if the Dialogue Runner stops the options, the same as the simple Presenter.
Lines 48–52 Stop listening for number keys, write the chosen option into the transcript after a >, and return its index.
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 run_line and run_options. What it gives back to the Dialogue Runner is the same as the simple Presenter:

  • run_line returns once the player has moved on. It makes no difference to the Dialogue Runner that the line is still on screen.
  • run_options returns the index of the chosen option in the array it was given, or -1. The number the player pressed is only the option’s position on screen, and _choices turns it back into that index.
  • Both give up when the token says the dialogue is moving on.

As long as your Presenter keeps to those rules, it can show dialogue any way you like: on a phone screen, in a log, over characters’ heads, or with no text at all.

Next step Connecting to Your Game Commands, Functions and variables: how your Yarn Scripts talk to your game.