Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:08:19 +08:00
commit db818a4853
17 changed files with 4551 additions and 0 deletions

347
commands/debug.md Normal file
View File

@@ -0,0 +1,347 @@
---
allowed_tools:
- mcp__godot__get_debug_output
- Skill
---
View enhanced debug output from the running Godot project with error highlighting and filtering.
# Process
1. **Check if project is running**
- Use mcp__godot__get_debug_output to fetch current debug output
2. **Parse and enhance the output:**
- Identify error types (ERROR, WARNING, INFO)
- Parse stack traces
- Extract file paths and line numbers
- Highlight important information
3. **Display formatted output:**
## Error Formatting
Present errors with the following enhanced format:
### ERROR messages
```
[ERROR] <error_message>
Location: <file_path>:<line_number>
Function: <function_name>
Details: <additional_context>
Stack Trace:
at <function> (<file>:<line>)
at <function> (<file>:<line>)
```
### WARNING messages
```
[WARNING] <warning_message>
Location: <file_path>:<line_number>
Context: <relevant_context>
```
### INFO/DEBUG messages
```
[INFO] <message>
```
## Common Error Patterns to Recognize
### 1. Null Reference Errors
Pattern: `Attempt to call function .* in base 'null instance'`
Response:
```
[ERROR] Null Reference Error
Trying to call a method on a null object
Quick Fixes:
- Check if the object exists before calling methods
- Verify @onready node paths are correct
- Ensure nodes aren't freed before being accessed
Example fix:
if my_node != null:
my_node.some_method()
```
### 2. Node Not Found
Pattern: `Node not found: .*` or `get_node: .*not found`
Response:
```
[ERROR] Node Not Found
Path: <node_path>
Possible Causes:
- Node doesn't exist in the scene tree
- Incorrect node path (check capitalization)
- Node hasn't been added yet (timing issue)
Quick Fixes:
- Use get_node_or_null() to check if node exists
- Verify the node path in the scene tree
- Use @onready for scene tree references
```
### 3. Index Out of Range
Pattern: `Index .* out of range`
Response:
```
[ERROR] Array Index Out of Bounds
Index: <index_value>
Array Size: <size>
Quick Fixes:
- Check array size before accessing: if index < array.size()
- Use range-based loops instead of index access
- Validate index is not negative
```
### 4. Type Errors
Pattern: `Invalid operands .* and .* in operator`
Response:
```
[ERROR] Type Mismatch
Cannot perform operation between incompatible types
Quick Fixes:
- Ensure variables are initialized
- Check for null values before operations
- Verify type annotations match actual types
```
### 5. Signal Errors
Pattern: `Signal .* already connected` or `Attempt to call an invalid function`
Response:
```
[ERROR] Signal Connection Error
Quick Fixes:
- Check if signal is already connected before connecting
- Verify method signature matches signal parameters
- Ensure method exists and is spelled correctly
```
## Enhanced Features
### Error Grouping
- Group repeated errors together
- Show count of occurrences
- Display first occurrence time
Example:
```
[ERROR] (x5) Null instance access
First occurred at: 14:32:15
Last occurred at: 14:32:23
See details with: /gd:debug --expand=<error_id>
```
### Filtering Options
Ask user if they want to filter output:
Question: "Would you like to filter the debug output?"
Header: "Filter"
Multi-select: false
Options:
- Show All: Display all messages (errors, warnings, info)
- Errors Only: Show only ERROR messages
- Errors + Warnings: Show errors and warnings
- Custom Filter: Filter by keyword
### Auto-suggestions
For each error, provide:
1. **Error Type**: Classification of the error
2. **Likely Cause**: Most common reason for this error
3. **Suggested Fix**: Code example or action to take
4. **Related Documentation**: Link to relevant Godot docs if applicable
Example output:
```
─────────────────────────────────────
[ERROR] Invalid get index 'health' (on base: 'null instance')
Location: res://scripts/player.gd:45
Function: take_damage()
Likely Cause:
The player node is null when trying to access the 'health' property.
This usually happens when:
- A node reference (@onready var) points to a non-existent node
- The node was freed/removed before this code runs
- The node path is incorrect
Suggested Fix:
# Add null check before accessing:
if player != null:
player.health -= damage
# Or verify node exists in _ready():
func _ready():
assert(player != null, "Player node not found!")
Related Code (player.gd:45):
45 | func take_damage(amount):
46 | player.health -= amount # ERROR HERE
47 | update_health_bar()
Next Steps:
1. Check that the player node exists in the scene tree
2. Verify the node path for @onready variables
3. Add null checks before accessing node properties
─────────────────────────────────────
```
## Watch Mode
Offer to enable watch mode:
"Would you like to enable watch mode? This will continuously monitor for new errors."
If yes:
- Poll mcp__godot__get_debug_output every 2 seconds
- Display new errors as they occur
- Highlight new errors in real-time
- User can press Ctrl+C or type 'stop' to exit watch mode
Example watch mode output:
```
[WATCH MODE] Monitoring debug output... (Press Ctrl+C to stop)
14:35:12 | [INFO] Game started
14:35:15 | [WARNING] Texture not found: res://sprites/missing.png
14:35:18 | [ERROR] Null instance access in enemy.gd:23
Press Enter to see error details, or type 'stop' to exit...
```
## Additional Commands
After showing debug output, offer helpful commands:
```
Debug output retrieved.
Helpful commands:
/gd:debug --errors-only Show only errors
/gd:debug --watch Enable watch mode
/gd:restart Restart the game
Need help fixing an error? Just ask! The godot-debugging skill can help interpret and fix these errors.
```
# Integration with godot-debugging Skill
After displaying errors, automatically activate the godot-debugging skill to provide:
- Detailed error explanations
- Code fixes
- Prevention strategies
# Example Full Output
```
Debug Output (Last 50 lines)
═══════════════════════════════════════
[INFO] Project initialized
[INFO] Scene loaded: res://scenes/main.tscn
[WARNING] Node has been removed from scene: Label
Location: res://scenes/ui/menu.tscn
Context: Attempting to access removed node
Note: This usually happens when you call queue_free() on a node
and then try to access it later in the same frame.
─────────────────────────────────────
[ERROR] Attempt to call function 'move_and_slide' in base 'null instance' on a null instance
Location: res://scripts/player.gd:67
Function: _physics_process(delta)
Stack Trace:
at CharacterBody2D._physics_process (res://scripts/player.gd:67)
at Node._propagate_process (core)
🔍 Analysis:
The player node (CharacterBody2D) is trying to call move_and_slide()
but the instance is null. This typically means:
- The player was freed but physics_process is still running
- The script is on a freed node
💡 Suggested Fix:
func _physics_process(delta):
if not is_instance_valid(self):
return
velocity.y += gravity * delta
move_and_slide()
─────────────────────────────────────
Summary:
• Total Messages: 24
• Errors: 3 (🔴)
• Warnings: 5 (🟡)
• Info: 16 (🔵)
Most Recent Error:
res://scripts/player.gd:67 - Null instance in _physics_process
═══════════════════════════════════════
Would you like me to help fix any of these errors?
```
# Error History Tracking
Keep track of errors across debug sessions:
- Store error fingerprints (file + line + error type)
- Track if error is new or recurring
- Show resolution history if previously fixed
Example:
```
[ERROR] (Recurring) Null instance access
This error has occurred 3 times in the last hour
Previously seen: 14:15, 14:22, 14:35
Note: This might indicate a deeper issue with your code structure.
Consider adding more robust null checks or restructuring your node references.
```
# Implementation Notes
1. **Parse debug output intelligently**
- Extract file paths as clickable links (if terminal supports it)
- Parse stack traces into structured format
- Identify error patterns automatically
2. **Color coding** (if terminal supports it)
- Red for errors
- Yellow for warnings
- Blue for info
- Gray for debug
3. **Smart filtering**
- Remember user's last filter choice
- Allow regex patterns for advanced filtering
- Group similar errors together
4. **Quick actions**
- Offer to open file at error line
- Suggest running godot-debugging skill
- Provide quick fix options
After displaying the enhanced debug output, remind the user:
"💡 Tip: I can help fix these errors! Just ask about any specific error and I'll provide a detailed explanation and fix using the godot-debugging skill."

28
commands/init-game.md Normal file
View File

@@ -0,0 +1,28 @@
---
description: Initialize a new Godot game project with interactive planning
allowed-tools:
- Task
- Write
- Read
- mcp__godot__*
---
You are helping initialize a new Godot game project.
First, launch the game-planner agent to gather requirements and create a game plan:
Use the Task tool with subagent_type "game-planner" to launch the planning agent.
After the planning agent completes, use the returned game plan to:
1. Update or create the project.godot file with appropriate settings (2D/3D, display settings, etc.)
2. Create the main scene based on the game type (Node2D for 2D games, Node3D for 3D games)
3. Set up the basic folder structure:
- scenes/ (for game scenes)
- scripts/ (for GDScript files)
- assets/ (for images, sounds, etc.)
- resources/ (for Godot resource files)
4. Create a README.md documenting the project plan, core mechanics, and initial architecture
Use the Godot MCP tools to create scenes and add nodes as appropriate based on the game plan.
Inform the user when initialization is complete and suggest next steps for development.

141
commands/restart.md Normal file
View File

@@ -0,0 +1,141 @@
---
description: Quickly restart the running Godot game
allowed_tools:
- mcp__godot__stop_project
- mcp__godot__run_project
- mcp__godot__get_debug_output
---
Quickly restart the running Godot project for fast iteration during development.
# Process
1. **Stop the current instance**
- Use mcp__godot__stop_project to stop the running game
- Display: `♻️ Restarting game...`
2. **Wait briefly** (0.5 seconds) for clean shutdown
3. **Start the project again**
- Use mcp__godot__run_project with projectPath set to current working directory
4. **Get initial output**
- Wait 1.5 seconds
- Use mcp__godot__get_debug_output to check for errors
5. **Display result**
## If restart successful:
```
✓ Game restarted successfully!
Quick stats:
• Restart time: <elapsed_time>
• Status: Running
Commands:
/gd:restart Restart again
/gd:debug View debug output
/gd:stop Stop the game
💡 Tip: Keep this workflow for rapid iteration:
Edit code → Save → /gd:restart → Test
```
## If errors occurred:
```
⚠ Game restarted with errors:
<formatted_errors>
Use /gd:debug for detailed error information
Commands:
/gd:debug View full debug output
/gd:stop Stop the game
Would you like help fixing these errors?
```
## If no game was running:
```
No game was running. Starting fresh...
<use same output as /gd:run>
```
# Usage Tips
Display these tips the first time user runs /gd:restart:
```
💡 Restart Command Tips:
1. Fast Iteration: Use /gd:restart after making code changes
instead of manually stopping and starting the game.
2. Keyboard Shortcut: Save this command for quick access
(check if your terminal supports command aliases)
3. Watch Your Console: Errors from the restart will be
displayed immediately.
4. No Need to Stop First: /gd:restart handles stopping
automatically.
Typical workflow:
1. Make changes to your code
2. Save (Cmd+S / Ctrl+S)
3. Run /gd:restart
4. Test your changes
5. Repeat!
```
# Performance Optimization
Track restart metrics for user feedback:
```
📊 Restart Performance:
• This restart: 2.1s
• Average: 2.3s
• Fastest: 1.8s
Your game restarts quickly! This is great for iteration.
```
# Error Recovery
If restart fails:
```
❌ Restart failed!
Possible causes:
- Previous instance didn't shut down cleanly
- Godot editor is not responding
- MCP server connection issue
Try these steps:
1. Wait a few seconds and try again
2. Check if Godot editor is running
3. Run /gd:setup to verify MCP configuration
Or manually:
1. /gd:stop (ensure game stops)
2. /gd:run (start fresh)
```
# Integration with Error Monitoring
After restart, if errors detected, activate godot-debugging skill hints:
```
🔍 Detected issues in restart:
[ERROR] Null instance in player.gd:45
Common cause: Node references changed or removed
Quick fix: Verify all @onready var paths are correct
after scene modifications.
Want detailed help? Just ask about this error!
```

339
commands/run.md Normal file
View File

@@ -0,0 +1,339 @@
---
description: Run the Godot game project with optional watch mode and enhanced error display
allowed_tools:
- mcp__godot__run_project
- mcp__godot__get_debug_output
- AskUserQuestion
- Skill
---
Run the current Godot project using the MCP server with enhanced debugging features.
# Process
## 1. Parse Command Arguments
Check if user provided any flags:
- `--watch` or `-w`: Enable watch mode (auto-restart on file changes)
- `--debug` or `-d`: Show detailed debug output immediately
- `--no-output`: Run without showing initial output
Examples:
- `/gd:run` - Normal run
- `/gd:run --watch` - Run with watch mode
- `/gd:run --debug` - Run with immediate debug output
## 2. Start the Project
Use mcp__godot__run_project with projectPath set to the current working directory.
Display:
```
🎮 Starting Godot project...
Project: <current_directory>
```
## 3. Get Initial Output
Wait 2 seconds, then use mcp__godot__get_debug_output to fetch initial output.
## 4. Parse and Display Output
Parse the output for:
- **Errors** (lines containing "ERROR", "Error:", or stack traces)
- **Warnings** (lines containing "WARNING", "WARN")
- **Info** (other output)
Display with enhanced formatting:
### If No Errors:
```
✓ Game started successfully!
Output:
<formatted_output>
Commands:
/gd:stop Stop the running game
/gd:debug View enhanced debug output
/gd:restart Quick restart
Tip: The game is now running. Make changes to your code and use /gd:restart to quickly reload!
```
### If Errors Found:
```
⚠ Game started with errors:
<formatted_errors>
Commands:
/gd:debug View detailed error information
/gd:stop Stop the game
Would you like me to help fix these errors? I can explain what's wrong and suggest solutions.
```
## 5. Error Highlighting
When displaying errors, highlight key information:
```
[ERROR] <error_type>
File: <file_path>:<line_number>
Message: <error_message>
💡 Quick Tip: <brief_suggestion>
```
Examples:
```
[ERROR] Null Instance
File: res://scripts/player.gd:45
Message: Attempt to call function 'take_damage' in base 'null instance' on a null instance
💡 Quick Tip: Add null check before calling methods: if node != null: node.method()
```
```
[WARNING] Resource Not Found
File: res://scenes/level.tscn
Message: Cannot load resource at path: 'res://sprites/missing.png'
💡 Quick Tip: Check that the file exists and the path is correct (case-sensitive)
```
## 6. Watch Mode (if --watch flag provided)
If watch mode is enabled:
1. Display:
```
📁 Watch mode enabled
Monitoring for file changes in:
- *.gd (GDScript files)
- *.tscn (Scene files)
- *.tres (Resource files)
The game will auto-restart when you save changes.
Press Ctrl+C or type 'stop' to exit watch mode.
```
2. Set up file watching (conceptual - explain to user):
```
Note: Watch mode requires manual restart for now.
To enable auto-restart:
1. Make your code changes
2. Use /gd:restart to quickly reload
3. Repeat as needed
Future enhancement: Automatic file watching with instant reload.
```
3. Provide workflow tips:
```
💡 Rapid Iteration Workflow:
1. Edit your code
2. Save the file (Ctrl+S / Cmd+S)
3. Run /gd:restart
4. See changes immediately!
This is much faster than closing and reopening the game.
```
## 7. Error Count Summary
After initial run, provide summary:
```
═══════════════════════════════════════
Status Summary
═══════════════════════════════════════
🔴 Errors: <count>
🟡 Warnings: <count>
🔵 Info messages: <count>
⏱ Startup time: <elapsed_time>
═══════════════════════════════════════
```
## 8. Offer Help
Based on what was found:
### If errors present:
```
I noticed some errors. Would you like me to:
1. Explain what these errors mean
2. Show you how to fix them
3. Run the debugger for more details
Just ask! For example: "Help me fix the null instance error"
```
### If warnings only:
```
There are some warnings you might want to address.
Use /gd:debug to see detailed information.
```
### If clean run:
```
Everything looks good! Your game is running smoothly.
```
## 9. Continuous Monitoring (Enhanced)
Offer to monitor for new errors:
Ask user:
Question: "Would you like me to monitor for runtime errors?"
Header: "Monitoring"
Multi-select: false
Options:
- Yes, watch for errors: I'll alert you if new errors occur
- No, just run: Just start the game without monitoring
- Debug mode: Show all output in real-time
If "Yes, watch for errors" selected:
```
👀 Monitoring enabled
I'll alert you if new errors occur while the game is running.
You can check anytime with /gd:debug
```
Then periodically (conceptually - every 30 seconds or when user asks):
- Check for new errors with get_debug_output
- If new errors found, alert user:
```
⚠ New error detected!
[ERROR] <brief_description>
Use /gd:debug to see full details
```
## 10. Integration with Debugging Skill
If errors are found, automatically suggest using the debugging skill:
```
💬 Need help understanding these errors?
Try asking:
- "What does this error mean?"
- "How do I fix the null instance error?"
- "Debug the player.gd script"
I can explain and fix these issues for you!
```
## 11. Performance Notes
Display performance indicators if available:
```
⚡ Performance:
FPS: <if_available>
Memory: <if_available>
Use /gd:profile for detailed performance analysis
```
## 12. Quick Actions
At the end, always show quick actions:
```
Quick Commands:
/gd:stop Stop the game
/gd:restart Restart quickly
/gd:debug Detailed debug view
/gd:profile Performance analysis
💡 Press Tab to see all /gd: commands
```
# Example Full Output
## Success Case:
```
🎮 Starting Godot project...
Project: /Users/user/games/platformer
✓ Game started successfully!
Output:
Godot Engine v4.2.1 - https://godotengine.org
Vulkan API 1.3.0 - Device: Apple M1
Loading scene: res://scenes/main.tscn
Player initialized
Level loaded: Level_1
═══════════════════════════════════════
Status Summary
═══════════════════════════════════════
🔴 Errors: 0
🟡 Warnings: 0
🔵 Info messages: 4
⏱ Startup time: 1.2s
═══════════════════════════════════════
Everything looks good! Your game is running smoothly.
Commands:
/gd:stop Stop the running game
/gd:debug View debug output
/gd:restart Quick restart
Tip: Make changes to your code and use /gd:restart to quickly reload!
```
## Error Case:
```
🎮 Starting Godot project...
Project: /Users/user/games/platformer
⚠ Game started with errors:
[ERROR] Null Instance
File: res://scripts/player.gd:45
Message: Attempt to call function 'take_damage' in base 'null instance'
💡 Quick Tip: Add null check: if player != null: player.take_damage(10)
[WARNING] Resource Not Found
File: res://scenes/level.tscn
Message: Cannot load resource: 'res://sprites/enemy.png'
💡 Quick Tip: Verify the file path is correct and the file exists
═══════════════════════════════════════
Status Summary
═══════════════════════════════════════
🔴 Errors: 1
🟡 Warnings: 1
🔵 Info messages: 3
⏱ Startup time: 1.5s
═══════════════════════════════════════
💬 Need help fixing these errors?
I can explain what went wrong and show you how to fix it.
Just ask: "Help me fix the null instance error"
Commands:
/gd:debug View detailed error information
/gd:stop Stop the game
```
# Important Notes
- Always format output in a user-friendly way
- Highlight errors in a visually distinct manner
- Provide actionable suggestions, not just error messages
- Make it easy to transition to fixing errors (mention debugging skill)
- Track context for follow-up questions about specific errors

583
commands/scene.md Normal file
View File

@@ -0,0 +1,583 @@
---
allowed_tools:
- AskUserQuestion
- mcp__godot__*
- Write
- Read
---
Create common scene templates for 2D games, including characters, enemies, levels, and more.
# Process
1. **Ask the user what type of scene they want to create:**
Use AskUserQuestion with the following options:
Question: "What type of scene would you like to create?"
Header: "Scene Type"
Multi-select: false
Options:
- 2D Player Character: Platformer character with movement and collision
- 2D Enemy: Basic enemy with AI placeholder
- 2D Level: Level scene with tilemap and camera
- 2D Projectile: Bullet/projectile with movement
- Collectible: Coin/item pickup
- Interactable Object: Chest, door, switch, etc.
Question 2: "Where should the scene be created?"
Header: "Location"
Multi-select: false
Options:
- scenes/characters/: For player and NPCs
- scenes/enemies/: For enemy characters
- scenes/levels/: For level scenes
- scenes/objects/: For items and interactables
- Custom path: I'll specify the path
2. **If Custom path selected, ask for the specific path and scene name**
3. **Create the appropriate template based on selection**
## 2D Player Character Template
Create scene with:
```
CharacterBody2D (root)
├── Sprite2D (sprite)
├── CollisionShape2D (collision)
├── AnimationPlayer (animation_player)
└── Camera2D (camera)
```
Node details:
- CharacterBody2D: motion_mode = MOTION_MODE_GROUNDED
- Sprite2D: centered = true, texture = placeholder (32x32 white rect)
- CollisionShape2D: shape = RectangleShape2D (16x32)
- Camera2D: enabled = true, position_smoothing_enabled = true
Create accompanying script:
```gdscript
extends CharacterBody2D
# Movement parameters
@export var speed: float = 200.0
@export var jump_velocity: float = -400.0
@export var acceleration: float = 800.0
@export var friction: float = 1000.0
# Get the gravity from the project settings
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
@onready var sprite = $Sprite2D
@onready var animation_player = $AnimationPlayer
func _physics_process(delta):
# Add gravity
if not is_on_floor():
velocity.y += gravity * delta
# Handle jump
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = jump_velocity
# Get input direction
var direction = Input.get_axis("ui_left", "ui_right")
# Apply movement
if direction != 0:
velocity.x = move_toward(velocity.x, direction * speed, acceleration * delta)
# Flip sprite based on direction
sprite.flip_h = direction < 0
else:
# Apply friction when no input
velocity.x = move_toward(velocity.x, 0, friction * delta)
move_and_slide()
# Animation logic (requires animations to be set up)
_update_animation()
func _update_animation():
if not animation_player:
return
# TODO: Create animations in AnimationPlayer:
# - "idle": Standing still
# - "run": Running
# - "jump": Jumping up
# - "fall": Falling down
# Example animation logic:
# if not is_on_floor():
# if velocity.y < 0:
# animation_player.play("jump")
# else:
# animation_player.play("fall")
# elif abs(velocity.x) > 10:
# animation_player.play("run")
# else:
# animation_player.play("idle")
pass
```
## 2D Enemy Template
Create scene with:
```
CharacterBody2D (root)
├── Sprite2D (sprite)
├── CollisionShape2D (collision)
├── Area2D (detection_area)
│ └── CollisionShape2D (detection_collision)
├── AnimationPlayer (animation_player)
└── Timer (patrol_timer)
```
Node details:
- CharacterBody2D: motion_mode = MOTION_MODE_GROUNDED
- Sprite2D: centered = true, modulate = Color(1, 0.5, 0.5) (reddish tint)
- CollisionShape2D: shape = RectangleShape2D (16x24)
- Area2D/CollisionShape2D: shape = CircleShape2D (radius = 100) for player detection
- Timer: wait_time = 2.0, one_shot = false
Create accompanying script:
```gdscript
extends CharacterBody2D
enum State { IDLE, PATROL, CHASE, ATTACK }
# Movement parameters
@export var patrol_speed: float = 50.0
@export var chase_speed: float = 150.0
@export var detection_range: float = 100.0
@export var attack_range: float = 30.0
@export var health: int = 100
var current_state: State = State.IDLE
var player: Node2D = null
var patrol_direction: int = 1
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
@onready var sprite = $Sprite2D
@onready var detection_area = $detection_area
@onready var patrol_timer = $patrol_timer
func _ready():
# Connect area signals for player detection
detection_area.body_entered.connect(_on_detection_area_entered)
detection_area.body_exited.connect(_on_detection_area_exited)
# Connect patrol timer
patrol_timer.timeout.connect(_on_patrol_timer_timeout)
patrol_timer.start()
current_state = State.PATROL
func _physics_process(delta):
# Apply gravity
if not is_on_floor():
velocity.y += gravity * delta
# State machine
match current_state:
State.IDLE:
velocity.x = 0
State.PATROL:
velocity.x = patrol_direction * patrol_speed
sprite.flip_h = patrol_direction < 0
# Turn around at ledges or walls
if is_on_wall() or not _check_floor_ahead():
patrol_direction *= -1
State.CHASE:
if player:
var direction = sign(player.global_position.x - global_position.x)
velocity.x = direction * chase_speed
sprite.flip_h = direction < 0
# Check if close enough to attack
var distance = global_position.distance_to(player.global_position)
if distance < attack_range:
current_state = State.ATTACK
else:
current_state = State.PATROL
State.ATTACK:
velocity.x = 0
# TODO: Implement attack logic
# For now, just return to chase after a moment
await get_tree().create_timer(0.5).timeout
current_state = State.CHASE
move_and_slide()
func _check_floor_ahead() -> bool:
# Raycast to check if there's floor ahead
var space_state = get_world_2d().direct_space_state
var query = PhysicsRayQueryParameters2D.create(
global_position,
global_position + Vector2(patrol_direction * 20, 30)
)
var result = space_state.intersect_ray(query)
return result.size() > 0
func _on_detection_area_entered(body):
if body.is_in_group("player"):
player = body
current_state = State.CHASE
func _on_detection_area_exited(body):
if body == player:
player = null
current_state = State.PATROL
func _on_patrol_timer_timeout():
if current_state == State.IDLE or current_state == State.PATROL:
patrol_direction *= -1
func take_damage(amount: int):
health -= amount
# TODO: Add damage flash/animation
if health <= 0:
die()
func die():
# TODO: Add death animation
queue_free()
```
## 2D Level Template
Create scene with:
```
Node2D (root)
├── TileMap (tilemap)
├── Node2D (spawn_points)
│ └── Marker2D (player_spawn)
├── Node2D (enemies)
├── Node2D (collectibles)
├── ParallaxBackground (background)
│ └── ParallaxLayer (layer1)
│ └── Sprite2D (bg_sprite)
└── Camera2D (camera)
```
Node details:
- TileMap: tile_set = null (needs to be set by user), layer 0 = "Ground", layer 1 = "Walls"
- Camera2D: enabled = true, limit_left = 0, limit_top = 0
Create accompanying script:
```gdscript
extends Node2D
@onready var tilemap = $TileMap
@onready var player_spawn = $spawn_points/player_spawn
@onready var camera = $Camera2D
# Preload player scene
const PLAYER_SCENE = preload("res://scenes/characters/player.tscn")
func _ready():
# Spawn player
spawn_player()
# Set camera limits based on level bounds
_setup_camera_limits()
func spawn_player():
# Instance and add player
var player = PLAYER_SCENE.instantiate()
player.global_position = player_spawn.global_position
add_child(player)
# Make camera follow player
camera.enabled = false # Disable level camera
# Player should have its own camera
func _setup_camera_limits():
# Get tilemap bounds
var used_rect = tilemap.get_used_rect()
var tile_size = tilemap.tile_set.tile_size if tilemap.tile_set else Vector2(16, 16)
# Set camera limits to level bounds
camera.limit_left = used_rect.position.x * tile_size.x
camera.limit_top = used_rect.position.y * tile_size.y
camera.limit_right = used_rect.end.x * tile_size.x
camera.limit_bottom = used_rect.end.y * tile_size.y
# Helper function to spawn enemies at runtime
func spawn_enemy(enemy_scene: PackedScene, position: Vector2):
var enemy = enemy_scene.instantiate()
enemy.global_position = position
$enemies.add_child(enemy)
# Helper function to spawn collectibles
func spawn_collectible(collectible_scene: PackedScene, position: Vector2):
var collectible = collectible_scene.instantiate()
collectible.global_position = position
$collectibles.add_child(collectible)
```
## 2D Projectile Template
Create scene with:
```
Area2D (root)
├── Sprite2D (sprite)
├── CollisionShape2D (collision)
└── Timer (lifetime_timer)
```
Node details:
- Sprite2D: centered = true, modulate = Color(1, 1, 0) (yellow)
- CollisionShape2D: shape = CircleShape2D (radius = 4)
- Timer: wait_time = 5.0, one_shot = true, autostart = true
Create accompanying script:
```gdscript
extends Area2D
@export var speed: float = 300.0
@export var damage: int = 10
@export var lifetime: float = 5.0
var direction: Vector2 = Vector2.RIGHT
@onready var sprite = $Sprite2D
@onready var lifetime_timer = $lifetime_timer
func _ready():
# Connect signals
body_entered.connect(_on_body_entered)
area_entered.connect(_on_area_entered)
lifetime_timer.timeout.connect(_on_lifetime_timeout)
# Set lifetime
lifetime_timer.wait_time = lifetime
func _physics_process(delta):
# Move in direction
position += direction.normalized() * speed * delta
func set_direction(new_direction: Vector2):
direction = new_direction.normalized()
# Rotate sprite to face direction
rotation = direction.angle()
func _on_body_entered(body):
# Hit something solid
if body.has_method("take_damage"):
body.take_damage(damage)
# Create hit effect
_create_hit_effect()
queue_free()
func _on_area_entered(area):
# Hit another area (enemy hitbox, etc.)
if area.get_parent().has_method("take_damage"):
area.get_parent().take_damage(damage)
# Create hit effect
_create_hit_effect()
queue_free()
func _on_lifetime_timeout():
# Despawn after lifetime expires
queue_free()
func _create_hit_effect():
# TODO: Spawn particle effect or animation
pass
```
## Collectible Template
Create scene with:
```
Area2D (root)
├── Sprite2D (sprite)
├── CollisionShape2D (collision)
├── AnimationPlayer (animation_player)
└── AudioStreamPlayer2D (pickup_sound)
```
Node details:
- Sprite2D: centered = true, modulate = Color(1, 0.8, 0) (gold color)
- CollisionShape2D: shape = CircleShape2D (radius = 8)
Create accompanying script:
```gdscript
extends Area2D
@export var collect_value: int = 1
@export var float_amplitude: float = 5.0
@export var float_speed: float = 2.0
var start_y: float
var time: float = 0.0
@onready var sprite = $Sprite2D
@onready var animation_player = $AnimationPlayer
@onready var pickup_sound = $pickup_sound
func _ready():
# Connect collection signal
body_entered.connect(_on_body_entered)
# Store starting position for floating animation
start_y = position.y
# Random offset for variety
time = randf() * TAU
func _process(delta):
# Floating animation
time += delta * float_speed
position.y = start_y + sin(time) * float_amplitude
# Rotate for visual interest
sprite.rotation += delta * 2.0
func _on_body_entered(body):
if body.is_in_group("player"):
# Notify player of collection
if body.has_method("collect_item"):
body.collect_item(collect_value)
# Play pickup sound
if pickup_sound and pickup_sound.stream:
# Play sound then destroy
pickup_sound.play()
# Hide visuals but keep node alive for sound
sprite.visible = false
$CollisionShape2D.set_deferred("disabled", true)
await pickup_sound.finished
queue_free()
```
## Interactable Object Template
Create scene with:
```
StaticBody2D (root)
├── Sprite2D (sprite)
├── CollisionShape2D (collision)
├── Area2D (interaction_area)
│ └── CollisionShape2D (interaction_collision)
└── Label (prompt_label)
```
Node details:
- Sprite2D: centered = true
- CollisionShape2D: shape = RectangleShape2D (32x32)
- Area2D/CollisionShape2D: shape = RectangleShape2D (48x48) for interaction detection
- Label: text = "Press E", horizontal_alignment = CENTER, visible = false
Create accompanying script:
```gdscript
extends StaticBody2D
signal interacted
@export var interaction_text: String = "Press E"
@export var can_interact_multiple_times: bool = false
var player_nearby: bool = false
var has_been_used: bool = false
@onready var sprite = $Sprite2D
@onready var interaction_area = $interaction_area
@onready var prompt_label = $prompt_label
func _ready():
# Connect area signals
interaction_area.body_entered.connect(_on_interaction_area_entered)
interaction_area.body_exited.connect(_on_interaction_area_exited)
# Set prompt text
prompt_label.text = interaction_text
# Position prompt above object
prompt_label.position.y = -40
func _process(_delta):
if player_nearby and Input.is_action_just_pressed("ui_accept"):
if not has_been_used or can_interact_multiple_times:
_interact()
func _on_interaction_area_entered(body):
if body.is_in_group("player"):
player_nearby = true
# Show prompt if not used or can use multiple times
if not has_been_used or can_interact_multiple_times:
prompt_label.visible = true
func _on_interaction_area_exited(body):
if body.is_in_group("player"):
player_nearby = false
prompt_label.visible = false
func _interact():
# Mark as used
has_been_used = true
# Hide prompt if can't be used again
if not can_interact_multiple_times:
prompt_label.visible = false
# Emit signal for other systems to respond
interacted.emit()
# TODO: Implement specific interaction logic
# Examples:
# - Open a door
# - Play an animation
# - Give an item
# - Show dialogue
# - Toggle a mechanism
print("Interacted with ", name)
```
# After Creating Scene
After creating the selected scene:
1. Inform the user of the files created:
- Scene file location
- Script file location
2. Provide customization tips:
- "The [scene name] has been created at [path]"
- "To customize this scene:"
- Replace the placeholder sprite with your own artwork
- Adjust the exported variables in the Inspector
- Add animations in the AnimationPlayer
- [Template-specific tips]
3. Provide usage instructions:
- For characters: "Add this to your level by dragging into the scene or instantiating in code"
- For levels: "Set this as your main scene in Project Settings or change to it with get_tree().change_scene_to_file()"
- For projectiles: "Instance this in your player/enemy script when firing"
- For collectibles/interactables: "Place these in your level scene as children"
4. Offer next steps:
- "Would you like me to:"
- Create related scenes (e.g., "create a projectile for this character?")
- Add more features (e.g., "add a dash ability?", "add more enemy states?")
- Set up animations (if AnimationPlayer exists)
- Create a test level to try it out

24
commands/setup.md Normal file
View File

@@ -0,0 +1,24 @@
---
description: Validate Godot environment and configure MCP server
allowed-tools:
- Read
- Write
- Bash(ls:*,which:*,test:*)
---
You are helping set up the Godot development environment for this project.
Run the setup script to validate the environment and configure the MCP server:
!bash ${CLAUDE_PLUGIN_ROOT}/scripts/setup-mcp.sh
The script will:
1. Check if Godot is installed at common locations
2. Verify the Godot MCP server is built and ready at ~/projects/godot-mcp
3. Create .mcp.json from the template with detected paths
4. Report any issues and provide guidance
After running the script:
- If successful, inform the user that the environment is ready and they can now use `/gd:init-game` to start planning their game
- If there are issues, explain what needs to be fixed and how to resolve them
- Remind the user to restart Claude Code for the MCP server changes to take effect

11
commands/stop.md Normal file
View File

@@ -0,0 +1,11 @@
---
description: Stop the running Godot game
allowed-tools:
- mcp__godot__stop_project
---
Stop the currently running Godot project.
1. Use mcp__godot__stop_project to stop the game
2. Display any final output or errors
3. Confirm to the user that the game has been stopped

652
commands/ui-template.md Normal file
View File

@@ -0,0 +1,652 @@
---
allowed_tools:
- AskUserQuestion
- mcp__godot__*
- Write
- Read
- Skill
---
Create a quick UI template for common game UI screens.
# Process
1. **Ask the user what type of UI they want to create:**
Use AskUserQuestion with the following options:
Question: "What type of UI screen would you like to create?"
Header: "UI Type"
Multi-select: false
Options:
- Main Menu: Title screen with New Game, Continue, Settings, Quit
- Pause Menu: In-game pause screen with Resume, Settings, Main Menu, Quit
- Settings Menu: Graphics, audio, and gameplay settings with tabs
- Game HUD: Health bar, score, and interactive buttons
- Inventory: Grid-based item management system
- Dialogue Box: Character dialogue with portrait and choices
- Confirmation Dialog: Yes/No popup dialog
Question 2: "Where should the UI scene be created?"
Header: "Location"
Multi-select: false
Options:
- scenes/ui/menus/: For menu screens
- scenes/ui/hud/: For in-game HUD elements
- scenes/ui/dialogs/: For popup dialogs
- Custom path: I'll specify the path
2. **If Custom path selected, ask for the specific path**
3. **Create the appropriate template based on selection**
## Main Menu Template
Create scene with:
```
CanvasLayer (root)
├── ColorRect (background - full rect anchors)
├── MarginContainer (full rect, margins: 40px all sides)
│ └── VBoxContainer (alignment: center)
│ ├── Control (spacer - custom_minimum_size.y = 100)
│ ├── Label (title - custom font size 48, center aligned)
│ ├── Control (spacer - custom_minimum_size.y = 50)
│ ├── VBoxContainer (button_container - separation: 10)
│ │ ├── Button (new_game_btn - text: "New Game", custom_minimum_size.x = 200)
│ │ ├── Button (continue_btn - text: "Continue", custom_minimum_size.x = 200)
│ │ ├── Button (settings_btn - text: "Settings", custom_minimum_size.x = 200)
│ │ └── Button (quit_btn - text: "Quit", custom_minimum_size.x = 200)
│ └── Control (spacer with size_flags_vertical = 3)
```
Create accompanying script:
```gdscript
extends CanvasLayer
@onready var new_game_btn = $MarginContainer/VBoxContainer/VBoxContainer/new_game_btn
@onready var continue_btn = $MarginContainer/VBoxContainer/VBoxContainer/continue_btn
@onready var settings_btn = $MarginContainer/VBoxContainer/VBoxContainer/settings_btn
@onready var quit_btn = $MarginContainer/VBoxContainer/VBoxContainer/quit_btn
func _ready():
# Connect button signals
new_game_btn.pressed.connect(_on_new_game_pressed)
continue_btn.pressed.connect(_on_continue_pressed)
settings_btn.pressed.connect(_on_settings_pressed)
quit_btn.pressed.connect(_on_quit_pressed)
# Set focus for gamepad support
new_game_btn.grab_focus()
# Check if save exists for continue button
continue_btn.disabled = not _has_save_file()
# Fade in animation
modulate.a = 0
var tween = create_tween()
tween.tween_property(self, "modulate:a", 1.0, 0.5)
func _has_save_file() -> bool:
# TODO: Implement save file checking
return FileAccess.file_exists("user://savegame.save")
func _on_new_game_pressed():
# TODO: Implement new game logic
get_tree().change_scene_to_file("res://scenes/main_game.tscn")
func _on_continue_pressed():
# TODO: Implement load game logic
pass
func _on_settings_pressed():
# TODO: Open settings menu
get_tree().change_scene_to_file("res://scenes/ui/menus/settings_menu.tscn")
func _on_quit_pressed():
get_tree().quit()
```
## Pause Menu Template
Create scene with:
```
CanvasLayer (root - layer: 100)
├── ColorRect (overlay - full rect, color: #00000080, mouse_filter: STOP)
├── CenterContainer (full rect anchors)
│ └── PanelContainer (custom_minimum_size: 400x500)
│ └── MarginContainer (margins: 20px all sides)
│ └── VBoxContainer (separation: 15)
│ ├── Label (title - text: "PAUSED", align: center, font size: 36)
│ ├── Control (spacer - custom_minimum_size.y = 20)
│ ├── Button (resume_btn - text: "Resume")
│ ├── Button (settings_btn - text: "Settings")
│ ├── Button (main_menu_btn - text: "Main Menu")
│ └── Button (quit_btn - text: "Quit")
```
Create accompanying script:
```gdscript
extends CanvasLayer
@onready var resume_btn = $CenterContainer/PanelContainer/MarginContainer/VBoxContainer/resume_btn
@onready var settings_btn = $CenterContainer/PanelContainer/MarginContainer/VBoxContainer/settings_btn
@onready var main_menu_btn = $CenterContainer/PanelContainer/MarginContainer/VBoxContainer/main_menu_btn
@onready var quit_btn = $CenterContainer/PanelContainer/MarginContainer/VBoxContainer/quit_btn
func _ready():
# Connect signals
resume_btn.pressed.connect(_on_resume_pressed)
settings_btn.pressed.connect(_on_settings_pressed)
main_menu_btn.pressed.connect(_on_main_menu_pressed)
quit_btn.pressed.connect(_on_quit_pressed)
# Pause the game
get_tree().paused = true
# Set focus
resume_btn.grab_focus()
# Pop-up animation
$CenterContainer.scale = Vector2(0.8, 0.8)
$CenterContainer.modulate.a = 0
var tween = create_tween()
tween.tween_property($CenterContainer, "scale", Vector2.ONE, 0.3).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
tween.parallel().tween_property($CenterContainer, "modulate:a", 1.0, 0.3)
func _on_resume_pressed():
_close_menu()
func _on_settings_pressed():
# TODO: Open settings submenu
pass
func _on_main_menu_pressed():
get_tree().paused = false
get_tree().change_scene_to_file("res://scenes/ui/menus/main_menu.tscn")
func _on_quit_pressed():
get_tree().quit()
func _close_menu():
get_tree().paused = false
queue_free()
func _input(event):
if event.is_action_pressed("ui_cancel"):
_close_menu()
```
## Settings Menu Template
Create scene with:
```
CanvasLayer (root)
├── ColorRect (background - full rect)
├── MarginContainer (full rect, margins: 40px all sides)
│ └── VBoxContainer (separation: 20)
│ ├── Label (title - text: "Settings", font size: 36)
│ ├── TabContainer (size_flags_vertical: 3)
│ │ ├── VBoxContainer (name: "Graphics", separation: 10)
│ │ │ ├── HBoxContainer
│ │ │ │ ├── Label (text: "Resolution:")
│ │ │ │ ├── Control (size_flags_horizontal: 3)
│ │ │ │ └── OptionButton (resolution_option)
│ │ │ ├── HBoxContainer
│ │ │ │ ├── Label (text: "Fullscreen:")
│ │ │ │ ├── Control (size_flags_horizontal: 3)
│ │ │ │ └── CheckBox (fullscreen_check)
│ │ │ └── HBoxContainer
│ │ │ ├── Label (text: "VSync:")
│ │ │ ├── Control (size_flags_horizontal: 3)
│ │ │ └── CheckBox (vsync_check)
│ │ └── VBoxContainer (name: "Audio", separation: 10)
│ │ ├── HBoxContainer
│ │ │ ├── Label (text: "Master Volume:")
│ │ │ └── HSlider (master_slider - min: 0, max: 100, value: 100)
│ │ ├── HBoxContainer
│ │ │ ├── Label (text: "Music Volume:")
│ │ │ └── HSlider (music_slider - min: 0, max: 100, value: 100)
│ │ └── HBoxContainer
│ │ ├── Label (text: "SFX Volume:")
│ │ └── HSlider (sfx_slider - min: 0, max: 100, value: 100)
│ └── HBoxContainer (separation: 10)
│ ├── Control (size_flags_horizontal: 3)
│ ├── Button (apply_btn - text: "Apply")
│ └── Button (back_btn - text: "Back")
```
Create accompanying script:
```gdscript
extends CanvasLayer
# Graphics tab
@onready var resolution_option = $MarginContainer/VBoxContainer/TabContainer/Graphics/HBoxContainer/resolution_option
@onready var fullscreen_check = $MarginContainer/VBoxContainer/TabContainer/Graphics/HBoxContainer2/fullscreen_check
@onready var vsync_check = $MarginContainer/VBoxContainer/TabContainer/Graphics/HBoxContainer3/vsync_check
# Audio tab
@onready var master_slider = $MarginContainer/VBoxContainer/TabContainer/Audio/HBoxContainer/master_slider
@onready var music_slider = $MarginContainer/VBoxContainer/TabContainer/Audio/HBoxContainer2/music_slider
@onready var sfx_slider = $MarginContainer/VBoxContainer/TabContainer/Audio/HBoxContainer3/sfx_slider
# Buttons
@onready var apply_btn = $MarginContainer/VBoxContainer/HBoxContainer/apply_btn
@onready var back_btn = $MarginContainer/VBoxContainer/HBoxContainer/back_btn
func _ready():
# Populate resolution options
resolution_option.add_item("1920x1080")
resolution_option.add_item("1280x720")
resolution_option.add_item("1024x768")
# Load current settings
_load_settings()
# Connect signals
apply_btn.pressed.connect(_on_apply_pressed)
back_btn.pressed.connect(_on_back_pressed)
master_slider.value_changed.connect(_on_master_volume_changed)
music_slider.value_changed.connect(_on_music_volume_changed)
sfx_slider.value_changed.connect(_on_sfx_volume_changed)
func _load_settings():
# TODO: Load from config file
fullscreen_check.button_pressed = (DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_FULLSCREEN)
vsync_check.button_pressed = (DisplayServer.window_get_vsync_mode() != DisplayServer.VSYNC_DISABLED)
func _on_apply_pressed():
_save_settings()
func _save_settings():
# Graphics settings
if fullscreen_check.button_pressed:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
else:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
if vsync_check.button_pressed:
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED)
else:
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
# TODO: Save to config file
# TODO: Apply resolution
func _on_back_pressed():
get_tree().change_scene_to_file("res://scenes/ui/menus/main_menu.tscn")
func _on_master_volume_changed(value: float):
# TODO: Apply to audio bus
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Master"), linear_to_db(value / 100.0))
func _on_music_volume_changed(value: float):
# TODO: Apply to audio bus
pass
func _on_sfx_volume_changed(value: float):
# TODO: Apply to audio bus
pass
```
## Game HUD Template
Create scene with:
```
CanvasLayer (root - layer: 10)
├── MarginContainer (full rect, margins: 20px all sides)
│ └── VBoxContainer
│ ├── HBoxContainer (top_bar - separation: 10)
│ │ ├── TextureRect (health_icon - expand_mode: keep_size, custom_minimum_size: 32x32)
│ │ ├── ProgressBar (health_bar - custom_minimum_size: 200x24)
│ │ ├── Control (spacer - size_flags_horizontal: 3)
│ │ ├── Label (score_label - text: "Score: 0")
│ │ └── TextureRect (coin_icon - expand_mode: keep_size, custom_minimum_size: 24x24)
│ ├── Control (middle_spacer - size_flags_vertical: 3)
│ └── HBoxContainer (bottom_bar - separation: 10)
│ ├── Control (spacer - size_flags_horizontal: 3)
│ ├── TextureButton (inventory_btn - custom_minimum_size: 48x48)
│ ├── TextureButton (map_btn - custom_minimum_size: 48x48)
│ └── TextureButton (pause_btn - custom_minimum_size: 48x48)
```
Create accompanying script:
```gdscript
extends CanvasLayer
@onready var health_bar = $MarginContainer/VBoxContainer/HBoxContainer/health_bar
@onready var score_label = $MarginContainer/VBoxContainer/HBoxContainer/score_label
@onready var inventory_btn = $MarginContainer/VBoxContainer/HBoxContainer2/inventory_btn
@onready var map_btn = $MarginContainer/VBoxContainer/HBoxContainer2/map_btn
@onready var pause_btn = $MarginContainer/VBoxContainer/HBoxContainer2/pause_btn
var current_score: int = 0
func _ready():
# Connect button signals
inventory_btn.pressed.connect(_on_inventory_pressed)
map_btn.pressed.connect(_on_map_pressed)
pause_btn.pressed.connect(_on_pause_pressed)
# Initialize health bar
health_bar.max_value = 100
health_bar.value = 100
func set_health(value: float):
var tween = create_tween()
tween.tween_property(health_bar, "value", value, 0.3)
# Change color based on health
if value < 30:
health_bar.modulate = Color.RED
elif value < 60:
health_bar.modulate = Color.YELLOW
else:
health_bar.modulate = Color.GREEN
func add_score(amount: int):
current_score += amount
score_label.text = "Score: %d" % current_score
# Bounce animation
var tween = create_tween()
tween.tween_property(score_label, "scale", Vector2(1.2, 1.2), 0.1)
tween.tween_property(score_label, "scale", Vector2.ONE, 0.1)
func _on_inventory_pressed():
# TODO: Open inventory
pass
func _on_map_pressed():
# TODO: Open map
pass
func _on_pause_pressed():
# Open pause menu
var pause_scene = load("res://scenes/ui/menus/pause_menu.tscn")
get_tree().root.add_child(pause_scene.instantiate())
```
## Inventory Template
Create scene with:
```
CanvasLayer (root - layer: 50)
├── ColorRect (overlay - full rect, color: #00000080)
├── CenterContainer (full rect)
│ └── PanelContainer (custom_minimum_size: 800x600)
│ └── MarginContainer (margins: 20px all sides)
│ └── VBoxContainer (separation: 15)
│ ├── Label (title - text: "Inventory", font size: 32)
│ ├── HSeparator
│ ├── HBoxContainer (size_flags_vertical: 3, separation: 15)
│ │ ├── ScrollContainer (size_flags_horizontal: 3)
│ │ │ └── GridContainer (item_grid - columns: 5, separation: 10)
│ │ └── PanelContainer (item_details - custom_minimum_size.x: 250)
│ │ └── MarginContainer (margins: 10px all sides)
│ │ └── VBoxContainer
│ │ ├── TextureRect (item_image - expand_mode: keep_aspect_centered, custom_minimum_size: 200x200)
│ │ ├── Label (item_name - text: "Select an item", align: center, font size: 18)
│ │ ├── RichTextLabel (item_description - text: "No item selected", bbcode_enabled: true, size_flags_vertical: 3)
│ │ └── Button (use_btn - text: "Use", disabled: true)
│ └── Button (close_btn - text: "Close")
```
Create accompanying script:
```gdscript
extends CanvasLayer
@onready var item_grid = $CenterContainer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/ScrollContainer/item_grid
@onready var item_image = $CenterContainer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/PanelContainer/MarginContainer/VBoxContainer/item_image
@onready var item_name = $CenterContainer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/PanelContainer/MarginContainer/VBoxContainer/item_name
@onready var item_description = $CenterContainer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/PanelContainer/MarginContainer/VBoxContainer/item_description
@onready var use_btn = $CenterContainer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/PanelContainer/MarginContainer/VBoxContainer/use_btn
@onready var close_btn = $CenterContainer/PanelContainer/MarginContainer/VBoxContainer/close_btn
var selected_item = null
const SLOT_SCENE = preload("res://scenes/ui/hud/hud_components/inventory_slot.tscn")
func _ready():
# Connect signals
use_btn.pressed.connect(_on_use_pressed)
close_btn.pressed.connect(_on_close_pressed)
# Populate inventory
_populate_inventory()
# Pause game
get_tree().paused = true
func _populate_inventory():
# Clear existing slots
for child in item_grid.get_children():
child.queue_free()
# TODO: Get inventory from inventory manager
# For now, create empty slots
for i in range(20):
var slot = SLOT_SCENE.instantiate()
slot.slot_clicked.connect(_on_slot_clicked.bind(slot))
item_grid.add_child(slot)
func _on_slot_clicked(slot):
# Update item details
if slot.item_data:
selected_item = slot.item_data
item_name.text = slot.item_data.name
item_description.text = slot.item_data.description
item_image.texture = slot.item_data.icon
use_btn.disabled = false
else:
selected_item = null
item_name.text = "Empty slot"
item_description.text = ""
item_image.texture = null
use_btn.disabled = true
func _on_use_pressed():
if selected_item:
# TODO: Use item logic
pass
func _on_close_pressed():
get_tree().paused = false
queue_free()
func _input(event):
if event.is_action_pressed("ui_cancel"):
_on_close_pressed()
```
## Dialogue Box Template
Create scene with:
```
CanvasLayer (root - layer: 20)
├── Control (spacer - size_flags_vertical: 3)
└── PanelContainer (dialogue_panel - anchor_left: 0, anchor_right: 1, anchor_bottom: 1, custom_minimum_size.y: 200)
└── MarginContainer (margins: 15px all sides)
└── VBoxContainer (separation: 10)
├── HBoxContainer (character_info - separation: 10)
│ ├── TextureRect (portrait - expand_mode: keep_size, custom_minimum_size: 80x80)
│ └── Label (character_name - text: "Character", font size: 20)
├── RichTextLabel (dialogue_text - bbcode_enabled: true, size_flags_vertical: 3, text: "Dialogue text goes here...")
└── VBoxContainer (choices_container - separation: 5)
```
Create accompanying script:
```gdscript
extends CanvasLayer
@onready var portrait = $PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/portrait
@onready var character_name = $PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/character_name
@onready var dialogue_text = $PanelContainer/MarginContainer/VBoxContainer/dialogue_text
@onready var choices_container = $PanelContainer/MarginContainer/VBoxContainer/choices_container
var current_dialogue_index: int = 0
var dialogue_data: Array = []
var text_speed: float = 0.05
var is_text_complete: bool = false
func _ready():
# Hide initially
$PanelContainer.modulate.a = 0
$PanelContainer.position.y = 50
func show_dialogue(data: Array):
dialogue_data = data
current_dialogue_index = 0
_display_current_dialogue()
# Slide in animation
var tween = create_tween()
tween.tween_property($PanelContainer, "modulate:a", 1.0, 0.3)
tween.parallel().tween_property($PanelContainer, "position:y", 0, 0.3)
func _display_current_dialogue():
if current_dialogue_index >= dialogue_data.size():
_close_dialogue()
return
var dialogue = dialogue_data[current_dialogue_index]
# Set character info
character_name.text = dialogue.get("character", "")
if dialogue.has("portrait"):
portrait.texture = dialogue.portrait
# Clear choices
for child in choices_container.get_children():
child.queue_free()
# Animate text
is_text_complete = false
_type_text(dialogue.text)
# Add choices if present
if dialogue.has("choices") and dialogue.choices.size() > 0:
await get_tree().create_timer(0.1).timeout # Wait for text to start
for i in range(dialogue.choices.size()):
var choice_text = dialogue.choices[i]
var btn = Button.new()
btn.text = choice_text
btn.pressed.connect(_on_choice_selected.bind(i))
choices_container.add_child(btn)
func _type_text(text: String):
dialogue_text.text = ""
dialogue_text.visible_characters = 0
dialogue_text.text = text
var char_count = text.length()
for i in range(char_count + 1):
dialogue_text.visible_characters = i
await get_tree().create_timer(text_speed).timeout
is_text_complete = true
func _input(event):
if event.is_action_pressed("ui_accept"):
if is_text_complete and choices_container.get_child_count() == 0:
_next_dialogue()
elif not is_text_complete:
# Skip text animation
dialogue_text.visible_ratio = 1.0
is_text_complete = true
func _next_dialogue():
current_dialogue_index += 1
_display_current_dialogue()
func _on_choice_selected(choice_index: int):
# TODO: Handle dialogue choice
# For now, just advance
_next_dialogue()
func _close_dialogue():
var tween = create_tween()
tween.tween_property($PanelContainer, "modulate:a", 0.0, 0.3)
tween.parallel().tween_property($PanelContainer, "position:y", 50, 0.3)
tween.tween_callback(queue_free)
```
## Confirmation Dialog Template
Create scene with:
```
CanvasLayer (root - layer: 200)
├── ColorRect (overlay - full rect, color: #00000080)
├── CenterContainer (full rect)
│ └── PanelContainer (custom_minimum_size: 400x200)
│ └── MarginContainer (margins: 20px all sides)
│ └── VBoxContainer (separation: 20)
│ ├── Label (message - text: "Are you sure?", align: center, autowrap: true, size_flags_vertical: 3)
│ └── HBoxContainer (separation: 10)
│ ├── Button (cancel_btn - text: "Cancel", size_flags_horizontal: 3)
│ └── Button (confirm_btn - text: "Confirm", size_flags_horizontal: 3)
```
Create accompanying script:
```gdscript
extends CanvasLayer
signal confirmed
signal cancelled
@onready var message = $CenterContainer/PanelContainer/MarginContainer/VBoxContainer/message
@onready var cancel_btn = $CenterContainer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/cancel_btn
@onready var confirm_btn = $CenterContainer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/confirm_btn
func _ready():
# Connect signals
cancel_btn.pressed.connect(_on_cancel_pressed)
confirm_btn.pressed.connect(_on_confirm_pressed)
# Set focus
cancel_btn.grab_focus()
# Pop-up animation
$CenterContainer.scale = Vector2(0.8, 0.8)
$CenterContainer.modulate.a = 0
var tween = create_tween()
tween.tween_property($CenterContainer, "scale", Vector2.ONE, 0.2)
tween.parallel().tween_property($CenterContainer, "modulate:a", 1.0, 0.2)
func set_message(text: String):
message.text = text
func _on_confirm_pressed():
confirmed.emit()
queue_free()
func _on_cancel_pressed():
cancelled.emit()
queue_free()
func _input(event):
if event.is_action_pressed("ui_cancel"):
_on_cancel_pressed()
```
# After Creating Template
After creating the selected template:
1. Inform the user of the files created:
- Scene file location
- Script file location
2. Provide next steps:
- "The [template name] has been created at [path]"
- "You can customize the appearance by:"
- Creating a theme resource
- Adjusting colors, fonts, and spacing
- Adding custom textures/icons
- "To use this UI:"
- [Specific usage instructions for the template]
- "For more advanced UI customization, you can invoke the godot-ui skill or ask me for help!"
3. Offer to:
- Create additional related scenes (e.g., "Would you like me to create the inventory slot component as well?")
- Set up theme resources
- Add more features to the template