--- name: godot-debugging description: Expert knowledge of Godot debugging, error interpretation, common bugs, and troubleshooting techniques. Use when helping fix Godot errors, crashes, or unexpected behavior. allowed_tools: - mcp__godot__* - Read - Write - Edit - Glob - Grep - Bash --- 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:** ```gdscript # 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:** ```gdscript # 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:** ```gdscript # 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:** ```gdscript # 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:** ```gdscript # 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:** ```gdscript # 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:** ```gdscript # 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:** ```gdscript # 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:** ```gdscript # 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:** ```gdscript # 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:** ```gdscript # 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:** ```gdscript # 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:** 1. Use the Godot Profiler (Debug > Profiler) 2. Check for hot spots in code 3. Look for memory growth over time **Solutions:** ```gdscript # 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:** ```gdscript # 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 ```gdscript # 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 1. **Set Breakpoints**: Click line number in script editor 2. **Run with Debugging**: Press F5 (or play with debugger enabled) 3. **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 4. **Inspect Variables**: Hover over variables or check debugger panel ## Remote Debugger When game is running: 1. Open **Debugger** tab at bottom of editor 2. View **Errors** tab for runtime errors 3. Check **Profiler** for performance issues 4. Use **Network Profiler** for multiplayer issues ## Assert Statements ```gdscript # 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 ```gdscript # 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 ```gdscript # 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 ```gdscript # 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 ```gdscript # 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 ```gdscript # 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 1. **Identify the error** - Read error message carefully 2. **Locate the source** - Find which file/line is causing it 3. **Understand the cause** - Why is this happening? 4. **Apply the fix** - Modify code to resolve issue 5. **Test the solution** - Verify fix works 6. **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