GDScript Node Operations Cheat Sheet
Quick-reference for Godot 4 scene tree operations. Each section includes copy-ready snippets with inline output comments for spawning, removing, and managing nodes.
Accessing Nodes
Use the $ shorthand or get_node() to reference nodes in the scene tree by their path.
# Direct child
var sprite := $Sprite2D
var label := $HUD/ScoreLabel # nested path
# Equivalent to:
var sprite2 := get_node("Sprite2D")@onready var player: CharacterBody2D = $Player
@onready var health_bar: ProgressBar = $HUD/HealthBar
@onready var anim: AnimationPlayer = $AnimationPlayer
# These resolve when the node enters the tree (after _ready)@onready is the standard pattern. Avoid calling $ in _init — nodes are not ready yet.
# get_node() accepts NodePath or StringName
var enemy := get_node("Enemies/Goblin")
# Use % for unique node names (scene-unique)
var player := get_node("%Player") # finds "Player" anywhere in this sceneFinding Nodes
Search for nodes by name, type, or group when you do not know the exact path.
# Search this node's subtree
var chest := find_child("TreasureChest")
# Returns null if not found
# With pattern matching
var any_enemy := find_child("Enemy*", true, false)# Get all direct children
for child in get_children():
print(child.name)
# Filter by type
for child in get_children():
if child is Enemy:
child.take_damage(10)# Get all Area2D nodes in the subtree
var areas: Array[Node] = find_children("*", "Area2D")
for area in areas:
print(area.name)Adding and Removing Nodes
Use add_child() to attach nodes to the tree and queue_free() to safely remove them.
var label := Label.new()
label.text = "Damage: 50"
label.position = Vector2(100, 50)
add_child(label)# Removes the node at the end of the current frame
func die() -> void:
$DeathParticles.emitting = true
# Wait for particles to finish
await get_tree().create_timer(1.0).timeout
queue_free()Never use free() directly in game code. queue_free() avoids mid-frame crashes.
# Remove from tree but keep in memory
var weapon := $Weapon
remove_child(weapon)
# weapon still exists — you can add_child() it elsewhere laterInstantiating Packed Scenes
Load a .tscn as a PackedScene, then call instantiate() to create new instances at runtime.
const BulletScene := preload("res://scenes/bullet.tscn")
func shoot() -> void:
var bullet := BulletScene.instantiate()
bullet.global_position = $Muzzle.global_position
bullet.rotation = rotation
get_parent().add_child(bullet)const EnemyScene := preload("res://scenes/enemy.tscn")
func spawn_wave(count: int) -> void:
for i in range(count):
var enemy: Enemy = EnemyScene.instantiate() as Enemy
enemy.global_position = get_random_spawn_point()
$Enemies.add_child(enemy)# Use load() when the path is dynamic
var scene_path := "res://scenes/enemies/%s.tscn" % enemy_type
var scene: PackedScene = load(scene_path)
var instance := scene.instantiate()
add_child(instance)preload() runs at parse time (fast). load() runs at call time (flexible but slower).
Node Tree Navigation
Walk up, down, and across the scene tree to find related nodes.
var parent := get_parent() # immediate parent node
var root := get_tree().root # root Viewport
var scene_root := owner # root of the packed scenevar count := $Enemies.get_child_count()
# count => number of enemy nodes
var first_enemy := $Enemies.get_child(0)
var last_enemy := $Enemies.get_child(-1)if enemy.is_inside_tree():
enemy.global_position = Vector2.ZERO
# Check before accessing tree-dependent properties
if is_inside_tree():
var viewport_size := get_viewport_rect().sizeReparenting Nodes
Move a node from one parent to another. Godot 4 added reparent() for convenience.
# Move a picked-up item from the world to the player
var item := $World/Sword
item.reparent($Player/Inventory)
# item is now a child of Player/Inventory# Remove from current parent, add to new parent
var node := $OldParent/Child
$OldParent.remove_child(node)
$NewParent.add_child(node)# reparent(keep_global_transform) preserves world position
var bullet := $Player/Gun/Bullet
bullet.reparent(get_parent(), true) # true = keep global transform
# Bullet now moves independently but stays at same world positionset_owner and Saving Scenes
owner determines which nodes are included when saving a PackedScene. Nodes added at runtime have no owner by default.
var wall := StaticBody2D.new()
add_child(wall)
wall.owner = self # now included if this scene is saved
var shape := CollisionShape2D.new()
wall.add_child(shape)
shape.owner = self # set owner for nested nodes toofunc set_owner_recursive(node: Node, new_owner: Node) -> void:
node.owner = new_owner
for child in node.get_children():
set_owner_recursive(child, new_owner)Only nodes with an owner are saved in PackedScene.pack(). Runtime-spawned nodes need explicit owner assignment.
Groups
Groups tag nodes for batch operations. Any node can belong to multiple groups.
# In code
add_to_group("enemies")
add_to_group("damageable")
# Check membership
if is_in_group("enemies"):
print("I'm an enemy!")var enemies := get_tree().get_nodes_in_group("enemies")
for enemy in enemies:
enemy.take_damage(10)
# Count
var count := get_tree().get_nodes_in_group("collectibles").size()# Call a method on every node in the group
get_tree().call_group("enemies", "freeze")
get_tree().call_group("ui_panels", "hide")
# With arguments
get_tree().call_group("enemies", "take_damage", 25)func _on_enemy_converted_to_ally() -> void:
remove_from_group("enemies")
add_to_group("allies")Node Lifecycle Callbacks
Key virtual methods and their execution order in the scene tree.
# 1. _init() — constructor (no tree access)
# 2. _enter_tree() — just added to tree
# 3. _ready() — all children are ready
# 4. _process() — every frame
# 5. _physics_process() — fixed physics tick
# 6. _exit_tree() — about to leave treefunc _enter_tree() -> void:
# Called every time node enters tree (including re-adds)
print("Entered tree")
func _ready() -> void:
# Called once (unless request_ready() was called)
print("Ready — all children initialized")func freeze() -> void:
set_process(false)
set_physics_process(false)
func unfreeze() -> void:
set_process(true)
set_physics_process(true)Disabling process is cheaper than removing a node. Use it for inactive enemies or paused objects.
Common Node Patterns
Practical patterns you will use in every Godot project.
var bullet_pool: Array[Node2D] = []
func get_bullet() -> Node2D:
for bullet in bullet_pool:
if not bullet.visible:
bullet.visible = true
return bullet
# Pool empty — create new
var new_bullet := BulletScene.instantiate()
bullet_pool.append(new_bullet)
add_child(new_bullet)
return new_bullet# If adding nodes during signal callbacks or physics:
call_deferred("add_child", new_node)
# Or use the deferred variant directly
add_child.call_deferred(new_node)Use call_deferred when modifying the tree inside _physics_process or signal callbacks to avoid mid-frame issues.
var target: Node2D
func _physics_process(delta: float) -> void:
if not is_instance_valid(target):
target = null
return
# Safe to use target
look_at(target.global_position)