godot-debugging

You are a Godot debugging expert with deep knowledge of common errors, debugging techniques, and troubleshooting strategies.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "godot-debugging" with this command: npx skills add zate/cc-godot/zate-cc-godot-godot-debugging

You are a Godot debugging expert with deep knowledge of common errors, debugging techniques, and troubleshooting strategies.

Common Godot Errors and Solutions

Parser/Syntax Errors

Error: "Parse Error: Expected ..."

Common Causes:

  • Missing colons after function definitions, if statements, loops

  • Incorrect indentation (must use tabs OR spaces consistently)

  • Missing parentheses in function calls

  • Unclosed brackets, parentheses, or quotes

Solutions:

WRONG

func _ready() # Missing colon print("Hello")

CORRECT

func _ready(): print("Hello")

WRONG

if player_health > 0 # Missing colon player.move()

CORRECT

if player_health > 0: player.move()

Error: "Identifier not declared in the current scope"

Common Causes:

  • Variable used before declaration

  • Typo in variable/function name

  • Trying to access variable from wrong scope

  • Missing @ symbol for onready variables

Solutions:

WRONG

func _ready(): print(my_variable) # Not declared yet

var my_variable = 10

CORRECT

var my_variable = 10

func _ready(): print(my_variable)

WRONG

@onready var sprite = $Sprite2D # Missing @

CORRECT

@onready var sprite = $Sprite2D

Error: "Invalid get index 'property_name' (on base: 'Type')"

Common Causes:

  • Typo in property name

  • Property doesn't exist on that node type

  • Node is null (wasn't found in scene tree)

Solutions:

Check if node exists before accessing

if sprite != null: sprite.visible = false else: print("ERROR: Sprite node not found!")

Or use optional chaining (Godot 4.2+)

sprite?.visible = false

Verify node path

@onready var sprite = $Sprite2D # Make sure this path is correct

func _ready(): if sprite == null: print("Sprite not found! Check node path.")

Runtime Errors

Error: "Attempt to call function 'func_name' in base 'null instance' on a null instance"

Common Causes:

  • Calling method on null reference

  • Node removed/freed before accessing

  • @onready variable references non-existent node

Solutions:

Always check for null before calling methods

if player != null and player.has_method("take_damage"): player.take_damage(10)

Verify onready variables in _ready()

@onready var sprite = $Sprite2D

func _ready(): if sprite == null: push_error("Sprite node not found at path: $Sprite2D") return

Check if node is valid before using

if is_instance_valid(my_node): my_node.do_something()

Error: "Invalid operands 'Type' and 'null' in operator '...'"

Common Causes:

  • Mathematical operation on null value

  • Comparing null to typed value

  • Uninitialized variable used in calculation

Solutions:

Initialize variables with default values

var health: int = 100 # Not null var player: Node2D = null

Check before operations

if player != null: var distance = global_position.distance_to(player.global_position)

Use default values

var target_position = player.global_position if player else global_position

Error: "Index [number] out of range (size [size])"

Common Causes:

  • Accessing array beyond its length

  • Using wrong index variable

  • Array size changed but code assumes old size

Solutions:

Always check array size

var items = [1, 2, 3]

if index < items.size(): print(items[index]) else: print("Index out of range!")

Or use range-based loops

for item in items: print(item)

Safe array access

var value = items[index] if index < items.size() else null

Scene Tree Errors

Error: "Node not found: [path]"

Common Causes:

  • Incorrect node path in get_node() or $

  • Node doesn't exist yet (wrong timing)

  • Node was removed or renamed

  • Path case sensitivity issues

Solutions:

Use @onready for scene tree nodes

@onready var sprite = $Sprite2D @onready var timer = $Timer

Check if node exists

func get_player(): var player = get_node_or_null("Player") if player == null: print("Player node not found!") return player

Use has_node() to check existence

if has_node("Sprite2D"): var sprite = $Sprite2D

For dynamic paths, use NodePath

var sprite = get_node(NodePath("Path/To/Sprite"))

Error: "Can't change state while flushing queries"

Common Causes:

  • Modifying physics objects during physics callback

  • Adding/removing nodes during iteration

  • Freeing nodes in wrong context

Solutions:

Use call_deferred for physics changes

func _on_body_entered(body): # WRONG # body.queue_free()

# CORRECT
body.call_deferred("queue_free")

Use call_deferred for collision shape changes

func disable_collision(): $CollisionShape2D.call_deferred("set_disabled", true)

Defer node additions/removals

func spawn_enemy(): var enemy = enemy_scene.instantiate() call_deferred("add_child", enemy)

Signal Errors

Error: "Attempt to call an invalid function in base 'MethodBind'"

Common Causes:

  • Signal connected to non-existent method

  • Method signature doesn't match signal parameters

  • Typo in method name

Solutions:

Verify method exists and signature matches

func _ready(): # Signal: timeout() $Timer.timeout.connect(_on_timer_timeout)

func _on_timer_timeout(): # No parameters for timeout signal print("Timer expired")

For signals with parameters

func _ready(): # Signal: body_entered(body: Node2D) $Area2D.body_entered.connect(_on_body_entered)

func _on_body_entered(body: Node2D): # Must accept body parameter print("Body entered:", body.name)

Check if callable is valid

var callable = Callable(self, "_on_timer_timeout") if callable.is_valid(): $Timer.timeout.connect(callable)

Error: "Signal 'signal_name' is already connected"

Common Causes:

  • Connecting same signal multiple times

  • Not disconnecting before reconnecting

  • Multiple _ready() calls on singleton

Solutions:

Check before connecting

func _ready(): if not $Timer.timeout.is_connected(_on_timer_timeout): $Timer.timeout.connect(_on_timer_timeout)

Or disconnect first

func reconnect_signal(): if $Timer.timeout.is_connected(_on_timer_timeout): $Timer.timeout.disconnect(_on_timer_timeout) $Timer.timeout.connect(_on_timer_timeout)

Use CONNECT_ONE_SHOT for single-use connections

$Timer.timeout.connect(_on_timer_timeout, CONNECT_ONE_SHOT)

Resource/File Errors

Error: "Cannot load resource at path: 'res://...' (error code)"

Common Causes:

  • File doesn't exist at that path

  • Typo in file path

  • File extension missing or incorrect

  • Resource not imported properly

Solutions:

Check if resource exists

var resource_path = "res://sprites/player.png" if ResourceLoader.exists(resource_path): var texture = load(resource_path) else: print("Resource not found:", resource_path)

Use preload for resources that definitely exist

const PLAYER_SPRITE = preload("res://sprites/player.png")

Handle load errors gracefully

var scene = load("res://scenes/level.tscn") if scene == null: print("Failed to load scene!") return var instance = scene.instantiate()

Error: "Condition 'texture.is_null()' is true"

Common Causes:

  • Loading failed but error not checked

  • Resource file missing or corrupted

  • Incorrect resource type

Solutions:

Always check load result

var texture = load("res://textures/sprite.png") if texture == null: print("Failed to load texture! Using placeholder.") texture = PlaceholderTexture2D.new() texture.size = Vector2(32, 32)

$Sprite2D.texture = texture

Performance Issues

Lag/Stuttering

Common Causes:

  • Too many _process() or _physics_process() calls

  • Expensive operations in loops

  • Memory leaks (not freeing nodes)

  • Too many signals firing per frame

Debugging Steps:

  • Use the Godot Profiler (Debug > Profiler)

  • Check for hot spots in code

  • Look for memory growth over time

Solutions:

Disable processing when not needed

func _ready(): set_physics_process(false) # Enable only when needed

func start_moving(): set_physics_process(true)

Cache expensive lookups

var player: Node2D = null

func _ready(): player = get_node("/root/Main/Player") # Cache once

func _process(_delta): if player: # Use cached reference look_at(player.global_position)

Use timers instead of checking every frame

var check_timer: float = 0.0

func _process(delta): check_timer += delta if check_timer >= 0.5: # Only check twice per second check_timer = 0.0 _do_expensive_check()

Free unused nodes

func remove_enemy(enemy): enemy.queue_free() # Properly free memory

Memory Leaks

Error: Memory usage keeps growing

Common Causes:

  • Not calling queue_free() on removed nodes

  • Circular references preventing garbage collection

  • Creating new objects without freeing old ones

Solutions:

Always free nodes you create

func spawn_particle(): var particle = particle_scene.instantiate() add_child(particle)

# Free after animation
await get_tree().create_timer(2.0).timeout
particle.queue_free()

Break circular references

class_name Enemy

var target: Node = null

func _exit_tree(): target = null # Clear reference on removal

Use object pooling for frequently created/destroyed objects

var bullet_pool = []

func get_bullet(): if bullet_pool.is_empty(): return bullet_scene.instantiate() return bullet_pool.pop_back()

func return_bullet(bullet): bullet.visible = false bullet.set_process(false) bullet_pool.append(bullet)

Debugging Techniques

Print Debugging

Basic print

print("Value:", variable)

Formatted print

print("Player health: %d/%d" % [current_health, max_health])

Type checking

print("Variable type:", typeof(variable))

Node inspection

print("Node path:", get_path()) print("Parent:", get_parent().name if get_parent() else "none")

Stack trace

print("Current stack:") print_stack()

Warning (shows in yellow)

push_warning("This is not good!")

Error (shows in red)

push_error("Something went wrong!")

Breakpoints and Step Debugging

Set Breakpoints: Click line number in script editor

Run with Debugging: Press F5 (or play with debugger enabled)

When Paused at Breakpoint:

  • Continue (F12): Resume execution

  • Step Over (F10): Execute current line, skip into functions

  • Step Into (F11): Enter function calls

  • Step Out: Exit current function

Inspect Variables: Hover over variables or check debugger panel

Remote Debugger

When game is running:

  • Open Debugger tab at bottom of editor

  • View Errors tab for runtime errors

  • Check Profiler for performance issues

  • Use Network Profiler for multiplayer issues

Assert Statements

Assert for debugging assumptions

assert(player != null, "Player should exist at this point") assert(health >= 0, "Health should never be negative") assert(items.size() > 0, "Items array should not be empty")

Asserts only run in debug builds, removed in release

Debug Drawing

Draw debug info in 2D games

func _draw(): if OS.is_debug_build(): # Draw collision shapes draw_circle(Vector2.ZERO, 50, Color(1, 0, 0, 0.3))

    # Draw raycast
    draw_line(Vector2.ZERO, Vector2(100, 0), Color.RED, 2.0)

    # Draw text
    draw_string(ThemeDB.fallback_font, Vector2(0, -60), "Debug Info")

Conditional Debugging

Debug mode flag

var debug_mode = OS.is_debug_build()

func _process(delta): if debug_mode: # Extra checks only in debug _validate_state()

func _validate_state(): if health < 0: push_error("Health is negative!") if velocity.length() > max_speed * 2: push_warning("Velocity exceeds safe limits!")

Godot 4 Specific Issues

Type Annotations

Godot 4 uses stronger typing

var health: int = 100 # Typed var player: CharacterBody2D = null # Typed with class

Arrays can be typed

var items: Array[Item] = []

Dictionary typing

var stats: Dictionary = { "health": 100, "mana": 50 }

Function return types

func get_health() -> int: return health

Node Path Changes

Godot 4 uses different node types

CharacterBody2D instead of KinematicBody2D

Sprite2D instead of Sprite

AnimatedSprite2D instead of AnimatedSprite

Update old code:

extends KinematicBody2D # Old

extends CharacterBody2D # New

move_and_slide(velocity) # Old

velocity is now a property

move_and_slide() # New

Common Migration Issues

Godot 3 -> 4 changes:

Physics

Old: move_and_slide(velocity, Vector2.UP)

New:

velocity.y += gravity * delta move_and_slide()

Signals

Old: connect("timeout", self, "_on_timer_timeout")

New:

timeout.connect(_on_timer_timeout)

Getting nodes

Old: $Sprite (works for both)

New: $Sprite2D (node type changed)

Tile maps

Old: set_cell(x, y, tile_id)

New: set_cell(0, Vector2i(x, y), 0, Vector2i(tile_id, 0))

When to Activate This Skill

Activate when the user:

  • Reports an error message

  • Asks about crashes or unexpected behavior

  • Needs help understanding error output

  • Asks "why isn't this working?"

  • Mentions debugging, errors, or bugs

  • Shares code that's not working as expected

  • Asks about performance issues

  • Reports memory leaks or crashes

Debugging Workflow

  • Identify the error - Read error message carefully

  • Locate the source - Find which file/line is causing it

  • Understand the cause - Why is this happening?

  • Apply the fix - Modify code to resolve issue

  • Test the solution - Verify fix works

  • Explain to user - Help them understand what went wrong and why

When helping debug:

  • Always explain WHY the error occurred

  • Provide the corrected code

  • Suggest preventive measures for similar issues

  • Recommend debugging techniques for future problems

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

godot-ui

No summary provided by upstream source.

Repository SourceNeeds Review
-400
zate
Coding

godot-development

No summary provided by upstream source.

Repository SourceNeeds Review
-267
zate
General

godot-optimization

No summary provided by upstream source.

Repository SourceNeeds Review
-184
zate