gd: added menu template

This commit is contained in:
2025-06-10 18:46:20 +02:00
parent f9a6c42b14
commit c554e24b01
421 changed files with 12371 additions and 2 deletions

View File

@ -0,0 +1,18 @@
@tool
extends Label
class_name ConfigNameLabel
## Displays the value of `application/config/name`, set in project settings.
const NO_NAME_STRING : String = "Title"
@export var lock : bool = false
func update_name_label():
if lock: return
var config_name : String = ProjectSettings.get_setting("application/config/name", NO_NAME_STRING)
if config_name.is_empty():
config_name = NO_NAME_STRING
text = config_name
func _ready():
update_name_label()

View File

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

View File

@ -0,0 +1,18 @@
@tool
extends Label
class_name ConfigVersionLabel
## Displays the value of `application/config/version`, set in project settings.
const NO_VERSION_STRING : String = "0.0.0"
## Prefixes the value of `application/config/version` when displaying to the user.
@export var version_prefix : String = "v"
func update_version_label() -> void:
var config_version : String = ProjectSettings.get_setting("application/config/version", NO_VERSION_STRING)
if config_version.is_empty():
config_version = NO_VERSION_STRING
text = version_prefix + config_version
func _ready() -> void:
update_version_label()

View File

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

View File

@ -0,0 +1,94 @@
class_name MainMenu
extends Control
## Defines the path to the game scene. Hides the play button if empty.
@export_file("*.tscn") var game_scene_path : String
@export var options_packed_scene : PackedScene
@export var credits_packed_scene : PackedScene
var options_scene
var credits_scene
var sub_menu
func load_game_scene() -> void:
SceneLoader.load_scene(game_scene_path)
func new_game() -> void:
load_game_scene()
func _open_sub_menu(menu : Control) -> void:
sub_menu = menu
sub_menu.show()
%BackButton.show()
%MenuContainer.hide()
func _close_sub_menu() -> void:
if sub_menu == null:
return
sub_menu.hide()
sub_menu = null
%BackButton.hide()
%MenuContainer.show()
func _event_is_mouse_button_released(event : InputEvent) -> bool:
return event is InputEventMouseButton and not event.is_pressed()
func _input(event : InputEvent) -> void:
if event.is_action_released("ui_cancel"):
if sub_menu:
_close_sub_menu()
else:
get_tree().quit()
if event.is_action_released("ui_accept") and get_viewport().gui_get_focus_owner() == null:
%MenuButtonsBoxContainer.focus_first()
func _hide_exit_for_web() -> void:
if OS.has_feature("web"):
%ExitButton.hide()
func _hide_new_game_if_unset() -> void:
if game_scene_path.is_empty():
%NewGameButton.hide()
func _add_or_hide_options() -> void:
if options_packed_scene == null:
%OptionsButton.hide()
else:
options_scene = options_packed_scene.instantiate()
options_scene.hide()
%OptionsContainer.call_deferred("add_child", options_scene)
func _add_or_hide_credits() -> void:
if credits_packed_scene == null:
%CreditsButton.hide()
else:
credits_scene = credits_packed_scene.instantiate()
credits_scene.hide()
if credits_scene.has_signal("end_reached"):
credits_scene.connect("end_reached", _on_credits_end_reached)
%CreditsContainer.call_deferred("add_child", credits_scene)
func _ready() -> void:
_hide_exit_for_web()
_add_or_hide_options()
_add_or_hide_credits()
_hide_new_game_if_unset()
func _on_new_game_button_pressed() -> void:
new_game()
func _on_options_button_pressed() -> void:
_open_sub_menu(options_scene)
func _on_credits_button_pressed() -> void:
_open_sub_menu(credits_scene)
func _on_exit_button_pressed() -> void:
get_tree().quit()
func _on_credits_end_reached() -> void:
if sub_menu == credits_scene:
_close_sub_menu()
func _on_back_button_pressed() -> void:
_close_sub_menu()

View File

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

View File

@ -0,0 +1,220 @@
[gd_scene load_steps=9 format=3 uid="uid://c6k5nnpbypshi"]
[ext_resource type="Script" uid="uid://bhgs1upaahk3y" path="res://addons/maaacks_game_template/base/scenes/menus/main_menu/main_menu.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://bq2ti3hrjlgdl" path="res://menus/scenes/menus/options_menu/master_options_menu_with_tabs.tscn" id="2_73am8"]
[ext_resource type="PackedScene" uid="uid://ct0yseu6qy88d" path="res://menus/scenes/credits/scrollable_credits.tscn" id="3_g46cd"]
[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_game_template/base/scripts/capture_focus.gd" id="4_l1ebe"]
[ext_resource type="PackedScene" uid="uid://bkcsjsk2ciff" path="res://addons/maaacks_game_template/base/scenes/music_players/background_music_player.tscn" id="4_w8sbm"]
[ext_resource type="Script" uid="uid://b5oej1q4h7jvh" path="res://addons/maaacks_game_template/base/scripts/ui_sound_controller.gd" id="6_bs342"]
[ext_resource type="Script" uid="uid://dmkubt2nsnsbn" path="res://addons/maaacks_game_template/base/scenes/menus/main_menu/config_version_label.gd" id="6_pdiij"]
[ext_resource type="Script" uid="uid://bkwlopi4qn32o" path="res://addons/maaacks_game_template/base/scenes/menus/main_menu/config_name_label.gd" id="7_j7612"]
[node name="MainMenu" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
game_scene_path = "uid://cxbskue0lj2gv"
options_packed_scene = ExtResource("2_73am8")
credits_packed_scene = ExtResource("3_g46cd")
[node name="UISoundController" type="Node" parent="."]
script = ExtResource("6_bs342")
[node name="BackgroundMusicPlayer" parent="." instance=ExtResource("4_w8sbm")]
bus = &"Master"
[node name="BackgroundTextureRect" type="TextureRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
expand_mode = 1
stretch_mode = 5
[node name="VersionMargin" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 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="VersionContainer" type="Control" parent="VersionMargin"]
layout_mode = 2
mouse_filter = 2
[node name="VersionLabel" type="Label" parent="VersionMargin/VersionContainer"]
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -88.0
offset_top = -26.0
grow_horizontal = 0
grow_vertical = 0
text = "v0.0.0"
horizontal_alignment = 2
script = ExtResource("6_pdiij")
[node name="MenuContainer" type="MarginContainer" parent="."]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="TitleMargin" type="MarginContainer" parent="MenuContainer"]
layout_mode = 2
theme_override_constants/margin_top = 24
[node name="TitleContainer" type="Control" parent="MenuContainer/TitleMargin"]
layout_mode = 2
mouse_filter = 2
[node name="TitleLabel" type="Label" parent="MenuContainer/TitleMargin/TitleContainer"]
layout_mode = 1
anchors_preset = 10
anchor_right = 1.0
offset_bottom = 67.0
grow_horizontal = 2
theme_override_font_sizes/font_size = 48
text = "Movement tests"
horizontal_alignment = 1
vertical_alignment = 1
script = ExtResource("7_j7612")
[node name="SubTitleMargin" type="MarginContainer" parent="MenuContainer"]
layout_mode = 2
theme_override_constants/margin_top = 92
[node name="SubTitleContainer" type="Control" parent="MenuContainer/SubTitleMargin"]
layout_mode = 2
mouse_filter = 2
[node name="SubTitleLabel" type="Label" parent="MenuContainer/SubTitleMargin/SubTitleContainer"]
layout_mode = 1
anchors_preset = 10
anchor_right = 1.0
offset_bottom = 34.0
grow_horizontal = 2
theme_override_font_sizes/font_size = 24
text = "Subtitle"
horizontal_alignment = 1
vertical_alignment = 1
[node name="MenuButtonsMargin" type="MarginContainer" parent="MenuContainer"]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/margin_top = 136
theme_override_constants/margin_bottom = 8
[node name="MenuButtonsContainer" type="Control" parent="MenuContainer/MenuButtonsMargin"]
layout_mode = 2
mouse_filter = 2
[node name="MenuButtonsBoxContainer" type="BoxContainer" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -64.0
offset_top = -104.0
offset_right = 64.0
offset_bottom = 104.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 4
theme_override_constants/separation = 16
alignment = 1
vertical = true
script = ExtResource("4_l1ebe")
[node name="NewGameButton" type="Button" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "New Game"
[node name="OptionsButton" type="Button" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Options"
[node name="CreditsButton" type="Button" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Credits"
[node name="ExitButton" type="Button" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Exit"
[node name="OptionsContainer" type="MarginContainer" parent="."]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="CreditsContainer" type="MarginContainer" parent="."]
unique_name_in_owner = true
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
mouse_filter = 2
theme_override_constants/margin_left = 16
theme_override_constants/margin_top = 32
theme_override_constants/margin_right = 16
theme_override_constants/margin_bottom = 32
[node name="FlowControlContainer" type="MarginContainer" parent="."]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
mouse_filter = 2
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="FlowControl" type="Control" parent="FlowControlContainer"]
layout_mode = 2
mouse_filter = 2
[node name="BackButton" type="Button" parent="FlowControlContainer/FlowControl"]
unique_name_in_owner = true
visible = false
layout_mode = 1
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_top = -31.0
offset_right = 45.0
grow_vertical = 0
text = "Back"
[connection signal="pressed" from="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer/NewGameButton" to="." method="_on_new_game_button_pressed"]
[connection signal="pressed" from="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer/OptionsButton" to="." method="_on_options_button_pressed"]
[connection signal="pressed" from="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer/CreditsButton" to="." method="_on_credits_button_pressed"]
[connection signal="pressed" from="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer/ExitButton" to="." method="_on_exit_button_pressed"]
[connection signal="pressed" from="FlowControlContainer/FlowControl/BackButton" to="." method="_on_back_button_pressed"]

View File

@ -0,0 +1,37 @@
class_name AudioOptionsMenu
extends Control
@export var audio_control_scene : PackedScene
@export var hide_busses : Array[String]
@onready var mute_control = %MuteControl
func _on_bus_changed(bus_value : float, bus_iter : int) -> void:
AppSettings.set_bus_volume(bus_iter, bus_value)
func _add_audio_control(bus_name : String, bus_value : float, bus_iter : int) -> void:
if audio_control_scene == null or bus_name in hide_busses or bus_name.begins_with(AppSettings.SYSTEM_BUS_NAME_PREFIX):
return
var audio_control = audio_control_scene.instantiate()
%AudioControlContainer.call_deferred("add_child", audio_control)
if audio_control is OptionControl:
audio_control.option_section = OptionControl.OptionSections.AUDIO
audio_control.option_name = bus_name
audio_control.value = bus_value
audio_control.connect("setting_changed", _on_bus_changed.bind(bus_iter))
func _add_audio_bus_controls() -> void:
for bus_iter in AudioServer.bus_count:
var bus_name : String = AppSettings.get_audio_bus_name(bus_iter)
var linear : float = AppSettings.get_bus_volume(bus_iter)
_add_audio_control(bus_name, linear, bus_iter)
func _update_ui() -> void:
_add_audio_bus_controls()
mute_control.value = AppSettings.is_muted()
func _ready() -> void:
_update_ui()
func _on_mute_control_setting_changed(value : bool) -> void:
AppSettings.set_mute(value)

View File

@ -0,0 +1,42 @@
[gd_scene load_steps=5 format=3 uid="uid://c8vnncjwqcpab"]
[ext_resource type="Script" uid="uid://bwugqn2cjr41e" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/audio/audio_options_menu.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://cl416gdb1fgwr" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/option_control/slider_option_control.tscn" id="2_raehj"]
[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_game_template/base/scripts/capture_focus.gd" id="3_dtraq"]
[ext_resource type="PackedScene" uid="uid://bsxh6v7j0257h" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/option_control/toggle_option_control.tscn" id="4_ojfec"]
[node name="Audio" type="MarginContainer"]
custom_minimum_size = Vector2(305, 0)
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_top = 24
theme_override_constants/margin_bottom = 24
script = ExtResource("1")
audio_control_scene = ExtResource("2_raehj")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
custom_minimum_size = Vector2(400, 0)
layout_mode = 2
size_flags_horizontal = 4
theme_override_constants/separation = 8
alignment = 1
script = ExtResource("3_dtraq")
search_depth = 3
[node name="AudioControlContainer" type="VBoxContainer" parent="VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
theme_override_constants/separation = 8
[node name="MuteControl" parent="VBoxContainer" instance=ExtResource("4_ojfec")]
unique_name_in_owner = true
layout_mode = 2
option_name = "Mute"
option_section = 2
key = "Mute"
section = "AudioSettings"
[connection signal="setting_changed" from="VBoxContainer/MuteControl" to="." method="_on_mute_control_setting_changed"]

View File

@ -0,0 +1,296 @@
@tool
class_name InputActionsList
extends Container
const EMPTY_INPUT_ACTION_STRING = " "
signal already_assigned(action_name : String, input_name : String)
signal minimum_reached(action_name : String)
signal button_clicked(action_name : String, readable_input_name : String)
const BUTTON_NAME_GROUP_STRING : String = "%s:%d"
@export var vertical : bool = true :
set(value):
vertical = value
if is_inside_tree():
%ParentBoxContainer.vertical = vertical
@export_range(1, 5) var action_groups : int = 2
@export var action_group_names : Array[String]
@export var input_action_names : Array[StringName] :
set(value):
var _value_changed = input_action_names != value
input_action_names = value
if _value_changed:
var _new_readable_action_names : Array[String]
for action in input_action_names:
_new_readable_action_names.append(action.capitalize())
readable_action_names = _new_readable_action_names
@export var readable_action_names : Array[String] :
set(value):
var _value_changed = readable_action_names != value
readable_action_names = value
if _value_changed:
var _new_action_name_map : Dictionary
for iter in range(input_action_names.size()):
var _input_name : StringName = input_action_names[iter]
var _readable_name : String = readable_action_names[iter]
_new_action_name_map[_input_name] = _readable_name
action_name_map = _new_action_name_map
## Show action names that are not explicitely listed in an action name map.
@export var show_all_actions : bool = true
@export_group("Icons")
@export var input_icon_mapper : InputIconMapper
@export var expand_icon : bool = false
@export_group("Built-in Actions")
## Shows Godot's built-in actions (action names starting with "ui_") in the tree.
@export var show_built_in_actions : bool = false
## Prevents assigning inputs that are already assigned to Godot's built-in actions (action names starting with "ui_"). Not recommended.
@export var catch_built_in_duplicate_inputs : bool = false
## Maps the names of built-in input actions to readable names for users.
@export var built_in_action_name_map := InputEventHelper.BUILT_IN_ACTION_NAME_MAP
@export_group("Debug")
## Maps the names of input actions to readable names for users.
@export var action_name_map : Dictionary
var action_button_map : Dictionary = {}
var button_readable_input_map : Dictionary = {}
var assigned_input_events : Dictionary = {}
var editing_action_name : String = ""
var editing_action_group : int = 0
var last_input_readable_name
func _clear_list() -> void:
for child in %ParentBoxContainer.get_children():
if child == %ActionBoxContainer:
continue
child.queue_free()
func _replace_action(action_name : String, readable_input_name : String = "") -> void:
var readable_action_name = tr(_get_action_readable_name(action_name))
button_clicked.emit(readable_action_name, readable_input_name)
func _on_button_pressed(action_name : String, action_group : int) -> void:
editing_action_name = action_name
editing_action_group = action_group
_replace_action(action_name)
func _new_action_box() -> Node:
var new_action_box : Node = %ActionBoxContainer.duplicate()
new_action_box.visible = true
new_action_box.vertical = !(vertical)
return new_action_box
func _add_header() -> void:
if action_group_names.is_empty(): return
var new_action_box := _new_action_box()
for group_iter in range(action_groups):
var group_name := ""
if group_iter < action_group_names.size():
group_name = action_group_names[group_iter]
var new_label := Label.new()
new_label.size_flags_horizontal = SIZE_EXPAND_FILL
new_label.size_flags_vertical = SIZE_EXPAND_FILL
new_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
new_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
new_label.text = group_name
new_action_box.add_child(new_label)
%ParentBoxContainer.add_child(new_action_box)
func _add_to_action_button_map(action_name : String, action_group : int, button_node : BaseButton) -> void:
var key_string : String = BUTTON_NAME_GROUP_STRING % [action_name, action_group]
action_button_map[key_string] = button_node
func _get_button_by_action(action_name : String, action_group : int) -> Button:
var key_string : String = BUTTON_NAME_GROUP_STRING % [action_name, action_group]
if key_string in action_button_map:
return action_button_map[key_string]
return null
func _update_next_button_disabled_state(action_name : String, action_group : int) -> void:
var button = _get_button_by_action(action_name, action_group)
if button:
button.disabled = false
func _update_assigned_inputs_and_button(action_name : String, action_group : int, input_event : InputEvent) -> void:
var new_readable_input_name = InputEventHelper.get_text(input_event)
var button = _get_button_by_action(action_name, action_group)
if not button: return
var icon : Texture
if input_icon_mapper:
icon = input_icon_mapper.get_icon(input_event)
if icon:
button.icon = icon
else:
button.icon = null
if button.icon == null:
button.text = new_readable_input_name
else:
button.text = ""
var old_readable_input_name : String
if button in button_readable_input_map:
old_readable_input_name = button_readable_input_map[button]
assigned_input_events.erase(old_readable_input_name)
button_readable_input_map[button] = new_readable_input_name
assigned_input_events[new_readable_input_name] = action_name
func _clear_button(action_name : String, action_group : int) -> void:
var button = _get_button_by_action(action_name, action_group)
if not button: return
button.icon = null
button.text = EMPTY_INPUT_ACTION_STRING
var old_readable_input_name : String
if button in button_readable_input_map:
old_readable_input_name = button_readable_input_map[button]
assigned_input_events.erase(old_readable_input_name)
button_readable_input_map[button] = EMPTY_INPUT_ACTION_STRING
func _add_new_button(content : Variant, container: Control, disabled : bool = false) -> Button:
var new_button := Button.new()
new_button.size_flags_horizontal = SIZE_EXPAND_FILL
new_button.size_flags_vertical = SIZE_EXPAND_FILL
new_button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
new_button.expand_icon = expand_icon
if content is Texture:
new_button.icon = content
elif content is String:
new_button.text = content
new_button.disabled = disabled
container.add_child(new_button)
return new_button
func _connect_button_and_add_to_maps(button : Button, input_name : String, action_name : String, group_iter : int) -> void:
button.pressed.connect(_on_button_pressed.bind(action_name, group_iter))
button_readable_input_map[button] = input_name
_add_to_action_button_map(action_name, group_iter, button)
func _add_action_options(action_name : String, readable_action_name : String, input_events : Array[InputEvent]) -> void:
var new_action_box = %ActionBoxContainer.duplicate()
new_action_box.visible = true
new_action_box.vertical = !(vertical)
new_action_box.get_child(0).text = readable_action_name
for group_iter in range(action_groups):
var input_event : InputEvent
if group_iter < input_events.size():
input_event = input_events[group_iter]
var text = InputEventHelper.get_text(input_event)
var is_disabled = group_iter > input_events.size()
if text.is_empty(): text = EMPTY_INPUT_ACTION_STRING
var icon : Texture
if input_icon_mapper:
icon = input_icon_mapper.get_icon(input_event)
var content = icon if icon else text
var button : Button = _add_new_button(content, new_action_box, is_disabled)
_connect_button_and_add_to_maps(button, text, action_name, group_iter)
%ParentBoxContainer.add_child(new_action_box)
func _get_all_action_names(include_built_in : bool = false) -> Array[StringName]:
var action_names : Array[StringName] = input_action_names.duplicate()
var full_action_name_map = action_name_map.duplicate()
if include_built_in:
for action_name in built_in_action_name_map:
if action_name is String:
action_name = StringName(action_name)
if action_name is StringName:
action_names.append(action_name)
if show_all_actions:
var all_actions := AppSettings.get_action_names(include_built_in)
for action_name in all_actions:
if not action_name in action_names:
action_names.append(action_name)
return action_names
func _get_action_readable_name(input_name : StringName) -> String:
var readable_name : String
if input_name in action_name_map:
readable_name = action_name_map[input_name]
elif input_name in built_in_action_name_map:
readable_name = built_in_action_name_map[input_name]
else:
readable_name = input_name.capitalize()
action_name_map[input_name] = readable_name
return readable_name
func _build_ui_list() -> void:
_clear_list()
_add_header()
var action_names : Array[StringName] = _get_all_action_names(show_built_in_actions)
for action_name in action_names:
var input_events = InputMap.action_get_events(action_name)
if input_events.size() < 1:
continue
var readable_name : String = _get_action_readable_name(action_name)
_add_action_options(action_name, readable_name, input_events)
func _assign_input_event(input_event : InputEvent, action_name : String) -> void:
assigned_input_events[InputEventHelper.get_text(input_event)] = action_name
func _assign_input_event_to_action_group(input_event : InputEvent, action_name : String, action_group : int) -> void:
_assign_input_event(input_event, action_name)
var action_events := InputMap.action_get_events(action_name)
action_events.resize(action_events.size() + 1)
action_events[action_group] = input_event
InputMap.action_erase_events(action_name)
var final_action_events : Array[InputEvent]
for input_action_event in action_events:
if input_action_event == null: continue
final_action_events.append(input_action_event)
InputMap.action_add_event(action_name, input_action_event)
AppSettings.set_config_input_events(action_name, final_action_events)
action_group = min(action_group, final_action_events.size() - 1)
_update_assigned_inputs_and_button(action_name, action_group, input_event)
_update_next_button_disabled_state(action_name, action_group)
func _build_assigned_input_events() -> void:
assigned_input_events.clear()
var action_names := _get_all_action_names(show_built_in_actions and catch_built_in_duplicate_inputs)
for action_name in action_names:
var input_events = InputMap.action_get_events(action_name)
for input_event in input_events:
_assign_input_event(input_event, action_name)
func _get_action_for_input_event(input_event : InputEvent) -> String:
if InputEventHelper.get_text(input_event) in assigned_input_events:
return assigned_input_events[InputEventHelper.get_text(input_event)]
return ""
func add_action_event(last_input_text : String, last_input_event : InputEvent) -> void:
last_input_readable_name = last_input_text
if last_input_event != null:
var assigned_action := _get_action_for_input_event(last_input_event)
if not assigned_action.is_empty():
var readable_action_name = tr(_get_action_readable_name(assigned_action))
already_assigned.emit(readable_action_name, last_input_readable_name)
else:
_assign_input_event_to_action_group(last_input_event, editing_action_name, editing_action_group)
editing_action_name = ""
func _refresh_ui_list_button_content() -> void:
var action_names : Array[StringName] = _get_all_action_names(show_built_in_actions)
for action_name in action_names:
var input_events := InputMap.action_get_events(action_name)
if input_events.size() < 1:
continue
var group_iter : int = 0
for input_event in input_events:
_update_assigned_inputs_and_button(action_name, group_iter, input_event)
group_iter += 1
while group_iter < action_groups:
_clear_button(action_name, group_iter)
group_iter += 1
func reset() -> void:
AppSettings.reset_to_default_inputs()
_build_assigned_input_events()
_refresh_ui_list_button_content()
func _ready() -> void:
if Engine.is_editor_hint(): return
vertical = vertical
_build_assigned_input_events()
_build_ui_list()
if input_icon_mapper:
input_icon_mapper.joypad_device_changed.connect(_refresh_ui_list_button_content)

View File

@ -0,0 +1,45 @@
[gd_scene load_steps=2 format=3 uid="uid://bxp45814v6ydv"]
[ext_resource type="Script" uid="uid://b3q5fgjev8gyo" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/input/input_actions_list.gd" id="1_cxorh"]
[node name="InputActionsList" type="ScrollContainer"]
custom_minimum_size = Vector2(560, 240)
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
follow_focus = true
script = ExtResource("1_cxorh")
action_groups = 3
action_group_names = Array[String](["Primary", "Secondary", "Tertiary", "Quaternary", "Quinary"])
input_action_names = Array[StringName]([&"move_forward", &"move_backward", &"move_up", &"move_down", &"move_left", &"move_right", &"interact"])
readable_action_names = Array[String](["Move Forward", "Move Backward", "Move Up", "Move Down", "Move Left", "Move Right", "Interact"])
action_name_map = {
"interact": "Interact",
"move_backward": "Move Backward",
"move_down": "Move Down",
"move_forward": "Move Forward",
"move_left": "Move Left",
"move_right": "Move Right",
"move_up": "Move Up"
}
[node name="ParentBoxContainer" type="BoxContainer" parent="."]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
vertical = true
[node name="ActionBoxContainer" type="BoxContainer" parent="ParentBoxContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 2
size_flags_vertical = 3
[node name="ActionNameLabel" type="Label" parent="ParentBoxContainer/ActionBoxContainer"]
custom_minimum_size = Vector2(150, 0)
layout_mode = 2

View File

@ -0,0 +1,215 @@
class_name InputActionsTree
extends Tree
signal already_assigned(action_name : String, input_name : String)
signal minimum_reached(action_name : String)
signal add_button_clicked(action_name : String)
signal remove_button_clicked(action_name : String, input_name : String)
@export var input_action_names : Array[StringName] :
set(value):
var _value_changed = input_action_names != value
input_action_names = value
if _value_changed:
var _new_readable_action_names : Array[String]
for action in input_action_names:
_new_readable_action_names.append(action.capitalize())
readable_action_names = _new_readable_action_names
@export var readable_action_names : Array[String] :
set(value):
var _value_changed = readable_action_names != value
readable_action_names = value
if _value_changed:
var _new_action_name_map : Dictionary
for iter in range(input_action_names.size()):
var _input_name : StringName = input_action_names[iter]
var _readable_name : String = readable_action_names[iter]
_new_action_name_map[_input_name] = _readable_name
action_name_map = _new_action_name_map
## Show action names that are not explicitely listed in an action name map.
@export var show_all_actions : bool = true
@export_group("Icons")
@export var add_button_texture : Texture2D
@export var remove_button_texture : Texture2D
@export var input_icon_mapper : InputIconMapper
@export_group("Built-in Actions")
## Shows Godot's built-in actions (action names starting with "ui_") in the tree.
@export var show_built_in_actions : bool = false
## Prevents assigning inputs that are already assigned to Godot's built-in actions (action names starting with "ui_"). Not recommended.
@export var catch_built_in_duplicate_inputs : bool = false
## Maps the names of built-in input actions to readable names for users.
@export var built_in_action_name_map := InputEventHelper.BUILT_IN_ACTION_NAME_MAP
@export_group("Debug")
## Maps the names of input actions to readable names for users.
@export var action_name_map : Dictionary
var tree_item_add_map : Dictionary = {}
var tree_item_remove_map : Dictionary = {}
var tree_item_action_map : Dictionary = {}
var assigned_input_events : Dictionary = {}
var editing_action_name : String = ""
var editing_item
var last_input_readable_name
func _start_tree() -> void:
clear()
create_item()
func _add_input_event_as_tree_item(action_name : String, input_event : InputEvent, parent_item : TreeItem) -> void:
var input_tree_item : TreeItem = create_item(parent_item)
var icon : Texture
if input_icon_mapper:
icon = input_icon_mapper.get_icon(input_event)
if icon:
input_tree_item.set_icon(0, icon)
input_tree_item.set_text(0, InputEventHelper.get_text(input_event))
if remove_button_texture != null:
input_tree_item.add_button(0, remove_button_texture, -1, false, "Remove")
tree_item_remove_map[input_tree_item] = input_event
tree_item_action_map[input_tree_item] = action_name
func _add_action_as_tree_item(readable_name : String, action_name : String, input_events : Array[InputEvent]) -> void:
var root_tree_item : TreeItem = get_root()
var action_tree_item : TreeItem = create_item(root_tree_item)
action_tree_item.set_text(0, readable_name)
tree_item_add_map[action_tree_item] = action_name
if add_button_texture != null:
action_tree_item.add_button(0, add_button_texture, -1, false, "Add")
for input_event in input_events:
_add_input_event_as_tree_item(action_name, input_event, action_tree_item)
func _get_all_action_names(include_built_in : bool = false) -> Array[StringName]:
var action_names : Array[StringName] = input_action_names.duplicate()
var full_action_name_map = action_name_map.duplicate()
if include_built_in:
for action_name in built_in_action_name_map:
if action_name is String:
action_name = StringName(action_name)
if action_name is StringName:
action_names.append(action_name)
if show_all_actions:
var all_actions := AppSettings.get_action_names(include_built_in)
for action_name in all_actions:
if not action_name in action_names:
action_names.append(action_name)
return action_names
func _get_action_readable_name(input_name : StringName) -> String:
var readable_name : String
if input_name in action_name_map:
readable_name = action_name_map[input_name]
elif input_name in built_in_action_name_map:
readable_name = built_in_action_name_map[input_name]
else:
readable_name = input_name.capitalize()
action_name_map[input_name] = readable_name
return readable_name
func _build_ui_tree() -> void:
_start_tree()
var action_names : Array[StringName] = _get_all_action_names(show_built_in_actions)
for action_name in action_names:
var input_events = InputMap.action_get_events(action_name)
if input_events.size() < 1:
continue
var readable_name : String = _get_action_readable_name(action_name)
_add_action_as_tree_item(readable_name, action_name, input_events)
func _assign_input_event(input_event : InputEvent, action_name : String) -> void:
assigned_input_events[InputEventHelper.get_text(input_event)] = action_name
func _assign_input_event_to_action(input_event : InputEvent, action_name : String) -> void:
_assign_input_event(input_event, action_name)
InputMap.action_add_event(action_name, input_event)
var action_events = InputMap.action_get_events(action_name)
AppSettings.set_config_input_events(action_name, action_events)
_add_input_event_as_tree_item(action_name, input_event, editing_item)
func _can_remove_input_event(action_name : String) -> bool:
return InputMap.action_get_events(action_name).size() > 1
func _remove_input_event(input_event : InputEvent) -> void:
assigned_input_events.erase(InputEventHelper.get_text(input_event))
func _remove_input_event_from_action(input_event : InputEvent, action_name : String) -> void:
_remove_input_event(input_event)
AppSettings.remove_action_input_event(action_name, input_event)
func _build_assigned_input_events() -> void:
assigned_input_events.clear()
var action_names := _get_all_action_names(show_built_in_actions and catch_built_in_duplicate_inputs)
for action_name in action_names:
var input_events = InputMap.action_get_events(action_name)
for input_event in input_events:
_assign_input_event(input_event, action_name)
func _get_action_for_input_event(input_event : InputEvent) -> String:
if InputEventHelper.get_text(input_event) in assigned_input_events:
return assigned_input_events[InputEventHelper.get_text(input_event)]
return ""
func add_action_event(last_input_text : String, last_input_event : InputEvent):
last_input_readable_name = last_input_text
if last_input_event != null:
var assigned_action := _get_action_for_input_event(last_input_event)
if not assigned_action.is_empty():
var readable_action_name = tr(_get_action_readable_name(assigned_action))
already_assigned.emit(readable_action_name, last_input_readable_name)
else:
_assign_input_event_to_action(last_input_event, editing_action_name)
editing_action_name = ""
func remove_action_event(item : TreeItem) -> void:
if item not in tree_item_remove_map:
return
var action_name = tree_item_action_map[item]
var input_event = tree_item_remove_map[item]
if not _can_remove_input_event(action_name):
var readable_action_name = _get_action_readable_name(action_name)
minimum_reached.emit(readable_action_name)
return
_remove_input_event_from_action(input_event, action_name)
var parent_tree_item = item.get_parent()
parent_tree_item.remove_child(item)
func reset() -> void:
AppSettings.reset_to_default_inputs()
_build_assigned_input_events()
_build_ui_tree()
func _add_item(item : TreeItem) -> void:
editing_item = item
editing_action_name = tree_item_add_map[item]
var readable_action_name = tr(_get_action_readable_name(editing_action_name))
add_button_clicked.emit(readable_action_name)
func _remove_item(item : TreeItem) -> void:
editing_item = item
editing_action_name = tree_item_action_map[item]
var readable_action_name = tr(_get_action_readable_name(editing_action_name))
var item_text = item.get_text(0)
remove_button_clicked.emit(readable_action_name, item_text)
func _check_item_actions(item : TreeItem) -> void:
if item in tree_item_add_map:
_add_item(item)
elif item in tree_item_remove_map:
_remove_item(item)
func _on_button_clicked(item : TreeItem, _column, _id, _mouse_button_index) -> void:
_check_item_actions(item)
func _on_item_activated() -> void:
var item = get_selected()
_check_item_actions(item)
func _ready() -> void:
if Engine.is_editor_hint(): return
_build_assigned_input_events()
_build_ui_tree()
button_clicked.connect(_on_button_clicked)
item_activated.connect(_on_item_activated)
if input_icon_mapper:
input_icon_mapper.joypad_device_changed.connect(_build_ui_tree)

View File

@ -0,0 +1,24 @@
[gd_scene load_steps=4 format=3 uid="uid://ci6wgl2ngd35n"]
[ext_resource type="Script" uid="uid://bp7d2e5djo2tp" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/input/input_actions_tree.gd" id="1_o33o4"]
[ext_resource type="Texture2D" uid="uid://c1eqf1cse1hch" path="res://addons/maaacks_game_template/base/assets/remapping_input_icons/addition_symbol.png" id="2_ppi0j"]
[ext_resource type="Texture2D" uid="uid://bteq3ica74h30" path="res://addons/maaacks_game_template/base/assets/remapping_input_icons/subtraction_symbol.png" id="3_hb3xh"]
[node name="InputActionsTree" type="Tree"]
custom_minimum_size = Vector2(400, 240)
size_flags_vertical = 3
hide_root = true
script = ExtResource("1_o33o4")
input_action_names = Array[StringName]([&"move_forward", &"move_backward", &"move_up", &"move_down", &"move_left", &"move_right", &"interact"])
readable_action_names = Array[String](["Move Forward", "Move Backward", "Move Up", "Move Down", "Move Left", "Move Right", "Interact"])
add_button_texture = ExtResource("2_ppi0j")
remove_button_texture = ExtResource("3_hb3xh")
action_name_map = {
"interact": "Interact",
"move_backward": "Move Backward",
"move_down": "Move Down",
"move_forward": "Move Forward",
"move_left": "Move Left",
"move_right": "Move Right",
"move_up": "Move Up"
}

View File

@ -0,0 +1,140 @@
@tool
class_name InputIconMapper
extends FileLister
signal joypad_device_changed
const COMMON_REPLACE_STRINGS: Dictionary = {
"L 1": "Left Shoulder",
"R 1": "Right Shoulder",
"L 2": "Left Trigger",
"R 2": "Right Trigger",
"Lt": "Left Trigger",
"Rt": "Right Trigger",
"Lb": "Left Shoulder",
"Rb": "Right Shoulder",
} # Dictionary[String, String]
## Gives priority to icons with occurrences of the provided strings.
@export var prioritized_strings : Array[String]
## Replaces the first occurence in icon names of the key with the value.
@export var replace_strings : Dictionary # Dictionary[String, String]
## Filters the icon names of the provided strings.
@export var filtered_strings : Array[String]
## Adds entries for "Up", "Down", "Left", "Right" to icon names ending with "Stick".
@export var add_stick_directions : bool = false
@export var intial_joypad_device : String = InputEventHelper.DEVICE_GENERIC
## Attempt to match the icon names to the input names based on the string rules.
@export var _match_icons_to_inputs_action : bool = false :
set(value):
if value and Engine.is_editor_hint():
_match_icons_to_inputs()
# For Godot 4.4
# @export_tool_button("Match Icons to Inputs") var _match_icons_to_inputs_action = _match_icons_to_inputs
@export var matching_icons : Dictionary # Dictionary[String, Texture]
@export_group("Debug")
@export var all_icons : Dictionary # Dictionary[String, Texture]
@onready var last_joypad_device = intial_joypad_device
func _is_end_of_word(full_string : String, what : String) -> bool:
var string_end_position = full_string.find(what) + what.length()
var end_of_word : bool
if string_end_position + 1 < full_string.length():
var next_character = full_string.substr(string_end_position, 1)
end_of_word = next_character == " "
return full_string.ends_with(what) or end_of_word
func _get_standard_joy_name(joy_name : String) -> String:
var all_replace_strings := replace_strings.duplicate()
all_replace_strings.merge(COMMON_REPLACE_STRINGS)
for what in all_replace_strings:
if joy_name.contains(what) and _is_end_of_word(joy_name, what):
var position = joy_name.find(what)
joy_name = joy_name.erase(position, what.length())
joy_name = joy_name.insert(position, all_replace_strings[what])
var combined_joystick_name : Array[String] = []
for part in joy_name.split(" "):
if part.to_lower() in filtered_strings:
continue
if not part.is_empty():
combined_joystick_name.append(part)
joy_name = " ".join(combined_joystick_name)
joy_name = joy_name.strip_edges()
return joy_name
func _match_icon_to_file(file : String) -> void:
var matching_string : String = file.get_file().get_basename()
var icon : Texture = load(file)
if not icon:
return
all_icons[matching_string] = icon
matching_string = matching_string.capitalize()
matching_string = _get_standard_joy_name(matching_string)
matching_string = matching_string.strip_edges()
if add_stick_directions and matching_string.ends_with("Stick"):
matching_icons[matching_string + " Up"] = icon
matching_icons[matching_string + " Down"] = icon
matching_icons[matching_string + " Left"] = icon
matching_icons[matching_string + " Right"] = icon
return
if matching_string in matching_icons:
return
matching_icons[matching_string] = icon
func _prioritized_files() -> Array[String]:
var priority_levels : Dictionary # Dictionary[String, int]
var priortized_files : Array[String]
for prioritized_string in prioritized_strings:
for file in files:
if file.containsn(prioritized_string):
if file in priority_levels:
priority_levels[file] += 1
else:
priority_levels[file] = 1
var priority_file_map : Dictionary # Dictionary[int, Array]
var max_priority_level : int = 0
for file in priority_levels:
var priority_level = priority_levels[file]
max_priority_level = max(priority_level, max_priority_level)
if priority_level in priority_file_map:
priority_file_map[priority_level].append(file)
else:
priority_file_map[priority_level] = [file]
while max_priority_level > 0:
for priority_file in priority_file_map[max_priority_level]:
priortized_files.append(priority_file)
max_priority_level -= 1
return priortized_files
func _match_icons_to_inputs() -> void:
matching_icons.clear()
all_icons.clear()
for prioritized_file in _prioritized_files():
_match_icon_to_file(prioritized_file)
for file in files:
_match_icon_to_file(file)
func get_icon(input_event : InputEvent) -> Texture:
var specific_text = InputEventHelper.get_device_specific_text(input_event, last_joypad_device)
if specific_text in matching_icons:
return matching_icons[specific_text]
return null
func _assign_joypad_0_to_last() -> void:
if last_joypad_device != intial_joypad_device : return
var connected_joypads := Input.get_connected_joypads()
if connected_joypads.is_empty(): return
last_joypad_device = InputEventHelper.get_device_name_by_id(connected_joypads[0])
func _input(event : InputEvent) -> void:
var device_name = InputEventHelper.get_device_name(event)
if device_name != InputEventHelper.DEVICE_GENERIC and device_name != last_joypad_device:
last_joypad_device = device_name
joypad_device_changed.emit()
func _ready() -> void:
_assign_joypad_0_to_last()
if files.size() == 0:
_refresh_files()
if matching_icons.size() == 0:
_match_icons_to_inputs()

View File

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

View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://qoexj4ptqt8a"]
[ext_resource type="Script" uid="uid://cqigj1uumknrp" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/input/input_icon_mapper.gd" id="1_msrpt"]
[node name="InputIconMapper" type="Node"]
script = ExtResource("1_msrpt")

View File

@ -0,0 +1,102 @@
@tool
class_name InputOptionsMenu
extends Control
const ALREADY_ASSIGNED_TEXT : String = "{key} already assigned to {action}."
const ONE_INPUT_MINIMUM_TEXT : String = "%s must have at least one key or button assigned."
const KEY_DELETION_TEXT : String = "Are you sure you want to remove {key} from {action}?"
@export_enum("List", "Tree") var remapping_mode : int = 0 :
set(value):
remapping_mode = value
if is_inside_tree():
match(remapping_mode):
0:
%InputActionsList.show()
%InputActionsTree.hide()
1:
%InputActionsList.hide()
%InputActionsTree.show()
@onready var assignment_placeholder_text = $KeyAssignmentDialog.dialog_text
var last_input_readable_name
func _horizontally_align_popup_labels() -> void:
$KeyAssignmentDialog.get_label().horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
$KeyDeletionDialog.get_label().horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
$OneInputMinimumDialog.get_label().horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
$AlreadyAssignedDialog.get_label().horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
$ResetConfirmationDialog.get_label().horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
func _ready() -> void:
remapping_mode = remapping_mode
if Engine.is_editor_hint(): return
_horizontally_align_popup_labels()
func _add_action_event() -> void:
var last_input_event = $KeyAssignmentDialog.last_input_event
last_input_readable_name = $KeyAssignmentDialog.last_input_text
match(remapping_mode):
0:
%InputActionsList.add_action_event(last_input_readable_name, last_input_event)
1:
%InputActionsTree.add_action_event(last_input_readable_name, last_input_event)
func _remove_action_event(item : TreeItem) -> void:
%InputActionsTree.remove_action_event(item)
func _on_reset_button_pressed() -> void:
$ResetConfirmationDialog.popup_centered()
func _on_key_deletion_dialog_confirmed() -> void:
var editing_item = %InputActionsTree.editing_item
if is_instance_valid(editing_item):
_remove_action_event(editing_item)
func _on_key_assignment_dialog_confirmed() -> void:
_add_action_event()
func _open_key_assignment_dialog(action_name : String, readable_input_name : String = assignment_placeholder_text) -> void:
$KeyAssignmentDialog.title = tr("Assign Key for {action}").format({action = action_name})
$KeyAssignmentDialog.dialog_text = readable_input_name
$KeyAssignmentDialog.get_ok_button().disabled = true
$KeyAssignmentDialog.popup_centered()
func _on_input_actions_tree_add_button_clicked(action_name) -> void:
_open_key_assignment_dialog(action_name)
func _on_input_actions_tree_remove_button_clicked(action_name, input_name) -> void:
$KeyDeletionDialog.title = tr("Remove Key for {action}").format({action = action_name})
$KeyDeletionDialog.dialog_text = tr(KEY_DELETION_TEXT).format({key = input_name, action = action_name})
$KeyDeletionDialog.popup_centered()
func _popup_already_assigned(action_name, input_name) -> void:
$AlreadyAssignedDialog.dialog_text = tr(ALREADY_ASSIGNED_TEXT).format({key = input_name, action = action_name})
$AlreadyAssignedDialog.popup_centered.call_deferred()
func _popup_minimum_reached(action_name : String) -> void:
$OneInputMinimumDialog.dialog_text = ONE_INPUT_MINIMUM_TEXT % action_name
$OneInputMinimumDialog.popup_centered.call_deferred()
func _on_input_actions_tree_already_assigned(action_name, input_name) -> void:
_popup_already_assigned(action_name, input_name)
func _on_input_actions_tree_minimum_reached(action_name) -> void:
_popup_minimum_reached(action_name)
func _on_input_actions_list_already_assigned(action_name, input_name) -> void:
_popup_already_assigned(action_name, input_name)
func _on_input_actions_list_minimum_reached(action_name) -> void:
_popup_minimum_reached(action_name)
func _on_input_actions_list_button_clicked(action_name, readable_input_name) -> void:
_open_key_assignment_dialog(action_name, readable_input_name)
func _on_reset_confirmation_dialog_confirmed() -> void:
match(remapping_mode):
0:
%InputActionsList.reset()
1:
%InputActionsTree.reset()

View File

@ -0,0 +1,136 @@
[gd_scene load_steps=7 format=3 uid="uid://dp3rgqaehb3xu"]
[ext_resource type="Script" uid="uid://eborw7q4b07h" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/input/input_options_menu.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://qoexj4ptqt8a" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/input/input_icon_mapper.tscn" id="2_627ul"]
[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_game_template/base/scripts/capture_focus.gd" id="2_wft4x"]
[ext_resource type="Script" uid="uid://custha7r0uoic" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/input/key_assignment_dialog.gd" id="3_wsh2h"]
[ext_resource type="PackedScene" uid="uid://bxp45814v6ydv" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/input/input_actions_list.tscn" id="4_lf2nw"]
[ext_resource type="PackedScene" uid="uid://ci6wgl2ngd35n" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/input/input_actions_tree.tscn" id="5_b2whh"]
[node name="Controls" type="MarginContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/margin_left = 32
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 32
theme_override_constants/margin_bottom = 8
script = ExtResource("1")
[node name="InputIconMapper" parent="." instance=ExtResource("2_627ul")]
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
script = ExtResource("2_wft4x")
search_depth = 5
[node name="InputMappingContainer" type="VBoxContainer" parent="VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="Label" type="Label" parent="VBoxContainer/InputMappingContainer"]
layout_mode = 2
text = "Actions & Inputs"
horizontal_alignment = 1
[node name="InputActionsList" parent="VBoxContainer/InputMappingContainer" node_paths=PackedStringArray("input_icon_mapper") instance=ExtResource("4_lf2nw")]
unique_name_in_owner = true
layout_mode = 2
input_icon_mapper = NodePath("../../../InputIconMapper")
[node name="InputActionsTree" parent="VBoxContainer/InputMappingContainer" node_paths=PackedStringArray("input_icon_mapper") instance=ExtResource("5_b2whh")]
unique_name_in_owner = true
visible = false
layout_mode = 2
input_icon_mapper = NodePath("../../../InputIconMapper")
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/InputMappingContainer"]
layout_mode = 2
alignment = 1
[node name="ResetButton" type="Button" parent="VBoxContainer/InputMappingContainer/HBoxContainer"]
layout_mode = 2
text = "Reset"
[node name="KeyAssignmentDialog" type="ConfirmationDialog" parent="."]
title = "Assign Key"
size = Vector2i(400, 158)
dialog_text = "
"
script = ExtResource("3_wsh2h")
[node name="VBoxContainer" type="VBoxContainer" parent="KeyAssignmentDialog"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_top = 8.0
offset_right = -8.0
offset_bottom = -49.0
grow_horizontal = 2
grow_vertical = 2
[node name="InputLabel" type="Label" parent="KeyAssignmentDialog/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "None"
horizontal_alignment = 1
[node name="InputTextEdit" type="TextEdit" parent="KeyAssignmentDialog/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
placeholder_text = "Focus here to assign inputs."
context_menu_enabled = false
shortcut_keys_enabled = false
selecting_enabled = false
deselect_on_focus_loss_enabled = false
drag_and_drop_selection_enabled = false
middle_mouse_paste_enabled = false
caret_move_on_right_click = false
[node name="DelayTimer" type="Timer" parent="KeyAssignmentDialog"]
unique_name_in_owner = true
wait_time = 0.1
one_shot = true
[node name="KeyDeletionDialog" type="ConfirmationDialog" parent="."]
title = "Remove Key"
size = Vector2i(419, 100)
dialog_text = "Are you sure you want to remove KEY from ACTION?"
[node name="OneInputMinimumDialog" type="AcceptDialog" parent="."]
title = "Cannot Remove"
size = Vector2i(398, 100)
[node name="AlreadyAssignedDialog" type="AcceptDialog" parent="."]
title = "Already Assigned"
size = Vector2i(398, 100)
[node name="ResetConfirmationDialog" type="ConfirmationDialog" parent="."]
size = Vector2i(486, 100)
dialog_text = "Are you sure you want to reset controls back to the defaults?"
[connection signal="already_assigned" from="VBoxContainer/InputMappingContainer/InputActionsList" to="." method="_on_input_actions_list_already_assigned"]
[connection signal="button_clicked" from="VBoxContainer/InputMappingContainer/InputActionsList" to="." method="_on_input_actions_list_button_clicked"]
[connection signal="minimum_reached" from="VBoxContainer/InputMappingContainer/InputActionsList" to="." method="_on_input_actions_list_minimum_reached"]
[connection signal="add_button_clicked" from="VBoxContainer/InputMappingContainer/InputActionsTree" to="." method="_on_input_actions_tree_add_button_clicked"]
[connection signal="already_assigned" from="VBoxContainer/InputMappingContainer/InputActionsTree" to="." method="_on_input_actions_tree_already_assigned"]
[connection signal="minimum_reached" from="VBoxContainer/InputMappingContainer/InputActionsTree" to="." method="_on_input_actions_tree_minimum_reached"]
[connection signal="remove_button_clicked" from="VBoxContainer/InputMappingContainer/InputActionsTree" to="." method="_on_input_actions_tree_remove_button_clicked"]
[connection signal="pressed" from="VBoxContainer/InputMappingContainer/HBoxContainer/ResetButton" to="." method="_on_reset_button_pressed"]
[connection signal="confirmed" from="KeyAssignmentDialog" to="." method="_on_key_assignment_dialog_confirmed"]
[connection signal="visibility_changed" from="KeyAssignmentDialog" to="KeyAssignmentDialog" method="_on_visibility_changed"]
[connection signal="focus_entered" from="KeyAssignmentDialog/VBoxContainer/InputTextEdit" to="KeyAssignmentDialog" method="_on_text_edit_focus_entered"]
[connection signal="focus_exited" from="KeyAssignmentDialog/VBoxContainer/InputTextEdit" to="KeyAssignmentDialog" method="_on_input_text_edit_focus_exited"]
[connection signal="gui_input" from="KeyAssignmentDialog/VBoxContainer/InputTextEdit" to="KeyAssignmentDialog" method="_on_input_text_edit_gui_input"]
[connection signal="confirmed" from="KeyDeletionDialog" to="." method="_on_key_deletion_dialog_confirmed"]
[connection signal="confirmed" from="ResetConfirmationDialog" to="." method="_on_reset_confirmation_dialog_confirmed"]

View File

@ -0,0 +1,104 @@
extends ConfirmationDialog
const LISTENING_TEXT : String = "Listening for input..."
const FOCUS_HERE_TEXT : String = "Focus here to assign inputs."
const CONFIRM_INPUT_TEXT : String = "Press again to confirm..."
const NO_INPUT_TEXT : String = "None"
enum InputConfirmation {
SINGLE,
DOUBLE,
OK_BUTTON
}
@export var input_confirmation : InputConfirmation = InputConfirmation.SINGLE
var last_input_event : InputEvent
var last_input_text : String
var listening : bool = false
var confirming : bool = false
func _record_input_event(event : InputEvent) -> void:
last_input_text = InputEventHelper.get_text(event)
if last_input_text.is_empty():
return
last_input_event = event
%InputLabel.text = last_input_text
get_ok_button().disabled = false
func _is_recordable_input(event : InputEvent) -> bool:
return event != null and \
(event is InputEventKey or \
event is InputEventMouseButton or \
event is InputEventJoypadButton or \
(event is InputEventJoypadMotion and \
abs(event.axis_value) > 0.5)) and \
event.is_pressed()
func _start_listening() -> void:
%InputTextEdit.placeholder_text = LISTENING_TEXT
listening = true
%DelayTimer.start()
func _stop_listening() -> void:
%InputTextEdit.placeholder_text = FOCUS_HERE_TEXT
listening = false
confirming = false
func _on_text_edit_focus_entered() -> void:
_start_listening.call_deferred()
func _on_input_text_edit_focus_exited() -> void:
_stop_listening()
func _focus_on_ok() -> void:
get_ok_button().grab_focus()
func _ready() -> void:
get_ok_button().focus_neighbor_top = ^"../../%InputTextEdit"
get_cancel_button().focus_neighbor_top = ^"../../%InputTextEdit"
func _input_matches_last(event : InputEvent) -> bool:
return last_input_text == InputEventHelper.get_text(event)
func _is_mouse_input(event : InputEvent) -> bool:
return event is InputEventMouse
func _input_confirms_choice(event : InputEvent) -> bool:
return confirming and not _is_mouse_input(event) and _input_matches_last(event)
func _should_process_input_event(event : InputEvent) -> bool:
return listening and _is_recordable_input(event) and %DelayTimer.is_stopped()
func _should_confirm_input_event(event : InputEvent) -> bool:
return not _is_mouse_input(event)
func _confirm_choice() -> void:
confirmed.emit()
hide()
func _process_input_event(event : InputEvent) -> void:
if not _should_process_input_event(event):
return
if _input_confirms_choice(event):
confirming = false
if input_confirmation == InputConfirmation.DOUBLE:
_confirm_choice()
else:
_focus_on_ok.call_deferred()
return
_record_input_event(event)
if input_confirmation == InputConfirmation.SINGLE:
_confirm_choice()
if _should_confirm_input_event(event):
confirming = true
%DelayTimer.start()
%InputTextEdit.placeholder_text = CONFIRM_INPUT_TEXT
func _on_input_text_edit_gui_input(event) -> void:
%InputTextEdit.set_deferred("text", "")
_process_input_event(event)
func _on_visibility_changed() -> void:
if visible:
%InputLabel.text = NO_INPUT_TEXT
%InputTextEdit.grab_focus()

View File

@ -0,0 +1,13 @@
class_name MasterOptionsMenu
extends Control
func _unhandled_input(event : InputEvent) -> void:
if not is_visible_in_tree():
return
if event.is_action_pressed("ui_page_down"):
$TabContainer.current_tab = ($TabContainer.current_tab+1) % $TabContainer.get_tab_count()
elif event.is_action_pressed("ui_page_up"):
if $TabContainer.current_tab == 0:
$TabContainer.current_tab = $TabContainer.get_tab_count()-1
else:
$TabContainer.current_tab = $TabContainer.current_tab-1

View File

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

View File

@ -0,0 +1,23 @@
[gd_scene load_steps=2 format=3 uid="uid://bvwl11s2p0hd"]
[ext_resource type="Script" uid="uid://c3mignmhuvvq4" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/master_options_menu.gd" id="1_u08d5"]
[node name="MasterOptionsMenu" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1_u08d5")
[node name="TabContainer" type="TabContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
tab_alignment = 1

View File

@ -0,0 +1,25 @@
[gd_scene load_steps=5 format=3 uid="uid://hmx6o472ropw"]
[ext_resource type="PackedScene" uid="uid://bvwl11s2p0hd" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/master_options_menu.tscn" id="1_uaidt"]
[ext_resource type="PackedScene" uid="uid://dp3rgqaehb3xu" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/input/input_options_menu.tscn" id="2_15wl6"]
[ext_resource type="PackedScene" uid="uid://c8vnncjwqcpab" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/audio/audio_options_menu.tscn" id="3_qg4me"]
[ext_resource type="PackedScene" uid="uid://b2numvphf2kau" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/video/video_options_menu.tscn" id="4_1t848"]
[node name="MasterOptionsMenu" instance=ExtResource("1_uaidt")]
[node name="TabContainer" parent="." index="0"]
current_tab = 0
[node name="Controls" parent="TabContainer" index="1" instance=ExtResource("2_15wl6")]
layout_mode = 2
metadata/_tab_index = 0
[node name="Audio" parent="TabContainer" index="2" instance=ExtResource("3_qg4me")]
visible = false
layout_mode = 2
metadata/_tab_index = 1
[node name="Video" parent="TabContainer" index="3" instance=ExtResource("4_1t848")]
visible = false
layout_mode = 2
metadata/_tab_index = 2

View File

@ -0,0 +1,45 @@
class_name MiniOptionsMenu
extends Control
@onready var mute_control = %MuteControl
@onready var fullscreen_control = %FullscreenControl
@export var audio_control_scene : PackedScene
@export var hide_busses : Array[String]
func _on_bus_changed(bus_value : float, bus_iter : int) -> void:
AppSettings.set_bus_volume(bus_iter, bus_value)
func _add_audio_control(bus_name : String, bus_value : float, bus_iter : int) -> void:
if audio_control_scene == null or bus_name in hide_busses or bus_name.begins_with(AppSettings.SYSTEM_BUS_NAME_PREFIX):
return
var audio_control = audio_control_scene.instantiate()
%AudioControlContainer.call_deferred("add_child", audio_control)
if audio_control is OptionControl:
audio_control.option_section = OptionControl.OptionSections.AUDIO
audio_control.option_name = bus_name
audio_control.value = bus_value
audio_control.connect("setting_changed", _on_bus_changed.bind(bus_iter))
func _add_audio_bus_controls() -> void:
for bus_iter in AudioServer.bus_count:
var bus_name : String = AppSettings.get_audio_bus_name(bus_iter)
var linear : float = AppSettings.get_bus_volume(bus_iter)
_add_audio_control(bus_name, linear, bus_iter)
func _update_ui() -> void:
_add_audio_bus_controls()
mute_control.value = AppSettings.is_muted()
fullscreen_control.value = AppSettings.is_fullscreen(get_window())
func _sync_with_config() -> void:
_update_ui()
func _ready() -> void:
_sync_with_config()
func _on_mute_control_setting_changed(value : bool) -> void:
AppSettings.set_mute(value)
func _on_fullscreen_control_setting_changed(value : bool) -> void:
AppSettings.set_fullscreen_enabled(value, get_window())

View File

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

View File

@ -0,0 +1,51 @@
[gd_scene load_steps=5 format=3 uid="uid://vh1ucj2rfbby"]
[ext_resource type="Script" uid="uid://1c0iyo5djoxj" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/mini_options_menu.gd" id="1_32vm2"]
[ext_resource type="PackedScene" uid="uid://cl416gdb1fgwr" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/option_control/slider_option_control.tscn" id="2_kpc65"]
[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_game_template/base/scripts/capture_focus.gd" id="3_7qt1o"]
[ext_resource type="PackedScene" uid="uid://bsxh6v7j0257h" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/option_control/toggle_option_control.tscn" id="4_b20fb"]
[node name="MiniOptionsMenu" type="VBoxContainer"]
custom_minimum_size = Vector2(400, 260)
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -200.0
offset_top = -130.0
offset_right = 200.0
offset_bottom = 130.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 4
theme_override_constants/separation = 8
alignment = 1
script = ExtResource("1_32vm2")
audio_control_scene = ExtResource("2_kpc65")
[node name="AudioControlContainer" type="VBoxContainer" parent="."]
unique_name_in_owner = true
layout_mode = 2
theme_override_constants/separation = 8
script = ExtResource("3_7qt1o")
search_depth = 2
[node name="MuteControl" parent="." instance=ExtResource("4_b20fb")]
unique_name_in_owner = true
layout_mode = 2
option_name = "Mute"
option_section = 2
key = "Mute"
section = "AudioSettings"
[node name="FullscreenControl" parent="." instance=ExtResource("4_b20fb")]
unique_name_in_owner = true
layout_mode = 2
option_name = "Fullscreen"
option_section = 3
key = "FullscreenEnabled"
section = "VideoSettings"
[connection signal="setting_changed" from="MuteControl" to="." method="_on_mute_control_setting_changed"]
[connection signal="setting_changed" from="FullscreenControl" to="." method="_on_fullscreen_control_setting_changed"]

View File

@ -0,0 +1,81 @@
@tool
class_name ListOptionControl
extends OptionControl
## Locks Option Titles from auto-updating when editing Option Values.
## Intentionally put first for initialization.
@export var lock_titles : bool = false
## Defines the list of possible values for the variable
## this option stores in the config file.
@export var option_values : Array :
set(value) :
option_values = value
_on_option_values_changed()
## Defines the list of options displayed to the user.
## Length should match with Option Values.
@export var option_titles : Array[String] :
set(value):
option_titles = value
if is_inside_tree():
_set_option_list(option_titles)
var custom_option_values : Array
func _on_option_values_changed() -> void:
if option_values.is_empty(): return
custom_option_values = option_values.duplicate()
var first_value = custom_option_values.front()
property_type = typeof(first_value)
_set_titles_from_values()
func _on_setting_changed(value : Variant) -> void:
if value < custom_option_values.size() and value >= 0:
super._on_setting_changed(custom_option_values[value])
func _set_titles_from_values() -> void:
if lock_titles: return
var mapped_titles : Array[String] = []
for option_value in custom_option_values:
mapped_titles.append(_value_title_map(option_value))
option_titles = mapped_titles
func _value_title_map(value : Variant) -> String:
return "%s" % value
func _match_value_to_other(value : Variant, other : Variant) -> Variant:
# Primarily for when the editor saves floats as ints instead
if value is int and other is float:
return float(value)
if value is float and other is int:
return int(round(value))
return value
func _set_value(value : Variant) -> Variant:
if option_values.is_empty(): return
if value == null:
return super._set_value(-1)
custom_option_values = option_values.duplicate()
value = _match_value_to_other(value, custom_option_values.front())
if value not in custom_option_values and typeof(value) == property_type:
custom_option_values.append(value)
custom_option_values.sort()
_set_titles_from_values()
if value not in option_values:
disable_option(custom_option_values.find(value))
value = custom_option_values.find(value)
return super._set_value(value)
func _set_option_list(option_titles_list : Array) -> void:
%OptionButton.clear()
for option_title in option_titles_list:
%OptionButton.add_item(option_title)
func disable_option(option_index : int, disabled : bool = true) -> void:
%OptionButton.set_item_disabled(option_index, disabled)
func _ready() -> void:
lock_titles = lock_titles
option_titles = option_titles
option_values = option_values
super._ready()

View File

@ -0,0 +1,14 @@
[gd_scene load_steps=3 format=3 uid="uid://b6bl3n5mp3m1e"]
[ext_resource type="PackedScene" uid="uid://d7te75il06t7" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/option_control/option_control.tscn" id="1_blo3b"]
[ext_resource type="Script" uid="uid://b8xqufg4re3c2" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/option_control/list_option_control.gd" id="2_kt4vl"]
[node name="OptionControl" instance=ExtResource("1_blo3b")]
script = ExtResource("2_kt4vl")
lock_titles = false
option_values = []
option_titles = []
[node name="OptionButton" type="OptionButton" parent="." index="1"]
unique_name_in_owner = true
layout_mode = 2

View File

@ -0,0 +1,140 @@
@tool
class_name OptionControl
extends Control
signal setting_changed(value)
enum OptionSections{
NONE,
INPUT,
AUDIO,
VIDEO,
GAME,
APPLICATION,
CUSTOM,
}
const OptionSectionNames : Dictionary = {
OptionSections.NONE : "",
OptionSections.INPUT : AppSettings.INPUT_SECTION,
OptionSections.AUDIO : AppSettings.AUDIO_SECTION,
OptionSections.VIDEO : AppSettings.VIDEO_SECTION,
OptionSections.GAME : AppSettings.GAME_SECTION,
OptionSections.APPLICATION : AppSettings.APPLICATION_SECTION,
OptionSections.CUSTOM : AppSettings.CUSTOM_SECTION,
}
## Locks config names in case of issues with inherited scenes.
## Intentionally put first for initialization.
@export var lock_config_names : bool = false
## Defines text displayed to the user.
@export var option_name : String :
set(value):
var _update_config : bool = option_name.to_pascal_case() == key and not lock_config_names
option_name = value
if is_inside_tree():
%OptionLabel.text = "%s%s" % [option_name, label_suffix]
if _update_config:
key = option_name.to_pascal_case()
## Defines what section in the config file this option belongs under.
@export var option_section : OptionSections :
set(value):
var _update_config : bool = OptionSectionNames[option_section] == section and not lock_config_names
option_section = value
if _update_config:
section = OptionSectionNames[option_section]
@export_group("Config Names")
## Defines the key for this option variable in the config file.
@export var key : String
## Defines the section for this option variable in the config file.
@export var section : String
@export_group("Format")
@export var label_suffix : String = " :"
@export_group("Properties")
## Defines whether the option is editable, or only visible by the user.
@export var editable : bool = true : set = set_editable
## Defines what kind of variable this option stores in the config file.
@export var property_type : Variant.Type = TYPE_BOOL
## It is advised to use an external editor to set the default value in the scene file.
## Godot can experience a bug (caching issue?) that may undo changes.
var default_value
var _connected_nodes : Array
func _on_setting_changed(value) -> void:
if Engine.is_editor_hint(): return
Config.set_config(section, key, value)
setting_changed.emit(value)
func _get_setting(default : Variant = null) -> Variant:
return Config.get_config(section, key, default)
func _connect_option_inputs(node) -> void:
if node in _connected_nodes: return
if node is Button:
if node is OptionButton:
node.item_selected.connect(_on_setting_changed)
elif node is ColorPickerButton:
node.color_changed.connect(_on_setting_changed)
else:
node.toggled.connect(_on_setting_changed)
_connected_nodes.append(node)
if node is Range:
node.value_changed.connect(_on_setting_changed)
_connected_nodes.append(node)
if node is LineEdit or node is TextEdit:
node.text_changed.connect(_on_setting_changed)
_connected_nodes.append(node)
func _set_value(value : Variant) -> Variant:
if value == null:
return
for node in get_children():
if node is Button:
if node is OptionButton:
node.select(value as int)
elif node is ColorPickerButton:
node.color = value as Color
else:
node.button_pressed = value as bool
if node is Range:
node.value = value as float
if node is LineEdit or node is TextEdit:
node.text = "%s" % value
return value
func set_value(value : Variant) -> void:
value = _set_value(value)
_on_setting_changed(value)
func set_editable(value : bool = true) -> void:
editable = value
for node in get_children():
if node is Button:
node.disabled = !editable
if node is Slider or node is SpinBox or node is LineEdit or node is TextEdit:
node.editable = editable
func _ready() -> void:
lock_config_names = lock_config_names
option_section = option_section
option_name = option_name
property_type = property_type
default_value = default_value
_set_value(_get_setting(default_value))
for child in get_children():
_connect_option_inputs(child)
child_entered_tree.connect(_connect_option_inputs)
func _set(property : StringName, value : Variant) -> bool:
if property == "value":
set_value(value)
return true
return false
func _get_property_list() -> Array[Dictionary]:
return [
{ "name": "value", "type": property_type, "usage": PROPERTY_USAGE_NONE},
{ "name": "default_value", "type": property_type}
]

View File

@ -0,0 +1,17 @@
[gd_scene load_steps=2 format=3 uid="uid://d7te75il06t7"]
[ext_resource type="Script" uid="uid://cafqki2b08kwu" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/option_control/option_control.gd" id="1_jvl5q"]
[node name="OptionControl" type="HBoxContainer"]
custom_minimum_size = Vector2(0, 40)
offset_right = 400.0
offset_bottom = 40.0
script = ExtResource("1_jvl5q")
default_value = false
[node name="OptionLabel" type="Label" parent="."]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = " :"
vertical_alignment = 1

View File

@ -0,0 +1,19 @@
[gd_scene load_steps=2 format=3 uid="uid://cl416gdb1fgwr"]
[ext_resource type="PackedScene" uid="uid://d7te75il06t7" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/option_control/option_control.tscn" id="1_16hlr"]
[node name="OptionControl" instance=ExtResource("1_16hlr")]
custom_minimum_size = Vector2(0, 28)
offset_bottom = 28.0
property_type = 3
default_value = 1.0
[node name="HSlider" type="HSlider" parent="." index="1"]
custom_minimum_size = Vector2(256, 0)
layout_mode = 2
size_flags_vertical = 4
max_value = 1.0
step = 0.05
value = 1.0
tick_count = 11
ticks_on_borders = true

View File

@ -0,0 +1,8 @@
[gd_scene load_steps=2 format=3 uid="uid://bsxh6v7j0257h"]
[ext_resource type="PackedScene" uid="uid://d7te75il06t7" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/option_control/option_control.tscn" id="1_8rnmo"]
[node name="OptionControl" instance=ExtResource("1_8rnmo")]
[node name="CheckButton" type="CheckButton" parent="." index="1"]
layout_mode = 2

View File

@ -0,0 +1,9 @@
@tool
class_name Vector2ListOptionControl
extends ListOptionControl
func _value_title_map(value : Variant) -> String:
if value is Vector2 or value is Vector2i:
return "%d x %d" % [value.x , value.y]
else:
return super._value_title_map(value)

View File

@ -0,0 +1,7 @@
[gd_scene load_steps=3 format=3 uid="uid://c01ayjblhcg1t"]
[ext_resource type="PackedScene" uid="uid://b6bl3n5mp3m1e" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/option_control/list_option_control.tscn" id="1_jqwiw"]
[ext_resource type="Script" uid="uid://brntdgf3sv0s0" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/option_control/vector_2_list_option_control.gd" id="2_w33vs"]
[node name="OptionControl" instance=ExtResource("1_jqwiw")]
script = ExtResource("2_w33vs")

View File

@ -0,0 +1,38 @@
class_name VideoOptionsMenu
extends Control
func _preselect_resolution(window : Window) -> void:
%ResolutionControl.value = window.size
func _update_resolution_options_enabled(window : Window) -> void:
if OS.has_feature("web"):
%ResolutionControl.editable = false
%ResolutionControl.tooltip_text = "Disabled for web"
elif AppSettings.is_fullscreen(window):
%ResolutionControl.editable = false
%ResolutionControl.tooltip_text = "Disabled for fullscreen"
else:
%ResolutionControl.editable = true
%ResolutionControl.tooltip_text = "Select a screen size"
func _update_ui(window : Window) -> void:
%FullscreenControl.value = AppSettings.is_fullscreen(window)
_preselect_resolution(window)
%VSyncControl.value = AppSettings.get_vsync(window)
_update_resolution_options_enabled(window)
func _ready() -> void:
var window : Window = get_window()
_update_ui(window)
window.connect("size_changed", _preselect_resolution.bind(window))
func _on_fullscreen_control_setting_changed(value) -> void:
var window : Window = get_window()
AppSettings.set_fullscreen_enabled(value, window)
_update_resolution_options_enabled(window)
func _on_resolution_control_setting_changed(value) -> void:
AppSettings.set_resolution(value, get_window(), false)
func _on_v_sync_control_setting_changed(value) -> void:
AppSettings.set_vsync(value, get_window())

View File

@ -0,0 +1,60 @@
[gd_scene load_steps=6 format=3 uid="uid://b2numvphf2kau"]
[ext_resource type="Script" uid="uid://cpe5r24151r5n" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/video/video_options_menu.gd" id="1"]
[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_game_template/base/scripts/capture_focus.gd" id="2_dgrai"]
[ext_resource type="PackedScene" uid="uid://bsxh6v7j0257h" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/option_control/toggle_option_control.tscn" id="3_uded6"]
[ext_resource type="PackedScene" uid="uid://c01ayjblhcg1t" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/option_control/vector_2_list_option_control.tscn" id="4_gwtfq"]
[ext_resource type="PackedScene" uid="uid://b6bl3n5mp3m1e" path="res://addons/maaacks_game_template/base/scenes/menus/options_menu/option_control/list_option_control.tscn" id="5_881de"]
[node name="Video" type="MarginContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
theme_override_constants/margin_top = 24
theme_override_constants/margin_bottom = 24
script = ExtResource("1")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
custom_minimum_size = Vector2(400, 0)
layout_mode = 2
size_flags_horizontal = 4
alignment = 1
script = ExtResource("2_dgrai")
search_depth = 2
[node name="FullscreenControl" parent="VBoxContainer" instance=ExtResource("3_uded6")]
unique_name_in_owner = true
layout_mode = 2
option_name = "Fullscreen"
option_section = 3
key = "FullscreenEnabled"
section = "VideoSettings"
[node name="ResolutionControl" parent="VBoxContainer" instance=ExtResource("4_gwtfq")]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Select a screen size"
option_values = [Vector2i(640, 360), Vector2i(960, 540), Vector2i(1024, 576), Vector2i(1280, 720), Vector2i(1600, 900), Vector2i(1920, 1080), Vector2i(2048, 1152), Vector2i(2560, 1440), Vector2i(3200, 1800), Vector2i(3840, 2160)]
option_titles = Array[String](["640 x 360", "960 x 540", "1024 x 576", "1280 x 720", "1600 x 900", "1920 x 1080", "2048 x 1152", "2560 x 1440", "3200 x 1800", "3840 x 2160"])
option_name = "Resolution"
option_section = 3
key = "ScreenResolution"
section = "VideoSettings"
property_type = 6
[node name="VSyncControl" parent="VBoxContainer" instance=ExtResource("5_881de")]
unique_name_in_owner = true
layout_mode = 2
lock_titles = true
option_values = [0, 1, 2, 3]
option_titles = Array[String](["Disabled", "Enabled", "Adaptive", "Mailbox"])
option_name = "V-Sync"
option_section = 3
key = "V-sync"
section = "VideoSettings"
property_type = 2
default_value = 0
[connection signal="setting_changed" from="VBoxContainer/FullscreenControl" to="." method="_on_fullscreen_control_setting_changed"]
[connection signal="setting_changed" from="VBoxContainer/ResolutionControl" to="." method="_on_resolution_control_setting_changed"]
[connection signal="setting_changed" from="VBoxContainer/VSyncControl" to="." method="_on_v_sync_control_setting_changed"]