281 lines
8.6 KiB
Markdown
281 lines
8.6 KiB
Markdown
# UI Components
|
||
|
||
This document describes the custom UI components available in the Skelly project.
|
||
|
||
## ValueStepper Component
|
||
|
||
### Overview
|
||
|
||
**ValueStepper** is a reusable UI control for stepping through discrete values with left/right arrow navigation. It provides an intuitive interface for selecting from predefined options and is particularly well-suited for game settings menus.
|
||
|
||
**Location**: `scenes/ui/components/ValueStepper.tscn` and `scenes/ui/components/ValueStepper.gd`
|
||
|
||
### Why ValueStepper Exists
|
||
|
||
Godot's built-in controls have limitations for discrete option selection:
|
||
- **OptionButton**: Dropdown popups don't work well with gamepad navigation
|
||
- **SpinBox**: Designed for numeric values, not text options
|
||
- **HSlider**: Better for continuous values, not discrete choices
|
||
|
||
ValueStepper fills this gap by providing:
|
||
- ✅ **Gamepad-friendly** discrete option selection
|
||
- ✅ **No popup complications** - values displayed inline
|
||
- ✅ **Dual input support** - mouse clicks + keyboard/gamepad
|
||
- ✅ **Clean horizontal layout** with current value always visible
|
||
- ✅ **Perfect for game settings** like language, difficulty, resolution
|
||
|
||
### Features
|
||
|
||
- **Multiple Data Sources**: Built-in support for language, resolution, difficulty
|
||
- **Custom Values**: Easy setup with custom arrays of values
|
||
- **Navigation Integration**: Built-in highlighting and input handling
|
||
- **Signal-Based**: Clean event communication with parent scenes
|
||
- **Visual Feedback**: Automatic highlighting and animations
|
||
- **Audio Support**: Integrated click sounds
|
||
- **Flexible Display**: Separate display names and internal values
|
||
|
||
### Visual Structure
|
||
|
||
```
|
||
[<] [Current Value] [>]
|
||
```
|
||
|
||
- **Left Arrow Button** (`<`): Navigate to previous value
|
||
- **Value Display**: Shows current selection (e.g., "English", "Hard", "1920×1080")
|
||
- **Right Arrow Button** (`>`): Navigate to next value
|
||
|
||
## API Reference
|
||
|
||
### Signals
|
||
|
||
```gdscript
|
||
signal value_changed(new_value: String, new_index: int)
|
||
```
|
||
Emitted when the value changes, providing both the new value string and its index.
|
||
|
||
### Properties
|
||
|
||
```gdscript
|
||
@export var data_source: String = "language"
|
||
@export var custom_format_function: String = ""
|
||
```
|
||
|
||
- **data_source**: Determines the data type ("language", "resolution", "difficulty", or "custom")
|
||
- **custom_format_function**: Reserved for future custom formatting (currently unused)
|
||
|
||
### Key Methods
|
||
|
||
#### Setup and Configuration
|
||
```gdscript
|
||
func setup_custom_values(custom_values: Array[String], custom_display_names: Array[String] = [])
|
||
```
|
||
Configure the stepper with custom values and optional display names.
|
||
|
||
#### Value Management
|
||
```gdscript
|
||
func get_current_value() -> String
|
||
func set_current_value(value: String)
|
||
func change_value(direction: int)
|
||
```
|
||
Get, set, or modify the current value programmatically.
|
||
|
||
#### Navigation Integration
|
||
```gdscript
|
||
func set_highlighted(highlighted: bool)
|
||
func handle_input_action(action: String) -> bool
|
||
func get_control_name() -> String
|
||
```
|
||
Integration methods for navigation systems and visual feedback.
|
||
|
||
## Usage Examples
|
||
|
||
### Basic Usage in Scene
|
||
|
||
1. **Add to Scene**: Instance `ValueStepper.tscn` in your scene
|
||
2. **Set Data Source**: Configure the `data_source` property
|
||
3. **Connect Signal**: Connect the `value_changed` signal
|
||
|
||
```gdscript
|
||
# In your scene script
|
||
@onready var language_stepper: ValueStepper = $LanguageStepper
|
||
|
||
func _ready():
|
||
language_stepper.value_changed.connect(_on_language_changed)
|
||
|
||
func _on_language_changed(new_value: String, new_index: int):
|
||
print("Language changed to: ", new_value)
|
||
```
|
||
|
||
### Built-in Data Sources
|
||
|
||
#### Language Selection
|
||
```gdscript
|
||
# Set data_source = "language" in editor or code
|
||
language_stepper.data_source = "language"
|
||
```
|
||
Automatically loads available languages from SettingsManager.
|
||
|
||
#### Resolution Selection
|
||
```gdscript
|
||
# Set data_source = "resolution"
|
||
resolution_stepper.data_source = "resolution"
|
||
```
|
||
Provides common resolution options with display names.
|
||
|
||
#### Difficulty Selection
|
||
```gdscript
|
||
# Set data_source = "difficulty"
|
||
difficulty_stepper.data_source = "difficulty"
|
||
```
|
||
Provides difficulty levels: Easy, Normal, Hard, Nightmare.
|
||
|
||
### Custom Values
|
||
|
||
```gdscript
|
||
# Setup custom theme selector
|
||
var theme_values = ["light", "dark", "blue", "green"]
|
||
var theme_names = ["Light Theme", "Dark Theme", "Blue Theme", "Green Theme"]
|
||
theme_stepper.setup_custom_values(theme_values, theme_names)
|
||
theme_stepper.data_source = "theme" # For better logging
|
||
```
|
||
|
||
### Navigation System Integration
|
||
|
||
```gdscript
|
||
# In a navigation-enabled menu
|
||
var navigable_controls: Array[Control] = []
|
||
|
||
func _setup_navigation():
|
||
navigable_controls.append(volume_slider)
|
||
navigable_controls.append(language_stepper) # Add stepper to navigation
|
||
navigable_controls.append(back_button)
|
||
|
||
func _update_visual_selection():
|
||
for i in range(navigable_controls.size()):
|
||
var control = navigable_controls[i]
|
||
if control is ValueStepper:
|
||
control.set_highlighted(i == current_index)
|
||
else:
|
||
# Handle other control highlighting
|
||
pass
|
||
|
||
func _handle_input(action: String):
|
||
var current_control = navigable_controls[current_index]
|
||
if current_control is ValueStepper:
|
||
if current_control.handle_input_action(action):
|
||
AudioManager.play_ui_click()
|
||
return true
|
||
return false
|
||
```
|
||
|
||
## Integration Patterns
|
||
|
||
### Settings Menu Pattern
|
||
See `scenes/ui/SettingsMenu.gd` for a complete example of integrating ValueStepper into a settings menu with full navigation support.
|
||
|
||
### Multiple Steppers Navigation
|
||
See `examples/ValueStepperExample.gd` for an example showing multiple steppers with keyboard/gamepad navigation.
|
||
|
||
## Extending ValueStepper
|
||
|
||
### Adding New Data Sources
|
||
|
||
1. **Add to `_load_data()` method**:
|
||
```gdscript
|
||
func _load_data():
|
||
match data_source:
|
||
"language":
|
||
_load_language_data()
|
||
"your_custom_type":
|
||
_load_your_custom_data()
|
||
# ... other cases
|
||
```
|
||
|
||
2. **Implement your loader**:
|
||
```gdscript
|
||
func _load_your_custom_data():
|
||
values = ["value1", "value2", "value3"]
|
||
display_names = ["Display 1", "Display 2", "Display 3"]
|
||
current_index = 0
|
||
```
|
||
|
||
3. **Add value application logic**:
|
||
```gdscript
|
||
func _apply_value_change(new_value: String, index: int):
|
||
match data_source:
|
||
"your_custom_type":
|
||
# Apply your custom logic here
|
||
YourManager.set_custom_setting(new_value)
|
||
```
|
||
|
||
### Custom Formatting
|
||
|
||
Override `_update_display()` for custom display formatting:
|
||
|
||
```gdscript
|
||
func _update_display():
|
||
if data_source == "your_custom_type":
|
||
# Custom formatting logic
|
||
value_display.text = "Custom: " + display_names[current_index]
|
||
else:
|
||
super._update_display() # Call parent implementation
|
||
```
|
||
|
||
## Best Practices
|
||
|
||
### When to Use ValueStepper
|
||
- ✅ **Discrete options**: Language, difficulty, resolution, theme
|
||
- ✅ **Settings menus**: Any option with predefined choices
|
||
- ✅ **Game configuration**: Graphics quality, control schemes
|
||
- ✅ **Limited options**: 2-10 options work best
|
||
|
||
### When NOT to Use ValueStepper
|
||
- ❌ **Continuous values**: Use sliders for volume, brightness
|
||
- ❌ **Large lists**: Use ItemList or OptionButton for 20+ items
|
||
- ❌ **Text input**: Use LineEdit for user-entered text
|
||
- ❌ **Numeric input**: Use SpinBox for number entry
|
||
|
||
### Performance Considerations
|
||
- ValueStepper is lightweight and suitable for multiple instances
|
||
- Data loading happens once in `_ready()`
|
||
- Visual updates are minimal (just text changes)
|
||
|
||
### Accessibility
|
||
- Visual highlighting provides clear focus indication
|
||
- Audio feedback confirms user actions
|
||
- Keyboard and gamepad support for non-mouse users
|
||
- Consistent navigation patterns
|
||
|
||
## Common Issues and Solutions
|
||
|
||
### Stepper Not Responding to Input
|
||
- Ensure `handle_input_action()` is called from parent's `_input()`
|
||
- Check that the stepper has proper focus/highlighting
|
||
- Verify input actions are defined in project input map
|
||
|
||
### Values Not Saving
|
||
- Override `_apply_value_change()` to handle persistence
|
||
- Connect to `value_changed` signal for custom save logic
|
||
- Ensure SettingsManager or your data manager is configured
|
||
|
||
### Display Names Not Showing
|
||
- Check that `display_names` array is properly populated
|
||
- Ensure `display_names.size()` matches `values.size()`
|
||
- Verify `_update_display()` is called after data loading
|
||
|
||
## File Structure
|
||
|
||
```
|
||
scenes/ui/components/
|
||
├── ValueStepper.gd # Main component script
|
||
└── ValueStepper.tscn # Component scene
|
||
|
||
examples/
|
||
├── ValueStepperExample.gd # Usage example script
|
||
└── ValueStepperExample.tscn # Example scene
|
||
|
||
docs/
|
||
└── UI_COMPONENTS.md # This documentation
|
||
```
|
||
|
||
This component provides a solid foundation for any game's settings system and can be easily extended for project-specific needs. |