Pardon our dust while we move our docs here. The Godot (GDScript) docs are on this site, and the Yarn language, Unity, Godot (C#) and Unreal docs are still at docs.yarnspinner.dev.

A smart variable is declared with an expression instead of a value. Its value is worked out from that expression every time something reads it, so it’s always up to date with the variables it’s made from.

<<declare $lamp_oil = 3>>
<<declare $lamp_hours = $lamp_oil * 2>>
<<declare $lamp_needs_oil = $lamp_oil < 5>>
Line 1 An ordinary variable. It’s declared with a value, and it’s stored in the Variable Storage.
Lines 2–3 Smart variables. They’re declared with an expression that uses another variable, and they aren’t stored.
One ordinary variable, and two smart variables worked out from it.

Only $lamp_oil is kept in the Variable Storage. When the oil goes up, $lamp_hours and $lamp_needs_oil change with it, without anything setting them.

flowchart LR
  oil[("Ordinary variable<br><br>$lamp_oil = 3")]
  hours["Smart variable<br><br>$lamp_hours<br>= $lamp_oil * 2<br>= 6"]
  needs["Smart variable<br><br>$lamp_needs_oil<br>= $lamp_oil #60; 5<br>= true"]
  oil --> hours
  oil --> needs
Setting $lamp_oil is enough. The two smart variables are worked out from it whenever they’re read.

Using smart variables in Yarn

Use a smart variable anywhere you’d use any other variable: in a line, in an <<if>>, or in another expression.

<<declare $lamp_oil = 3>>
<<declare $lamp_hours = $lamp_oil * 2>>
<<declare $lamp_needs_oil = $lamp_oil < 5>>
Rosa: There's only enough oil for {$lamp_hours} more hours.
<<set $lamp_oil to $lamp_oil + 4>>
Rosa: Thanks. Now it'll last {$lamp_hours} hours.
<<if $lamp_needs_oil>>
    Rosa: It'll need topping up soon.
<<endif>>
Smart variables in lines and a condition.

You can’t <<set>> a smart variable. The Yarn Project won’t compile, and the error says what the variable is always equal to. Change the variables it’s made from instead.

Using smart variables from GDScript

There’s nothing to set up in Godot. Read a smart variable the same way as any other variable, and you get its value as it is right now:

var storage := dialogue_runner.variable_storage
var hours := storage.get_float("$lamp_hours")
var needs_oil := storage.get_bool("$lamp_needs_oil")
Reading two smart variables from GDScript.

A few things work differently from ordinary variables:

  • Don’t set them from GDScript. set_value() doesn’t stop you, but it stores a value under the smart variable’s name, and from then on that stored value is used in place of the expression. Change the variables it’s made from.
  • They don’t tell you when they change. Subscriptions, change listeners and variable_changed only fire when a variable is set, and smart variables never are. To keep something up to date, read the smart variable again when you need it, for example every frame, or when one of the variables it’s made from changes.
  • They aren’t saved. Saving only saves stored variables. After loading, smart variables are worked out again from the loaded values, so they come back correct.
  • The debug view doesn’t list them, because it only shows stored variables.

The demo scene

In this scene, Rosa says how many hours of oil the lamp has left. When the player adds some, $lamp_oil goes up, and $lamp_hours and $lamp_needs_oil change with it. The panel in the top left shows all three, read from GDScript every frame, and the lamp gets brighter as $lamp_hours goes up.

The demo scene running. Only $lamp_oil is set. $lamp_hours and $lamp_needs_oil change with it.
GameNode2D
BackgroundColorRect
FloorColorRect
DoorColorRect
LampColorRect
GlowColorRect
RosaNode2D
BodyColorRect
NameLabel
YarnDialogueRunner
VariableStorageYarnInMemoryVariableStorage
UICanvasLayer
CallLogPanelPanelContainer
CallLogRichTextLabel
InventoryPanelPanelContainer
InventoryLabel
YarnLinePresenter
YarnOptionsPresenter
The demo scene. Game’s script reads the variables every frame and shows them in CallLog and Inventory.
title: SmartVariablesDemo
---
<<declare $lamp_oil = 3>>
<<declare $lamp_hours = $lamp_oil * 2>>
<<declare $lamp_needs_oil = $lamp_oil < 5>>
Rosa: There's only enough oil for {$lamp_hours} more hours.
-> Add some oil.
    <<set $lamp_oil to $lamp_oil + 4>>
    Rosa: Thanks. Now it'll last {$lamp_hours} hours.
-> Leave it.
    Rosa: Fair enough.
<<if $lamp_needs_oil>>
    Rosa: It'll need topping up soon.
<<else>>
    Rosa: That's plenty for tonight.
<<endif>>
===
Line 3 An ordinary variable, stored in the Variable Storage.
Lines 4–5 Two smart variables, worked out from $lamp_oil.
Line 6 Shows 6, because $lamp_oil is 3.
Lines 8–9 Adding oil sets $lamp_oil to 7. The next line shows 14, without $lamp_hours being set.
Lines 12–16 $lamp_needs_oil is false if oil was added, because 7 isn’t less than 5.
The Yarn Script.
extends Node2D

@export var dialogue_runner: YarnDialogueRunner
@export var status_label: RichTextLabel
@export var hours_label: Label
@export var lamp: ColorRect
@export var glow: ColorRect


func _ready():
	dialogue_runner.start_dialogue("SmartVariablesDemo")


func _process(_delta: float) -> void:
	# Smart variables are worked out each time they're read, so read them
	# every frame to keep the display up to date.
	var storage := dialogue_runner.variable_storage
	var oil := storage.get_float("$lamp_oil")
	var hours := storage.get_float("$lamp_hours")
	var needs_oil := storage.get_bool("$lamp_needs_oil")

	status_label.text = "$lamp_oil = %d\n$lamp_hours = %d  (smart)\n$lamp_needs_oil = %s  (smart)" % [oil, hours, needs_oil]
	hours_label.text = "%d hours of oil" % hours

	var brightness := clampf(hours / 14.0, 0.0, 1.0)
	lamp.color = Color(0.35, 0.35, 0.35).lerp(Color(1.0, 0.85, 0.35), brightness)
	glow.modulate.a = brightness
Lines 3–7 Nodes in the scene, set in the Inspector.
Line 11 Start the dialogue. The Dialogue Runner’s Auto Start is off.
Lines 14–16 Smart variables don’t tell you when they change, so read them every frame.
Lines 17–20 Read the stored variable and the two smart variables, the same way.
Lines 22–23 Show the values in the panel in the top left, and the hours in the top right.
Lines 25–27 Make the lamp brighter the more hours of oil it has, up to full at 14 hours.
The script on the Game node.
Next step Saliency and Storylets How the Dialogue Runner picks one line or node when several could run.