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>>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 --> needsUsing 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>>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")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_changedonly 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.
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>>
===$lamp_oil.$lamp_oil is 3.$lamp_oil to 7. The next line shows 14, without $lamp_hours being set.$lamp_needs_oil is false if oil was added, because 7 isn’t less than 5.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