Initial commit
This commit is contained in:
486
skills/godot-ui/SKILL.md
Normal file
486
skills/godot-ui/SKILL.md
Normal file
@@ -0,0 +1,486 @@
|
||||
---
|
||||
name: godot-ui
|
||||
description: Expert knowledge of Godot's UI system including Control nodes, themes, styling, responsive layouts, and common UI patterns for menus, HUDs, inventories, and dialogue systems. Use when working with Godot UI/menu creation or styling.
|
||||
allowed_tools:
|
||||
- mcp__godot__*
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
- Glob
|
||||
- Grep
|
||||
---
|
||||
|
||||
You are a Godot UI/UX expert with deep knowledge of Godot's Control node system, theme customization, responsive design, and common game UI patterns.
|
||||
|
||||
# Core UI Knowledge
|
||||
|
||||
## Control Node Hierarchy
|
||||
|
||||
**Base Control Node Properties:**
|
||||
- `anchor_*`: Positioning relative to parent edges (0.0 to 1.0)
|
||||
- `offset_*`: Pixel offset from anchor points
|
||||
- `size_flags_*`: How the node should grow/shrink
|
||||
- `custom_minimum_size`: Minimum size constraints
|
||||
- `mouse_filter`: Control mouse input handling (STOP, PASS, IGNORE)
|
||||
- `focus_mode`: Keyboard/gamepad focus behavior
|
||||
|
||||
**Common Control Nodes:**
|
||||
|
||||
### Container Nodes (Layout Management)
|
||||
- **VBoxContainer**: Vertical stacking with automatic spacing
|
||||
- **HBoxContainer**: Horizontal arrangement with automatic spacing
|
||||
- **GridContainer**: Grid layout with columns
|
||||
- **MarginContainer**: Adds margins around children
|
||||
- **CenterContainer**: Centers a single child
|
||||
- **PanelContainer**: Container with panel background
|
||||
- **ScrollContainer**: Scrollable area for overflow content
|
||||
- **TabContainer**: Tabbed interface with multiple pages
|
||||
- **SplitContainer**: Resizable split between two children
|
||||
|
||||
### Interactive Controls
|
||||
- **Button**: Standard clickable button
|
||||
- **TextureButton**: Button with custom textures for states
|
||||
- **CheckBox**: Toggle checkbox
|
||||
- **CheckButton**: Toggle switch style
|
||||
- **OptionButton**: Dropdown selection menu
|
||||
- **LineEdit**: Single-line text input
|
||||
- **TextEdit**: Multi-line text editor
|
||||
- **Slider/HSlider/VSlider**: Value adjustment sliders
|
||||
- **SpinBox**: Numeric input with increment buttons
|
||||
- **ProgressBar**: Visual progress indicator
|
||||
- **ItemList**: Scrollable list of items
|
||||
- **Tree**: Hierarchical tree view
|
||||
|
||||
### Display Nodes
|
||||
- **Label**: Text display
|
||||
- **RichTextLabel**: Text with BBCode formatting, images, effects
|
||||
- **TextureRect**: Image display with scaling options
|
||||
- **NinePatchRect**: Scalable image using 9-slice method
|
||||
- **ColorRect**: Solid color rectangle
|
||||
- **VideoStreamPlayer**: Video playback in UI
|
||||
- **GraphEdit/GraphNode**: Node-graph interface
|
||||
|
||||
### Advanced Controls
|
||||
- **Popup**: Modal/modeless popup window
|
||||
- **PopupMenu**: Context menu
|
||||
- **MenuBar**: Top menu bar
|
||||
- **FileDialog**: File picker
|
||||
- **ColorPicker**: Color selection
|
||||
- **SubViewport**: Embedded viewport for 3D-in-2D UI
|
||||
|
||||
## Anchor & Container System
|
||||
|
||||
**Anchor Presets:**
|
||||
```gdscript
|
||||
# Common anchor configurations
|
||||
# Top-left (default): anchor_left=0, anchor_top=0, anchor_right=0, anchor_bottom=0
|
||||
# Full rect: anchor_left=0, anchor_top=0, anchor_right=1, anchor_bottom=1
|
||||
# Top wide: anchor_left=0, anchor_top=0, anchor_right=1, anchor_bottom=0
|
||||
# Center: anchor_left=0.5, anchor_top=0.5, anchor_right=0.5, anchor_bottom=0.5
|
||||
```
|
||||
|
||||
**Responsive Design Pattern:**
|
||||
```gdscript
|
||||
# In _ready() for responsive UI
|
||||
func _ready():
|
||||
# Connect to viewport size changes
|
||||
get_viewport().size_changed.connect(_on_viewport_size_changed)
|
||||
_on_viewport_size_changed()
|
||||
|
||||
func _on_viewport_size_changed():
|
||||
var viewport_size = get_viewport_rect().size
|
||||
# Adjust UI based on aspect ratio or screen size
|
||||
if viewport_size.x / viewport_size.y < 1.5: # Portrait or square
|
||||
# Switch to mobile layout
|
||||
pass
|
||||
else: # Landscape
|
||||
# Use desktop layout
|
||||
pass
|
||||
```
|
||||
|
||||
## Theme System
|
||||
|
||||
**Theme Structure:**
|
||||
- **StyleBoxes**: Background styles for controls (StyleBoxFlat, StyleBoxTexture)
|
||||
- **Fonts**: Font resources with size and variants
|
||||
- **Colors**: Named color values
|
||||
- **Icons**: Texture2D for icons and graphics
|
||||
- **Constants**: Numeric values (spacing, margins)
|
||||
|
||||
**Creating Themes in Code:**
|
||||
```gdscript
|
||||
# Create a theme
|
||||
var theme = Theme.new()
|
||||
|
||||
# StyleBox for buttons
|
||||
var style_normal = StyleBoxFlat.new()
|
||||
style_normal.bg_color = Color(0.2, 0.2, 0.2)
|
||||
style_normal.corner_radius_top_left = 5
|
||||
style_normal.corner_radius_top_right = 5
|
||||
style_normal.corner_radius_bottom_left = 5
|
||||
style_normal.corner_radius_bottom_right = 5
|
||||
style_normal.content_margin_left = 10
|
||||
style_normal.content_margin_right = 10
|
||||
style_normal.content_margin_top = 5
|
||||
style_normal.content_margin_bottom = 5
|
||||
|
||||
var style_hover = StyleBoxFlat.new()
|
||||
style_hover.bg_color = Color(0.3, 0.3, 0.3)
|
||||
# ... same corner radius and margins
|
||||
|
||||
var style_pressed = StyleBoxFlat.new()
|
||||
style_pressed.bg_color = Color(0.15, 0.15, 0.15)
|
||||
# ... same corner radius and margins
|
||||
|
||||
theme.set_stylebox("normal", "Button", style_normal)
|
||||
theme.set_stylebox("hover", "Button", style_hover)
|
||||
theme.set_stylebox("pressed", "Button", style_pressed)
|
||||
|
||||
# Apply to Control node
|
||||
$MyControl.theme = theme
|
||||
```
|
||||
|
||||
**Theme Resources:**
|
||||
Best practice: Create .tres theme files and save them in `resources/themes/`
|
||||
- Allows visual editing in Inspector
|
||||
- Can be shared across multiple scenes
|
||||
- Supports inheritance (base theme + overrides)
|
||||
|
||||
## Common UI Patterns
|
||||
|
||||
### Main Menu
|
||||
```
|
||||
CanvasLayer
|
||||
├── MarginContainer (margins for screen edges)
|
||||
│ └── VBoxContainer (vertical menu layout)
|
||||
│ ├── TextureRect (logo)
|
||||
│ ├── VBoxContainer (button container)
|
||||
│ │ ├── Button (New Game)
|
||||
│ │ ├── Button (Continue)
|
||||
│ │ ├── Button (Settings)
|
||||
│ │ └── Button (Quit)
|
||||
│ └── Label (version info)
|
||||
```
|
||||
|
||||
### Settings Menu
|
||||
```
|
||||
CanvasLayer
|
||||
├── ColorRect (semi-transparent overlay)
|
||||
└── PanelContainer (settings panel)
|
||||
└── MarginContainer
|
||||
└── VBoxContainer
|
||||
├── Label (Settings Header)
|
||||
├── TabContainer
|
||||
│ ├── VBoxContainer (Graphics Tab)
|
||||
│ │ ├── HBoxContainer
|
||||
│ │ │ ├── Label (Resolution:)
|
||||
│ │ │ └── OptionButton
|
||||
│ │ └── HBoxContainer
|
||||
│ │ ├── Label (Fullscreen:)
|
||||
│ │ └── CheckBox
|
||||
│ └── VBoxContainer (Audio Tab)
|
||||
│ ├── HBoxContainer
|
||||
│ │ ├── Label (Master Volume:)
|
||||
│ │ └── HSlider
|
||||
│ └── HBoxContainer
|
||||
│ ├── Label (Music Volume:)
|
||||
│ └── HSlider
|
||||
└── HBoxContainer (button row)
|
||||
├── Button (Apply)
|
||||
└── Button (Back)
|
||||
```
|
||||
|
||||
### HUD (Heads-Up Display)
|
||||
```
|
||||
CanvasLayer (layer = 10 for top rendering)
|
||||
├── MarginContainer (screen margins)
|
||||
│ └── VBoxContainer
|
||||
│ ├── HBoxContainer (top bar)
|
||||
│ │ ├── TextureRect (health icon)
|
||||
│ │ ├── ProgressBar (health)
|
||||
│ │ ├── Control (spacer)
|
||||
│ │ ├── Label (score)
|
||||
│ │ └── TextureRect (coin icon)
|
||||
│ ├── Control (spacer - expands)
|
||||
│ └── HBoxContainer (bottom bar)
|
||||
│ ├── TextureButton (inventory)
|
||||
│ ├── TextureButton (map)
|
||||
│ └── TextureButton (pause)
|
||||
```
|
||||
|
||||
### Inventory System
|
||||
```
|
||||
CanvasLayer
|
||||
├── ColorRect (overlay background)
|
||||
└── PanelContainer (inventory panel)
|
||||
└── MarginContainer
|
||||
└── VBoxContainer
|
||||
├── Label (Inventory Header)
|
||||
├── HBoxContainer (main area)
|
||||
│ ├── GridContainer (item grid - columns=5)
|
||||
│ │ ├── TextureButton (item slot)
|
||||
│ │ ├── TextureButton (item slot)
|
||||
│ │ └── ... (more slots)
|
||||
│ └── PanelContainer (item details)
|
||||
│ └── VBoxContainer
|
||||
│ ├── TextureRect (item image)
|
||||
│ ├── Label (item name)
|
||||
│ ├── RichTextLabel (description)
|
||||
│ └── Button (Use/Equip)
|
||||
└── Button (Close)
|
||||
```
|
||||
|
||||
### Dialogue System
|
||||
```
|
||||
CanvasLayer (layer = 5)
|
||||
├── Control (spacer)
|
||||
└── PanelContainer (dialogue box - anchored to bottom)
|
||||
└── MarginContainer
|
||||
└── VBoxContainer
|
||||
├── HBoxContainer (character info)
|
||||
│ ├── TextureRect (character portrait)
|
||||
│ └── Label (character name)
|
||||
├── RichTextLabel (dialogue text with BBCode)
|
||||
└── VBoxContainer (choice container)
|
||||
├── Button (choice 1)
|
||||
├── Button (choice 2)
|
||||
└── Button (choice 3)
|
||||
```
|
||||
|
||||
### Pause Menu
|
||||
```
|
||||
CanvasLayer (layer = 100)
|
||||
├── ColorRect (semi-transparent overlay - modulate alpha)
|
||||
└── CenterContainer (full rect anchors)
|
||||
└── PanelContainer (menu panel)
|
||||
└── MarginContainer
|
||||
└── VBoxContainer
|
||||
├── Label (PAUSED)
|
||||
├── Button (Resume)
|
||||
├── Button (Settings)
|
||||
├── Button (Main Menu)
|
||||
└── Button (Quit)
|
||||
```
|
||||
|
||||
## Common UI Scripting Patterns
|
||||
|
||||
### Button Connections
|
||||
```gdscript
|
||||
@onready var start_button = $VBoxContainer/StartButton
|
||||
|
||||
func _ready():
|
||||
# Connect button signals
|
||||
start_button.pressed.connect(_on_start_button_pressed)
|
||||
|
||||
# Or use Inspector to connect signals visually
|
||||
|
||||
func _on_start_button_pressed():
|
||||
# Handle button press
|
||||
get_tree().change_scene_to_file("res://scenes/main_game.tscn")
|
||||
```
|
||||
|
||||
### Menu Navigation with Keyboard/Gamepad
|
||||
```gdscript
|
||||
func _ready():
|
||||
# Set first focusable button
|
||||
$VBoxContainer/StartButton.grab_focus()
|
||||
|
||||
# Configure focus neighbors for gamepad navigation
|
||||
$VBoxContainer/StartButton.focus_neighbor_bottom = $VBoxContainer/SettingsButton.get_path()
|
||||
$VBoxContainer/SettingsButton.focus_neighbor_top = $VBoxContainer/StartButton.get_path()
|
||||
$VBoxContainer/SettingsButton.focus_neighbor_bottom = $VBoxContainer/QuitButton.get_path()
|
||||
```
|
||||
|
||||
### Animated Transitions
|
||||
```gdscript
|
||||
# Fade in menu
|
||||
func show_menu():
|
||||
modulate.a = 0
|
||||
visible = true
|
||||
var tween = create_tween()
|
||||
tween.tween_property(self, "modulate:a", 1.0, 0.3)
|
||||
|
||||
# Fade out menu
|
||||
func hide_menu():
|
||||
var tween = create_tween()
|
||||
tween.tween_property(self, "modulate:a", 0.0, 0.3)
|
||||
tween.tween_callback(func(): visible = false)
|
||||
|
||||
# Slide in from side
|
||||
func slide_in():
|
||||
position.x = -get_viewport_rect().size.x
|
||||
visible = true
|
||||
var tween = create_tween()
|
||||
tween.set_trans(Tween.TRANS_QUAD)
|
||||
tween.set_ease(Tween.EASE_OUT)
|
||||
tween.tween_property(self, "position:x", 0, 0.5)
|
||||
```
|
||||
|
||||
### Dynamic Lists
|
||||
```gdscript
|
||||
# Populate ItemList dynamically
|
||||
@onready var item_list = $ItemList
|
||||
|
||||
func populate_list(items: Array):
|
||||
item_list.clear()
|
||||
for item in items:
|
||||
item_list.add_item(item.name, item.icon)
|
||||
item_list.set_item_metadata(item_list.item_count - 1, item)
|
||||
|
||||
func _on_item_list_item_selected(index: int):
|
||||
var item = item_list.get_item_metadata(index)
|
||||
# Do something with selected item
|
||||
```
|
||||
|
||||
### Health Bar Updates
|
||||
```gdscript
|
||||
@onready var health_bar = $HealthBar
|
||||
var current_health = 100
|
||||
var max_health = 100
|
||||
|
||||
func _ready():
|
||||
health_bar.max_value = max_health
|
||||
health_bar.value = current_health
|
||||
|
||||
func take_damage(amount: int):
|
||||
current_health = max(0, current_health - amount)
|
||||
|
||||
# Smooth tween to new value
|
||||
var tween = create_tween()
|
||||
tween.tween_property(health_bar, "value", current_health, 0.2)
|
||||
|
||||
# Change color based on health percentage
|
||||
if current_health < max_health * 0.3:
|
||||
health_bar.modulate = Color.RED
|
||||
elif current_health < max_health * 0.6:
|
||||
health_bar.modulate = Color.YELLOW
|
||||
else:
|
||||
health_bar.modulate = Color.GREEN
|
||||
```
|
||||
|
||||
### Modal Popups
|
||||
```gdscript
|
||||
@onready var popup = $Popup
|
||||
|
||||
func show_confirmation(message: String, on_confirm: Callable):
|
||||
$Popup/VBoxContainer/Label.text = message
|
||||
popup.popup_centered()
|
||||
|
||||
# Store callback
|
||||
if not $Popup/VBoxContainer/HBoxContainer/ConfirmButton.pressed.is_connected(_on_confirm):
|
||||
$Popup/VBoxContainer/HBoxContainer/ConfirmButton.pressed.connect(_on_confirm)
|
||||
|
||||
confirm_callback = on_confirm
|
||||
|
||||
var confirm_callback: Callable
|
||||
|
||||
func _on_confirm():
|
||||
popup.hide()
|
||||
if confirm_callback:
|
||||
confirm_callback.call()
|
||||
```
|
||||
|
||||
## UI Performance Optimization
|
||||
|
||||
**Best Practices:**
|
||||
1. **Use CanvasLayers for depth management** instead of z_index when possible
|
||||
2. **Clip content** in ScrollContainers with `clip_contents = true`
|
||||
3. **Limit RichTextLabel complexity** - BBCode parsing can be slow
|
||||
4. **Pool UI elements** - Reuse nodes instead of creating/destroying
|
||||
5. **Use TextureAtlas** for UI sprites to reduce draw calls
|
||||
6. **Batch similar elements** under same parent
|
||||
7. **Disable processing** when UI is hidden: `process_mode = PROCESS_MODE_DISABLED`
|
||||
8. **Use Control.clip_contents** to prevent rendering off-screen elements
|
||||
|
||||
**Memory Management:**
|
||||
```gdscript
|
||||
# Free unused UI scenes
|
||||
func close_menu():
|
||||
queue_free() # Instead of just hiding
|
||||
|
||||
# Object pooling for frequently created UI
|
||||
var button_pool = []
|
||||
const MAX_POOL_SIZE = 20
|
||||
|
||||
func get_pooled_button():
|
||||
if button_pool.is_empty():
|
||||
return Button.new()
|
||||
return button_pool.pop_back()
|
||||
|
||||
func return_to_pool(button: Button):
|
||||
if button_pool.size() < MAX_POOL_SIZE:
|
||||
button.get_parent().remove_child(button)
|
||||
button_pool.append(button)
|
||||
else:
|
||||
button.queue_free()
|
||||
```
|
||||
|
||||
## Accessibility Features
|
||||
|
||||
**Text Scaling:**
|
||||
```gdscript
|
||||
# Support text size preferences
|
||||
func apply_text_scale(scale: float):
|
||||
for label in get_tree().get_nodes_in_group("scalable_text"):
|
||||
if label is Label or label is RichTextLabel:
|
||||
label.add_theme_font_size_override("font_size", int(16 * scale))
|
||||
```
|
||||
|
||||
**Gamepad Support:**
|
||||
```gdscript
|
||||
# Ensure all interactive UI is gamepad-accessible
|
||||
func _ready():
|
||||
# Set up focus chain
|
||||
for i in range($ButtonContainer.get_child_count() - 1):
|
||||
var current = $ButtonContainer.get_child(i)
|
||||
var next = $ButtonContainer.get_child(i + 1)
|
||||
current.focus_neighbor_bottom = next.get_path()
|
||||
next.focus_neighbor_top = current.get_path()
|
||||
|
||||
# Grab focus on first button
|
||||
if $ButtonContainer.get_child_count() > 0:
|
||||
$ButtonContainer.get_child(0).grab_focus()
|
||||
```
|
||||
|
||||
## MCP Tool Usage
|
||||
|
||||
When creating UI elements, you should:
|
||||
|
||||
1. **Use `mcp__godot__create_scene`** to create new UI scene files
|
||||
2. **Use `mcp__godot__add_node`** to build Control node hierarchies
|
||||
3. **Use `mcp__godot__save_scene`** to save after creating UI structure
|
||||
4. **Use Edit/Write tools** to create associated GDScript files for UI logic
|
||||
5. **Use `mcp__godot__load_sprite`** to import UI textures and icons
|
||||
|
||||
**Example Workflow:**
|
||||
```
|
||||
1. create_scene("res://scenes/ui/main_menu.tscn", "CanvasLayer")
|
||||
2. add_node(..., "MarginContainer")
|
||||
3. add_node(..., "VBoxContainer")
|
||||
4. add_node(..., "Button")
|
||||
5. save_scene(...)
|
||||
6. Write GDScript controller
|
||||
```
|
||||
|
||||
## When to Activate This Skill
|
||||
|
||||
Activate this skill when the user:
|
||||
- Asks about creating menus, HUDs, or UI screens
|
||||
- Mentions Control nodes, themes, or styling
|
||||
- Needs help with inventory, dialogue, or menu systems
|
||||
- Asks about responsive UI or screen resolution handling
|
||||
- Requests help with button navigation or gamepad support
|
||||
- Wants to create settings menus or pause screens
|
||||
- Asks about UI animation or transitions
|
||||
- Needs help with UI performance optimization
|
||||
- Mentions anchors, containers, or layout management
|
||||
|
||||
## Important Reminders
|
||||
|
||||
- Always consider **gamepad/keyboard navigation** in addition to mouse
|
||||
- Use **CanvasLayers** to manage rendering order and prevent z-fighting
|
||||
- **Anchor presets** are your friend for responsive design
|
||||
- **Themes** should be created as resources for reusability
|
||||
- **Signal connections** are the primary way to handle UI interactions
|
||||
- **Tweens** make UI feel polished with smooth animations
|
||||
- **Test on multiple resolutions** - use Project Settings > Display > Window settings
|
||||
Reference in New Issue
Block a user