add godot skill

This commit is contained in:
Infuseting 2026-10-08 08:43:36 +02:00
parent 51a66b9575
commit fa5d2ba5fa
69 changed files with 6646 additions and 0 deletions

147
godot-47-migration/SKILL.md Normal file
View File

@ -0,0 +1,147 @@
---
name: godot-47-migration
description: "Godot 4.7 migration guide: new features (HDR, Steam Frame, AreaLight3D, VirtualJoystick, tween_await, Perfetto tracing), breaking changes, and step-by-step migration patterns for existing Godot 4.x projects. Use when upgrading a Godot project to version 4.7."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- migration
- godot-4.7
- breaking-changes
created: 2026-06-20
---
# Godot 4.7 Migration Guide
Complete guide for migrating Godot 4.x projects to version 4.7.
## What's New in Godot 4.7
### Rendering & Visual
- **HDR output settings** — Configure HDR display support in project settings
- **New AreaLight3D** — Enhanced area lighting with new modes and intensity profiles
- **Nearest-neighbor viewport scaling** — Pixel-perfect rendering for retro-style games
### UI & Controls
- **VirtualJoystick** — Built-in virtual joystick node replaces third-party solutions
- **PopupMenu search** — Native search in popup menus
- **Control offset transforms** — New transform system for UI animation
### Animation
- **Collapsible animation tracks** — Better organization in AnimationTrackEdit
- **tween_await()** — Await tween completion for sequential chains
### Mobile & Android
- **Perfetto tracing** — Built-in Android performance profiling
- **Android XR settings** — New project settings for extended reality
### Steam Integration
- **Steam Frame configuration** — Enhanced Steam overlay and frame support
## Step-by-Step Migration
### Step 1: Backup Your Project
```bash
cp -r MyGame MyGame_backup_4.6
```
### Step 2: Open in Godot 4.7 Editor
1. Launch Godot 4.7
2. Open your existing project
3. Editor will auto-migrate project.godot settings
4. Review migration warnings in the output panel
### Step 3: Update project.godot Settings
```ini
# Add HDR support if needed
[rendering]
environment/default_environment="default_world.tres"
# Add Steam Frame config if using Steam
[steam]
app_id="YOUR_APP_ID"
```
### Step 4: Review AreaLight3D Changes
- Check all AreaLight3D nodes for new property options
- Update intensity profiles if using custom values
### Step 5: Replace Custom VirtualJoysticks
If using third-party virtual joystick implementations:
1. Remove custom VirtualJoystick scripts
2. Add the built-in VirtualJoystick node to your scene
3. Update input handling code (axis names may differ)
### Step 6: Update Tween Patterns
Replace nested callbacks with `tween_await()`:
```gdscript
# Before (4.6)
func play_sequence():
var tween = create_tween()
tween.tween_property($Sprite, "position:x", 100, 1.0)
tween.finished.connect(func():
var tween2 = create_tween()
tween2.tween_property($Sprite, "position:y", 50, 0.5)
)
# After (4.7)
func play_sequence():
await create_tween().tween_property($Sprite, "position:x", 100, 1.0).finished
await create_tween().tween_property($Sprite, "position:y", 50, 0.5).finished
```
### Step 7: Test All Platforms
- Windows desktop build
- Linux/X11 build
- Android build (verify Perfetto tracing works)
- HTML5 build
- Steam build (if applicable)
## Breaking Changes Checklist
- [ ] Review VirtualJoystick API changes if using custom implementations
- [ ] Update tween callback patterns to use await syntax
- [ ] Test HDR rendering pipeline impact on visual quality
- [ ] Verify Android XR settings if targeting XR devices
- [ ] Check Steam Frame configuration compatibility
## Godot 4.7 Feature Quick Reference
| Feature | Category | Skill Coverage |
|---------|----------|---------------|
| HDR output | Rendering | godot-project-setup |
| AreaLight3D | Rendering | godot-code-review |
| VirtualJoystick | Mobile | godot-brainstorming |
| tween_await() | Animation | godot-gdscript-patterns |
| Perfetto tracing | Profiling | godot-debugging |
| Steam Frame | Networking | godot-project-setup |
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools support migration:
- **`script_validate`** — Run with `suggestTweenAwait: true` to detect nested tween callbacks and suggest `await` migration
- **`project_settings_read`** — Check current HDR/Steam settings before migration
- **`project_settings_set`** — Apply 4.7 settings (e.g., `rendering/hdr`, `steam/frame/*`) directly
- **`file_search`** — Search for deprecated API patterns across the project
- **`class_db_info`** — Verify new 4.7 classes like AreaLight3D and VirtualJoystick are available
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [Full Migration Guide](../../docs/migration-4.7.md)
- [Godot 4.7 Changelog](https://github.com/godotengine/godot/releases)
- [Godot 4 Migration Guide](https://godotengine.org/article/migration-guide-godot-4/)

View File

@ -0,0 +1,14 @@
# Claude Code extension for godot-4.7-migration
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "migrating to Godot 4.7"
- "Godot version upgrade"
- "breaking changes in Godot 4.7"
- "new Godot 4.7 features"
- "HDR setup in Godot"
tool_permissions:
- read
- write
- bash

View File

@ -0,0 +1,13 @@
# OpenAI / Codex CLI extension for godot-4.7-migration
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "migrating to Godot 4.7"
- "Godot version upgrade"
- "breaking changes in Godot 4.7"
- "new Godot 4.7 features"
- "HDR setup in Godot"

303
godot-animation/SKILL.md Normal file
View File

@ -0,0 +1,303 @@
---
name: godot-animation
description: "Animation systems for Godot 4.x: AnimationPlayer vs AnimationTree, state machines, blend spaces (1D/2D/directional), animation libraries, root motion, collapsible tracks, and tween_await(). Use when building character animations, cutscenes, or visual effects in Godot."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- animation
- animationplayer
- animationtree
- blend-space
- root-motion
created: 2026-06-20
---
# Animation Systems for Godot 4.x
Comprehensive patterns for character animation, state machines, and visual effects in Godot 4.x.
## AnimationPlayer vs AnimationTree
### AnimationPlayer — Simple Animations
Use AnimationPlayer for linear sequences, cutscenes, and simple property animations.
```gdscript
@onready var anim_player = $AnimationPlayer
func play_attack():
anim_player.play("attack")
func play_transition(target: String, fade_time: float = 0.2):
anim_player.play(target, fade_time)
# Signal-based animation completion
func _ready():
anim_player.animation_finished.connect(_on_animation_finished)
func _on_animation_finished(anim_name: String):
if anim_name == "attack":
print("Attack animation complete")
```
### AnimationTree — State Machine Animations
Use AnimationTree for complex character animations with branching states (idle, walk, run, jump).
```gdscript
@onready var anim_tree = $AnimationTree
@onready var anim_state = anim_tree.get("state_machine")
func _ready():
anim_state.travel("Idle") # Start at Idle state
func set_animation(state_name: String):
if anim_state.has_state(state_name):
anim_state.travel(state_name)
func _physics_process(delta: float):
var speed = velocity.length()
anim_tree.set("parameters/Move/speed", speed)
anim_tree.process(delta)
```
### When to Use Which
| Use Case | Tool |
|----------|------|
| Cutscenes, UI animations | AnimationPlayer |
| Character movement states | AnimationTree + StateMachine |
| Simple one-shot effects | AnimationPlayer |
| Blending between movements | AnimationTree + BlendSpace2D |
| Complex combat combos | AnimationTree + AnimationNodeBlendTree |
## State Machines in AnimationTree
### Building a Character State Machine
```gdscript
# In the AnimationTree, create an AnimationNodeStateMachine:
# States: Idle, Walk, Run, Jump, Fall, Attack
# Transitions: Idle <-> Walk, Idle <-> Run, Walk -> Run, etc.
@onready var anim_tree = $AnimationTree
@onready var state_machine = anim_tree.get("state_machine")
func _physics_process(delta: float):
var speed = velocity.length()
var new_state = "Idle"
if not is_on_floor():
new_state = "Fall" if velocity.y < 0 else "Jump"
elif speed > 0.1:
new_state = "Run" if speed > run_threshold else "Walk"
# Only transition if the target state exists
if state_machine.has_state(new_state):
state_machine.travel(new_state)
anim_tree.set("parameters/Move/speed", speed)
anim_tree.process(delta)
```
### Transition Conditions
Set transition conditions in the AnimationTree editor:
- **Blend**: Use blend amounts based on parameters (e.g., speed)
- **One Shot**: Play once then return (for attacks, interactions)
- **Transition Rules**: Define when transitions are allowed
## Blend Spaces
### 1D Blend Space
Use for animations ordered along a single axis (e.g., walk speed).
```gdscript
# AnimationTree setup:
# BlendSpace1D: Walk -> Run -> Sprint
# Parameter: "speed" (float)
anim_tree.set("parameters/BlendSpace1D/speed", current_speed)
```
### 2D Blend Space
Use for directional movement blending.
```gdscript
# AnimationTree setup:
# BlendSpace2D: X = speed, Y = direction blend
# Parameters: "speed" and "direction"
anim_tree.set("parameters/BlendSpace2D/speed", velocity.length())
anim_tree.set("parameters/BlendSpace2D/direction", velocity.angle())
```
### Directional Blend Space (Godot 4.x)
Use a directional blend for 8-directional character animations.
```gdscript
# Set up AnimationNodeBlendDBone2D for directional blending
@onready var anim_tree = $AnimationTree
func _physics_process(delta: float):
var dir = velocity.normalized()
anim_tree.set("parameters/Directional/speed", velocity.length())
anim_tree.set("parameters/Directional/direction", dir)
anim_tree.process(delta)
```
## Animation Libraries and Blending Tracks
### Animation Libraries
Group related animations into libraries for organization.
```gdscript
# Access library animations by name
@onready var anim_player = $AnimationPlayer
func play_animation(anim_name: String):
# Animations are organized by library in the editor
anim_player.play(anim_name)
# List available animations
func get_available_animations() -> Array:
return anim_player.get_animation_list()
```
### Blending Tracks
Blend between multiple animation tracks for smooth transitions.
```gdscript
# Set blend amount in AnimationTree
anim_tree.set("parameters/Idle/blend_amount", 1.0)
# Blend between two animations
anim_player.play("idle", -1.0, 1.0) # loop, 1s fade-in
anim_player.play("walk", -1.0, 0.5) # loop, 0.5s fade-in
# Crossfade by playing both simultaneously
# The AnimationTree blend space handles the mixing
```
## Root Motion for Character Movement
### Using Root Motion in AnimationTree
Root motion applies bone animation directly to the character transform.
```gdscript
class_name RootMotionCharacter extends CharacterBody3D
@onready var anim_tree = $AnimationTree
@onready var anim_state = anim_tree.get("state_machine")
@export var root_motion_enabled: bool = true
func _physics_process(delta: float):
# Calculate root motion displacement
var prev_transform = global_transform
# Process animation tree (applies root motion)
anim_tree.process(delta)
if root_motion_enabled:
var root_delta = global_transform.xform_inv(prev_transform)
velocity = Vector3(root_delta.origin.x / delta, 0, root_delta.origin.z / delta)
else:
# Use scripted movement instead
velocity = transform.basis.z * speed
move_and_slide()
```
### When to Use Root Motion
- Platformer jump arcs (precise landing positions)
- Attack animations with fixed reach distances
- Cutscene character movements
### When to Avoid Root Motion
- Multiplayer games (server authority conflicts)
- Characters that need precise position control
- Games with custom physics responses
## Godot 4.7 Animation Features
### Collapsible Animation Tracks
Godot 4.7 introduces collapsible animation tracks for better organization in the AnimationPlayer editor.
```gdscript
# No code change needed — this is an editor feature
# In the AnimationPlayer inspector, click track headers to collapse/expand
# Group related properties under collapsible sections
```
### tween_await() for Sequential Animations
Use await with tweens for clean sequential animation chains.
```gdscript
func play_combo_animation():
# Chain animations without nested callbacks
await create_tween().tween_property($Sprite, "offset:x", 10.0, 0.1).finished
await create_tween().tween_property($Sprite, "offset:x", 0.0, 0.1).finished
print("Combo animation complete")
# Or with AnimationPlayer
func play_with_wait():
var tween = create_tween()
anim_player.play("attack")
await tween.tween_interval(0.5)
anim_player.play("recovery")
```
## Animation Performance Tips
- Use **AnimationTree** with state machines instead of multiple AnimationPlayers
- Keep animation libraries small and focused
- Use **AnimationNodeSync** to synchronize multiple character animations
- Preload animation resources with preload() for always-needed scenes
- Avoid animating properties that can be set directly (e.g., position vs tween)
- Use `animation_finished` signal instead of polling for completion
## Quick Reference: Animation Methods
| Method | Use Case |
|--------|----------|
| `anim_player.play(name)` | Start an animation |
| `anim_player.play(name, fade_time)` | Crossfade to animation |
| `anim_tree.process(delta)` | Update state machine |
| `anim_tree.set("params/...", value)` | Set blend parameters |
| `state_machine.travel(name)` | Switch to a state |
| `create_tween()` | Create animation tweens |
| `await tween.finished` | Wait for tween completion |
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools enhance animation debugging:
- **`animation_play`** — Play any animation from the project without opening the editor
- **`animation_list`** — List all available animations and their libraries
- **`animation_state_get`** — Query current AnimationTree state machine status
- **`animation_preview_frame`** — Preview a specific frame of any animation
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [AnimationPlayer Documentation](references/animationplayer-guide.md)
- [AnimationTree State Machine Patterns](references/animationtree-states.md)
- [Blend Space Design Guide](references/blend-spaces.md)

View File

@ -0,0 +1,15 @@
# Claude Code extension for godot-animation
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "animation systems in Godot"
- "Godot AnimationTree and state machines"
- "Godot blend spaces"
- "Godot skeleton IK"
- "Godot sprite animation"
- "Godot animation blending"
tool_permissions:
- read
- write
- bash

View File

@ -0,0 +1,14 @@
# OpenAI / Codex CLI extension for godot-animation
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "animation systems in Godot"
- "Godot AnimationTree and state machines"
- "Godot blend spaces"
- "Godot skeleton IK"
- "Godot sprite animation"
- "Godot animation blending"

219
godot-architecture/SKILL.md Normal file
View File

@ -0,0 +1,219 @@
---
name: godot-architecture
description: "Game architecture patterns for Godot 4.x: ECS-like composition, scene organization, dependency injection via signals, service locator vs direct reference, and scalable project structure. Use when designing or refactoring a Godot game's overall architecture."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- architecture
- ecs
- composition
- service-locator
created: 2026-06-21
---
# Godot Game Architecture Patterns
Architectural approaches for building scalable, maintainable Godot games.
## Scene Composition Over Inheritance
Godot's scene system naturally supports composition. Build complex behavior by combining small, focused scenes rather than deep inheritance chains.
### Component-Based Composition
```gdscript
# Player.tscn hierarchy:
# CharacterBody2D (root — movement logic)
# Sprite2D (visual)
# CollisionShape2D (physics body)
# HealthComponent (Node — health logic, emits signals)
# InventoryComponent (Node — item management)
# ExperienceComponent (Node — XP/leveling)
# Each component is a standalone .tscn with its own script.
# The player scene composes them without inheritance coupling.
```
### Component Script Pattern
```gdscript
class_name HealthComponent extends Node
signal health_changed(current: int, maximum: int)
signal died()
@export var max_health: int = 100
var current_health: int = 100:
set(value):
current_health = clampi(value, 0, max_health)
health_changed.emit(current_health, max_health)
if current_health <= 0:
died.emit()
func take_damage(amount: int) -> void:
current_health -= amount
func heal(amount: int) -> void:
current_health += amount
```
## Dependency Management Patterns
### Direct Reference (Simple Cases)
```gdscript
# Good for tightly-coupled parent-child relationships
@onready var health_component = $HealthComponent as HealthComponent
@onready var animation_player = $AnimationPlayer as AnimationPlayer
```
### Signal-Based Decoupling (Recommended)
```gdscript
# Components communicate via signals — no direct references needed
func _ready():
$HealthComponent.died.connect(_on_player_died)
$InventoryComponent.item_collected.connect(_on_item_collected)
func _on_player_died():
get_tree().reload_current_scene()
```
### Service Locator (Autoload Singletons)
```gdscript
# GameManager.gd — added as autoload in Project Settings
extends Node
var current_level: int = 0
var player_score: int = 0
var is_paused: bool = false
signal game_started()
signal game_paused(paused: bool)
signal level_completed(level_id: int, score: int)
# Access from anywhere: GameManager.current_level
```
### When to Use Each Approach
| Pattern | Use When | Avoid When |
|---------|----------|------------|
| Direct reference (`$Path`) | Parent-child, same scene | Cross-scene communication |
| Signals | Decoupled components, event-driven flow | Simple data access |
| Autoload singleton | Global state (score, settings) | Per-instance data |
| Resource-based | Data that needs saving/loading | Runtime-only behavior |
## ECS-Like Patterns in Godot
Godot isn't a true ECS engine, but you can approximate entity-component systems:
### Entity Registry Pattern
```gdscript
class_name EntityRegistry extends Node
# Tracks all active entities by type
var _entities: Dictionary = {}
func register(entity_id: String, entity: Node) -> void:
if not _entities.has(entity_id):
_entities[entity_id] = []
_entities[entity_id].append(entity)
func unregister(entity_id: String, entity: Node) -> void:
if _entities.has(entity_id):
_entities[entity_id].erase(entity)
func get_all(entity_id: String) -> Array:
return _entities.get(entity_id, [])
```
### Data-Driven with Resources
```gdscript
# EntityData.tres — defines entity properties without code
class_name EntityData extends Resource
@export var entity_name: String = ""
@export var health: int = 100
@export var speed: float = 100.0
@export var components: Array[String] = [] # Component scene paths
```
## Project Structure Patterns
### Feature-Based Organization
```
res://
├── core/ # Engine-level systems (autoloads, event bus)
│ ├── GameManager.gd
│ └── EventBus.gd
├── features/ # Game features as self-contained modules
│ ├── combat/
│ │ ├── scripts/
│ │ ├── scenes/
│ │ └── resources/
│ ├── inventory/
│ │ ├── scripts/
│ │ ├── scenes/
│ │ └── resources/
│ └── dialogue/
├── levels/ # Level scenes and data
├── ui/ # UI components and screens
└── assets/ # Art, audio, fonts
```
### Module Pattern for Large Projects
```gdscript
# FeatureModule.gd — reusable feature registration pattern
class_name FeatureModule extends Node
func initialize() -> void:
pass # Override in subclasses
func cleanup() -> void:
pass # Override in subclasses
# Usage: CombatModule.gd extends FeatureModule
# - Registers signals with EventBus
# - Spawns combat-related autoloads if needed
# - Connects to game lifecycle events
```
## Godot 4.7 Architecture Notes
- **tween_await()** — Use for sequential animations without callback chains in component scripts
- **DrawableTexture2D** — Procedural rendering for debug visualization of entity bounds
- **Perfetto tracing** — Profile architecture decisions with CPU/GPU trace markers
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools can inspect and modify your project live:
- **`scene_tree`** — Inspect scene hierarchy to audit composition patterns
- **`script_read`** / **`script_edit`** — Read and modify architecture scripts
- **`class_db_info`** — Query available methods on Godot classes for integration points
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [Composition Patterns](references/composition-patterns.md)
- [Service Locator vs Signals](references/service-locator-vs-signals.md)
- [ECS Approximation Guide](references/ecs-approximation.md)

View File

@ -0,0 +1,15 @@
# Claude Code extension for godot-architecture
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "designing Godot game architecture"
- "Godot scene composition patterns"
- "ECS patterns in Godot"
- "dependency injection Godot"
- "service locator vs signals"
- "Godot project structure"
tool_permissions:
- read
- write
- bash

View File

@ -0,0 +1,14 @@
# OpenAI / Codex CLI extension for godot-architecture
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "designing Godot game architecture"
- "Godot scene composition patterns"
- "ECS patterns in Godot"
- "dependency injection Godot"
- "service locator vs signals"
- "Godot project structure"

298
godot-audio/SKILL.md Normal file
View File

@ -0,0 +1,298 @@
---
name: godot-audio
description: "Audio patterns for Godot 4.x: AudioStreamPlayer hierarchy, bus mixing, spatial audio (2D/3D), randomization, dynamic music, and AudioListener positioning. Use when implementing sound effects, music systems, or audio mixing in Godot games."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- audio
- sound
- music
- spatial-audio
- bus-mixing
created: 2026-06-21
---
# Audio in Godot
Patterns for game audio implementation.
## AudioStreamPlayer Hierarchy
Godot provides separate nodes for 2D and 3D audio:
```gdscript
# 2D audio — attached near the sound source
# AudioStreamPlayer2D (auto-pans based on position relative to AudioListener2D)
var sfx_player = AudioStreamPlayer2D.new()
sfx_player.stream = preload("res://sounds/footstep.ogg")
add_child(sfx_player)
sfx_player.play()
# 3D audio — auto-attenuates and pans based on distance to AudioListener3D
var sfx_player_3d = AudioStreamPlayer3D.new()
sfx_player_3d.stream = preload("res://sounds/explosion.ogg")
sfx_player_3d.unit_size = 1.0 # Meters per audio unit
add_child(sfx_player_3d)
sfx_player_3d.play()
```
### AudioListener Placement
```gdscript
# Camera typically has AudioListener3D as a child
# The camera's position determines where the "ears" are in 3D space
$Camera3D.add_child(AudioListener3D.new())
# For 2D, attach to the main camera or viewport center
$Camera2D.add_child(AudioListener2D.new())
```
## Bus Mixing
Route audio through buses for independent volume/effects control:
```gdscript
# Configure buses via code (or use Project Settings → Audio)
func setup_audio_buses() -> void:
var bus_count = AudioServer.bus_count
# Find bus index by name
var music_bus = AudioServer.get_bus_index("Music")
var sfx_bus = AudioServer.get_bus_index("SFX")
var ambient_bus = AudioServer.get_bus_index("Ambient")
# Set volume (linear, not dB)
AudioServer.set_bus_volume_db(music_bus, linear_to_db(0.7))
AudioServer.set_bus_volume_db(sfx_bus, linear_to_db(1.0))
AudioServer.set_bus_volume_db(ambient_bus, linear_to_db(0.5))
# Mute individual buses
AudioServer.set_bus_mute(sfx_bus, false)
# Solo for debugging
AudioServer.set_bus_solo(music_bus, true)
```
### Per-Player Bus Assignment
```gdscript
# Assign a player node to a specific bus
$AudioStreamPlayer.bus = "SFX" # By name
# Or by index:
$AudioStreamPlayer.bus = AudioServer.get_bus_index("SFX")
```
## Spatial Audio (3D)
`AudioStreamPlayer3D` automatically handles distance attenuation and stereo panning:
```gdscript
class_name EnemyExplosion extends Node3D
var explosion_player: AudioStreamPlayer3D
func _ready() -> void:
explosion_player = AudioStreamPlayer3D.new()
explosion_player.stream = preload("res://sounds/explosion.ogg")
explosion_player.unit_size = 1.0 # 1 unit = 1 meter
explosion_player.max_distance = 50.0
explosion_player.attenuation_model = AudioStreamPlayer3D.ATTENUATION_INVERSE_DISTANCE
add_child(explosion_player)
func explode() -> void:
explosion_player.play()
# Visual effects here...
```
### Spatial Audio Models
| Model | Use Case |
|-------|----------|
| `ATTENUATION_DISABLED` | UI sounds, non-spatial audio in 3D scenes |
| `ATTENUATION_INVERSE_DISTANCE` | Default — realistic falloff |
| `ATTENUATION_LINEAR_DISTANCE` | Linear volume drop with distance |
## Sound Effect Randomization
Avoid the "machine gun" effect by randomizing pitch and playback:
```gdscript
class_name SFXPool extends Node
@export var sounds: Array[AudioStream] = []
var _players: Array[AudioStreamPlayer] = []
@export var pool_size: int = 8
func _ready() -> void:
for i in range(pool_size):
var player = AudioStreamPlayer.new()
player.bus = "SFX"
add_child(player)
_players.append(player)
func play_random() -> void:
if sounds.is_empty():
return
# Find first available player
for player in _players:
if not player.playing:
player.stream = sounds[randi() % sounds.size()]
# Randomize pitch slightly (0.9 to 1.1)
player.pitch_scale = randf_range(0.9, 1.1)
player.play()
return
# All busy — steal the first one
_players[0].stream = sounds[randi() % sounds.size()]
_players[0].pitch_scale = randf_range(0.9, 1.1)
_players[0].play()
```
## Dynamic Music System
Crossfade between music tracks based on game state:
```gdscript
class_name MusicManager extends Node
var _current_player: AudioStreamPlayer = null
var _next_player: AudioStreamPlayer = null
const FADE_DURATION := 1.5
func _ready() -> void:
_current_player = AudioStreamPlayer.new()
_current_player.bus = "Music"
add_child(_current_player)
_next_player = AudioStreamPlayer.new()
_next_player.bus = "Music"
add_child(_next_player)
func play(track: AudioStream, fade_in: bool = true) -> void:
if _current_player.stream == track and _current_player.playing:
return
# Start next player at silent
_next_player.stream = track
_next_player.volume_db = -80.0 # Effectively silent
_next_player.play()
# Crossfade
var tween = create_tween()
if _current_player.playing:
tween.tween_method(_set_current_volume, 0.0, -80.0, FADE_DURATION)
tween.tween_method(_set_next_volume, -80.0, 0.0, FADE_DURATION)
# Swap roles after fade
await tween.finished
var temp = _current_player
_current_player = _next_player
_next_player = temp
_next_player.stop()
_next_player.volume_db = -80.0
func _set_current_volume(vol: float) -> void:
if _current_player:
_current_player.volume_db = vol
func _set_next_volume(vol: float) -> void:
if _next_player:
_next_player.volume_db = vol
func stop(fade_out: bool = true) -> void:
if not _current_player or not _current_player.playing:
return
if fade_out:
var tween = create_tween()
tween.tween_method(_set_current_volume, _current_player.volume_db, -80.0, FADE_DURATION)
await tween.finished
_current_player.stop()
else:
_current_player.stop()
```
## Accessibility Patterns
### Subtitle/SRT Sync
```gdscript
class_name DialogueAudio extends AudioStreamPlayer
signal subtitle_shown(text: String)
signal subtitle_hidden()
@export var subtitles: Array[SubtitleCue] = []
var _active_cue_index: int = -1
class_name SubtitleCue extends RefCounted
var time: float = 0.0
var text: String = ""
func play() -> void:
super.play()
_active_cue_index = -1
func _process(_delta: float) -> void:
if not playing:
return
var elapsed = get_playback_position()
while _active_cue_index + 1 < subtitles.size():
var cue = subtitles[_active_cue_index + 1]
if elapsed >= cue.time:
_active_cue_index += 1
subtitle_shown.emit(cue.text)
else:
break
```
### Audio Cues for Visually Impaired Players
```gdscript
# Emit directional audio cues for important events
func play_directional_cue(direction: Vector3, sound: AudioStream) -> void:
var player = AudioStreamPlayer3D.new()
player.stream = sound
player.unit_size = 1.0
player.max_distance = 20.0
# Position relative to listener
player.global_position = $AudioListener3D.global_position + direction.normalized() * 2.0
add_child(player)
player.play()
# Auto-cleanup after sound ends
player.finished.connect(func(): player.queue_free())
```
## Godot 4.7 Audio Notes
- **AudioServer** — Improved bus effect chain for real-time DSP processing
- **Vorbis/Opus** — Opus codec support for higher quality at lower bitrates
- **Spatial audio** — Enhanced HRTF filtering for headphone-based 3D audio
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools can inspect and modify your project live:
- **`script_read`** / **`script_edit`** — Read and modify audio scripts
- **`project_info`** — Check configured audio buses and settings
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [Audio Bus Configuration](references/audio-buses.md)
- [Spatial Audio Deep Dive](references/spatial-audio.md)
- [Dynamic Music Patterns](references/dynamic-music.md)

View File

@ -0,0 +1,15 @@
# Claude Code extension for godot-audio
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "implementing audio in Godot"
- "Godot sound effects"
- "audio bus mixing Godot"
- "spatial audio Godot"
- "dynamic music Godot"
- "sound pool Godot"
tool_permissions:
- read
- write
- bash

View File

@ -0,0 +1,14 @@
# OpenAI / Codex CLI extension for godot-audio
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "implementing audio in Godot"
- "Godot sound effects"
- "audio bus mixing Godot"
- "spatial audio Godot"
- "dynamic music Godot"
- "sound pool Godot"

View File

@ -0,0 +1,138 @@
---
name: godot-brainstorming
description: "Architecture decision patterns for Godot game development across genres (RPG, platformer, RTS, puzzle). Covers scene organization, state machines, network architecture, and component composition. Use when designing a Godot game's architecture or making Godot-specific technical decisions."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- architecture
- state-machines
- multiplayer
created: 2026-06-20
---
# Godot Brainstorming & Architecture
Help design game architecture using Godot's node-based composition model.
## Scene Organization Strategies
### Composition Over Inheritance
Godot's strength is scene composition. Prefer composing small, focused scenes over deep inheritance hierarchies.
```gdscript
# Instead of: PlayerCharacter extends CharacterBody2D with all abilities
# Use: Compose a player from reusable components
# - BaseCharacter (CharacterBody2D) — movement, health
# - InventoryComponent (Node) — inventory logic
# - AbilitySystem (Node) — ability management
```
### Scene Hierarchy Patterns
| Pattern | When to Use | Example |
|---------|-------------|---------|
| Single root scene | Entire game/level | Main.tscn, Level1.tscn |
| Component scenes | Reusable behaviors | HealthBar.tscn, ProgressBar.tscn |
| Entity templates | Instantiable objects | Enemy.tscn, Collectible.tscn |
| UI compositions | Complex screens | MainMenu.tscn, SettingsPanel.tscn |
## State Machine Patterns
### AnimationTree State Machine
```gdscript
@export var animation_tree: AnimationTree
@onready var state_machine: AnimationNodeStateMachinePlayback = \
animation_tree.get("parameters/playback")
func change_state(new_state: String):
state_machine.travel(new_state)
```
### Custom State Machine
For complex AI or game logic:
```gdscript
class_name GameState extends RefCounted
var current_state: String = "menu"
func transition_to(new_state: String):
var prev = current_state
current_state = new_state
on_transition(prev, new_state)
func on_transition(from: String, to: String):
match to:
"playing": handle_playing()
"paused": handle_paused()
```
## Network Architecture Decisions
### Authoritative Server Model
- Best for competitive multiplayer
- Server validates all actions
- Client sends inputs, server simulates
### Peer-to-Peer Model
- Best for casual/co-op games
- Lower latency for local interactions
- Higher cheating risk
### Godot 4.7: Steam Frame Integration
For multiplayer with Steam, use the new Steam Frame configuration for lobby management and overlay features.
## Genre-Specific Patterns
### RPG Architecture
- Entity-component system for character stats
- Dialogue tree system (use `RichTextLabel` + custom parser)
- Save/load system using `JSON` or `Resource` serialization
### Platformer Architecture
- Level streaming for large worlds
- Input buffering for responsive controls
- TileMapLayer for layered level design
### RTS Architecture
- Pathfinding with A* on NavigationMesh
- Unit grouping and selection system
- Command queue pattern for input handling
## Godot 4.7 Brainstorming Considerations
- **tween_await()** — Use for sequential animation chains instead of nested callbacks
- **VirtualJoystick** — Built-in mobile controls simplify mobile game development
- **Control offset transforms** — New UI animation system enables complex transitions without custom shaders
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools can inspect your live project:
- **`scene_tree`** — Inspect current scene hierarchy for architecture decisions
- **`class_db_info`** — Query class capabilities to choose the right node type
- **`map_project`** — Generate a project graph to visualize architecture
- **`project_settings_read`** — Check current project configuration during brainstorming
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [Scene Composition Patterns](references/scene-composition.md)
- [State Machine Implementations](references/state-machines.md)
- [Network Architecture Guide](references/network-architecture.md)

View File

@ -0,0 +1,13 @@
# Claude Code extension for godot-brainstorming
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "designing Godot game architecture"
- "Godot scene organization"
- "Godot state machine patterns"
- "Godot multiplayer network architecture"
- "Godot game design decisions"
tool_permissions:
- read
- write

View File

@ -0,0 +1,13 @@
# OpenAI / Codex CLI extension for godot-brainstorming
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "designing Godot game architecture"
- "Godot scene organization"
- "Godot state machine patterns"
- "Godot multiplayer network architecture"
- "Godot game design decisions"

181
godot-code-review/SKILL.md Normal file
View File

@ -0,0 +1,181 @@
---
name: godot-code-review
description: "Reviews Godot projects for common pitfalls, performance issues, memory leaks, and anti-patterns. Provides a performance checklist, code style guidelines, and memory management review. Use when reviewing Godot GDScript or C# code."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- code-review
- performance
- anti-patterns
created: 2026-06-20
---
# Godot Code Review
Systematic review patterns for Godot game development projects.
## Common Pitfalls & Anti-Patterns
### _process() Abuse
```gdscript
# BAD: Using _process for everything
func _process(delta):
# Movement that could be in _physics_process
position += velocity * delta
# Animation updates
update_animation()
# GOOD: Use the right callback
func _physics_process(delta):
# Physics-related movement
position += velocity * delta
func _process(delta):
# Non-physics updates (UI, animations)
update_animation()
```
### Hardcoded Paths
```gdscript
# BAD
var sprite = get_node("Character/Sprite2D")
# GOOD
@onready var sprite = $Sprite2D as Sprite2D
```
### Signal Leaks
```gdscript
# BAD: Forgetting to disconnect signals
func _ready():
EventBus.some_signal.connect(on_some_signal)
# If this node is freed, the callback still holds a reference
# GOOD: Auto-disconnect when using method references
func _ready():
EventBus.some_signal.connect(on_some_signal) # Godot auto-handles method refs
# GOOD: Manual disconnect for lambdas
func _ready():
var callback = func(): handle_event()
EventBus.some_signal.connect(callback)
func _exit_tree():
EventBus.some_signal.disconnect(callback)
```
## Performance Review Checklist
### Callback Selection
| Use Case | Correct Callback | Why |
|----------|-----------------|-----|
| Physics movement | `_physics_process` | Fixed timestep, syncs with physics server |
| Non-physics updates | `_process` | Frame-rate dependent, for UI/rendering |
| One-time setup | `_ready` | Called once when node enters tree |
| Frame before ready | `_enter_tree` | Called before `_ready`, even if tree paused |
| Cleanup | `_exit_tree` | Called when node is removed from tree |
| Physics tick (input) | `_physics_input_event` | For character body input handling |
### Memory Management
- [ ] All `queue_free()` calls have corresponding signal disconnections
- [ ] No circular references between nodes
- [ ] Large resources (textures, audio) unloaded when no longer needed
- [ ] Scene instances properly freed after use
- [ ] Autoload nodes don't hold unnecessary references
### Code Style Guidelines
#### GDScript Style
- Use 4-space indentation (not tabs)
- Follow [GDScript Style Guide](https://godotengine.org/doc/en/latest/tutorials/scripting/gdscript/gdscript_styleguide.html)
- Use `@export` for inspector-exposed variables
- Use `@onready` for node paths
- Type annotations on function parameters and return values
- Use `const` for compile-time constants
#### C# Style
- Use PascalCase for class names, camelCase for members
- Use Godot's `[Export]` attributes for inspector properties
- Implement `_ExitTree()` for cleanup of event subscriptions
- Prefer structs for small value types to avoid GC pressure
### Memory Management Patterns
```gdscript
# Proper node lifecycle
func spawn_enemy():
var enemy = enemy_scene.instantiate()
add_child(enemy)
# Enemy manages its own cleanup via queue_free() on death
func load_level(level_path: String):
# Unload previous level
if current_level:
current_level.queue_free()
current_level = null
# Load new level
var scene = load(level_path)
current_level = scene.instantiate()
add_child(current_level)
```
## Godot 4.7 Code Review Notes
- Check for **HDR rendering** configuration in project settings
- Review **AreaLight3D** usage for new 4.7 features (modes, intensity profiles)
- Verify **VirtualJoystick** is used instead of custom implementations on mobile
- Check that **tween_await()** replaces nested callback patterns where applicable
## Quick Review Commands
```bash
# Find all _process calls that might be physics-related
grep -rn "_process" --include="*.gd" scripts/
# Find hardcoded get_node calls (should use @onready)
grep -rn "get_node(" --include="*.gd" scripts/
# Find potential signal leaks (connect without disconnect pattern)
grep -rn "\.connect(" --include="*.gd" scripts/
# Find queue_free usage to verify cleanup
grep -rn "queue_free" --include="*.gd" scripts/
```
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools enhance code review:
- **`script_read`** — Read any script file for review without opening the editor
- **`script_validate`** — Run syntax validation with `checkDeprecated: true` and `suggestTweenAwait: true`
- **`file_search`** — Search across all project files for patterns (e.g., `queue_free`, signal leaks)
- **`file_browse`** — List project directories to verify structure
- **`class_db_info`** — Verify methods/properties exist before flagging issues
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [GDScript Style Guide](references/gdscript-styleguide.md)
- [Performance Optimization Guide](references/performance-checklist.md)
- [Memory Management Patterns](references/memory-patterns.md)

View File

@ -0,0 +1,13 @@
# Claude Code extension for godot-code-review
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "reviewing Godot code"
- "Godot performance checklist"
- "Godot memory leak detection"
- "Godot anti-patterns"
- "Godot code style review"
tool_permissions:
- read
- bash

View File

@ -0,0 +1,13 @@
# OpenAI / Codex CLI extension for godot-code-review
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "reviewing Godot code"
- "Godot performance checklist"
- "Godot memory leak detection"
- "Godot anti-patterns"
- "Godot code style review"

View File

@ -0,0 +1,243 @@
---
name: godot-csharp-patterns
description: "C# Mono patterns for Godot 4.x: Export attributes, signal handling, performance optimization, GDExtension interop, and best practices for first-class C# support. Use when writing C# code in Godot."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- csharp
- mono
- performance
created: 2026-06-20
---
# C# Patterns for Godot 4.x
Best practices for Godot's first-class C# (Mono) support.
## Export Attributes
### Inspector Properties
```csharp
using Godot;
public partial class Player : CharacterBody2D
{
// Basic export
[Export] public float Speed { get; set; } = 200f;
// With range
[Export(PropertyHint.Range, "0,100,0.5")]
public float JumpForce { get; set; } = 400f;
// File path
[Export(FileFilter = "*.png,*.svg")]
public string IconPath { get; set; } = "";
// Resource export
[Export] public PackedScene BulletScene { get; set; }
// Enum with hint
[Export] public MovementMode MoveMode { get; set; }
public enum MovementMode
{
Walk,
Run,
Sprint
}
}
```
### Export Categories
```csharp
[ExportGroup("Combat")]
[Export] public int AttackDamage { get; set; } = 10;
[Export] public float AttackCooldown { get; set; } = 0.5f;
[ExportGroup("Movement")]
[Export] public float MoveSpeed { get; set; } = 200f;
[Export] public float JumpForce { get; set; } = 400f;
```
## Signal Handling
### C# Event Syntax vs Godot Signals
```csharp
// Godot signal declaration in GDScript (autoload)
// class_name EventBus extends Node
// signal player_died(Vector2 position)
// C# subscriber
public override void _Ready()
{
// Method reference (recommended — auto-disconnect on free)
EventBus.PlayerDied += OnPlayerDied;
// Lambda (requires manual disconnect)
EventBus.PlayerDied += (pos) => GD.Print($"Died at {pos}");
}
public override void _ExitTree()
{
// Clean up lambda subscriptions
EventBus.PlayerDied -= OnPlayerDied;
}
private void OnPlayerDied(Vector2 position)
{
GD.Print($"Player died at {position}");
}
```
### Using Godot.Signal for C#-Side Signals
```csharp
public partial class Enemy : CharacterBody2D
{
[Signal] public delegate void HealthChangedEventHandler(int newHealth);
[Signal] public delegate void DefeatedEventHandler();
private int health = 100;
public void TakeDamage(int amount)
{
health -= amount;
EmitSignal(SignalName.HealthChanged, health);
if (health <= 0)
{
EmitSignal(SignalName.Defeated);
QueueFree();
}
}
}
```
## Performance Considerations
### Avoiding GC Pressure
```csharp
// Bad: Creates garbage every frame
public override void _Process(double delta)
{
var text = $"Score: {score}"; // String allocation
label.Text = text;
}
// Good: Reuse objects, avoid allocations
private readonly StringBuilder _sb = new();
public override void _Process(double delta)
{
_sb.Clear();
_sb.Append("Score: ");
_sb.Append(score);
label.Text = _sb.ToString();
}
```
### Structs vs Classes
```csharp
// Use structs for small, short-lived data
public struct Vector2Int
{
public int X { get; }
public int Y { get; }
public Vector2Int(int x, int y) => (X, Y) = (x, y);
}
// Use classes for objects with identity/lifetime
public class PlayerData : Resource
{
public string Name { get; set; }
public int Level { get; set; }
}
```
### Instance Pooling
```csharp
public partial class BulletPool : Node
{
private readonly Queue<Bullet> _pool = new();
private readonly PackedScene _bulletScene;
public BulletPool(PackedScene bulletScene) => _bulletScene = bulletScene;
public Bullet Get()
{
if (_pool.TryDequeue(out var bullet))
{
bullet.Visible = true;
return bullet;
}
return _bulletScene.Instantiate<Bullet>();
}
public void Return(Bullet bullet)
{
bullet.Visible = false;
bullet.QueueFree();
_pool.Enqueue(bullet);
}
}
```
## GDExtension Interop
### Calling GDScript from C#
```csharp
// GDScript (autoload "GameData")
// class_name GameData extends Node
// var high_score: int = 0
// C# access
var gameData = Engine.GetSingleton("GameData") as GodotObject;
var highScore = gameData.Get("high_score");
```
### Exposing C# to GDScript via GDExtension
Create a `.gdextension` file to expose C# classes to GDScript projects.
## Godot 4.7 C# Notes
- C# fully supports new 4.7 features including VirtualJoystick and tween_await()
- Use `await` with tweens for async animation sequences
- Export attributes work the same — no changes in 4.7
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools can inspect and modify your project live:
- **`script_read`** — Read C# script files for review
- **`script_edit`** — Update C# scripts directly
- **`script_validate`** — Validate C# scripts for syntax errors
- **`scene_tree`** — Inspect scene hierarchy to verify node composition
- **`class_db_info`** — Query Godot class info for C# interop patterns
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [C# API Reference](references/csharp-api.md)
- [Performance Guide](references/performance.md)
- [GDExtension Guide](references/gdextension.md)

View File

@ -0,0 +1,14 @@
# Claude Code extension for godot-csharp-patterns
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "writing C# code in Godot"
- "Godot Mono / C# patterns"
- "Export attributes in Godot"
- "Godot GDExtension interop"
- "Godot C# performance optimization"
tool_permissions:
- read
- write
- bash

View File

@ -0,0 +1,13 @@
# OpenAI / Codex CLI extension for godot-csharp-patterns
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "writing C# code in Godot"
- "Godot Mono / C# patterns"
- "Export attributes in Godot"
- "Godot GDExtension interop"
- "Godot C# performance optimization"

218
godot-debugging/SKILL.md Normal file
View File

@ -0,0 +1,218 @@
---
name: godot-debugging
description: "Debugging workflow for Godot 4.x: print vs push_error, editor output window, debugger panel, profiling with built-in profiler, network debugging, export/build troubleshooting. Use when debugging Godot games or resolving Godot-specific errors."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- debugging
- profiling
- error-handling
created: 2026-06-20
---
# Godot Debugging Guide
Comprehensive debugging workflow and common issue resolution for Godot 4.x.
## Output Methods
### print() vs push_error() vs push_warning()
```gdscript
# print() — General output, filtered in editor output
print("Debug: position = ", position)
print("Debug: player health = ", player_health)
# printf-style formatting
printf("Player %s has %d health", player_name, health)
# push_error() — Shows in red, includes file and line number
push_error("Failed to load resource: ", resource_path)
# push_warning() — Shows in yellow, non-fatal issues
push_warning("Missing animation for state: ", state)
# load() returns null on failure — check before use
var data = load(risky_path)
if data == null:
push_error("Could not load resource at: %s" % risky_path)
# Or with ResourceLoader for error codes
var err, data = ResourceLoader.load_interactive(risky_path)
if err != OK:
push_error("Failed to load: %s (error code %d)" % [risky_path, err])
```
### Editor Output Window
Access via **Window > Debugger** or **Debug > Start Debugging**. The output panel shows:
- Standard print output (gray)
- Warnings (yellow)
- Errors (red) with file/line info
- Custom logger messages
## Debugger Panel
### Built-in Debugger Features
1. **Scene Tree** — Visual tree of current scene with property inspection
2. **Resources** — List of loaded resources, check for leaks
3. **Profiler** — CPU and GPU usage breakdown
4. **Remote Transform** — Edit node properties in running scenes
5. **Network Profiler** — Monitor multiplayer traffic
### Remote Debugging
```gdscript
# Connect to remote scene tree from editor
# When running in debug mode (F6), the editor connects automatically
# Access remote nodes
func get_remote_node(path: String):
var node = get_node(path) # Works on local and remote
return node
```
## Profiling Workflow
### CPU Profiler
```
Profile > Start Profiling in the editor
Then view: Profile > CPU Usage or Profile > Frame Time Graph
```
Key metrics:
- **Frame time** — Should be under 16.67ms for 60fps
- **Physics FPS** — Should match target (usually 60)
- **Script time** — Time spent in GDScript/C# code
- **Draw calls** — Number of GPU draw calls per frame
### Memory Profiler
```
Profile > Memory Usage
Check for:
- Growing resource count over time
- Unfreed scene instances
- Large textures not being unloaded
```
### GPU Profiler
```
Project Settings > Rendering > Debug > GPU Profiling = On
Then: Profile > GPU Usage
```
## Common Error Patterns & Fixes
### "Script class 'XXX' does not exist in current project"
- **Cause:** Autoload script reference broken after rename/move
- **Fix:** Re-add the autoload in Project Settings > Autoload
### "Cannot instantiate non-imported scene: res://..."
- **Cause:** Scene file not saved or path is incorrect
- **Fix:** Open the scene in editor and save it, verify path
### "Invalid get index 'XXX' (on base: 'Node2D')"
- **Cause:** Accessing a property that doesn't exist on the node
- **Fix:** Check node type and available properties, use `has_method()` or `is()` checks
### "Failed to connect signal"
- **Cause:** Method name mismatch or signal doesn't exist
- **Fix:** Verify signal exists on source node, check method signature matches
### Export/Build Errors
| Error | Cause | Fix |
|-------|-------|-----|
| "Export failed with code -1" | Missing export preset config | Re-create export preset |
| "Android build failed" | SDK/NDK path wrong | Check Project Settings > Android |
| "HTML5 export failed" | Emscripten not installed | Install Godot HTML5 export template |
| "Windows export failed" | MSVC not installed | Install Visual Studio Build Tools |
## Network Debugging
### Multiplayer API Inspection
```gdscript
# Check connection status
print("Connected: ", Network.multiplayer.is_connected_to_server())
print("Peer count: ", Network.multiplayer.get_peers().size())
# Log RPC calls for debugging
func _rpc_debug_message(msg):
print("[RPC] ", msg)
# Monitor network traffic
# Use Profile > Network Profiler in the editor
```
### Lag Compensation Debugging
```gdscript
# Log timing information
print("Server time: ", get_tree().get_network_unique_id())
print("Local latency: ", Network.multiplayer.get_connection_status())
```
## Godot 4.7 Debugging Notes
- **Perfetto tracing** — New built-in Android profiler for detailed performance analysis
- **New editor log improvements** — Better error messages with more context
- **tween_await() debugging** — Use `await` breakpoints in C# debugger for animation sequences
## Debug Utility Functions
```gdscript
class_name DebugUtils extends Node
static func draw_rect_debug(rect: Rect2, color: Color, lifetime: float = 0.0):
# Draw debug rectangle (use with CanvasItem.draw_rect)
pass
static func log_tree(node: Node, indent: int = 0):
# Print scene tree for debugging
var prefix = " ".repeat(indent)
print(prefix + node.name, " (", node.get_class(), ")")
for child in node.get_children():
log_tree(child, indent + 1)
static func check_node_ready(node: Node):
# Verify node is ready before accessing children
if not node.is_inside_tree():
push_error("Node not in tree: ", node.name)
return node.is_inside_tree()
```
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools provide live debugging capabilities:
- **`debugger_output`** — Read real-time debugger output from the running scene
- **`output_log`** — Read Godot's output log with level filtering (error/warning/info)
- **`scene_run` / `scene_stop`** — Start and stop scenes for testing fixes
- **`pause_resume`** — Pause execution to inspect state at breakpoints
- **`script_validate`** — Validate scripts without leaving the agent workflow
- **`scene_tree`** — Inspect runtime scene hierarchy for missing nodes
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [Debugging Guide](references/debugging-workflow.md)
- [Profiler Deep Dive](references/profiler-guide.md)
- [Network Debugging](references/network-debug.md)

View File

@ -0,0 +1,13 @@
# Claude Code extension for godot-debugging
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "debugging Godot errors"
- "profiling Godot performance"
- "resolving Godot crashes"
- "network debugging in Godot"
- "export build troubleshooting"
tool_permissions:
- read
- bash

View File

@ -0,0 +1,13 @@
# OpenAI / Codex CLI extension for godot-debugging
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "debugging Godot errors"
- "profiling Godot performance"
- "resolving Godot crashes"
- "network debugging in Godot"
- "export build troubleshooting"

View File

@ -0,0 +1,251 @@
---
name: godot-dialog-systems
description: "Dialogue system patterns for Godot 4.x: resource-based dialogue trees, branching narratives, typewriter text effects, voice sync, and portrait management. Use when building NPC conversations, quest dialogue, or narrative systems in Godot."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- dialogue
- narrative
- branching
- typewriter
created: 2026-06-21
---
# Dialogue Systems in Godot
Patterns for building conversation and narrative systems.
## Resource-Based Dialogue Data
Store dialogue as `Resource` files for easy editing and versioning:
```gdscript
class_name DialogueLine extends Resource
@export var speaker: String = ""
@export var text: String = ""
@export var voice_file: String = "" # Optional audio cue
@export var portrait_index: int = -1
@export var next_line_id: String = "" # Branch target
@export var choices: Array[DialogueChoice] = []
class_name DialogueChoice extends Resource
@export var choice_text: String = ""
@export var target_line_id: String = ""
@export var requires_flag: String = "" # Optional quest flag check
```
### Dialogue Script Resource
```gdscript
class_name DialogueScript extends Resource
@export var script_name: String = ""
@export var start_line_id: String = ""
@export var lines: Dictionary = {} # id -> DialogueLine
```
## Dialogue Manager
Central system for playing dialogue sequences:
```gdscript
class_name DialogueManager extends Node
signal dialogue_started(speaker: String, text: String)
signal dialogue_ended()
signal choice_presented(choices: Array[DialogueChoice])
signal typewriter_progress(chars_shown: int, total_chars: int)
var current_script: DialogueScript = null
var current_line: DialogueLine = null
var is_playing: bool = false
var flags: Dictionary = {} # Quest/state flags
func play(script: DialogueScript) -> void:
current_script = script
is_playing = true
_show_line(script.start_line_id)
func _show_line(line_id: String) -> void:
if not current_script.lines.has(line_id):
push_error("DialogueManager: Line '%s' not found" % line_id)
end_dialogue()
return
current_line = current_script.lines[line_id] as DialogueLine
dialogue_started.emit(current_line.speaker, current_line.text)
if current_line.choices.size() > 0:
choice_presented.emit(current_line.choices)
else:
# Auto-advance after typewriter
_auto_advance()
func advance() -> void:
if not current_line:
return
if current_line.next_line_id.is_empty():
end_dialogue()
else:
_show_line(current_line.next_line_id)
func select_choice(choice_index: int) -> void:
if not current_line or choice_index >= current_line.choices.size():
return
var choice = current_line.choices[choice_index]
# Check flag requirement
if not choice.requires_flag.is_empty():
if not flags.get(choice.requires_flag, false):
return # Player hasn't met requirement
_show_line(choice.target_line_id)
func end_dialogue() -> void:
is_playing = false
current_script = null
current_line = null
dialogue_ended.emit()
func set_flag(flag: String, value: bool = true) -> void:
flags[flag] = value
```
## Typewriter Text Effect
Animate text appearing character-by-character:
```gdscript
class_name DialogueText extends RichTextLabel
signal typing_complete()
@export var type_speed: float = 0.03 # Seconds per character
var _type_timer: float = 0.0
var _full_text: String = ""
var _char_index: int = 0
var _is_typing: bool = false
var _waiting_for_input: bool = false
func start_typing(text: String) -> void:
_full_text = text
_char_index = 0
_is_typing = true
_waiting_for_input = false
text = ""
func _process(delta: float) -> void:
if not _is_typing:
return
if _waiting_for_input:
return # Wait for player to click to advance
_type_timer += delta
if _type_timer >= type_speed and _char_index < _full_text.length():
_type_timer = 0.0
_char_index += 1
text = _full_text.substr(0, _char_index)
DialogueManager.typewriter_progress.emit(_char_index, _full_text.length())
if _char_index >= _full_text.length():
_is_typing = false
typing_complete.emit()
# Skip to end on input
func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.pressed:
if _is_typing:
# Show remaining text instantly
text = _full_text
_char_index = _full_text.length()
_is_typing = false
typing_complete.emit()
```
## Branching Dialogue Pattern
Handle conditional branches based on game state:
```gdscript
# In dialogue script editor or code:
# Line "greeting":
# text: "Hello there, {0}!"
# choices:
# - text: "Hi!" → target: "friendly_chat"
# - text: "Who are you?" → target: "hostile_chat"
# - text: "I need help." → target: "quest_offer" (requires: met_quest_giver)
func _resolve_branch(line_id: String) -> String:
var line = current_script.lines[line_id] as DialogueLine
if line.choices.size() == 0:
return line.next_line_id
# Filter choices by flags
var valid_choices: Array[DialogueChoice] = []
for choice in line.choices:
if choice.requires_flag.is_empty():
valid_choices.append(choice)
elif flags.get(choice.requires_flag, false):
valid_choices.append(choice)
# If no valid choices, use default next_line_id
if valid_choices.size() == 0:
return line.next_line_id
return "" # Wait for player choice
```
## Portrait Management
Display character portraits during dialogue:
```gdscript
class_name DialoguePortrait extends Container
@export var portrait_frames: Array[Texture2D] = []
func show_portrait(index: int) -> void:
if index < 0 or index >= portrait_frames.size():
return
# Update portrait sprite with frame[index]
pass
# Animate portrait reactions (idle, talking, surprised)
enum PortraitState { IDLE, TALKING, SURPRISED }
var current_state: PortraitState = PortraitState.IDLE
```
## Godot 4.7 Dialogue Notes
- **tween_await()** — Animate dialogue panel entrance/exit smoothly: `await get_tree().create_tween().tween_property(panel, "modulate:a", 1.0).finished`
- **RichTextLabel** — Improved bbcode for dialogue formatting (`[wave]`, `[shake]`, `[carousel]`)
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools can inspect and modify your project live:
- **`script_read`** / **`script_edit`** — Read and modify dialogue scripts
- **`scene_tree`** — Inspect dialogue UI node hierarchy
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [Dialogue Resource Design](references/dialogue-resources.md)
- [Branching Narrative Patterns](references/branching-narrative.md)
- [Typewriter Effect Variants](references/typewriter-effects.md)

View File

@ -0,0 +1,15 @@
# Claude Code extension for godot-dialog-systems
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "building dialogue systems in Godot"
- "Godot branching narrative"
- "NPC conversation Godot"
- "typewriter text effect Godot"
- "resource-based dialogue Godot"
- "voice sync Godot"
tool_permissions:
- read
- write
- bash

View File

@ -0,0 +1,14 @@
# OpenAI / Codex CLI extension for godot-dialog-systems
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "building dialogue systems in Godot"
- "Godot branching narrative"
- "NPC conversation Godot"
- "typewriter text effect Godot"
- "resource-based dialogue Godot"
- "voice sync Godot"

View File

@ -0,0 +1,182 @@
---
name: godot-gdscript-patterns
description: "GDScript best practices for Godot 4.x: node composition, typed signals, scene instantiation, resource management, singletons, event buses, and common design patterns. Use when writing or reviewing GDScript code in Godot."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- gdscript
- design-patterns
- signals
created: 2026-06-20
---
# GDScript Patterns for Godot 4.x
Best practices and patterns for writing clean, maintainable GDScript code.
## Node Hierarchy Best Practices
### Composition Over Inheritance
```gdscript
# Good: Small, focused scenes composed together
# Player.tscn contains:
# - CharacterBody2D (root)
# - Sprite2D (visual)
# - CollisionShape2D (physics)
# - AnimatedSprite2D (animation)
# - HealthComponent (Node — logic)
# - InventoryComponent (Node — logic)
# Avoid: Deep inheritance chains
# Player extends CharacterBody2D extends Node2D...
```
### Scene Instantiation Patterns
```gdscript
# preload() — Compile-time, for always-needed resources
@onready var bullet_scene = preload("res://scenes/entities/bullet.tscn")
# load() — Runtime, for conditional loading
var dialog_scene = load("res://scenes/ui/dialog.tscn")
# instantiate() — Create instance from scene
func spawn_bullet():
var bullet = bullet_scene.instantiate()
add_child(bullet)
```
## Typed Signals (Godot 4.x)
### Declaring Typed Signals
```gdscript
# Godot 4.x typed signal syntax
signal health_changed(new_health: int, max_health: int)
signal player_died(position: Vector2)
signal item_collected(item_data: Resource)
```
### Connecting Signals
```gdscript
# Auto-connect in editor (rename method to on_NodeName_signal)
func _on_health_changed(new_health: int, _max_health: int):
update_health_bar(new_health)
# Manual connection
func _ready():
enemy.health_depleted.connect(_on_enemy_defeated)
# Callable syntax (Godot 4.x)
button.pressed.connect(func: do_something())
```
## Scene Loading Patterns
### Resource Loading
```gdscript
# Preload for compile-time constants
const PlayerScene = preload("res://scenes/entities/player.tscn")
# Load for runtime/conditional loading
func load_level(level_name: String):
var path = "res://levels/%s.tscn" % level_name
var scene = load(path)
if scene:
current_level = scene.instantiate()
add_child(current_level)
```
### Resource Management
```gdscript
# Use Resource for game data
@export_file("*.tres") var player_data: String
var data: PlayerData = load(player_data) as PlayerData
# Scene packs for level data
var level_pack = preload("res://resources/data/level_pack.tscn")
```
## Common Patterns
### Singleton (Autoload) Pattern
```gdscript
# Add to Project Settings > Autoload as "GameData"
class_name GameData extends Node
var player_score: int = 0
var high_score: int = 0
func add_score(points: int):
player_score += points
if player_score > high_score:
high_score = player_score
```
### Event Bus Pattern
```gdscript
# Add to Project Settings > Autoload as "EventBus"
class_name EventBus extends Node
signal game_started()
signal level_completed(level_id: int)
signal player_died()
signal game_paused(paused: bool)
# Usage in any script
func _ready():
EventBus.level_completed.connect(on_level_completed)
```
### Node Path Best Practices
```gdscript
# Good: Use @onready for scene tree paths
@onready var health_bar = $UI/HealthBar as ProgressBar
@onready var sprite = $Sprite2D as Sprite2D
# Avoid: Hardcoded paths
var health_bar = get_node("UI/HealthBar") # Fragile to hierarchy changes
```
## Godot 4.7 GDScript Notes
- **DrawableTexture2D** — Create dynamic textures at runtime for procedural rendering
- **tween_await()** — Use await with tweens for sequential animations
- **Typed signals** are now the standard — prefer typed over untyped
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools can inspect and modify your project live:
- **`script_read`** — Read any script file from the project
- **`script_edit`** — Write updated script contents directly to disk
- **`script_validate`** — Validate GDScript syntax with `suggestTweenAwait: true` for Godot 4.7 migration
- **`scene_tree`** — Inspect the current scene hierarchy
- **`class_db_info`** — Query available methods/properties on any Godot class
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [GDScript Style Guide](references/gdscript-style.md)
- [Signal Patterns Deep Dive](references/signals.md)
- [Resource Management](references/resource-management.md)

View File

@ -0,0 +1,14 @@
# Claude Code extension for godot-gdscript-patterns
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "writing GDScript code"
- "Godot signals and events"
- "node composition in Godot"
- "GDScript design patterns"
- "resource management in Godot"
tool_permissions:
- read
- write
- bash

View File

@ -0,0 +1,13 @@
# OpenAI / Codex CLI extension for godot-gdscript-patterns
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "writing GDScript code"
- "Godot signals and events"
- "node composition in Godot"
- "GDScript design patterns"
- "resource management in Godot"

View File

@ -0,0 +1,304 @@
---
name: godot-headless-workflow
description: "AI-agent development loop for Godot 4.x without opening the editor: headless validation (--check-only), project import, gdUnit4 testing, safe hand-editing of text scenes (.tscn), CLI export pipeline for Android/iOS, mobile checklist, and RevenueCat plugin integration. Use when building or modifying a Godot game as a coding agent, running Godot in CI, validating GDScript changes, exporting mobile builds, or integrating in-app purchases."
license: MIT
compatibility:
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- headless
- ci
- ai-agents
- testing
- export
- revenuecat
created: 2026-08-24
---
# Godot Headless Workflow (Agent Development Loop)
The complete edit→validate→test→run→export loop for Godot projects **without ever opening
the editor**. Every change must pass the headless gate before you claim it works.
## Golden Rules
1. **Never trust an edit you haven't validated headlessly.** A script that looks right but
was never parsed is not done.
2. **GDScript only** unless the project already uses C# (see `godot-csharp-patterns`). Pin the
engine version in the project's `AGENTS.md` (e.g., `Godot 4.7.2 stable, GDScript`).
3. **Scenes are text** (`.tscn`/`.tres`) — editable by hand *with care*, or better, generated
by tool scripts. See §Text Asset Safety.
4. **Never touch `.godot/`** — it's a regenerable cache.
5. **The editor GUI is optional.** Use it (or the package's MCP bridge) only for visual
layout iteration; logic, tests, and builds are all CLI work.
## Prerequisites
```bash
godot --version # e.g. 4.7.stable.official — confirm once per machine/session
```
The Godot binary must be on PATH **in the shell you run commands from** (Git Bash on Windows,
native bash on Linux). Export templates are needed only for §CLI Export:
| Host | Template install path |
|---|---|
| Windows | `%APPDATA%\Godot\export_templates\<engine-version>\` |
| Linux | `~/.local/share/godot/export_templates/<engine-version>/` |
| macOS | `~/Library/Application Support/Godot/export_templates/<engine-version>/` |
## Cross-Platform (Windows ↔ Ubuntu hosts)
All Godot CLI flags, gdUnit4 runners (`runtest.cmd` / `runtest.sh`), `res://` paths, and
export behavior are **identical across hosts** — projects stay fully portable. Watch only:
1. **Line endings kill `.sh` scripts**: if `verify.sh` is checked out with CRLF on Linux it
fails with `$'\r': command not found`. Add to every game repo's `.gitattributes`:
`*.sh text eol=lf` (and commit it from day one).
2. **Runner extension differs by host** — `runtest.cmd` on Windows, `runtest.sh` elsewhere;
detect or parameterize instead of hardcoding one.
3. **Don't mix toolchain worlds**: run Godot + Android SDK/adb **natively** per OS. Building
inside WSL while your emulator/adb runs on Windows causes device-detection pain.
On Ubuntu, USB debugging needs udev rules for your phone vendor before `adb devices` lists it.
4. **Signing env vars are the same** on both hosts — just use absolute paths appropriate
per filesystem in `GODOT_ANDROID_KEYSTORE_PATH`.
## The Core Loop
```
edit (.gd / .tscn / project settings)
→ full import (after adding/removing assets or scenes)
→ parse check (every changed script)
→ unit tests (gdUnit4, see references/testing.md)
→ smoke run (boot the game headlessly for N frames)
→ commit (only when everything above exits 0)
→ export (when feature-complete; see references/exporting.md)
```
### Step 1 — Full import (required after asset/scene changes)
```bash
godot --headless --path . --import
```
Imports all resources, generates `.godot/` cache, resolves UIDs, and reports load errors.
Run this before anything else in a fresh clone, and whenever you added textures/scenes/fonts.
Exit code ≠ 0 means broken resources — fix before continuing.
### Step 2 — Parse-check changed scripts
```bash
godot --headless --check-only --script res://scripts/player.gd
```
Syntax + basic type checking without executing. Fast enough to run per-edit. Note: it does
NOT catch broken node paths, missing autoloads, or runtime type errors — those need steps 3–4.
### Step 3 — Unit tests (gdUnit4)
Standardize every project on [gdUnit4](https://github.com/MikeSchulze/gdUnit4). Tests live in
`res://tests/`; suites extend `GdUnitTestSuite`. Run from the project root:
```bash
# Windows
addons/gdUnit4/runtest.cmd -a res://tests --continue
# Linux/macOS
./addons/gdUnit4/runtest.sh -a res://tests --continue
```
Non-zero exit = failures (CI-safe). HTML/XML reports land in `res://reports/`.
Full patterns — assertions, async tests, parametrized cases — in `references/testing.md`.
### Step 4 — Smoke run
Boot the real game headlessly and let it tick a bounded number of frames:
```bash
godot --headless --path . --quit-after 300 # ~300 frames of main scene
```
Runtime errors (bad autoload order, missing nodes, signal typos) print here and fail loudly.
For deeper checks (instantiate specific scenes, assert state), use a SceneTree driver script:
```gdscript
# tools/smoke_test.gd — instantiate target scenes, tick frames, assert they survive
extends SceneTree
func _init() -> void:
call_deferred("_run")
func _run() -> void:
var targets := ["res://scenes/main.tscn"]
var errors := 0
for path in targets:
var ps: PackedScene = load(path)
if ps == null:
push_error("FAILED TO LOAD: " + path)
errors += 1
continue
var node := ps.instantiate()
root.add_child(node)
await process_frame
await process_frame
node.queue_free()
await process_frame
print("SMOKE RESULT: ", "FAIL" if errors > 0 else "PASS")
quit(1 if errors > 0 else 0)
```
```bash
godot --headless --path . -s res://tools/smoke_test.gd
```
### Copy-paste verification gate (put this in every game repo)
```bash
#!/usr/bin/env bash
# verify.sh — the agent gate. Run after EVERY batch of edits. All steps must exit 0.
set -e
godot --headless --path . --import
for f in $(git diff --name-only HEAD | grep '\.gd$'); do
godot --headless --check-only --script "res://${f#*/}" || exit 1
done
addons/gdUnit4/runtest.cmd -a res://tests --continue # Linux/macOS: runtest.sh
godot --headless --path . --quit-after 300
echo "ALL CHECKS PASSED"
```
## Text Asset Safety (.tscn / .tres)
Scenes and resources are plain text — readable, diffable, mergeable. Rules:
| Do | Don't |
|---|---|
| Read `.tscn` to learn structure before editing scripts | Invent `uid://` values |
| Edit node properties, positions, exported values in place | Delete or rename `.uid` sidecar files |
| Add `[node]` blocks mirroring existing syntax exactly | Reference resources by UID you haven't seen |
| Prefer generating scenes via a tool script (below) | Hand-write complex nested scenes from scratch |
| Let Godot regenerate paths after moving files (`--import`) | Edit anything under `.godot/` |
Since Godot 4.4, scripts carry `.uid` sidecars and `ext_resource` entries may point at
`uid://…`. If both `uid` and `path` exist, either resolving is fine — **keep the `path`
attribute when hand-editing** and never fabricate UIDs.
When structural changes get risky (new node trees, wiring signals between nodes), generate
the scene programmatically instead:
```gdscript
# tools/build_scene.gd — run: godot --headless --path . -s res://tools/build_scene.gd
extends SceneTree
func _init() -> void:
call_deferred("_build")
func _build() -> void:
var root_node := Node2D.new()
root_node.name = "Level1"
var sprite := Sprite2D.new()
sprite.name = "Player"
sprite.position = Vector2(640, 360)
root_node.add_child(sprite)
var packed := PackedScene.new()
packed.pack(root_node)
ResourceSaver.save(packed, "res://scenes/levels/level_1.tscn")
print("SCENE SAVED")
quit(0)
```
This is deterministic, reviewable, and can't corrupt unrelated scene sections.
## CLI Export
One-time setup: create presets (editor UI or by writing `export_presets.cfg` — see
`references/exporting.md` for working Android/iOS preset skeletons).
```bash
# Windows/Linux desktop
godot --headless --path . --export-release "Windows Desktop" builds/win/game.exe
# Android (AAB for Play Store; APK for sideload/Galaxy Store)
godot --headless --path . --export-release "Android" builds/android/game.aab
# iOS — produces an Xcode project FOLDER, archived later on macOS
godot --headless --path . --export-release "iOS" builds/ios/
```
Release Android exports read signing config from env vars:
```bash
export GODOT_ANDROID_KEYSTORE_PATH=/path/to/release.keystore
export GODOT_ANDROID_KEYSTORE_USER=alias
export GODOT_ANDROID_KEYSTORE_PASSWORD=secret
```
Details, debugging exports (`--export-debug`), and the store-submission checklist live in
`references/exporting.md`.
## Mobile Quick Checklist
- Stretch mode `canvas_items` + portrait/landscape locked in `project.godot`
- Touch input: enable "Emulate Mouse From Touch"; consider Godot 4.7's `VirtualJoystick`
- Respect notches/safe areas (get visible rect from viewport, anchor UI inside it)
- Budget: mid-range phone should hold 60 fps — profile with `godot-performance` skill before shipping
- Min versions when using the RevenueCat plugin below: iOS 15+, Android SDK 24+
## In-App Purchases (RevenueCat)
Use the community **Godotx RevenueCat** plugin (MIT; wraps the official RC iOS/Android SDKs;
built against Godot 4.7-stable). Condensed flow — full playbook in `references/revenuecat.md`:
1. Install via AssetLib ("Godotx RevenueCat") → gives `addons/`, `ios/plugins/`, `android/plugins/`
2. Enable the plugin **per export preset**; Android additionally needs **Gradle Build ON**
3. Create the app + IAP products in Play Console / App Store Connect first, mirror them in
the RevenueCat dashboard, note the platform-specific public API keys
4. Init at startup with the platform key → fetch offerings → paywall → purchase → entitlement check
5. Test a real sandbox purchase on device **before** building game systems on top of it
⚠️ Verify exact method names against the addon's own README/API reference at integration
time — don't guess API surface (see failure modes below).
## Agent Failure Modes (catch these in review)
| Failure mode | Symptom | Gate |
|---|---|---|
| **Godot 3 API bleed** | `onready var`, `yield(`, `.instance()`, `tool` alone, `export var`, string-form `connect("x", self, "y")` | `grep -rnE "onready \|yield\(|\.instance\(\)|^tool\b|^export \|connect\(\"" scripts/` → must be empty |
| **Unity API bleed (C# projects)** | `UnityEngine`, `MonoBehaviour`, `GetComponent` | Only relevant in .NET projects; prefer GDScript to avoid entirely |
| **Invented UIDs / broken refs** | Load errors at import | Run `--import` gate after every scene edit |
| **Autoload order bugs** | Singleton accessed before init in `_init()` | Move access to `_ready()`; smoke run catches it |
| **Wrong callback** | Game logic in `_process()` that needs physics ticks | Use `_physics_process()` for movement/forces (`godot-physics` skill) |
| **Signal leaks** | Connections to freed nodes; crashes on scene change | Connect in `_ready()`, disconnect or use `CONNECT_ONE_SHOT`; review with `godot-code-review` skill |
| **Guessed external APIs** | Hallucinated plugin/engine methods | Only cite APIs found in the project's addons/docs or verified via `--check-only` passing |
## Editor vs Headless — When To Use Which
| Task | Tool |
|---|---|
| Script edits, tests, exports, CI, refactors | **Headless CLI (this skill)** |
| Visual layout polish, animation curves, theme tweaking | Editor GUI (human) or the package's `mcp-bridge/` MCP server for agent-driven editor control |
| Import cache issues | Always `--import` headless, never hand-fix `.godot/` |
## Related Skills
| Need | Skill |
|---|---|
| Scaffold the project | `godot-project-setup` |
| GDScript style & architecture decisions | `godot-gdscript-patterns`, `godot-brainstorming` |
| Review before merging features | `godot-code-review` |
| Diagnose failing smoke runs | `godot-debugging` |
| Ship fast on phones | `godot-performance` |
| Input handling, save systems, audio, shaders | `godot-input`, `godot-save-systems`, `godot-audio`, `godot-shaders-vfx` |
## References
- `references/testing.md` — gdUnit4 deep dive: suites, assertions, async, CI integration
- `references/exporting.md` — export presets, Android/iOS pipelines, store submission checklist
- `references/revenuecat.md` — RevenueCat plugin integration playbook for mobile games

View File

@ -0,0 +1,15 @@
# Claude Code extension for godot-headless-workflow
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "building or modifying a Godot game from a coding agent"
- "validating GDScript changes without opening the editor"
- "running Godot tests in CI"
- "exporting Android/iOS builds from the command line"
- "integrating RevenueCat purchases in a Godot game"
tool_permissions:
- read
- bash
- edit
- write

View File

@ -0,0 +1,13 @@
# OpenAI / Codex CLI extension for godot-headless-workflow
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: false
when_to_use:
- "building or modifying a Godot game from a coding agent"
- "validating GDScript changes without opening the editor"
- "running Godot tests in CI"
- "exporting Android/iOS builds from the command line"
- "integrating RevenueCat purchases in a Godot game"

View File

@ -0,0 +1,112 @@
# CLI Export Pipeline (Android / iOS / Desktop)
Export from CI or an agent shell with zero editor interaction, once presets exist.
## One-Time Setup Per Machine
1. Export templates matching the engine version must be installed — per host:
- Windows: `%APPDATA%\Godot\export_templates\<version>\`
- Linux: `~/.local/share/godot/export_templates/<version>/`
- macOS: `~/Library/Application Support/Godot/export_templates/<version>/`
Install via any editor's Manage Export Templates UI once, or copy manually.
2. Android toolchain (per build host): JDK 17, Android SDK with build-tools/platform-tools,
debug keystore. Typical `ANDROID_HOME`: `%LOCALAPPDATA%\Android\Sdk` (Windows),
`~/Android/Sdk` (Linux).
3. iOS builds always finish on macOS/Xcode — see iOS section. Generating the Xcode project
folder itself works fine from Windows or Linux.
## Presets (`export_presets.cfg`)
Create presets once (editor UI is fine) or write entries by hand. Minimal skeletons:
```ini
[preset.0]
name="Windows Desktop"
platform="Windows Desktop"
export_path="builds/win/game.exe"
[preset.1]
name="Android"
platform="Android"
export_path="builds/android/game.aab"
gradle_build/use_gradle_build=true # REQUIRED for native plugins (RevenueCat etc.)
package/unique_name="com.yourstudio.game"
[preset.2]
name="iOS"
platform="iOS"
export_path="builds/ios/game.xcodeproj"
```
After hand-editing presets, validate with a real export — typos fail at export time.
## Commands
```bash
# Debug build (faster, includes debugger; use for device testing during dev)
godot --headless --path . --export-debug "Android" builds/android/debug.apk
# Release build (store submission)
godot --headless --path . --export-release "Windows Desktop" builds/win/game.exe
godot --headless --path . --export-release "Android" builds/android/game.aab # AAB = Play Store
godot --headless --path . --export-release "Android" builds/android/game.apk # APK = sideload/Galaxy Store
godot --headless --path . --export-release "iOS" builds/ios/ # Xcode project folder
```
Exit code 0 + file exists = success. On failure read the FULL log — export errors cascade
(missing template → missing SDK → signing failure).
## Android Signing
Debug keystore is automatic. For release:
```bash
keytool -genkeypair -v -keystore release.keystore -alias game -keyalg RSA -validity 10000
export GODOT_ANDROID_KEYSTORE_PATH=/abs/path/release.keystore
export GODOT_ANDROID_KEYSTORE_USER=game
export GODOT_ANDROID_KEYSTORE_PASSWORD=secret
godot --headless --path . --export-release "Android" builds/android/game.aab
```
Guard the keystore like a password — losing it means losing app-signing continuity on Play.
Keep one shared release keystore in sync between your Windows and Linux machines (password
manager / secure transfer); signing with different keystores breaks update compatibility.
## Gradle Build Notes
Native Android plugins (RevenueCat, ads, Firebase…) require **Gradle Build enabled** in the
preset. First gradle build downloads dependencies (slow); subsequent builds cache. If gradle
fails, check: JDK version (17), `ANDROID_HOME`, and that plugin versions are mutually compatible.
## iOS Pipeline
The export produces an **Xcode project folder** (not an IPA):
```bash
godot --headless --path . --export-release "iOS" builds/ios/
# → builds/ios/ contains .xcodeproj — open on macOS, set team/signing, Product → Archive
```
No Mac? Options: GitHub Actions `macos-*` runner, Codemagic, or borrow a Mac for a single
archive session. Set `bundle identifier`, version, and signing before archiving.
## Store Submission Checklist (Shipaton-relevant)
- [ ] Icon 1024×1024 PNG exported from the icon preset
- [ ] Screenshot 1179×2556 px **frameless** (Devpost requirement) — capture from emulator/device, not editor viewport
- [ ] `version/code` bumped every Play upload; iOS `CFBundleVersion` too
- [ ] Privacy policy URL + data-safety forms (purchases ⇒ declare IAP)
- [ ] IAP products created in store consoles BEFORE RevenueCat mirrors them
- [ ] Internal testing track first: verify purchase + restore flow on real device
- [ ] Submit days before deadline — review queues stretch unpredictably
## Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| `No export template found` | Version mismatch | Install templates matching exact engine version |
| Gradle: `SDK location not found` | Env | Set `ANDROID_HOME`, accept licenses |
| Plugin listed but "not enabled" | Preset toggle | Enable plugin checkbox per preset (both platforms!) |
| AAB rejected: versionCode lower | Increment | Bump `version/code` in preset |
| iOS archive fails: signing | Team/capabilities | Set development team in exported Xcode project |

View File

@ -0,0 +1,108 @@
# RevenueCat Plugin Integration Playbook (Godot Mobile Games)
For hackathon/game-jam mobile games that must monetize via RevenueCat. Based on research of
the **Godotx RevenueCat** plugin (MIT, community-maintained) — verified August 2026.
## Versions (pin these)
| Component | Version | Note |
|---|---|---|
| Godot | 4.7-stable | plugin is built against this |
| Godotx RevenueCat plugin | v2.2.x | github.com/FunkyMuse/godot-revenuecat (also on official Asset Library, id 4493) |
| Bundled RC iOS SDK | 5.79.0 | min iOS 15.0 |
| Bundled RC Android SDK | 10.10.0 | min Android SDK 24 |
## Why this plugin
It wraps the **official** native RevenueCat SDKs and ships them as `.xcframework` (iOS) +
`.aar` (Android), managed through Godot's export-plugin pipeline — no C++/Java/Obj-C work
for us. It's thin glue: if it breaks, fork and patch it rather than replacing it.
⚠️ **No first-party RevenueCat SDK exists for Godot.** This community plugin is the
pragmatic path; budget a spike to validate it BEFORE building game systems on top.
## Install
1. AssetLib in-editor → "Godotx RevenueCat" → Install & Enable
(or download release ZIP → copy `addons/`, `ios/plugins/`, `android/plugins/` into project root)
2. Commit all three folders
3. Run `godot --headless --path . --import` to regenerate cache
## Configure Export Presets
Both platforms need their plugin enabled — per preset:
- **Android preset**: enable the RevenueCat plugin AND `Gradle Build` (native plugins require gradle)
- **iOS preset**: enable the RevenueCat plugin
Verify with an actual headless export after setup (see `exporting.md`).
## Store Setup Order (do it in this sequence)
1. Create app in Google Play Console + App Store Connect
2. Create IAP products there (e.g., `premium_unlock`, `hints_10`)
3. Create matching products + entitlement (`premium`) + offering (`default`) in the
RevenueCat dashboard; note each platform's **public** API key
4. Sandbox-test purchases with internal testers before writing more game code
## Runtime Integration Skeleton
Read the addon's bundled README/API reference for exact method names — verify against the
installed version, don't guess (see SKILL.md failure modes). The flow is always:
```gdscript
# autoload IapManager — adapt names to the installed plugin API
extends Node
const IOS_API_KEY := "appl_xxx"
const ANDROID_API_KEY := "goog_xxx"
signal entitlements_changed(is_premium: bool)
func _ready() -> void:
var key := IOS_API_KEY if OS.get_name() == "iOS" else ANDROID_API_KEY
_configure(key) # plugin configure/init call
check_entitlements()
func _configure(key: String) -> void:
pass # TODO: plugin init — see addons/godotx_revenue_cat README
func buy(product_id: String) -> void:
pass # TODO: purchase call → await result → check_entitlements()
func restore() -> void:
pass # TODO: restore purchases (App Store requires a restore button!)
func check_entitlements() -> void:
pass # TODO: fetch customer info → emit entitlements_changed
func show_paywall() -> void:
pass # TODO: present_paywall (plugin ships native paywall UI)
```
Wire `entitlements_changed` into game systems (unlock levels, remove ads, etc.).
## Hackathon-Specific Requirements
- Judges must get **a free trial OR a promo unlock** for premium features:
- Simplest: make the paywall offer a trial; or add a hidden "promo code" input redeeming
entitlement locally / via your own backend
- Demo video must show the real purchase flow on device (sandbox OK)
- Grand Prize judging uses RevenueCat-reported revenue → integrate early so revenue accrues
- Consider RevenueCat Ads tracking (AdMob ILRD → AdTracker) as an extra category entry
## Failure Playbook
| Problem | Action |
|---|---|
| Plugin init fails on export | Check plugin enabled in BOTH presets; Android needs Gradle Build ON |
| Products not loading | Store products not yet approved/available to tester account; check dashboard mirroring |
| Purchase succeeds, entitlement stale | Call customer-info refresh after purchase; don't cache entitlements across sessions without refresh |
| iOS build missing plugin symbols | Re-export; confirm `.xcframework` copied by export plugin |
| Plugin abandoned/broken | Fork repo, patch (thin native glue), or pin last-good release tag |
## Sources
- Plugin: github.com/FunkyMuse/godot-revenuecat · godot-x/revenuecat · Asset Library #4493
- Official SDKs: github.com/RevenueCat/purchases-ios · purchases-android · docs.revenuecat.com
- Engine ranking context: ship-a-ton-2026/docs/engine-research.md

View File

@ -0,0 +1,103 @@
# gdUnit4 Testing Deep Dive
Standardize every Godot project on [gdUnit4](https://github.com/MikeSchulze/gdUnit4) — the
actively maintained test framework with a headless CLI runner built for CI and agents.
## Install
1. AssetLib → "gdUnit4" → Install (or add as git submodule under `addons/gdUnit4`)
2. Enable the addon in Project Settings → Plugins
3. Commit `addons/gdUnit4/` with the project so agents on fresh clones can run tests
## Layout
```
tests/
├── unit/ # pure logic: score, economy, state machines
│ └── test_score_manager.gd
├── integration/ # scenes wired together
└── fixtures/ # test data (.tres)
reports/ # generated HTML/XML — .gitignore this
```
## Suite Conventions
```gdscript
# tests/unit/test_score_manager.gd
class_name TestScoreManager
extends GdUnitTestSuite
const ScoreManager := preload("res://scripts/core/score_manager.gd")
var _sm
func before_test() -> void:
_sm = ScoreManager.new() # auto-freed after each test
func test_add_score_increments() -> void:
_sm.add(10)
assert_int(_sm.score).is_equal(10)
func test_score_never_negative() -> void:
_sm.add(-5)
assert_int(_sm.score).is_equal(0)
func test_addition_is_parameterized(a: int, b: int, expected: int) -> void:
_sm.add(a + b)
assert_int(_sm.score).is_equal(expected) # supply via test-suite args or use fuzzers
```
Assertion helpers to know: `assert_int`, `assert_float`, `assert_str`, `assert_bool`,
`assert_that(expr)`, `assert_array`, `assert_dict`, `assert_object(obj).is_instanceof(...)`,
`assert_signal(spy).is_emitted("name")`. Full list in gdUnit4 docs.
## Async & Scene Tests
Await signals/frames inside tests — essential for scene-dependent logic:
```gdscript
func test_enemy_dies_after_hit() -> void:
var enemy := auto_free(load("res://scenes/entities/enemy.tscn").instantiate())
scene_runner().add_child(enemy) # scene_runner() gives a managed tree context
await get_tree().process_frame
enemy.take_damage(enemy.hp)
await assert_signal(enemy).is_emitted("died")
```
Keep rendering-dependent asserts out of headless runs: no pixel/screenshot checks in CI.
## Running From CLI
```bash
# everything under res://tests
addons/gdUnit4/runtest.cmd -a res://tests --continue # Windows
./addons/gdUnit4/runtest.sh -a res://tests --continue # Linux/macOS
# one suite only
addons/gdUnit4/runtest.cmd -a tests/unit/test_score_manager.gd
# filter by name pattern; stop-on-first-failure for fast loops
addons/gdUnit4/runtest.cmd -a res://tests --include test_score --no-failure
```
- Exit code 0 = all passed; non-zero = failures → CI/agent gate safe
- Reports: `res://reports/` (HTML + XML/JUnit)
- Runner is headless by default when no display is available
## Agent Workflow Integration
1. Write the failing test FIRST when fixing a bug (reproduce via test, then fix)
2. Run only the touched suite while iterating (`--include`), full suite before commit
3. If a test is flaky (async timing), fix the race condition — never delete the assertion
4. New feature ⇒ new suite; refactoring ⇒ tests unchanged, code changes
## Common Pitfalls
| Pitfall | Fix |
|---|---|
| Test class missing `class_name` / wrong base class | Must be `extends GdUnitTestSuite` + `class_name Test*` |
| Asserting inside awaited coroutines after timeout | Use `await assert_signal(...)` helpers, not manual timers |
| Scene loads but `_ready` errors | Run smoke test (`SKILL.md §Smoke run`) — it surfaces these |
| Tests pass alone, fail together | Suites sharing autoload state → reset in `before_test()` |
| Adding tests without enabling addon | `--import` + check plugin enabled in project.godot |

237
godot-i18n/SKILL.md Normal file
View File

@ -0,0 +1,237 @@
---
name: godot-i18n
description: "Localization patterns for Godot 4.x: TranslationServer, CSV/PO/JSON translation formats, runtime language switching, RTL text support, font fallback chains, and pluralization. Use when implementing internationalization in Godot games."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- i18n
- localization
- translation
- rtl
created: 2026-06-21
---
# Localization in Godot
Patterns for internationalizing Godot games.
## TranslationServer Basics
Godot's built-in translation system uses `.translation` files (CSV-based):
```gdscript
# Mark strings for translation with _() shorthand
func _ready():
$Label.text = tr("Hello, Player!")
$Button.text = tr("Start Game")
# tr() calls TranslationServer.translate() internally
# Strings are looked up in loaded .translation resources
```
### Creating Translation Files
Use the editor: **Project → Tools → Translation** to create `.csv` files, then export as `.translation`.
Or create CSV manually:
```csv
msgid "Hello, Player!"
msgstr "¡Hola, Jugador!"
msgid "Start Game"
msgstr "Comenzar Juego"
msgid "Score: {0}"
msgstr "Puntuación: {0}"
```
### Registering Translations
In **Project Settings → Locale → Configured Locales**, add supported languages (e.g., `es`, `fr`, `ja`).
## Runtime Language Switching
```gdscript
class_name LanguageManager extends Node
signal language_changed(locale: String)
var available_locales: Array[String] = ["en", "es", "fr", "ja"]
var current_locale: String = "en"
func set_language(locale: String) -> void:
if not available_locales.has(locale):
push_error("LanguageManager: Unsupported locale '%s'" % locale)
return
TranslationServer.set_locale(locale)
current_locale = locale
language_changed.emit(locale)
_notify_ui_update()
func _notify_ui_update() -> void:
# Re-tr() all UI labels that need updating
# Connect to language_changed signal in UI nodes
pass
# Save/restore preference
func _ready() -> void:
var saved = ConfigFile.new()
if saved.load("user://settings.cfg") == OK:
var locale = saved.get_value("general", "language", "en")
set_language(locale)
func _save_preference() -> void:
var config = ConfigFile.new()
config.load("user://settings.cfg")
config.set_value("general", "language", current_locale)
config.save("user://settings.cfg")
```
### Language Selector UI
```gdscript
# LanguageSelector.gd — attached to OptionButton
extends OptionButton
func _ready():
for locale in LanguageManager.available_locales:
var display_name = TranslationServer.get_locale_name(locale)
add_item(display_name, locale)
selected = LanguageManager.available_locales.find(LanguageManager.current_locale)
item_selected.connect(LanguageManager.set_language)
```
## Pluralization
Godot supports plural forms via `trn()`:
```gdscript
# trn(singular, plural, count)
func show_item_count(count: int):
$Label.text = trn("You have {0} item.", "You have {0} items.", count) % count
# For complex plurals (Slavic languages with 3+ forms), use custom logic
func get_plural_string(msgid_singular: String, msgid_plural: String, n: int) -> String:
# Godot handles plural forms via .po format plural rules
return trn(msgid_singular, msgid_plural, n)
```
## RTL Text Support
For Arabic, Hebrew, and other right-to-left languages:
```gdscript
# In Label or RichTextLabel:
$Label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
$Label.text_direction = TextServer.DIRECTION_RTL
$Label.alignment = HORIZONTAL_ALIGNMENT_RIGHT
# For mixed LTR/RTL content, use RichTextLabel with bbcode
$RichTextLabel.text = "[rtl]مرحبا[/rtl] [ltr]Hello[/ltr]"
```
### RTL-Aware UI Layout
```gdscript
# Container layouts flip automatically with RTL
# Use Control.layout_mode for proper RTL support:
$PanelContainer.set_anchors_preset(Control.PRESET_FULL_RECT)
# For manual positioning, mirror anchors:
func mirror_for_rtl(control: Control) -> void:
if TranslationServer.get_locale() in ["ar", "he", "fa"]:
var left = control.anchor_left
control.anchor_left = control.anchor_right
control.anchor_right = left
```
## Font Fallback Chains
Handle characters not available in the primary font:
```gdscript
# Theme approach (recommended):
# 1. Create Theme resource
# 2. Set "FontFile" for Label/LineEdit
# 3. Add fallback fonts via DynamicFont
# Code approach:
var dynamic_font = DynamicFont.new()
dynamic_font.font_path = "res://fonts/primary.ttf"
dynamic_font.fallback_fonts.append(DynamicFontLoading.new())
$Label.add_theme_font_override("font", dynamic_font)
# For CJK + Latin support, chain fonts:
# Primary: Noto Sans (Latin) → Fallback: Noto Sans CJK
```
## Translation Best Practices
### Use Context Keys
```gdscript
# When the same word has different meanings:
tr("Attack [weapon]") # As a verb
tr("Attack [noun]") # As a noun/stat name
# In .po files, msgctxt provides context
# msgctxt "weapon" / msgid "Attack" / msgstr "Atacar"
# msgctxt "noun" / msgid "Attack" / msgstr "Ataque"
```
### Avoid String Concatenation for Translatable Text
```gdscript
# Bad — translators can't reorder
tr("You found ") + item_name + tr(" in the cave!")
# Good — translators control word order
tr("You found {0} in the cave!") % item_name
# Better — named placeholders for clarity
tr("You found {item} in {location}!") % {"item": item_name, "location": location_name}
```
### Externalize All UI Strings
```gdscript
# Hardcoded strings make translation impossible
$Label.text = "Score: 100" # BAD
# Use tr() for everything visible to players
$Label.text = tr("Score: {0}") % score # GOOD
```
## Godot 4.7 i18n Notes
- **TextServer** API improved for complex script handling
- **RichTextLabel** supports better RTL paragraph alignment
- Translation import pipeline handles `.po` files natively (no conversion needed)
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools can inspect and modify your project live:
- **`project_info`** — Check configured locales and translation file paths
- **`script_read`** / **`script_edit`** — Read and modify localization scripts
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [TranslationServer Deep Dive](references/translation-server.md)
- [RTL Layout Patterns](references/rtl-layout.md)
- [Font Fallback Strategies](references/font-fallbacks.md)

View File

@ -0,0 +1,15 @@
# Claude Code extension for godot-i18n
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "localizing Godot games"
- "Godot translation system"
- "i18n Godot patterns"
- "runtime language switching Godot"
- "RTL text Godot"
- "font fallback Godot"
tool_permissions:
- read
- write
- bash

View File

@ -0,0 +1,14 @@
# OpenAI / Codex CLI extension for godot-i18n
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "localizing Godot games"
- "Godot translation system"
- "i18n Godot patterns"
- "runtime language switching Godot"
- "RTL text Godot"
- "font fallback Godot"

375
godot-input/SKILL.md Normal file
View File

@ -0,0 +1,375 @@
---
name: godot-input
description: "Input handling patterns for Godot 4.x: Input map configuration, action polling vs events, gamepad/virtual joystick support, input remapping UI, dead zones, and touch/multi-touch. Use when implementing player controls, controller support, or input customization in Godot."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- input
- controls
- gamepad
- touch
- remapping
created: 2026-06-21
---
# Input Handling in Godot
Patterns for robust, accessible input systems.
## Input Map Configuration
Define actions in **Project Settings → Input Map** or via code:
```gdscript
# Register actions at runtime (or use editor)
InputMap.add_action("move_left")
InputMap.action_add_event("move_left", InputEventKey.new())
var left_event = InputEventKey.new()
left_event.keycode = KEY_A
InputMap.action_add_event("move_left", left_event)
# Add gamepad button
var dpad_event = InputEventJoypadButton.new()
dpad_event.button_index = JOY_BUTTON_DPAD_LEFT
dpad_event.pressed = true
InputMap.action_add_event("move_left", dpad_event)
```
### Recommended Action Naming
```
move_left, move_right, move_up, move_down
jump, crouch, interact, attack
inventory, pause, menu_confirm, menu_cancel
```
## Polling vs Events
Two approaches to reading input:
### Polling (Continuous — `_physics_process`)
For movement and analog input:
```gdscript
class_name PlayerController extends CharacterBody2D
@export var speed: float = 200.0
func _physics_process(delta: float) -> void:
# Input.get_action_strength returns 0.0 to 1.0
# Automatically combines keyboard + gamepad inputs
var input_x = Input.get_action_strength("move_right") - Input.get_action_strength("move_left")
var input_y = Input.get_action_strength("move_down") - Input.get_action_strength("move_up")
var direction = Vector2(input_x, input_y).normalized()
velocity = direction * speed
move_and_slide()
```
### Events (Discrete — `_unhandled_input`)
For camera rotation, menu navigation, one-shot actions:
```gdscript
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("interact"):
interact_with_target()
elif event.is_action_pressed("pause"):
toggle_pause()
# Use _unhandled_input so UI can consume input first
# Use _input if this node should always receive input
```
### When to Use Each
| Approach | Use For | Method |
|----------|---------|--------|
| Polling | Movement, analog sticks, continuous actions | `_physics_process` / `_process` |
| Events | Camera rotation, one-shot actions, menu nav | `_unhandled_input` |
| Both | Games with movement + discrete actions | Combined |
## Gamepad Support
### Detecting Connected Controllers
```gdscript
func _ready() -> void:
var connected = Input.get_connected_joypads()
if connected.size() > 0:
print("Gamepad connected: ", connected[0])
# Enable rumble, vibrate, etc.
```
### Rumble / Haptic Feedback
```gdscript
# Simple rumble on action
func play_rumble(strength: float = 0.5, duration: float = 0.1) -> void:
Input.start_joy_vibration(0, strength, strength, duration)
# Dual-motor (separate weak/strong rumble)
Input.start_joy_vibration(0, 0.3, 0.8, 0.2) # weak, strong, duration
```
### Dead Zone Configuration
Prevent drift from analog sticks:
```gdscript
# Set global dead zone (Project Settings → Input Devices → Keyboard/Mouse → Joystick Dead Zone)
# Or per-action in code:
Input.set_deadzone_value(0.15) # Default is typically 0.15-0.25
# Custom dead zone for specific input reading
func get_analog_input(action_name: String, dead_zone: float = 0.15) -> float:
var value = Input.get_action_strength(action_name)
if value < dead_zone:
return 0.0
# Remap from [dead_zone, 1.0] to [0.0, 1.0]
return (value - dead_zone) / (1.0 - dead_zone)
```
## Input Remapping UI
Let players customize controls:
```gdscript
class_name InputRemapButton extends Button
var current_action: String = ""
var waiting_for_input: bool = false
func _ready():
pressed.connect(_on_button_pressed)
func set_action(action: String) -> void:
current_action = action
update_display()
func _on_button_pressed() -> void:
if waiting_for_input:
return
waiting_for_input = true
text = "Press a key..."
# Grab focus so input events come here
grab_focus()
func _input(event: InputEvent) -> void:
if not waiting_for_input:
return
if event is InputEventKey or event is InputEventJoypadButton:
# Clear old bindings for this action
var old_events = InputMap.action_get_events(current_action)
for old_event in old_events:
InputMap.action_remove_event(current_action, old_event)
# Add new binding
InputMap.action_add_event(current_action, event.duplicate())
waiting_for_input = false
update_display()
get_viewport().set_input_as_handled() # Prevent game from receiving it
func update_display() -> void:
if not waiting_for_input:
var events = InputMap.action_get_events(current_action)
if events.size() > 0:
text = _event_to_string(events[0])
else:
text = "Not assigned"
func _event_to_string(event: InputEvent) -> String:
if event is InputEventKey:
return OS.get_keycode_string(event.keycode)
elif event is InputEventJoypadButton:
return "Gamepad Button %d" % event.button_index
return "Unknown"
```
### Saving Remapped Controls
```gdscript
class_name InputSettings extends Node
const SETTINGS_PATH := "user://input_settings.cfg"
func save_bindings() -> void:
var config = ConfigFile.new()
var actions = InputMap.get_actions()
for action in actions:
var events = InputMap.action_get_events(action)
var event_strings = []
for event in events:
event_strings.append(_event_to_string(event))
config.set_value("input", action, ",".join(event_strings))
config.save(SETTINGS_PATH)
func load_bindings() -> Error:
var config = ConfigFile.new()
if config.load(SETTINGS_PATH) != OK:
return Error.ERR_FILE_NOT_FOUND
var actions = InputMap.get_actions()
for action in actions:
var event_strings = config.get_value("input", action, "") as String
if event_strings.is_empty():
continue
# Clear and re-add
var old_events = InputMap.action_get_events(action)
for old_event in old_events:
InputMap.action_remove_event(action, old_event)
for str in event_strings.split(","):
var event = _string_to_event(str)
if event:
InputMap.action_add_event(action, event)
return Error.OK
func _event_to_string(event: InputEvent) -> String:
if event is InputEventKey:
return "key:%d" % event.keycode
elif event is InputEventJoypadButton:
return "joypad:%d" % event.button_index
return ""
func _string_to_event(str: String) -> InputEvent:
var parts = str.split(":")
if parts[0] == "key":
var event = InputEventKey.new()
event.keycode = int(parts[1])
return event
elif parts[0] == "joypad":
var event = InputEventJoypadButton.new()
event.button_index = int(parts[1])
event.pressed = true
return event
return null
```
## Touch and Multi-Touch
### Virtual Joystick (Godot 4.7+)
```gdscript
# Godot 4.7 includes VirtualJoystick control node
# Place in scene, configure via inspector:
# - Base texture, knob texture
# - Dead zone value
# - Action names for directional output
# Read virtual joystick input:
func _physics_process(delta: float) -> void:
var joystick = $VirtualJoystick as VirtualJoystick
if joystick and joystick.is_active():
var direction = joystick.get_vector() # Vector2 (-1 to 1)
velocity = direction * speed
```
### Multi-Touch Gestures
```gdscript
class_name TouchHandler extends Node2D
var _touch_points: Dictionary = {} # index -> Vector2
func _input(event: InputEvent) -> void:
if event is InputEventScreenDrag:
_handle_drag(event)
elif event is InputEventScreenTouch:
if event.pressed:
_touch_points[event.index] = event.position
else:
_touch_points.erase(event.index)
func _handle_drag(event: InputEventScreenDrag) -> void:
if _touch_points.has(event.index):
var delta = event.position - _touch_points[event.index]
_touch_points[event.index] = event.position
# Use delta for movement, camera pan, etc.
# Pinch-to-zoom with two fingers
func get_pinch_scale() -> float:
var indices = _touch_points.keys()
if indices.size() < 2:
return 1.0
var idx_a = indices[0]
var idx_b = indices[1]
var current_dist = _touch_points[idx_a].distance_to(_touch_points[idx_b])
# Compare to previous frame distance for zoom delta
return current_dist / maxf(1.0, _get_previous_distance())
var _previous_distance: float = 0.0
func _get_previous_distance() -> float:
return _previous_distance
```
## Accessibility Patterns
### Hold vs Toggle Actions
```gdscript
# Support both hold and toggle for accessibility
@export var use_toggle_jump: bool = false
var jump_toggled: bool = false
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("jump"):
if use_toggle_jump:
jump_toggled = !jump_toggled
# Movement code checks either:
# Input.is_action_pressed("jump") OR jump_toggled
```
### Sensitivity Slider
```gdscript
# For camera or aim sensitivity
@export var sensitivity: float = 1.0
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
camera.rotate_y(-event.relative.x * sensitivity * 0.002)
camera.rotate_x(-event.relative.y * sensitivity * 0.002)
```
## Godot 4.7 Input Notes
- **VirtualJoystick** — New built-in control node for touch-based analog input
- **InputActionEvent** — Enhanced event structure for complex action queries
- **Gamepad** — Improved auto-detection and button mapping consistency
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools can inspect and modify your project live:
- **`project_info`** — Check configured input actions and settings
- **`script_read`** / **`script_edit`** — Read and modify input scripts
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [Input Map Best Practices](references/input-map.md)
- [Gamepad Support Guide](references/gamepad-support.md)
- [Touch Input Patterns](references/touch-input.md)

View File

@ -0,0 +1,15 @@
# Claude Code extension for godot-input
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "implementing input in Godot"
- "Godot gamepad support"
- "input remapping Godot"
- "touch controls Godot"
- "virtual joystick Godot"
- "multi-touch Godot"
tool_permissions:
- read
- write
- bash

View File

@ -0,0 +1,14 @@
# OpenAI / Codex CLI extension for godot-input
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "implementing input in Godot"
- "Godot gamepad support"
- "input remapping Godot"
- "touch controls Godot"
- "virtual joystick Godot"
- "multi-touch Godot"

View File

@ -0,0 +1,314 @@
---
name: godot-inventory-economy
description: "Inventory and economy patterns for Godot 4.x: resource-based item definitions, stackable/unique items, drag-drop UI, equip systems, currency management, and save integration. Use when building item systems, shops, or economies in Godot games."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- inventory
- economy
- items
- equip
created: 2026-06-21
---
# Inventory and Economy Systems in Godot
Patterns for item management, equipping, and game economies.
## Resource-Based Item Definitions
Define all items as `Resource` files — no code changes needed for new items:
```gdscript
class_name ItemData extends Resource
enum ItemType { CONSUMABLE, EQUIPMENT, KEY_ITEM, MATERIAL }
enum EquipmentSlot { HEAD, CHEST, HANDS, FEET, WEAPON, ACCESSORY }
@export var item_id: String = ""
@export var display_name: String = ""
@export var description: String = ""
@export var icon: Texture2D = null
@export var item_type: ItemType = ItemType.MATERIAL
@export var stack_size: int = 1 # Max per stack, 0 = unique
@export var value: int = 0 # Gold/sell value
@export var equipment_slot: EquipmentSlot = EquipmentSlot.WEAPON
# Stat modifiers (for equipment)
@export var stat_modifiers: Dictionary = {} # {"speed": 10, "defense": 5}
# Consumable effects
@export var heal_amount: int = 0
@export var effect_script: Script = null # Optional custom effect
```
### Item Instance (Runtime)
```gdscript
class_name InventoryItem extends Resource
@export var item_data: ItemData
@export var quantity: int = 1
@export var durability: float = 1.0 # 0.0 to 1.0, -1 = unbreakable
@export var is_equipped: bool = false
```
## Inventory Manager
Core inventory system with stack management:
```gdscript
class_name Inventory extends Node
signal item_added(item: InventoryItem)
signal item_removed(item: InventoryItem)
signal item_equipped(item: InventoryItem, slot: ItemData.EquipmentSlot)
signal item_unequipped(slot: ItemData.EquipmentSlot)
signal inventory_changed()
var items: Array[InventoryItem] = []
var equipped: Dictionary = {} # slot -> InventoryItem
@export var max_slots: int = 20
func add_item(data: ItemData, quantity: int = 1) -> bool:
if items.size() >= max_slots:
return false
# Try to stack with existing item
for inv_item in items:
if inv_item.item_data == data and data.stack_size > 1:
var remaining = data.stack_size - inv_item.quantity
if remaining > 0:
var add_amount = min(quantity, remaining)
inv_item.quantity += add_amount
quantity -= add_amount
item_added.emit(inv_item)
inventory_changed.emit()
if quantity <= 0:
return true
# Create new stack
while quantity > 0:
var new_item = InventoryItem.new()
new_item.item_data = data
var add_amount = min(quantity, data.stack_size if data.stack_size > 0 else 1)
new_item.quantity = add_amount
new_item.durability = data.stack_size > 1 ? 1.0 : -1.0
items.append(new_item)
item_added.emit(new_item)
quantity -= add_amount
inventory_changed.emit()
return true
func remove_item(index: int, quantity: int = 1) -> bool:
if index < 0 or index >= items.size():
return false
var inv_item = items[index]
if inv_item.quantity <= quantity:
item_removed.emit(inv_item)
items.remove_at(index)
else:
inv_item.quantity -= quantity
item_removed.emit(inv_item)
inventory_changed.emit()
return true
func equip(item_index: int) -> bool:
var inv_item = items[item_index]
var slot = inv_item.item_data.equipment_slot
# Unequip current item in slot
if equipped.has(slot):
unequip(slot)
inv_item.is_equipped = true
equipped[slot] = inv_item
item_equipped.emit(inv_item, slot)
return true
func unequip(slot: ItemData.EquipmentSlot) -> bool:
if not equipped.has(slot):
return false
var inv_item = equipped[slot]
inv_item.is_equipped = false
equipped.erase(slot)
item_unequipped.emit(slot)
return true
```
## Drag-and-Drop Inventory UI
Grid-based inventory with drag support:
```gdscript
class_name InventorySlot extends Container
signal slot_clicked(index: int)
signal drag_started(item: InventoryItem, index: int)
signal drop_requested(from_index: int, to_index: int)
var item: InventoryItem = null
var slot_index: int = 0
func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.pressed:
if event.button_index == MOUSE_BUTTON_LEFT and item:
drag_started.emit(item, slot_index)
# Handle dropping another item into this slot
func accept_drop(from_slot: InventorySlot) -> bool:
if not item:
# Empty slot — move item here
drop_requested.emit(from_slot.slot_index, slot_index)
return true
elif from_slot.item and not item:
drop_requested.emit(from_slot.slot_index, slot_index)
return true
return false
func update_display() -> void:
# Update child controls (icon, quantity label) based on item
pass
```
### Inventory Grid
```gdscript
class_name InventoryGrid extends GridContainer
@export var inventory: Inventory
@export var slot_scene: PackedScene
func _ready():
for i in range(inventory.max_slots):
var slot = slot_scene.instantiate() as InventorySlot
slot.slot_index = i
slot.drag_started.connect(_on_drag_started)
slot.drop_requested.connect(_on_drop_requested)
add_child(slot)
refresh()
func refresh() -> void:
for child in get_children():
if child is InventorySlot:
var slot = child as InventorySlot
if slot.slot_index < inventory.items.size():
slot.item = inventory.items[slot.slot_index]
else:
slot.item = null
slot.update_display()
func _on_drop_requested(from: int, to: int) -> void:
# Swap items between slots
if from >= inventory.items.size() or to >= inventory.items.size():
return
var temp = inventory.items[from]
inventory.items[from] = inventory.items[to]
inventory.items[to] = temp
refresh()
```
## Currency and Shop System
```gdscript
class_name EconomyManager extends Node
signal gold_changed(amount: int)
signal item_purchased(item: ItemData, cost: int)
signal item_sold(item: ItemData, price: int)
var gold: int = 0
func add_gold(amount: int) -> void:
gold += amount
gold_changed.emit(gold)
func spend_gold(amount: int) -> bool:
if gold < amount:
return false
gold -= amount
gold_changed.emit(gold)
return true
# Shop transaction
func buy_item(inventory: Inventory, item_data: ItemData, price: int) -> bool:
if not spend_gold(price):
return false
inventory.add_item(item_data)
item_purchased.emit(item_data, price)
return true
func sell_item(inventory: Inventory, item_index: int, price: int) -> bool:
var inv_item = inventory.items[item_index]
if not inv_item:
return false
inventory.remove_item(item_index, 1)
add_gold(price)
item_sold.emit(inv_item.item_data, price)
return true
```
## Save Integration
Serialize inventory for save systems:
```gdscript
func serialize_inventory() -> Dictionary:
var result = []
for inv_item in items:
result.append({
"item_id": inv_item.item_data.item_id,
"quantity": inv_item.quantity,
"durability": inv_item.durability,
"equipped": inv_item.is_equipped
})
return result
func deserialize_inventory(data: Array) -> void:
items.clear()
for entry in data:
var item_data = load("res://items/" + entry["item_id"] + ".tres") as ItemData
if item_data:
var inv_item = InventoryItem.new()
inv_item.item_data = item_data
inv_item.quantity = entry["quantity"]
inv_item.durability = entry.get("durability", 1.0)
items.append(inv_item)
inventory_changed.emit()
```
## Godot 4.7 Economy Notes
- **ResourceCache** — Preload item resources at game start for instant UI updates
- **tween_await()** — Animate gold counter changes or item pickup effects
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools can inspect and modify your project live:
- **`script_read`** / **`script_edit`** — Read and modify inventory scripts
- **`scene_tree`** — Inspect inventory UI node hierarchy
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [Item Resource Design](references/item-resources.md)
- [Inventory UI Patterns](references/inventory-ui.md)
- [Economy Balance Guide](references/economy-balance.md)

View File

@ -0,0 +1,15 @@
# Claude Code extension for godot-inventory-economy
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "building inventory systems in Godot"
- "Godot item management"
- "game economy Godot"
- "drag-drop inventory Godot"
- "equip system Godot"
- "shop system Godot"
tool_permissions:
- read
- write
- bash

View File

@ -0,0 +1,14 @@
# OpenAI / Codex CLI extension for godot-inventory-economy
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "building inventory systems in Godot"
- "Godot item management"
- "game economy Godot"
- "drag-drop inventory Godot"
- "equip system Godot"
- "shop system Godot"

360
godot-networking/SKILL.md Normal file
View File

@ -0,0 +1,360 @@
---
name: godot-networking
description: "Networking and multiplayer for Godot 4.x: Multiplayer API with RPC system, authority models (server-authoritative vs client-predictive), replication patterns (rpc(), rset(), rset_group()), connection management, lag compensation, and Steam Frame integration. Use when building multiplayer games, networked simulations, or online features in Godot."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- networking
- multiplayer
- rpc
- replication
- steam-frame
created: 2026-06-20
---
# Networking and Multiplayer for Godot 4.x
Comprehensive patterns for multiplayer, RPC communication, and network synchronization in Godot 4.x.
## Multiplayer API (Godot 4.x RPC System)
### Setting Up Multiplayer
```gdscript
# In _ready(), set the peer and peer ID
func _ready():
var peer = ENetMultiplayerPeer.new()
peer.create_client("127.0.0.1", 9000)
multiplayer.multiplayer_peer = peer
multiplayer.server_id = 1 # Server is always ID 1
```
### RPC Methods
```gdscript
# Remote: Call only from non-owner
@rpc("reliable", "call_remote")
func rpc_move_player(position: Vector3):
global_position = position
# Reliable RPC (ordered, guaranteed delivery)
@rpc("reliable", "authority", "call_remote")
func rpc_take_damage(amount: int, source_id: int):
health -= amount
if health <= 0:
rpc("rpc_die", source_id)
# Unreliable RPC (faster, may drop packets — good for position updates)
@rpc("unreliable", "authority", "call_remote")
func rpc_update_position(position: Vector3, rotation: float):
global_position = position
rotation_degrees = rotation
```
### RPC Reliability Options
| Mode | Use Case |
|------|----------|
| `"reliable"` | Damage, state changes, game events |
| `"unreliable"` | Position updates, player movement |
| `"unreliable_ordered"` | Ordered but fast (chat messages) |
| `"call_local"` | Only on the node owner |
| `"call_remote"` | On all non-owner nodes |
| `"call_authority"` | Only on the authority node |
## Authority Models
### Server-Authoritative Model
The server has final say on all game state. Clients send inputs, server validates and broadcasts results.
```gdscript
class_name ServerAuthPlayer extends CharacterBody3D
@export var player_id: int = 0
var _server := false
func _ready():
if multiplayer.is_server():
_server = true
set_multiplayer_authority(1)
else:
# Client-owned players are predicted locally
set_multiplayer_authority(multiplayer.get_unique_id())
# Client sends input to server
func _input(event: InputEvent) -> void:
if not multiplayer.is_server():
rpc("rpc_request_move", event)
# Server receives and validates input
@rpc("authority")
func rpc_request_move(event: InputEvent):
# Validate the input (check timing, etc.)
_process_input(event)
```
### Client-Predictive Model
Clients predict their own actions locally and correct on server acknowledgment.
```gdscript
class_name ClientPredictivePlayer extends CharacterBody3D
@export var prediction_enabled: bool = true
var _input_buffer := []
var _last_confirmed_input: int = 0
func _physics_process(delta: float):
# Apply buffered inputs up to current state
for input_data in _input_buffer:
if input_data.sequence > _last_confirmed_input:
_apply_input(input_data)
# Send position to server
rpc("rpc_update_position", global_position, rotation_degrees)
```
### When to Use Which
| Model | Best For |
|-------|----------|
| Server-authoritative | Competitive games, anti-cheat priority |
| Client-predictive | Fast-paced action, smooth feel priority |
| Hybrid (recommended) | Most multiplayer games |
## Replication Patterns
### rpc() — Remote Procedure Call
```gdscript
# Call a method on all other nodes
rpc("rpc_on_enemy_hit", damage, target_id)
# With parameters
@rpc("reliable")
func rpc_spawn_pickup(pickup_type: String, position: Vector3):
var pickup = preload("res://scenes/pickups/" + pickup_type + ".tscn").instantiate()
pickup.global_position = position
get_tree().root.add_child(pickup)
```
### rset() — Remote Set
Set a property on remote nodes.
```gdscript
# Set health bar on all clients
rset("health", new_health)
rset("max_health", max_health)
rset("visible", true)
# Bulk set with rset_group()
rset_group("players", "set_state", state_name)
```
### rset_group() — Broadcast to Group
```gdscript
# Add nodes to a group for batch updates
func _ready():
multiplayer.add_to_group("players")
# Update all players at once
func broadcast_position():
rset_group("players", "sync_position", global_position, rotation_degrees)
# Server-only group
func _ready():
if multiplayer.is_server():
add_to_group("server_only")
```
### Networked Autoload
Use autoloads for network-wide state.
```gdscript
# NetworkManager.gd (Autoload)
class_name NetworkManager extends Node
signal player_connected(player_id: int)
signal player_disconnected(player_id: int)
signal game_started()
var players := {}
func _ready():
multiplayer.peer_connected.connect(_on_peer_connected)
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
func _on_peer_connected(id: int):
players[id] = PlayerInfo.new(id)
player_connected.emit(id)
func _on_peer_disconnected(id: int):
players.erase(id)
player_disconnected.emit(id)
```
## Connection Management
### Server Setup
```gdscript
class_name MultiplayerServer extends Node
const PORT = 9000
const MAX_PLAYERS = 16
var server: HTTPServer
func start_server():
var peer = ENetMultiplayerPeer.new()
peer.create_server(PORT, MAX_PLAYERS)
multiplayer.multiplayer_peer = peer
print("Server started on port ", PORT)
func stop_server():
if multiplayer.multiplayer_peer:
multiplayer.multiplayer_peer.disconnect_from_server()
```
### Client Connection
```gdscript
class_name MultiplayerClient extends Node
signal connected
signal connection_failed
func connect_to_server(host: String, port: int):
var peer = ENetMultiplayerPeer.new()
var error = peer.create_client(host, port)
if error != OK:
connection_failed.emit()
return
multiplayer.multiplayer_peer = peer
# Wait for server to assign unique ID
await get_tree().create_timer(0.5).timeout
if multiplayer.is_connected_to_server():
connected.emit()
```
### Lag Compensation
```gdscript
# Store player positions for rewind capability
class_name PositionHistory extends Node
@export var history_length: int = 3.0
var _history := []
func _physics_process(delta: float):
_history.push_back({
"position": global_position,
"rotation": rotation_degrees,
"time": Time.get_ticks_msec()
})
# Prune old entries
var cutoff = Time.get_ticks_msec() - int(history_length * 1000)
while _history.size() > 0 and _history[0].time < cutoff:
_history.pop_front()
func get_position_at(time_ms: int) -> Variant:
for entry in _history:
if entry.time >= time_ms:
return entry
return _history.back()
```
## Steam Frame Integration (Godot 4.7+)
### Using Steam Networking Sockets
```gdscript
# Requires Steamworks SDK and godot-steam plugin
func connect_steam_server(app_id: int, steam_id: String):
var peer = SteamMultiplayerPeer.new()
peer.create_client(steam_id)
multiplayer.multiplayer_peer = peer
```
### Frame-Synced Updates
For competitive games requiring frame-accurate sync:
```gdscript
@export var tick_rate: int = 60
var _tick_counter: int = 0
func _physics_process(delta: float):
_tick_counter += 1
if _tick_counter % (int(60 / tick_rate)) == 0:
rpc("rpc_tick_update", _tick_counter, get_state_snapshot())
```
### Steam Lobby Integration
```gdscript
func create_lobby(max_players: int = 4):
# Create lobby via Steamworks API
var lobby_id = Steam.matchmaking.create_lobby(
max_players,
Steam.LobbyType.LobbyPrivate
)
return lobby_id
func join_lobby(lobby_id: String):
Steam.matchmaking.join_lobby(lobby_id)
```
## Best Practices
### Do
- Use `@rpc("unreliable")` for frequent position updates — saves bandwidth
- Validate all client input on the server before applying
- Keep RPC payloads small — serialize only what changes
- Use `multiplayer.is_server()` guards to prevent client-side authority bugs
- Implement a heartbeat/ping system for connection quality monitoring
### Don't
- Don't call `rpc()` in `_process` without throttling — floods the network
- Don't trust client-reported health/damage — always recalculate server-side
- Don't send full state snapshots every frame — use delta compression
- Don't forget `set_multiplayer_authority()` when spawning remote nodes
- Don't mix reliable and unreliable RPCs for the same logical update
### Debugging Tips
```
# Enable network debugging in Project Settings:
network/enable_packet_logging = true
network/max_packet_size = 1400
# Print all RPC traffic:
multiplayer.peer_connected.connect(func(id): print("Peer connected: ", id))
multiplayer.peer_disconnected.connect(func(id): print("Peer disconnected: ", id))
```
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools enhance networking debugging:
- **`network_peer_list`** — List connected peers and their unique IDs
- **`rpc_trace`** — Log all RPC calls with sender/receiver info for debugging
- **`network_stats`** — View bandwidth usage, packet loss, and latency metrics
- **`lobby_inspect`** — Query Steam lobby state and player list
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [Multiplayer API Documentation](references/multiplayer-api.md)
- [RPC Patterns Deep Dive](references/rpc-patterns.md)
- [Authority Models Guide](references/authority-models.md)

View File

@ -0,0 +1,15 @@
# Claude Code extension for godot-networking
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "networking and multiplayer in Godot"
- "Godot RPC system and replication"
- "Godot server-authoritative games"
- "Godot client prediction"
- "Godot lag compensation"
- "Godot Steam networking"
tool_permissions:
- read
- write
- bash

View File

@ -0,0 +1,14 @@
# OpenAI / Codex CLI extension for godot-networking
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "networking and multiplayer in Godot"
- "Godot RPC system and replication"
- "Godot server-authoritative games"
- "Godot client prediction"
- "Godot lag compensation"
- "Godot Steam networking"

329
godot-performance/SKILL.md Normal file
View File

@ -0,0 +1,329 @@
---
name: godot-performance
description: "Performance optimization for Godot 4.x: profiling workflow (built-in profiler, CPU/GPU usage), memory management with ResourceCache and instance pooling, instancing optimization with packed scenes and LOD, render passes, draw calls, mobile-specific optimizations, Perfetto tracing, and nearest-neighbor viewport scaling. Use when debugging performance, optimizing games, or targeting low-end hardware."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- performance
- profiling
- optimization
- memory
- mobile
created: 2026-06-20
---
# Performance Optimization for Godot 4.x
Systematic approach to profiling, optimizing, and debugging performance in Godot 4.x.
## Profiling Workflow
### Built-in Profiler
Access the profiler via Debug > Start Profiling or press Ctrl+Shift+P.
```gdscript
# Enable profiling in your project settings:
# Project Settings > Debug > Profile Every Frame = true
# Use time measure for specific code sections
var start_time = Time.get_ticks_usec()
# ... expensive operation ...
var elapsed = Time.get_ticks_usec() - start_time
print("Operation took: ", elapsed, " us")
```
### CPU/GPU Usage Monitoring
```gdscript
# Display FPS and frame time in debug overlay
# Project Settings > Debug > Show FPS = true
# Monitor specific metrics
func get_performance_stats():
var stats = {
fps: Performance.get_monitor(Performance.TIME_FPS),
frame_time: Performance.get_monitor(Performance.TIME_FRAME_DURATION),
draw_calls: Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME),
active_objects: Performance.get_monitor(Performance.OBJECT_COUNT),
mem_alloc: Performance.get_monitor(Performance.MEMORY_STATIC),
mem_pool: Performance.get_monitor(Performance.MEMORY_STATIC_SPECIALIZATION)
}
return stats
```
### Profiling Checklist
1. **Start with a clean build** — Profile the exported release build, not the debug version
2. **Enable all profiler categories** — CPU, GPU, Physics, Rendering, Audio
3. **Record a representative gameplay segment** — 30-60 seconds of typical play
4. **Identify hotspots** — Look for functions consuming >5% of frame time
5. **Profile before and after changes** — Measure impact of each optimization
## Memory Management
### ResourceCache Pattern
Preload and cache frequently-used resources.
```gdscript
class_name ResourceCache extends Node
static var _cache := {}
static func preload_resource(path: String) -> Variant:
if not _cache.has(path):
var res = load(path)
if res:
_cache[path] = res
else:
push_warning("Failed to load resource: " + path)
return _cache[path]
static func get_scene(path: String) -> PackedScene:
return preload_resource(path) as PackedScene
# Usage
var bullet_scene = ResourceCache.get_scene("res://scenes/entities/bullet.tscn")
var bullet = bullet_scene.instantiate()
```
### Instance Pooling Pattern
Reuse objects instead of creating/destroying them.
```gdscript
class_name ObjectPool<T> extends Node where T is RefCounted
@export var pool_size: int = 20
var _pool: Array[T] = []
var _active: Array[T] = []
func _ready():
for i in range(pool_size):
var instance = create_instance()
instance.set_process_mode(PROCESS_MODE_ALWAYS)
add_child(instance as Node)
_pool.append(instance as T)
func get_free() -> T:
if _pool.size() > 0:
var obj = _pool.pop_back()
obj.set_active(true)
_active.append(obj)
return obj
return null
func release(obj: T) -> void:
obj.set_active(false)
_active.erase(obj)
_pool.append(obj)
```
## Instancing Optimization
### Packed Scene Best Practices
```gdscript
# preload() for always-needed scenes (compile-time)
const BulletScene = preload("res://scenes/entities/bullet.tscn")
func spawn_bullet():
var bullet = BulletScene.instantiate()
add_child(bullet)
# load() for conditional scenes (runtime)
func load_level(level_name: String):
var path = "res://levels/" + level_name + ".tscn"
var scene = load(path) # Cached by Godot internally
if scene:
current_level = scene.instantiate()
add_child(current_level)
```
### Level of Detail (LOD)
Use LOD to reduce rendering cost for distant objects.
```gdscript
class_name LODObject extends Node3D
@export var lod_distances: PackedFloat32Array = [10.0, 30.0, 60.0]
@onready var lod_nodes = [$HighDetail, $MediumDetail, $LowDetail]
func _process():
var dist = global_position.distance_to(get_viewport().get_camera3D().global_position)
for i in range(lod_nodes.size()):
lod_nodes[i].visible = dist < lod_distances[i]
```
### Instanced Rendering
Use `MultiMeshInstance2D/3D` for many similar objects.
```gdscript
# Draw 1000 identical trees with a single draw call
var mesh = preload("res://models/tree.tscn")
var multi_mesh = MultiMesh.new()
multi_mesh.mesh = mesh.resource
multi_mesh.transform_format = MultiMesh.TRANSFORM_3D
multi_mesh.instance_count = 1000
for i in range(1000):
var x = randf() * 200 - 100
var z = randf() * 200 - 100
multi_mesh.set_instance_transform(i, Transform3D(Basis(), Vector3(x, 0, z)))
$MultiMeshInstance3D.multi_mesh = multi_mesh
```
## Render Passes and Draw Calls
### Minimizing Draw Calls
```gdscript
# BAD: Many small sprites (many draw calls)
for i in range(100):
var sprite = Sprite2D.new()
sprite.texture = grass_texture
add_child(sprite)
# GOOD: Use TileMap or MultiMesh (single draw call)
# TileMap handles batching automatically
# Or use a custom mesh with instanced rendering
```
### Render Pass Optimization
- **Forward+** renderer: Better for many lights, use for 3D games
- **Mobile** renderer: Fewer passes, better for low-end devices
- **Stencil shadows**: Use shadow maps instead of volumetric shadows when possible
- **LightmapGI**: Bake static lighting to avoid real-time shadows
```gdscript
# Set renderer in Project Settings > Rendering > Renderer
# Forward Plus: Better quality, higher cost
# Mobile: Optimized for low-end hardware
```
## Mobile-Specific Optimizations
### Texture Compression
```gdscript
# Project Settings > Export > Android/iOS
# Set texture format:
# - ASTC: Best quality/size ratio (modern devices)
# - ETC2: Fallback for older Android devices
# - DXT: Desktop only
# Use compressed textures in the editor:
# Texture > Compressed = True
```
### Mobile Rendering Tips
- Use **Mobile renderer** instead of Forward+ for Android/iOS
- Limit active lights per object (use light masks)
- Bake static lighting with LightmapGI
- Use smaller shadow map sizes (512x512 or 256x256)
- Disable post-processing effects when not needed
- Use `CanvasGroup` to batch UI rendering
- Set `texture_filter` to `TEXTURE_FILTER_NEAREST` for pixel art
### CPU Optimization for Mobile
- Avoid `_get_node()` in _process — use `@onready` instead
- Minimize `_instantiate()` calls — use object pooling
- Use `NavigationServer3D` for pathfinding (avoids scene tree overhead)
- Batch physics queries instead of individual raycasts
- Use `PhysicsServer3D` directly for bulk operations
## Godot 4.7 Performance Features
### Perfetto Tracing for Android (Godot 4.7)
Perfetto provides detailed profiling data for Android builds.
```gdscript
# Enable in Project Settings > Debug
# Perfetto tracing captures:
# - Frame timing breakdown
# - GC pauses
# - Thread scheduling
# - GPU command queue
# After export, run:
# godot --perfetto-trace output.perfetto
# Then open in https://ui.perfetto.dev/
```
### Nearest-Neighbor Viewport Scaling (Godot 4.7)
Pixel-perfect scaling for retro-style games.
```gdscript
# Project Settings > Display > Window > Stretch Mode
stretch_mode = CanvasGroup.STRETCH_MODE_SCALE
stretch_aspect = CanvasGroup.STRETCH_ASPECT_KEEP
# Godot 4.7: Use nearest-neighbor scaling for crisp pixel art
texture_filter = TextureFilter.NEAREST
# Calculate integer scale factor
func get_viewport_scale() -> int:
var target_width = 320 # Original game resolution
return max(1, int(DisplayServer.window_get_size().x / target_width))
```
## Performance Checklist
### Before Release
- [ ] Profile the release build (not debug)
- [ ] Check FPS stays above 30 (60 ideal) on target hardware
- [ ] Verify memory usage stays within device limits
- [ ] Test on lowest-spec target device
- [ ] Check for GC spikes (>1ms pauses)
- [ ] Verify texture compression is enabled
- [ ] Reduce draw calls with instancing
- [ ] Use lightmaps for static scenes
- [ ] Implement object pooling for frequently spawned entities
### Runtime Monitoring
```gdscript
# Add to debug HUD
class_name PerfHUD extends CanvasLayer
@onready var fps_label = $FPSLabel
@onready var mem_label = $MemLabel
func _process(delta):
fps_label.text = "FPS: " + str(Performance.get_monitor(Performance.TIME_FPS))
mem_label.text = "Mem: " + str(Performance.get_monitor(Performance.MEMORY_STATIC) / 1024 / 1024) + " MB"
```
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools enhance performance debugging:
- **`profiler_start`** — Start a profiling session from the editor
- **`profiler_stop`** — Stop and display results
- **`memory_stats`** — View current memory allocation breakdown
- **`render_stats`** — Query draw calls, triangles, and active textures
- **`perfetto_export`** — Export Perfetto trace for Android debugging
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [Profiler Documentation](references/profiler-guide.md)
- [Memory Management Patterns](references/memory-patterns.md)
- [Mobile Optimization Checklist](references/mobile-checklist.md)

View File

@ -0,0 +1,15 @@
# Claude Code extension for godot-performance
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "performance optimization in Godot"
- "Godot profiling and benchmarking"
- "Godot draw call reduction"
- "Godot instancing optimization"
- "Godot memory management"
- "Godot mobile performance"
tool_permissions:
- read
- write
- bash

View File

@ -0,0 +1,14 @@
# OpenAI / Codex CLI extension for godot-performance
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "performance optimization in Godot"
- "Godot profiling and benchmarking"
- "Godot draw call reduction"
- "Godot instancing optimization"
- "Godot memory management"
- "Godot mobile performance"

320
godot-physics/SKILL.md Normal file
View File

@ -0,0 +1,320 @@
---
name: godot-physics
description: "Physics systems for Godot 4.x: collision layers/masks, rigid body dynamics, character controllers (CharacterBody2D/3D), area nodes for triggers, physics interpolation, bulk queries, and performance optimization. Use when implementing physics, collisions, movement, or triggers in Godot."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- physics
- collision
- character-body
- rigid-body
- area
created: 2026-06-20
---
# Physics Systems for Godot 4.x
Comprehensive patterns for physics, collisions, and movement in Godot 4.x.
## Collision Layers and Masks (Godot 4.x Typed System)
Godot 4.x uses bit-based collision layer/mask system for efficient spatial queries.
### Configuring Collision Layers
Set layers in the editor via Inspector > CollisionShape3D > Collision Layer (bits 1-32).
```gdscript
# Recommended layer layout
const LAYER_PLAYER = 0x1
const LAYER_ENEMIES = 0x2
const LAYER_PROJECTILES = 0x4
const LAYER_ENVIRONMENT = 0x8
const LAYER_PICKUPS = 0x10
const LAYER_TRIGGERS = 0x20
collision_layer = LAYER_PLAYER
collision_mask = LAYER_ENVIRONMENT | LAYER_PICKUPS
```
### Layer Design Best Practices
| Layer | Bit | Use Case |
|-------|-----|----------|
| 1 | `0x1` | Player characters |
| 2 | `0x2` | Enemies and NPCs |
| 4 | `0x4` | Projectiles and weapons |
| 8 | `0x8` | Environment / terrain |
| 16 | `0x10` | Pickups and collectibles |
| 32 | `0x20` | Triggers (Area-only) |
### Typed Collision Queries
```gdscript
var ray_query := PhysicsRayQueryParameters3D.new()
ray_query.from = camera.global_position
ray_query.to = global_position + forward * range
ray_query.collision_mask = LAYER_ENVIRONMENT
ray_query.exclude = [self]
var result = get_world_3d().direct_space.intersect_ray(ray_query)
if result:
var collider = result.get("collider")
print("Hit: ", collider.get_name())
```
## Rigid Body Dynamics
### Basic Rigid Body Setup
```gdscript
class_name PlayerRigidBody extends CharacterBody3D
@export var gravity_scale: float = 1.0
@export var jump_force: float = 8.0
@export var move_speed: float = 5.0
var velocity := Vector3.ZERO
func _physics_process(delta: float) -> void:
velocity.y -= 9.8 * gravity_scale * delta
var input_dir := Input.get_vector("move_left", "move_right", "move_up", "move_down")
velocity.x = input_dir.x * move_speed
velocity.z = input_dir.y * move_speed
move_and_slide()
if is_on_floor() and Input.is_action_just_pressed("jump"):
velocity.y = jump_force
```
### Physics Material
```gdscript
var bouncy_material := PhysicsMaterial.new()
bouncy_material.bounce = 0.8
bouncy_material.friction = 0.2
collision_shape.shape.material = bouncy_material
```
### Rigid Body with Forces
```gdscript
class_name Projectile extends RigidBody3D
@export var lifetime: float = 3.0
@export var damage: int = 10
func _ready() -> void:
linear_velocity = transform.basis.z * 20.0
get_tree().create_timer(lifetime).timeout.connect(queue_free)
func _on_body_entered(body: Node3D) -> void:
if body is Enemy:
body.take_damage(damage)
queue_free()
```
## Character Controllers
### CharacterBody3D Movement Pattern
```gdscript
class_name ThirdPersonController extends CharacterBody3D
@export var walk_speed: float = 4.0
@export var run_speed: float = 8.0
@export var jump_velocity: float = 4.5
@export var gravity_amount: float = 9.8
@export var sprint_factor: float = 2.0
var _is_sprinting := false
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity.y -= gravity_amount * delta
if is_on_floor() and Input.is_action_just_pressed("jump"):
velocity.y = jump_velocity
var input_dir := Vector2.ZERO
input_dir = Vector2(
Input.get_axis("move_left", "move_right"),
Input.get_axis("move_up", "move_down")
)
_is_sprinting = Input.is_action_pressed("sprint")
var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if direction:
var spd = run_speed * sprint_factor if _is_sprinting else walk_speed
velocity.x = direction.x * spd
velocity.z = direction.z * spd
else:
velocity.x = move_toward(velocity.x, 0, walk_speed)
velocity.z = move_toward(velocity.z, 0, walk_speed)
move_and_slide()
```
### CharacterBody2D Platformer Pattern
```gdscript
class_name PlatformerCharacter extends CharacterBody2D
@export var speed: float = 200.0
@export var jump_force: float = -450.0
@export var gravity: float = 1200.0
@export var max_fall_speed: float = 700.0
@export var acceleration: float = 800.0
@export var friction: float = 600.0
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity.y += gravity * delta
velocity.y = min(velocity.y, max_fall_speed)
if is_on_floor() and Input.is_action_just_pressed("jump"):
velocity.y = jump_force
var move_input := Input.get_axis("move_left", "move_right")
if move_input:
velocity.x = move_toward(velocity.x, move_input * speed, acceleration * delta)
else:
velocity.x = move_toward(velocity.x, 0, friction * delta)
move_and_slide()
```
## Area Nodes for Triggers and Detection
### Basic Pickup Zone
```gdscript
class_name PickupZone extends Area3D
@export var pickup_scene: PackedScene
@export var min_items: int = 1
@export var max_items: int = 3
var _has_been_activated := false
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node3D) -> void:
if _has_been_activated: return
if body is CharacterBody3D:
_spawn_items()
_has_been_activated = true
queue_free()
func _spawn_items() -> void:
var count = randi() % (max_items - min_items + 1) + min_items
for i in range(count):
var item = pickup_scene.instantiate()
item.position = global_position + Vector3(randf() * 2 - 1, 0.5, randf() * 2 - 1)
get_tree().root.add_child(item)
```
### Enemy Detection Sensor
```gdscript
class_name EnemySensor extends Area3D
signal player_detected(player: CharacterBody3D)
signal player_lost(player: CharacterBody3D)
@export var detection_radius: float = 10.0
@export var detection_layers: int = 0x1
func _ready() -> void:
body_entered.connect(_on_body_entered)
body_exited.connect(_on_body_exited)
var sphere := SphereShape3D.new()
sphere.radius = detection_radius
$CollisionShape3D.shape = sphere
collision_mask = detection_layers
func _on_body_entered(body: Node3D) -> void:
if body is CharacterBody3D:
player_detected.emit(body)
func _on_body_exited(body: Node3D) -> void:
if body is CharacterBody3D:
player_lost.emit(body)
```
## Physics Interpolation (Godot 4.x Feature)
Godot 4.x supports physics interpolation for smooth rendering between physics ticks.
### Enabling Physics Interpolation
Enable in Project Settings > Rendering > Other > Physics Interpolation set to "Interpolate". The engine automatically interpolates transforms between physics ticks when rendering. No extra code needed for standard nodes.
For custom rendering, use the built-in interpolation factor available through the rendering pipeline.
## Performance: Bulk Physics Queries
### Space State Batched Queries
```gdscript
func get_all_collisions_in_area(origin: Vector3, radius: float, mask: int) -> Array:
var query := PhysicsShapeQueryParameters3D.new()
var sphere := SphereShape3D.new()
sphere.radius = radius
query.shape = sphere
query.transform = Transform3D.IDENTITY.translated(origin)
query.collision_mask = mask
query.exclude = [self]
var space = get_world_3d().direct_space
return space.intersect_shape(query)
func aoe_attack(center: Vector3, radius: float, damage: int) -> void:
var hits := get_all_collisions_in_area(center, radius, LAYER_ENEMIES | LAYER_PROJECTILES)
for hit in hits:
var collider = hit.get("collider")
if collider is Enemy:
collider.take_damage(damage)
```
### Collision Layer Optimization
- Only check layers you need — avoid `collision_mask = 0xFFFFFFFF`
- Use `NavigationServer3D` for pathfinding queries
- Use small-radius `Area3D` nodes for proximity checks
- Avoid querying the entire world every frame
## Godot 4.7 Physics Notes
- Physics interpolation is more robust with improved accuracy
- Use `AreaLight3D` for dynamic light casting on visible surfaces
- Test physics performance with Perfetto tracing on mobile targets
## Quick Reference: Movement Methods
| Method | Use Case | Returns |
|--------|----------|---------|
| `move_and_slide()` | Standard character movement | SlideFlags |
| `move_and_collide()` | When you need collision info | CollisionData or null |
| `move_and_snap()` | Platformer with snap-to-floor | SlideFlags |
| `move_teleport()` | Teleportation (no sliding) | CollisionData or null |
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools enhance physics debugging:
- **`physics_debug_draw`** — Toggle collision shape visualization in editor
- **`physics_query_shape`** — Run shape queries from any position without placing nodes
- **`rigid_body_apply_force`** — Apply forces to rigid bodies for testing
- **`physics_stats`** — View active physics bodies, sleep count, and frame cost
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [CharacterBody3D Documentation](references/characterbody3d-guide.md)
- [Collision Layer Design Patterns](references/collision-layers.md)
- [Physics Material Reference](references/physics-materials.md)

View File

@ -0,0 +1,15 @@
# Claude Code extension for godot-physics
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "physics systems in Godot"
- "Godot collision layers and masks"
- "Godot rigid body dynamics"
- "Godot character physics movement"
- "Godot physics materials"
- "Godot area detection"
tool_permissions:
- read
- write
- bash

View File

@ -0,0 +1,14 @@
# OpenAI / Codex CLI extension for godot-physics
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "physics systems in Godot"
- "Godot collision layers and masks"
- "Godot rigid body dynamics"
- "Godot character physics movement"
- "Godot physics materials"
- "Godot area detection"

View File

@ -0,0 +1,166 @@
---
name: godot-project-setup
description: "Scaffolds a new Godot 4.x project with proper folder structure, .gdignore, export presets, .gitignore, and GDScript/C# Mono initialization. Use when creating a new Godot game or app."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- project-setup
- scaffolding
- export-presets
created: 2026-06-20
---
# Godot Project Setup
Scaffold a complete, production-ready Godot 4.x project with industry-standard organization.
## Quick Start
```bash
# Create a new project
./scripts/new-godot-project.sh MyGame /path/to/output
```
## Standard Directory Structure
After scaffolding, your project will have:
```
MyGame/
├── .gdignore # Exclude editor cache from export
├── .gitignore # Version control ignores
├── project.godot # Project configuration
├── scenes/ # Scene files (.tscn)
│ ├── ui/ # UI scene compositions
│ ├── levels/ # Level/world scenes
│ └── entities/ # Character/entity scenes
├── scripts/ # Script files (.gd, .cs)
│ ├── core/ # Game systems (manager, event bus)
│ ├── entities/ # Entity behaviors
│ ├── ui/ # UI controllers
│ └── levels/ # Level-specific logic
├── resources/ # Resource files (.tres, .res)
│ ├── data/ # Game data (items, skills, etc.)
│ └── config/ # Configuration resources
├── assets/ # Raw assets (do not edit in Godot)
│ ├── textures/
│ ├── audio/
│ ├── fonts/
│ └── models/
├── levels/ # Level data and configurations
└── export_presets.cfg # Export presets (auto-generated)
```
## Project.godot Configuration
Key settings for a clean project:
```ini
[application]
config/icon="res://assets/icons/icon.png"
[display]
window/size/width=1280
window/size/height=720
window/stretch/mode="canvas_items"
[rendering]
renderer/rendering_method="forward_plus"
```
## .gdignore Setup
Create `.gdignore` in directories that Godot should not import:
```
# In assets/ directory
# Godot ignores this directory for resource imports
```
## GDScript Project Initialization
Standard project.godot autoload setup:
```gdscript
# Add to Project Settings > Autoload
# Name: GameManager, Path: res://scripts/core/game_manager.gd
# Name: EventBus, Path: res://scripts/core/event_bus.gd
# Name: AudioManager, Path: res://scripts/core/audio_manager.gd
```
## C# Mono Project Initialization
For C# projects, the scaffolded project includes:
- `.csproj` file configured for Godot 4.x
- `global.json` for .NET version pinning
- Standard namespace structure matching directory layout
- `[Export]` attribute patterns in template scripts
## Godot 4.7 Specific Setup
When creating projects with Godot 4.7 features:
1. **HDR Settings** — Enable in project.godot:
```ini
[rendering]
environment/default_environment="default_world.tres"
```
2. **Steam Frame Configuration** — Add to project.godot:
```ini
[steam]
app_id="YOUR_APP_ID"
```
3. **VirtualJoystick** — Use built-in node for mobile projects instead of third-party solutions
4. **Export Presets** — Configure for:
- Windows Desktop (64-bit)
- Linux/X11 (64-bit)
- Android (API 31+)
- HTML5 (Web)
## Export Presets
Common export configurations:
| Target | Settings | Notes |
|--------|----------|-------|
| Windows | Desktop, 64-bit, D3D12 | Default for PC games |
| Linux | Desktop, 64-bit, Vulkan | Requires vulkan drivers |
| Android | Mobile, API 31+, ARM64 | Use VirtualJoystick for controls |
| HTML5 | Web, WASM | Limited to web-compatible features |
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools can inspect and modify your project live:
- **`project_settings_read`** — Read current project.godot settings
- **`project_settings_set`** — Modify project settings (e.g., enable HDR, set Steam app ID)
- **`file_browse`** — List project directories to verify scaffold structure
- **`file_write`** — Write configuration files directly
- **`class_db_info`** — Query Godot class database for export preset node types
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [Godot Project Configuration](references/project-configuration.md)
- [Export Presets Deep Dive](references/export-presets.md)
- [Mono/C# Setup Guide](references/mono-setup.md)

View File

@ -0,0 +1,13 @@
# Claude Code extension for godot-project-setup
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "creating a new Godot project"
- "setting up Godot project structure"
- "Godot export presets"
- "scaffolding a Godot game"
tool_permissions:
- read
- write
- bash

View File

@ -0,0 +1,12 @@
# OpenAI / Codex CLI extension for godot-project-setup
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "creating a new Godot project"
- "setting up Godot project structure"
- "Godot export presets"
- "scaffolding a Godot game"

247
godot-save-systems/SKILL.md Normal file
View File

@ -0,0 +1,247 @@
---
name: godot-save-systems
description: "Save system patterns for Godot 4.x: resource-based saves, JSON vs binary serialization, versioning/migration strategies, cloud save integration, and corruption recovery. Use when implementing persistence in Godot games."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- save-system
- persistence
- serialization
- migration
created: 2026-06-21
---
# Save Systems in Godot
Patterns for persisting game state reliably.
## Resource-Based Saves (Recommended)
Godot's `Resource` system provides native serialization with type safety:
```gdscript
class_name SaveData extends Resource
const SAVE_VERSION := 1
@export var save_version: int = SAVE_VERSION
@export var timestamp: float = 0.0
@export var player_position: Vector2 = Vector2.ZERO
@export var player_health: int = 100
@export var inventory: Array[Resource] = []
@export var quest_flags: Dictionary = {}
```
### Save Manager
```gdscript
class_name SaveManager extends Node
const SAVE_PATH := "user://savegame.tres"
func save(data: SaveData) -> Error:
data.timestamp = Time.get_unix_time_from_system()
var error = ResourceSaver.save(data, SAVE_PATH)
if error != Error.OK:
push_error("SaveManager: Failed to save — %d" % error)
return error
func load() -> SaveData:
if not FileAccess.file_exists(SAVE_PATH):
return null
var data = ResourceLoader.load(SAVE_PATH) as SaveData
if not data:
push_error("SaveManager: Failed to load save file")
return null
return data
func delete() -> void:
DirAccess.remove_absolute(SAVE_PATH)
func has_save() -> bool:
return FileAccess.file_exists(SAVE_PATH)
```
### Multiple Save Slots
```gdscript
func get_slot_path(slot_index: int) -> String:
return "user://savegame_%d.tres" % slot_index
# Save slot metadata for UI display
class_name SlotInfo extends Resource
@export var slot_index: int = 0
@export var timestamp: float = 0.0
@export var player_level: int = 1
@export var thumbnail: Texture2D = null
```
## JSON Serialization
For human-readable saves or cross-platform data exchange:
```gdscript
func save_as_json(data: SaveData, path: String) -> Error:
var dict := {
"version": data.save_version,
"timestamp": data.timestamp,
"position": { "x": data.player_position.x, "y": data.player_position.y },
"health": data.player_health,
"inventory": [],
"quests": data.quest_flags
}
var json = JSON.stringify(dict)
var file = FileAccess.open(path, FileAccess.WRITE)
if not file:
return Error.CANT_OPEN
file.store_string(json)
return Error.OK
func load_from_json(path: String) -> SaveData:
var file = FileAccess.open(path, FileAccess.READ)
if not file:
return null
var json_str = file.get_as_text()
var parse_result = JSON.parse_string(json_str)
if parse_result is Dictionary:
return _dict_to_save_data(parse_result as Dictionary)
return null
```
## Versioning and Migration
Handle save format changes across game updates:
```gdscript
func load() -> SaveData:
var raw = ResourceLoader.load(SAVE_PATH) as SaveData
if not raw:
return null
# Migrate older save formats to current version
while raw.save_version < SaveData.SAVE_VERSION:
raw = _migrate(raw, raw.save_version)
return raw
func _migrate(data: SaveData, from_version: int) -> SaveData:
match from_version:
0:
# v0 → v1: add quest_flags field
data.quest_flags = {}
data.save_version = 1
_:
push_warning("SaveManager: Unknown migration from v%d" % from_version)
return data
```
## Corruption Recovery
Protect against corrupted save files:
```gdscript
func load_safe() -> SaveData:
var data = load()
if data and _validate(data):
return data
# Try backup
if FileAccess.file_exists(SAVE_PATH + ".bak"):
push_warning("SaveManager: Primary save corrupted, loading backup")
var temp = SAVE_PATH
OS.move_to_trash(SAVE_PATH) # Move corrupted file
DirAccess.copy_absolute(SAVE_PATH + ".bak", SAVE_PATH)
return load()
push_error("SaveManager: No valid save found")
return null
func _validate(data: SaveData) -> bool:
if not data:
return false
if data.save_version < 1:
return false
# Add more validation checks
return true
# Create backup on successful save
func save_with_backup(data: SaveData) -> Error:
# Backup current save
if FileAccess.file_exists(SAVE_PATH):
DirAccess.copy_absolute(SAVE_PATH, SAVE_PATH + ".bak")
return save(data)
```
## Cloud Save Integration Pattern
Abstract cloud provider behind an interface:
```gdot
class_name CloudSaveProvider extends RefCounted
virtual func upload(data: String, key: String) -> bool:
return false
virtual func download(key: String) -> String:
return ""
virtual func delete(key: String) -> bool:
return false
# Usage: implement concrete providers
# class_name GooglePlaySaveProvider extends CloudSaveProvider
# class_name AppleCloudSaveProvider extends CloudSaveProvider
```
## Auto-Save Strategies
```gdscript
class_name AutoSaver extends Node
@export var interval_seconds: float = 30.0
var _timer: Timer
var _save_manager: SaveManager
func _ready() -> void:
_timer = Timer.new()
_timer.wait_time = interval_seconds
_timer.timeout.connect(_on_auto_save)
add_child(_timer)
_timer.start()
func _on_auto_save() -> void:
var data = _build_save_data()
_save_manager.save(data)
```
## Godot 4.7 Save Notes
- **ResourceSaver** supports `ResourceSaver.FLAG_COMPRESS` for smaller save files
- **ConfigFile** — Use for settings/preferences (separate from game saves)
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools can inspect and modify your project live:
- **`script_read`** / **`script_edit`** — Read and modify save system scripts
- **`project_info`** — Check user:// directory paths for save location debugging
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [Resource Save Patterns](references/resource-saves.md)
- [Save Migration Strategies](references/save-migration.md)
- [Cloud Save Integration](references/cloud-saves.md)

View File

@ -0,0 +1,15 @@
# Claude Code extension for godot-save-systems
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "implementing save systems in Godot"
- "Godot persistence patterns"
- "save file serialization Godot"
- "save migration versioning"
- "cloud save Godot"
- "auto-save Godot"
tool_permissions:
- read
- write
- bash

View File

@ -0,0 +1,14 @@
# OpenAI / Codex CLI extension for godot-save-systems
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "implementing save systems in Godot"
- "Godot persistence patterns"
- "save file serialization Godot"
- "save migration versioning"
- "cloud save Godot"
- "auto-save Godot"

299
godot-shaders-vfx/SKILL.md Normal file
View File

@ -0,0 +1,299 @@
---
name: godot-shaders-vfx
description: "Visual effects patterns for Godot 4.x: Godot Shader Language (GSL) basics, canvas_item/vertex/spatial shaders, particle systems (GPUParticles2D/3D), shader materials, and post-processing. Use when implementing custom rendering effects, particles, or visual polish in Godot."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- shaders
- vfx
- particles
- post-processing
- gsl
created: 2026-06-21
---
# Shaders and Visual Effects in Godot
Patterns for custom rendering and particle effects.
## Shader Types
Godot supports three shader modes:
| Mode | Node Type | Use Case |
|------|-----------|----------|
| `canvas_item` | Sprite2D, TileMap, Control | 2D sprite effects, UI shaders |
| `spatial` | MeshInstance3D, SurfaceTool | 3D material effects |
| `vertex` | Any mesh | Displacement, morphing (no fragment stage) |
## Canvas Item Shader (2D)
Basic 2D shader structure:
```gdscript
// Pixelate effect for retro aesthetics
shader_type canvas_item;
uniform float pixel_size : hint_range(1.0, 32.0) = 4.0;
uniform float brightness : hint_range(0.0, 2.0) = 1.0;
void fragment() {
// Snap UV to pixel grid
vec2 uv = floor(UV * (1.0 / pixel_size)) * pixel_size;
vec4 color = texture(TEXTURE, uv);
// Apply brightness
color.rgb *= brightness;
COLOR = color;
}
```
### Applying Shaders in Code
```gdscript
# Apply shader material to a sprite
var shader_mat = ShaderMaterial.new()
shader_mat.shader = preload("res://shaders/pixelate.gdshader")
$Sprite2D.material = shader_mat
# Set uniform at runtime
shader_mat.set_shader_parameter("pixel_size", 8.0)
shader_mat.set_shader_parameter("brightness", 1.5)
```
### Color Tint Shader
```gdscript
// Selective color tint — useful for damage flash, selection highlight
shader_type canvas_item;
uniform vec4 tint_color : hint_color = vec4(1.0, 0.0, 0.0, 1.0);
uniform float tint_amount : hint_range(0.0, 1.0) = 0.5;
void fragment() {
vec4 original = texture(TEXTURE, UV);
COLOR = mix(original, original * tint_color, tint_amount);
}
```
## Spatial Shader (3D)
Basic 3D shader with lighting:
```gdscript
// Animated water surface
shader_type spatial;
render_mode unshaded, depth_prepass_alpha_blend;
uniform float wave_speed : hint_range(0.1, 5.0) = 1.0;
uniform float wave_height : hint_range(0.01, 0.5) = 0.1;
uniform vec4 water_color : hint_color = vec4(0.0, 0.5, 0.8, 0.7);
void vertex() {
// Animate vertices with sine waves
float wave = sin(UV.x * 10.0 + TIME * wave_speed) * wave_height;
wave += sin(UV.y * 8.0 + TIME * wave_speed * 1.3) * wave_height * 0.5;
VERTEX.y += wave;
// Recalculate normal for lighting
NORMAL.y += wave * 2.0;
NORMAL = normalize(NORMAL);
}
void fragment() {
// Fresnel-like effect — edges more reflective
float fresnel = pow(1.0 - abs(dot(NORMAL, VIEW_DIR)), 3.0);
vec3 color = mix(water_color.rgb, vec3(1.0), fresnel * 0.5);
ALBEDO = color;
ALPHA = water_color.a;
SPECULAR = 0.8;
ROUGHNESS = 0.2;
}
```
## Particle Systems
### GPUParticles2D
```gdscript
# Code-driven particle setup
var particles = GPUParticles2D.new()
particles.one_shot = true
particles.lifetime = 1.5
particles.amount = 50
particles.emitting = false
# Process mode
particles.process_material = ParticleProcessMaterial.new()
particles.process_material.initial_velocity_min = 50.0
particles.process_material.initial_velocity_max = 150.0
particles.process_material.gravity = Vector3(0, -200, 0)
particles.process_material.spread = 360.0
# Visual
particles.material = ParticleMaterial.new()
particles.material.texture = preload("res://assets/particle.png")
particles.material.h_color = Color(1.0, 0.5, 0.0, 1.0)
particles.material.h_color2 = Color(1.0, 1.0, 0.0, 0.0) # Fade to transparent
add_child(particles)
```
### Triggering Particles
```gdscript
func emit_explosion(position: Vector2) -> void:
var particles = load("res://vfx/explosion.tscn").instantiate() as GPUParticles2D
particles.global_position = position
particles.emitting = true
get_parent().add_child(particles)
# Auto-destroy after particles finish
particles.finished.connect(func(): particles.queue_free())
```
### GPUParticles3D
```gdscript
# 3D particle burst (fire, smoke, magic)
var particles_3d = GPUParticles3D.new()
particles_3d.one_shot = true
particles_3d.lifetime = 2.0
particles_3d.amount = 100
# Directional emission from a cone
particles_3d.direction = Vector3.UP
particles_3d.emitting = false
add_child(particles_3d)
```
## Post-Processing
Apply screen-space effects via WorldEnvironment:
```gdscript
# Access environment settings
var env = get_world_3d().environment
# Glow/bloom
env.glow_enabled = true
env.glow_bloom_intensity = 0.5
env.glow_bloom_soft = true
env.glow_bloom_threshold = 0.8
# Tone mapping
env.tone_mapping = Environment.TONE_MAPPING_ACES
# Fog
env.fog_mode = Environment.FOG_MODE_EXPONENTIATED
env.fog_color = Color(0.6, 0.7, 0.8)
env.fog_density = 0.02
# SSAO (3D ambient occlusion)
env.ssao_enabled = true
env.ssao_max_distance = 5.0
```
### Screen Shader (WorldEnvironment)
For custom full-screen post-processing:
```gdscript
// Vignette + chromatic aberration post-process
shader_type spatial;
render_mode unshaded;
uniform float vignette_amount : hint_range(0.0, 1.0) = 0.5;
uniform float chroma_amount : hint_range(0.0, 0.01) = 0.003;
void fragment() {
// Chromatic aberration — shift RGB channels radially
vec2 center = UV - 0.5;
float dist = length(center);
vec2 dir = normalize(center) * chroma_amount * dist;
float r = texture(SCREEN_TEXTURE, UV + dir).r;
float g = texture(SCREEN_TEXTURE, UV).g;
float b = texture(SCREEN_TEXTURE, UV - dir).b;
// Vignette
float vignette = mix(1.0, 0.0, dist * vignette_amount * 2.0);
vec3 color = vec3(r, g, b) * vignette;
COLOR = vec4(color, 1.0);
}
```
## Shader Performance Tips
### Minimize Fragment Operations
```gdscript
// Bad — expensive per-pixel
void fragment() {
COLOR.rgb = pow(texture(TEXTURE, UV).rgb, 3.0) * sin(TIME * 10.0);
}
// Good — precompute where possible
uniform float precomputed_value;
void fragment() {
COLOR.rgb = texture(TEXTURE, UV).rgb * precomputed_value;
}
```
### Use `hint_range` for Uniforms
Always constrain uniforms to prevent editor/script errors:
```gdscript
uniform float speed : hint_range(0.1, 10.0) = 1.0;
uniform vec4 color : hint_color = vec4(1.0);
uniform sampler2D tex : hint_black; // Default black texture fallback
```
### Shader Material Reuse
```gdscript
# Share one ShaderMaterial across multiple nodes for performance
var shared_material = ShaderMaterial.new()
shared_material.shader = preload("res://shaders/wave.gdshader")
$Sprite2D_A.material = shared_material
$Sprite2D_B.material = shared_material # Same material instance
```
## Godot 4.7 VFX Notes
- **GPUParticles** — Improved burst and emit rate controls
- **Screen shaders** — Enhanced SCREEN_TEXTURE sampling quality
- **render_mode** — New `world_vertex_coords` option for world-space shader effects
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools can inspect and modify your project live:
- **`script_read`** / **`script_edit`** — Read and modify shader scripts
- **`scene_tree`** — Inspect particle node hierarchy and parameters
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [GSL Shader Patterns](references/gsl-patterns.md)
- [Particle System Guide](references/particle-systems.md)
| [Post-Processing Effects](references/post-processing.md)

View File

@ -0,0 +1,15 @@
# Claude Code extension for godot-shaders-vfx
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "writing shaders in Godot"
- "Godot visual effects"
- "particle systems Godot"
- "post-processing Godot"
- "Godot Shader Language"
- "GSL shader patterns"
tool_permissions:
- read
- write
- bash

View File

@ -0,0 +1,14 @@
# OpenAI / Codex CLI extension for godot-shaders-vfx
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "writing shaders in Godot"
- "Godot visual effects"
- "particle systems Godot"
- "post-processing Godot"
- "Godot Shader Language"
- "GSL shader patterns"

View File

@ -0,0 +1,285 @@
---
name: godot-state-management
description: "State management patterns for Godot 4.x: enum-based state, finite state machines (FSM), hierarchical FSM, and behavior trees for AI. Use when implementing character states, game flow, menu systems, or AI decision-making in Godot."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- state-machine
- fsm
- behavior-tree
- ai
created: 2026-06-21
---
# State Management in Godot
Patterns for managing game and entity states cleanly.
## Enum-Based State (Simple)
For straightforward state tracking without transitions:
```gdscript
class_name PlayerController extends CharacterBody2D
enum State { IDLE, RUNNING, JUMPING, FALLING, ATTACKING }
var current_state: State = State.IDLE
var velocity: Vector2
func _physics_process(delta: float) -> void:
match current_state:
State.IDLE:
_handle_idle(delta)
State.RUNNING:
_handle_running(delta)
State.JUMPING:
_handle_jumping(delta)
State.FALLING:
_handle_falling(delta)
State.ATTACKING:
_handle_attacking(delta)
func set_state(new_state: State) -> void:
if current_state == new_state:
return
current_state = new_state
```
## Finite State Machine (FSM)
For entities with defined transitions between states:
```gdscript
class_name FSM extends Node
signal state_entered(state_name: StringName)
signal state_exited(state_name: StringName)
var current_state: FSMState = null
var states: Dictionary = {}
func _ready() -> void:
for child in get_children():
if child is FSMState:
states[child.name] = child
func initialize(initial: StringName) -> void:
if not states.has(initial):
push_error("FSM: Initial state '%s' not found" % initial)
return
current_state = states[initial]
current_state.enter()
state_entered.emit(initial)
func change_state(new_state_name: StringName) -> void:
if not states.has(new_state_name):
push_error("FSM: State '%s' not found" % new_state_name)
return
if current_state:
current_state.exit()
state_exited.emit(current_state.name)
current_state = states[new_state_name]
current_state.enter()
state_entered.emit(new_state_name)
func _process(delta: float) -> void:
if current_state:
current_state.update(delta)
func _unhandled_input(event: InputEvent) -> void:
if current_state:
current_state.handle_input(event)
```
### FSM State Base Class
```gdscript
class_name FSMState extends Node
func enter() -> void:
pass # Override
func exit() -> void:
pass # Override
func update(delta: float) -> void:
pass # Override
func handle_input(event: InputEvent) -> void:
pass # Override
# Helper to transition from within a state
func request_state(fsm: FSM, new_state: StringName) -> void:
fsm.change_state(new_state)
```
### Concrete State Example
```gdscript
# EnemyIdle.gd extends FSMState
class_name EnemyIdle extends FSMState
@export var idle_timer: float = 2.0
var _timer: float = 0.0
func enter() -> void:
_timer = 0.0
func update(delta: float) -> void:
_timer += delta
if _timer >= idle_timer:
get_parent().request_state(get_parent().get_node("FSM"), "Patrol")
```
## Hierarchical State Machine
For nested states (e.g., combat has sub-states for melee/range):
```gdscript
class_name HierarchicalFSM extends Node
var current_parent: StringName = ""
var current_child: StringName = ""
# Parent states: "Exploring", "Combat", "Dead"
# Combat child states: "Melee", "Ranged", "Dodging"
func enter_state(parent: StringName, child: StringName) -> void:
current_parent = parent
current_child = child
```
### Hierarchical State Pattern
```gdscript
# PlayerCombatState.gd — parent state delegates to children
class_name PlayerCombatState extends FSMState
var sub_states: Dictionary = {}
var active_sub_state: StringName = ""
func enter() -> void:
# Register sub-states from child nodes
for child in get_children():
if child is FSMState:
sub_states[child.name] = child
func update(delta: float) -> void:
# Choose sub-state based on context
var new_sub_state = _determine_sub_state()
if new_sub_state != active_sub_state:
_switch_sub_state(new_sub_state)
if sub_states.has(active_sub_state):
sub_states[active_sub_state].update(delta)
func _determine_sub_state() -> StringName:
# Example: switch based on weapon type or enemy distance
return "Melee"
```
## Behavior Trees (AI)
For complex AI decision-making:
```gdscript
class_name BehaviorTree extends Node
var root_node: BTNode = null
func tick(delta: float, blackboard: Dictionary) -> BTNode.Status:
if not root_node:
root_node = get_child(0)
return root_node.execute(blackboard, delta)
# Blackboard — shared data between nodes
# { "target": Node, "health": int, "last_seen_pos": Vector2 }
```
### BT Node Types
```gdscript
class_name BTNode extends Node
enum Status { SUCCESS, FAILURE, RUNNING }
func execute(blackboard: Dictionary, delta: float) -> Status:
return Status.SUCCESS # Override in subclasses
# --- Composite Nodes ---
class_name Sequence extends BTNode
func execute(bb: Dictionary, delta: float) -> Status:
for child in get_children():
if child is BTNode:
var result = child.execute(bb, delta)
if result != Status.SUCCESS:
return result
return Status.SUCCESS
class_name Selector extends BTNode
func execute(bb: Dictionary, delta: float) -> Status:
for child in get_children():
if child is BTNode:
var result = child.execute(bb, delta)
if result == Status.SUCCESS:
return Status.SUCCESS
return Status.FAILURE
# --- Leaf Nodes ---
class_name Condition extends BTNode
func check(blackboard: Dictionary) -> bool:
return true # Override
func execute(bb: Dictionary, _delta: float) -> Status:
return Status.SUCCESS if check(bb) else Status.FAILURE
class_name Action extends BTNode
@export var duration: float = 0.0
var elapsed: float = 0.0
func do_action(blackboard: Dictionary) -> void:
pass # Override
func execute(bb: Dictionary, delta: float) -> Status:
if duration > 0:
elapsed += delta
do_action(bb)
return Status.SUCCESS if elapsed >= duration else Status.RUNNING
else:
do_action(bb)
return Status.SUCCESS
```
## Godot 4.7 State Notes
- **tween_await()** — Use for state transitions with animated duration: `await get_tree().create_tween().tween_property(...) .finished`
- **AnimationTree** — Combine FSM logic with AnimationTree state machines for visual feedback
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools can inspect and modify your project live:
- **`scene_tree`** — Inspect scene hierarchy to audit FSM node structure
- **`script_read`** / **`script_edit`** — Read and modify state machine scripts
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [FSM Implementation Guide](references/fsm-guide.md)
- [Behavior Tree Patterns](references/behavior-trees.md)
- [Hierarchical FSM Examples](references/hierarchical-fsm.md)

View File

@ -0,0 +1,15 @@
# Claude Code extension for godot-state-management
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "implementing state machines in Godot"
- "Godot FSM patterns"
- "behavior trees Godot AI"
- "hierarchical state machine Godot"
- "character state management"
- "game flow states Godot"
tool_permissions:
- read
- write
- bash

View File

@ -0,0 +1,14 @@
# OpenAI / Codex CLI extension for godot-state-management
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "implementing state machines in Godot"
- "Godot FSM patterns"
- "behavior trees Godot AI"
- "hierarchical state machine Godot"
- "character state management"
- "game flow states Godot"

289
godot-ui/SKILL.md Normal file
View File

@ -0,0 +1,289 @@
---
name: godot-ui
description: "UI systems for Godot 4.x: Control node hierarchy, containers and layout modes (Anchor, Margin, Center, Fill, Expand), theme resources, custom styles, RichTextLabel formatting, PopupMenu search, and control offset transforms. Use when building menus, HUDs, inventory screens, or any UI in Godot."
license: MIT
compatibility:
- godot-4.0
- godot-4.1
- godot-4.2
- godot-4.3
- godot-4.4
- godot-4.5
- godot-4.6
- godot-4.7
metadata:
author: vl4dt
version: 0.1.0
tags:
- godot
- ui
- control
- container
- theme
- rich-text
created: 2026-06-20
---
# UI Systems for Godot 4.x
Comprehensive patterns for user interfaces, menus, and HUD elements in Godot 4.x.
## Control Node Hierarchy and Containers
### Container Types
Godot 4.x provides several container nodes for responsive UI layouts.
```gdscript
# Vertical layout
var vbox = VBoxContainer.new()
vbox.set_anchors_preset(Control.PRESET_FULL_RECT)
add_child(vbox)
# Horizontal layout
var hbox = HBoxContainer.new()
hbox.add_child(Button.new())
hbox.add_child(Button.new())
vbox.add_child(hbox)
# Grid layout
var grid = GridContainer.new()
grid.columns = 3
for i in range(9):
var btn = Button.new()
btn.text = str(i)
grid.add_child(btn)
vbox.add_child(grid)
```
### Anchor Presets
Use anchor presets for responsive positioning.
```gdscript
# Top-left corner, stays fixed
button.set_anchors_preset(Control.PRESET_TOP_LEFT)
# Full screen (fills parent)
panel.set_anchors_preset(Control.PRESET_FULL_RECT)
# Centered with margins
label.set_anchors_preset(Control.PRESET_CENTER)
label.offset_left = -100
label.offset_right = 100
```
## Layout Modes (Anchor, Margin, Center, Fill, Expand)
### Anchor Mode
Control how a node positions itself relative to its parent.
```gdscript
# Manual anchor setup
control.anchor_right = 1.0 # Right edge sticks to parent right
control.offset_right = -20 # But 20px from the edge
control.anchor_bottom = 1.0
control.offset_bottom = -20
```
### Margin Container
Add padding around a child node.
```gdscript
var margin = MarginContainer.new()
margin.add_theme_constant_override("margin_left", 16)
margin.add_theme_constant_override("margin_top", 16)
margin.add_theme_constant_override("margin_right", 16)
margin.add_theme_constant_override("margin_bottom", 16)
margin.add_child(content_panel)
```
### Center Container
Center a child node within its parent.
```gdscript
var center = CenterContainer.new()
center.horizontal_alignment = CenterContainer.ALIGNMENT_CENTER
center.vertical_alignment = CenterContainer.ALIGNMENT_MIDDLE
center.add_child(dialog_panel)
```
### Fill and Expand
```gdscript
# Fill available space
button.expand_mode = Button.BUTTON_EXPAND_FILL # Fill width and height
button.expand_mode = Button.BUTTON_EXPAND # Fill width only
# Control fills parent rect
control.size_flags_horizontal = Control.SIZE_FILL
control.size_flags_vertical = Control.SIZE_FILL
```
## Theme Resources and Custom Styles
### Creating a Custom Theme
```gdscript
# Create theme in code
var theme = Theme.new()
# Custom font
var custom_font = FontFile.new()
custom_font.font_data = load("res://fonts/my_font.tres")
theme.add_font("Default", "Source", custom_font)
theme.default_font_size = 16
# Custom button style
var normal_style = StyleBoxTexture.new()
normal_style.texture = load("res://ui/button_normal.png")
normal_style.set_margin_left(8)
normal_style.set_margin_right(8)
normal_style.set_margin_top(4)
normal_style.set_margin_bottom(4)
var hover_style = normal_style.duplicate()
hover_style.modulate = Color(1.2, 1.2, 1.2) # Lighter on hover
theme.add_stylebox("normal", "Button", normal_style)
theme.add_stylebox("hover", "Button", hover_style)
```
### Theme Overrides per Control
```gdscript
# Override theme on individual controls
button.add_theme_color_override("font_color", Color.WHITE)
button.add_theme_font_size_override("font_size", 20)
button.add_theme_stylebox_override("normal", custom_style)
# Apply theme to entire scene tree
add_child(button)
button.theme = theme
```
### Theme Variation Pattern
```gdscript
# Create themed button scenes for consistency
# ui_button_primary.tscn, ui_button_secondary.tscn, etc.
const PRIMARY_BTN = preload("res://ui/button_primary.tscn")
const SECONDARY_BTN = preload("res://ui/button_secondary.tscn")
func create_primary_button(text: String) -> Button:
var btn = PRIMARY_BTN.instantiate()
btn.text = text
return btn
```
## RichTextLabel Formatting and Image Sizing
### RichTextEffect Custom Tags
Godot 4.x supports custom rich text effects.
```gdscript
# Use built-in BBCode tags in RichTextLabel
rich_text_label.text = "[color=red]Alert![/color] Health is low."
rich_text_label.text = "[b]Bold[/b] and [i]italic[/i] text"
rich_text_label.text = "[img=64]res://icons/heart.png[/img] 10/20 HP"
# Custom effect for damage numbers
func _process(delta: float):
# RichTextLabel processes BBCode every frame
# For performance, cache formatted text when possible
```
### Image Sizing in RichTextLabel
```gdscript
# Fixed size images
rich_text_label.text = "[img=32]res://icons/coin.png[/img] [img=32]res://icons/coin.png[/img]"
# Scale by font size
rich_text_label.text = "[font_size=48][img=48]res://icons/star.png[/img][/font_size]"
# Responsive sizing using theme font
func get_icon_size() -> int:
return ThemeDB.get_font("Default").get_height() * 2
```
## Godot 4.7 UI Features
### PopupMenu Search (Godot 4.7)
Godot 4.7 adds search/filtering to PopupMenu for better UX in large menus.
```gdscript
# The PopupMenu now supports search filtering out of the box
# Connect the search_changed signal for custom filtering
@onready var popup_menu = $PopupMenu
@onready var search_line_edit = $SearchField
func _ready():
search_line_edit.text_changed.connect(_on_search_changed)
popup_menu.index_pressed.connect(_on_menu_selected)
func _on_search_changed(query: String):
# Filter menu items based on search query
for i in range(popup_menu.item_count):
var item_text = popup_menu.get_item_text(i)
popup_menu.set_item_hidden(i, not item_text.containsn(query))
```
### Control Offset Transforms (Godot 4.7)
Godot 4.7 allows animating control offsets directly for smooth UI transitions.
```gdscript
# Animate panel sliding in using offset transforms
func slide_in(panel: Control):
panel.offset_left = -300 # Start off-screen
var tween = create_tween()
tween.tween_property(panel, "offset_left", 0, 0.3)
tween.set_ease(Tween.EASE_OUT)
func slide_out(panel: Control):
var tween = create_tween()
tween.tween_property(panel, "offset_left", -300, 0.25)
tween.connect("finished", panel.queue_free)
# Use for: sliding menus, notifications, dialog entrances
```
## UI Performance Tips
- Use **Container** nodes instead of manually positioning controls
- Prefer **Anchor presets** over manual offset adjustments when possible
- Cache `RichTextLabel` formatted text when content does not change
- Use `TextureRect` with `STRETCH_SCALE` for responsive images
- Avoid creating/destroying UI nodes every frame — use pooling instead
- Set `visible_ratio` to 0 on off-screen panels instead of queue_free()
## Quick Reference: Container Nodes
| Container | Layout |
|-----------|--------|
| `HBoxContainer` | Horizontal row |
| `VBoxContainer` | Vertical column |
| `GridContainer` | Grid with fixed columns |
| `CenterContainer` | Centered child |
| `MarginContainer` | Padded child |
| `ScrollContainer` | Scrollable area |
| `AspectRatioContainer` | Fixed aspect ratio |
| `Control` (root) | Full rect anchor |
## MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools enhance UI debugging:
- **`ui_inspect`** — Inspect the current control tree and their properties
- **`ui_preview_theme`** — Preview theme changes in real-time
- **`ui_resize_preview`** — Test responsive layouts at different screen sizes
- **`ui_find_control`** — Find controls by path or name in the scene tree
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
## References
- [Control Node Documentation](references/control-reference.md)
- [Container Layout Patterns](references/container-patterns.md)
- [Theme Resource Guide](references/theme-resources.md)

View File

@ -0,0 +1,15 @@
# Claude Code extension for godot-ui
# Safely ignored by other agents per Agent Skills Open Standard
when_to_use:
- "UI development in Godot"
- "Godot Control nodes and containers"
- "Godot custom themes and styling"
- "Godot responsive layouts"
- "Godot input controls UI"
- "Godot HUD design"
tool_permissions:
- read
- write
- bash

View File

@ -0,0 +1,14 @@
# OpenAI / Codex CLI extension for godot-ui
# Safely ignored by other agents per Agent Skills Open Standard
policy:
safe: true
no_network: true
when_to_use:
- "UI development in Godot"
- "Godot Control nodes and containers"
- "Godot custom themes and styling"
- "Godot responsive layouts"
- "Godot input controls UI"
- "Godot HUD design"