feat: Introduce comprehensive settings management with UI for video, audio, and remappable controls.
This commit is contained in:
@@ -18,12 +18,22 @@ func _process(delta):
|
||||
if TurnManager.turn_based_mode:
|
||||
return
|
||||
|
||||
# Continuous movement input
|
||||
var 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
|
||||
if Input.is_action_pressed("move_east"): move_vec.x += 1
|
||||
if Input.is_action_pressed("move_west"): move_vec.x -= 1
|
||||
# 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
|
||||
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
|
||||
|
||||
# 2. Keyboard Movement (Fallback)
|
||||
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
|
||||
if Input.is_action_pressed("move_east"): move_vec.x += 1
|
||||
if Input.is_action_pressed("move_west"): move_vec.x -= 1
|
||||
|
||||
# Fallback for explicit diagonal actions (if mapped)
|
||||
if move_vec == Vector2i.ZERO:
|
||||
@@ -33,7 +43,7 @@ func _process(delta):
|
||||
elif Input.is_action_pressed("move_northwest"): move_vec = Vector2i(-1, -1)
|
||||
|
||||
|
||||
# Action inputs (still momentary)
|
||||
# 3. Action inputs (momentary)
|
||||
if Input.is_action_just_pressed("action_grab"):
|
||||
player.grab_item(player.current_position)
|
||||
elif Input.is_action_just_pressed("action_put"):
|
||||
|
||||
@@ -5,12 +5,24 @@ extends Node
|
||||
|
||||
const SETTINGS_FILE = "user://game_settings.cfg"
|
||||
|
||||
const RESOLUTIONS = [
|
||||
Vector2i(1024, 576),
|
||||
Vector2i(1280, 720),
|
||||
Vector2i(1366, 768),
|
||||
Vector2i(1600, 900),
|
||||
Vector2i(1920, 1080),
|
||||
Vector2i(2560, 1440)
|
||||
]
|
||||
|
||||
# Default values
|
||||
var settings = {
|
||||
"video": {
|
||||
"fullscreen": true,
|
||||
"vsync": true,
|
||||
"resolution_idx": 1
|
||||
"resolution_idx": 1,
|
||||
"msaa": 0, # 0: Disabled, 1: 2x, 2: 4x, 3: 8x
|
||||
"shadow_quality": 2, # 0: Low, 1: Med, 2: High, 3: Ultra
|
||||
"fps_cap": 2 # 0: Unlimited, 1: 30, 2: 60, 3: 120, 4: 144
|
||||
},
|
||||
"audio": {
|
||||
"master_volume": 0.8,
|
||||
@@ -18,15 +30,24 @@ var settings = {
|
||||
"sfx_volume": 0.9
|
||||
},
|
||||
"controls": {
|
||||
"use_controller": false,
|
||||
# Movement (Keybinds are typically fixed to WASD/Arrows in Input Map, but we show them)
|
||||
"move_up": KEY_W,
|
||||
"move_down": KEY_S,
|
||||
"move_left": KEY_A,
|
||||
"move_right": KEY_D,
|
||||
# Actions
|
||||
"grab": KEY_SPACE,
|
||||
"put": KEY_R,
|
||||
"tekton_grab": KEY_G,
|
||||
# Power-Up Controls
|
||||
"powerup_1": KEY_1,
|
||||
"powerup_2": KEY_2,
|
||||
"powerup_3": KEY_3,
|
||||
"powerup_4": KEY_4,
|
||||
"grab": KEY_SPACE,
|
||||
"put": KEY_R,
|
||||
# Power Bar Controls / Special
|
||||
"attack_mode": KEY_Q,
|
||||
"spawn_boost": KEY_E,
|
||||
"tekton_grab": KEY_G
|
||||
"spawn_boost": KEY_E
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,16 +84,59 @@ func apply_all_settings():
|
||||
|
||||
func apply_video_settings():
|
||||
var video = settings.video
|
||||
|
||||
if video.fullscreen:
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
|
||||
else:
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
|
||||
|
||||
# Apply resolution size when windowed
|
||||
var res_idx = video.resolution_idx
|
||||
if res_idx >= 0 and res_idx < RESOLUTIONS.size():
|
||||
var target_res = RESOLUTIONS[res_idx]
|
||||
DisplayServer.window_set_size(target_res)
|
||||
# Center window after resize
|
||||
var screen = DisplayServer.window_get_current_screen()
|
||||
var screen_rect = DisplayServer.screen_get_usable_rect(screen)
|
||||
var window_size = DisplayServer.window_get_size()
|
||||
DisplayServer.window_set_position(screen_rect.position + (screen_rect.size - window_size) / 2)
|
||||
|
||||
if video.vsync:
|
||||
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED)
|
||||
else:
|
||||
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
|
||||
|
||||
# Apply MSAA
|
||||
var viewport = get_viewport()
|
||||
match video.msaa:
|
||||
0: viewport.msaa_3d = Viewport.MSAA_DISABLED
|
||||
1: viewport.msaa_3d = Viewport.MSAA_2X
|
||||
2: viewport.msaa_3d = Viewport.MSAA_4X
|
||||
3: viewport.msaa_3d = Viewport.MSAA_8X
|
||||
|
||||
# Apply FPS Cap
|
||||
match video.fps_cap:
|
||||
0: Engine.max_fps = 0
|
||||
1: Engine.max_fps = 30
|
||||
2: Engine.max_fps = 60
|
||||
3: Engine.max_fps = 120
|
||||
4: Engine.max_fps = 144
|
||||
|
||||
# Apply Shadow Quality (Simplified for Forward+)
|
||||
match video.shadow_quality:
|
||||
0: # Low
|
||||
RenderingServer.directional_soft_shadow_filter_set_quality(RenderingServer.SHADOW_QUALITY_SOFT_LOW)
|
||||
RenderingServer.positional_soft_shadow_filter_set_quality(RenderingServer.SHADOW_QUALITY_SOFT_LOW)
|
||||
1: # Medium
|
||||
RenderingServer.directional_soft_shadow_filter_set_quality(RenderingServer.SHADOW_QUALITY_SOFT_MEDIUM)
|
||||
RenderingServer.positional_soft_shadow_filter_set_quality(RenderingServer.SHADOW_QUALITY_SOFT_MEDIUM)
|
||||
2: # High
|
||||
RenderingServer.directional_soft_shadow_filter_set_quality(RenderingServer.SHADOW_QUALITY_SOFT_HIGH)
|
||||
RenderingServer.positional_soft_shadow_filter_set_quality(RenderingServer.SHADOW_QUALITY_SOFT_HIGH)
|
||||
3: # Ultra
|
||||
RenderingServer.directional_soft_shadow_filter_set_quality(RenderingServer.SHADOW_QUALITY_SOFT_ULTRA)
|
||||
RenderingServer.positional_soft_shadow_filter_set_quality(RenderingServer.SHADOW_QUALITY_SOFT_ULTRA)
|
||||
|
||||
func apply_audio_settings():
|
||||
var audio = settings.audio
|
||||
set_bus_volume("Master", audio.master_volume)
|
||||
|
||||
@@ -8,6 +8,10 @@ extends CanvasLayer
|
||||
# Video Controls
|
||||
@onready var fullscreen_toggle = %FullscreenToggle
|
||||
@onready var vsync_toggle = %VSyncToggle
|
||||
@onready var resolution_btn = %ResolutionBtn
|
||||
@onready var msaa_btn = %MSAABtn
|
||||
@onready var shadow_btn = %ShadowBtn
|
||||
@onready var fps_btn = %FPSCapBtn
|
||||
|
||||
# Audio Controls
|
||||
@onready var master_slider = %MasterSlider
|
||||
@@ -15,6 +19,7 @@ extends CanvasLayer
|
||||
@onready var sfx_slider = %SFXSlider
|
||||
|
||||
# Controls (Keybinds)
|
||||
@onready var use_controller_btn = %UseControllerBtn
|
||||
@onready var SettingsManager = get_node_or_null("/root/SettingsManager")
|
||||
var listening_action: String = "" # Set when waiting for a keypress
|
||||
|
||||
@@ -37,31 +42,76 @@ func _load_ui_values():
|
||||
fullscreen_toggle.button_pressed = s.video.fullscreen
|
||||
vsync_toggle.button_pressed = s.video.vsync
|
||||
|
||||
# Populate Resolution dropdown if not already populated
|
||||
if resolution_btn.item_count == 0:
|
||||
for i in range(SettingsManager.RESOLUTIONS.size()):
|
||||
var res = SettingsManager.RESOLUTIONS[i]
|
||||
resolution_btn.add_item("%dx%d" % [res.x, res.y])
|
||||
resolution_btn.selected = s.video.resolution_idx
|
||||
|
||||
# MSAA
|
||||
if msaa_btn.item_count == 0:
|
||||
msaa_btn.add_item("Disabled")
|
||||
msaa_btn.add_item("2x")
|
||||
msaa_btn.add_item("4x")
|
||||
msaa_btn.add_item("8x")
|
||||
msaa_btn.selected = s.video.msaa
|
||||
|
||||
# Shadows
|
||||
if shadow_btn.item_count == 0:
|
||||
shadow_btn.add_item("Low")
|
||||
shadow_btn.add_item("Medium")
|
||||
shadow_btn.add_item("High")
|
||||
shadow_btn.add_item("Ultra")
|
||||
shadow_btn.selected = s.video.shadow_quality
|
||||
|
||||
# FPS Cap
|
||||
if fps_btn.item_count == 0:
|
||||
fps_btn.add_item("Unlimited")
|
||||
fps_btn.add_item("30 FPS")
|
||||
fps_btn.add_item("60 FPS")
|
||||
fps_btn.add_item("120 FPS")
|
||||
fps_btn.add_item("144 FPS")
|
||||
fps_btn.selected = s.video.fps_cap
|
||||
|
||||
# Audio
|
||||
master_slider.value = s.audio.master_volume
|
||||
music_slider.value = s.audio.music_volume
|
||||
sfx_slider.value = s.audio.sfx_volume
|
||||
|
||||
# Controls
|
||||
use_controller_btn.button_pressed = s.controls.get("use_controller", false)
|
||||
_update_all_key_labels()
|
||||
|
||||
func _connect_signals():
|
||||
# Video
|
||||
fullscreen_toggle.toggled.connect(_on_video_setting_changed)
|
||||
vsync_toggle.toggled.connect(_on_video_setting_changed)
|
||||
resolution_btn.item_selected.connect(_on_resolution_selected)
|
||||
msaa_btn.item_selected.connect(_on_video_property_changed.bind("msaa"))
|
||||
shadow_btn.item_selected.connect(_on_video_property_changed.bind("shadow_quality"))
|
||||
fps_btn.item_selected.connect(_on_video_property_changed.bind("fps_cap"))
|
||||
|
||||
# Audio
|
||||
master_slider.value_changed.connect(_on_audio_setting_changed.bind("master_volume"))
|
||||
music_slider.value_changed.connect(_on_audio_setting_changed.bind("music_volume"))
|
||||
sfx_slider.value_changed.connect(_on_audio_setting_changed.bind("sfx_volume"))
|
||||
|
||||
# Controls
|
||||
use_controller_btn.toggled.connect(_on_control_setting_changed.bind("use_controller"))
|
||||
|
||||
# Close
|
||||
close_button.pressed.connect(func(): visible = false)
|
||||
|
||||
# Connect remapping buttons
|
||||
# Connect remapping buttons (exclude non-keybinds like use_controller)
|
||||
for action_name in SettingsManager.settings.controls.keys():
|
||||
if action_name == "use_controller":
|
||||
continue
|
||||
|
||||
var btn = get_node_or_null("%" + action_name.to_pascal_case() + "Btn")
|
||||
if btn:
|
||||
if btn.pressed.is_connected(_on_remap_button_pressed):
|
||||
btn.pressed.disconnect(_on_remap_button_pressed)
|
||||
btn.pressed.connect(_on_remap_button_pressed.bind(action_name))
|
||||
|
||||
func _on_video_setting_changed(_unused = false):
|
||||
@@ -70,11 +120,25 @@ func _on_video_setting_changed(_unused = false):
|
||||
SettingsManager.save_settings()
|
||||
SettingsManager.apply_video_settings()
|
||||
|
||||
func _on_resolution_selected(idx: int):
|
||||
SettingsManager.settings.video.resolution_idx = idx
|
||||
SettingsManager.save_settings()
|
||||
SettingsManager.apply_video_settings()
|
||||
|
||||
func _on_video_property_changed(idx: int, property_name: String):
|
||||
SettingsManager.settings.video[property_name] = idx
|
||||
SettingsManager.save_settings()
|
||||
SettingsManager.apply_video_settings()
|
||||
|
||||
func _on_audio_setting_changed(key: String, value: float):
|
||||
SettingsManager.settings.audio[key] = value
|
||||
SettingsManager.save_settings()
|
||||
SettingsManager.apply_audio_settings()
|
||||
|
||||
func _on_control_setting_changed(value: bool, key: String):
|
||||
SettingsManager.settings.controls[key] = value
|
||||
SettingsManager.save_settings()
|
||||
|
||||
func _on_remap_button_pressed(action_name: String):
|
||||
listening_action = action_name
|
||||
var btn = get_node_or_null("%" + action_name.to_pascal_case() + "Btn")
|
||||
|
||||
Reference in New Issue
Block a user