Line and Asset Providers

The Dialogue Runner has two helpers for getting lines ready:

  • The Line Provider gets each line’s text, in the right language, ready to show.
  • The Asset Provider finds assets that go with each line, like voice clips, and loads them before they’re needed.

If the Dialogue Runner doesn’t have them when play begins, it makes a Yarn Builtin Line Provider and a Yarn Project Asset Provider. They’re in its Line Provider and Asset Provider properties. Both are objects the Dialogue Runner makes while the game runs, so you set them up from Blueprints or C++, not in the Details panel.

The Line Provider

When a line runs, the Dialogue Runner gives the Line Provider the line’s ID, and gets back a line that’s ready for the Presenters:

flowchart TB
  id["Line ID: line:a1b2c3d"]
  presenters["Presenters"]
  id -->|"1. find the text"| text["Rosa: You've got {0} coins."]
  text -->|"2. fill in values"| subs["Rosa: You've got 15 coins."]
  subs -->|"3. read markup"| presenters
What the Line Provider does with each line.
  1. It finds the text in the current language. See Localising Your Game.
  2. It fills in values, like {$coins} or {days_since_lamp_lit()}, which the dialogue has already worked out.
  3. It reads the markup, like [b] or [wave], so the Presenters know what to show. [select], [plural] and [ordinal] are replaced with their words here. See Markup.

The Dialogue Runner does this for every line, and for every option. The result is a Yarn Localized Line, FYarnLocalizedLine in C++, which the Dialogue Runner gives to each of its Dialogue Presenters:

FieldWhat it holds
Raw LineThe line as it came from the dialogue: its Line ID, and its Substitutions, the values for {0}, {1} and so on.
TextThe line’s finished text, with values filled in and markup removed.
Character NameThe name before the colon, like Rosa, or empty if there isn’t one.
Text Without Character NameText without the character’s name and the colon.
MetadataThe line’s tags, without the #, like line:a1b2c3d and picture:lamp.
Text MarkupThe markup in the line, and where each attribute starts and ends.
Shadow Source Line IDFor a shadow line, the ID of the line it copies. Otherwise empty.
The fields of a Yarn Localized Line.

In a translation, each value is written as a number in braces, like {0}. Translators should keep these, and can move them to wherever they belong in their language:

id,text,...
line:c3d4e5f,Rosa: Du hast {0} Münzen.,...
A line with $coins in it, exported and translated into German. The other columns are cut short here.

To get the Line Provider from a Blueprint, get the Yarn Dialogue Runner’s Line Provider. From C++, it’s the Dialogue Runner’s LineProvider property.

The Line Providers that come with the plugin

Line ProviderWhere it gets text
Yarn Builtin Line ProviderThe translations in the Yarn Project, from your .yarnproject file’s strings files. The Dialogue Runner makes one of these if it doesn’t have a Line Provider.
Yarn String Table Line ProviderOne Unreal String Table.
Yarn Culture Aware Line ProviderOne Unreal String Table for each language.
Yarn Line ProviderYour Yarn Scripts, in your base language. It doesn’t translate. The others are built on it.
The Line Providers in YarnLocalization.h and YarnDialoguePresenter.h.

The Yarn Builtin Line Provider’s settings are in When a line isn’t translated. The two String Table Line Providers are in Using Unreal’s String Tables.

Shadow lines

Sometimes the same line appears in more than one place, like a greeting that can come up in several nodes. A shadow line is a copy of another line that shares its translations and voice clip, so it only needs translating and recording once.

Give the copy a #shadow: tag with the ID of the line it copies, instead of a #line: tag:

title: Morning
---
Rosa: Hello again. #line:a1b2c3d #audio:/Game/Dialogue/Voice/HelloAgain
===

title: Evening
---
Rosa: Hello again. #shadow:a1b2c3d
===
Line 3 The original line.
Line 8 The shadow. The Line Provider looks up the original line’s translation for it. It doesn’t have an #audio: tag, so the Asset Provider uses the original line’s, and the Voice Over Presenter plays the original line’s clip.
A line, and a shadow of it in another node.

A shadow line must have exactly the same text as the line it copies. If it doesn’t, the Yarn Project won’t compile, and you’ll see “Shadow lines must have the same text as their source”. All the Line Providers that come with the plugin support shadow lines. See Shadow Lines.

The Asset Provider

The Asset Provider finds an asset for a line. The Yarn Project Asset Provider, which the Dialogue Runner makes if it doesn’t have one, finds it from a tag on the line. The tag is #audio: followed by the asset’s path in the Content Browser, without Content:

Rosa: Hello again. #line:a1b2c3d #audio:/Game/Dialogue/Voice/HelloAgain
A line whose voice clip is the asset HelloAgain, in the Content Browser folder Dialogue/Voice.

Whoever asks for the asset says what kind it should be. The Voice Over Presenter asks for a Sound Base, so it plays the line’s clip if the tag names a sound, and nothing if it names something else. See Voice-Over.

For a shadow line without an #audio: tag of its own, the Asset Provider uses the tag on the line it copies.

The Yarn Project Asset Provider has one setting. Metadata Prefix is the tag it reads, without the colon. It’s audio to start with. A line can name one asset this way.

Getting a line’s asset in your own Presenter

To get a line’s asset in a Presenter of your own, ask the Dialogue Runner’s Asset Provider for it:

  1. Call Get Dialogue Runner, and get its Asset Provider.
  2. Call Get Asset For Line on it, with the line in Line and the kind of asset in Asset Class, like Texture 2D.
  3. Cast the result to that class. It’s empty if the line doesn’t have an asset of that kind.
PLACEHOLDER — Screenshot needed
A Blueprint graph inside a Presenter’s Run Line event. Get Dialogue Runner goes into an Asset Provider getter, which goes into a Get Asset For Line message node, with Line connected to Run Line’s Line pin and Asset Class set to Texture 2D. Its Return Value goes into Cast To Texture2D.
UYarnDialogueRunner* Runner = GetDialogueRunner();
UObject* AssetProvider = Runner ? Runner->AssetProvider.GetObject() : nullptr;
UTexture2D* Texture = AssetProvider
	? Cast<UTexture2D>(IYarnAssetProvider::Execute_GetAssetForLine(AssetProvider, Line, UTexture2D::StaticClass()))
	: nullptr;
Getting a line’s image from the Asset Provider, in a Presenter’s RunLine_Implementation().

Loading assets before they’re needed

Each time a node starts, the Dialogue Runner calls the Line Provider’s Prepare For Lines, and each Presenter’s On Prepare For Lines, with the IDs of the lines in the node.

The Voice Over Presenter then calls the Asset Provider’s Prepare Assets For Lines. The Yarn Project Asset Provider starts loading the assets named in those lines’ tags in the background, so they’ve usually loaded by the time each line runs. To do the same in your own Presenter, override On Prepare For Lines and call Prepare Assets For Lines with the line IDs and your kind of asset.

Choosing assets with your own tag

Naming an asset’s path in each line means the paths have to stay the same, and a line can only name one asset with #audio:. If you’d rather pick a line’s asset yourself, give the line a tag of your own, like #picture:lamp, and read it in your Presenter. A line’s tags are in the Yarn Localized Line’s Metadata, without the #. Get Metadata Value finds the tag that starts with a key, like picture, and gives you the rest of it, like lamp.

In a Blueprint Presenter, override Run Line:

  1. Break the Line, and connect its Metadata to a Get Metadata Value node, with Key set to picture.
  2. If it returns true, look up its Out Value in a map of names to textures, with a Find node.
  3. Call Set Brush From Texture on your Image, with the texture. If the line has no picture: tag, or the name isn’t in the map, hide the Image instead.
  4. Call On Line Presentation Complete.
PLACEHOLDER — Screenshot needed
A Blueprint Presenter’s Run Line event. Break YarnLocalizedLine’s Metadata goes into Get Metadata Value with Key set to picture. A Branch on its Return Value leads to a Find node on a Pictures map of strings to Texture 2D, then Set Brush From Texture on a Picture Image, and finally On Line Presentation Complete. The false branches hide the Picture Image before On Line Presentation Complete.

In C++:

#pragma once

#include "CoreMinimal.h"
#include "YarnDialoguePresenter.h"
#include "YarnLineMetadata.h"
#include "Components/Image.h"
#include "Engine/Texture2D.h"
#include "PicturePresenter.generated.h"

UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class UPicturePresenter : public UYarnDialoguePresenter
{
	GENERATED_BODY()

public:
	UPROPERTY(BlueprintReadWrite, Category = "Dialogue")
	TObjectPtr<UImage> Picture;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dialogue")
	TMap<FString, TObjectPtr<UTexture2D>> Pictures;

	virtual void RunLine_Implementation(const FYarnLocalizedLine& Line, bool bCanHurry) override
	{
		UTexture2D* Texture = PictureFor(Line);

		if (Picture)
		{
			Picture->SetBrushFromTexture(Texture);
			Picture->SetVisibility(Texture ? ESlateVisibility::Visible : ESlateVisibility::Hidden);
		}

		OnLinePresentationComplete();
	}

	virtual void OnDialogueComplete_Implementation() override
	{
		if (Picture)
		{
			Picture->SetVisibility(ESlateVisibility::Hidden);
		}
	}

protected:
	virtual bool CanHandleOptions() const override
	{
		return false;
	}

private:
	UTexture2D* PictureFor(const FYarnLocalizedLine& Line) const
	{
		FString PictureName;
		if (!UYarnLineMetadataLibrary::GetMetadataValue(Line.Metadata, TEXT("picture"), PictureName))
		{
			return nullptr;
		}

		const TObjectPtr<UTexture2D>* Found = Pictures.Find(PictureName);
		return Found ? Found->Get() : nullptr;
	}
};
Lines 16–17 An Image in your dialogue widget. Set it from a Blueprint after you create the widget.
Lines 19–20 The pictures the lines can name, set in the Details panel. The key is the name in the tag, like lamp.
Lines 24–30 Show the line’s picture, or hide the Image if it doesn’t have one.
Line 32 Finish straight away. The picture stays up until the next line, and the Line Presenter waits for the player.
Lines 44–47 This Presenter doesn’t show options, so the Dialogue Runner doesn’t count on it to pick one.
Lines 52–56 Find the line’s picture: tag. #picture:lamp is in Metadata as picture:lamp, and GetMetadataValue() gives back lamp.
Lines 58–59 Find the texture with that name in the map, or no picture if it isn’t there.
PicturePresenter.h, a Presenter that shows the picture named in a line’s #picture: tag.
title: Pictures
---
Rosa: This is the old lamp from the tower. #picture:lamp
Rosa: Here's the key to get in. #picture:key
Rosa: Good luck up there.
===
Lines 3–4 Each #picture: tag names a texture in the Presenter’s Pictures map.
Line 5 No #picture: tag, so the picture is hidden.
A Yarn Script for PicturePresenter.

Add the Presenter to the same Actor as your Yarn Dialogue Runner, and add it to the Yarn Dialogue Runner’s Dialogue Presenters list, next to your Line Presenter:

BP_Rosa (Self)
DefaultSceneRoot Scene Component
YarnDialogueRunner Yarn Dialogue Runner
YarnLinePresenter Yarn Line Presenter
PicturePresenter Picture Presenter
An example Actor with a Line Presenter and PicturePresenter.

The lines keep their own line tags, so you can still tag them with ysc tag, and the line’s other tags stay in its Metadata.

Writing your own Asset Provider

To find assets another way, write an Asset Provider of your own. It’s any object that implements the Yarn Asset Provider interface, which has two functions:

FunctionWhat it does
Get Asset For LineGets the asset of the class it’s given for a line, or nothing if the line doesn’t have one.
Prepare Assets For LinesStarts loading the assets for lines that are about to run. It can do nothing.
The functions in the Yarn Asset Provider interface, in YarnAssetProvider.h.

To write one in Blueprints, make a Blueprint Class with Object as its parent class. In Class Settings, add Yarn Asset Provider to Implemented Interfaces. Then implement Get Asset For Line, from the Interfaces section of the My Blueprint panel. Make one with Construct Object from Class, and set it as the Yarn Dialogue Runner’s Asset Provider.

The Yarn Project Asset Provider finds the same clip in every language. This Asset Provider finds a different clip for each language instead, from a folder for each language, named after its code, like /Game/Dialogue/Voice/de. In each folder, each clip is named after its line’s ID, without line::

#pragma once

#include "CoreMinimal.h"
#include "YarnAssetProvider.h"
#include "Engine/AssetManager.h"
#include "LanguageFolderAssetProvider.generated.h"

UCLASS(BlueprintType)
class ULanguageFolderAssetProvider : public UObject, public IYarnAssetProvider
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dialogue")
	FString RootPath = TEXT("/Game/Dialogue/Voice");

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dialogue")
	FString LocaleCode = TEXT("en");

	virtual UObject* GetAssetForLine_Implementation(const FYarnLocalizedLine& Line, UClass* AssetClass) override
	{
		const FString LineID = Line.ShadowSourceLineID.IsEmpty() ? Line.RawLine.LineID : Line.ShadowSourceLineID;
		return StaticLoadObject(AssetClass, nullptr, *MakePath(LineID), nullptr, LOAD_NoWarn | LOAD_Quiet);
	}

	virtual void PrepareAssetsForLines_Implementation(const TArray<FString>& LineIDs, UClass* AssetClass) override
	{
		TArray<FSoftObjectPath> Paths;
		for (const FString& LineID : LineIDs)
		{
			Paths.Add(FSoftObjectPath(MakePath(LineID)));
		}
		PreloadHandle = UAssetManager::GetStreamableManager().RequestAsyncLoad(Paths);
	}

private:
	TSharedPtr<FStreamableHandle> PreloadHandle;

	FString MakePath(const FString& LineID) const
	{
		FString AssetName = LineID;
		AssetName.RemoveFromStart(TEXT("line:"));
		return FString::Printf(TEXT("%s/%s/%s.%s"), *RootPath, *LocaleCode, *AssetName, *AssetName);
	}
};
Lines 14–15 The folder with a folder for each language in it.
Lines 17–18 The language to use. Set it when the game’s language changes.
Lines 20–24 For a shadow line, use the ID of the line it copies, so they share a clip. StaticLoadObject() gives back nothing if there’s no asset at the path, or if it isn’t of the class that was asked for.
Lines 26–34 Start loading the clips for the lines in the node in the background. Keeping the handle keeps them loaded until the next node starts.
Lines 39–44 The clip for the line tagged #line:a1b2c3d, in German, is /Game/Dialogue/Voice/de/a1b2c3d.
LanguageFolderAssetProvider.h, an Asset Provider with a folder for each language.

Give it to the Dialogue Runner before the dialogue starts, the same way as a Line Provider:

ULanguageFolderAssetProvider* AssetProvider = NewObject<ULanguageFolderAssetProvider>(this);
AssetProvider->LocaleCode = TEXT("de");
DialogueRunner->AssetProvider = AssetProvider;
Giving the Dialogue Runner a LanguageFolderAssetProvider, in an Actor’s BeginPlay().

The Voice Over Presenter uses the Dialogue Runner’s Asset Provider, whichever one it is, so it plays the clips this one finds.

Writing your own Line Provider

To get text from somewhere else, write a Line Provider of your own. Make a class that inherits from Yarn Line Provider, or from one of the Line Providers that come with the plugin, and override Get Localized Line. Give it to the Dialogue Runner the same way as a String Table Line Provider. See Giving the Dialogue Runner a String Table Line Provider.

In Blueprints, make a Blueprint Class with Yarn Builtin Line Provider as its parent class, and override Get Localized Line. Right-click the function’s entry node and choose Add Call to Parent Function, so you start from the line the Yarn Builtin Line Provider makes. Change what you need, and return it.

PLACEHOLDER — Screenshot needed
A Blueprint Class based on Yarn Builtin Line Provider, with its Get Localized Line function open. The entry node’s Line pin goes into the Parent: Get Localized Line node, whose result is changed with Set Members in YarnLocalizedLine and connected to the Return Node.

In C++, a Line Provider has two helpers for the work every Line Provider does. ResolveShadowLineID() gives you the ID of the line a shadow line copies, or the line’s own ID if it isn’t a shadow line. FinalizeLocalizedLine() fills in the line’s values, reads its markup, and fills in its Metadata, from the text you found.

The String Table Line Provider reads each String Table entry’s source text. This Line Provider reads the entry’s text in Unreal’s current language instead, so it uses the translations made with Unreal’s own localisation tools:

#pragma once

#include "CoreMinimal.h"
#include "YarnLocalization.h"
#include "Internationalization/StringTable.h"
#include "Internationalization/StringTableCore.h"
#include "Internationalization/Internationalization.h"
#include "Internationalization/Culture.h"
#include "LocalisedStringTableLineProvider.generated.h"

UCLASS(BlueprintType, Blueprintable)
class ULocalisedStringTableLineProvider : public UYarnLineProvider
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dialogue")
	TObjectPtr<UStringTable> StringTable;

	virtual FYarnLocalizedLine GetLocalizedLine_Implementation(const FYarnLine& Line) override
	{
		const FString LineID = ResolveShadowLineID(Line.LineID);
		const FString Key = UYarnLocalizationLibrary::LineIDToStringTableKey(LineID);

		if (!StringTable || !StringTable->GetStringTable()->FindEntry(Key).IsValid())
		{
			return Super::GetLocalizedLine_Implementation(Line);
		}

		const FText Text = FText::FromStringTable(StringTable->GetStringTableId(), Key);
		const FString Language = FInternationalization::Get().GetCurrentLanguage()->GetName();

		FYarnLocalizedLine LocalizedLine;
		LocalizedLine.RawLine = Line;
		FinalizeLocalizedLine(LocalizedLine, Line, LineID, Text.ToString(), Language);
		return LocalizedLine;
	}
};
Lines 17–18 The String Table with your lines, keyed by line ID without line:. Import Yarn Project To String Table fills one in.
Lines 22–23 Find the ID to look up, following a shadow line to the line it copies, and turn it into a String Table key.
Lines 25–28 If there’s no String Table, or no entry for the line, use the text from your Yarn Script.
Line 30 The entry’s text, in Unreal’s current language.
Line 31 Unreal’s current language, so [plural] and [ordinal] follow its rules.
Lines 33–36 Fill in the line’s values, read its markup and tags, and return it.
LocalisedStringTableLineProvider.h, a Line Provider that uses the translations of a String Table.

To get assets ready before they’re needed, override Prepare For Lines as well. The Dialogue Runner calls it with the IDs of the lines that might run, each time a node starts. By default, it does nothing.

Presenters and other code register their own markup processors with the Line Provider’s Register Marker Processor. Those are kept by the Yarn Line Provider, and FinalizeLocalizedLine() uses them, so a Line Provider of your own doesn’t need to do anything to support them.