mcpField
G
godot-debugging

godot-debugging

github.com/zate开发20375 次安装50 周前更新
代码仓库

关于 godot-debugging

godot-debugging 是面向 Godot 开发者的故障排查技能,涵盖常见错误及其成因与修复方法,并介绍调试、性能检查和内存问题排查。

主要功能

  1. 01排查常见 Godot 错误
  2. 02说明错误成因与修复方法
  3. 03使用断点和调试器
  4. 04检查性能与内存问题
  5. 05处理 Godot 4 迁移问题

适用场景

  1. 01修复解析或运行时错误
  2. 02追查节点缺失或信号异常
  3. 03调查游戏卡顿或内存增长

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/zate/cc-godot --skill godot-debugging

安装后在 Claude Code 中输入/godot-debugging确认已加载。

详细介绍

你是一位 Godot 调试专家,深入了解常见错误、调试技巧和故障排除策略。

常见 Godot 错误及解决方法

解析器/语法错误

错误:“Parse Error: Expected ...”

常见原因:

  • 函数定义、if 语句和循环后缺少冒号
  • 缩进不正确(必须始终一致地使用制表符或空格)
  • 函数调用缺少括号
  • 方括号、圆括号或引号未闭合

解决方法:

# 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()
错误:“Identifier not declared in the current scope”

常见原因:

  • 使用变量前未声明
  • 变量/函数名称拼写错误
  • 尝试从错误的作用域访问变量
  • onready 变量缺少 @ 符号

解决方法:

# 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
错误:“Invalid get index 'property_name' (on base: 'Type')”

常见原因:

  • 属性名称拼写错误
  • 该节点类型不存在此属性
  • 节点为 null(未在场景树中找到)

解决方法:

# 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.")

运行时错误

错误:“Attempt to call function 'func_name' in base 'null instance' on a null instance”

常见原因:

  • 在 null 引用上调用方法
  • 访问前节点已被移除/释放
  • @onready 变量引用了不存在的节点

解决方法:

# 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()
错误:“Invalid operands 'Type' and 'null' in operator '...'”

常见原因:

  • 对 null 值执行数学运算
  • 将 null 与类型化值进行比较
  • 在计算中使用未初始化的变量

解决方法:

# 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
错误:“Index [number] out of range (size [size])”

常见原因:

  • 访问超出数组长度的索引
  • 使用了错误的索引变量
  • 数组大小已更改,但代码仍按旧大小处理

解决方法:

# 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

场景树错误

错误:“Node not found: [path]”

常见原因:

  • get_node() 或 $ 中的节点路径不正确
  • 节点尚不存在(时机不对)
  • 节点已被移除或重命名
  • 路径的大小写不匹配

解决方法:

# 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"))
错误:“Can't change state while flushing queries”

常见原因:

  • 在物理回调期间修改物理对象
  • 遍历期间添加/移除节点
  • 在错误的上下文中释放节点

解决方法:

# 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)

信号错误

错误:“Attempt to call an invalid function in base 'MethodBind'”

常见原因:

  • 信号连接到了不存在的方法
  • 方法签名与信号参数不匹配
  • 方法名称拼写错误

解决方法:

# 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)
错误:“Signal 'signal_name' is already connected”

常见原因:

  • 多次连接同一个信号
  • 重新连接前未断开连接
  • 单例多次调用 _ready()

解决方法:

# 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)

资源/文件错误

错误:“Cannot load resource at path: 'res://...' (error code)”

常见原因:

  • 指定路径下不存在该文件
  • 文件路径拼写错误
  • 缺少文件扩展名或扩展名不正确
  • 资源未正确导入

解决方法:

# 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()
错误:“Condition 'texture.is_null()' is true”

常见原因:

  • 加载失败,但未检查错误
  • 资源文件缺失或已损坏
  • 资源类型不正确

解决方法:

# 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

性能问题

卡顿/停顿

常见原因:

  • _process() 或 _physics_process() 调用过多
  • 循环中执行了开销较大的操作
  • 内存泄漏(未释放节点)
  • 每帧触发的信号过多

调试步骤:

  1. 使用 Godot 分析器(Debug > Profiler)
  2. 检查代码中的性能热点
  3. 查看内存是否随时间增长

解决方法:

# 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

内存泄漏

错误:内存使用量持续增长

常见原因:

  • 移除节点时未调用 queue_free()
  • 循环引用导致无法进行垃圾回收
  • 创建新对象时未释放旧对象

解决方法:

# 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)

调试技巧

打印调试

# 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!")

断点和单步调试

  1. 设置断点:在脚本编辑器中点击行号

  2. 启用调试运行:按 F5(或在启用调试器的情况下点击播放)

  3. 在断点处暂停时:

    • 继续(F12):恢复执行
    • 单步跳过(F10):执行当前行,跳过函数内部
    • 单步进入(F11):进入函数调用
    • 单步跳出:退出当前函数
  4. 检查变量:将鼠标悬停在变量上,或查看调试器面板

远程调试器

游戏运行时:

  1. 打开编辑器底部的 Debugger 选项卡
  2. 在 Errors 选项卡中查看运行时错误
  3. 查看 Profiler,排查性能问题
  4. 使用 Network Profiler 排查多人游戏问题

断言语句

# 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

调试绘制

# 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")

条件调试

# 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 特定问题

类型注解

# 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

节点路径变更

# 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

常见迁移问题

# 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))

何时启用此技能

在用户出现以下情况时启用:

  • 报告错误消息
  • 询问崩溃或意外行为
  • 需要帮助理解错误输出
  • 询问“为什么无法正常工作?”
  • 提到调试、错误或 Bug
  • 分享无法按预期工作的代码
  • 询问性能问题
  • 报告内存泄漏或崩溃

调试工作流

  1. 识别错误 - 仔细阅读错误消息
  2. 定位源头 - 找出导致问题的文件和行
  3. 理解原因 - 为什么会发生这种情况?
  4. 应用修复 - 修改代码以解决问题
  5. 测试解决方案 - 验证修复是否有效
  6. 向用户说明 - 帮助他们理解出了什么问题以及原因

协助调试时:

  • 始终解释错误发生的原因
  • 提供修正后的代码
  • 建议预防类似问题的措施
  • 为今后的问题推荐调试技巧

评论

支持 Markdown