Quick answer: The value on the left of the . is null. Most often a $Node path is wrong, a node is used before it is ready (missing @onready), it was freed, or a function or dictionary returned nothing. Read the variable name in the error, then check where it is assigned.
Godot 3 phrased it "Invalid get index 'position' (on base: 'null instance')"; Godot 4.3 and later say "Invalid access to property or key 'position' on a base object of type 'Nil'". Both mean the same thing: you used .something on a value that is null.
Read the error carefully
The debugger stops on the failing line. The thing before the dot is null. For player.health -= 1, player is null. Hover it, or look at the Stack Variables panel, then trace back to where it was assigned.
Cause 1: wrong node path
$HUD/HealthBar returns null (with a "Node not found" error) if any part of the path is wrong, including capitalisation, or if the node was renamed or moved. Fixes:
- Drag the node from the Scene dock into the script editor while holding Ctrl to insert a correct path.
- Mark important nodes as Access as Unique Name and use
%HealthBar, which survives moving them within the scene.
Cause 2: used before it is ready
Children are not available in _init() or in a member variable initialiser:
var bar = $HealthBar # runs before children exist -> null
@onready var bar = $HealthBar # runs just before _ready() -> correct
Cause 3: the node was freed
A reference to an enemy that has been queue_free()d is not null; it is a freed instance, which gives a similar error. Check with is_instance_valid(enemy) before using stored references.
Cause 4: a function returned nothing
A function that does not return a value on every path returns null:
func find_target():
for e in enemies:
if e.visible:
return e
# no return here -> null when nothing matches
Callers must handle null: var t = find_target(); if t: attack(t).
Cause 5: dictionaries and resources
data["player"] errors on a missing key, but data.get("player") returns null silently, and the error moves to the next line. JSON-loaded data and load() with a wrong path (returns null) behave the same way. Check ResourceLoader.exists(path) and validate parsed data.
Cause 6: get_node from a different scene
Paths are relative to the node calling them. A script on an instanced enemy cannot find $Player because the player is not its child. Pass references in, use groups (get_tree().get_first_node_in_group("player")), or an autoload.
Make it fail early
Typed variables and asserts surface the problem where it starts rather than where it crashes:
@onready var bar: ProgressBar = %HealthBar
func _ready():
assert(bar != null, "HealthBar missing from HUD scene")
These errors also happen to players in builds you cannot debug. Logging them with the stack trace and scene name is what makes the rare ones fixable.