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
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>>
===days_since_lamp_lit() returns 12, which is stored in $lamp_days and shown in the line.has_item("brass key") returns false, so Rosa hands over the key.true, because the player now has the key.Setting it up
BP_Lighthouse has a String array variable called Inventory, and these functions:
| Function | Inputs | Output | What it does |
|---|---|---|---|
play_sound | Sound Name, a String | None | If Sound Name is door_creak, calls Play Sound 2D with the door sound. |
light_lamp | None | ReturnValue, a Boolean | Calls the Fade Lamp On custom event, and returns true so the dialogue waits. |
give_item | Item Name, a String | None | Adds Item Name to Inventory. |
DaysSinceLampLit | Parameters, an array of Yarn Value | ReturnValue, a Yarn Value | Returns Make Yarn Value From Number with 12. |
HasItem | Parameters, an array of Yarn Value | ReturnValue, a Yarn Value | Gets 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 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.
The Event Graph connects everything when the game starts:
- After Event BeginPlay, get the Yarn Dialogue Runner’s Command Handler Objects, and Add Self. The Dialogue Runner now finds
play_sound,light_lampandgive_itemon this Actor. - Add a Register Function Handler node with Function Name
days_since_lamp_litand Parameter Count 0. Connect a Create Event node set toDaysSinceLampLitto its Handler pin. - Add another with Function Name
has_itemand Parameter Count 1, and a Create Event node set toHasItem. - Call Start Dialogue on the Dialogue Runner, with Node Name
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;
};<<give_item>> adds to and has_item() checks.#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()));
}light_lamp is added with AddBlockingCommandHandler, so the dialogue waits for it.<<play_sound>>. It starts the sound and returns, so the dialogue doesn’t wait for it.<<light_lamp>>. It starts a timer that runs TickLamp every 0.02 seconds.CompleteBlockingCommand(), so the dialogue carries on.<<give_item>>. It returns straight away, so the dialogue doesn’t wait for it.days_since_lamp_lit() returns a number.has_item() checks the same inventory that <<give_item>> adds to, so its answer changes once the key has been given.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.