Putting It Together

Commands let your Yarn Scripts tell your game to do something. Functions let them ask your game for a value. The example below uses both, set up once in Blueprints and once in C++.

In the example, Rosa hears the door, tells the player how long the lamp has been out, and lights it. Then she gives the player a key if they don’t already have one.

The Actor

BP_Lighthouse (Self)
DefaultSceneRoot Scene Component
LampLight Point Light
YarnDialogueRunner Yarn Dialogue Runner
YarnLinePresenter Yarn Line Presenter
YarnOptionsPresenter Yarn Options Presenter
An Actor made up for this example. The Dialogue Runner has Auto Start turned off, and its Dialogue Presenters list the Line Presenter and Options Presenter. The lamp’s light starts with its Intensity at 0.

The Actor’s Blueprint, or its C++ parent class, adds the Commands and Functions when the game starts, and then starts the dialogue. Auto Start is off so that the dialogue can’t start before they’ve been added.

The Yarn Script

title: CommandsFunctionsDemo
---
<<declare $lamp_days = 0>>
<<play_sound door_creak>>
Rosa: Oh! You gave me a fright.
<<set $lamp_days to days_since_lamp_lit()>>
Rosa: The lamp's been out for {$lamp_days} days.
Rosa: Let me get it going again.
<<light_lamp>>
Rosa: There. Much better.
<<if not has_item("brass key")>>
    Rosa: You'll need this to get up the stairs.
    <<give_item "brass key">>
<<endif>>
<<if has_item("brass key")>>
    Rosa: Now you've got the key. Up you go!
<<endif>>
===
Line 3 A variable to hold the Function’s value, so it can go in a line.
Line 4 A Command. The game plays a creaking sound. It doesn’t make the dialogue wait, so Rosa’s first line appears while the sound is still playing.
Lines 6–7 A Function. days_since_lamp_lit() returns 12, which is stored in $lamp_days and shown in the line.
Line 9 A blocking Command. The lamp fades on before Rosa’s next line.
Line 11 A Function in a condition. has_item("brass key") returns false, so Rosa hands over the key.
Line 13 A Command with an argument in quotes, because it has a space in it.
Lines 15–17 The same Function again. This time it returns true, because the player now has the key.
The node the Actor runs.

Setting it up

BP_Lighthouse has a String array variable called Inventory, and these functions:

FunctionInputsOutputWhat it does
play_soundSound Name, a StringNoneIf Sound Name is door_creak, calls Play Sound 2D with the door sound.
light_lampNoneReturnValue, a BooleanCalls the Fade Lamp On custom event, and returns true so the dialogue waits.
give_itemItem Name, a StringNoneAdds Item Name to Inventory.
DaysSinceLampLitParameters, an array of Yarn ValueReturnValue, a Yarn ValueReturns Make Yarn Value From Number with 12.
HasItemParameters, an array of Yarn ValueReturnValue, a Yarn ValueGets index 0 of Parameters, turns it into text with Yarn Value To String, checks whether Inventory contains it, and returns the result through Make Yarn Value From Bool.
The functions in BP_Lighthouse. The first three are Commands, found by name on a Command Handler Object. The last two are Functions.

The Fade Lamp On custom event plays a Timeline that’s 0.8 seconds long, and sets LampLight’s intensity from 0 up to its full brightness as it plays. From the Timeline’s Finished pin, it calls Complete Blocking Command on the Dialogue Runner, so the dialogue carries on once the lamp is lit.

PLACEHOLDER — Screenshot needed
The Fade Lamp On custom event in BP_Lighthouse. It plays a 0.8 second Timeline, whose Update pin calls Set Intensity on LampLight using a Lerp from 0 to 5000, and whose Finished pin calls Complete Blocking Command on the Yarn Dialogue Runner.

The Event Graph connects everything when the game starts:

  1. After Event BeginPlay, get the Yarn Dialogue Runner’s Command Handler Objects, and Add Self. The Dialogue Runner now finds play_sound, light_lamp and give_item on this Actor.
  2. Add a Register Function Handler node with Function Name days_since_lamp_lit and Parameter Count 0. Connect a Create Event node set to DaysSinceLampLit to its Handler pin.
  3. Add another with Function Name has_item and Parameter Count 1, and a Create Event node set to HasItem.
  4. Call Start Dialogue on the Dialogue Runner, with Node Name CommandsFunctionsDemo.
PLACEHOLDER — Screenshot needed
The Event Graph of BP_Lighthouse. Event BeginPlay connects to an Add node that adds Self to the Yarn Dialogue Runner’s Command Handler Objects, then to two Register Function Handler nodes for days_since_lamp_lit and has_item, each with a Create Event node on its Handler pin, then to Start Dialogue with CommandsFunctionsDemo.

The same setup in C++ is an Actor class that BP_Lighthouse can use as its parent class. It adds the Commands and Functions by name in BeginPlay.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "YarnSpinnerCore.h"
#include "LighthouseDirector.generated.h"

class UYarnDialogueRunner;
class UPointLightComponent;
class USoundBase;

UCLASS()
class ALighthouseDirector : public AActor
{
	GENERATED_BODY()

protected:
	virtual void BeginPlay() override;

private:
	UPROPERTY(EditAnywhere, Category = "Lighthouse")
	TObjectPtr<USoundBase> DoorCreak;

	UPROPERTY()
	TObjectPtr<UYarnDialogueRunner> DialogueRunner;

	UPROPERTY()
	TObjectPtr<UPointLightComponent> LampLight;

	TArray<FString> Inventory;
	FTimerHandle LampTimer;
	float LampElapsed = 0.0f;

	void PlaySound(const TArray<FString>& Parameters);
	void LightLamp();
	void TickLamp();
	void GiveItem(const TArray<FString>& Parameters);
	FYarnValue DaysSinceLampLit() const;
	FYarnValue HasItem(const TArray<FYarnValue>& Parameters) const;
};
Lines 21–22 The door sound, set in the Details panel.
Lines 24–28 The Dialogue Runner and the lamp’s light, found when the game starts.
Line 30 The inventory that <<give_item>> adds to and has_item() checks.
Lines 31–32 The timer that fades the lamp on, and how far through it is.
LighthouseDirector.h.
#include "LighthouseDirector.h"
#include "Components/PointLightComponent.h"
#include "Kismet/GameplayStatics.h"
#include "TimerManager.h"
#include "YarnDialogueRunner.h"

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

	DialogueRunner = FindComponentByClass<UYarnDialogueRunner>();
	LampLight = FindComponentByClass<UPointLightComponent>();

	DialogueRunner->AddCommandHandler(TEXT("play_sound"), [this](const TArray<FString>& Parameters) { PlaySound(Parameters); });
	DialogueRunner->AddBlockingCommandHandler(TEXT("light_lamp"), [this](const TArray<FString>& Parameters) { LightLamp(); });
	DialogueRunner->AddCommandHandler(TEXT("give_item"), [this](const TArray<FString>& Parameters) { GiveItem(Parameters); });
	DialogueRunner->AddFunction(TEXT("days_since_lamp_lit"), [this](const TArray<FYarnValue>& Parameters) { return DaysSinceLampLit(); }, 0);
	DialogueRunner->AddFunction(TEXT("has_item"), [this](const TArray<FYarnValue>& Parameters) { return HasItem(Parameters); }, 1);

	DialogueRunner->StartDialogue(TEXT("CommandsFunctionsDemo"));
}

void ALighthouseDirector::PlaySound(const TArray<FString>& Parameters)
{
	if (Parameters.Num() > 0 && Parameters[0] == TEXT("door_creak"))
	{
		UGameplayStatics::PlaySound2D(this, DoorCreak);
	}
}

void ALighthouseDirector::LightLamp()
{
	LampElapsed = 0.0f;
	GetWorldTimerManager().SetTimer(LampTimer, this, &ALighthouseDirector::TickLamp, 0.02f, true);
}

void ALighthouseDirector::TickLamp()
{
	LampElapsed += 0.02f;
	const float Alpha = FMath::Clamp(LampElapsed / 0.8f, 0.0f, 1.0f);
	LampLight->SetIntensity(FMath::Lerp(0.0f, 5000.0f, Alpha));
	if (Alpha >= 1.0f)
	{
		GetWorldTimerManager().ClearTimer(LampTimer);
		DialogueRunner->CompleteBlockingCommand();
	}
}

void ALighthouseDirector::GiveItem(const TArray<FString>& Parameters)
{
	if (Parameters.Num() > 0)
	{
		Inventory.Add(Parameters[0]);
	}
}

FYarnValue ALighthouseDirector::DaysSinceLampLit() const
{
	return FYarnValue(12.0f);
}

FYarnValue ALighthouseDirector::HasItem(const TArray<FYarnValue>& Parameters) const
{
	return FYarnValue(Inventory.Contains(Parameters[0].ConvertToString()));
}
Lines 11–12 Find the Dialogue Runner and the lamp’s light. Both are components on this Actor.
Lines 14–16 Three Commands. light_lamp is added with AddBlockingCommandHandler, so the dialogue waits for it.
Lines 17–18 Two Functions. The last argument is how many arguments each one takes.
Line 20 Start the dialogue once everything has been added.
Lines 23–29 <<play_sound>>. It starts the sound and returns, so the dialogue doesn’t wait for it.
Lines 31–35 <<light_lamp>>. It starts a timer that runs TickLamp every 0.02 seconds.
Lines 37–47 Raise the light’s intensity over 0.8 seconds. When it’s at full brightness, stop the timer and call CompleteBlockingCommand(), so the dialogue carries on.
Lines 49–55 <<give_item>>. It returns straight away, so the dialogue doesn’t wait for it.
Lines 57–60 days_since_lamp_lit() returns a number.
Lines 62–65 has_item() checks the same inventory that <<give_item>> adds to, so its answer changes once the key has been given.
LighthouseDirector.cpp.

What it shows

Every Command changes something you can see or hear. <<light_lamp>> makes the next line wait, because it’s a blocking Command and the dialogue stays put until Complete Blocking Command is called. <<play_sound>> and <<give_item>> don’t block, and the dialogue carries on without waiting.

Each Function returns a value the Yarn Script uses: days_since_lamp_lit() puts a number in a line, through $lamp_days, and has_item() picks which lines Rosa says. The second has_item() check gives a different answer from the first because <<give_item>> has changed the inventory it reads.

Next step Variables and Storage Reading and changing Yarn variables from Blueprints and C++, and saving and loading them.