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. 05Godot 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 フレームあたりのシグナル発火数が多すぎる

デバッグ手順:

  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)

デバッグ手法

print デバッグ

# 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. エディター下部のデバッガータブを開きます
  2. エラータブで実行時エラーを確認します
  3. パフォーマンスの問題はプロファイラーで確認します
  4. マルチプレイヤーの問題にはネットワークプロファイラーを使用します

アサート文

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

このスキルを有効にするタイミング

ユーザーが次のような場合に有効にします:

  • エラーメッセージを報告する
  • クラッシュや予期しない動作について質問する
  • エラー出力の理解に助けを必要としている
  • 「なぜ動かないのですか?」と質問する
  • デバッグ、エラー、バグについて言及する
  • 期待どおりに動作しないコードを共有する
  • パフォーマンスの問題について質問する
  • メモリリークやクラッシュを報告する

デバッグのワークフロー

  1. エラーを特定する - エラーメッセージを注意深く読みます
  2. 原因箇所を特定する - 問題を引き起こしているファイルや行を見つけます
  3. 原因を理解する - なぜこの問題が発生しているのかを確認します
  4. 修正を適用する - 問題を解決するためにコードを変更します
  5. 解決策をテストする - 修正が機能することを確認します
  6. ユーザーに説明する - 何が、なぜ問題だったのかを理解できるようにします

デバッグを支援するとき:

  • エラーが発生した理由を必ず説明します
  • 修正済みのコードを提示します
  • 同様の問題を防ぐ方法を提案します
  • 今後の問題に備えたデバッグ手法を推奨します

コメント