godot-profile-performance
Godotプロジェクトのパフォーマンスボトルネックを検出します。負荷の高い_process関数、ループ内のget_node()呼び出し、_process内でのインスタンス化などを識別し、Godotプロファイラーと連携して最適化の提案を提供します。
description の原文を見る
Detects performance bottlenecks in Godot projects including expensive _process functions, get_node() calls in loops, instantiations in _process, and provides optimization suggestions with Godot profiler integration
SKILL.md 本文
Godot Performance Profiler
概要
Godotプロジェクトを分析し、GDScriptコード内のパフォーマンスボトルネックを検出します。_process、_physics_process、_drawなどのフレーム関連関数における負荷の高い処理を特定します。実行前後のコード例を含む実用的な最適化提案を提供し、Godotの組み込みプロファイラーと統合して検証を行います。
基本原則: フレーム時間は貴重です。負荷の高い処理は_readyまたはシグナルハンドラーに配置し、フレームごとのコールバック内には配置しないでください。
使用する場合
以下の場合に使用してください:
- フレームレートが低下または不安定なパフォーマンス
- CPU使用率が予期せず高い
- ゲームプレイ中にゲームが途切れる
_process関数に複雑なロジックが含まれている- メモリ使用量が時間とともに増加している
- ゲームをリリースする前または大きなアップデート前
以下の場合には使用しないでください:
- ネットワーク/サーバーパフォーマンスの問題(サーバープロファイリングツールを使用)
- GPU/シェーダー最適化(Godotの GPU プロファイラーを使用)
- 物理シミュレーションの調整(Godotの物理デバッグツールを使用)
検出パターン
負荷の高い _process 関数
トリガー: _processまたは_physics_process内に15行以上のコードがある関数
検出:
# プロセス関数の行数をカウント
rg "^func _process" -A 50 --glob "*.gd" | wc -l
閾値:
- 警告:15行以上
- 重大:30行以上
ループ内の get_node()
パターン:
# ❌ 悪い例:_process内の get_node()
func _process(delta):
get_node("UI/HealthBar").value = health # 毎フレーム呼び出される!
get_node("Player").position = position
検出正規表現:
func _process.*\n(?:.*\n)*?\s+get_node\(
_process内のインスタンス化
パターン:
# ❌ 悪い例:毎フレームオブジェクトを作成
func _process(delta):
var bullet = Bullet.new() # メモリの浪費!
add_child(bullet)
検出キーワード:
_processまたは_physics_process内の.new()- フレームコールバック内の
.instantiate() - ループ内の
add_child()またはremove_child()
_physics_process内の複雑な処理
アンチパターン:
- 重いパス探索計算
- 複雑なAI状態マシン
- 大規模な配列操作
- ファイルI/Oまたはネットワーク呼び出し
分析手順
フレーム時間分析
ステップ:
- Godotプロファイラーを有効化(Debug > Profiler)
- 通常のゲームプレイで60秒間実行
- 「Time (ms)」値が高い関数を特定
- 「Time %」でソートして最大の犯人を見つける
- フレームごと0.1msを超える関数を探す
解釈:
- <0.01ms:優秀
- 0.01-0.05ms:良好
- 0.05-0.1ms:許容範囲
-
0.1ms:最適化が必要
メモリ使用量検出
インジケーター:
- Memory モニターの継続的な増加
- ガベージコレクションスパイクの頻繁な発生
- ノード数の増加傾向
検出:
# プロセス関数内のオブジェクト作成を確認
rg "\.new\(\)|instantiate\(\)" -B 5 -A 2 --glob "*.gd" | \
rg -A 10 "func _process|func _physics_process"
ドローコール最適化
確認:
- Godotの「Monitors」タブ > 「Draw Calls」
- 各ユニークなマテリアル = 追加のドローコール
- GPUスキニング対CPUスキニング
最適化の対象:
- 2Dゲームの場合:<100ドローコール
- シンプルな3Dの場合:<500ドローコール
- テクスチャアトラスを使用してマテリアルスイッチを削減
物理パフォーマンス
危険信号:
- 衝突形状が複雑
- リジッドボディが多すぎる(>100)
- 複雑なポリゴン衝突
_physics_processで物理以外の処理を実行
最適化提案
ノード参照のキャッシュ化
変更前:
extends CharacterBody2D
func _process(delta):
get_node("UI/HealthBar").value = health
get_node("UI/ManaBar").value = mana
get_node("UI/LevelLabel").text = str(level)
変更後:
extends CharacterBody2D
@onready var health_bar = $UI/HealthBar
@onready var mana_bar = $UI/ManaBar
@onready var level_label = $UI/LevelLabel
func _process(delta):
health_bar.value = health
mana_bar.value = mana
level_label.text = str(level)
インパクト: フレームごとのノード参照を3つ削減(各約0.01ms)
初期化を _ready に移動
変更前:
func _process(delta):
var gravity = ProjectSettings.get("physics/2d/default_gravity")
velocity.y += gravity * delta
変更後:
var gravity
func _ready():
gravity = ProjectSettings.get("physics/2d/default_gravity")
func _process(delta):
velocity.y += gravity * delta
弾/パーティクル用オブジェクトプール
変更前:
func shoot():
var bullet = BulletScene.instantiate()
bullet.position = global_position
get_parent().add_child(bullet)
変更後:
var bullet_pool: Array[Bullet] = []
func _ready():
# 弾を事前インスタンス化
for i in range(50):
var bullet = BulletScene.instantiate()
bullet.hide()
bullet_pool.append(bullet)
get_parent().add_child(bullet)
func shoot():
for bullet in bullet_pool:
if not bullet.visible:
bullet.position = global_position
bullet.show()
bullet.activate()
return
インパクト: ゲームプレイ中のインスタンス化オーバーヘッドを排除
シグナルベースの更新
変更前:
func _process(delta):
# 毎フレームヘルスが変化したかチェック
if health != previous_health:
update_health_bar()
previous_health = health
変更後:
signal health_changed(new_health)
var health = 100:
set(value):
if health != value:
health = value
health_changed.emit(health)
func _ready():
health_changed.connect(update_health_bar)
配列操作のバッチ化
変更前:
func _process(delta):
for enemy in enemies:
if enemy.position.distance_to(player.position) < 100:
enemy.target_player()
変更後:
var check_timer = 0.0
const CHECK_INTERVAL = 0.1 # 60回ではなく10回/秒チェック
func _process(delta):
check_timer += delta
if check_timer >= CHECK_INTERVAL:
check_timer = 0
update_enemy_targets()
func update_enemy_targets():
for enemy in enemies:
if enemy.position.distance_to(player.position) < 100:
enemy.target_player()
Godot プロファイラー統合
プロファイラーを有効にする
- Debug > Start with Profiler でゲームを実行
- またはゲーム実行中にボトムパネルの「Profiler」タブをクリック
- 特定のモニターを有効化:
- CPU Time
- Function Time
- Node Count
- Memory
- Draw Calls
監視する主要メトリクス
フレーム時間(ms):
- フレームごとの総時間を表示
- 目標:60 FPSで<16.67ms、30 FPSで<33.33ms
- スパイクはカクつきを示す
関数の内訳:
- 時間でソートされたすべての関数をリストアップ
_process、_physics_process、_drawを探す- 関数名をクリックして呼び出し元を表示
メモリモニター:
- 継続的な増加を監視
- 急激なスパイクはアロケーションを示す
- プラトー後の低下 = ガベージコレクション
プロファイリング作業フロー
1. ベースラインを確立(最適化前)
└─ 60秒間プロファイラーデータを記録
2. 最大の時間消費者トップ3を特定
└─ プロファイラーで「Time %」でソート
3. 最適化を適用
└─ 上記のパターンを使用
4. 改善を検証
└─ 再度プロファイル、メトリクスを比較
└─ フレーム時間が減少したことを確認
5. 次のボトルネックについて繰り返す
例
例1:UI コントローラーの最適化
問題:
# ui_controller.gd
extends Control
func _process(delta):
# 毎フレーム呼び出される - 5つのノード参照!
get_node("HealthBar").value = player.health
get_node("ManaBar").value = player.mana
get_node("LevelLabel").text = "Level: " + str(player.level)
get_node("XpBar").value = player.xp
get_node("GoldLabel").text = "Gold: " + str(player.gold)
プロファイラー出力:
Function | Time (ms) | Time %
----------------------------------------
_process | 0.18 | 12.3%
get_node | 0.15 | 10.2% (x5)
最適化済み:
# ui_controller.gd
extends Control
@onready var health_bar = $HealthBar
@onready var mana_bar = $ManaBar
@onready var level_label = $LevelLabel
@onready var xp_bar = $XpBar
@onready var gold_label = $GoldLabel
func _ready():
# ポーリングの代わりにプレイヤーシグナルに接続
player.health_changed.connect(_on_health_changed)
player.mana_changed.connect(_on_mana_changed)
player.leveled_up.connect(_on_leveled_up)
player.xp_changed.connect(_on_xp_changed)
player.gold_changed.connect(_on_gold_changed)
func _on_health_changed(value): health_bar.value = value
func _on_mana_changed(value): mana_bar.value = value
func _on_leveled_up(level): level_label.text = "Level: " + str(level)
func _on_xp_changed(value): xp_bar.value = value
func _on_gold_changed(value): gold_label.text = "Gold: " + str(value)
結果:
Function | Time (ms) | Time %
----------------------------------------
_process | 0.00 | 0.0% (削除!)
_on_health_changed| 0.01 | 0.7% (イベント駆動)
例2:敵スポーナーの修正
問題:
# spawner.gd
extends Node2D
func _process(delta):
if enemies.size() < max_enemies:
var enemy = EnemyScene.instantiate() # メモリの浪費!
enemy.position = random_position()
add_child(enemy)
enemies.append(enemy)
メモリリーク: プーリングなしの継続的なインスタンス化
最適化済み:
# spawner.gd
extends Node2D
var spawn_timer = 0.0
const SPAWN_RATE = 2.0 # 2秒ごとにスポーン確認
func _process(delta):
spawn_timer += delta
if spawn_timer >= SPAWN_RATE:
spawn_timer = 0
try_spawn()
func try_spawn():
if enemies.size() < max_enemies:
spawn_enemy()
func spawn_enemy():
# 頻繁にスポーンされる敵にはオブジェクトプールを検討
var enemy = EnemyScene.instantiate()
enemy.position = random_position()
add_child(enemy)
enemies.append(enemy)
追加改善: 頻繁にスポーンされる敵にはオブジェクトプーリングを使用
例3:物理最適化
問題:
# player.gd
extends CharacterBody2D
func _physics_process(delta):
# 毎物理フレーム重い計算
var nearby = get_tree().get_nodes_in_group("enemies")
for enemy in nearby:
if global_position.distance_to(enemy.global_position) < detection_radius:
enemy.set_target(self)
# 物理処理を実行
velocity.y += gravity * delta
move_and_slide()
プロファイラー出力:
Function | Time (ms) | Time %
--------------------------------------------
_physics_process | 0.45 | 28.5%
get_nodes_in_group | 0.25 | 15.8%
distance_to | 0.12 | 7.6%
最適化済み:
# player.gd
extends CharacterBody2D
var detection_timer = 0.0
const DETECTION_RATE = 0.2 # 1秒ごとに5回
func _physics_process(delta):
# 物理とAIを分離
velocity.y += gravity * delta
move_and_slide()
# 検出頻度を低下
detection_timer += delta
if detection_timer >= DETECTION_RATE:
detection_timer = 0
update_enemy_detection()
func update_enemy_detection():
var nearby = get_tree().get_nodes_in_group("enemies")
for enemy in nearby:
if global_position.distance_to(enemy.global_position) < detection_radius:
enemy.set_target(self)
結果: 物理フレーム時間が約60%削減
成功基準
パフォーマンス最適化が成功したと判断されるのは以下の場合です:
定量的メトリクス
- 対象関数のフレーム時間が50%以上削減
-
_process関数の行数が15行未満 -
_processまたは_physics_process内にget_node()呼び出しがない - フレームコールバック内に
.new()または.instantiate()呼び出しがない - プロファイラーのトップ3関数の「Time %」の合計が30%未満
- フレーム時間が60 FPS目標で16.67ms未満
- メモリ使用量が安定(継続的な増加がない)
定性的チェック
- ノード参照が
_ready()または@onreadyでキャッシュされている - 複雑なロジックがシグナルまたはタイマーに移動されている
- 頻繁なインスタンス化にはオブジェクトプーリングが使用されている
-
_physics_processに物理関連のコードのみが含まれている - プロファイラーデータがベースラインと比較して改善を示している
検証ステップ
- 最適化前にプロファイル: ベースラインメトリクスを記録
- 最適化を適用: 対象となる変更を実施
- 最適化後にプロファイル: メトリクスを比較
- ゲーム内で検証: 実際のゲームプレイがより滑らかに感じることを確認
- エッジケースを確認: 最適化がすべてのシナリオで機能することを確認
クイックリファレンス
| パターン | 検出 | 修正 | インパクト |
|---|---|---|---|
| _process内の get_node() | _process後のget_nodeを検索 | @onreadyでキャッシュ | 呼び出しごと~0.01ms |
| _process内の .new() | フレーム関数内の.new()を検索 | オブジェクトプーリングを使用 | GC圧力を排除 |
| 負荷の高い _process | 行数が15行以上 | シグナル/タイマーに移動 | フレーム当たりの負荷を削減 |
| 物理 + AI | _physics_process内のAI | タイマーで分離 | 5~10倍削減 |
| キャッシュなしの設定 | ループ内のProjectSettings.get() | _readyでキャッシュ | ワンタイム設定 |
よくある間違い
間違い:「後で最適化します」
問題: 技術的負債が蓄積し、後で修正するのが難しくなる 修正: 早期かつ頻繁にプロファイルして、ボトルネックが現れたら修正
間違い:早すぎる最適化
問題: ボトルネックではないコードを最適化 修正: 必ず最初にプロファイルして、時間消費の多い関数に注力
間違い:マイクロ最適化
問題: 0.001msを節約するのに数時間費やす 修正: 0.1ms以上の関数を対象にして、それ以外は無視
間違い:改善を検証しない
問題: 最適化が機能したと測定せずに仮定 修正: 必ず前後でプロファイラーを実行
間違い:リリースビルドのみを最適化
問題: デバッグビルドはリリースビルドと異なるパフォーマンス特性 修正: 正確なデータを得るためにリリースビルドをプロファイル
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- majiayu000
- ライセンス
- MIT
- 最終更新
- 2026/5/9
Source: https://github.com/majiayu000/claude-skill-registry-data / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。