← 블로그로

Godot 4: 프롬프트에 시그널 규칙을 넣어 MCP가 체력 바를 연결하게 하세요

Cursor에서 Godot MCP에 요청하기 전에 health_changed 시그널 계약을 정의하여 HUD가 깊은 get_node 경로 없이 연결되도록 하세요—프롬프트 템플릿, GDScript 샘플, 안티패턴, Play 승인 포함.

게시일
  • Godot MCP
  • Godot 4
  • signals
  • HUD
  • GDScript
  • Cursor
  • AI game development
  • tutorial

Godot에서 “AI가 내 씬을 망쳤다”는 가장 흔한 실패는 문법 오류가 아니라, 하드코딩된 체력 바 스크립트입니다:

$"/root/Game/Player/HitPoints".hp  # 또는 get_node("../../Player/...")

트리를 다시 부모-자식 관계로 바꾸거나 Player를 자식 씬으로 인스턴스화하면 그 경로는 죽습니다. Godot MCP는 프롬프트를 충실히 따릅니다—하드 경로를 금지하지 않으면 모델은 계속 그런 경로를 만들어냅니다.

이 글은 한 가지 작업만 합니다: Cursor(또는 Claude Code)에서 시그널 계약을 먼저 명시하고, MCP가 플레이어 체력을 HUD 바에 연결하도록 하는 것입니다. 설치 안내나 전체 플랫포머 빌드는 다루지 않습니다.

단계수행할 작업
1시그널, 인자, 발신자/수신자 이름 지정
2제약 조건과 함께 MCP에 스크립트 및 연결 편집 요청
3Play에서 승인; 안티패턴을 사용하여 프롬프트 수정

관련 자료:

배경: 씬 구성 — 자식 씬은 깊은 경로 대신 시그널/export를 노출해야 합니다.

시그널 규칙이 프롬프트에 있어야 하는 이유

접근 방식단기적재구성 후
get_node("../Player/Health")빠름종종 깨짐
@onready var bar = %HealthBar (고유 이름)한 씬에서는 괜찮음씬 간에는 여전히 취약
signal health_changed(current, max) + connect두 줄 추가HUD와 Player 트리가 독립적으로 변경 가능

MCP는 제약 조건을 반복하지 않는 한 지속적인 “아키텍처 메모리”가 없습니다. 붙여넣기 가능한 계약 블록은 모든 diff에서 하드 경로를 정리하는 것보다 저렴합니다.

최소 노드 구조

전체 UI는 필요 없습니다. v1에는 다음만 필요합니다:

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

이름 지정:

노드권장 이름참고
Player 루트Player체력 스크립트 소유
HUD 레이어HUDCanvasLayer
바HealthBarProgressBar; 절대 HP 또는 max_value = 1 비율

HUD가 AI Studio에서 생성된 경우, 배선 전에 안정적인 영어 ID로 이름을 바꾸세요.

시그널 계약 (모든 프롬프트 상단에 붙여넣기)

이것을 프로젝트 규칙으로 취급하세요:

[시그널 계약 — 체력]
1. Player의 체력 스크립트만 다음 시그널을 방출합니다:
   signal health_changed(current: float, max_health: float)
   과거형/완료형 이름; 인자는 항상 (current, max_health).
2. HUD 바 스크립트는 해당 시그널만 연결하고 ProgressBar를 업데이트합니다; HUD에서 Player로 get_node 금지.
3. 금지: 깊은 크로스 씬 NodePath, Autoload를 체력 버스로 사용(여기서는 불필요), _process에서 Player 폴링.
4. 에디터 시그널 연결 또는 HUD의 @export Node/NodePath 후 .connect 선호 — 절대 "/root/..." 하드코딩 금지.
5. 데미지와 힐은 Player의 값만 변경하고 시그널을 방출합니다; UI는 HP를 변경하지 않습니다.

이 블록이 이 글의 핵심입니다: 구현 전에 계약.

1단계: 읽기 전용 스모크 테스트

현재 편집 중인 씬의 루트 아래 자식 이름을 나열하세요. 파일이나 노드를 수정하지 마세요.

Player와 HUD(또는 해당하는 것)가 보일 때만 쓰기 작업을 진행하세요.

2단계: Player만 방출

계약 뒤에 추가:

[시그널 계약 — 체력] (전체 블록 붙여넣기)

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를 한 번 방출
- take_damage(amount), heal(amount); 0..max_health로 클램프; 변경 후마다 방출
- 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는 건드리지 마세요.

스크립트가 Player 루트에 있다면 extends Node를 해당 타입(예: CharacterBody2D)으로 변경하거나, PlayerHealth를 자식 Health로 만드세요—프롬프트에서 하나를 선택하여 MCP가 마운트 지점을 추측하지 않게 하세요.

3단계: HUD가 export 참조를 통해 구독

[시그널 계약 — 체력] (전체 블록 붙여넣기)

res://scripts/hud_health.gd를 HUD 또는 HealthBar에 생성:
- @export var player_health: PlayerHealth
- @onready var bar: ProgressBar = $HealthBar  # 스크립트가 HUD에 있을 때만; 경로는 HUD 하위 트리 내부 유지
- _ready: assert 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
  (또는 비율: max_value = 1; value = current / max_health)
- 금지: Player로 get_node; 금지: /root 절대 경로
그런 다음 에디터에서 export player_health를 Player의 PlayerHealth(또는 Health 자식)에 할당. 씬 저장.

참조:

extends CanvasLayer

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

func _ready() -> void:
	assert(player_health != null, "인스펙터에서 PlayerHealth를 할당하세요")
	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: export 참조가 유지되면 바는 여전히 업데이트됨—이것이 하드 경로보다 나은 점입니다

재부모화 후 깨지면 빈 export를 수정하세요—MCP에게 “또 다른 get_node”를 요청하지 마세요.

승인 프롬프트:

Play 오류에서 진단: 시그널이 발생하지 않으면 connect를 확인; player_health가 null이면 export만 수정. 절대 get_node 절대 경로로 "수정"하지 마세요. 먼저 진단하고, 제 OK를 기다린 후 편집하세요.

안티패턴: 프롬프트를 변경하고, 병합하지 마세요

안티패턴왜 해로운가프롬프트에 추가
get_node("/root/Game/Player")루트 경로 변경”No /root 또는 절대 NodePath”
get_parent().get_node("Player")형제 결합”HUD는 Player 형제 레이아웃을 가정하지 않아야 함”
Autoload GlobalHealth.hp전역 쓰레기통”이 작업에 새 Autoload 금지”
HUD _process에서 Player 찾기느리고 취약”폴링 금지; 시그널만”
시그널 이름 update / set_hp모호하고 충돌 위험”시그널은 health_changed여야 함”

수동 작성은 고성능 이동 및 복잡한 전투 수학에서 여전히 우세합니다; 체력 바 배선은 보일러플레이트이므로 계약된 MCP 프롬프트가 일반적으로 더 저렴합니다. Godot MCP vs 수동 스크립팅 참조.

재사용 가능한 “계약 + 작업” 구조

마나/보스 바에 이름을 바꿔 사용하세요:

[시그널 계약]
- 발신자: <노드/스크립트>
- signal <이름>(...)
- 수신자: <HUD 메서드>
- 금지: 절대 NodePath, Autoload 버스, _process 폴링

[작업]
1. …
2. …
3. 씬 저장; 새 시그널과 연결 방법(에디터 / .connect) 나열

요약

  1. 하드 경로는 프롬프트 구멍이지, “MCP가 Godot을 못함”이 아닙니다
  2. 편집을 요청하기 전에 health_changed(current, max_health)를 붙여넣으세요
  3. Player는 방출만; HUD는 연결하고 ProgressBar만 업데이트
  4. 객체 간 연결은 @export 또는 에디터 시그널 연결 사용—/root/... 금지
  5. Player를 재부모화하고 바를 확인하여 승인—“스크립트 생성 OK”가 아니라

이제 붙여넣기 가능한 Godot MCP 프롬프트 규칙 세트가 생겼습니다; 인벤토리, 퀘스트 로그, 킬 카운터도 같은 패턴을 재사용할 수 있습니다.

이 글도 추천합니다