← Retour au blog

Godot 4 : Mettez les règles de signaux dans votre invite pour que MCP câble la barre de vie

Avant de demander à Godot MCP dans Cursor, définissez un contrat de signal health_changed pour que le HUD se connecte sans chemins get_node profonds—modèles d'invite, exemples GDScript, anti-patterns et acceptation dans Play.

Publié le
  • Godot MCP
  • Godot 4
  • signals
  • HUD
  • GDScript
  • Cursor
  • AI game development
  • tutorial

L’échec « l’IA a cassé ma scène » le plus courant dans Godot n’est pas une erreur de syntaxe—c’est un script de barre de vie qui code en dur :

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

Réorganisez l’arbre ou instanciez Player comme scène enfant, et ce chemin meurt. Godot MCP suit votre invite fidèlement—si vous n’interdisez pas les chemins durs, le modèle continuera d’en inventer.

Ce post fait une seule chose : dans Cursor (ou Claude Code), énoncez d’abord un contrat de signal, puis laissez MCP câbler la santé du joueur à la barre HUD. Pas de tutoriel d’installation, pas de construction complète de plateforme.

ÉtapeCe que vous faites
1Nommez le signal, les arguments, l’émetteur / le récepteur
2Invitez MCP avec des contraintes pour modifier les scripts et les connexions
3Acceptez dans Play ; corrigez l’invite en utilisant les anti-patterns

Liens connexes :

Contexte : Organisation des scènes — les scènes enfants doivent exposer des signaux / exports, pas des chemins profonds.

Pourquoi les règles de signaux appartiennent à l’invite

ApprocheCourt termeAprès réorganisation
get_node("../Player/Health")RapideSe casse souvent
@onready var bar = %HealthBar (nom unique)Bien dans une scèneToujours fragile entre scènes
signal health_changed(current, max) + connectDeux lignes supplémentairesLes arbres HUD et Player peuvent changer indépendamment

MCP n’a pas de « mémoire d’architecture » durable à moins que vous ne répétiez les contraintes. Un bloc de contrat copiable est moins cher que de nettoyer les chemins durs dans chaque diff.

Squelette de nœuds minimal

Vous n’avez pas besoin d’une UI complète. La v1 n’a besoin que de :

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

Nommage :

NœudNom suggéréNotes
Racine PlayerPlayerPossède le script de santé
Couche HUDHUDCanvasLayer
BarreHealthBarProgressBar ; HP absolu ou max_value = 1 ratio

Si le HUD provient de AI Studio, renommez-le en identifiants anglais stables avant le câblage.

Contrat de signal (à coller en haut de chaque invite)

Traitez cela comme une loi de projet :

[Contrat de signal — santé]
1. Seul le script de santé sur Player émet :
   signal health_changed(current: float, max_health: float)
   Nommage au passé / complété ; arguments toujours (current, max_health).
2. Le script de la barre HUD ne fait que connecter ce signal et mettre à jour ProgressBar ; pas de get_node vers Player depuis HUD.
3. Interdit : NodePaths profonds entre scènes, Autoload comme bus de santé (pas nécessaire ici), polling de Player dans _process.
4. Préférez les connexions de signaux dans l'éditeur, ou @export Node / NodePath sur le HUD puis .connect — ne codez jamais en dur "/root/...".
5. Les dégâts et les soins ne modifient que les valeurs sur Player et émettent ; l'UI ne mute jamais les HP.

Ce bloc est le but de cet article : contrat avant implémentation.

Étape 1 : Test de fumée en lecture seule

Listez les noms des enfants sous la racine de la scène actuellement éditée. Ne modifiez aucun fichier ni nœud.

N’écrivez qu’une fois que vous voyez Player et HUD (ou vos équivalents).

Étape 2 : Player émet uniquement

Ajoutez après le contrat :

[Contrat de signal — santé] (collez le bloc complet)

Dans res://scripts/player_health.gd (créez-le s'il manque) et attachez-le à Player :
- @export var max_health: float = 100.0
- var current: float
- signal health_changed(current: float, max_health: float)
- Dans _ready : current = max_health ; émettez health_changed une fois
- take_damage(amount), heal(amount) ; clampez 0..max_health ; émettez après chaque changement
- Ne référencez pas HUD ; ne faites pas de get_node dans l'UI
Enregistrez et rapportez les types d'arguments du signal.

Implémentation de référence :

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)

Touche de dégâts temporaire (à supprimer plus tard) :

Ajoutez un debug_damage.gd temporaire sur Player : appuyer sur H appelle PlayerHealth.take_damage(10) sur le même nœud (ou parent). Uniquement pour l'acceptation du câblage. Ne touchez pas au HUD.

Si le script se trouve sur la racine Player, changez extends Node pour correspondre (par exemple CharacterBody2D), ou faites de PlayerHealth un enfant Health—choisissez-en un dans l’invite pour que MCP ne devine pas le point de montage.

Étape 3 : Le HUD s’abonne via une référence exportée

[Contrat de signal — santé] (collez le bloc complet)

Créez res://scripts/hud_health.gd sur HUD ou HealthBar :
- @export var player_health: PlayerHealth
- @onready var bar: ProgressBar = $HealthBar  # uniquement si le script est sur HUD ; le chemin reste dans le sous-arbre 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
  (ou ratio : max_value = 1 ; value = current / max_health)
- Interdit : get_node vers Player ; interdit : chemins absolus /root
Ensuite, assignez le player_health exporté au PlayerHealth de Player (ou à l'enfant Health) dans l'éditeur. Enregistrez la scène.

Référence :

extends CanvasLayer

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

func _ready() -> void:
	assert(player_health != null, "Assignez PlayerHealth dans l'inspecteur")
	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

$HealthBar à l’intérieur du HUD est acceptable—même sous-arbre. Interdit est un chemin profond vers Player.

La connexion via l’éditeur « Nœud → Connecter un signal » via MCP est également acceptable :

Connectez via l'éditeur : Player (PlayerHealth) health_changed → script HUD _on_health_changed. N'écrivez pas de chaînes NodePath à la main.

Étape 4 : Liste de contrôle d’acceptation dans Play

  1. À l’entrée : barre pleine (ou ratio correct)
  2. Appuyez sur H : bar.value diminue
  3. À 0 : barre vide ; l’UI ne doit pas écrire current
  4. Après heal : la barre monte
  5. Re-parentez Player sous par exemple Entities et rejouez : la barre se met toujours à jour si la référence exportée reste—c’est la victoire par rapport aux chemins durs

Si cela casse après le re-parentage, corrigez l’export vide—ne demandez pas à MCP un « autre get_node ».

Invite d’acceptation :

Diagnostiquez à partir des erreurs de Play : si le signal ne se déclenche jamais, vérifiez la connexion ; si player_health est null, corrigez uniquement l'export. Ne « corrigez » pas avec des chemins get_node absolus. Diagnostiquez d'abord ; attendez mon OK avant de modifier.

Anti-patterns : changez l’invite, ne fusionnez pas

Anti-patternPourquoi c’est nuisibleAjoutez à l’invite
get_node("/root/Game/Player")Instabilité du chemin racine« Pas de /root ni de NodePath absolu »
get_parent().get_node("Player")Couplage entre frères« Le HUD ne doit pas supposer la disposition des frères de Player »
Autoload GlobalHealth.hpTiroir global« Pas de nouvel Autoload pour cette tâche »
HUD _process cherchant PlayerLent et fragile« Pas de polling ; signaux uniquement »
Signal nommé update / set_hpVague, sujet aux collisions« Le signal doit être health_changed »

L’écriture manuelle reste gagnante pour les mouvements à chemin chaud et les maths de combat complexes ; le câblage de barre de vie est du boilerplate où une invite MCP contractée est généralement moins chère. Voir Godot MCP vs script manuel.

Squelette réutilisable « contrat + tâche »

Échangez les noms pour la mana / les barres de boss :

[Contrat de signal]
- Émetteur : <nœud/script>
- signal <nom>(...)
- Récepteur : <méthode HUD>
- Interdit : NodePath absolu, bus Autoload, polling _process

[Tâche]
1. …
2. …
3. Enregistrez la scène ; listez les nouveaux signaux et comment ils se connectent (éditeur / .connect)

Résumé

  1. Les chemins durs sont un trou d’invite, pas « MCP ne sait pas faire Godot »
  2. Collez health_changed(current, max_health) avant de demander des modifications
  3. Player émet uniquement ; HUD connecte et met à jour ProgressBar uniquement
  4. Les liens entre objets utilisent @export ou la connexion de signal de l’éditeur—pas /root/...
  5. Acceptez en re-parentant Player et en regardant la barre—pas par « script généré OK »

Vous avez maintenant un ensemble de règles d’invite Godot MCP copiable ; inventaire, journal de quêtes et compteurs de kills peuvent réutiliser le même modèle.

D’autres guides qui pourraient vous intéresser