Child nodes are not available when a script's object is created. @onready waits until they are:
extends CharacterBody2D
@onready var sprite: Sprite2D = $Sprite2D
@onready var health_bar: ProgressBar = $UI/HealthBar
@onready var jump_sound := $JumpSound as AudioStreamPlayer2D
func _ready():
health_bar.value = 100
func jump():
jump_sound.play()
@onready var sprite = $Sprite2D is the same as declaring var sprite and assigning sprite = $Sprite2D at the start of _ready(). It is shorter, and it keeps the declaration next to its value.
When Each Value Is Set
A plain member variable gets its value when the object is created, before it is in the tree. An @onready variable gets its value right before _ready():
plain var set, in tree: false
_init
_enter_tree
@onready var set, in tree: true
_ready, parent is root
- Plain
varvalues, then_init(), when the object is created. _enter_tree(), when the node is added to the tree.@onreadyvalues, once the node's children are ready._ready().
The Error @onready Prevents
Without @onready, a path is looked up while the node has no parent and no children:
var parent_name = get_parent().name # runs at creation: there is no parent yet
SCRIPT ERROR: Invalid access to property or key 'name' on a base object of type 'null instance'.
The same message appears when a $Path has a typo. If you see null instance, check the node name and spelling in the Scene dock first, then check whether the code runs before _ready().
Typing @onready Variables
Add a type so the editor can autocomplete the node's methods:
@onready var timer: Timer = $Cooldown
@onready var label := $Score as Label
With : Timer, Godot checks the node when the variable is set, and reports an error if $Cooldown is a different type. With as Label, a node of the wrong type gives null instead.
Unique Names: %Node
A $ path breaks when you move the node to another parent. Mark the node as a unique name (right click it in the Scene dock, then Access as Unique Name) and use %:
@onready var score_label: Label = %ScoreLabel
%ScoreLabel finds the node anywhere in the same scene, so you can rearrange the scene freely.
@onready or @export?
@onready var bar = $UI/Bar | @export var bar: ProgressBar | |
|---|---|---|
| Set by | the path in the code | picking the node in the Inspector |
| Moving or renaming the node | breaks the path | keeps working |
| Visible in the Inspector | no | yes |
Use @onready for a node's own children that rarely move. Use @export for references a designer sets, or for nodes in other parts of the scene.
Do Not Combine Them
@export @onready var bar = $Bar looks handy but does not do what you expect: the @onready value replaces the one set in the Inspector, so the export is useless. Godot warns with ONREADY_WITH_EXPORT. Pick one of the two.
Frequently Asked Questions
What does @onready do in Godot?
It delays a member variable's starting value until just before _ready() runs. At that point the node and its children are in the scene tree, so @onready var label = $Label finds the child. Without @onready, the value would be computed when the object is created, before it has any children.
What is the difference between @onready and _ready?
None in timing: @onready var x = $X is the same as declaring var x and setting x = $X as the first line of _ready(). @onready is shorter and keeps the declaration and its value together.
Should I use @onready or @export for nodes?
Both work. @onready var bar = $UI/HealthBar depends on the path, so renaming or moving the node breaks it. @export var bar: ProgressBar, set in the Inspector, survives moves. Many projects use %UniqueName with @onready as a middle ground.
Why do I get "Invalid access ... on a base object of type 'null instance'"?
Code used a node that was not there. Either the path in $ or get_node() does not match the scene, or the variable was read before _ready(), for example from _init() or from another node's _ready() that ran first.