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 scene
The whole Presenter uses one RichTextLabel. Turn on its Scroll Following property, so it scrolls down as the transcript grows.
- Add a
NodecalledTranscriptPresenter, and attach the script below to it. - Set its Transcript property to the
RichTextLabel. - Add
TranscriptPresenterto 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 indexRichTextLabel the transcript is written to._choices[0], and so on. It’s empty the rest of the time.ui_accept moves on to the next line, the same as the simple Presenter.KEY_1 to KEY_9 are next to each other, so subtracting KEY_1 turns the key into 0 to 8.>, and return its index.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_linereturns once the player has moved on. It makes no difference to the Dialogue Runner that the line is still on screen.run_optionsreturns 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_choicesturns 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.