Markup

Markup is tags in square brackets that you put in your lines, like [b]this[/b]. Yarn Spinner for Unreal Engine uses them to change the words, pause the typewriter, or run your own code as the line types out.

Markup comes in two kinds:

  • Markup that changes the text. It’s applied before the line is shown. Tags that pick words, like [plural], work this way, and so does any marker processor you write.
  • Markup that does something while the line types out. It marks a point in the line, and runs when the typewriter reaches it. [pause/] works this way.
PLACEHOLDER — Video needed
The examples from this page running with a Line Presenter: Rosa’s lines appearing one after another with words chosen by select, plural and ordinal, a pause, a door knock sound from an sfx tag, and the dialogue box shaking at a shake_box tag.

What the Line Presenter shows

The Line Presenter shows each line as plain text, in a Text Block. Before the line reaches any Presenter, Yarn Spinner removes its markup tags and keeps a list of the attributes, with where each one starts and how long it is. Tags like [b] or [wave] are removed, and their text is shown without formatting.

The markup is still part of the line, so your own code can use it. Every Presenter gets it in the line’s Text Markup, as a list of Attributes. Each attribute has a Name, a Position and a Length in the line’s text, and any Properties you gave the tag. See Running code partway through a line.

To show square brackets in a line, put a backslash before each one: \[like this\]. To show a whole run of text without treating any of it as markup, wrap it in [nomarkup]:

Rosa: The label says [nomarkup][DO NOT TOUCH][/nomarkup].
Square brackets shown as written.

Changing the words

These tags are replaced with different words, depending on a value. They’re self-closing: they end in /] and have no closing tag. They work with any Presenter, with no setup.

[select] picks one of several options by name:

<<declare $mood = "happy">>
Rosa: I'm [select value={$mood} happy="glad" sad="sorry"/] you came.
Choosing a word based on $mood.

[plural] picks the right word for a number. % is replaced with the number:

<<declare $coins = 3>>
Rosa: You have [plural value={$coins} one="% coin" other="% coins"/].
Choosing coin or coins based on $coins.

[ordinal] does the same for positions, like 1st, 2nd and 3rd:

<<declare $visits = 2>>
Rosa: It's your [ordinal value={$visits} one="%st" two="%nd" few="%rd" other="%th"/] visit.
Adding the right ending to a number.

[plural] and [ordinal] follow the rules of the line’s language, using Unreal’s own plural rules for that culture, so you only need to give the forms that language uses. English uses one and other for plurals, and one, two, few and other for ordinals. If a line has no form for the number, Yarn Spinner uses other.

Pausing partway through a line

[pause/] stops the typewriter for a moment. Every Line Presenter handles it, with no setup:

Rosa: The lamp went out, and then [pause/]I heard footsteps.
Rosa: Somebody was on the stairs. [pause=0.5/]Somebody heavy.
Two pauses: one second, then half a second.

On its own, [pause/] waits for 1 second. Give it a number to wait that many seconds, like [pause=0.5/]. To give the time in milliseconds instead, use ms, like [pause ms=500/]. If the player hurries the line during a pause, the pause ends.

In Unreal, the number in [pause=500/] is seconds, so it waits for 500 seconds. Use [pause ms=500/] for half a second.

A self-closing tag removes the space that comes after it. Put the space before the tag instead: then [pause/]I heard, not then[pause/] I heard.

Pauses only happen while the typewriter is running. With the Line Presenter’s Typewriter Mode set to Instant, the whole line appears at once and [pause/] does nothing. See Typewriter and Effects for more on how text appears.

The pause comes from a Yarn Pause Event Processor in the Line Presenter’s Action Markup Handlers list. Every Line Presenter starts with one. Its settings:

SettingDefaultWhat it does
Default Pause Duration1.0Seconds to wait for a [pause/] with no number.
Pause Marker NamepauseThe tag name it looks for.
The Yarn Pause Event Processor’s settings.

If you remove it from the list, [pause/] stops working.

Running code partway through a line

Markup can trigger your own code when the typewriter reaches a certain point in a line, like changing a character’s expression, playing a sound, or shaking the screen. To do this, add an action markup handler to the Line Presenter’s Action Markup Handlers list.

Select the Line Presenter’s component, and find Action Markup Handlers in the Markup section of the Details panel. Click + to add an entry, then pick the kind of handler from the entry’s drop-down. Each handler’s own settings appear under its entry.

The Action Markup Handlers list with two entries: a Yarn Pause Event Processor with Default Pause Duration 1.0 and Pause Marker Name pause, and a Yarn Sound Effect Handler with Sound Marker Name sfx and one Sound Map entry, door_knock.
A Line Presenter with a pause handler and a sound effect handler.

Yarn Spinner comes with three handlers:

HandlerWhat it does
Yarn Pause Event ProcessorPauses the typewriter at [pause/]. See Pausing partway through a line.
Yarn Sound Effect HandlerPlays a sound at [sfx name="door_knock"/]. See Playing sounds.
Yarn Markup Event HandlerFires an event when the typewriter reaches any tag in its Attributes To Watch list. Its events can be bound from C++.
The action markup handlers that come with Yarn Spinner.

Playing sounds

The Yarn Sound Effect Handler plays a sound when the typewriter reaches an [sfx/] tag. Give the tag the sound’s name:

Rosa: Did you hear that? [sfx name="door_knock"/]Someone's at the door.
A sound partway through a line.

Add the handler to the Line Presenter’s Action Markup Handlers list, and fill in its settings:

SettingDefaultWhat it does
Sound Marker NamesfxThe tag name it looks for.
Sound MapEmptyEach sound’s name, and the Sound asset to play for it.
Volume Multiplier1.0The volume of every sound it plays.
Pitch Multiplier1.0The pitch of every sound it plays.
The Yarn Sound Effect Handler’s settings.

The sounds play in 2D. The handler starts loading the sounds a line needs as soon as the line arrives, so they’re ready by the time the typewriter reaches them.

Writing your own handler

This handler shakes the dialogue box wherever a line has [shake_box/]:

To write a handler without C++, make a Blueprint whose parent class is Yarn Blueprint Action Markup Handler:

  1. In the Content Browser, click Add, choose Blueprint Class, open All Classes, and pick Yarn Blueprint Action Markup Handler. Name it BP_ShakeBoxHandler.
  2. Add a variable called Positions, an array of Integer.
  3. Override On Prepare For Line. Clear Positions, then loop over Parse Result’s Attributes. For each attribute whose Name is shake_box, add its Position to Positions.
  4. Override On Character Will Appear. If Positions contains Character Index, remove it, call a custom event that runs the Yarn Shake node on the Line Presenter’s Line Container, and return the shake’s duration. Otherwise, return 0.
  5. Compile, then pick BP_ShakeBoxHandler in an entry of the Line Presenter’s Action Markup Handlers list.
PLACEHOLDER — Screenshot needed
A Blueprint graph for BP_ShakeBoxHandler’s On Character Will Appear function: Positions Contains Character Index into a Branch; on True, Remove Item from Positions, call the Start Shake custom event, and a Return Node with Return Value 0.4; on False, a Return Node with Return Value 0.

Yarn Shake runs over several frames, so it can only go in the Event Graph, not inside a function like On Character Will Appear. That’s why step 4 calls a custom event to start it. To get the Line Container, cast Presenter to Yarn Line Presenter.

These Blueprint functions help when reading markup:

NodeWhat it does
Get Attributes At PositionEvery attribute that covers a character.
Get Self Closing Attributes At PositionEvery self-closing tag, like [sfx/], at a character.
Is At Range StartWhether a character is where a named tag, like [wave], starts.
Is At Range EndWhether a character is where a named tag ends.
Get Float Property, Get Int Property, Get Bool PropertyA property from an attribute, like volume from [sfx volume=2/], or a default value if it isn’t there.
Blueprint functions for working with markup.
#pragma once

#include "CoreMinimal.h"
#include "YarnActionMarkupHandler.h"
#include "ShakeBoxHandler.generated.h"

UCLASS()
class UShakeBoxHandler : public UYarnActionMarkupHandler
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere, Category = "Shake")
	float Strength = 12.0f;

	UPROPERTY(EditAnywhere, Category = "Shake")
	float Duration = 0.4f;

	virtual void OnPrepareForLine(UYarnDialoguePresenter* Presenter, const FYarnMarkupParseResult& ParseResult) override;

	virtual float OnCharacterWillAppear(UYarnDialoguePresenter* Presenter, int32 CharacterIndex, const FYarnMarkupParseResult& ParseResult, const FYarnLineCancellationToken& CancellationToken) override;

private:
	TArray<int32> Positions;
};
ShakeBoxHandler.h
#include "ShakeBoxHandler.h"

#include "YarnEffects.h"
#include "YarnLinePresenter.h"

void UShakeBoxHandler::OnPrepareForLine(UYarnDialoguePresenter* Presenter, const FYarnMarkupParseResult& ParseResult)
{
	Positions.Empty();
	for (const FYarnMarkupAttribute& Attribute : ParseResult.FindAttributesByName(TEXT("shake_box")))
	{
		Positions.Add(Attribute.Position);
	}
}

float UShakeBoxHandler::OnCharacterWillAppear(UYarnDialoguePresenter* Presenter, int32 CharacterIndex, const FYarnMarkupParseResult& ParseResult, const FYarnLineCancellationToken& CancellationToken)
{
	UYarnLinePresenter* LinePresenter = Cast<UYarnLinePresenter>(Presenter);
	if (!LinePresenter || !Positions.Contains(CharacterIndex))
	{
		return 0.0f;
	}

	Positions.Remove(CharacterIndex);

	UYarnShakeEffect* Shake = UYarnShakeEffect::YarnShake(LinePresenter->LineContainer, Strength, Duration, 20.0f, CancellationToken);
	Shake->RegisterWithGameInstance(LinePresenter);
	Shake->Activate();
	return Duration;
}
Lines 6–13 OnPrepareForLine is called when a line arrives, before any of it is shown. Look through the line’s markup, and remember the position of each [shake_box/].
Line 15 OnCharacterWillAppear is called before each character appears. It returns how many seconds the typewriter waits before showing the character.
Lines 17–21 If there’s no [shake_box/] here, return 0, so the typewriter carries on without waiting.
Line 23 After a wait, the typewriter asks about the same character again. Forget the position once it’s used, so the box only shakes once.
Lines 25–27 Shake the Line Presenter’s Line Container widget, using the shake effect from Typewriter and Effects. Passing CancellationToken stops the shake early if the player hurries the line.
Line 28 Make the typewriter wait for as long as the shake lasts.
ShakeBoxHandler.cpp, an event handler that shakes the dialogue box.

Build your project, then select the Line Presenter’s component, add an entry to Action Markup Handlers, and pick Shake Box Handler. Its Strength and Duration appear under the entry.

An action markup handler can override any of these functions. Write only the ones you need:

FunctionWhen it’s called
OnPrepareForLine(Presenter, ParseResult)A line has arrived, before any of it is shown.
OnLineDisplayBegin(Presenter, ParseResult)Before the first character appears.
OnCharacterWillAppear(Presenter, CharacterIndex, ParseResult, CancellationToken)Before each character appears. Return a number of seconds to make the typewriter wait.
OnLineDisplayComplete(Presenter)The whole line is showing.
OnLineWillDismiss(Presenter)The line is about to leave the screen.
The functions an action markup handler can override.

Presenter is the Line Presenter the handler is on. ParseResult is the line’s text and markup. CharacterIndex is the position in ParseResult.Text, the same as each attribute’s Position. OnCharacterWillAppear isn’t called when Typewriter Mode is Instant.

If the player hurries the line while the typewriter is waiting, the wait ends and the rest of the line appears. For anything of your own that takes a while, check CancellationToken.IsHurryUpRequested() and finish early.

Writing your own text-changing markup

A marker processor gets the text inside a tag before the line reaches any Presenter, and can change it however you like. Its output replaces the tag, and the tag is removed from the line’s attributes.

Because the Line Presenter shows plain text, a marker processor is most useful with a Presenter of your own that shows the line in a Rich Text Block. A Rich Text Block formats text with tags like <Whisper>text</>, where Whisper is the name of a row in its Text Style Set data table. See Building Your Own Presenter.

This marker processor turns [whisper]text[/whisper] into <Whisper>text</>:

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "YarnMarkup.h"
#include "WhisperMarkupProcessor.generated.h"

UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class UWhisperMarkupProcessor : public UActorComponent, public IYarnMarkupProcessor
{
	GENERATED_BODY()

public:
	virtual void BeginPlay() override;

	virtual FYarnMarkupReplacementResult ProcessMarkup_Implementation(
		const FYarnMarkupAttribute& Attribute,
		FString& Text,
		TArray<FYarnMarkupAttribute>& ChildAttributes,
		const FString& LocaleCode) override;
};
WhisperMarkupProcessor.h
#include "WhisperMarkupProcessor.h"

#include "YarnDialogueRunner.h"
#include "YarnLocalization.h"

void UWhisperMarkupProcessor::BeginPlay()
{
	Super::BeginPlay();

	UYarnDialogueRunner* Runner = GetOwner()->FindComponentByClass<UYarnDialogueRunner>();
	if (!Runner->LineProvider)
	{
		Runner->LineProvider = NewObject<UYarnBuiltinLineProvider>(Runner);
	}
	Runner->LineProvider->RegisterMarkerProcessor(TEXT("whisper"), this);
}

FYarnMarkupReplacementResult UWhisperMarkupProcessor::ProcessMarkup_Implementation(
	const FYarnMarkupAttribute& Attribute,
	FString& Text,
	TArray<FYarnMarkupAttribute>& ChildAttributes,
	const FString& LocaleCode)
{
	Text = TEXT("<Whisper>") + Text + TEXT("</>");
	return FYarnMarkupReplacementResult();
}
Line 10 Find the Dialogue Runner on the same Actor.
Lines 11–14 The Dialogue Runner makes its Line Provider when the game starts. If this component starts first, make the same Line Provider the Dialogue Runner would, so there’s one to register with.
Line 15 Register the processor with the Line Provider, for the whisper tag.
Lines 18–22 ProcessMarkup is called for each [whisper] in a line, before the line reaches any Presenter. Text holds the text inside the tag.
Line 24 Change the text inside the tag by changing Text.
Line 25 Return a result. Any messages you add to its Diagnostics are logged as warnings in the Output Log.
WhisperMarkupProcessor.cpp, a marker processor for a whisper tag.

To use it, add the Whisper Markup Processor component to the Actor that has your Dialogue Runner. Then give your Rich Text Block a Text Style Set with a row called Whisper, styled the way you want whispers to look.

Rosa: [whisper]Don't tell the others.[/whisper]
The whisper tag in a line.
PLACEHOLDER — Screenshot needed
A dialogue box using a Rich Text Block, showing: Rosa: Don’t tell the others, with the words after Rosa’s name in grey italics.

Marker processors work inside the Dialogue Runner’s Line Provider, so the changed text shows up everywhere the line’s text is used, including options.

Register your processors before dialogue starts. If the Dialogue Runner’s Auto Start is on, it can start dialogue before your processor’s component has registered. In that case, turn Auto Start off, and call Start Dialogue from your Actor’s Event BeginPlay.

To handle more than one tag with the same processor, call RegisterMarkerProcessor once for each tag name. Attribute is the tag itself: its name is in Attribute.Name, and any values you gave it, such as [whisper volume=2], are in Attribute.Properties. To read one as text, use Attribute.GetProperty(TEXT("volume")).

A Blueprint can be a marker processor too. Add the Yarn Markup Processor interface in the Blueprint’s Class Settings, and implement Process Markup. Register it with the Register Marker Processor node, on the Dialogue Runner’s Line Provider. Deregister Marker Processor removes it.

Markup palettes

Yarn Spinner also has a ready-made marker processor, the Yarn Palette Markup Processor component, which applies a Yarn Rich Text Palette. A palette is a list of tag names, each with the text to put before and after the tag’s text. Its Custom Markers take any start and end text, so you can use them for Rich Text Block tags:

UYarnRichTextPalette* Palette = NewObject<UYarnRichTextPalette>(this);

FYarnRichTextMarker Happy;
Happy.MarkerName = TEXT("happy");
Happy.StartTag = TEXT("<Happy>");
Happy.EndTag = TEXT("</>");
Palette->CustomMarkers.Add(Happy);

UYarnPaletteMarkupProcessor* PaletteProcessor = FindComponentByClass<UYarnPaletteMarkupProcessor>();
PaletteProcessor->Palette = Palette;

UYarnDialogueRunner* Runner = FindComponentByClass<UYarnDialogueRunner>();
Runner->LineProvider->RegisterMarkerProcessor(TEXT("happy"), PaletteProcessor);
A palette with a happy tag, set up in an Actor’s BeginPlay, after Super::BeginPlay().

Register the processor once for each tag in the palette. A palette’s Basic Styles produce tags like <b> and <color=#FFD700FF>, which a Rich Text Block doesn’t read, so use Custom Markers for Rich Text Block styles.

Next step Voice-Over Playing recorded audio for each line.