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>>
<<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).
A Function can go directly inside a line, like Rosa: You've got {coin_count()} coins. When the Yarn Project is imported, the addon finds your Functions and tells the compiler what each one returns, so the compiler can work out how to show the value.
If the addon can’t find a Function’s return type, a Function used directly inside a line won’t compile, and you’ll see “Can’t determine the type of the expression”. That happens when:
- the addon can’t find the Function’s script, because it isn’t in the Yarn Project’s folder or in a scene that uses the Yarn Project, or it’s outside the folder set in the Ysls Scan Path import option (see The .ysls.json file),
- the Function’s method doesn’t say what it returns, like
func _coin_count():instead offunc _coin_count() -> int:, or it’s a lambda, - the Yarn Project’s Generate Ysls import option is off.
Fix whichever applies, or store the value in a variable first and put the variable in the line:
<<set $coin_total to coin_count()>>
Rosa: You've got {$coin_total} coins.Writing a Function
Functions the Dialogue Runner finds by itself
Give a static method a name that starts with _yarn_function_, and the rest of the name is the Function’s name in Yarn:
static func _yarn_function_shout(text: String) -> String:
return text.to_upper()The Dialogue Runner finds these methods in the same places it finds Commands: in scripts with a class_name, in scripts attached to nodes in the running scene, and in autoloads. See Where the Dialogue Runner looks.
The method has to be static. If a _yarn_function_ method isn’t, the Dialogue Runner logs an error and skips it. A method in an autoload can be either.
Functions that use your game’s state
A static method can’t read the variables on a node, like an inventory. For a Function that needs to, add it to the Dialogue Runner with add_function():
@export var dialogue_runner: YarnDialogueRunner
var _inventory: Array[String] = ["lantern"]
var _coins := 15
func _ready():
dialogue_runner.add_function("has_item", _has_item)
dialogue_runner.add_function("coin_count", _coin_count)
func _has_item(item_name: String) -> bool:
return item_name in _inventory
func _coin_count() -> int:
return _coinsstatic method couldn’t see these.has_item("brass key") in Yarn calls this. It returns a bool, so it can be used in an <<if>>.coin_count() returns a number.Pass a named method, like _has_item, so the Dialogue Runner can see its parameters. If you pass a lambda, give the number of arguments it takes as a third argument: dialogue_runner.add_function("double", func(n): return n * 2, 1).
To remove a Function, call dialogue_runner.remove_function("has_item"). To add a Function to every Dialogue Runner in your game, call YarnSpinner.register_function(), with the same arguments, before the Dialogue Runners start.
Two Functions can’t have the same name. If a second one uses a name that’s already taken, the Dialogue Runner logs an error and ignores it.
Values going in and out
A Function has to return one of the kinds of value Yarn understands:
| Return type | In Yarn |
|---|---|
bool | true or false. |
int or float | A number. |
String | Text. |
A Function must return its value immediately. It can’t await anything. If it returns something else, returns nothing, or awaits, the Dialogue Runner logs an error and stops the dialogue.
Arguments are converted to the types of the method’s parameters. A Yarn number becomes an int or float, and true and false become a bool. A parameter without a type gets the value as it is. Parameters with default values can be left out.
If a Function gets the wrong number of arguments, or one can’t be converted, the Dialogue Runner logs an error and stops the dialogue.
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 random_range_float, round_places, inc, dec, decimal, int, abs, sign, clamp, lerp, inverse_lerp, smoothstep, pow, sqrt, wrap and mod. For converting between types there are string, number and bool.
Autocomplete in VS Code
The Yarn Spinner extension for VS Code suggests your Functions as you type, along with what they return. It reads them from the project’s .ysls.json file, which the addon writes for you, including Functions you add with add_function(). See The .ysls.json file.
The scene in the video
title: FunctionsDemo
---
<<declare $lamp_days = 0>>
<<set $lamp_days to days_since_lamp_lit()>>
Rosa: The lamp's been out for {$lamp_days} days.
<<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.
<<if coin_count() >= 10>>
Rosa: That's enough to buy oil for the lamp.
<<endif>>
<<declare $sign_text = "">>
<<set $sign_text to shout("keep out")>>
Rosa: The sign on the door says "{$sign_text}".
===extends Node2D
@export var dialogue_runner: YarnDialogueRunner
@export var call_log: RichTextLabel
@export var inventory_label: Label
var _inventory: Array[String] = ["lantern"]
var _coins := 15
static var _log: RichTextLabel
func _ready():
_log = call_log
dialogue_runner.add_function("days_since_lamp_lit", _days_since_lamp_lit)
dialogue_runner.add_function("has_item", _has_item)
dialogue_runner.add_function("coin_count", _coin_count)
_update_inventory()
static func _log_function(call: String, result: Variant) -> void:
var shown := '"%s"' % result if result is String else str(result)
_log.append_text("[color=#7fc4e8]Function[/color] %s → %s\n" % [call, shown])
func _update_inventory() -> void:
var text := "Inventory\n"
for item in _inventory:
text += "• " + item + "\n"
text += "• %d coins" % _coins
inventory_label.text = text
# Functions added in _ready
func _days_since_lamp_lit() -> int:
var days := 12
_log_function("days_since_lamp_lit()", days)
return days
func _has_item(item_name: String) -> bool:
var result := item_name in _inventory
_log_function('has_item("%s")' % item_name, result)
return result
func _coin_count() -> int:
_log_function("coin_count()", _coins)
return _coins
# A Function the Dialogue Runner finds by itself
static func _yarn_function_shout(text: String) -> String:
var result := text.to_upper()
_log_function('shout("%s")' % text, result)
return resultstatic variable so the static Function can write to it too. This is only for the demo.shout isn’t here: it’s static and named _yarn_function_shout, so the Dialogue Runner finds it by itself.days_since_lamp_lit() returns a number.has_item() returns a bool.coin_count() returns the number of coins.shout() is static, so it can’t read this node’s variables. It returns its text in capitals.