Basic game template addon
This commit is contained in:
@@ -0,0 +1 @@
|
||||
Remapping input icons by Marek Belski is marked with CC0 1.0. To view a copy of this license, visit https://creativecommons.org/publicdomain/zero/1.0/
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 357 B |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c1eqf1cse1hch"
|
||||
path="res://.godot/imported/addition_symbol.png-e8a7f3ce4d91474fb1dc85f298d0b607.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/maaacks_game_template/base/assets/remapping_input_icons/addition_symbol.png"
|
||||
dest_files=["res://.godot/imported/addition_symbol.png-e8a7f3ce4d91474fb1dc85f298d0b607.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 327 B |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bteq3ica74h30"
|
||||
path="res://.godot/imported/subtraction_symbol.png-88291598586ab54d7f002593f7569b3e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/maaacks_game_template/base/assets/remapping_input_icons/subtraction_symbol.png"
|
||||
dest_files=["res://.godot/imported/subtraction_symbol.png-88291598586ab54d7f002593f7569b3e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
188
addons/maaacks_game_template/base/nodes/config/app_settings.gd
Normal file
188
addons/maaacks_game_template/base/nodes/config/app_settings.gd
Normal file
@@ -0,0 +1,188 @@
|
||||
class_name AppSettings
|
||||
extends Node
|
||||
## Interface to read/write general application settings through [PlayerConfig].
|
||||
|
||||
const INPUT_SECTION = &'InputSettings'
|
||||
const AUDIO_SECTION = &'AudioSettings'
|
||||
const VIDEO_SECTION = &'VideoSettings'
|
||||
const GAME_SECTION = &'GameSettings'
|
||||
const APPLICATION_SECTION = &'ApplicationSettings'
|
||||
const CUSTOM_SECTION = &'CustomSettings'
|
||||
|
||||
const FULLSCREEN = &'Fullscreen'
|
||||
const SCREEN_RESOLUTION = &'ScreenResolution'
|
||||
const V_SYNC = &'V-Sync'
|
||||
const MUTE_SETTING = &'Mute'
|
||||
const MASTER_BUS_INDEX = 0
|
||||
const SYSTEM_BUS_NAME_PREFIX = "_"
|
||||
|
||||
# Input
|
||||
static var default_action_events : Dictionary
|
||||
static var initial_bus_volumes : Array
|
||||
|
||||
static func get_config_input_events(action_name : String, default = null) -> Array:
|
||||
return PlayerConfig.get_config(INPUT_SECTION, action_name, default)
|
||||
|
||||
static func set_config_input_events(action_name : String, inputs : Array) -> void:
|
||||
PlayerConfig.set_config(INPUT_SECTION, action_name, inputs)
|
||||
|
||||
static func _clear_config_input_events() -> void:
|
||||
PlayerConfig.erase_section(INPUT_SECTION)
|
||||
|
||||
static func remove_action_input_event(action_name : String, input_event : InputEvent) -> void:
|
||||
InputMap.action_erase_event(action_name, input_event)
|
||||
var action_events : Array[InputEvent] = InputMap.action_get_events(action_name)
|
||||
var config_events : Array = get_config_input_events(action_name, action_events)
|
||||
config_events.erase(input_event)
|
||||
set_config_input_events(action_name, config_events)
|
||||
|
||||
static func set_input_from_config(action_name : String) -> void:
|
||||
var action_events : Array[InputEvent] = InputMap.action_get_events(action_name)
|
||||
var config_events = get_config_input_events(action_name, action_events)
|
||||
if config_events == action_events:
|
||||
return
|
||||
if config_events.is_empty():
|
||||
PlayerConfig.erase_section_key(INPUT_SECTION, action_name)
|
||||
return
|
||||
InputMap.action_erase_events(action_name)
|
||||
for config_event in config_events:
|
||||
if config_event not in action_events:
|
||||
InputMap.action_add_event(action_name, config_event)
|
||||
|
||||
static func _get_action_names() -> Array[StringName]:
|
||||
return InputMap.get_actions()
|
||||
|
||||
static func _get_custom_action_names() -> Array[StringName]:
|
||||
var callable_filter := func(action_name): return not (action_name.begins_with("ui_") or action_name.begins_with("spatial_editor"))
|
||||
var action_list := _get_action_names()
|
||||
return action_list.filter(callable_filter)
|
||||
|
||||
static func get_action_names(built_in_actions : bool = false) -> Array[StringName]:
|
||||
if built_in_actions:
|
||||
return _get_action_names()
|
||||
else:
|
||||
return _get_custom_action_names()
|
||||
|
||||
static func reset_to_default_inputs() -> void:
|
||||
_clear_config_input_events()
|
||||
for action_name in default_action_events:
|
||||
InputMap.action_erase_events(action_name)
|
||||
var input_events = default_action_events[action_name]
|
||||
for input_event in input_events:
|
||||
InputMap.action_add_event(action_name, input_event)
|
||||
|
||||
static func set_default_inputs() -> void:
|
||||
var action_list : Array[StringName] = _get_action_names()
|
||||
for action_name in action_list:
|
||||
default_action_events[action_name] = InputMap.action_get_events(action_name)
|
||||
|
||||
static func set_inputs_from_config() -> void:
|
||||
var action_list : Array[StringName] = _get_action_names()
|
||||
for action_name in action_list:
|
||||
set_input_from_config(action_name)
|
||||
|
||||
# Audio
|
||||
|
||||
static func get_bus_volume(bus_index : int) -> float:
|
||||
var initial_linear = 1.0
|
||||
if initial_bus_volumes.size() > bus_index:
|
||||
initial_linear = initial_bus_volumes[bus_index]
|
||||
var linear = db_to_linear(AudioServer.get_bus_volume_db(bus_index))
|
||||
linear /= initial_linear
|
||||
return linear
|
||||
|
||||
static func set_bus_volume(bus_index : int, linear : float) -> void:
|
||||
var initial_linear = 1.0
|
||||
if initial_bus_volumes.size() > bus_index:
|
||||
initial_linear = initial_bus_volumes[bus_index]
|
||||
linear *= initial_linear
|
||||
AudioServer.set_bus_volume_db(bus_index, linear_to_db(linear))
|
||||
|
||||
static func is_muted() -> bool:
|
||||
return AudioServer.is_bus_mute(MASTER_BUS_INDEX)
|
||||
|
||||
static func set_mute(mute_flag : bool) -> void:
|
||||
AudioServer.set_bus_mute(MASTER_BUS_INDEX, mute_flag)
|
||||
|
||||
static func get_audio_bus_name(bus_iter : int) -> String:
|
||||
return AudioServer.get_bus_name(bus_iter)
|
||||
|
||||
static func set_audio_from_config() -> void:
|
||||
for bus_iter in AudioServer.bus_count:
|
||||
var bus_key : String = get_audio_bus_name(bus_iter).to_pascal_case()
|
||||
var bus_volume : float = get_bus_volume(bus_iter)
|
||||
initial_bus_volumes.append(bus_volume)
|
||||
bus_volume = PlayerConfig.get_config(AUDIO_SECTION, bus_key, bus_volume)
|
||||
if is_nan(bus_volume):
|
||||
bus_volume = 1.0
|
||||
PlayerConfig.set_config(AUDIO_SECTION, bus_key, bus_volume)
|
||||
set_bus_volume(bus_iter, bus_volume)
|
||||
var mute_audio_flag : bool = is_muted()
|
||||
mute_audio_flag = PlayerConfig.get_config(AUDIO_SECTION, MUTE_SETTING, mute_audio_flag)
|
||||
set_mute(mute_audio_flag)
|
||||
|
||||
# Video
|
||||
|
||||
static func set_fullscreen_enabled(value : bool, window : Window) -> void:
|
||||
window.mode = Window.MODE_EXCLUSIVE_FULLSCREEN if (value) else Window.MODE_WINDOWED
|
||||
|
||||
static func set_resolution(value : Vector2i, window : Window, update_config : bool = true) -> void:
|
||||
if value.x == 0 or value.y == 0:
|
||||
return
|
||||
window.size = value
|
||||
if update_config:
|
||||
PlayerConfig.set_config(VIDEO_SECTION, SCREEN_RESOLUTION, value)
|
||||
|
||||
static func is_fullscreen(window : Window) -> bool:
|
||||
return (window.mode == Window.MODE_EXCLUSIVE_FULLSCREEN) or (window.mode == Window.MODE_FULLSCREEN)
|
||||
|
||||
static func get_resolution(window : Window) -> Vector2i:
|
||||
var current_resolution : Vector2i = window.size
|
||||
return PlayerConfig.get_config(VIDEO_SECTION, SCREEN_RESOLUTION, current_resolution)
|
||||
|
||||
static func _on_window_size_changed(window: Window) -> void:
|
||||
PlayerConfig.set_config(VIDEO_SECTION, SCREEN_RESOLUTION, window.size)
|
||||
|
||||
static func _set_fullscreen_from_config(window: Window) -> bool:
|
||||
var fullscreen_enabled : bool = is_fullscreen(window)
|
||||
fullscreen_enabled = PlayerConfig.get_config(VIDEO_SECTION, FULLSCREEN, fullscreen_enabled)
|
||||
set_fullscreen_enabled(fullscreen_enabled, window)
|
||||
return fullscreen_enabled
|
||||
|
||||
static func set_vsync(vsync_mode : DisplayServer.VSyncMode, window : Window = null) -> void:
|
||||
var window_id : int = 0
|
||||
if window:
|
||||
window_id = window.get_window_id()
|
||||
DisplayServer.window_set_vsync_mode(vsync_mode, window_id)
|
||||
|
||||
static func get_vsync(window : Window = null) -> DisplayServer.VSyncMode:
|
||||
var window_id : int = 0
|
||||
if window:
|
||||
window_id = window.get_window_id()
|
||||
var vsync_mode = DisplayServer.window_get_vsync_mode(window_id)
|
||||
return vsync_mode
|
||||
|
||||
static func _set_v_sync_from_config(window: Window) -> DisplayServer.VSyncMode:
|
||||
var vsync := get_vsync(window)
|
||||
vsync = PlayerConfig.get_config(VIDEO_SECTION, V_SYNC, vsync)
|
||||
set_vsync(vsync)
|
||||
return vsync
|
||||
|
||||
static func set_video_from_config(window : Window) -> void:
|
||||
window.size_changed.connect(_on_window_size_changed.bind(window))
|
||||
var fullscreen_enabled := _set_fullscreen_from_config(window)
|
||||
if not (fullscreen_enabled or OS.has_feature("web")):
|
||||
var current_resolution : Vector2i = get_resolution(window)
|
||||
set_resolution(current_resolution, window)
|
||||
_set_v_sync_from_config(window)
|
||||
|
||||
# All
|
||||
|
||||
static func set_from_config() -> void:
|
||||
set_default_inputs()
|
||||
set_inputs_from_config()
|
||||
set_audio_from_config()
|
||||
|
||||
static func set_from_config_and_window(window : Window) -> void:
|
||||
set_from_config()
|
||||
set_video_from_config(window)
|
||||
@@ -0,0 +1 @@
|
||||
uid://dwflyh7g2rjxt
|
||||
@@ -0,0 +1,56 @@
|
||||
class_name PlayerConfig
|
||||
extends Object
|
||||
|
||||
## Interface for a single configuration file through [ConfigFile].
|
||||
|
||||
const CONFIG_FILE_LOCATION := "user://player_config.cfg"
|
||||
|
||||
static var config_file : ConfigFile
|
||||
|
||||
static func _save_config_file() -> void:
|
||||
var save_error : int = config_file.save(CONFIG_FILE_LOCATION)
|
||||
if save_error:
|
||||
push_error("save config file failed with error %d" % save_error)
|
||||
|
||||
static func load_config_file() -> void:
|
||||
if config_file != null:
|
||||
return
|
||||
config_file = ConfigFile.new()
|
||||
var load_error : int = config_file.load(CONFIG_FILE_LOCATION)
|
||||
if load_error:
|
||||
var save_error : int = config_file.save(CONFIG_FILE_LOCATION)
|
||||
if save_error:
|
||||
push_error("save config file failed with error %d" % save_error)
|
||||
|
||||
static func set_config(section: String, key: String, value) -> void:
|
||||
load_config_file()
|
||||
config_file.set_value(section, key, value)
|
||||
_save_config_file()
|
||||
|
||||
static func get_config(section: String, key: String, default = null) -> Variant:
|
||||
load_config_file()
|
||||
return config_file.get_value(section, key, default)
|
||||
|
||||
static func has_section(section: String) -> bool:
|
||||
load_config_file()
|
||||
return config_file.has_section(section)
|
||||
|
||||
static func has_section_key(section: String, key: String) -> bool:
|
||||
load_config_file()
|
||||
return config_file.has_section_key(section, key)
|
||||
|
||||
static func erase_section(section: String) -> void:
|
||||
if has_section(section):
|
||||
config_file.erase_section(section)
|
||||
_save_config_file()
|
||||
|
||||
static func erase_section_key(section: String, key: String) -> void:
|
||||
if has_section_key(section, key):
|
||||
config_file.erase_section_key(section, key)
|
||||
_save_config_file()
|
||||
|
||||
static func get_section_keys(section: String) -> PackedStringArray:
|
||||
load_config_file()
|
||||
if config_file.has_section(section):
|
||||
return config_file.get_section_keys(section)
|
||||
return PackedStringArray()
|
||||
@@ -0,0 +1 @@
|
||||
uid://dxjk8pgi7yhtq
|
||||
@@ -0,0 +1,18 @@
|
||||
@tool
|
||||
extends Label
|
||||
## Displays the value of `application/config/name`, set in project settings.
|
||||
|
||||
const NO_NAME_STRING : String = "Title"
|
||||
|
||||
## If true, update the title when ready.
|
||||
@export var auto_update : bool = true
|
||||
|
||||
func update_name_label():
|
||||
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():
|
||||
if auto_update:
|
||||
update_name_label()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bkwlopi4qn32o
|
||||
@@ -0,0 +1,17 @@
|
||||
@tool
|
||||
extends Label
|
||||
## 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()
|
||||
@@ -0,0 +1 @@
|
||||
uid://dmkubt2nsnsbn
|
||||
@@ -0,0 +1,10 @@
|
||||
extends RichTextLabel
|
||||
## If true, disable opening links. For platforms that don't permit linking to other domains.
|
||||
@export var disable_opening_links: bool = false
|
||||
|
||||
func _on_meta_clicked(meta: String) -> void:
|
||||
if meta.begins_with("https://") and not disable_opening_links:
|
||||
var _err = OS.shell_open(meta)
|
||||
|
||||
func _ready() -> void:
|
||||
meta_clicked.connect(_on_meta_clicked)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cc2wtqasev7le
|
||||
@@ -0,0 +1,28 @@
|
||||
@tool
|
||||
extends Label
|
||||
## Displays the value of `version` from the config file of the specified plugin.
|
||||
|
||||
const NO_VERSION_STRING : String = "0.0.0"
|
||||
|
||||
@export var plugin_directory : String
|
||||
@export var version_prefix : String = "v"
|
||||
|
||||
func _get_plugin_version() -> String:
|
||||
if not plugin_directory.is_empty():
|
||||
for enabled_plugin in ProjectSettings.get_setting("editor_plugins/enabled"):
|
||||
if enabled_plugin.contains(plugin_directory):
|
||||
var config := ConfigFile.new()
|
||||
var error = config.load(enabled_plugin)
|
||||
if error != OK:
|
||||
break
|
||||
return config.get_value("plugin", "version", NO_VERSION_STRING)
|
||||
return ""
|
||||
|
||||
func update_version_label() -> void:
|
||||
var plugin_version = _get_plugin_version()
|
||||
if plugin_version.is_empty():
|
||||
plugin_version = NO_VERSION_STRING
|
||||
text = version_prefix + plugin_version
|
||||
|
||||
func _ready() -> void:
|
||||
update_version_label()
|
||||
@@ -0,0 +1 @@
|
||||
uid://kgp5jnrxxhdy
|
||||
@@ -0,0 +1,130 @@
|
||||
class_name MainMenu
|
||||
extends Control
|
||||
## Base menu scene that links to a game scene, an options menu, and credits.
|
||||
|
||||
signal sub_menu_opened
|
||||
signal sub_menu_closed
|
||||
signal game_started
|
||||
signal game_exited
|
||||
|
||||
## Defines the path to the game scene. Hides the play button if empty.
|
||||
@export_file("*.tscn") var game_scene_path : String
|
||||
## The scene to open when a player clicks the 'Options' button.
|
||||
@export var options_packed_scene : PackedScene
|
||||
## The scene to open when a player clicks the 'Credits' button.
|
||||
@export var credits_packed_scene : PackedScene
|
||||
@export var confirm_exit : bool = true
|
||||
@export_group("Extra Settings")
|
||||
## If true, signals that the game has started loading in the background, instead of directly loading it.
|
||||
## Requires Maaack's Scene Loader.
|
||||
@export var signal_game_start : bool = false
|
||||
## If true, signals that the player clicked the 'Exit' button, instead of immediately exiting.
|
||||
@export var signal_game_exit : bool = false
|
||||
|
||||
var sub_menu : Control
|
||||
|
||||
@onready var menu_container = %MenuContainer
|
||||
@onready var menu_buttons_box_container = %MenuButtonsBoxContainer
|
||||
@onready var new_game_button = %NewGameButton
|
||||
@onready var options_button = %OptionsButton
|
||||
@onready var credits_button = %CreditsButton
|
||||
@onready var exit_button = %ExitButton
|
||||
@onready var exit_confirmation = %ExitConfirmation
|
||||
## If Maaack's Scene Loader is installed, then it will be used to change scenes.
|
||||
@onready var scene_loader_node = get_tree().root.get_node_or_null(^"SceneLoader")
|
||||
|
||||
func get_game_scene_path() -> String:
|
||||
return game_scene_path
|
||||
|
||||
func load_game_scene() -> void:
|
||||
if scene_loader_node:
|
||||
if signal_game_start:
|
||||
scene_loader_node.load_scene(get_game_scene_path(), true)
|
||||
game_started.emit()
|
||||
else:
|
||||
scene_loader_node.load_scene(get_game_scene_path())
|
||||
else:
|
||||
get_tree().change_scene_to_file(get_game_scene_path())
|
||||
|
||||
func new_game() -> void:
|
||||
load_game_scene()
|
||||
|
||||
func try_exit_game() -> void:
|
||||
if confirm_exit and (not exit_confirmation.visible):
|
||||
exit_confirmation.show()
|
||||
else:
|
||||
exit_game()
|
||||
|
||||
func exit_game() -> void:
|
||||
if OS.has_feature("web"):
|
||||
return
|
||||
if signal_game_exit:
|
||||
game_exited.emit()
|
||||
else:
|
||||
get_tree().quit()
|
||||
|
||||
func _open_sub_menu(menu : PackedScene) -> Node:
|
||||
sub_menu = menu.instantiate()
|
||||
add_child(sub_menu)
|
||||
menu_container.hide()
|
||||
sub_menu.hidden.connect(_close_sub_menu, CONNECT_ONE_SHOT)
|
||||
sub_menu.tree_exiting.connect(_close_sub_menu, CONNECT_ONE_SHOT)
|
||||
sub_menu_opened.emit()
|
||||
return sub_menu
|
||||
|
||||
func _close_sub_menu() -> void:
|
||||
if sub_menu == null:
|
||||
return
|
||||
sub_menu.queue_free()
|
||||
sub_menu = null
|
||||
menu_container.show()
|
||||
sub_menu_closed.emit()
|
||||
|
||||
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:
|
||||
try_exit_game()
|
||||
if event.is_action_released("ui_accept") and get_viewport().gui_get_focus_owner() == null:
|
||||
menu_buttons_box_container.focus_first()
|
||||
|
||||
func _hide_exit_for_web() -> void:
|
||||
if OS.has_feature("web"):
|
||||
exit_button.hide()
|
||||
|
||||
func _hide_new_game_if_unset() -> void:
|
||||
if get_game_scene_path().is_empty():
|
||||
new_game_button.hide()
|
||||
|
||||
func _hide_options_if_unset() -> void:
|
||||
if options_packed_scene == null:
|
||||
options_button.hide()
|
||||
|
||||
func _hide_credits_if_unset() -> void:
|
||||
if credits_packed_scene == null:
|
||||
credits_button.hide()
|
||||
|
||||
func _ready() -> void:
|
||||
_hide_exit_for_web()
|
||||
_hide_options_if_unset()
|
||||
_hide_credits_if_unset()
|
||||
_hide_new_game_if_unset()
|
||||
|
||||
func _on_new_game_button_pressed() -> void:
|
||||
new_game()
|
||||
|
||||
func _on_options_button_pressed() -> void:
|
||||
_open_sub_menu(options_packed_scene)
|
||||
|
||||
func _on_credits_button_pressed() -> void:
|
||||
_open_sub_menu(credits_packed_scene)
|
||||
|
||||
func _on_exit_button_pressed() -> void:
|
||||
try_exit_game()
|
||||
|
||||
func _on_exit_confirmation_confirmed():
|
||||
exit_game()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bhgs1upaahk3y
|
||||
@@ -0,0 +1,175 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://c6k5nnpbypshi"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bhgs1upaahk3y" path="res://addons/maaacks_game_template/base/nodes/menus/main_menu/main_menu.gd" id="1"]
|
||||
[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd" id="4_l1ebe"]
|
||||
[ext_resource type="Script" uid="uid://dmkubt2nsnsbn" path="res://addons/maaacks_game_template/base/nodes/labels/config_version_label.gd" id="6_pdiij"]
|
||||
[ext_resource type="PackedScene" uid="uid://cwt4p3bufkke5" path="res://addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.tscn" id="7_im16j"]
|
||||
[ext_resource type="Script" uid="uid://bkwlopi4qn32o" path="res://addons/maaacks_game_template/base/nodes/labels/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")
|
||||
|
||||
[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="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 = "Title"
|
||||
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="VersionMargin" type="MarginContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 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="ExitConfirmation" parent="." instance=ExtResource("7_im16j")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(300, 160)
|
||||
layout_mode = 1
|
||||
offset_left = -150.0
|
||||
offset_top = -80.0
|
||||
offset_right = 150.0
|
||||
offset_bottom = 80.0
|
||||
ui_cancel_closes = false
|
||||
text = "Really exit the game?"
|
||||
title_visible = false
|
||||
|
||||
[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="confirmed" from="ExitConfirmation" to="." method="_on_exit_confirmation_confirmed"]
|
||||
@@ -0,0 +1,38 @@
|
||||
extends Control
|
||||
|
||||
## Scene for adjusting the volume of the audio busses.
|
||||
@export var audio_control_scene : PackedScene
|
||||
## Optional names of audio busses that should be ignored.
|
||||
@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)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bwugqn2cjr41e
|
||||
@@ -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/nodes/menus/options_menu/audio/audio_options_menu.gd" id="1"]
|
||||
[ext_resource type="PackedScene" uid="uid://cl416gdb1fgwr" path="res://addons/maaacks_game_template/base/nodes/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/nodes/utilities/capture_focus.gd" id="3_dtraq"]
|
||||
[ext_resource type="PackedScene" uid="uid://bsxh6v7j0257h" path="res://addons/maaacks_game_template/base/nodes/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"]
|
||||
@@ -0,0 +1,349 @@
|
||||
@tool
|
||||
class_name InputActionsList
|
||||
extends Container
|
||||
## Scene to list the input actions out as buttons in a grid format.
|
||||
|
||||
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"
|
||||
|
||||
## If true, lists action names on the vertical axis.
|
||||
@export var vertical : bool = true :
|
||||
set(value):
|
||||
vertical = value
|
||||
if is_inside_tree():
|
||||
%ParentBoxContainer.vertical = vertical
|
||||
## The number of inputs to make editable per action name.
|
||||
@export_range(1, 5) var action_groups : int = 2
|
||||
## The header to each input action group.
|
||||
@export var action_group_names : Array[String]
|
||||
## The names of the action names that should be listed for editing.
|
||||
@export var input_action_names : Array[StringName] :
|
||||
set(value):
|
||||
var _value_changed = input_action_names != value
|
||||
input_action_names = value
|
||||
if _value_changed:
|
||||
_refresh_readable_action_names()
|
||||
## The readable names of the action names that should be listed for editing.
|
||||
@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
|
||||
## If true, capitalizes action names in order to make them readable.
|
||||
@export var capitalize_action_names : bool = true :
|
||||
set(value):
|
||||
capitalize_action_names = value
|
||||
_refresh_readable_action_names()
|
||||
## If true, show action names that are not explicitely listed in an input action name map.
|
||||
@export var show_all_actions : bool = true
|
||||
## Optional minimum size to add to all edit buttons.
|
||||
@export var button_minimum_size : Vector2
|
||||
@export_group("Icons")
|
||||
## Optional link to an input icon mapper to replace the text with icons.
|
||||
@export var input_icon_mapper : InputIconMapper
|
||||
## If true, expand the icons to fill the buttons.
|
||||
@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 _refresh_readable_action_names():
|
||||
var _new_readable_action_names : Array[String]
|
||||
for action_name in input_action_names:
|
||||
if capitalize_action_names:
|
||||
action_name = action_name.capitalize()
|
||||
_new_readable_action_names.append(action_name)
|
||||
readable_action_names = _new_readable_action_names
|
||||
|
||||
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
|
||||
var button = _get_button_by_action(action_name, action_group)
|
||||
var readable_input_name : String
|
||||
if button and button in button_readable_input_map:
|
||||
readable_input_name = button_readable_input_map[button]
|
||||
_replace_action(action_name, readable_input_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()
|
||||
if button_minimum_size.x > 0:
|
||||
new_label.custom_minimum_size.x = button_minimum_size.x
|
||||
new_label.size_flags_horizontal = SIZE_SHRINK_CENTER
|
||||
else:
|
||||
new_label.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||
if button_minimum_size.y > 0:
|
||||
new_label.custom_minimum_size.y = button_minimum_size.y
|
||||
new_label.size_flags_vertical = SIZE_SHRINK_CENTER
|
||||
else:
|
||||
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, disabled: bool = false) -> void:
|
||||
var button = _get_button_by_action(action_name, action_group + 1)
|
||||
if button:
|
||||
button.disabled = disabled
|
||||
|
||||
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()
|
||||
if button_minimum_size.x > 0:
|
||||
new_button.custom_minimum_size.x = button_minimum_size.x
|
||||
new_button.size_flags_horizontal = SIZE_SHRINK_CENTER
|
||||
else:
|
||||
new_button.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||
if button_minimum_size.y > 0:
|
||||
new_button.custom_minimum_size.y = button_minimum_size.y
|
||||
new_button.size_flags_vertical = SIZE_SHRINK_CENTER
|
||||
else:
|
||||
new_button.size_flags_vertical = SIZE_EXPAND_FILL
|
||||
new_button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
new_button.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
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(action_name : StringName) -> String:
|
||||
var readable_name : String
|
||||
if action_name in action_name_map:
|
||||
readable_name = action_name_map[action_name]
|
||||
elif action_name in built_in_action_name_map:
|
||||
readable_name = built_in_action_name_map[action_name]
|
||||
else:
|
||||
readable_name = action_name
|
||||
if capitalize_action_names:
|
||||
readable_name = readable_name.capitalize()
|
||||
action_name_map[action_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)
|
||||
_update_next_button_disabled_state(action_name, group_iter)
|
||||
group_iter += 1
|
||||
while group_iter < action_groups:
|
||||
_clear_button(action_name, group_iter)
|
||||
_update_next_button_disabled_state(action_name, group_iter, true)
|
||||
group_iter += 1
|
||||
|
||||
func _set_action_box_container_size() -> void:
|
||||
if button_minimum_size.x > 0:
|
||||
%ActionBoxContainer.size_flags_horizontal = SIZE_SHRINK_CENTER
|
||||
else:
|
||||
%ActionBoxContainer.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||
if button_minimum_size.y > 0:
|
||||
%ActionBoxContainer.size_flags_vertical = SIZE_SHRINK_CENTER
|
||||
else:
|
||||
%ActionBoxContainer.size_flags_vertical = SIZE_EXPAND_FILL
|
||||
|
||||
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
|
||||
_set_action_box_container_size()
|
||||
_build_assigned_input_events()
|
||||
_build_ui_list.call_deferred()
|
||||
if input_icon_mapper:
|
||||
input_icon_mapper.joypad_device_changed.connect(_refresh_ui_list_button_content)
|
||||
@@ -0,0 +1 @@
|
||||
uid://b3q5fgjev8gyo
|
||||
@@ -0,0 +1,44 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bxp45814v6ydv"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b3q5fgjev8gyo" path="res://addons/maaacks_game_template/base/nodes/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
|
||||
|
||||
[node name="ActionNameLabel" type="Label" parent="ParentBoxContainer/ActionBoxContainer"]
|
||||
custom_minimum_size = Vector2(150, 0)
|
||||
layout_mode = 2
|
||||
@@ -0,0 +1,232 @@
|
||||
@tool
|
||||
class_name InputActionsTree
|
||||
extends Tree
|
||||
## Scene to list the input actions out in a tree format.
|
||||
|
||||
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)
|
||||
|
||||
## The names of the action names that should be listed for editing.
|
||||
@export var input_action_names : Array[StringName] :
|
||||
set(value):
|
||||
var _value_changed = input_action_names != value
|
||||
input_action_names = value
|
||||
if _value_changed:
|
||||
_refresh_readable_action_names()
|
||||
## The readable names of the action names that should be listed for editing.
|
||||
@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
|
||||
## If true, capitalizes action names in order to make them readable.
|
||||
@export var capitalize_action_names : bool = true :
|
||||
set(value):
|
||||
capitalize_action_names = value
|
||||
_refresh_readable_action_names()
|
||||
## Show action names that are not explicitely listed in an action name map.
|
||||
@export var show_all_actions : bool = true
|
||||
@export_group("Icons")
|
||||
## Icon for the button that adds a new input to an action name.
|
||||
@export var add_button_texture : Texture2D
|
||||
## Icon for the button that removes an input to an action name.
|
||||
@export var remove_button_texture : Texture2D
|
||||
## Optional link to an input icon mapper to replace the text with icons.
|
||||
@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 _refresh_readable_action_names():
|
||||
var _new_readable_action_names : Array[String]
|
||||
for action_name in input_action_names:
|
||||
if capitalize_action_names:
|
||||
action_name = action_name.capitalize()
|
||||
_new_readable_action_names.append(action_name)
|
||||
readable_action_names = _new_readable_action_names
|
||||
|
||||
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(action_name : StringName) -> String:
|
||||
var readable_name : String
|
||||
if action_name in action_name_map:
|
||||
readable_name = action_name_map[action_name]
|
||||
elif action_name in built_in_action_name_map:
|
||||
readable_name = built_in_action_name_map[action_name]
|
||||
else:
|
||||
readable_name = action_name
|
||||
if capitalize_action_names:
|
||||
readable_name = readable_name.capitalize()
|
||||
action_name_map[action_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.call_deferred()
|
||||
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)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bp7d2e5djo2tp
|
||||
@@ -0,0 +1,30 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://ci6wgl2ngd35n"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bp7d2e5djo2tp" path="res://addons/maaacks_game_template/base/nodes/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)
|
||||
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
|
||||
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"
|
||||
}
|
||||
@@ -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()
|
||||
@@ -0,0 +1 @@
|
||||
uid://cqigj1uumknrp
|
||||
@@ -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/nodes/menus/options_menu/input/input_icon_mapper.gd" id="1_msrpt"]
|
||||
|
||||
[node name="InputIconMapper" type="Node"]
|
||||
script = ExtResource("1_msrpt")
|
||||
@@ -0,0 +1,92 @@
|
||||
@tool
|
||||
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 = $KeyAssignmentWindow.text
|
||||
|
||||
var last_input_readable_name
|
||||
|
||||
func _ready() -> void:
|
||||
remapping_mode = remapping_mode
|
||||
|
||||
func _add_action_event() -> void:
|
||||
var last_input_event = $KeyAssignmentWindow.last_input_event
|
||||
last_input_readable_name = $KeyAssignmentWindow.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:
|
||||
$ResetConfirmation.show()
|
||||
|
||||
func _on_key_deletion_confirmation_confirmed() -> void:
|
||||
var editing_item = %InputActionsTree.editing_item
|
||||
if is_instance_valid(editing_item):
|
||||
_remove_action_event(editing_item)
|
||||
|
||||
func _on_key_assignment_window_confirmed() -> void:
|
||||
_add_action_event()
|
||||
|
||||
func _open_key_assignment_window(action_name : String, readable_input_name : String = assignment_placeholder_text) -> void:
|
||||
$KeyAssignmentWindow.title = tr("Assign Key for {action}").format({action = action_name})
|
||||
$KeyAssignmentWindow.text = readable_input_name
|
||||
$KeyAssignmentWindow.confirm_button.disabled = true
|
||||
$KeyAssignmentWindow.show()
|
||||
|
||||
func _on_input_actions_tree_add_button_clicked(action_name) -> void:
|
||||
_open_key_assignment_window(action_name)
|
||||
|
||||
func _on_input_actions_tree_remove_button_clicked(action_name, input_name) -> void:
|
||||
$KeyDeletionConfirmation.title = tr("Remove Key for {action}").format({action = action_name})
|
||||
$KeyDeletionConfirmation.text = tr(KEY_DELETION_TEXT).format({key = input_name, action = action_name})
|
||||
$KeyDeletionConfirmation.show()
|
||||
|
||||
func _popup_already_assigned(action_name, input_name) -> void:
|
||||
$AlreadyAssignedMessage.text = tr(ALREADY_ASSIGNED_TEXT).format({key = input_name, action = action_name})
|
||||
$AlreadyAssignedMessage.show()
|
||||
|
||||
func _popup_minimum_reached(action_name : String) -> void:
|
||||
$OneInputMinimumMessage.text = ONE_INPUT_MINIMUM_TEXT % action_name
|
||||
$OneInputMinimumMessage.show()
|
||||
|
||||
func _on_input_actions_tree_already_assigned(action_name, input_name) -> void:
|
||||
_popup_already_assigned.call_deferred(action_name, input_name)
|
||||
|
||||
func _on_input_actions_tree_minimum_reached(action_name) -> void:
|
||||
_popup_minimum_reached.call_deferred(action_name)
|
||||
|
||||
func _on_input_actions_list_already_assigned(action_name, input_name) -> void:
|
||||
_popup_already_assigned.call_deferred(action_name, input_name)
|
||||
|
||||
func _on_input_actions_list_minimum_reached(action_name) -> void:
|
||||
_popup_minimum_reached.call_deferred(action_name)
|
||||
|
||||
func _on_input_actions_list_button_clicked(action_name, readable_input_name) -> void:
|
||||
_open_key_assignment_window(action_name, readable_input_name)
|
||||
|
||||
func _on_reset_confirmation_confirmed() -> void:
|
||||
match(remapping_mode):
|
||||
0:
|
||||
%InputActionsList.reset()
|
||||
1:
|
||||
%InputActionsTree.reset()
|
||||
@@ -0,0 +1 @@
|
||||
uid://eborw7q4b07h
|
||||
@@ -0,0 +1,103 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://dp3rgqaehb3xu"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://eborw7q4b07h" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_options_menu.gd" id="1"]
|
||||
[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd" id="2_wft4x"]
|
||||
[ext_resource type="PackedScene" uid="uid://bxp45814v6ydv" path="res://addons/maaacks_game_template/base/nodes/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/nodes/menus/options_menu/input/input_actions_tree.tscn" id="5_b2whh"]
|
||||
[ext_resource type="PackedScene" uid="uid://cwt4p3bufkke5" path="res://addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.tscn" id="7_5j1ya"]
|
||||
[ext_resource type="PackedScene" uid="uid://dgravx3vt5g3i" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/input/key_assignment_window.tscn" id="7_r3r3g"]
|
||||
[ext_resource type="PackedScene" uid="uid://6gdbfi0172ji" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window.tscn" id="8_jtpjy"]
|
||||
|
||||
[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="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
|
||||
alignment = 1
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/InputMappingContainer"]
|
||||
layout_mode = 2
|
||||
text = "Actions & Inputs"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="InputActionsList" parent="VBoxContainer/InputMappingContainer" instance=ExtResource("4_lf2nw")]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(560, 440)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="InputActionsTree" parent="VBoxContainer/InputMappingContainer" instance=ExtResource("5_b2whh")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(400, 440)
|
||||
layout_mode = 2
|
||||
|
||||
[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="KeyDeletionConfirmation" parent="." instance=ExtResource("7_5j1ya")]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(420, 200)
|
||||
layout_mode = 2
|
||||
text = "Are you sure you want to remove KEY from ACTION?"
|
||||
title = "Remove Key"
|
||||
|
||||
[node name="ResetConfirmation" parent="." instance=ExtResource("7_5j1ya")]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(420, 200)
|
||||
layout_mode = 2
|
||||
text = "Are you sure you want to reset controls back to the defaults?"
|
||||
title = "Reset to Default"
|
||||
|
||||
[node name="OneInputMinimumMessage" parent="." instance=ExtResource("8_jtpjy")]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(420, 200)
|
||||
layout_mode = 2
|
||||
update_content = true
|
||||
title = "One Input Minimum"
|
||||
|
||||
[node name="AlreadyAssignedMessage" parent="." instance=ExtResource("8_jtpjy")]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(420, 200)
|
||||
layout_mode = 2
|
||||
update_content = true
|
||||
title = "Already Assigned"
|
||||
|
||||
[node name="KeyAssignmentWindow" parent="." instance=ExtResource("7_r3r3g")]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[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="KeyDeletionConfirmation" to="." method="_on_key_deletion_confirmation_confirmed"]
|
||||
[connection signal="confirmed" from="ResetConfirmation" to="." method="_on_reset_confirmation_confirmed"]
|
||||
[connection signal="confirmed" from="KeyAssignmentWindow" to="." method="_on_key_assignment_window_confirmed"]
|
||||
@@ -0,0 +1,112 @@
|
||||
@tool
|
||||
extends ConfirmationOverlaidWindow
|
||||
## Scene to confirm a new input for an action name.
|
||||
|
||||
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
|
||||
}
|
||||
## Confirmations required before a new input is accepted for an aciton.
|
||||
@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
|
||||
confirm_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_input_text_edit_focus_entered() -> void:
|
||||
_start_listening.call_deferred()
|
||||
|
||||
func _on_input_text_edit_focus_exited() -> void:
|
||||
_stop_listening()
|
||||
|
||||
func _focus_on_ok() -> void:
|
||||
confirm_button.grab_focus()
|
||||
|
||||
func _ready() -> void:
|
||||
confirm_button.focus_neighbor_top = ^"../../../BodyMargin/VBoxContainer/InputTextEdit"
|
||||
close_button.focus_neighbor_top = ^"../../../BodyMargin/VBoxContainer/InputTextEdit"
|
||||
super._ready()
|
||||
|
||||
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()
|
||||
close()
|
||||
|
||||
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:
|
||||
super._on_visibility_changed()
|
||||
if visible:
|
||||
if not text.strip_edges().is_empty():
|
||||
%InputLabel.text = text
|
||||
else:
|
||||
%InputLabel.text = NO_INPUT_TEXT
|
||||
%InputTextEdit.grab_focus()
|
||||
@@ -0,0 +1 @@
|
||||
uid://custha7r0uoic
|
||||
@@ -0,0 +1,65 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://dgravx3vt5g3i"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://cwt4p3bufkke5" path="res://addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.tscn" id="1_6c67a"]
|
||||
[ext_resource type="Script" uid="uid://custha7r0uoic" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/input/key_assignment_window.gd" id="2_oif0q"]
|
||||
|
||||
[node name="KeyAssignmentWindow" instance=ExtResource("1_6c67a")]
|
||||
custom_minimum_size = Vector2(420, 200)
|
||||
offset_left = -210.0
|
||||
offset_top = -100.0
|
||||
offset_right = 210.0
|
||||
offset_bottom = 100.0
|
||||
script = ExtResource("2_oif0q")
|
||||
input_confirmation = 0
|
||||
close_button_text = "Close"
|
||||
title = "Set Input"
|
||||
|
||||
[node name="TitleLabel" parent="ContentContainer/BoxContainer/TitleMargin/BoxContainer" index="0"]
|
||||
text = "Set Input"
|
||||
|
||||
[node name="DescriptionLabel" parent="ContentContainer/BoxContainer/BodyMargin" index="0"]
|
||||
visible = false
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="ContentContainer/BoxContainer/BodyMargin" index="1"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="InputLabel" type="Label" parent="ContentContainer/BoxContainer/BodyMargin/VBoxContainer" index="0"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "None"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="InputTextEdit" type="TextEdit" parent="ContentContainer/BoxContainer/BodyMargin/VBoxContainer" index="1"]
|
||||
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="MenuButtons" parent="ContentContainer/BoxContainer/MenuButtonsMargin" index="0"]
|
||||
null_focus_enabled = false
|
||||
joypad_enabled = false
|
||||
mouse_hidden_enabled = false
|
||||
|
||||
[node name="CloseButton" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="0"]
|
||||
focus_neighbor_top = NodePath("../../../BodyMargin/VBoxContainer/InputTextEdit")
|
||||
text = "Close"
|
||||
|
||||
[node name="ConfirmButton" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="1"]
|
||||
focus_neighbor_top = NodePath("../../../BodyMargin/VBoxContainer/InputTextEdit")
|
||||
|
||||
[node name="DelayTimer" type="Timer" parent="." index="1"]
|
||||
unique_name_in_owner = true
|
||||
wait_time = 0.1
|
||||
one_shot = true
|
||||
|
||||
[connection signal="focus_entered" from="ContentContainer/BoxContainer/BodyMargin/VBoxContainer/InputTextEdit" to="." method="_on_input_text_edit_focus_entered"]
|
||||
[connection signal="focus_exited" from="ContentContainer/BoxContainer/BodyMargin/VBoxContainer/InputTextEdit" to="." method="_on_input_text_edit_focus_exited"]
|
||||
[connection signal="gui_input" from="ContentContainer/BoxContainer/BodyMargin/VBoxContainer/InputTextEdit" to="." method="_on_input_text_edit_gui_input"]
|
||||
@@ -0,0 +1,46 @@
|
||||
extends Control
|
||||
|
||||
@onready var mute_control = %MuteControl
|
||||
@onready var fullscreen_control = %FullscreenControl
|
||||
|
||||
## Scene for adjusting the volume of the audio busses.
|
||||
@export var audio_control_scene : PackedScene
|
||||
## Optional names of audio busses that should be ignored.
|
||||
@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())
|
||||
@@ -0,0 +1 @@
|
||||
uid://c0jjk82iuuyh3
|
||||
@@ -0,0 +1,84 @@
|
||||
@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 _refresh_option_values(value : Variant) -> void:
|
||||
if option_values.is_empty(): return
|
||||
if value == null:
|
||||
return
|
||||
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))
|
||||
|
||||
func set_value(value : Variant) -> void:
|
||||
_refresh_option_values(value)
|
||||
value = custom_option_values.find(value)
|
||||
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()
|
||||
@@ -0,0 +1 @@
|
||||
uid://b8xqufg4re3c2
|
||||
@@ -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/nodes/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/nodes/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
|
||||
@@ -0,0 +1,136 @@
|
||||
@tool
|
||||
class_name OptionControl
|
||||
extends Control
|
||||
## Generic scene for editing a value of the [PlayerConfig].
|
||||
|
||||
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
|
||||
PlayerConfig.set_config(section, key, value)
|
||||
setting_changed.emit(value)
|
||||
|
||||
func _get_setting(default : Variant = null) -> Variant:
|
||||
return PlayerConfig.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) -> void:
|
||||
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
|
||||
|
||||
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}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
uid://cafqki2b08kwu
|
||||
@@ -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/nodes/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
|
||||
@@ -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/nodes/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
|
||||
@@ -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/nodes/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
|
||||
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
uid://brntdgf3sv0s0
|
||||
@@ -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/nodes/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/nodes/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")
|
||||
@@ -0,0 +1,13 @@
|
||||
extends TabContainer
|
||||
## Applies UI page up and page down inputs to tab switching.
|
||||
|
||||
func _unhandled_input(event : InputEvent) -> void:
|
||||
if not is_visible_in_tree():
|
||||
return
|
||||
if event.is_action_pressed("ui_page_down"):
|
||||
current_tab = (current_tab+1) % get_tab_count()
|
||||
elif event.is_action_pressed("ui_page_up"):
|
||||
if current_tab == 0:
|
||||
current_tab = get_tab_count()-1
|
||||
else:
|
||||
current_tab = current_tab-1
|
||||
@@ -0,0 +1 @@
|
||||
uid://c3mignmhuvvq4
|
||||
@@ -0,0 +1,37 @@
|
||||
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())
|
||||
@@ -0,0 +1 @@
|
||||
uid://cpe5r24151r5n
|
||||
@@ -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/nodes/menus/options_menu/video/video_options_menu.gd" id="1"]
|
||||
[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd" id="2_dgrai"]
|
||||
[ext_resource type="PackedScene" uid="uid://bsxh6v7j0257h" path="res://addons/maaacks_game_template/base/nodes/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/nodes/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/nodes/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 = "Fullscreen"
|
||||
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"]
|
||||
126
addons/maaacks_game_template/base/nodes/opening/opening.gd
Normal file
126
addons/maaacks_game_template/base/nodes/opening/opening.gd
Normal file
@@ -0,0 +1,126 @@
|
||||
extends Control
|
||||
## Scene for displaying opening logos, placards, or other images before a game.
|
||||
|
||||
## Defines the path to the next scene.
|
||||
@export_file("*.tscn") var next_scene_path : String
|
||||
## The list of images to show in the opening sequence.
|
||||
@export var images : Array[Texture2D]
|
||||
@export_group("Animation")
|
||||
## The time to fade-in the next image.
|
||||
@export var fade_in_time : float = 0.2
|
||||
## The time to fade-out the previous image.
|
||||
@export var fade_out_time : float = 0.2
|
||||
## The time to keep an image visible after fade-in and before fade-out.
|
||||
@export var visible_time : float = 1.6
|
||||
@export_group("Transition")
|
||||
## The delay before starting the first fade-in animation once ready.
|
||||
@export var start_delay : float = 0.5
|
||||
## The delay after ending the last fade-in animation before loading the next scene.
|
||||
@export var end_delay : float = 0.5
|
||||
## If true, show a loading screen if the next scene is not yet ready.
|
||||
## Requires Maaack's Scene Loader.
|
||||
@export var show_loading_screen : bool = false
|
||||
|
||||
## If Maaack's Scene Loader is installed, then it will be used to change scenes.
|
||||
@onready var scene_loader_node = get_tree().root.get_node_or_null(^"SceneLoader")
|
||||
|
||||
var tween : Tween
|
||||
var next_image_index : int = 0
|
||||
|
||||
func get_next_scene_path() -> String:
|
||||
return next_scene_path
|
||||
|
||||
func _on_scene_loaded() -> void:
|
||||
scene_loader_node.change_scene_to_resource()
|
||||
|
||||
func _load_next_scene() -> void:
|
||||
if scene_loader_node:
|
||||
var status = scene_loader_node.get_status()
|
||||
if status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
_on_scene_loaded()
|
||||
elif show_loading_screen:
|
||||
scene_loader_node.change_scene_to_loading_screen()
|
||||
elif not scene_loader_node.scene_loaded.is_connected(_on_scene_loaded):
|
||||
scene_loader_node.scene_loaded.connect(_on_scene_loaded, CONNECT_ONE_SHOT)
|
||||
else:
|
||||
get_tree().change_scene_to_file(get_next_scene_path())
|
||||
|
||||
func _add_textures_to_container(textures : Array[Texture2D]) -> void:
|
||||
for texture in textures:
|
||||
var texture_rect : TextureRect = TextureRect.new()
|
||||
texture_rect.texture = texture
|
||||
texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
texture_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
texture_rect.modulate.a = 0.0
|
||||
%ImagesContainer.call_deferred("add_child", texture_rect)
|
||||
|
||||
func _event_skips_image(event : InputEvent) -> bool:
|
||||
return event.is_action_released(&"ui_accept") or event.is_action_released(&"ui_select")
|
||||
|
||||
func _event_skips_intro(event : InputEvent) -> bool:
|
||||
return event.is_action_released(&"ui_cancel")
|
||||
|
||||
func _event_is_mouse_button_released(event : InputEvent) -> bool:
|
||||
return event is InputEventMouseButton and not event.is_pressed()
|
||||
|
||||
func _unhandled_input(event : InputEvent) -> void:
|
||||
if _event_skips_intro(event):
|
||||
_load_next_scene()
|
||||
elif _event_skips_image(event):
|
||||
_show_next_image(false)
|
||||
|
||||
func _gui_input(event : InputEvent) -> void:
|
||||
if _event_is_mouse_button_released(event):
|
||||
_show_next_image(false)
|
||||
|
||||
func _transition_out() -> void:
|
||||
await get_tree().create_timer(end_delay).timeout
|
||||
_load_next_scene()
|
||||
|
||||
func _transition_in() -> void:
|
||||
await get_tree().create_timer(start_delay).timeout
|
||||
if next_image_index == 0:
|
||||
_show_next_image()
|
||||
|
||||
func _wait_and_fade_out(texture_rect : TextureRect) -> void:
|
||||
var _compare_next_index = next_image_index
|
||||
await get_tree().create_timer(visible_time, false).timeout
|
||||
if _compare_next_index != next_image_index : return
|
||||
tween = create_tween()
|
||||
tween.tween_property(texture_rect, "modulate:a", 0.0, fade_out_time)
|
||||
await tween.finished
|
||||
_show_next_image.call_deferred()
|
||||
|
||||
func _hide_previous_image() -> void:
|
||||
if tween and tween.is_running():
|
||||
tween.stop()
|
||||
if %ImagesContainer.get_child_count() == 0:
|
||||
return
|
||||
var current_image = %ImagesContainer.get_child(next_image_index - 1)
|
||||
if current_image:
|
||||
current_image.modulate.a = 0.0
|
||||
|
||||
func _show_next_image(animated : bool = true) -> void:
|
||||
_hide_previous_image()
|
||||
if next_image_index >= %ImagesContainer.get_child_count():
|
||||
if animated:
|
||||
_transition_out()
|
||||
else:
|
||||
_load_next_scene()
|
||||
return
|
||||
var texture_rect = %ImagesContainer.get_child(next_image_index)
|
||||
if animated:
|
||||
tween = create_tween()
|
||||
tween.tween_property(texture_rect, "modulate:a", 1.0, fade_in_time)
|
||||
await tween.finished
|
||||
else:
|
||||
texture_rect.modulate.a = 1.0
|
||||
next_image_index += 1
|
||||
_wait_and_fade_out(texture_rect)
|
||||
|
||||
func _ready() -> void:
|
||||
AppSettings.set_from_config_and_window(get_window())
|
||||
if scene_loader_node:
|
||||
scene_loader_node.load_scene(get_next_scene_path(), true)
|
||||
_add_textures_to_container(images)
|
||||
_transition_in()
|
||||
@@ -0,0 +1 @@
|
||||
uid://dtco0s8byckx6
|
||||
21
addons/maaacks_game_template/base/nodes/opening/opening.tscn
Normal file
21
addons/maaacks_game_template/base/nodes/opening/opening.tscn
Normal file
@@ -0,0 +1,21 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://sikc02ddepyt"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dtco0s8byckx6" path="res://addons/maaacks_game_template/base/nodes/opening/opening.gd" id="1_fcjph"]
|
||||
|
||||
[node name="Opening" 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_fcjph")
|
||||
|
||||
[node name="ImagesContainer" 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
|
||||
@@ -0,0 +1,68 @@
|
||||
extends Control
|
||||
## Node that captures UI focus when switching menus.
|
||||
##
|
||||
## This script assists with capturing UI focus when
|
||||
## opening, closing, or switching between menus.
|
||||
## When attached to a node, it will check if it was changed to visible
|
||||
## and if it should grab focus. If both are true, it will capture focus
|
||||
## on the first eligible node in its scene tree.
|
||||
|
||||
## Hierarchical depth to search in the scene tree for a focusable control node.
|
||||
@export var search_depth : int = 1
|
||||
## If true, always capture focus when made visible.
|
||||
@export var enabled : bool = false
|
||||
## If true, capture focus if nothing currently is in focus.
|
||||
@export var null_focus_enabled : bool = true
|
||||
## If true, capture focus if there is a joypad detected.
|
||||
@export var joypad_enabled : bool = true
|
||||
## If true, capture focus if the mouse is hidden.
|
||||
@export var mouse_hidden_enabled : bool = true
|
||||
|
||||
## Locks focus
|
||||
@export var lock : bool = false :
|
||||
set(value):
|
||||
var value_changed : bool = lock != value
|
||||
lock = value
|
||||
if value_changed and not lock:
|
||||
update_focus()
|
||||
|
||||
func _focus_first_search(control_node : Control, levels : int = 1) -> bool:
|
||||
if control_node == null or !control_node.is_visible_in_tree():
|
||||
return false
|
||||
if control_node.focus_mode == FOCUS_ALL:
|
||||
control_node.grab_focus()
|
||||
if control_node is ItemList:
|
||||
control_node.select(0)
|
||||
return true
|
||||
if levels < 1:
|
||||
return false
|
||||
var children = control_node.get_children()
|
||||
for child in children:
|
||||
if _focus_first_search(child, levels - 1):
|
||||
return true
|
||||
return false
|
||||
|
||||
func focus_first() -> void:
|
||||
_focus_first_search(self, search_depth)
|
||||
|
||||
func update_focus() -> void:
|
||||
if lock : return
|
||||
if _is_visible_and_should_capture():
|
||||
focus_first()
|
||||
|
||||
func _should_capture_focus() -> bool:
|
||||
return enabled or \
|
||||
(get_viewport().gui_get_focus_owner() == null and null_focus_enabled) or \
|
||||
(Input.get_connected_joypads().size() > 0 and joypad_enabled) or \
|
||||
(Input.mouse_mode not in [Input.MOUSE_MODE_VISIBLE, Input.MOUSE_MODE_CONFINED] and mouse_hidden_enabled)
|
||||
|
||||
func _is_visible_and_should_capture() -> bool:
|
||||
return is_visible_in_tree() and _should_capture_focus()
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
call_deferred("update_focus")
|
||||
|
||||
func _ready() -> void:
|
||||
if is_inside_tree():
|
||||
update_focus()
|
||||
connect("visibility_changed", _on_visibility_changed)
|
||||
@@ -0,0 +1 @@
|
||||
uid://1nf36h0gms3q
|
||||
@@ -0,0 +1,55 @@
|
||||
@tool
|
||||
extends Node
|
||||
class_name FileLister
|
||||
## Helper class for listing all the scenes in a directory.
|
||||
|
||||
## List of paths to scene files.
|
||||
@export var _refresh_files_action : bool = false :
|
||||
set(value):
|
||||
if value and Engine.is_editor_hint():
|
||||
_refresh_files()
|
||||
# For Godot 4.4
|
||||
# @export_tool_button("Refresh Files") var _refresh_files_action = _refresh_files
|
||||
## Filled in the editor by selecting a directory.
|
||||
@export var files : Array[String]
|
||||
## Fills files with those discovered in directories, and matching constraints.
|
||||
@export_dir var directories : Array[String] :
|
||||
set(value):
|
||||
directories = value
|
||||
_refresh_files()
|
||||
@export_group("Constraints")
|
||||
## Include any results that match the string.
|
||||
@export var search : String
|
||||
## Exclude any results that match the string.
|
||||
@export var filter : String
|
||||
@export_subgroup("Advanced Search")
|
||||
## Include any results that begin with the string.
|
||||
@export var begins_with : String
|
||||
## Include any results that end with the string.
|
||||
@export var ends_with : String
|
||||
## Exclude any results that begin with the string.
|
||||
@export var not_begins_with : String
|
||||
## Exclude any results that end with the string.
|
||||
@export var not_ends_with : String
|
||||
|
||||
|
||||
func _refresh_files():
|
||||
if not is_inside_tree(): return
|
||||
files.clear()
|
||||
for directory in directories:
|
||||
var dir_access = DirAccess.open(directory)
|
||||
if dir_access:
|
||||
for file in dir_access.get_files():
|
||||
if (not search.is_empty()) and (not file.contains(search)):
|
||||
continue
|
||||
if (not filter.is_empty()) and (file.contains(filter)):
|
||||
continue
|
||||
if (not begins_with.is_empty()) and (not file.begins_with(begins_with)):
|
||||
continue
|
||||
if (not ends_with.is_empty()) and (not file.ends_with(ends_with)):
|
||||
continue
|
||||
if (not not_begins_with.is_empty()) and (file.begins_with(not_begins_with)):
|
||||
continue
|
||||
if (not not_ends_with.is_empty()) and (file.ends_with(not_ends_with)):
|
||||
continue
|
||||
files.append(directory + "/" + file)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bij7wsh8d44gv
|
||||
@@ -0,0 +1,175 @@
|
||||
class_name InputEventHelper
|
||||
extends Node
|
||||
## Helper class for organizing constants related to [InputEvent].
|
||||
|
||||
const DEVICE_KEYBOARD = "Keyboard"
|
||||
const DEVICE_MOUSE = "Mouse"
|
||||
const DEVICE_XBOX_CONTROLLER = "Xbox"
|
||||
const DEVICE_SWITCH_CONTROLLER = "Switch"
|
||||
const DEVICE_SWITCH_JOYCON_LEFT_CONTROLLER = "Switch Left Joycon"
|
||||
const DEVICE_SWITCH_JOYCON_RIGHT_CONTROLLER = "Switch Right Joycon"
|
||||
const DEVICE_SWITCH_JOYCON_COMBINED_CONTROLLER = "Switch Combined Joycons"
|
||||
const DEVICE_PLAYSTATION_CONTROLLER = "Playstation"
|
||||
const DEVICE_STEAMDECK_CONTROLLER = "Steamdeck"
|
||||
const DEVICE_GENERIC = "Generic"
|
||||
|
||||
const JOYSTICK_LEFT_NAME = "Left Stick"
|
||||
const JOYSTICK_RIGHT_NAME = "Right Stick"
|
||||
const D_PAD_NAME = "Dpad"
|
||||
|
||||
const MOUSE_BUTTONS : Array = ["None", "Left", "Right", "Middle", "Scroll Up", "Scroll Down", "Wheel Left", "Wheel Right"]
|
||||
|
||||
const JOYPAD_BUTTON_NAME_MAP : Dictionary = {
|
||||
DEVICE_GENERIC : ["Trigger A", "Trigger B", "Trigger C", "", "", "", "", "Left Stick Press", "Right Stick Press", "Left Shoulder", "Right Shoulder", "Up", "Down", "Left", "Right"],
|
||||
DEVICE_XBOX_CONTROLLER : ["A", "B", "X", "Y", "View", "Home", "Menu", "Left Stick Press", "Right Stick Press", "Left Shoulder", "Right Shoulder", "Up", "Down", "Left", "Right", "Share"],
|
||||
DEVICE_SWITCH_CONTROLLER : ["B", "A", "Y", "X", "Minus", "", "Plus", "Left Stick Press", "Right Stick Press", "Left Shoulder", "Right Shoulder", "Up", "Down", "Left", "Right", "Capture"],
|
||||
DEVICE_PLAYSTATION_CONTROLLER : ["Cross", "Circle", "Square", "Triangle", "Select", "PS", "Options", "Left Stick Press", "Right Stick Press", "Left Shoulder", "Right Shoulder", "Up", "Down", "Left", "Right", "Microphone"],
|
||||
DEVICE_STEAMDECK_CONTROLLER : ["A", "B", "X", "Y", "View", "", "Options", "Left Stick Press", "Right Stick Press", "Left Shoulder", "Right Shoulder", "Up", "Down", "Left", "Right"]
|
||||
} # Dictionary[String, Array]
|
||||
|
||||
const SDL_DEVICE_NAMES: Dictionary = {
|
||||
DEVICE_XBOX_CONTROLLER: ["XInput", "XBox"],
|
||||
DEVICE_PLAYSTATION_CONTROLLER: ["Sony", "PS5", "PS4", "Nacon"],
|
||||
DEVICE_STEAMDECK_CONTROLLER: ["Steam"],
|
||||
DEVICE_SWITCH_CONTROLLER: ["Switch"],
|
||||
DEVICE_SWITCH_JOYCON_LEFT_CONTROLLER: ["Joy-Con (L)", "Left Joy-Con"],
|
||||
DEVICE_SWITCH_JOYCON_RIGHT_CONTROLLER: ["Joy-Con (R)", "Right Joy-Con"],
|
||||
DEVICE_SWITCH_JOYCON_COMBINED_CONTROLLER: ["Joy-Con (L/R)", "Combined Joy-Cons"],
|
||||
}
|
||||
|
||||
const JOY_BUTTON_NAMES : Dictionary = {
|
||||
JOY_BUTTON_A: "Button A",
|
||||
JOY_BUTTON_B: "Button B",
|
||||
JOY_BUTTON_X: "Button X",
|
||||
JOY_BUTTON_Y: "Button Y",
|
||||
JOY_BUTTON_LEFT_SHOULDER: "Left Shoulder",
|
||||
JOY_BUTTON_RIGHT_SHOULDER: "Right Shoulder",
|
||||
JOY_BUTTON_LEFT_STICK: "Left Stick",
|
||||
JOY_BUTTON_RIGHT_STICK: "Right Stick",
|
||||
JOY_BUTTON_START : "Button Start",
|
||||
JOY_BUTTON_GUIDE : "Button Guide",
|
||||
JOY_BUTTON_BACK : "Button Back",
|
||||
JOY_BUTTON_DPAD_UP : D_PAD_NAME + " Up",
|
||||
JOY_BUTTON_DPAD_DOWN : D_PAD_NAME + " Down",
|
||||
JOY_BUTTON_DPAD_LEFT : D_PAD_NAME + " Left",
|
||||
JOY_BUTTON_DPAD_RIGHT : D_PAD_NAME + " Right",
|
||||
JOY_BUTTON_MISC1 : "Misc",
|
||||
}
|
||||
|
||||
const JOYPAD_DPAD_NAMES : Dictionary = {
|
||||
JOY_BUTTON_DPAD_UP : D_PAD_NAME + " Up",
|
||||
JOY_BUTTON_DPAD_DOWN : D_PAD_NAME + " Down",
|
||||
JOY_BUTTON_DPAD_LEFT : D_PAD_NAME + " Left",
|
||||
JOY_BUTTON_DPAD_RIGHT : D_PAD_NAME + " Right",
|
||||
}
|
||||
|
||||
const JOY_AXIS_NAMES : Dictionary = {
|
||||
JOY_AXIS_TRIGGER_LEFT: "Left Trigger",
|
||||
JOY_AXIS_TRIGGER_RIGHT: "Right Trigger",
|
||||
}
|
||||
|
||||
const BUILT_IN_ACTION_NAME_MAP : Dictionary = {
|
||||
"ui_accept" : "Accept",
|
||||
"ui_select" : "Select",
|
||||
"ui_cancel" : "Cancel",
|
||||
"ui_focus_next" : "Focus Next",
|
||||
"ui_focus_prev" : "Focus Prev",
|
||||
"ui_left" : "Left (UI)",
|
||||
"ui_right" : "Right (UI)",
|
||||
"ui_up" : "Up (UI)",
|
||||
"ui_down" : "Down (UI)",
|
||||
"ui_page_up" : "Page Up",
|
||||
"ui_page_down" : "Page Down",
|
||||
"ui_home" : "Home",
|
||||
"ui_end" : "End",
|
||||
"ui_cut" : "Cut",
|
||||
"ui_copy" : "Copy",
|
||||
"ui_paste" : "Paste",
|
||||
"ui_undo" : "Undo",
|
||||
"ui_redo" : "Redo",
|
||||
}
|
||||
|
||||
static func has_joypad() -> bool:
|
||||
return Input.get_connected_joypads().size() > 0
|
||||
|
||||
static func is_joypad_event(event: InputEvent) -> bool:
|
||||
return event is InputEventJoypadButton or event is InputEventJoypadMotion
|
||||
|
||||
static func is_mouse_event(event: InputEvent) -> bool:
|
||||
return event is InputEventMouseButton or event is InputEventMouseMotion
|
||||
|
||||
static func get_device_name_by_id(device_id : int) -> String:
|
||||
if device_id >= 0:
|
||||
var device_name = Input.get_joy_name(device_id)
|
||||
for device_key in SDL_DEVICE_NAMES:
|
||||
for keyword in SDL_DEVICE_NAMES[device_key]:
|
||||
if device_name.containsn(keyword):
|
||||
return device_key
|
||||
return DEVICE_GENERIC
|
||||
|
||||
static func get_device_name(event: InputEvent) -> String:
|
||||
if event is InputEventJoypadButton or event is InputEventJoypadMotion:
|
||||
if event.device == -1:
|
||||
return DEVICE_GENERIC
|
||||
var device_id = event.device
|
||||
return get_device_name_by_id(device_id)
|
||||
return DEVICE_GENERIC
|
||||
|
||||
static func _display_server_supports_keycode_from_physical():
|
||||
return OS.has_feature("windows") or OS.has_feature("macos") or OS.has_feature("linux")
|
||||
|
||||
static func get_text(event : InputEvent) -> String:
|
||||
if event == null:
|
||||
return ""
|
||||
if event is InputEventJoypadButton:
|
||||
if event.button_index in JOY_BUTTON_NAMES:
|
||||
return JOY_BUTTON_NAMES[event.button_index]
|
||||
elif event is InputEventJoypadMotion:
|
||||
var full_string := ""
|
||||
var direction_string := ""
|
||||
var is_right_or_down : bool = event.axis_value > 0.0
|
||||
if event.axis in JOY_AXIS_NAMES:
|
||||
return JOY_AXIS_NAMES[event.axis]
|
||||
match(event.axis):
|
||||
JOY_AXIS_LEFT_X:
|
||||
full_string = JOYSTICK_LEFT_NAME
|
||||
direction_string = "Right" if is_right_or_down else "Left"
|
||||
JOY_AXIS_LEFT_Y:
|
||||
full_string = JOYSTICK_LEFT_NAME
|
||||
direction_string = "Down" if is_right_or_down else "Up"
|
||||
JOY_AXIS_RIGHT_X:
|
||||
full_string = JOYSTICK_RIGHT_NAME
|
||||
direction_string = "Right" if is_right_or_down else "Left"
|
||||
JOY_AXIS_RIGHT_Y:
|
||||
full_string = JOYSTICK_RIGHT_NAME
|
||||
direction_string = "Down" if is_right_or_down else "Up"
|
||||
full_string += " " + direction_string
|
||||
return full_string
|
||||
elif event is InputEventKey:
|
||||
var keycode : Key = event.get_physical_keycode()
|
||||
if keycode:
|
||||
keycode = event.get_physical_keycode_with_modifiers()
|
||||
else:
|
||||
keycode = event.get_keycode_with_modifiers()
|
||||
if _display_server_supports_keycode_from_physical():
|
||||
keycode = DisplayServer.keyboard_get_keycode_from_physical(keycode)
|
||||
return OS.get_keycode_string(keycode)
|
||||
return event.as_text()
|
||||
|
||||
static func get_device_specific_text(event : InputEvent, device_name : String = "") -> String:
|
||||
if device_name.is_empty():
|
||||
device_name = get_device_name(event)
|
||||
if event is InputEventJoypadButton:
|
||||
var joypad_button : String = ""
|
||||
if event.button_index in JOYPAD_DPAD_NAMES:
|
||||
joypad_button = JOYPAD_DPAD_NAMES[event.button_index]
|
||||
elif event.button_index < JOYPAD_BUTTON_NAME_MAP[device_name].size():
|
||||
joypad_button = JOYPAD_BUTTON_NAME_MAP[device_name][event.button_index]
|
||||
return "%s %s" % [device_name, joypad_button]
|
||||
if event is InputEventJoypadMotion:
|
||||
return "%s %s" % [device_name, get_text(event)]
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index < MOUSE_BUTTONS.size():
|
||||
var mouse_button : String = MOUSE_BUTTONS[event.button_index]
|
||||
return "%s %s" % [DEVICE_MOUSE, mouse_button]
|
||||
return get_text(event).capitalize()
|
||||
@@ -0,0 +1 @@
|
||||
uid://6xujceamar4h
|
||||
@@ -0,0 +1,27 @@
|
||||
extends Node
|
||||
|
||||
## Node for opening a pause menu when detecting a 'ui_cancel' event.
|
||||
|
||||
@export var pause_menu_packed : PackedScene
|
||||
@export var focused_viewport : Viewport
|
||||
|
||||
var pause_menu : Node
|
||||
|
||||
func _unhandled_input(event : InputEvent) -> void:
|
||||
if event.is_action_pressed("ui_cancel"):
|
||||
if pause_menu.visible: return
|
||||
if not focused_viewport:
|
||||
focused_viewport = get_viewport()
|
||||
var _initial_focus_control = focused_viewport.gui_get_focus_owner()
|
||||
pause_menu.show()
|
||||
if pause_menu is CanvasLayer:
|
||||
await pause_menu.visibility_changed
|
||||
else:
|
||||
await pause_menu.hidden
|
||||
if is_inside_tree() and _initial_focus_control:
|
||||
_initial_focus_control.grab_focus()
|
||||
|
||||
func _ready() -> void:
|
||||
pause_menu = pause_menu_packed.instantiate()
|
||||
pause_menu.hide()
|
||||
get_tree().current_scene.call_deferred("add_child", pause_menu)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cyh0d64pfygbl
|
||||
@@ -0,0 +1,20 @@
|
||||
@tool
|
||||
class_name ConfirmationOverlaidWindow
|
||||
extends OverlaidWindow
|
||||
|
||||
signal confirmed
|
||||
|
||||
@onready var confirm_button : Button = %ConfirmButton
|
||||
|
||||
@export var confirm_button_text : String = "Confirm" :
|
||||
set(value):
|
||||
confirm_button_text = value
|
||||
if update_content and is_inside_tree():
|
||||
confirm_button.text = confirm_button_text
|
||||
|
||||
func confirm():
|
||||
confirmed.emit()
|
||||
close()
|
||||
|
||||
func _on_confirm_button_pressed():
|
||||
confirm()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bgthh72eu0du
|
||||
@@ -0,0 +1,23 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://cwt4p3bufkke5"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://6gdbfi0172ji" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window.tscn" id="1_vfkm2"]
|
||||
[ext_resource type="Script" uid="uid://bgthh72eu0du" path="res://addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.gd" id="2_sw7p1"]
|
||||
|
||||
[node name="ConfirmationOverlaidWindow" instance=ExtResource("1_vfkm2")]
|
||||
script = ExtResource("2_sw7p1")
|
||||
confirm_button_text = "Confirm"
|
||||
update_content = true
|
||||
close_button_text = "Cancel"
|
||||
|
||||
[node name="MenuButtons" parent="ContentContainer/BoxContainer/MenuButtonsMargin" index="0"]
|
||||
vertical = false
|
||||
|
||||
[node name="CloseButton" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="0"]
|
||||
text = "Cancel"
|
||||
|
||||
[node name="ConfirmButton" type="Button" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="1"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Confirm"
|
||||
|
||||
[connection signal="pressed" from="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/ConfirmButton" to="." method="_on_confirm_button_pressed"]
|
||||
@@ -0,0 +1,63 @@
|
||||
@tool
|
||||
class_name OverlaidWindow
|
||||
extends WindowContainer
|
||||
|
||||
@export var pauses_game : bool = false :
|
||||
set(value):
|
||||
pauses_game = value
|
||||
if pauses_game:
|
||||
process_mode = PROCESS_MODE_ALWAYS
|
||||
else:
|
||||
process_mode = PROCESS_MODE_INHERIT
|
||||
@export var makes_mouse_visible : bool = true
|
||||
@export var exclusive : bool = true
|
||||
@export var exclusive_background_color : Color
|
||||
|
||||
var _initial_pause_state : bool = false
|
||||
var _initial_focus_mode : FocusMode = FOCUS_ALL
|
||||
var _initial_mouse_mode : Input.MouseMode
|
||||
var _initial_focus_control
|
||||
var _scene_tree : SceneTree
|
||||
var _exclusive_control_node : ColorRect
|
||||
|
||||
func close() -> void:
|
||||
if not visible: return
|
||||
_scene_tree.paused = _initial_pause_state
|
||||
Input.set_mouse_mode(_initial_mouse_mode)
|
||||
if is_instance_valid(_initial_focus_control) and _initial_focus_control.is_inside_tree():
|
||||
_initial_focus_control.focus_mode = _initial_focus_mode
|
||||
_initial_focus_control.grab_focus()
|
||||
if _exclusive_control_node:
|
||||
_exclusive_control_node.queue_free()
|
||||
super.close()
|
||||
|
||||
func _overlaid_window_setup():
|
||||
if _scene_tree:
|
||||
_initial_pause_state = _scene_tree.paused
|
||||
_initial_mouse_mode = Input.get_mouse_mode()
|
||||
_initial_focus_control = get_viewport().gui_get_focus_owner()
|
||||
if _initial_focus_control:
|
||||
_initial_focus_mode = _initial_focus_control.focus_mode
|
||||
_initial_focus_control.release_focus()
|
||||
if Engine.is_editor_hint(): return
|
||||
_scene_tree.paused = pauses_game or _initial_pause_state
|
||||
if makes_mouse_visible:
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||
if exclusive:
|
||||
_exclusive_control_node = ColorRect.new()
|
||||
_exclusive_control_node.name = self.name + "ExclusiveControl"
|
||||
_exclusive_control_node.color = exclusive_background_color
|
||||
_exclusive_control_node.set_anchors_preset(PRESET_FULL_RECT)
|
||||
add_sibling.call_deferred(_exclusive_control_node)
|
||||
await _exclusive_control_node.draw
|
||||
get_parent().move_child(_exclusive_control_node, get_index())
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
if is_visible_in_tree():
|
||||
_overlaid_window_setup()
|
||||
|
||||
func _enter_tree() -> void:
|
||||
_scene_tree = get_tree()
|
||||
if not visibility_changed.is_connected(_on_visibility_changed):
|
||||
visibility_changed.connect(_on_visibility_changed)
|
||||
_on_visibility_changed()
|
||||
@@ -0,0 +1 @@
|
||||
uid://xfugmpspqbcc
|
||||
@@ -0,0 +1,12 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://6gdbfi0172ji"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://xfugmpspqbcc" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window.gd" id="1_euyj1"]
|
||||
[ext_resource type="PackedScene" uid="uid://b2s0kvrx8r2kq" path="res://addons/maaacks_game_template/base/nodes/windows/window_container.tscn" id="2_pmk27"]
|
||||
|
||||
[node name="OverlaidWindow" instance=ExtResource("2_pmk27")]
|
||||
process_mode = 0
|
||||
script = ExtResource("1_euyj1")
|
||||
pauses_game = false
|
||||
makes_mouse_visible = true
|
||||
exclusive = true
|
||||
exclusive_background_color = Color(0, 0, 0, 0.5)
|
||||
@@ -0,0 +1,19 @@
|
||||
@tool
|
||||
class_name OverlaidWindowContainer
|
||||
extends OverlaidWindow
|
||||
|
||||
var instance : Node
|
||||
@onready var scene_container : Container = %SceneContainer
|
||||
|
||||
@export var packed_scene : PackedScene :
|
||||
set(value):
|
||||
packed_scene = value
|
||||
if is_inside_tree():
|
||||
for child in scene_container.get_children():
|
||||
child.queue_free()
|
||||
if packed_scene:
|
||||
instance = packed_scene.instantiate()
|
||||
scene_container.add_child(instance)
|
||||
|
||||
func _ready() -> void:
|
||||
packed_scene = packed_scene
|
||||
@@ -0,0 +1 @@
|
||||
uid://c6pmyo50c1tqy
|
||||
@@ -0,0 +1,13 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://crndfbb22ri4s"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://6gdbfi0172ji" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window.tscn" id="1_07348"]
|
||||
[ext_resource type="Script" uid="uid://c6pmyo50c1tqy" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window_scene_container.gd" id="2_p673y"]
|
||||
|
||||
[node name="OverlaidWindowSceneContainer" instance=ExtResource("1_07348")]
|
||||
script = ExtResource("2_p673y")
|
||||
packed_scene = null
|
||||
|
||||
[node name="SceneContainer" type="MarginContainer" parent="ContentContainer/BoxContainer/BodyMargin" index="1"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
@@ -0,0 +1,80 @@
|
||||
@tool
|
||||
class_name WindowContainer
|
||||
extends PanelContainer
|
||||
|
||||
signal closed
|
||||
signal opened
|
||||
|
||||
@export var ui_cancel_closes : bool = true
|
||||
|
||||
@export_group("Content")
|
||||
@export var update_content : bool = false
|
||||
@export_multiline var text : String :
|
||||
set(value):
|
||||
text = value
|
||||
if update_content and is_inside_tree():
|
||||
description_label.text = text
|
||||
|
||||
@export var close_button_text : String = "Close" :
|
||||
set(value):
|
||||
close_button_text = value
|
||||
if update_content and is_inside_tree():
|
||||
close_button.text = close_button_text
|
||||
|
||||
@export_subgroup("Title")
|
||||
@export var title : String = "Menu" :
|
||||
set(value):
|
||||
title = value
|
||||
if update_content and is_inside_tree():
|
||||
title_label.text = title
|
||||
|
||||
@export_range(0, 1000, 1) var title_font_size : int = 16 :
|
||||
set(value):
|
||||
title_font_size = value
|
||||
if update_content and is_inside_tree():
|
||||
title_label.set("theme_override_font_sizes/font_size", title_font_size)
|
||||
|
||||
@export var title_visible : bool = true :
|
||||
set(value):
|
||||
title_visible = value
|
||||
if update_content and is_inside_tree():
|
||||
title_margin.visible = title_visible
|
||||
|
||||
@onready var content_container : Container = %ContentContainer
|
||||
@onready var title_label : Label = %TitleLabel
|
||||
@onready var title_margin : MarginContainer = %TitleMargin
|
||||
@onready var description_label : RichTextLabel = %DescriptionLabel
|
||||
@onready var close_button : Button = %CloseButton
|
||||
@onready var menu_buttons : BoxContainer = %MenuButtons
|
||||
|
||||
func _ready() -> void:
|
||||
update_content = update_content
|
||||
text = text
|
||||
close_button_text = close_button_text
|
||||
title = title
|
||||
title_font_size = title_font_size
|
||||
title_visible = title_visible
|
||||
|
||||
func close() -> void:
|
||||
if not visible: return
|
||||
hide()
|
||||
closed.emit()
|
||||
|
||||
func _handle_cancel_input() -> void:
|
||||
close()
|
||||
|
||||
func _unhandled_input(event : InputEvent) -> void:
|
||||
if visible and event.is_action_released("ui_cancel") and ui_cancel_closes:
|
||||
_handle_cancel_input()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
func _on_close_button_pressed() -> void:
|
||||
close()
|
||||
|
||||
func show() -> void:
|
||||
super.show()
|
||||
opened.emit()
|
||||
|
||||
func _exit_tree():
|
||||
if Engine.is_editor_hint(): return
|
||||
close()
|
||||
@@ -0,0 +1 @@
|
||||
uid://b3onujul5qho1
|
||||
@@ -0,0 +1,90 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://b2s0kvrx8r2kq"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b3onujul5qho1" path="res://addons/maaacks_game_template/base/nodes/windows/window_container.gd" id="1_te2s1"]
|
||||
[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd" id="2_xihbi"]
|
||||
|
||||
[node name="WindowContainer" type="PanelContainer"]
|
||||
process_mode = 3
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -80.0
|
||||
offset_top = -50.0
|
||||
offset_right = 80.0
|
||||
offset_bottom = 50.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
script = ExtResource("1_te2s1")
|
||||
|
||||
[node name="ContentContainer" type="MarginContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 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="BoxContainer" type="BoxContainer" parent="ContentContainer"]
|
||||
layout_mode = 2
|
||||
vertical = true
|
||||
|
||||
[node name="TitleMargin" type="MarginContainer" parent="ContentContainer/BoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = -14
|
||||
theme_override_constants/margin_top = -14
|
||||
theme_override_constants/margin_right = -14
|
||||
theme_override_constants/margin_bottom = 8
|
||||
|
||||
[node name="BoxContainer" type="BoxContainer" parent="ContentContainer/BoxContainer/TitleMargin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 0
|
||||
vertical = true
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="ContentContainer/BoxContainer/TitleMargin/BoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 16
|
||||
text = "Menu"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="HSeparator" type="HSeparator" parent="ContentContainer/BoxContainer/TitleMargin/BoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="BodyMargin" type="MarginContainer" parent="ContentContainer/BoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="DescriptionLabel" type="RichTextLabel" parent="ContentContainer/BoxContainer/BodyMargin"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
bbcode_enabled = true
|
||||
fit_content = true
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="MenuButtonsMargin" type="MarginContainer" parent="ContentContainer/BoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_top = 8
|
||||
|
||||
[node name="MenuButtons" type="BoxContainer" parent="ContentContainer/BoxContainer/MenuButtonsMargin"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(128, 0)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/separation = 16
|
||||
alignment = 1
|
||||
vertical = true
|
||||
script = ExtResource("2_xihbi")
|
||||
|
||||
[node name="CloseButton" type="Button" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Close"
|
||||
|
||||
[connection signal="pressed" from="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/CloseButton" to="." method="_on_close_button_pressed"]
|
||||
@@ -0,0 +1,104 @@
|
||||
keys,en,fr
|
||||
|
||||
___ MAIN MENU,,
|
||||
|
||||
Title,Title,Titre
|
||||
Subtitle,Subtitle,Sous-titre
|
||||
New Game,New Game,
|
||||
Continue,Continue,
|
||||
Play,Play,Jouer
|
||||
Level Select,Level Select,
|
||||
Options,Options,Options
|
||||
Credits,Credits,Crédits
|
||||
Exit,Exit,Quitter
|
||||
"Are you sure you want to start a new game?
|
||||
|
||||
All progress in the current game will be lost.","Are you sure you want to start a new game?
|
||||
|
||||
All progress in the current game will be lost.",
|
||||
|
||||
___ LOADING SCREEN,,
|
||||
Loading...,Loading...,Chargement...
|
||||
Still Loading...,Still Loading...,
|
||||
Loading Complete!,Loading Complete!,
|
||||
Any Moment Now...,Any Moment Now...,
|
||||
|
||||
___ DIALOGS IN GAME,,
|
||||
|
||||
Win,Win,
|
||||
Lose,Lose,
|
||||
Tutorial,Tutorial,
|
||||
Change Level Color: ,Change Level Color: ,
|
||||
Close,Close,
|
||||
|
||||
Level complete!,Level complete!,
|
||||
You lost...,You lost...,Vous avez perdu...
|
||||
You won!,You won!,Vous avez gagné !
|
||||
Thanks for playing!,Thanks for playing!,Merci d'avoir joué !
|
||||
|
||||
Exit Game,Exit Game,Quitter le jeu
|
||||
Main Menu,Main Menu,Menu principal
|
||||
Restart,Restart,Recommencer
|
||||
Continue,Continue,Continuer
|
||||
Menu,Menu,Menu
|
||||
|
||||
Please Confirm...,Please Confirm...,Veuillez confirmer...
|
||||
Go back to main menu?,Go back to main menu?,Retourner au menu principal ?
|
||||
Quit the game?,Quit the game?,Quitter le jeu ?
|
||||
Cancel,Cancel,Annuler
|
||||
OK,OK,OK
|
||||
|
||||
__ TUTORIALS,,
|
||||
|
||||
"Click the Win button to progress.
|
||||
Click the Lose button to try again.","Click the Win button to progress.
|
||||
Click the Lose button to try again.",
|
||||
"Progress is saved.
|
||||
Pressing Continue from the main menu will load the last level played.","Progress is saved.
|
||||
Pressing Continue from the main menu will load the last level played.",
|
||||
"The color picker at the bottom-right updates the level state. This change persists until the game is reset.
|
||||
|
||||
The label at the bottom-center displays the current input action detected, if any are setup for the project.","The color picker at the bottom-right updates the level state. This change persists until the game is reset.
|
||||
|
||||
The label at the bottom-center displays the current input action detected, if any are setup for the project.",
|
||||
|
||||
___ OPTIONS MENU,,
|
||||
|
||||
Controls,Controls,Contrôles
|
||||
Mouse Sensitivity :,Mouse Sensitivity :,Sensibilité souris :
|
||||
Actions & Inputs,Actions & Inputs,Actions et contrôles
|
||||
Add,Add,Ajouter
|
||||
Remove,Remove,Enlever
|
||||
Assign Key for {action},Assign Key for {action},Choisir le contrôle pour {action}
|
||||
Listening for input...,Listening for input...,Appuyez sur un bouton...
|
||||
Press again to confirm...,Press again to confirm...,Appuyez encore pour confirmer...
|
||||
Focus here to assign inputs.,Focus here to assign inputs.,Mettez le focus ici pour choisir le contrôle.
|
||||
Already Assigned,Already Assigned,Déjà utilisé
|
||||
{key} already assigned to {action}.,{key} already assigned to {action}.,{key} est déjà utilisé pour {action}.
|
||||
Remove Key for {action},Remove Key for {action},Supprimer le contrôle pour {action}
|
||||
Are you sure you want to remove {key} from {action}?,Are you sure you want to remove {key} from {action}?,Êtes-vous sûr de vouloir supprimer {key} pour {action} ?
|
||||
Reset,Reset,Réinitialiser
|
||||
|
||||
Audio,Audio,Audio
|
||||
Master :,Master :,Principal :
|
||||
Music :,Music :,Musique :
|
||||
SFX :,SFX :,Effets :
|
||||
Mute :,Mute :,Silencieux :
|
||||
|
||||
Video,Video,Vidéo
|
||||
Fullscreen :,Fullscreen :,Plein écran :
|
||||
Resolution :,Resolution :,Résolution :
|
||||
Anti-Aliasing :,Anti-Aliasing :,Anticrénelage :
|
||||
Disabled (Fastest),Disabled (Fastest),Désactivé (Plus rapide)
|
||||
8x (Slowest),8x (Slowest),8x (Plus lent)
|
||||
Camera Shake :,Camera Shake :,Secousse Caméra :
|
||||
Normal,Normal,Normale
|
||||
Reduced,Reduced,Réduite
|
||||
Minimal,Minimal,Minimum
|
||||
None,None,Aucune
|
||||
|
||||
Game,Game,Jeu
|
||||
Reset Game :,Reset Game :,Réinitialiser le jeu :
|
||||
Do you want to reset your game data?,Do you want to reset your game data?,Voulez-vous réinitialiser votre partie ?
|
||||
|
||||
Back,Back,Retour
|
||||
|
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="csv_translation"
|
||||
type="Translation"
|
||||
uid="uid://i6ihop1vp2ei"
|
||||
|
||||
[deps]
|
||||
|
||||
files=["res://addons/maaacks_game_template/base/translations/menus_translations.en.translation", "res://addons/maaacks_game_template/base/translations/menus_translations.fr.translation"]
|
||||
|
||||
source_file="res://addons/maaacks_game_template/base/translations/menus_translations.csv"
|
||||
dest_files=["res://addons/maaacks_game_template/base/translations/menus_translations.en.translation", "res://addons/maaacks_game_template/base/translations/menus_translations.fr.translation"]
|
||||
|
||||
[params]
|
||||
|
||||
compress=true
|
||||
delimiter=0
|
||||
unescape_keys=false
|
||||
unescape_translations=true
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user