Functions
A Function lets a Yarn Script ask your game for a value, like how many coins the player has, or whether they’re carrying a key. You call it by name, with any arguments in brackets, and your game sends back the answer:
title: FunctionsDemo
---
<<if has_item("brass key")>>
Rosa: Good, you've got the key.
<<else>>
Rosa: You'll need a key to get up the stairs.
<<endif>>
<<declare $coin_total = 0>>
<<set $coin_total to coin_count()>>
Rosa: You've got {$coin_total} coins.
===Where a Command tells your game to do something, a Function asks it a question. The dialogue uses the answer and carries on.
sequenceDiagram
participant R as Dialogue Runner
participant G as Your game code
Note over R: #60;#60;if has_item("brass key")#62;#62;
R->>G: has_item("brass key")
G-->>R: returns false
Note over R: Shows "You'll need a key to get up the stairs."
Note over R: #60;#60;set $coin_total to coin_count()#62;#62;
R->>G: coin_count()
G-->>R: returns 15
Note over R: Shows "You've got 15 coins."Using Functions in Yarn
You can use a Function anywhere Yarn expects a value:
- in a condition:
<<if has_item("brass key")>>, - in a
<<set>>:<<set $coin_total to coin_count()>>, - in an expression with other values:
<<if coin_count() >= 10>>.
Put text arguments in double quotes, like "brass key". Numbers, true and false, and variables go in as they are: has_item($chosen_item).
The Yarn Spinner compiler doesn’t know what your own Functions return. A Function used directly inside a line, like Rosa: You've got {coin_count()} coins., won’t compile, and you’ll see “Can’t determine the type of the expression” when the Yarn Project is imported. The same happens when you <<set>> a variable that hasn’t been declared to a Function’s value.
Declare a variable, store the value in it, and put the variable in the line:
<<declare $coin_total = 0>>
<<set $coin_total to coin_count()>>
Rosa: You've got {$coin_total} coins.Or pass the Function’s value through a built-in Function whose type the compiler knows, like string(): Rosa: You've got {string(coin_count())} coins.
Writing a Function
Every Function is added to a Dialogue Runner by name. It takes its arguments as an array of Yarn Values, and returns one Yarn Value.
To add has_item from a Blueprint:
- Add a function called
HasItem. Give it one input, Parameters, an array of Yarn Value, and one output calledReturnValue, a Yarn Value. - In the function, Get index 0 from Parameters, and turn it into text with Yarn Value To String.
- Check your inventory for that text, and pass the result to Make Yarn Value From Bool. Connect that to ReturnValue on the Return Node.
- After Event BeginPlay, add a Register Function Handler node. Connect your Dialogue Runner to Dialogue Runner, type
has_iteminto Function Name, and set Parameter Count to 1. - Drag from the Handler pin, add a Create Event node, and choose
HasItemfrom its list.
To remove a Function, use the Unregister Function Handler node.
In C++, call AddFunction() on the Dialogue Runner, with the Function’s name, a lambda, and the number of arguments it takes:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "PlayerInventory.generated.h"
UCLASS()
class APlayerInventory : public AActor
{
GENERATED_BODY()
protected:
virtual void BeginPlay() override;
private:
TArray<FString> Inventory = { FString(TEXT("lantern")) };
int32 Coins = 15;
};#include "PlayerInventory.h"
#include "YarnDialogueRunner.h"
void APlayerInventory::BeginPlay()
{
Super::BeginPlay();
UYarnDialogueRunner* DialogueRunner = FindComponentByClass<UYarnDialogueRunner>();
DialogueRunner->AddFunction(TEXT("has_item"), [this](const TArray<FYarnValue>& Parameters)
{
return FYarnValue(Inventory.Contains(Parameters[0].ConvertToString()));
}, 1);
DialogueRunner->AddFunction(TEXT("coin_count"), [this](const TArray<FYarnValue>& Parameters)
{
return FYarnValue(static_cast<float>(Coins));
}, 0);
}has_item("brass key") in Yarn calls this. The last argument, 1, is how many arguments it takes. It returns a bool, so it can be used in an <<if>>.coin_count() takes no arguments and returns a number. Yarn numbers are float.To remove a Function, call DialogueRunner->RemoveFunction(TEXT("has_item")).
If you add a Function with a name that’s already in use, the new one replaces the old one. Don’t use the names of the built-in Functions.
Functions marked with YarnFunction
As with Commands, you can mark a C++ UFUNCTION with metadata, and add every marked function on an object with UYarnCommandLibrary::RegisterCommandsFromObject(). The function takes a const TArray<FYarnValue>& and returns an FYarnValue:
UFUNCTION(meta = (YarnFunction = "shout"))
FYarnValue Shout(const TArray<FYarnValue>& Parameters);FYarnValue ATextFunctions::Shout(const TArray<FYarnValue>& Parameters)
{
if (Parameters.Num() == 0)
{
return FYarnValue(FString());
}
return FYarnValue(Parameters[0].ConvertToString().ToUpper());
}Functions added this way accept any number of arguments.
Values going in and out
A Function returns a Yarn Value, which holds one of the kinds of value Yarn understands:
| In Yarn | In Blueprints | In C++ |
|---|---|---|
true or false | Make Yarn Value From Bool | FYarnValue(true) |
| A number | Make Yarn Value From Number | FYarnValue(12.0f) |
| Text | Make Yarn Value From String | FYarnValue(FString(TEXT("text"))) |
float or an FString to FYarnValue. FYarnValue(12) doesn’t compile, because an int could become a float or a bool. FYarnValue(TEXT("text")) compiles, but makes a bool, because C++ turns the text’s pointer into true.The arguments arrive in the same order as in the Yarn Script. A Yarn number is a float. In Blueprints, turn an argument into text with Yarn Value To String, and then into a number with String To Float if you need one. In C++, use ConvertToString(), ConvertToNumber() or ConvertToBool(), or check its type with IsNumber(), IsString() and IsBool().
A Function must return its value immediately. It can’t make the dialogue wait.
When you add a Function, you say how many arguments it takes. If a Yarn Script calls it with a different number, the Dialogue Runner logs an error and the dialogue stops. To accept any number of arguments, give -1. If a Yarn Script calls a Function that hasn’t been added, the Dialogue Runner logs an error like Yarn VM: Unknown function 'coin_count' - halting, and the dialogue stops.
To stop the dialogue from inside a C++ Function, for example because an argument makes no sense, call DialogueRunner->ReportFunctionError() with a message, and return any value. The message is logged, and the dialogue stops.
Built-in Functions
These Functions are always available:
| Function | What it returns |
|---|---|
visited("NodeName") | true if the node has been run before. |
visited_count("NodeName") | How many times the node has been run. |
random() | A random number between 0 and 1. |
random_range(1, 6) | A random whole number from the first number to the second, including both. |
dice(6) | A random whole number from 1 to the number you give it, like rolling a die. |
round(n), floor(n), ceil(n) | The number rounded to the nearest whole number, down, or up. |
min(a, b), max(a, b) | The smaller or larger of two numbers. |
For maths there are also random_range_float, round_places, inc, dec, decimal and int. For converting between types there are string, number and bool. For formatting numbers there are format and format_invariant, and for node groups there’s has_any_content. See Saliency and Storylets.
Autocomplete in VS Code
The Yarn Spinner extension for VS Code suggests your Functions as you type. It reads them from the .ysls.json file the plugin writes next to each .yarnproject file. It includes C++ functions marked with YarnFunction, and Register Function Handler nodes whose Function Name is typed into the node. Functions added with AddFunction() in C++ aren’t included. See Autocomplete in VS Code on the Commands page.
For a setup that uses Commands and Functions together, see Putting It Together.