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.
===
Two Functions in a node. has_item decides which line Rosa says, and coin_count’s value is shown in a line.

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."
The Dialogue Runner calls the Function in your game, and the value it returns is used in the Yarn Script.

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.
Declaring $coin_total, then storing coin_count()’s value in it for Rosa’s line.

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:

  1. Add a function called HasItem. Give it one input, Parameters, an array of Yarn Value, and one output called ReturnValue, a Yarn Value.
  2. In the function, Get index 0 from Parameters, and turn it into text with Yarn Value To String.
  3. Check your inventory for that text, and pass the result to Make Yarn Value From Bool. Connect that to ReturnValue on the Return Node.
  4. After Event BeginPlay, add a Register Function Handler node. Connect your Dialogue Runner to Dialogue Runner, type has_item into Function Name, and set Parameter Count to 1.
  5. Drag from the Handler pin, add a Create Event node, and choose HasItem from its list.
PLACEHOLDER — Screenshot needed
Two Blueprint graphs. In the Event Graph, Event BeginPlay connects to Register Function Handler, with the Yarn Dialogue Runner component in Dialogue Runner, has_item in Function Name and 1 in Parameter Count; its Handler pin connects to a Create Event node set to HasItem. The HasItem function gets index 0 from Parameters, passes it through Yarn Value To String into a Contains check on an Inventory array, and returns the result through Make Yarn Value From Bool.

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;
};
Lines 16–17 The game state the Functions read.
PlayerInventory.h. An Actor that keeps the player’s items and coins.
#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);
}
Line 8 Find the Dialogue Runner on this Actor.
Lines 10–13 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>>.
Lines 15–18 coin_count() takes no arguments and returns a number. Yarn numbers are float.
PlayerInventory.cpp. The Dialogue Runner is a component on the same Actor.

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);
A Function marked with YarnFunction metadata, in an Actor’s header.
FYarnValue ATextFunctions::Shout(const TArray<FYarnValue>& Parameters)
{
	if (Parameters.Num() == 0)
	{
		return FYarnValue(FString());
	}
	return FYarnValue(Parameters[0].ConvertToString().ToUpper());
}
Its implementation, which returns its text in capitals.

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 YarnIn BlueprintsIn C++
true or falseMake Yarn Value From BoolFYarnValue(true)
A numberMake Yarn Value From NumberFYarnValue(12.0f)
TextMake Yarn Value From StringFYarnValue(FString(TEXT("text")))
How to make each kind of value a Function can return.
In C++, pass a 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:

FunctionWhat 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.
Some of the built-in Functions.

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.

Next step Putting It Together A small Actor that uses Commands and Functions together.