feat: Implement core game managers, player movement logic, and initial UI scenes.
This commit is contained in:
@@ -0,0 +1,367 @@
|
||||
extends CanvasLayer
|
||||
|
||||
# TouchControlsManager - Manages mobile touch controls including virtual joystick and action buttons
|
||||
|
||||
signal grab_pressed
|
||||
signal put_pressed
|
||||
signal special_pressed
|
||||
|
||||
# Touch control nodes
|
||||
var virtual_joystick: Control
|
||||
var grab_button: Button
|
||||
var put_button: Button
|
||||
var special_button: Button
|
||||
var settings_button: Button
|
||||
|
||||
# Settings - persisted to config file
|
||||
const CONFIG_PATH = "user://touch_controls_settings.cfg"
|
||||
var button_size: float = 70.0
|
||||
var button_opacity: float = 0.7
|
||||
var joystick_enabled: bool = true
|
||||
var touch_buttons_enabled: bool = true # Master toggle for action buttons (grab, put, special)
|
||||
var joystick_position: Vector2 = Vector2(120, -120) # Relative to bottom-left
|
||||
var button_positions: Dictionary = {
|
||||
"grab": Vector2(-200, -240), # Relative to bottom-right
|
||||
"put": Vector2(-120, -160),
|
||||
"special": Vector2(-200, -80)
|
||||
}
|
||||
var button_scale: float = 1.0
|
||||
|
||||
# Reference to main scene and player
|
||||
var main_scene: Node3D
|
||||
var local_player: Node3D
|
||||
|
||||
func initialize(p_main: Node3D):
|
||||
main_scene = p_main
|
||||
_create_touch_ui()
|
||||
_load_settings()
|
||||
|
||||
func set_player(p_player: Node3D):
|
||||
local_player = p_player
|
||||
|
||||
func _create_touch_ui():
|
||||
print("[TouchControls] Creating touch UI...")
|
||||
# Use layer 10 - above regular UI but below pause menu
|
||||
layer = 10
|
||||
|
||||
# Create main container
|
||||
var container = Control.new()
|
||||
container.name = "TouchControls"
|
||||
container.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
container.mouse_filter = Control.MOUSE_FILTER_PASS # Pass input to children
|
||||
add_child(container)
|
||||
|
||||
# Create virtual joystick (bottom-left)
|
||||
var joystick_script = load("res://scripts/ui/virtual_joystick.gd")
|
||||
virtual_joystick = Control.new()
|
||||
virtual_joystick.set_script(joystick_script)
|
||||
virtual_joystick.name = "VirtualJoystick"
|
||||
virtual_joystick.set_anchors_preset(Control.PRESET_BOTTOM_LEFT)
|
||||
|
||||
# Use standard size from joystick script defaults (radius 60 -> size 160)
|
||||
var joy_size = Vector2(160, 160)
|
||||
virtual_joystick.custom_minimum_size = joy_size
|
||||
virtual_joystick.size = joy_size
|
||||
|
||||
# Position relative to Bottom-Left anchor
|
||||
# joystick_position (120, -120) interpreted as margin from anchor
|
||||
# x=120 (right from left edge), y=-120 (up from bottom edge - implies bottom margin)
|
||||
# We want the *center* or *bottom-left* corner?
|
||||
# Assuming (120, -120) is top-left corner of the control relative to anchor?
|
||||
# Let's align bottom-left corner of control to (120, -120) from screen bottom-left
|
||||
# Screen Bottom-Left is (0, 1) in normalized anchors.
|
||||
# offset_left = 120
|
||||
# offset_bottom = -120 (120px up from bottom)
|
||||
# offset_top = -120 - 160 = -280
|
||||
# offset_right = 120 + 160 = 280
|
||||
|
||||
virtual_joystick.offset_left = 120
|
||||
virtual_joystick.offset_top = -280
|
||||
virtual_joystick.offset_right = 280
|
||||
virtual_joystick.offset_bottom = -120
|
||||
|
||||
virtual_joystick.direction_changed.connect(_on_joystick_direction)
|
||||
container.add_child(virtual_joystick)
|
||||
|
||||
# Create action buttons (bottom-right)
|
||||
grab_button = _create_action_button("Grab", "👋", button_positions.grab)
|
||||
put_button = _create_action_button("Put", "📦", button_positions.put)
|
||||
special_button = _create_action_button("Special", "⚡", button_positions.special)
|
||||
|
||||
container.add_child(grab_button)
|
||||
container.add_child(put_button)
|
||||
container.add_child(special_button)
|
||||
|
||||
# Create settings button (top-right corner)
|
||||
settings_button = Button.new()
|
||||
settings_button.name = "SettingsBtn"
|
||||
settings_button.text = "⚙"
|
||||
settings_button.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
||||
settings_button.offset_left = -70 # Use offsets instead of position for anchored controls
|
||||
settings_button.offset_right = -20
|
||||
settings_button.offset_top = 70
|
||||
settings_button.offset_bottom = 120
|
||||
settings_button.custom_minimum_size = Vector2(50, 50)
|
||||
settings_button.mouse_filter = Control.MOUSE_FILTER_STOP # Ensure it receives input
|
||||
settings_button.pressed.connect(_on_settings_pressed)
|
||||
_style_button(settings_button, 0.5)
|
||||
container.add_child(settings_button)
|
||||
|
||||
# Always visible now - controlled by settings toggle
|
||||
# Can be hidden via settings if user doesn't want touch controls on desktop
|
||||
visible = true
|
||||
|
||||
func _create_action_button(button_name: String, icon: String, pos: Vector2) -> Button:
|
||||
var btn = Button.new()
|
||||
btn.name = button_name + "Btn"
|
||||
btn.text = icon
|
||||
btn.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT)
|
||||
# Use offsets strictly for anchored positioning
|
||||
# pos.x and pos.y are negative offsets from bottom-right (e.g. -200, -240)
|
||||
btn.offset_left = pos.x
|
||||
btn.offset_top = pos.y
|
||||
btn.offset_right = pos.x + button_size
|
||||
btn.offset_bottom = pos.y + button_size
|
||||
|
||||
btn.custom_minimum_size = Vector2(button_size, button_size)
|
||||
btn.pivot_offset = Vector2(button_size / 2, button_size / 2) # Center pivot for scaling
|
||||
|
||||
# Connect signals
|
||||
btn.button_down.connect(func(): _on_button_pressed(button_name))
|
||||
btn.button_up.connect(func(): _on_button_released(button_name))
|
||||
|
||||
_style_button(btn, button_opacity)
|
||||
return btn
|
||||
|
||||
func _style_button(btn: Button, opacity: float):
|
||||
var style = StyleBoxFlat.new()
|
||||
style.bg_color = Color(0.2, 0.2, 0.25, opacity)
|
||||
style.border_width_left = 2
|
||||
style.border_width_top = 2
|
||||
style.border_width_right = 2
|
||||
style.border_width_bottom = 2
|
||||
style.border_color = Color(0.647, 0.996, 0.224, 0.8)
|
||||
style.corner_radius_top_left = 15
|
||||
style.corner_radius_top_right = 15
|
||||
style.corner_radius_bottom_right = 15
|
||||
style.corner_radius_bottom_left = 15
|
||||
btn.add_theme_stylebox_override("normal", style)
|
||||
|
||||
var pressed_style = style.duplicate()
|
||||
pressed_style.bg_color = Color(0.4, 0.8, 0.4, opacity + 0.2)
|
||||
btn.add_theme_stylebox_override("pressed", pressed_style)
|
||||
|
||||
var hover_style = style.duplicate()
|
||||
hover_style.bg_color = Color(0.3, 0.3, 0.35, opacity)
|
||||
btn.add_theme_stylebox_override("hover", hover_style)
|
||||
|
||||
btn.add_theme_font_size_override("font_size", 28)
|
||||
|
||||
func _on_joystick_direction(direction: Vector2i):
|
||||
if local_player and local_player.has_method("simple_move_to"):
|
||||
var target_pos = local_player.current_position + direction
|
||||
local_player.movement_manager.simple_move_to(target_pos)
|
||||
|
||||
func _on_button_pressed(button_name: String):
|
||||
if not local_player:
|
||||
return
|
||||
|
||||
# Visual feedback - scale up
|
||||
var btn: Button
|
||||
match button_name:
|
||||
"Grab": btn = grab_button
|
||||
"Put": btn = put_button
|
||||
"Special": btn = special_button
|
||||
|
||||
if btn:
|
||||
var tween = create_tween()
|
||||
tween.tween_property(btn, "scale", Vector2(1.15, 1.15), 0.1)
|
||||
|
||||
# Trigger action
|
||||
match button_name:
|
||||
"Grab":
|
||||
emit_signal("grab_pressed")
|
||||
if local_player.has_method("grab_item"):
|
||||
local_player.grab_item(local_player.current_position)
|
||||
"Put":
|
||||
emit_signal("put_pressed")
|
||||
if local_player.has_method("auto_put_item"):
|
||||
local_player.auto_put_item()
|
||||
"Special":
|
||||
emit_signal("special_pressed")
|
||||
var powerup_mgr = local_player.get_node_or_null("PowerUpManager")
|
||||
if powerup_mgr and powerup_mgr.has_method("use_special_effect"):
|
||||
powerup_mgr.use_special_effect()
|
||||
|
||||
func _on_button_released(button_name: String):
|
||||
var btn: Button
|
||||
match button_name:
|
||||
"Grab": btn = grab_button
|
||||
"Put": btn = put_button
|
||||
"Special": btn = special_button
|
||||
|
||||
if btn:
|
||||
var tween = create_tween()
|
||||
tween.tween_property(btn, "scale", Vector2(1.0, 1.0), 0.1)
|
||||
|
||||
func _on_settings_pressed():
|
||||
# Open settings panel in main scene
|
||||
if main_scene:
|
||||
var settings_panel = main_scene.get_node_or_null("SettingsPanel")
|
||||
if settings_panel:
|
||||
settings_panel.visible = true
|
||||
print("[TouchControls] Opening settings panel")
|
||||
else:
|
||||
print("[TouchControls] SettingsPanel not found in main scene")
|
||||
|
||||
func _is_touch_device() -> bool:
|
||||
# Check if running on mobile
|
||||
return OS.has_feature("android") or OS.has_feature("ios") or OS.has_feature("web_android") or OS.has_feature("web_ios")
|
||||
|
||||
func _load_settings():
|
||||
"""Load touch control settings from config file."""
|
||||
var config = ConfigFile.new()
|
||||
var err = config.load(CONFIG_PATH)
|
||||
if err != OK:
|
||||
print("[TouchControls] No saved settings found, using defaults")
|
||||
return
|
||||
|
||||
# Load settings values
|
||||
button_size = config.get_value("touch_controls", "button_size", 70.0)
|
||||
button_opacity = config.get_value("touch_controls", "button_opacity", 0.7)
|
||||
button_scale = config.get_value("touch_controls", "button_scale", 1.0)
|
||||
joystick_enabled = config.get_value("touch_controls", "joystick_enabled", true)
|
||||
touch_buttons_enabled = config.get_value("touch_controls", "touch_buttons_enabled", true)
|
||||
|
||||
# Load button positions
|
||||
var grab_pos = config.get_value("touch_controls", "grab_position", Vector2(-200, -240))
|
||||
var put_pos = config.get_value("touch_controls", "put_position", Vector2(-120, -160))
|
||||
var special_pos = config.get_value("touch_controls", "special_position", Vector2(-200, -80))
|
||||
button_positions = {"grab": grab_pos, "put": put_pos, "special": special_pos}
|
||||
|
||||
# Apply loaded settings
|
||||
_apply_settings()
|
||||
print("[TouchControls] Settings loaded")
|
||||
|
||||
func _save_settings():
|
||||
"""Save touch control settings to config file."""
|
||||
var config = ConfigFile.new()
|
||||
|
||||
config.set_value("touch_controls", "button_size", button_size)
|
||||
config.set_value("touch_controls", "button_opacity", button_opacity)
|
||||
config.set_value("touch_controls", "button_scale", button_scale)
|
||||
config.set_value("touch_controls", "joystick_enabled", joystick_enabled)
|
||||
config.set_value("touch_controls", "touch_buttons_enabled", touch_buttons_enabled)
|
||||
config.set_value("touch_controls", "grab_position", button_positions.grab)
|
||||
config.set_value("touch_controls", "put_position", button_positions.put)
|
||||
config.set_value("touch_controls", "special_position", button_positions.special)
|
||||
|
||||
var err = config.save(CONFIG_PATH)
|
||||
if err != OK:
|
||||
push_error("[TouchControls] Failed to save settings: %s" % err)
|
||||
else:
|
||||
print("[TouchControls] Settings saved")
|
||||
|
||||
func _apply_settings():
|
||||
"""Apply current settings to UI elements."""
|
||||
# Apply joystick visibility
|
||||
if virtual_joystick:
|
||||
virtual_joystick.visible = joystick_enabled
|
||||
|
||||
# Apply touch buttons visibility - dependent on master joystick_enabled switch
|
||||
# If joystick is disabled, ALL touch controls are hidden
|
||||
# Note: We ignore touch_buttons_enabled here to ensure "Enable Virtual Joystick" shows EVERYTHING as requested
|
||||
var buttons_visible = joystick_enabled
|
||||
|
||||
print("[TouchControls] Applying settings: JoystickEnabled=", joystick_enabled, " ButtonsVisible=", buttons_visible)
|
||||
|
||||
if grab_button:
|
||||
grab_button.visible = buttons_visible
|
||||
grab_button.scale = Vector2(button_scale, button_scale)
|
||||
# Use offsets for anchored controls, not position
|
||||
grab_button.offset_left = button_positions.grab.x
|
||||
grab_button.offset_top = button_positions.grab.y
|
||||
grab_button.offset_right = button_positions.grab.x + button_size
|
||||
grab_button.offset_bottom = button_positions.grab.y + button_size
|
||||
|
||||
if put_button:
|
||||
put_button.visible = buttons_visible
|
||||
put_button.scale = Vector2(button_scale, button_scale)
|
||||
put_button.offset_left = button_positions.put.x
|
||||
put_button.offset_top = button_positions.put.y
|
||||
put_button.offset_right = button_positions.put.x + button_size
|
||||
put_button.offset_bottom = button_positions.put.y + button_size
|
||||
|
||||
if special_button:
|
||||
special_button.visible = buttons_visible
|
||||
special_button.scale = Vector2(button_scale, button_scale)
|
||||
special_button.offset_left = button_positions.special.x
|
||||
special_button.offset_top = button_positions.special.y
|
||||
special_button.offset_right = button_positions.special.x + button_size
|
||||
special_button.offset_bottom = button_positions.special.y + button_size
|
||||
|
||||
# Force layer update
|
||||
visible = true
|
||||
|
||||
# =============================================================================
|
||||
# Public Settings API
|
||||
# =============================================================================
|
||||
|
||||
func set_touch_buttons_enabled(enabled: bool):
|
||||
"""Enable or disable all action buttons (grab, put, special)."""
|
||||
touch_buttons_enabled = enabled
|
||||
_apply_settings()
|
||||
|
||||
func set_joystick_enabled(enabled: bool):
|
||||
"""Enable or disable the virtual joystick (and all touch controls)."""
|
||||
joystick_enabled = enabled
|
||||
_apply_settings()
|
||||
|
||||
func set_button_scale(p_scale: float):
|
||||
"""Set scale for all action buttons."""
|
||||
button_scale = p_scale
|
||||
_apply_settings()
|
||||
|
||||
func set_button_position(button_name: String, new_position: Vector2):
|
||||
"""Update position of a specific button."""
|
||||
button_positions[button_name] = new_position
|
||||
match button_name:
|
||||
"grab":
|
||||
if grab_button:
|
||||
grab_button.offset_left = new_position.x
|
||||
grab_button.offset_top = new_position.y
|
||||
grab_button.offset_right = new_position.x + button_size
|
||||
grab_button.offset_bottom = new_position.y + button_size
|
||||
"put":
|
||||
if put_button:
|
||||
put_button.offset_left = new_position.x
|
||||
put_button.offset_top = new_position.y
|
||||
put_button.offset_right = new_position.x + button_size
|
||||
put_button.offset_bottom = new_position.y + button_size
|
||||
"special":
|
||||
if special_button:
|
||||
special_button.offset_left = new_position.x
|
||||
special_button.offset_top = new_position.y
|
||||
special_button.offset_right = new_position.x + button_size
|
||||
special_button.offset_bottom = new_position.y + button_size
|
||||
|
||||
func get_button_positions() -> Dictionary:
|
||||
"""Get current button positions for settings UI."""
|
||||
return button_positions.duplicate()
|
||||
|
||||
func get_settings() -> Dictionary:
|
||||
"""Get all current settings as a dictionary."""
|
||||
return {
|
||||
"button_size": button_size,
|
||||
"button_opacity": button_opacity,
|
||||
"button_scale": button_scale,
|
||||
"joystick_enabled": joystick_enabled,
|
||||
"touch_buttons_enabled": touch_buttons_enabled,
|
||||
"button_positions": button_positions.duplicate()
|
||||
}
|
||||
|
||||
func show_controls():
|
||||
visible = true
|
||||
|
||||
func hide_controls():
|
||||
visible = false
|
||||
Reference in New Issue
Block a user