← ブログへ

Godot 4:シグナル規約をプロンプトに書いて MCP で HP バーを配線する

Cursor で Godot MCP に指示する前に health_changed シグナル契約を定義し、深い get_node パスなしで HUD を接続する——プロンプトテンプレート、GDScript サンプル、アンチパターン、Play 検収。

公開日
  • Godot MCP
  • Godot 4
  • シグナル
  • HUD
  • GDScript
  • Cursor
  • AI game development
  • tutorial

Godot で最も多い「AI が直したら壊れた」原因は構文エラーではなく、HP バースクリプトにハードコードされたパスです:

$"/root/Game/Player/HitPoints".hp  # または get_node("../../Player/...")

シーンを組み替えたり Player を子シーンとしてインスタンス化すると、そのパスは死にます。Godot MCP はプロンプトに忠実に従います——ハードパスを禁止しなければ、モデルは作り続けます。

この記事は一つのことだけ扱います:Cursor(または Claude Code)で先にシグナル契約を示し、MCP にプレイヤー HP を HUD バーへ配線させること。インストール手順やフルプラットフォーマー構築は省略します。

ステップやること
1シグナル名・引数・発信者 / 受信者を決める
2制約付きプロンプトで MCP にスクリプトと接続を編集させる
3Play で検収;アンチパターンを見てプロンプトを直す

関連記事:

公式背景:Scene organization — 子シーンは深いパスではなくシグナル / エクスポートで外に公開する。

なぜ「シグナル規約」をプロンプトに書くのか

やり方短期シーン再構成後
get_node("../Player/Health")速いよく壊れる
@onready var bar = %HealthBar(ユニーク名)同一シーンなら可シーン跨ぎでは脆い
signal health_changed(current, max) + connect2 行多いHUD と Player のツリーを独立して変更可

MCP に永続的な「アーキテクチャ記憶」はなく、毎回制約を書かない限りありません。貼り付け可能な契約ブロックは、diff からハードパスを掃除するより安いです。

まず最小ノード骨格を決める

フル UI は不要。v1 にはこれだけ:

Game (Node2D / Node)
├── Player (CharacterBody2D)  ← player_health.gd
│   └── ...
└── HUD (CanvasLayer)
    └── HealthBar (ProgressBar または TextureProgressBar)  ← hud_health.gd

命名:

ノード推奨名備考
プレイヤー根PlayerHP スクリプトを付ける
HUD 層HUDCanvasLayer
バーHealthBarProgressBar;絶対 HP か max_value = 1 の比率

HUD が AI Studio から来た場合、配線前に安定した英語 ID にリネームしてください。

シグナル契約(毎回のプロンプト先頭に貼る)

以下をプロジェクト規約として扱い、指示の先頭に貼ります:

【シグナル契約 — HP】
1. Player 上の HP スクリプトのみが発信:
   signal health_changed(current: float, max_health: float)
   過去形 / 完了の命名;引数は常に (current, max_health)。
2. HUD バースクリプトはそのシグナルにだけ接続し ProgressBar を更新;HUD から Player へ get_node 禁止。
3. 禁止:シーン跨ぎの深い NodePath、Autoload を HP バスに(本タスクでは不要)、_process で Player をポーリング。
4. 接続はエディタのシグナル接続を優先、または HUD スクリプトで @export NodePath / エクスポート Node 参照で Player を指して connect;"/root/..." のハードコード禁止。
5. ダメージ・回復は Player 側の値だけ変更して emit;UI は HP を変更しない。

このブロックが本記事の核心です:実装より先に規約。

ステップ 1:読み取り専用スモークテスト(MCP がシーンを見えるか)

現在編集中のシーンのルート直下の子ノード名を一覧表示。ファイルやノードは一切変更しない。

Player と HUD(または同等名)が見えてから書き込みます。

ステップ 2:MCP に Player で「発信のみ」を実装させる

プロンプト例(契約の後に続ける):

【シグナル契約 — HP】(上記を全文貼り付け)

res://scripts/player_health.gd(なければ作成)を Player にアタッチ:
- @export var max_health: float = 100.0
- var current: float
- signal health_changed(current: float, max_health: float)
- _ready で current = max_health、health_changed を 1 回 emit
- take_damage(amount)、heal(amount);0..max_health にクランプ;変更のたびに emit
- HUD を参照しない;UI へ get_node しない
保存後、シグナル引数の型を報告。

参考実装(モデル生成後にレビュー可):

extends Node
class_name PlayerHealth

signal health_changed(current: float, max_health: float)

@export var max_health: float = 100.0
var current: float

func _ready() -> void:
	current = max_health
	health_changed.emit(current, max_health)

func take_damage(amount: float) -> void:
	if amount <= 0.0 or current <= 0.0:
		return
	current = maxf(0.0, current - amount)
	health_changed.emit(current, max_health)

func heal(amount: float) -> void:
	if amount <= 0.0 or current <= 0.0:
		return
	current = minf(max_health, current + amount)
	health_changed.emit(current, max_health)

一時ダメージキー(本番では削除):

Player に一時的な debug_damage.gd を追加:H キーで同一ノード(または親)の PlayerHealth.take_damage(10) を呼ぶ。配線検収専用。HUD は触らない。

HP スクリプトを Player 根に直接付ける場合、extends Node を根の型に合わせる(例:CharacterBody2D)、または PlayerHealth を子ノード Health にする——どちらかをプロンプトに明記し、MCP にマウント位置を推測させない。

ステップ 3:HUD は購読のみ、エクスポート参照で Player を見つける

【シグナル契約 — HP】(全文貼り付け)

res://scripts/hud_health.gd を HUD または HealthBar に作成:
- @export var player_health: PlayerHealth   # または Node を as PlayerHealth
- @onready var bar: ProgressBar = $HealthBar  # スクリプトが HUD 上の場合;パスは HUD サブツリー内のみ
- _ready:player_health != null をアサート;player_health.health_changed.connect(_on_health_changed)
- _on_health_changed(current, max_health):bar.max_value = max_health;bar.value = current
  (0..1 比率のみ:bar.max_value = 1;bar.value = current / max_health)
- Player へ get_node 禁止;/root 絶対パス禁止
次に:エディタでエクスポート player_health を Player の PlayerHealth(または Health 子)に割り当て。シーンを保存。

参考実装:

extends CanvasLayer

@export var player_health: PlayerHealth
@onready var bar: ProgressBar = $HealthBar

func _ready() -> void:
	assert(player_health != null, "Assign PlayerHealth in the inspector")
	player_health.health_changed.connect(_on_health_changed)

func _on_health_changed(current: float, max_health: float) -> void:
	bar.max_value = max_health
	bar.value = current

HUD 内部の $HealthBar は問題なし——同一サブツリー。禁止なのは Player へ跨る深いパスです。

エディタの「ノード → シグナルを接続」も MCP で可。プロンプトに明記:

エディタでシグナル接続:Player(PlayerHealth)の health_changed → HUD スクリプトの _on_health_changed。NodePath 文字列を手書きしない。

ステップ 4:Play 検収チェックリスト

  1. シーン入場時:バー満タン(または正しい比率)
  2. H 押下:bar.value が下がる
  3. 0 まで:バー空;死亡ロジックがあっても UI が current を書き換えない
  4. heal 後:バーが上がる
  5. Player をより深い子(例:Entities)にリペアレントして再 Play:エクスポート参照が残っていればバーは更新される——ハードパスに対する核心の利点

リペアレント後に切れたら、空のエクスポートを直す——MCP に「別の get_node」を書かせない。

検収プロンプト:

Play モードのエラーから診断:シグナルが発火しないなら connect を確認;player_health が null ならエクスポート参照だけ修正。「修正」に絶対 get_node パスを使わない。先に診断し、編集前に私の OK を待つ。

アンチパターン:これを見たらプロンプトを直す、マージしない

アンチパターン問題プロンプトへの追加
get_node("/root/Game/Player")ルートパス変更で壊れる「/root と絶対 NodePath 禁止」
get_parent().get_node("Player")兄弟関係への依存「HUD は Player との相対階層を仮定しない」
Autoload GlobalHealth.hpグローバル状態はテスト困難「本タスクで新規 Autoload 禁止」
HUD の _process で毎フレーム Player 探索遅く脆い「ポーリング禁止;シグナルのみ」
シグナル名 update / set_hp曖昧、衝突しやすい「シグナル名は health_changed 必須」

ホットパスの移動や複雑な戦闘計算は手書きが依然有利;HP バー配線はボイラープレートで、契約付き MCP プロンプトの方が通常安い。詳細は Godot MCP vs 手書きスクリプト。

再利用可能な「契約 + タスク」プロンプト骨格

マナバー、ボスバーに差し替えるときはシグナル名とノード名だけ変更:

【シグナル契約】
- 発信者:<ノード/スクリプト>
- signal <name>(...)
- 受信者:<HUD スクリプトメソッド>
- 禁止:絶対 NodePath、Autoload バス、_process ポーリング

【タスク】
1. …
2. …
3. シーンを保存;新規シグナルと接続方法を列挙(エディタ接続 / .connect)

まとめ

  1. ハードパスは プロンプトの穴であり、「MCP が Godot を知らない」わけではない
  2. 編集依頼の前に health_changed(current, max_health) 契約を貼る
  3. Player は emit のみ;HUD は connect + ProgressBar 更新のみ
  4. オブジェクト間は @export 参照かエディタシグナル接続——/root/... ではない
  5. 「スクリプト生成成功」ではなく「Player の親を組み替えてもダメージが反映される」で検収

これで貼り付け可能な Godot MCP プロンプト規約ができました。インベントリ、クエストログ、キルカウンターも同じパターンで拡張できます。

こちらの記事もおすすめです