feat : mobile input controller
This commit is contained in:
@@ -917,6 +917,7 @@ func reset() -> void:
|
||||
available_rooms.clear()
|
||||
is_host = false
|
||||
_all_ready = false
|
||||
is_tutorial_mode = false
|
||||
match_duration = 180 # Reset to default 3 minutes
|
||||
game_mode = "Freemode"
|
||||
_update_available_areas(game_mode)
|
||||
|
||||
@@ -7,6 +7,10 @@ var movement_manager: Node
|
||||
var race_manager: Node
|
||||
@onready var SettingsManager = get_node_or_null("/root/SettingsManager")
|
||||
|
||||
# Analog stick repeat throttle (prevents firing a new move every frame while held)
|
||||
var _analog_move_timer: float = 0.0
|
||||
const ANALOG_MOVE_RATE: float = 0.18 # seconds between grid steps when holding stick
|
||||
|
||||
func initialize(p_player: Node3D, p_movement_manager: Node, p_race_manager: Node):
|
||||
player = p_player
|
||||
movement_manager = p_movement_manager
|
||||
@@ -21,16 +25,21 @@ func _process(delta):
|
||||
return
|
||||
|
||||
var move_vec = Vector2i.ZERO
|
||||
# 1. Controller / Joystick Movement
|
||||
if SettingsManager and SettingsManager.settings.controls.get("use_controller", false):
|
||||
var joystick_vec = Input.get_vector("move_west", "move_east", "move_north", "move_south")
|
||||
if joystick_vec.length() > 0.3: # Deadzone
|
||||
|
||||
# 1. Left Analog Stick — always active (controller plugged in, any mode)
|
||||
var joystick_vec = Input.get_vector("move_west", "move_east", "move_north", "move_south", 0.25)
|
||||
if joystick_vec.length() > 0.25:
|
||||
_analog_move_timer -= delta
|
||||
if _analog_move_timer <= 0.0:
|
||||
if abs(joystick_vec.x) > abs(joystick_vec.y):
|
||||
move_vec.x = 1 if joystick_vec.x > 0 else -1
|
||||
else:
|
||||
move_vec.y = 1 if joystick_vec.y > 0 else -1
|
||||
_analog_move_timer = ANALOG_MOVE_RATE
|
||||
else:
|
||||
_analog_move_timer = 0.0 # Reset so next touch fires immediately
|
||||
|
||||
# 2. Keyboard Movement (Fallback)
|
||||
# 2. Keyboard / D-pad Movement (fires via action pressed, handled by event)
|
||||
if move_vec == Vector2i.ZERO:
|
||||
if Input.is_action_pressed("move_north"): move_vec.y -= 1
|
||||
if Input.is_action_pressed("move_south"): move_vec.y += 1
|
||||
@@ -75,8 +84,11 @@ func handle_unhandled_input(event):
|
||||
if not player.is_multiplayer_authority() or player.is_frozen or player.is_stop_frozen or (TurnManager.turn_based_mode and (not player.is_my_turn or movement_manager.is_moving)):
|
||||
return
|
||||
|
||||
# --- Keyboard Shortcuts (Event-based) ---
|
||||
if event is InputEventKey and event.pressed and not event.echo:
|
||||
# --- Action Shortcuts (Keyboard OR Controller Button) ---
|
||||
var is_action_event = (event is InputEventKey and event.pressed and not event.echo) \
|
||||
or (event is InputEventJoypadButton and event.pressed)
|
||||
|
||||
if is_action_event:
|
||||
# Safety check for SettingsManager
|
||||
if not SettingsManager:
|
||||
return
|
||||
@@ -87,21 +99,21 @@ func handle_unhandled_input(event):
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
|
||||
|
||||
# 3. Action Buttons (Remappable via InputMap)
|
||||
# 2. Action Buttons (Remappable via InputMap)
|
||||
if event.is_action_pressed("action_knock_tekton"):
|
||||
if player.powerup_manager:
|
||||
player.powerup_manager.use_special_effect()
|
||||
if player.is_attack_mode and player.has_method("enter_attack_mode"):
|
||||
player.enter_attack_mode()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
elif event.is_action_pressed("action_grab_tekton"):
|
||||
if not player.is_carrying_tekton:
|
||||
if player.powerup_manager and player.powerup_manager.has_method("can_use_special"):
|
||||
player.grab_tekton()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
# Handle spawn point selection if not yet selected
|
||||
if not player.spawn_point_selected and player.highlighted_spawn_points.size() > 0:
|
||||
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
|
||||
@@ -53,7 +53,13 @@ var settings = {
|
||||
|
||||
# Power Bar Controls / Special
|
||||
"attack_mode": KEY_Q,
|
||||
"attack_mode_alt": KEY_U
|
||||
"attack_mode_alt": KEY_U,
|
||||
|
||||
# Controller Button Bindings (JOY_BUTTON_* indices)
|
||||
"ctrl_grab": JOY_BUTTON_A,
|
||||
"ctrl_tekton_grab": JOY_BUTTON_RIGHT_SHOULDER,
|
||||
"ctrl_use_powerup": JOY_BUTTON_Y,
|
||||
"ctrl_attack_mode": JOY_BUTTON_X
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,6 +213,21 @@ func apply_control_settings():
|
||||
event.keycode = secondary_key
|
||||
InputMap.action_add_event(action_name, event)
|
||||
|
||||
# Add Joypad defaults for actions - use saved ctrl_* bindings
|
||||
var joy_button_mapped = -1
|
||||
match action_name:
|
||||
"action_grab": joy_button_mapped = settings.controls.get("ctrl_grab", JOY_BUTTON_A)
|
||||
"use_powerup": joy_button_mapped = settings.controls.get("ctrl_use_powerup", JOY_BUTTON_Y)
|
||||
"action_grab_tekton": joy_button_mapped = settings.controls.get("ctrl_tekton_grab", JOY_BUTTON_RIGHT_SHOULDER)
|
||||
"action_knock_tekton": joy_button_mapped = settings.controls.get("ctrl_attack_mode", JOY_BUTTON_X)
|
||||
|
||||
if action_name == "action_put": joy_button_mapped = JOY_BUTTON_B
|
||||
|
||||
if joy_button_mapped != -1:
|
||||
var joy_btn_event = InputEventJoypadButton.new()
|
||||
joy_btn_event.button_index = joy_button_mapped
|
||||
InputMap.action_add_event(action_name, joy_btn_event)
|
||||
|
||||
# Add Joypad defaults for movement
|
||||
if action_name.begins_with("move_"):
|
||||
var joy_axis = -1
|
||||
@@ -230,6 +251,69 @@ func set_control(action_name: String, keycode: int):
|
||||
emit_signal("control_remapped", action_name, keycode)
|
||||
save_settings()
|
||||
|
||||
# --- Controller Binding ---
|
||||
|
||||
# Godot 4 JoyButton name map (var not const — enum keys can't be used in const dicts)
|
||||
var CTRL_BUTTON_NAMES: Dictionary = {
|
||||
JOY_BUTTON_A: "A / Cross",
|
||||
JOY_BUTTON_B: "B / Circle",
|
||||
JOY_BUTTON_X: "X / Square",
|
||||
JOY_BUTTON_Y: "Y / Triangle",
|
||||
JOY_BUTTON_LEFT_SHOULDER: "LB / L1",
|
||||
JOY_BUTTON_RIGHT_SHOULDER: "RB / R1",
|
||||
JOY_BUTTON_LEFT_STICK: "L3",
|
||||
JOY_BUTTON_RIGHT_STICK: "R3",
|
||||
JOY_BUTTON_BACK: "Select / Share",
|
||||
JOY_BUTTON_START: "Start / Options",
|
||||
JOY_BUTTON_DPAD_UP: "D-Up",
|
||||
JOY_BUTTON_DPAD_DOWN: "D-Down",
|
||||
JOY_BUTTON_DPAD_LEFT: "D-Left",
|
||||
JOY_BUTTON_DPAD_RIGHT: "D-Right",
|
||||
}
|
||||
|
||||
func get_joy_button_name(button_index: int) -> String:
|
||||
return CTRL_BUTTON_NAMES.get(button_index, "Btn %d" % button_index)
|
||||
|
||||
func get_controller_binding_text(ctrl_key: String) -> String:
|
||||
"""Return the button name for a ctrl_* settings key."""
|
||||
var idx = settings.controls.get(ctrl_key, -1)
|
||||
if idx == -1:
|
||||
return "Unbound"
|
||||
return get_joy_button_name(idx)
|
||||
|
||||
func get_action_display(action_key: String) -> String:
|
||||
"""Return keyboard text or controller button name based on use_controller setting."""
|
||||
if settings.controls.get("use_controller", false):
|
||||
# Map action key to its ctrl_* key
|
||||
var ctrl_key_map = {
|
||||
"grab": "ctrl_grab",
|
||||
"use_powerup": "ctrl_use_powerup",
|
||||
"tekton_grab": "ctrl_tekton_grab",
|
||||
"attack_mode": "ctrl_attack_mode",
|
||||
}
|
||||
if ctrl_key_map.has(action_key):
|
||||
return get_controller_binding_text(ctrl_key_map[action_key])
|
||||
# Movement axes on left stick
|
||||
if action_key.begins_with("move_"):
|
||||
return "L-Stick"
|
||||
return get_control_text(action_key)
|
||||
return get_control_text(action_key)
|
||||
|
||||
func set_controller_binding(ctrl_key: String, button_index: int):
|
||||
"""Save a controller button binding and re-apply input maps."""
|
||||
if settings.controls.has(ctrl_key):
|
||||
settings.controls[ctrl_key] = button_index
|
||||
apply_control_settings()
|
||||
emit_signal("control_remapped", ctrl_key, button_index)
|
||||
save_settings()
|
||||
|
||||
func is_controller_button_used(button_index: int) -> String:
|
||||
"""Check if a controller button is already assigned."""
|
||||
for key in settings.controls.keys():
|
||||
if key.begins_with("ctrl_") and settings.controls[key] == button_index:
|
||||
return key
|
||||
return ""
|
||||
|
||||
func get_control_keycode(action_name: String) -> int:
|
||||
return settings.controls.get(action_name, -1)
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ var tekton_grab_button: Button
|
||||
const CONFIG_PATH = "user://touch_controls_settings.cfg"
|
||||
var button_size: float = 70.0
|
||||
var button_opacity: float = 0.7
|
||||
var joystick_size: float = 60.0
|
||||
var joystick_enabled: bool = true
|
||||
var touch_buttons_enabled: bool = true # Master toggle for action buttons
|
||||
var joystick_position: Vector2 = Vector2(120, -120) # Relative to bottom-left
|
||||
@@ -45,20 +46,30 @@ func initialize(p_main: Node):
|
||||
_load_settings()
|
||||
|
||||
# Connect to remapping signals
|
||||
if SettingsManager and not SettingsManager.control_remapped.is_connected(_on_control_remapped):
|
||||
SettingsManager.control_remapped.connect(_on_control_remapped)
|
||||
if SettingsManager:
|
||||
if not SettingsManager.control_remapped.is_connected(_on_control_remapped):
|
||||
SettingsManager.control_remapped.connect(_on_control_remapped)
|
||||
# Also refresh when settings_applied fires (e.g. use_controller toggle)
|
||||
if not SettingsManager.settings_applied.is_connected(_on_settings_applied_refresh):
|
||||
SettingsManager.settings_applied.connect(_on_settings_applied_refresh)
|
||||
|
||||
func _on_control_remapped(_action: String, _key: int):
|
||||
print("[TouchControls] Control remapped: %s. Refreshing labels." % _action)
|
||||
|
||||
# Refresh primary assigned buttons
|
||||
_refresh_all_shortcut_labels()
|
||||
|
||||
func _on_settings_applied_refresh():
|
||||
"""Called when SettingsManager.settings_applied fires (e.g. use_controller toggled)."""
|
||||
_refresh_all_shortcut_labels()
|
||||
|
||||
func _refresh_all_shortcut_labels():
|
||||
"""Re-run _ensure_shortcut_label on all known buttons to reflect current binding mode."""
|
||||
if grab_button: _ensure_shortcut_label(grab_button, "Grab")
|
||||
if put_button: _ensure_shortcut_label(put_button, "Put")
|
||||
if attack_mode_button: _ensure_shortcut_label(attack_mode_button, "AttackMode")
|
||||
if spawn_boost_button: _ensure_shortcut_label(spawn_boost_button, "SpawnBoost")
|
||||
if tekton_grab_button: _ensure_shortcut_label(tekton_grab_button, "TektonGrab")
|
||||
|
||||
# Also check all direct children of containers just in case
|
||||
# Also walk containers for any dynamically added buttons
|
||||
if power_bar_container:
|
||||
for child in power_bar_container.get_children():
|
||||
if child is Button:
|
||||
@@ -106,35 +117,13 @@ func _create_touch_ui():
|
||||
# Check if container already exists (added in scene)
|
||||
var container = self
|
||||
|
||||
# Helper to find or create control
|
||||
var find_or_create_joystick = func():
|
||||
var joy = container.get_node_or_null("VirtualJoystick")
|
||||
if joy:
|
||||
print("[TouchControls] Found existing VirtualJoystick")
|
||||
return joy
|
||||
|
||||
var joystick_script = load("res://scripts/ui/virtual_joystick.gd")
|
||||
joy = Control.new()
|
||||
joy.set_script(joystick_script)
|
||||
joy.name = "VirtualJoystick"
|
||||
joy.set_anchors_preset(Control.PRESET_BOTTOM_LEFT)
|
||||
|
||||
# Use standard size from joystick script defaults (radius 60 -> size 160)
|
||||
var joy_size = Vector2(160, 160)
|
||||
joy.custom_minimum_size = joy_size
|
||||
joy.size = joy_size
|
||||
|
||||
joy.offset_left = 120
|
||||
joy.offset_top = -280
|
||||
joy.offset_right = 280
|
||||
joy.offset_bottom = -120
|
||||
|
||||
container.add_child(joy)
|
||||
return joy
|
||||
|
||||
virtual_joystick = find_or_create_joystick.call()
|
||||
if not virtual_joystick.direction_changed.is_connected(_on_joystick_direction):
|
||||
virtual_joystick.direction_changed.connect(_on_joystick_direction)
|
||||
virtual_joystick = container.get_node_or_null("VirtualJoystick")
|
||||
if virtual_joystick:
|
||||
print("[TouchControls] Found local VirtualJoystick node")
|
||||
if not virtual_joystick.direction_changed.is_connected(_on_joystick_direction):
|
||||
virtual_joystick.direction_changed.connect(_on_joystick_direction)
|
||||
else:
|
||||
push_error("[TouchControls] VirtualJoystick node missing!")
|
||||
|
||||
# --- Actions Containers ---
|
||||
power_bar_container = container.get_node_or_null("PowerBarBtn")
|
||||
@@ -298,10 +287,10 @@ func _ensure_shortcut_label(btn: Button, button_name: String):
|
||||
if not SettingsManager: return
|
||||
|
||||
match button_name:
|
||||
"Grab": existing_lbl.text = SettingsManager.get_control_text("grab")
|
||||
"Put": existing_lbl.text = SettingsManager.get_control_text("put")
|
||||
"AttackMode": existing_lbl.text = SettingsManager.get_control_text("attack_mode")
|
||||
"TektonGrab": existing_lbl.text = SettingsManager.get_control_text("tekton_grab")
|
||||
"Grab": existing_lbl.text = SettingsManager.get_action_display("grab")
|
||||
"Put": existing_lbl.text = SettingsManager.get_action_display("put")
|
||||
"AttackMode": existing_lbl.text = SettingsManager.get_action_display("attack_mode")
|
||||
"TektonGrab": existing_lbl.text = SettingsManager.get_action_display("tekton_grab")
|
||||
|
||||
|
||||
print("[TouchControls] Updated %s shortcut label to: %s" % [button_name, existing_lbl.text])
|
||||
@@ -330,10 +319,10 @@ func _ensure_shortcut_label(btn: Button, button_name: String):
|
||||
shortcut_lbl.add_theme_constant_override("outline_size", 4)
|
||||
|
||||
match button_name:
|
||||
"Grab": shortcut_lbl.text = SettingsManager.get_control_text("grab") if SettingsManager else "Space"
|
||||
"Put": shortcut_lbl.text = SettingsManager.get_control_text("put") if SettingsManager else "R"
|
||||
"AttackMode": shortcut_lbl.text = SettingsManager.get_control_text("attack_mode") if SettingsManager else "Q"
|
||||
"TektonGrab": shortcut_lbl.text = SettingsManager.get_control_text("tekton_grab") if SettingsManager else "G"
|
||||
"Grab": shortcut_lbl.text = SettingsManager.get_action_display("grab") if SettingsManager else "Space"
|
||||
"Put": shortcut_lbl.text = SettingsManager.get_action_display("put") if SettingsManager else "R"
|
||||
"AttackMode": shortcut_lbl.text = SettingsManager.get_action_display("attack_mode") if SettingsManager else "Q"
|
||||
"TektonGrab": shortcut_lbl.text = SettingsManager.get_action_display("tekton_grab") if SettingsManager else "G"
|
||||
|
||||
|
||||
btn.add_child(shortcut_lbl)
|
||||
@@ -434,6 +423,7 @@ func _load_settings():
|
||||
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)
|
||||
joystick_size = config.get_value("touch_controls", "joystick_size", 60.0)
|
||||
touch_buttons_enabled = config.get_value("touch_controls", "touch_buttons_enabled", true)
|
||||
|
||||
# Load button positions
|
||||
@@ -463,6 +453,7 @@ func _save_settings():
|
||||
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", "joystick_size", joystick_size)
|
||||
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)
|
||||
@@ -480,6 +471,8 @@ func _apply_settings():
|
||||
# Apply joystick visibility
|
||||
if virtual_joystick:
|
||||
virtual_joystick.visible = joystick_enabled and _is_touch_device()
|
||||
if virtual_joystick.has_method("set_radius"):
|
||||
virtual_joystick.set_radius(joystick_size)
|
||||
|
||||
# Apply touch buttons visibility - FORCED ON per request to "just show them"
|
||||
# Apply touch buttons visibility
|
||||
|
||||
@@ -30,7 +30,7 @@ var _previous_playerboard_state: Array = []
|
||||
|
||||
func initialize(player_node):
|
||||
# Get PowerUp Inventory UI from scene
|
||||
powerup_inventory_ui = player_node.get_node_or_null("PowerUpInventoryUI")
|
||||
powerup_inventory_ui = player_node.get_node_or_null("TouchLayer/TouchControls/PowerUpInventoryUI")
|
||||
|
||||
# Get node references from main scene
|
||||
playerboard_ui = player_node.get_node_or_null("PlayerBoardUI/PlayerboardUI")
|
||||
|
||||
+13
-2
@@ -202,6 +202,7 @@ func _process(delta):
|
||||
var mesh_cache: Array[MeshInstance3D] = []
|
||||
var original_scales: Array[Vector3] = []
|
||||
var prompt_container: Node3D
|
||||
var _prompt_key_label: Label = null # cached ref for live refresh
|
||||
@onready var SettingsManager = get_node_or_null("/root/SettingsManager")
|
||||
|
||||
func _update_prompt_label():
|
||||
@@ -245,13 +246,23 @@ func _ready():
|
||||
prompt_container.visible = false
|
||||
var key_label = prompt_container.get_node_or_null("KeyLabel")
|
||||
if key_label:
|
||||
_prompt_key_label = key_label
|
||||
var shortcut_text = "G"
|
||||
if SettingsManager and SettingsManager.has_method("get_control_text"):
|
||||
shortcut_text = SettingsManager.get_control_text("tekton_grab")
|
||||
if SettingsManager and SettingsManager.has_method("get_action_display"):
|
||||
shortcut_text = SettingsManager.get_action_display("tekton_grab")
|
||||
key_label.text = "[ " + str(shortcut_text) + " ]"
|
||||
# Listen for controller mode change to refresh live
|
||||
if SettingsManager and not SettingsManager.settings_applied.is_connected(_on_settings_refresh):
|
||||
SettingsManager.settings_applied.connect(_on_settings_refresh)
|
||||
else:
|
||||
push_warning("[Tekton] 'InteractionPrompt' node missing. UI will not appear.")
|
||||
|
||||
func _on_settings_refresh():
|
||||
"""Called when settings_applied fires - refresh the interaction prompt key label."""
|
||||
if _prompt_key_label and SettingsManager:
|
||||
var shortcut_text = SettingsManager.get_action_display("tekton_grab")
|
||||
_prompt_key_label.text = "[ " + shortcut_text + " ]"
|
||||
|
||||
func _cache_meshes(node: Node):
|
||||
if node is MeshInstance3D:
|
||||
mesh_cache.append(node)
|
||||
|
||||
@@ -24,15 +24,18 @@ signal effect_selected(effect: int)
|
||||
func _ready():
|
||||
print("[PowerUpUI] _ready called")
|
||||
|
||||
# Connect to SettingsManager to update labels
|
||||
if SettingsManager and not SettingsManager.control_remapped.is_connected(_on_control_remapped):
|
||||
SettingsManager.control_remapped.connect(_on_control_remapped)
|
||||
# Connect to SettingsManager to update labels on rebind OR controller toggle
|
||||
if SettingsManager:
|
||||
if not SettingsManager.control_remapped.is_connected(_on_control_remapped):
|
||||
SettingsManager.control_remapped.connect(_on_control_remapped)
|
||||
if not SettingsManager.settings_applied.is_connected(_on_control_remapped_refresh):
|
||||
SettingsManager.settings_applied.connect(_on_control_remapped_refresh)
|
||||
|
||||
# New Single Button UI
|
||||
power_up_button = get_node_or_null("PowerUpBtn")
|
||||
# New Single Button UI (moved to InteractionBtn)
|
||||
power_up_button = get_node_or_null("../InteractionBtn/PowerUpBtn")
|
||||
if not power_up_button:
|
||||
# Fallback to Container/PowerUpBtn just in case
|
||||
power_up_button = get_node_or_null("Container/PowerUpBtn")
|
||||
# Fallback just in case
|
||||
power_up_button = get_node_or_null("PowerUpBtn")
|
||||
|
||||
if not power_up_button:
|
||||
print("[PowerUpUI] ERROR: PowerUpBtn not found")
|
||||
@@ -51,6 +54,10 @@ func _on_control_remapped(_action: String, _key: int):
|
||||
# Refresh all labels
|
||||
_update_shortcuts_for_mode(LobbyManager.is_game_mode(GameMode.Mode.STOP_N_GO))
|
||||
|
||||
func _on_control_remapped_refresh():
|
||||
"""Called when settings_applied fires (e.g. use_controller toggled)."""
|
||||
_update_shortcuts_for_mode(LobbyManager.is_game_mode(GameMode.Mode.STOP_N_GO))
|
||||
|
||||
func _setup_powerup_btn(btn: Button):
|
||||
# Start DISABLED
|
||||
btn.disabled = true
|
||||
@@ -105,7 +112,7 @@ func _update_btn_shortcut(btn: Button):
|
||||
return
|
||||
|
||||
# Show universal powerup shortcut
|
||||
sc_lbl.text = " %s " % SettingsManager.get_control_text("use_powerup")
|
||||
sc_lbl.text = " %s " % SettingsManager.get_action_display("use_powerup")
|
||||
|
||||
func _on_btn_pressed(effect_id: int = -1):
|
||||
var target_effect = effect_id if effect_id != -1 else current_effect
|
||||
|
||||
+170
-14
@@ -26,6 +26,25 @@ var listening_action: String = "" # Set when waiting for a keypress
|
||||
# Keys removed from the game - skip them even if stale user config has them
|
||||
const DEPRECATED_ACTIONS: Array = ["put", "put_alt"]
|
||||
|
||||
# Touch Input Controls
|
||||
@onready var joystick_size_slider = %JoystickSizeSlider
|
||||
@onready var joystick_size_value = %JoystickSizeValue
|
||||
@onready var joystick_enabled_toggle = %JoystickEnabledToggle
|
||||
@onready var button_opacity_slider = %ButtonOpacitySlider
|
||||
@onready var button_opacity_value = %ButtonOpacityValue
|
||||
@onready var touch_buttons_enabled_toggle = %TouchButtonsEnabledToggle
|
||||
|
||||
const TOUCH_CONFIG_PATH = "user://touch_controls_settings.cfg"
|
||||
|
||||
# Controller Rebinding
|
||||
@onready var ctrl_grab_btn = %CtrlGrabBtn
|
||||
@onready var ctrl_powerup_btn = %CtrlPowerupBtn
|
||||
@onready var ctrl_tekton_grab_btn = %CtrlTektonGrabBtn
|
||||
@onready var ctrl_attack_btn = %CtrlAttackBtn
|
||||
@onready var ctrl_reset_btn = %CtrlResetBtn
|
||||
|
||||
var listening_ctrl_key: String = "" # ctrl_* key being rebound
|
||||
|
||||
func _ready():
|
||||
# Theme inheritance is broken by CanvasLayer root, no need for theme = null
|
||||
_load_ui_values()
|
||||
@@ -87,6 +106,33 @@ func _load_ui_values():
|
||||
# Controls
|
||||
use_controller_btn.button_pressed = s.controls.get("use_controller", false)
|
||||
_update_all_key_labels()
|
||||
|
||||
# Touch Input
|
||||
_load_touch_ui_values()
|
||||
|
||||
# Controller Bindings
|
||||
_update_all_ctrl_labels()
|
||||
|
||||
func _load_touch_ui_values():
|
||||
"""Load touch control settings from config file."""
|
||||
var config = ConfigFile.new()
|
||||
var joystick_size: float = 60.0
|
||||
var joystick_on: bool = true
|
||||
var btn_opacity: float = 0.7
|
||||
var touch_btns_on: bool = true
|
||||
|
||||
if config.load(TOUCH_CONFIG_PATH) == OK:
|
||||
joystick_size = config.get_value("touch_controls", "joystick_size", 60.0)
|
||||
joystick_on = config.get_value("touch_controls", "joystick_enabled", true)
|
||||
btn_opacity = config.get_value("touch_controls", "button_opacity", 0.7)
|
||||
touch_btns_on = config.get_value("touch_controls", "touch_buttons_enabled", true)
|
||||
|
||||
joystick_size_slider.value = joystick_size
|
||||
joystick_size_value.text = str(int(joystick_size))
|
||||
joystick_enabled_toggle.button_pressed = joystick_on
|
||||
button_opacity_slider.value = btn_opacity
|
||||
button_opacity_value.text = str(int(btn_opacity * 100)) + "%"
|
||||
touch_buttons_enabled_toggle.button_pressed = touch_btns_on
|
||||
|
||||
func _connect_signals():
|
||||
# Video
|
||||
@@ -105,8 +151,21 @@ func _connect_signals():
|
||||
# Controls
|
||||
use_controller_btn.toggled.connect(_on_control_setting_changed.bind("use_controller"))
|
||||
|
||||
# Touch Input
|
||||
joystick_size_slider.value_changed.connect(_on_joystick_size_changed)
|
||||
joystick_enabled_toggle.toggled.connect(_on_touch_toggle_changed.bind("joystick_enabled"))
|
||||
button_opacity_slider.value_changed.connect(_on_button_opacity_changed)
|
||||
touch_buttons_enabled_toggle.toggled.connect(_on_touch_toggle_changed.bind("touch_buttons_enabled"))
|
||||
|
||||
# Controller Rebinding
|
||||
ctrl_grab_btn.pressed.connect(_on_ctrl_remap_pressed.bind("ctrl_grab"))
|
||||
ctrl_powerup_btn.pressed.connect(_on_ctrl_remap_pressed.bind("ctrl_use_powerup"))
|
||||
ctrl_tekton_grab_btn.pressed.connect(_on_ctrl_remap_pressed.bind("ctrl_tekton_grab"))
|
||||
ctrl_attack_btn.pressed.connect(_on_ctrl_remap_pressed.bind("ctrl_attack_mode"))
|
||||
ctrl_reset_btn.pressed.connect(_on_ctrl_reset_pressed)
|
||||
|
||||
# Close
|
||||
close_button.pressed.connect(func(): visible = false)
|
||||
close_button.pressed.connect(func(): listening_ctrl_key = ""; visible = false)
|
||||
|
||||
# Connect remapping buttons
|
||||
for action_name in SettingsManager.settings.controls.keys():
|
||||
@@ -153,6 +212,54 @@ func _on_audio_setting_changed(value: float, key: String):
|
||||
func _on_control_setting_changed(value: bool, key: String):
|
||||
SettingsManager.settings.controls[key] = value
|
||||
SettingsManager.save_settings()
|
||||
# Emit settings_applied so all UI (touch buttons, prompts) refresh their shortcut labels
|
||||
if key == "use_controller":
|
||||
SettingsManager.apply_control_settings()
|
||||
SettingsManager.emit_signal("settings_applied")
|
||||
|
||||
func _on_joystick_size_changed(value: float):
|
||||
joystick_size_value.text = str(int(value))
|
||||
_save_touch_value("joystick_size", value)
|
||||
# Live update the joystick if it's in the scene
|
||||
var tc = _get_touch_controls()
|
||||
if tc:
|
||||
tc.joystick_size = value
|
||||
if tc.virtual_joystick and tc.virtual_joystick.has_method("set_radius"):
|
||||
tc.virtual_joystick.set_radius(value)
|
||||
|
||||
func _on_button_opacity_changed(value: float):
|
||||
button_opacity_value.text = str(int(value * 100)) + "%"
|
||||
_save_touch_value("button_opacity", value)
|
||||
var tc = _get_touch_controls()
|
||||
if tc:
|
||||
tc.button_opacity = value
|
||||
if tc.has_method("_apply_settings"):
|
||||
tc._apply_settings()
|
||||
|
||||
func _on_touch_toggle_changed(state: bool, key: String):
|
||||
_save_touch_value(key, state)
|
||||
var tc = _get_touch_controls()
|
||||
if tc:
|
||||
if key == "joystick_enabled":
|
||||
tc.joystick_enabled = state
|
||||
elif key == "touch_buttons_enabled":
|
||||
tc.touch_buttons_enabled = state
|
||||
if tc.has_method("_apply_settings"):
|
||||
tc._apply_settings()
|
||||
|
||||
func _save_touch_value(key: String, value) -> void:
|
||||
"""Save a single touch config key without overwriting other keys."""
|
||||
var config = ConfigFile.new()
|
||||
config.load(TOUCH_CONFIG_PATH) # Load existing (ignore error if not found)
|
||||
config.set_value("touch_controls", key, value)
|
||||
config.save(TOUCH_CONFIG_PATH)
|
||||
|
||||
func _get_touch_controls() -> Node:
|
||||
"""Try to find the TouchControls node in the Main scene."""
|
||||
var main = get_tree().get_root().get_node_or_null("Main")
|
||||
if main:
|
||||
return main.get_node_or_null("TouchLayer/TouchControls")
|
||||
return null
|
||||
|
||||
func _on_remap_button_pressed(action_name: String):
|
||||
listening_action = action_name
|
||||
@@ -162,40 +269,51 @@ func _on_remap_button_pressed(action_name: String):
|
||||
btn.modulate = Color(0.5, 1.0, 0.5) # Signal "Listening"
|
||||
|
||||
func _input(event):
|
||||
# Keyboard remapping
|
||||
if listening_action != "" and event is InputEventKey and event.pressed:
|
||||
# Capture the key
|
||||
var keycode = event.keycode
|
||||
|
||||
# PREVENT DUPLICATES (User Request)
|
||||
var existing_action = SettingsManager.is_key_used(keycode)
|
||||
if existing_action != "" and existing_action != listening_action:
|
||||
# Visual Feedback for Error
|
||||
var error_btn = get_node_or_null("%" + listening_action.to_pascal_case() + "Btn")
|
||||
if error_btn:
|
||||
error_btn.text = "ALREADY USED!"
|
||||
error_btn.modulate = Color(1.0, 0.3, 0.3) # Red error
|
||||
|
||||
# Play sound if available
|
||||
error_btn.modulate = Color(1.0, 0.3, 0.3)
|
||||
var sfx = get_node_or_null("/root/SfxManager")
|
||||
if sfx and sfx.has_method("play_rpc"):
|
||||
sfx.rpc("play_rpc", "error") # Assumes an error sound exists
|
||||
|
||||
# Reset listening but keep text for briefly before reverting
|
||||
sfx.rpc("play_rpc", "error")
|
||||
listening_action = ""
|
||||
await get_tree().create_timer(1.2).timeout
|
||||
_update_all_key_labels()
|
||||
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
|
||||
# Proceed with remapping if not a duplicate
|
||||
SettingsManager.set_control(listening_action, keycode)
|
||||
|
||||
# Feedback and Reset
|
||||
_update_key_label(listening_action)
|
||||
listening_action = ""
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
# Controller button remapping
|
||||
if listening_ctrl_key != "" and event is InputEventJoypadButton and event.pressed:
|
||||
var btn_idx = event.button_index
|
||||
|
||||
# Consume event to prevent triggers
|
||||
# Duplicate check
|
||||
var existing = SettingsManager.is_controller_button_used(btn_idx)
|
||||
var ctrl_btn = _get_ctrl_btn(listening_ctrl_key)
|
||||
if existing != "" and existing != listening_ctrl_key:
|
||||
if ctrl_btn:
|
||||
ctrl_btn.text = "ALREADY USED!"
|
||||
ctrl_btn.modulate = Color(1.0, 0.3, 0.3)
|
||||
listening_ctrl_key = ""
|
||||
await get_tree().create_timer(1.2).timeout
|
||||
_update_all_ctrl_labels()
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
|
||||
SettingsManager.set_controller_binding(listening_ctrl_key, btn_idx)
|
||||
listening_ctrl_key = ""
|
||||
_update_all_ctrl_labels()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
func _update_all_key_labels():
|
||||
@@ -210,6 +328,44 @@ func _update_key_label(action_name: String):
|
||||
btn.text = SettingsManager.get_control_text(action_name)
|
||||
btn.modulate = Color.WHITE
|
||||
|
||||
func _update_all_ctrl_labels():
|
||||
"""Update all controller button labels from saved settings."""
|
||||
if not SettingsManager: return
|
||||
ctrl_grab_btn.text = SettingsManager.get_controller_binding_text("ctrl_grab")
|
||||
ctrl_grab_btn.modulate = Color.WHITE
|
||||
ctrl_powerup_btn.text = SettingsManager.get_controller_binding_text("ctrl_use_powerup")
|
||||
ctrl_powerup_btn.modulate = Color.WHITE
|
||||
ctrl_tekton_grab_btn.text = SettingsManager.get_controller_binding_text("ctrl_tekton_grab")
|
||||
ctrl_tekton_grab_btn.modulate = Color.WHITE
|
||||
ctrl_attack_btn.text = SettingsManager.get_controller_binding_text("ctrl_attack_mode")
|
||||
ctrl_attack_btn.modulate = Color.WHITE
|
||||
|
||||
func _on_ctrl_remap_pressed(ctrl_key: String):
|
||||
listening_ctrl_key = ctrl_key
|
||||
var btn = _get_ctrl_btn(ctrl_key)
|
||||
if btn:
|
||||
btn.text = "Press a button..."
|
||||
btn.modulate = Color(0.5, 1.0, 0.5)
|
||||
|
||||
func _get_ctrl_btn(ctrl_key: String) -> Button:
|
||||
match ctrl_key:
|
||||
"ctrl_grab": return ctrl_grab_btn
|
||||
"ctrl_use_powerup": return ctrl_powerup_btn
|
||||
"ctrl_tekton_grab": return ctrl_tekton_grab_btn
|
||||
"ctrl_attack_mode": return ctrl_attack_btn
|
||||
return null
|
||||
|
||||
func _on_ctrl_reset_pressed():
|
||||
"""Reset controller bindings to defaults."""
|
||||
if not SettingsManager: return
|
||||
SettingsManager.settings.controls["ctrl_grab"] = JOY_BUTTON_A
|
||||
SettingsManager.settings.controls["ctrl_use_powerup"] = JOY_BUTTON_Y
|
||||
SettingsManager.settings.controls["ctrl_tekton_grab"] = JOY_BUTTON_RIGHT_SHOULDER
|
||||
SettingsManager.settings.controls["ctrl_attack_mode"] = JOY_BUTTON_X
|
||||
SettingsManager.apply_control_settings()
|
||||
SettingsManager.save_settings()
|
||||
_update_all_ctrl_labels()
|
||||
|
||||
func open():
|
||||
_load_ui_values()
|
||||
visible = true
|
||||
|
||||
@@ -27,14 +27,28 @@ var _repeat_timer: float = 0.0
|
||||
var _initial_repeat: bool = true
|
||||
|
||||
func _ready():
|
||||
# Set minimum size for touch target
|
||||
custom_minimum_size = Vector2(joystick_radius * 2 + 40, joystick_radius * 2 + 40)
|
||||
_update_minimum_size()
|
||||
center_position = size / 2
|
||||
|
||||
# Enable touch input
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
set_process(true)
|
||||
|
||||
func set_radius(new_radius: float):
|
||||
joystick_radius = new_radius
|
||||
# Proportional knob
|
||||
knob_radius = new_radius * (25.0 / 60.0)
|
||||
|
||||
_update_minimum_size()
|
||||
# Optional: recalculate center if already initialized
|
||||
if is_inside_tree():
|
||||
center_position = size / 2
|
||||
queue_redraw()
|
||||
|
||||
func _update_minimum_size():
|
||||
custom_minimum_size = Vector2(joystick_radius * 2 + 40, joystick_radius * 2 + 40)
|
||||
size = custom_minimum_size
|
||||
|
||||
func _draw():
|
||||
# Draw base circle
|
||||
var base_circle_color = pressed_color if is_pressed else base_color
|
||||
|
||||
Reference in New Issue
Block a user