8.6 KiB
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
signal value_changed(new_value: String, new_index: int)
Emitted when the value changes, providing both the new value string and its index.
Properties
@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
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
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
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
- Add to Scene: Instance
ValueStepper.tscnin your scene - Set Data Source: Configure the
data_sourceproperty - Connect Signal: Connect the
value_changedsignal
# 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
# Set data_source = "language" in editor or code
language_stepper.data_source = "language"
Automatically loads available languages from SettingsManager.
Resolution Selection
# Set data_source = "resolution"
resolution_stepper.data_source = "resolution"
Provides common resolution options with display names.
Difficulty Selection
# Set data_source = "difficulty"
difficulty_stepper.data_source = "difficulty"
Provides difficulty levels: Easy, Normal, Hard, Nightmare.
Custom Values
# 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
# 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
- Add to
_load_data()method:
func _load_data():
match data_source:
"language":
_load_language_data()
"your_custom_type":
_load_your_custom_data()
# ... other cases
- Implement your loader:
func _load_your_custom_data():
values = ["value1", "value2", "value3"]
display_names = ["Display 1", "Display 2", "Display 3"]
current_index = 0
- Add value application logic:
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:
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_changedsignal for custom save logic - Ensure SettingsManager or your data manager is configured
Display Names Not Showing
- Check that
display_namesarray is properly populated - Ensure
display_names.size()matchesvalues.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.