Ported settings menu from GFOLF
All checks were successful
linting & formatting / build (push) Successful in 12s
itch.io publish action / build (linux64, x86_64) (push) Successful in 2m35s
itch.io publish action / build (osx, app) (push) Successful in 2m37s
itch.io publish action / build (win64, exe) (push) Successful in 2m40s

This commit is contained in:
Rob Kelly 2025-03-22 19:16:52 -06:00
parent cdd89ea30f
commit e2ca0cb1a2
48 changed files with 1289 additions and 31 deletions

58
assets/text/text.csv Normal file
View File

@ -0,0 +1,58 @@
keys,en
UI_UNSET,unset
UI_LISTEN,listening...
UI_CANCEL,Cancel
UI_ACCEPT,Accept
UI_START,Start
UI_ON,On
UI_OFF,Off
UI_BACK,"⏎ Back"
UI_LOCKED,Locked
UI_QUIT,Quit
,
PAUSE_HEADING,Paused
PAUSE_RESUME,Resume
PAUSE_SETTINGS,Settings
PAUSE_END,"Quit to Title"
PAUSE_END_MSG,"End the game and return to the title screen?\nUnsaved progress will be lost."
PAUSE_QUIT,"Quit to Desktop"
PAUSE_QUIT_MSG,"Quit to desktop?\nUnsaved progress will be lost."
,
SETTINGS_GAME,Game
SETTINGS_GAME_HEADING,"Game Configuration"
SETTINGS_GAME_ACCESSIBILITY_HEADING,Accessibility
SETTINGS_GAME_CAMERA_HEADING,Camera
SETTINGS_SCREEN_SHAKE,"Enable Screen Shake"
SETTINGS_HEAD_BOB,"Enable Head Bob"
SETTINGS_SENSITIVITY_X,"Sensitivity, Horizontal"
SETTINGS_SENSITIVITY_Y,"Sensitivity, Vertical"
SETTINGS_MOUSE_ACCELERATION,"Mouse Acceleration"
SETTINGS_INVERT_PITCH,"Invert Pitch"
SETTINGS_GRAPHICS,Graphics
SETTINGS_GRAPHICS_HEADING,"Graphics & Display"
SETTINGS_GRAPHICS_DISPLAY_HEADING,Display
SETTINGS_GRAPHICS_FULLSCREEN,Fullscreen
SETTINGS_GRAPHICS_VSYNC,V-Sync
SETTINGS_AUDIO,Audio
SETTINGS_AUDIO_HEADING,"Audio Settings"
SETTINGS_CONTROLS,Controls
SETTINGS_CONTROLS_HEADING,"Control Bindings"
,
ACTION_move_forward,"Move forward"
ACTION_move_left,"Move left"
ACTION_move_back,"Move back"
ACTION_move_right,"Move right"
ACTION_jump,Jump
ACTION_sneak,Sneak
ACTION_sprint,Sprint
ACTION_fire,"Fire equipped tool"
ACTION_interact,Interact
ACTION_switch_mode,"Tool mode switch"
ACTION_select_next_tool,"Select next tool"
ACTION_select_prev_tool,"Select previous tool"
ACTION_select_point_spray,"Select spot spray"
ACTION_select_wide_spray,"Select wide spray"
ACTION_select_brush,"Select toothbrush"
ACTION_select_mp3_player,"Select MP3 player"
ACTION_select_manual,"Select manual"
ACTION_pause,Pause
1 keys en
2 UI_UNSET unset
3 UI_LISTEN listening...
4 UI_CANCEL Cancel
5 UI_ACCEPT Accept
6 UI_START Start
7 UI_ON On
8 UI_OFF Off
9 UI_BACK ⏎ Back
10 UI_LOCKED Locked
11 UI_QUIT Quit
12
13 PAUSE_HEADING Paused
14 PAUSE_RESUME Resume
15 PAUSE_SETTINGS Settings
16 PAUSE_END Quit to Title
17 PAUSE_END_MSG End the game and return to the title screen?\nUnsaved progress will be lost.
18 PAUSE_QUIT Quit to Desktop
19 PAUSE_QUIT_MSG Quit to desktop?\nUnsaved progress will be lost.
20
21 SETTINGS_GAME Game
22 SETTINGS_GAME_HEADING Game Configuration
23 SETTINGS_GAME_ACCESSIBILITY_HEADING Accessibility
24 SETTINGS_GAME_CAMERA_HEADING Camera
25 SETTINGS_SCREEN_SHAKE Enable Screen Shake
26 SETTINGS_HEAD_BOB Enable Head Bob
27 SETTINGS_SENSITIVITY_X Sensitivity, Horizontal
28 SETTINGS_SENSITIVITY_Y Sensitivity, Vertical
29 SETTINGS_MOUSE_ACCELERATION Mouse Acceleration
30 SETTINGS_INVERT_PITCH Invert Pitch
31 SETTINGS_GRAPHICS Graphics
32 SETTINGS_GRAPHICS_HEADING Graphics & Display
33 SETTINGS_GRAPHICS_DISPLAY_HEADING Display
34 SETTINGS_GRAPHICS_FULLSCREEN Fullscreen
35 SETTINGS_GRAPHICS_VSYNC V-Sync
36 SETTINGS_AUDIO Audio
37 SETTINGS_AUDIO_HEADING Audio Settings
38 SETTINGS_CONTROLS Controls
39 SETTINGS_CONTROLS_HEADING Control Bindings
40
41 ACTION_move_forward Move forward
42 ACTION_move_left Move left
43 ACTION_move_back Move back
44 ACTION_move_right Move right
45 ACTION_jump Jump
46 ACTION_sneak Sneak
47 ACTION_sprint Sprint
48 ACTION_fire Fire equipped tool
49 ACTION_interact Interact
50 ACTION_switch_mode Tool mode switch
51 ACTION_select_next_tool Select next tool
52 ACTION_select_prev_tool Select previous tool
53 ACTION_select_point_spray Select spot spray
54 ACTION_select_wide_spray Select wide spray
55 ACTION_select_brush Select toothbrush
56 ACTION_select_mp3_player Select MP3 player
57 ACTION_select_manual Select manual
58 ACTION_pause Pause

View File

@ -0,0 +1,17 @@
[remap]
importer="csv_translation"
type="Translation"
uid="uid://o71c6t5pv7hy"
[deps]
files=["res://assets/text/text.en.translation"]
source_file="res://assets/text/text.csv"
dest_files=["res://assets/text/text.en.translation"]
[params]
compress=true
delimiter=0

View File

@ -17,12 +17,15 @@ warnings/threads/thread_model=2
config/name="Grunk" config/name="Grunk"
run/main_scene="uid://884jqafhtrv0" run/main_scene="uid://884jqafhtrv0"
config/project_settings_override="user://settings.godot"
config/features=PackedStringArray("4.4", "Forward Plus") config/features=PackedStringArray("4.4", "Forward Plus")
run/max_fps=60 run/max_fps=60
[autoload] [autoload]
GameRuntime="*res://src/game/game_runtime.gd"
ItemCatalog="*res://src/items/item_catalog.tscn" ItemCatalog="*res://src/items/item_catalog.tscn"
GameSettings="*res://src/game/game_settings.gd"
GameManager="*res://src/game/game_manager.tscn" GameManager="*res://src/game/game_manager.tscn"
[debug] [debug]
@ -65,6 +68,9 @@ config/input/mouse_sensitivity_x=0.45
config/input/mouse_sensitivity_y=0.45 config/input/mouse_sensitivity_y=0.45
config/input/invert_pitch=false config/input/invert_pitch=false
config/input/mouse_acceleration=30.0 config/input/mouse_acceleration=30.0
audio/buses/override_bus_layout="user://audio_bus_layout.tres"
config/accessibility/enable_screen_shake=true
config/accessibility/enable_head_bob=true
[input] [input]
@ -118,6 +124,16 @@ switch_mode={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
] ]
} }
select_next_tool={
"deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":4,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
select_prev_tool={
"deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":5,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
select_point_spray={ select_point_spray={
"deadzone": 0.2, "deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":49,"key_label":0,"unicode":49,"location":0,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":49,"key_label":0,"unicode":49,"location":0,"echo":false,"script":null)
@ -133,14 +149,14 @@ select_brush={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":51,"key_label":0,"unicode":51,"location":0,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":51,"key_label":0,"unicode":51,"location":0,"echo":false,"script":null)
] ]
} }
select_next_tool={ select_mp3_player={
"deadzone": 0.2, "deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":4,"canceled":false,"pressed":false,"double_click":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":52,"key_label":0,"unicode":52,"location":0,"echo":false,"script":null)
] ]
} }
select_prev_tool={ select_manual={
"deadzone": 0.2, "deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":5,"canceled":false,"pressed":false,"double_click":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":53,"key_label":0,"unicode":53,"location":0,"echo":false,"script":null)
] ]
} }
pause={ pause={
@ -149,6 +165,10 @@ pause={
] ]
} }
[internationalization]
locale/translations=PackedStringArray("res://assets/text/text.en.translation")
[layer_names] [layer_names]
3d_render/layer_1="World" 3d_render/layer_1="World"

View File

@ -5,6 +5,12 @@ class_name Game extends Node
static var manager: GameManagerType: static var manager: GameManagerType:
get(): get():
return GameManager return GameManager
static var settings: GameSettingsType:
get():
return GameSettings
static var runtime: GameRuntimeType:
get():
return GameRuntime
## Global static access to Game singleton ## Global static access to Game singleton
static var instance: Game static var instance: Game

9
src/game/game_runtime.gd Normal file
View File

@ -0,0 +1,9 @@
class_name GameRuntimeType extends Node
## Autoloaded runtime manager
func _notification(what: int) -> void:
if what == NOTIFICATION_WM_CLOSE_REQUEST:
# Handle close request here
# TODO: save?
get_tree().quit()

View File

@ -0,0 +1 @@
uid://dcg434tx4r288

53
src/game/game_settings.gd Normal file
View File

@ -0,0 +1,53 @@
class_name GameSettingsType extends Node
## Autoloaded container for project settings, for quick runtime access.
var version: String
var settings_file: String
var audio_bus_file: String
var mouse_sensitivity_x: float
var mouse_sensitivity_y: float
var mouse_acceleration: float
var invert_pitch: bool
var enable_screen_shake: bool
var enable_head_bob: bool
func _init() -> void:
ProjectSettings.settings_changed.connect(_read_settings)
_read_settings()
_load_audio_bus_override()
func _read_settings() -> void:
version = ProjectSettings.get_setting("application/config/version")
settings_file = ProjectSettings.get_setting("application/config/project_settings_override")
audio_bus_file = ProjectSettings.get_setting("game/audio/buses/override_bus_layout")
mouse_sensitivity_x = ProjectSettings.get_setting("game/config/input/mouse_sensitivity_x")
mouse_sensitivity_y = ProjectSettings.get_setting("game/config/input/mouse_sensitivity_y")
mouse_acceleration = ProjectSettings.get_setting("game/config/input/mouse_acceleration")
invert_pitch = ProjectSettings.get_setting("game/config/input/invert_pitch")
enable_screen_shake = ProjectSettings.get_setting(
"game/config/accessibility/enable_screen_shake"
)
enable_head_bob = ProjectSettings.get_setting("game/config/accessibility/enable_head_bob")
func _load_audio_bus_override() -> void:
# Load override audio bus file
if FileAccess.file_exists(audio_bus_file):
print_debug("Loading audio bus override from ", audio_bus_file)
var bus_layout: AudioBusLayout = load(audio_bus_file)
AudioServer.set_bus_layout(bus_layout)
func write() -> void:
print_debug("Writing settings to ", settings_file)
ProjectSettings.save_custom(settings_file)
print_debug("Writing audio bus levels to ", audio_bus_file)
ResourceSaver.save(AudioServer.generate_bus_layout(), audio_bus_file)

View File

@ -0,0 +1 @@
uid://bo00uxdf81beu

View File

@ -0,0 +1,19 @@
@tool
class_name CheckerContainer extends PanelContainer
## PanelContainer that sets its theme based on its parent parity
@export var even_variation := "CheckerContainerEven"
@export var odd_variation := "CheckerContainerOdd"
func _ready() -> void:
get_parent().child_order_changed.connect(_recompute)
func _is_even_child() -> bool:
var parent := get_parent()
return parent.get_children().find(self) % 2 == 0 if parent else false
func _recompute() -> void:
theme_type_variation = even_variation if _is_even_child() else odd_variation

View File

@ -0,0 +1 @@
uid://csuuvuagv6lpf

View File

@ -0,0 +1,63 @@
@tool
extends Label
## A prompt with the input symbol bound to the given action.
const PROMPT_FORMAT := "{0} - {1}"
const HOLD_FORMAT := "{0} {1}"
const UNKNOWN_LABEL_SYM := "[unknown]"
const ACTION_HOLD_KEY := "ACTION_HOLD_MODIFIER"
@export var action: StringName:
set(value):
action = value
_update()
@export var hold := false:
set(value):
hold = value
_update()
@export var show_name := true:
set(value):
show_name = value
_update()
@export var event_index := 0:
set(value):
event_index = value
_update()
func _ready() -> void:
_update()
func _update() -> void:
var input_symbol: String
var actions := InputMap.action_get_events(action)
if actions and event_index < len(actions):
var primary := actions[event_index]
input_symbol = PromptMap.from_event(primary)
var loc_action := tr(ControlBinding.ACTION_KEY_FMT.format([action]))
if not input_symbol:
input_symbol = str(PromptMap.UNKNOWN_INPUT_SYMBOL)
if hold:
input_symbol = HOLD_FORMAT.format([input_symbol, tr(ACTION_HOLD_KEY)])
if show_name:
text = (
PROMPT_FORMAT
. format(
[
input_symbol,
loc_action if loc_action else str(action),
]
)
)
else:
text = input_symbol

View File

@ -0,0 +1 @@
uid://dn50ip335p7oh

View File

@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3 uid="uid://b47goj32i6sdh"]
[ext_resource type="Script" path="res://src/ui/elements/input_prompt/input_prompt.gd" id="1_qq6w5"]
[node name="InputPrompt" type="Label"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_type_variation = &"InputPrompt"
text = "❓ - ACTION_"
script = ExtResource("1_qq6w5")
hold = null

View File

@ -0,0 +1,97 @@
@tool
class_name NumericSlider extends HBoxContainer
## HSlider with an attached SpinBox
signal value_changed(value: float)
@export_category("Shared Range Properties")
@export var min_value := 0.0:
set(v):
_set_shared("min_value", v)
min_value = v
@export var max_value := 100.0:
set(v):
_set_shared("max_value", v)
max_value = v
@export var step := 1.0:
set(v):
_set_shared("step", v)
step = v
@export var page := 0:
set(v):
_set_shared("page", v)
page = v
@export var value: float:
get:
return _value
set(v):
_value = v
value_changed.emit(v)
@export var exp_edit := false:
set(v):
_set_shared("exp_edit", v)
exp_edit = v
@export var rounded := false:
set(v):
_set_shared("rounded", v)
rounded = v
@export var allow_greater := false:
set(v):
_set_shared("allow_greater", v)
allow_greater = v
@export var allow_lesser := false:
set(v):
_set_shared("allow_lesser", v)
allow_lesser = v
var _value := 0.0:
set(v):
if slider:
slider.set_value_no_signal(v)
if spin_box:
spin_box.set_value_no_signal(v)
_value = v
@onready var slider: HSlider = $HSlider
@onready var spin_box: SpinBox = $SpinBox
func _ready() -> void:
# Force propagation of properties to shared range elements
min_value = min_value
max_value = max_value
step = step
page = page
_value = _value
exp_edit = exp_edit
rounded = rounded
allow_greater = allow_greater
allow_lesser = allow_lesser
func set_value_no_signal(new_value: float) -> void:
_value = new_value
func _set_shared(property: String, new_value: Variant) -> void:
if slider:
slider.set(property, new_value)
if spin_box:
spin_box.set(property, new_value)
func _on_spin_box_value_changed(new_value: float) -> void:
value = new_value
func _on_slider_value_changed(new_value: float) -> void:
value = new_value

View File

@ -0,0 +1 @@
uid://ei2iyprpp4rt

View File

@ -0,0 +1,27 @@
[gd_scene load_steps=2 format=3 uid="uid://dqqcyi26d3bpg"]
[ext_resource type="Script" uid="uid://ei2iyprpp4rt" path="res://src/ui/elements/numeric_slider/numeric_slider.gd" id="1_kcr4o"]
[node name="NumericSlider" type="HBoxContainer"]
anchors_preset = 14
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_top = -8.0
offset_bottom = 10.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_kcr4o")
[node name="HSlider" type="HSlider" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 4
[node name="SpinBox" type="SpinBox" parent="."]
custom_minimum_size = Vector2(120, 0)
layout_mode = 2
alignment = 2
[connection signal="value_changed" from="HSlider" to="." method="_on_slider_value_changed"]
[connection signal="value_changed" from="SpinBox" to="." method="_on_spin_box_value_changed"]

View File

@ -0,0 +1,29 @@
@tool
extends CheckBox
## Checkbox which updates its label based on check state.
@export var true_text := "UI_ON":
set(value):
true_text = value
_refresh()
@export var false_text := "UI_OFF":
set(value):
false_text = value
_refresh()
func _ready() -> void:
_refresh()
func _set_text(value: bool) -> void:
text = true_text if value else false_text
func _refresh() -> void:
_set_text(button_pressed)
func _on_toggled(toggled_on: bool) -> void:
_set_text(toggled_on)

View File

@ -0,0 +1 @@
uid://da0um27cm4eiv

View File

@ -0,0 +1,10 @@
[gd_scene load_steps=2 format=3 uid="uid://b7ce38k7rx466"]
[ext_resource type="Script" path="res://src/ui/elements/text_checkbox/text_checkbox.gd" id="1_6mma0"]
[node name="TextCheckbox" type="CheckBox"]
size_flags_horizontal = 0
text = "UI_OFF"
script = ExtResource("1_6mma0")
[connection signal="toggled" from="." to="." method="_on_toggled"]

Binary file not shown.

View File

@ -5,14 +5,13 @@ extends Control
var _freeze_input := false var _freeze_input := false
@onready var menu_list: VBoxContainer = %MenuList @onready var menu_list: Control = %MenuList
@onready var quit_confirm: CenterContainer = %QuitConfirm @onready var quit_confirm: Control = %QuitConfirm
@onready var end_game_confirm: CenterContainer = %EndGameConfirm @onready var end_game_confirm: Control = %EndGameConfirm
@onready var settings_container: MarginContainer = %SettingsContainer @onready var settings_container: Container = %SettingsContainer
func _ready() -> void: func _ready() -> void:
print_debug("PAUSING")
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
Engine.time_scale = 1.0 Engine.time_scale = 1.0

View File

@ -2,9 +2,7 @@
[ext_resource type="Theme" uid="uid://doq7ay6f7dgfo" path="res://src/ui/menus/menu.theme" id="1_b4t8b"] [ext_resource type="Theme" uid="uid://doq7ay6f7dgfo" path="res://src/ui/menus/menu.theme" id="1_b4t8b"]
[ext_resource type="Script" uid="uid://cllx5glqld8wn" path="res://src/ui/menus/pause_menu/pause_menu.gd" id="1_rd0j2"] [ext_resource type="Script" uid="uid://cllx5glqld8wn" path="res://src/ui/menus/pause_menu/pause_menu.gd" id="1_rd0j2"]
[ext_resource type="PackedScene" uid="uid://d3eaqw2rdurct" path="res://src/ui/menus/settings_menu/settings_menu.tscn" id="3_dowgp"]
[sub_resource type="Resource" id="Resource_6sp1a"]
metadata/__load_path__ = "res://src/ui/menus/settings_menu/settings_menu.tscn"
[node name="PauseMenu" type="Control"] [node name="PauseMenu" type="Control"]
process_mode = 3 process_mode = 3
@ -16,7 +14,7 @@ grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
theme = ExtResource("1_b4t8b") theme = ExtResource("1_b4t8b")
script = ExtResource("1_rd0j2") script = ExtResource("1_rd0j2")
settings_scene = SubResource("Resource_6sp1a") settings_scene = ExtResource("3_dowgp")
[node name="Shade" type="ColorRect" parent="."] [node name="Shade" type="ColorRect" parent="."]
layout_mode = 1 layout_mode = 1
@ -57,7 +55,7 @@ layout_mode = 2
theme_override_colors/font_color = Color(1, 1, 1, 1) theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_outline_color = Color(0, 0, 0, 1) theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 8 theme_override_constants/outline_size = 8
text = "paused" text = "PAUSE_HEADING"
horizontal_alignment = 1 horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="MenuList"] [node name="HSeparator" type="HSeparator" parent="MenuList"]
@ -71,34 +69,35 @@ alignment = 1
[node name="ResumeButton" type="Button" parent="MenuList/VBoxContainer"] [node name="ResumeButton" type="Button" parent="MenuList/VBoxContainer"]
layout_mode = 2 layout_mode = 2
text = "Resume" text = "PAUSE_RESUME"
[node name="SettingsButton" type="Button" parent="MenuList/VBoxContainer"] [node name="SettingsButton" type="Button" parent="MenuList/VBoxContainer"]
layout_mode = 2 layout_mode = 2
text = "Settings" text = "PAUSE_SETTINGS"
[node name="EndGameButton" type="Button" parent="MenuList/VBoxContainer"] [node name="EndGameButton" type="Button" parent="MenuList/VBoxContainer"]
layout_mode = 2 layout_mode = 2
theme_type_variation = &"DangerButton" theme_type_variation = &"DangerButton"
text = "Quit to Title" text = "PAUSE_END"
[node name="QuitButton" type="Button" parent="MenuList/VBoxContainer"] [node name="QuitButton" type="Button" parent="MenuList/VBoxContainer"]
layout_mode = 2 layout_mode = 2
theme_type_variation = &"DangerButton" theme_type_variation = &"DangerButton"
text = "Quit to Desktop" text = "PAUSE_QUIT"
[node name="SettingsContainer" type="MarginContainer" parent="."] [node name="SettingsContainer" type="MarginContainer" parent="."]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(1000, 600)
layout_mode = 1 layout_mode = 1
anchors_preset = 8 anchors_preset = 8
anchor_left = 0.5 anchor_left = 0.5
anchor_top = 0.5 anchor_top = 0.5
anchor_right = 0.5 anchor_right = 0.5
anchor_bottom = 0.5 anchor_bottom = 0.5
offset_left = -640.0 offset_left = -400.0
offset_top = -360.0 offset_top = -300.0
offset_right = 640.0 offset_right = 430.0
offset_bottom = 360.0 offset_bottom = 300.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
mouse_filter = 2 mouse_filter = 2
@ -130,8 +129,7 @@ theme_override_constants/separation = 16
[node name="Label" type="Label" parent="QuitConfirm/PanelContainer/MarginContainer/VBoxContainer"] [node name="Label" type="Label" parent="QuitConfirm/PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2 layout_mode = 2
theme_override_font_sizes/font_size = 24 theme_override_font_sizes/font_size = 24
text = "End the game and return to the title screen? text = "PAUSE_QUIT_MSG"
Unsaved progress will be lost."
horizontal_alignment = 1 horizontal_alignment = 1
[node name="HBoxContainer" type="HBoxContainer" parent="QuitConfirm/PanelContainer/MarginContainer/VBoxContainer"] [node name="HBoxContainer" type="HBoxContainer" parent="QuitConfirm/PanelContainer/MarginContainer/VBoxContainer"]
@ -141,13 +139,13 @@ theme_override_constants/separation = 16
[node name="CancelButton" type="Button" parent="QuitConfirm/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"] [node name="CancelButton" type="Button" parent="QuitConfirm/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
text = "Cancel" text = "UI_CANCEL"
[node name="ConfirmQuitButton" type="Button" parent="QuitConfirm/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"] [node name="ConfirmQuitButton" type="Button" parent="QuitConfirm/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
theme_type_variation = &"DangerButton" theme_type_variation = &"DangerButton"
text = "Quit" text = "UI_QUIT"
[node name="EndGameConfirm" type="CenterContainer" parent="."] [node name="EndGameConfirm" type="CenterContainer" parent="."]
unique_name_in_owner = true unique_name_in_owner = true
@ -176,8 +174,7 @@ theme_override_constants/separation = 16
[node name="Label" type="Label" parent="EndGameConfirm/PanelContainer/MarginContainer/VBoxContainer"] [node name="Label" type="Label" parent="EndGameConfirm/PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2 layout_mode = 2
theme_override_font_sizes/font_size = 24 theme_override_font_sizes/font_size = 24
text = "End the game and return to the desktop? text = "PAUSE_END_MSG"
Unsaved progress will be lost."
horizontal_alignment = 1 horizontal_alignment = 1
[node name="HBoxContainer" type="HBoxContainer" parent="EndGameConfirm/PanelContainer/MarginContainer/VBoxContainer"] [node name="HBoxContainer" type="HBoxContainer" parent="EndGameConfirm/PanelContainer/MarginContainer/VBoxContainer"]
@ -187,13 +184,13 @@ theme_override_constants/separation = 16
[node name="CancelButton" type="Button" parent="EndGameConfirm/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"] [node name="CancelButton" type="Button" parent="EndGameConfirm/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
text = "Cancel" text = "UI_CANCEL"
[node name="ConfirmEndGameButton" type="Button" parent="EndGameConfirm/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"] [node name="ConfirmEndGameButton" type="Button" parent="EndGameConfirm/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
theme_type_variation = &"DangerButton" theme_type_variation = &"DangerButton"
text = "Quit" text = "UI_QUIT"
[connection signal="pressed" from="MenuList/VBoxContainer/ResumeButton" to="." method="resume"] [connection signal="pressed" from="MenuList/VBoxContainer/ResumeButton" to="." method="resume"]
[connection signal="pressed" from="MenuList/VBoxContainer/SettingsButton" to="." method="settings"] [connection signal="pressed" from="MenuList/VBoxContainer/SettingsButton" to="." method="settings"]

View File

@ -0,0 +1,108 @@
class_name ControlBinding extends CheckerContainer
## Input for rebinding an action.
const ACTION_KEY_FMT := "ACTION_{0}"
const LISTENING_TEXT := "UI_LISTEN"
const MOTION_THRESHOLD := 0.5
@export var key: StringName
var listening: bool = false:
set(value):
if button:
button.disabled = value
listening = value
@onready var action: Label = %Action
@onready var button: Button = %Button
func _get_settings_key() -> String:
return "input/%s" % key
func _ready() -> void:
# gdlint:ignore = private-method-call
super._ready()
# Set action label text
var loc_action := tr(ACTION_KEY_FMT.format([key]))
# Fall back to just the key if no localization exists
@warning_ignore("incompatible_ternary")
action.text = loc_action if loc_action else key
# Set the binding label
_set_label_from_binding()
func _set_label_from_binding() -> void:
var actions := InputMap.action_get_events(key)
if actions:
var primary := actions[0]
button.text = PromptMap.from_event(primary)
if button.text == PromptMap.UNKNOWN_INPUT_SYMBOL:
print_debug("No mapping for input event: ", primary)
# Special case: Can't rebind things bound to ESC
if primary is InputEventKey and (primary as InputEventKey).physical_keycode == KEY_ESCAPE:
button.disabled = true
func _input(event: InputEvent) -> void:
if not listening:
return
if event is InputEventKey:
var key_event: InputEventKey = event
if (
key_event.physical_keycode in [KEY_CTRL, KEY_ALT, KEY_SHIFT, KEY_META]
and key_event.pressed
):
# Ignore modifier key until release
return
if key_event.physical_keycode == KEY_ESCAPE:
get_viewport().set_input_as_handled()
cancel_rebinding()
return
if event is InputEventJoypadMotion:
var motion_event: InputEventJoypadMotion = event
if abs(motion_event.axis_value) < MOTION_THRESHOLD:
# Ignore axis motion unless it's over our threshold
return
if (
event is InputEventKey
or event is InputEventMouseButton
or event is InputEventJoypadButton
or event is InputEventJoypadMotion
):
get_viewport().set_input_as_handled()
rebind(event)
func start_listening() -> void:
button.text = LISTENING_TEXT
listening = true
func cancel_rebinding() -> void:
_set_label_from_binding()
listening = false
func rebind(event: InputEvent) -> void:
# Clear previous binding
InputMap.action_erase_events(key)
# Add new binding
InputMap.action_add_event(key, event)
# Update label
_set_label_from_binding()
listening = false
func apply() -> void:
var setting: Dictionary = ProjectSettings.get_setting(_get_settings_key())
setting["events"] = InputMap.action_get_events(key)
ProjectSettings.set_setting(_get_settings_key(), setting)

View File

@ -0,0 +1 @@
uid://y0kwpyt3w5bt

View File

@ -0,0 +1,36 @@
[gd_scene load_steps=3 format=3 uid="uid://dwvpddd7id1h"]
[ext_resource type="Script" uid="uid://y0kwpyt3w5bt" path="res://src/ui/menus/settings_menu/control_binding/control_binding.gd" id="1_7mwhu"]
[ext_resource type="Theme" uid="uid://doq7ay6f7dgfo" path="res://src/ui/menus/menu.theme" id="1_82ajm"]
[node name="ControlBinding" type="PanelContainer" groups=["ControlBindings"]]
theme = ExtResource("1_82ajm")
theme_type_variation = &"CheckerContainerOdd"
script = ExtResource("1_7mwhu")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
theme_override_constants/margin_left = 8
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 8
theme_override_constants/margin_bottom = 8
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 48
alignment = 2
[node name="Action" type="Label" parent="MarginContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
theme_type_variation = &"SettingsInputLabel"
text = "Action"
[node name="Button" type="Button" parent="MarginContainer/HBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(300, 42)
layout_mode = 2
theme_type_variation = &"InputBindButton"
text = "UI_UNSET"
[connection signal="pressed" from="MarginContainer/HBoxContainer/Button" to="." method="start_listening"]

View File

@ -0,0 +1,13 @@
extends Setting
## Setting with checkbox representing a boolean value.
@onready var checkbox: CheckBox = %TextCheckbox
func initialize_value(value: Variant) -> void:
@warning_ignore("unsafe_cast")
checkbox.button_pressed = value as bool
func get_value() -> Variant:
return checkbox.button_pressed

View File

@ -0,0 +1 @@
uid://dv8n1d4g4awyc

View File

@ -0,0 +1,12 @@
[gd_scene load_steps=4 format=3 uid="uid://bpmpj4n6xp17l"]
[ext_resource type="PackedScene" uid="uid://dcah6r3ku60g6" path="res://src/ui/menus/settings_menu/settings/setting/setting.tscn" id="1_5t42f"]
[ext_resource type="Script" uid="uid://dv8n1d4g4awyc" path="res://src/ui/menus/settings_menu/settings/checkbox_setting/checkbox_setting.gd" id="2_mwq55"]
[ext_resource type="PackedScene" uid="uid://b7ce38k7rx466" path="res://src/ui/elements/text_checkbox/text_checkbox.tscn" id="3_fosy2"]
[node name="CheckboxSetting" instance=ExtResource("1_5t42f")]
script = ExtResource("2_mwq55")
[node name="TextCheckbox" parent="PanelContainer/MarginContainer" index="0" instance=ExtResource("3_fosy2")]
unique_name_in_owner = true
layout_mode = 2

View File

@ -0,0 +1,11 @@
extends "res://src/ui/menus/settings_menu/settings/checkbox_setting/checkbox_setting.gd"
## Special case setting for fullscreen mode
func initialize_value(value: Variant) -> void:
@warning_ignore("unsafe_cast")
checkbox.button_pressed = (value as Window.Mode) == Window.MODE_FULLSCREEN
func get_value() -> Variant:
return Window.MODE_FULLSCREEN if checkbox.button_pressed else Window.MODE_WINDOWED

View File

@ -0,0 +1 @@
uid://wlo8hxahp1x7

View File

@ -0,0 +1,13 @@
extends Setting
## Setting with a dropdown menu representing an enum value
@onready var dropdown: OptionButton = %Dropdown
func initialize_value(value: Variant) -> void:
@warning_ignore("unsafe_cast")
dropdown.selected = dropdown.get_item_index(value as int)
func get_value() -> Variant:
return dropdown.get_item_id(dropdown.selected)

View File

@ -0,0 +1 @@
uid://cfsf8k44t38g7

View File

@ -0,0 +1,13 @@
[gd_scene load_steps=3 format=3 uid="uid://dpry41u0ctikn"]
[ext_resource type="PackedScene" uid="uid://dcah6r3ku60g6" path="res://src/ui/menus/settings_menu/settings/setting/setting.tscn" id="1_km84n"]
[ext_resource type="Script" path="res://src/ui/menus/settings_menu/settings/dropdown_setting/dropdown_setting.gd" id="2_ubo13"]
[node name="DropdownSetting" instance=ExtResource("1_km84n")]
script = ExtResource("2_ubo13")
[node name="Dropdown" type="OptionButton" parent="PanelContainer/MarginContainer" index="0"]
unique_name_in_owner = true
layout_mode = 2
selected = 0
item_count = 1

View File

@ -0,0 +1,13 @@
extends Setting
## Setting with numeric slider representing a floating-point value
@onready var slider: NumericSlider = %NumericSlider
func initialize_value(value: Variant) -> void:
@warning_ignore("unsafe_cast")
slider.value = value as float
func get_value() -> Variant:
return slider.value

View File

@ -0,0 +1 @@
uid://dpxpgs6iw7b7x

View File

@ -0,0 +1,12 @@
[gd_scene load_steps=4 format=3 uid="uid://dut1lj8ju37sq"]
[ext_resource type="PackedScene" uid="uid://dcah6r3ku60g6" path="res://src/ui/menus/settings_menu/settings/setting/setting.tscn" id="1_t2sut"]
[ext_resource type="Script" uid="uid://dpxpgs6iw7b7x" path="res://src/ui/menus/settings_menu/settings/numeric_setting/numeric_setting.gd" id="2_3nkup"]
[ext_resource type="PackedScene" uid="uid://dqqcyi26d3bpg" path="res://src/ui/elements/numeric_slider/numeric_slider.tscn" id="2_piwkl"]
[node name="NumericSetting" instance=ExtResource("1_t2sut")]
script = ExtResource("2_3nkup")
[node name="NumericSlider" parent="PanelContainer/MarginContainer" index="0" instance=ExtResource("2_piwkl")]
unique_name_in_owner = true
layout_mode = 2

View File

@ -0,0 +1,25 @@
class_name Setting extends HBoxContainer
## Base class for settings menu inputs
@export var key: StringName
func _ready() -> void:
if not Engine.is_editor_hint():
initialize_value(ProjectSettings.get_setting(key))
## Initialize the value of this setting from project settings.
func initialize_value(_value: Variant) -> void:
pass # Implemented in derived type
## Apply the set value of this setting to project settings.
func apply() -> void:
ProjectSettings.set_setting(key, get_value())
## Get the raw value of this setting.
func get_value() -> Variant:
# Implemented in derived type
return null

View File

@ -0,0 +1 @@
uid://d2ajb1x2wpm05

View File

@ -0,0 +1,26 @@
[gd_scene load_steps=2 format=3 uid="uid://dcah6r3ku60g6"]
[ext_resource type="Script" uid="uid://d2ajb1x2wpm05" path="res://src/ui/menus/settings_menu/settings/setting/setting.gd" id="1_rrash"]
[node name="Setting" type="HBoxContainer"]
script = ExtResource("1_rrash")
[node name="Spacer" type="HSeparator" parent="."]
custom_minimum_size = Vector2(32, 0)
layout_mode = 2
[node name="SettingLabel" type="Label" parent="."]
layout_mode = 2
size_flags_horizontal = 3
text = "Label"
[node name="PanelContainer" type="PanelContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer"]
layout_mode = 2
theme_override_constants/margin_left = 4
theme_override_constants/margin_top = 2
theme_override_constants/margin_right = 4
theme_override_constants/margin_bottom = 2

View File

@ -0,0 +1,77 @@
extends MarginContainer
## Menu allowing the user to adjust game configuration.
const SETTINGS_GROUP := "Settings"
const VOLUME_GROUP := "VolumeSliders"
const BINDINGS_GROUP := "ControlBindings"
@export var volume_slider_scene: PackedScene
@export var control_binding_scene: PackedScene
@onready var bus_mixer_list: HBoxContainer = %BusMixerList
@onready var control_binding_list: VBoxContainer = %ControlBindingList
func _ready() -> void:
populate_volume_mixers()
populate_control_bindings()
func _get_settings_elements() -> Array[Setting]:
var elements: Array[Setting] = []
elements.assign(get_tree().get_nodes_in_group(SETTINGS_GROUP))
return elements
func _get_volume_sliders() -> Array[VolumeSlider]:
var elements: Array[VolumeSlider] = []
elements.assign(get_tree().get_nodes_in_group(VOLUME_GROUP))
return elements
func _get_control_bindings() -> Array[ControlBinding]:
var elements: Array[ControlBinding] = []
elements.assign(get_tree().get_nodes_in_group(BINDINGS_GROUP))
return elements
func populate_control_bindings() -> void:
for action: StringName in InputMap.get_actions():
if not action.begins_with("ui_"):
var binding: ControlBinding = control_binding_scene.instantiate()
binding.key = action
control_binding_list.add_child(binding)
func populate_volume_mixers() -> void:
for idx: int in range(AudioServer.bus_count):
var slider: VolumeSlider = volume_slider_scene.instantiate()
slider.bus_idx = idx
bus_mixer_list.add_child(slider)
## Close menu without applying settings.
func cancel() -> void:
queue_free()
## Apply all settings.
func apply() -> void:
for setting: Setting in _get_settings_elements():
setting.apply()
for mixer: VolumeSlider in _get_volume_sliders():
mixer.apply()
for binding: ControlBinding in _get_control_bindings():
binding.apply()
## Write all applied settings to disk.
func save_settings() -> void:
Game.settings.write()
## Apply settings and close menu.
func accept() -> void:
apply()
save_settings()
queue_free()

View File

@ -0,0 +1 @@
uid://dyb8cwai3cyw2

View File

@ -0,0 +1,14 @@
extends MarginContainer
## Menu allowing the user to adjust game configuration.
## Close menu without applying settings.
func cancel() -> void:
queue_free()
## Close menu and apply settings.
func accept() -> void:
# TODO apply settings
# TODO write to disk
queue_free()

View File

@ -0,0 +1,336 @@
[gd_scene load_steps=8 format=3 uid="uid://d3eaqw2rdurct"]
[ext_resource type="Script" uid="uid://dyb8cwai3cyw2" path="res://src/ui/menus/settings_menu/settings_menu.gd" id="1_lbcn7"]
[ext_resource type="Theme" uid="uid://doq7ay6f7dgfo" path="res://src/ui/menus/menu.theme" id="1_vuur6"]
[ext_resource type="PackedScene" uid="uid://bpmpj4n6xp17l" path="res://src/ui/menus/settings_menu/settings/checkbox_setting/checkbox_setting.tscn" id="2_f274v"]
[ext_resource type="PackedScene" uid="uid://dut1lj8ju37sq" path="res://src/ui/menus/settings_menu/settings/numeric_setting/numeric_setting.tscn" id="3_jox8e"]
[ext_resource type="PackedScene" uid="uid://d2ya6ly01724l" path="res://src/ui/menus/settings_menu/volume_slider/volume_slider.tscn" id="3_vuur6"]
[ext_resource type="PackedScene" uid="uid://dwvpddd7id1h" path="res://src/ui/menus/settings_menu/control_binding/control_binding.tscn" id="4_6bmd3"]
[ext_resource type="Script" uid="uid://wlo8hxahp1x7" path="res://src/ui/menus/settings_menu/settings/checkbox_setting/fullscreen_setting.gd" id="4_ug1id"]
[node name="SettingsMenu" type="MarginContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("1_vuur6")
script = ExtResource("1_lbcn7")
volume_slider_scene = ExtResource("3_vuur6")
control_binding_scene = ExtResource("4_6bmd3")
[node name="TabContainer" type="TabContainer" parent="."]
layout_mode = 2
current_tab = 0
[node name="SETTINGS_GAME" type="MarginContainer" parent="TabContainer"]
layout_mode = 2
theme_type_variation = &"SettingsPageContainer"
metadata/_tab_index = 0
[node name="VBoxContainer" type="VBoxContainer" parent="TabContainer/SETTINGS_GAME"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/SETTINGS_GAME/VBoxContainer"]
layout_mode = 2
[node name="Icon" type="Label" parent="TabContainer/SETTINGS_GAME/VBoxContainer/HBoxContainer"]
layout_mode = 2
theme_type_variation = &"InputPrompt"
theme_override_font_sizes/font_size = 48
text = "⚙"
[node name="Label" type="Label" parent="TabContainer/SETTINGS_GAME/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_vertical = 8
theme_type_variation = &"HeaderXLarge"
text = "SETTINGS_GAME_HEADING"
[node name="ScrollContainer" type="ScrollContainer" parent="TabContainer/SETTINGS_GAME/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="MarginContainer" type="MarginContainer" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_type_variation = &"SettingsListMargin"
[node name="SettingsList" type="VBoxContainer" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer"]
layout_mode = 2
[node name="AccessibilityHeading" type="HBoxContainer" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList"]
layout_mode = 2
[node name="Label" type="Label" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/AccessibilityHeading"]
layout_mode = 2
theme_type_variation = &"HeaderMedium"
text = "SETTINGS_GAME_ACCESSIBILITY_HEADING"
[node name="HSeparator" type="HSeparator" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/AccessibilityHeading"]
layout_mode = 2
size_flags_horizontal = 3
[node name="ScreenShake" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList" groups=["Settings"] instance=ExtResource("2_f274v")]
layout_mode = 2
key = &"game/config/accessibility/enable_screen_shake"
[node name="SettingLabel" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/ScreenShake" index="1"]
text = "SETTINGS_SCREEN_SHAKE"
[node name="TextCheckbox" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/ScreenShake/PanelContainer/MarginContainer" index="0"]
physics_interpolation_mode = 0
[node name="HeadBob" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList" groups=["Settings"] instance=ExtResource("2_f274v")]
layout_mode = 2
key = &"game/config/accessibility/enable_head_bob"
[node name="SettingLabel" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/HeadBob" index="1"]
text = "SETTINGS_HEAD_BOB"
[node name="TextCheckbox" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/HeadBob/PanelContainer/MarginContainer" index="0"]
physics_interpolation_mode = 0
[node name="CameraHeading" type="HBoxContainer" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList"]
layout_mode = 2
[node name="Label" type="Label" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/CameraHeading"]
layout_mode = 2
theme_type_variation = &"HeaderMedium"
text = "SETTINGS_GAME_CAMERA_HEADING"
[node name="HSeparator" type="HSeparator" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/CameraHeading"]
layout_mode = 2
size_flags_horizontal = 3
[node name="SensitivityX" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList" groups=["Settings"] instance=ExtResource("3_jox8e")]
layout_mode = 2
key = &"game/config/input/mouse_sensitivity_x"
[node name="SettingLabel" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/SensitivityX" index="1"]
text = "SETTINGS_SENSITIVITY_X"
[node name="NumericSlider" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/SensitivityX/PanelContainer/MarginContainer" index="0"]
physics_interpolation_mode = 0
max_value = 1.0
step = 0.01
[node name="SensitivityY" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList" groups=["Settings"] instance=ExtResource("3_jox8e")]
layout_mode = 2
key = &"game/config/input/mouse_sensitivity_y"
[node name="SettingLabel" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/SensitivityY" index="1"]
text = "SETTINGS_SENSITIVITY_Y"
[node name="NumericSlider" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/SensitivityY/PanelContainer/MarginContainer" index="0"]
physics_interpolation_mode = 0
max_value = 1.0
step = 0.01
[node name="MouseAcceleration" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList" groups=["Settings"] instance=ExtResource("3_jox8e")]
layout_mode = 2
key = &"game/config/input/mouse_acceleration"
[node name="SettingLabel" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/MouseAcceleration" index="1"]
text = "SETTINGS_MOUSE_ACCELERATION"
[node name="NumericSlider" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/MouseAcceleration/PanelContainer/MarginContainer" index="0"]
physics_interpolation_mode = 0
[node name="InvertPitch" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList" groups=["Settings"] instance=ExtResource("2_f274v")]
layout_mode = 2
key = &"game/config/input/invert_pitch"
[node name="SettingLabel" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/InvertPitch" index="1"]
text = "SETTINGS_INVERT_PITCH"
[node name="TextCheckbox" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/InvertPitch/PanelContainer/MarginContainer" index="0"]
physics_interpolation_mode = 0
[node name="SETTINGS_GRAPHICS" type="MarginContainer" parent="TabContainer"]
visible = false
layout_mode = 2
theme_type_variation = &"SettingsPageContainer"
metadata/_tab_index = 1
[node name="VBoxContainer" type="VBoxContainer" parent="TabContainer/SETTINGS_GRAPHICS"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer"]
layout_mode = 2
[node name="Icon" type="Label" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
theme_type_variation = &"InputPrompt"
theme_override_font_sizes/font_size = 48
text = "🖵"
[node name="Label" type="Label" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 0
theme_type_variation = &"HeaderXLarge"
text = "SETTINGS_GRAPHICS_HEADING"
[node name="ScrollContainer" type="ScrollContainer" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="MarginContainer" type="MarginContainer" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_type_variation = &"SettingsListMargin"
[node name="SettingsList" type="VBoxContainer" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer"]
layout_mode = 2
[node name="DisplayHeading" type="HBoxContainer" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList"]
layout_mode = 2
[node name="Label" type="Label" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/DisplayHeading"]
layout_mode = 2
theme_type_variation = &"HeaderMedium"
text = "SETTINGS_GRAPHICS_DISPLAY_HEADING"
[node name="HSeparator" type="HSeparator" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/DisplayHeading"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Fullscreen" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList" groups=["Settings"] instance=ExtResource("2_f274v")]
layout_mode = 2
script = ExtResource("4_ug1id")
key = &"display/window/size/mode"
[node name="SettingLabel" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/Fullscreen" index="1"]
text = "SETTINGS_GRAPHICS_FULLSCREEN"
[node name="TextCheckbox" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/Fullscreen/PanelContainer/MarginContainer" index="0"]
physics_interpolation_mode = 0
[node name="VSync" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList" groups=["Settings"] instance=ExtResource("2_f274v")]
layout_mode = 2
key = &"display/window/vsync/vsync_mode"
[node name="SettingLabel" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/VSync" index="1"]
text = "SETTINGS_GRAPHICS_VSYNC"
[node name="TextCheckbox" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/VSync/PanelContainer/MarginContainer" index="0"]
physics_interpolation_mode = 0
[node name="SETTINGS_AUDIO" type="MarginContainer" parent="TabContainer"]
visible = false
layout_mode = 2
theme_type_variation = &"SettingsPageContainer"
metadata/_tab_index = 2
[node name="VBoxContainer" type="VBoxContainer" parent="TabContainer/SETTINGS_AUDIO"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/SETTINGS_AUDIO/VBoxContainer"]
layout_mode = 2
[node name="Icon" type="Label" parent="TabContainer/SETTINGS_AUDIO/VBoxContainer/HBoxContainer"]
layout_mode = 2
theme_type_variation = &"InputPrompt"
theme_override_font_sizes/font_size = 48
text = "🕬"
[node name="Label" type="Label" parent="TabContainer/SETTINGS_AUDIO/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_vertical = 8
theme_type_variation = &"HeaderXLarge"
text = "SETTINGS_AUDIO_HEADING"
[node name="ScrollContainer" type="ScrollContainer" parent="TabContainer/SETTINGS_AUDIO/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="MarginContainer" type="MarginContainer" parent="TabContainer/SETTINGS_AUDIO/VBoxContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_type_variation = &"SettingsListMargin"
[node name="BusMixerList" type="HBoxContainer" parent="TabContainer/SETTINGS_AUDIO/VBoxContainer/ScrollContainer/MarginContainer"]
unique_name_in_owner = true
layout_mode = 2
theme_override_constants/separation = 16
[node name="SETTINGS_CONTROLS" type="MarginContainer" parent="TabContainer"]
visible = false
layout_mode = 2
theme_type_variation = &"SettingsPageContainer"
metadata/_tab_index = 3
[node name="VBoxContainer" type="VBoxContainer" parent="TabContainer/SETTINGS_CONTROLS"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/SETTINGS_CONTROLS/VBoxContainer"]
layout_mode = 2
[node name="Icon" type="Label" parent="TabContainer/SETTINGS_CONTROLS/VBoxContainer/HBoxContainer"]
layout_mode = 2
theme_type_variation = &"InputPrompt"
theme_override_font_sizes/font_size = 48
text = "␼"
[node name="Label" type="Label" parent="TabContainer/SETTINGS_CONTROLS/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_vertical = 8
theme_type_variation = &"HeaderXLarge"
text = "SETTINGS_CONTROLS_HEADING"
[node name="ScrollContainer" type="ScrollContainer" parent="TabContainer/SETTINGS_CONTROLS/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="MarginContainer" type="MarginContainer" parent="TabContainer/SETTINGS_CONTROLS/VBoxContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_type_variation = &"SettingsListMargin"
[node name="ControlBindingList" type="VBoxContainer" parent="TabContainer/SETTINGS_CONTROLS/VBoxContainer/ScrollContainer/MarginContainer"]
unique_name_in_owner = true
layout_mode = 2
[node name="SouthEast" type="MarginContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 8
size_flags_vertical = 8
theme_override_constants/margin_left = 16
theme_override_constants/margin_top = 16
theme_override_constants/margin_right = 16
theme_override_constants/margin_bottom = 16
[node name="HBoxContainer" type="HBoxContainer" parent="SouthEast"]
layout_mode = 2
theme_override_constants/separation = 16
[node name="CancelButton" type="Button" parent="SouthEast/HBoxContainer"]
layout_mode = 2
theme_type_variation = &"CancelButton"
text = "UI_CANCEL"
[node name="AcceptButton" type="Button" parent="SouthEast/HBoxContainer"]
layout_mode = 2
theme_type_variation = &"AlertButton"
text = "UI_ACCEPT"
[connection signal="pressed" from="SouthEast/HBoxContainer/CancelButton" to="." method="cancel"]
[connection signal="pressed" from="SouthEast/HBoxContainer/AcceptButton" to="." method="accept"]
[editable path="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/ScreenShake"]
[editable path="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/HeadBob"]
[editable path="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/SensitivityX"]
[editable path="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/SensitivityY"]
[editable path="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/MouseAcceleration"]
[editable path="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/InvertPitch"]
[editable path="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/Fullscreen"]
[editable path="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/VSync"]

View File

@ -0,0 +1,63 @@
class_name VolumeSlider extends VBoxContainer
## Input element for adjusting the volume of a single bus.
const VOLUME_FACTOR := 50.0
@export var bus_idx: int
var value: float:
get:
return slider.value
set(v):
slider.set_value_no_signal(v)
spin_box.set_value_no_signal(v)
_set_bus_volume(v)
var _applied_value: float
@onready var bus_name: Label = %BusName
@onready var slider: VSlider = %Slider
@onready var spin_box: SpinBox = %SpinBox
@onready var audio_demo: AudioStreamPlayer = %AudioDemo
func _ready() -> void:
bus_name.text = AudioServer.get_bus_name(bus_idx)
audio_demo.bus = bus_name.text
_applied_value = _db_to_volume(AudioServer.get_bus_volume_db(bus_idx))
value = _applied_value
func _exit_tree() -> void:
# Revert volume to the value from the last time `apply` was called
_set_bus_volume(_applied_value)
func _db_to_volume(db: float) -> float:
return db_to_linear(db) * VOLUME_FACTOR
func _volume_to_db(volume: float) -> float:
return linear_to_db(volume / VOLUME_FACTOR)
func _on_component_value_changed(new_value: float) -> void:
value = new_value
func _set_bus_volume(new_value: float) -> void:
AudioServer.set_bus_volume_db(bus_idx, _volume_to_db(new_value))
func apply() -> void:
# Prevent value from being reverted during destructor.
_applied_value = value
func _on_slider_drag_ended(value_changed: bool) -> void:
if value_changed:
audio_demo.play()
func _on_spin_box_value_changed(_new_value: float) -> void:
audio_demo.play()

View File

@ -0,0 +1 @@
uid://bt3oajqvevj4k

View File

@ -0,0 +1,53 @@
[gd_scene load_steps=3 format=3 uid="uid://d2ya6ly01724l"]
[ext_resource type="Theme" uid="uid://doq7ay6f7dgfo" path="res://src/ui/menus/menu.theme" id="1_k7hk0"]
[ext_resource type="Script" uid="uid://bt3oajqvevj4k" path="res://src/ui/menus/settings_menu/volume_slider/volume_slider.gd" id="1_rfe0k"]
[node name="VolumeSlider" type="VBoxContainer" groups=["VolumeSliders"]]
custom_minimum_size = Vector2(300, 0)
theme = ExtResource("1_k7hk0")
script = ExtResource("1_rfe0k")
[node name="NameContainer" type="HBoxContainer" parent="."]
layout_mode = 2
alignment = 1
[node name="Icon" type="Label" parent="NameContainer"]
layout_mode = 2
theme_type_variation = &"InputPrompt"
theme_override_constants/outline_size = 5
theme_override_font_sizes/font_size = 24
text = "🎧"
[node name="BusName" type="Label" parent="NameContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 8
theme_type_variation = &"HeaderMedium"
text = "Channel"
[node name="Slider" type="VSlider" parent="."]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 1
size_flags_vertical = 3
step = 0.1
[node name="DigitalDisplay" type="HBoxContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 4
[node name="SpinBox" type="SpinBox" parent="DigitalDisplay"]
unique_name_in_owner = true
layout_mode = 2
step = 0.1
alignment = 2
suffix = "%"
[node name="AudioDemo" type="AudioStreamPlayer" parent="."]
unique_name_in_owner = true
[connection signal="drag_ended" from="Slider" to="." method="_on_slider_drag_ended"]
[connection signal="value_changed" from="Slider" to="." method="_on_component_value_changed"]
[connection signal="value_changed" from="DigitalDisplay/SpinBox" to="." method="_on_component_value_changed"]
[connection signal="value_changed" from="DigitalDisplay/SpinBox" to="." method="_on_spin_box_value_changed"]

View File

@ -0,0 +1 @@
uid://bjcah3n6wug68

View File

@ -25,7 +25,7 @@ func pause() -> void:
get_tree().paused = true get_tree().paused = true
var pause_menu: Control = pause_scene.instantiate() var pause_menu: Control = pause_scene.instantiate()
ui_root.add_child(pause_menu) ui_root.add_child(pause_menu)
pause_menu.tree_exited.connect(unpause) pause_menu.tree_exiting.connect(unpause)
func unpause() -> void: func unpause() -> void: