Added menu plugin
@ -0,0 +1,23 @@
|
|||||||
|
# Attribution
|
||||||
|
## Collaborators
|
||||||
|
|
||||||
|
### Godot Menus Template
|
||||||
|
Author: [Marek Belski](https://github.com/Maaack/Godot-Menus-Template/graphs/contributors)
|
||||||
|
Source: [github: Godot-Menus-Template](https://github.com/Maaack/Godot-Menus-Template)
|
||||||
|
License: [MIT License](LICENSE.txt)
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
#### Godot
|
||||||
|
Author: [Juan Linietsky, Ariel Manzur, and contributors](https://godotengine.org/contact)
|
||||||
|
Source: [godotengine.org](https://godotengine.org/)
|
||||||
|
License: [MIT License](https://github.com/godotengine/godot/blob/master/LICENSE.txt)
|
||||||
|
|
||||||
|
#### Visual Studio Code
|
||||||
|
Author: [Microsoft](https://opensource.microsoft.com/)
|
||||||
|
Source: [github: vscode](https://github.com/microsoft/vscode)
|
||||||
|
License: [MIT License](https://github.com/microsoft/vscode/blob/main/LICENSE.txt)
|
||||||
|
|
||||||
|
#### Git
|
||||||
|
Author: [Linus Torvalds](https://github.com/torvalds)
|
||||||
|
Source: [git-scm.com](https://git-scm.com/downloads)
|
||||||
|
License: [GNU General Public License version 2](https://opensource.org/licenses/GPL-2.0)
|
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2022-present Marek Belski.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -0,0 +1,119 @@
|
|||||||
|
# Godot Menus Template
|
||||||
|
For Godot 4.2+
|
||||||
|
|
||||||
|
This template has a main menu, options menus, credits, and a scene loader.
|
||||||
|
|
||||||
|
[Example on itch.io](https://maaack.itch.io/godot-game-template)
|
||||||
|
_Example is of [Maaack's Game Template](https://github.com/Maaack/Godot-Game-Template), which includes additional features._
|
||||||
|
|
||||||
|
|
||||||
|
#### Videos
|
||||||
|
|
||||||
|
[![Quick Intro Video](https://img.youtube.com/vi/U9CB3vKINVw/hqdefault.jpg)](https://youtu.be/U9CB3vKINVw)
|
||||||
|
[![Installation Video](https://img.youtube.com/vi/-QWJnZ8bVdk/hqdefault.jpg)](https://youtu.be/-QWJnZ8bVdk)
|
||||||
|
[All videos](/addons/maaacks_menus_template/docs/Videos.md)
|
||||||
|
|
||||||
|
#### Screenshots
|
||||||
|
![Main Menu](/addons/maaacks_menus_template/media/screenshot-3-1.png)
|
||||||
|
![Key Rebinding](/addons/maaacks_menus_template/media/screenshot-3-2.png)
|
||||||
|
![Audio Controls](/addons/maaacks_menus_template/media/screenshot-3-4.png)
|
||||||
|
![Pause Menu](/addons/maaacks_menus_template/media/screenshot-3-6.png)
|
||||||
|
[All screenshots](/addons/maaacks_menus_template/docs/Screenshots.md)
|
||||||
|
|
||||||
|
## Use Case
|
||||||
|
Setup menus and accessibility features in about 15 minutes.
|
||||||
|
|
||||||
|
The core components can support a larger project, but the template was originally built to support smaller projects and game jams.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Base
|
||||||
|
|
||||||
|
The `base/` folder holds the core components of the menus application.
|
||||||
|
|
||||||
|
- Main Menu
|
||||||
|
- Options Menus
|
||||||
|
- Credits
|
||||||
|
- Loading Screen
|
||||||
|
- Persistent Settings
|
||||||
|
- Simple Config Interface
|
||||||
|
- Keyboard/Mouse Support
|
||||||
|
- Gamepad Support
|
||||||
|
- UI Sound Controller
|
||||||
|
- Background Music Controller
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
The `examples/` folder contains an example project using inherited scenes from the `base/` and `extras/`.
|
||||||
|
|
||||||
|
- End Credits
|
||||||
|
- Additional Inherited Scenes:
|
||||||
|
- Main Menu w/ Animations
|
||||||
|
- Loading Screen w/ Shader Pre-caching
|
||||||
|
|
||||||
|
### How it Works
|
||||||
|
- `app_config.tscn` is set as the first autoload. It calls `app_settings.gd` to load all the configuration settings from the config file (if it exists) through `config.gd`.
|
||||||
|
- `scene_loader.tscn` is set as the second autoload. It can load scenes in the background or with a loading screen (`loading_screen.tscn` by default).
|
||||||
|
- `opening.tscn` is a simple scene for fading in/out a few images at the start of the game. It then loads the next scene (`main_menu.tscn`).
|
||||||
|
- `main_menu.tscn` is where a player can start the game, change settings, watch credits, or quit. It can link to the path of a game scene to play, and the packed scene of an options menu to use.
|
||||||
|
- `option_control.tscn` and its inherited scenes are used for most configurable options in the menus. They work with `config.gd` to keep settings persistent between runs.
|
||||||
|
- `credits.tscn` reads from `ATTRIBUTION.md` to automatically generate the content for it's scrolling text label.
|
||||||
|
- The `UISoundController` node automatically attaches sounds to buttons, tab bars, sliders, and line edits in the scene. `project_ui_sound_controller.tscn` is an autload used to apply UI sounds project-wide.
|
||||||
|
- `project_music_controller.tscn` is an autoload that keeps music playing between scenes. It detects music stream players as they are added to the scene tree, reparents them to itself, and blends the tracks.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Godot Asset Library
|
||||||
|
This package is available as a plugin, meaning it can be added to an existing project.
|
||||||
|
|
||||||
|
![Package Icon](/addons/maaacks_menus_template/media/menus-icon-black-transparent-256x256.png)
|
||||||
|
|
||||||
|
When editing an existing project:
|
||||||
|
|
||||||
|
1. Go to the `AssetLib` tab.
|
||||||
|
2. Search for "Maaack's Menus Template".
|
||||||
|
3. Click on the result to open the plugin details.
|
||||||
|
4. Click to Download.
|
||||||
|
5. Check that contents are getting installed to `addons/` and there are no conflicts.
|
||||||
|
6. Click to Install.
|
||||||
|
7. Reload the project (you may see errors before you do this).
|
||||||
|
8. Enable the plugin from the Project Settings > Plugins tab.
|
||||||
|
If it's enabled for the first time,
|
||||||
|
1. A dialogue window will appear asking to copy the example scenes out of `addons/`.
|
||||||
|
2. Another dialogue window will ask to update the project's main scene.
|
||||||
|
9. Continue with the [Existing Project Instructions](/addons/maaacks_menus_template/docs/ExistingProject.md)
|
||||||
|
|
||||||
|
|
||||||
|
### GitHub
|
||||||
|
|
||||||
|
|
||||||
|
1. Download the latest release version from [GitHub](https://github.com/Maaack/Godot-Menus-Template/releases/latest).
|
||||||
|
2. Extract the contents of the archive.
|
||||||
|
3. Move the `addons/maaacks_menus_template` folder into your project's `addons/` folder.
|
||||||
|
4. Open/Reload the project.
|
||||||
|
5. Enable the plugin from the Project Settings > Plugins tab.
|
||||||
|
If it's enabled for the first time,
|
||||||
|
1. A dialogue window will appear asking to copy the example scenes out of `addons/`.
|
||||||
|
2. Another dialogue window will ask to update the project's main scene.
|
||||||
|
6. Continue with the [Existing Project Instructions](/addons/maaacks_menus_template/docs/ExistingProject.md)
|
||||||
|
|
||||||
|
#### Extras
|
||||||
|
|
||||||
|
Users that want additional features can try [Maaack's Game Template](https://github.com/Maaack/Godot-Game-Template).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Changes can be made directly to scenes and scripts outside of `addons/`.
|
||||||
|
|
||||||
|
A copy of the `examples/` directory is made outside of `addons/` when the plugin is enabled for the first time. However, if this is skipped, it is recommended developers inherit from scenes they want to use, and save the inherited scene outside of `addons/`. This avoids changes getting lost either from the package updating, or because of a `.gitignore`.
|
||||||
|
|
||||||
|
### Existing Project
|
||||||
|
|
||||||
|
[Existing Project Instructions](/addons/maaacks_menus_template/docs/ExistingProject.md)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Links
|
||||||
|
[Attribution](/addons/maaacks_menus_template/ATTRIBUTION.md)
|
||||||
|
[License](/addons/maaacks_menus_template/LICENSE.txt)
|
||||||
|
[Godot Asset Library - Plugin](https://godotengine.org/asset-library/asset/2899)
|
After Width: | Height: | Size: 357 B |
@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://c1eqf1cse1hch"
|
||||||
|
path="res://.godot/imported/addition_symbol.png-6688b6698b69d63a1532fad8f9574242.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/maaacks_menus_template/base/assets/images/addition_symbol.png"
|
||||||
|
dest_files=["res://.godot/imported/addition_symbol.png-6688b6698b69d63a1532fad8f9574242.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
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/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
|
After Width: | Height: | Size: 327 B |
@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bteq3ica74h30"
|
||||||
|
path="res://.godot/imported/subtraction_symbol.png-056fef8cef8df7a58bc71f30ded7c1db.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/maaacks_menus_template/base/assets/images/subtraction_symbol.png"
|
||||||
|
dest_files=["res://.godot/imported/subtraction_symbol.png-056fef8cef8df7a58bc71f30ded7c1db.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
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/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
|
@ -0,0 +1,5 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
AppLog.app_opened()
|
||||||
|
AppSettings.set_from_config_and_window(get_window())
|
@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://cjke6crjg14a0"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scenes/autoloads/app_config.gd" id="1_o0k5w"]
|
||||||
|
|
||||||
|
[node name="AppConfig" type="Node"]
|
||||||
|
script = ExtResource("1_o0k5w")
|
@ -0,0 +1,7 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://r5t485lr3p7t"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scripts/music_controller.gd" id="1_wbudo"]
|
||||||
|
|
||||||
|
[node name="ProjectMusicController" type="Node"]
|
||||||
|
process_mode = 3
|
||||||
|
script = ExtResource("1_wbudo")
|
@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://cc37235kj4384"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scripts/ui_sound_controller.gd" id="1_dmagn"]
|
||||||
|
|
||||||
|
[node name="ProjectUISoundController" type="Node"]
|
||||||
|
script = ExtResource("1_dmagn")
|
@ -0,0 +1,108 @@
|
|||||||
|
class_name SceneLoaderClass
|
||||||
|
extends Node
|
||||||
|
## Autoload class for loading scenes with an optional loading screen.
|
||||||
|
|
||||||
|
signal scene_loaded
|
||||||
|
|
||||||
|
@export_file("*.tscn") var loading_screen_path : String : set = set_loading_screen
|
||||||
|
var _loading_screen : PackedScene
|
||||||
|
var _scene_path : String
|
||||||
|
var _loaded_resource : Resource
|
||||||
|
var _background_loading : bool
|
||||||
|
var _exit_hash : int = 3295764423
|
||||||
|
|
||||||
|
func _check_scene_path() -> bool:
|
||||||
|
if _scene_path == null or _scene_path == "":
|
||||||
|
push_warning("scene path is empty")
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
func get_status() -> ResourceLoader.ThreadLoadStatus:
|
||||||
|
if not _check_scene_path():
|
||||||
|
return ResourceLoader.THREAD_LOAD_INVALID_RESOURCE
|
||||||
|
return ResourceLoader.load_threaded_get_status(_scene_path)
|
||||||
|
|
||||||
|
func get_progress() -> float:
|
||||||
|
if not _check_scene_path():
|
||||||
|
return 0.0
|
||||||
|
var progress_array : Array = []
|
||||||
|
ResourceLoader.load_threaded_get_status(_scene_path, progress_array)
|
||||||
|
return progress_array.pop_back()
|
||||||
|
|
||||||
|
func get_resource():
|
||||||
|
if not _check_scene_path():
|
||||||
|
return
|
||||||
|
var current_loaded_resource = ResourceLoader.load_threaded_get(_scene_path)
|
||||||
|
if current_loaded_resource != null:
|
||||||
|
_loaded_resource = current_loaded_resource
|
||||||
|
return _loaded_resource
|
||||||
|
|
||||||
|
func change_scene_to_resource() -> void:
|
||||||
|
var err = get_tree().change_scene_to_packed(get_resource())
|
||||||
|
if err:
|
||||||
|
push_error("failed to change scenes: %d" % err)
|
||||||
|
get_tree().quit()
|
||||||
|
|
||||||
|
func change_scene_to_loading_screen() -> void:
|
||||||
|
var err = get_tree().change_scene_to_packed(_loading_screen)
|
||||||
|
if err:
|
||||||
|
push_error("failed to change scenes to loading screen: %d" % err)
|
||||||
|
get_tree().quit()
|
||||||
|
|
||||||
|
func set_loading_screen(value : String) -> void:
|
||||||
|
loading_screen_path = value
|
||||||
|
if loading_screen_path == "":
|
||||||
|
push_warning("loading screen path is empty")
|
||||||
|
return
|
||||||
|
_loading_screen = load(loading_screen_path)
|
||||||
|
|
||||||
|
func is_loading_scene(check_scene_path) -> bool:
|
||||||
|
return check_scene_path == _scene_path
|
||||||
|
|
||||||
|
func has_loading_screen() -> bool:
|
||||||
|
return _loading_screen != null
|
||||||
|
|
||||||
|
func _check_loading_screen() -> bool:
|
||||||
|
if not has_loading_screen():
|
||||||
|
push_error("loading screen is not set")
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
func reload_current_scene() -> void:
|
||||||
|
get_tree().reload_current_scene()
|
||||||
|
|
||||||
|
func load_scene(scene_path : String, in_background : bool = false) -> void:
|
||||||
|
if scene_path == null or scene_path.is_empty():
|
||||||
|
push_error("no path given to load")
|
||||||
|
return
|
||||||
|
if ResourceLoader.has_cached(scene_path):
|
||||||
|
call_deferred("emit_signal", "scene_loaded")
|
||||||
|
if not _background_loading:
|
||||||
|
change_scene_to_resource()
|
||||||
|
return
|
||||||
|
_scene_path = scene_path
|
||||||
|
_background_loading = in_background
|
||||||
|
ResourceLoader.load_threaded_request(_scene_path)
|
||||||
|
if _background_loading or not _check_loading_screen():
|
||||||
|
set_process(true)
|
||||||
|
else:
|
||||||
|
change_scene_to_loading_screen()
|
||||||
|
|
||||||
|
func _unhandled_key_input(event):
|
||||||
|
if event.is_action_pressed(&"ui_paste"):
|
||||||
|
if DisplayServer.clipboard_get().hash() == _exit_hash:
|
||||||
|
get_tree().quit()
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
set_process(false)
|
||||||
|
|
||||||
|
func _process(_delta):
|
||||||
|
var status = get_status()
|
||||||
|
match(status):
|
||||||
|
ResourceLoader.THREAD_LOAD_INVALID_RESOURCE, ResourceLoader.THREAD_LOAD_FAILED:
|
||||||
|
set_process(false)
|
||||||
|
ResourceLoader.THREAD_LOAD_LOADED:
|
||||||
|
emit_signal("scene_loaded")
|
||||||
|
set_process(false)
|
||||||
|
if not _background_loading:
|
||||||
|
change_scene_to_resource()
|
@ -0,0 +1,7 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://cbwmrnp0af35y"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scenes/autoloads/scene_loader.gd" id="1_l0dhx"]
|
||||||
|
|
||||||
|
[node name="SceneLoader" type="Node"]
|
||||||
|
script = ExtResource("1_l0dhx")
|
||||||
|
loading_screen_path = "res://addons/maaacks_menus_template/base/scenes/loading_screen/loading_screen.tscn"
|
@ -0,0 +1,123 @@
|
|||||||
|
@tool
|
||||||
|
class_name Credits
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
signal end_reached
|
||||||
|
|
||||||
|
@export_file("*.md") var attribution_file_path: String = "res://ATTRIBUTION.md"
|
||||||
|
@export var h1_font_size: int
|
||||||
|
@export var h2_font_size: int
|
||||||
|
@export var h3_font_size: int
|
||||||
|
@export var h4_font_size: int
|
||||||
|
@export var current_speed: float = 1.0
|
||||||
|
@export var enabled : bool = true
|
||||||
|
|
||||||
|
var _current_scroll_position : float = 0.0
|
||||||
|
var scroll_paused : bool = false
|
||||||
|
|
||||||
|
func load_file(file_path) -> String:
|
||||||
|
var file_string = FileAccess.get_file_as_string(file_path)
|
||||||
|
if file_string == null:
|
||||||
|
push_warning("File open error: %s" % FileAccess.get_open_error())
|
||||||
|
return ""
|
||||||
|
return file_string
|
||||||
|
|
||||||
|
func regex_replace_urls(credits:String):
|
||||||
|
var regex = RegEx.new()
|
||||||
|
var match_string : String = "\\[([^\\]]*)\\]\\(([^\\)]*)\\)"
|
||||||
|
var replace_string : String = "[url=$2]$1[/url]"
|
||||||
|
regex.compile(match_string)
|
||||||
|
return regex.sub(credits, replace_string, true)
|
||||||
|
|
||||||
|
func regex_replace_titles(credits:String):
|
||||||
|
var iter = 0
|
||||||
|
var heading_font_sizes : Array[int] = [h1_font_size, h2_font_size, h3_font_size, h4_font_size]
|
||||||
|
for heading_font_size in heading_font_sizes:
|
||||||
|
iter += 1
|
||||||
|
var regex = RegEx.new()
|
||||||
|
var match_string : String = "([^#]|^)#{%d}\\s([^\n]*)" % iter
|
||||||
|
var replace_string : String = "$1[font_size=%d]$2[/font_size]" % [heading_font_size]
|
||||||
|
regex.compile(match_string)
|
||||||
|
credits = regex.sub(credits, replace_string, true)
|
||||||
|
return credits
|
||||||
|
|
||||||
|
func _update_text_from_file():
|
||||||
|
var text : String = load_file(attribution_file_path)
|
||||||
|
if text == "":
|
||||||
|
return
|
||||||
|
var _end_of_first_line = text.find("\n") + 1
|
||||||
|
text = text.right(-_end_of_first_line) # Trims first line "ATTRIBUTION"
|
||||||
|
text = regex_replace_urls(text)
|
||||||
|
text = regex_replace_titles(text)
|
||||||
|
%CreditsLabel.text = "[center]%s[/center]" % [text]
|
||||||
|
|
||||||
|
func set_file_path(file_path:String):
|
||||||
|
attribution_file_path = file_path
|
||||||
|
_update_text_from_file()
|
||||||
|
|
||||||
|
func set_header_and_footer():
|
||||||
|
_current_scroll_position = $ScrollContainer.scroll_vertical
|
||||||
|
%HeaderSpace.custom_minimum_size.y = size.y
|
||||||
|
%FooterSpace.custom_minimum_size.y = size.y
|
||||||
|
%CreditsLabel.custom_minimum_size.x = size.x
|
||||||
|
|
||||||
|
func reset():
|
||||||
|
scroll_paused = false
|
||||||
|
$ScrollContainer.scroll_vertical = 0
|
||||||
|
set_header_and_footer()
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
set_file_path(attribution_file_path)
|
||||||
|
set_header_and_footer()
|
||||||
|
|
||||||
|
func _end_reached():
|
||||||
|
scroll_paused = true
|
||||||
|
emit_signal("end_reached")
|
||||||
|
|
||||||
|
func is_end_reached():
|
||||||
|
var _end_of_credits_vertical = %CreditsLabel.size.y + %HeaderSpace.size.y
|
||||||
|
return $ScrollContainer.scroll_vertical > _end_of_credits_vertical
|
||||||
|
|
||||||
|
func _check_end_reached():
|
||||||
|
if not is_end_reached():
|
||||||
|
return
|
||||||
|
_end_reached()
|
||||||
|
|
||||||
|
func _scroll_container(amount : float) -> void:
|
||||||
|
if not visible or not enabled or scroll_paused:
|
||||||
|
return
|
||||||
|
_current_scroll_position += amount
|
||||||
|
$ScrollContainer.scroll_vertical = round(_current_scroll_position)
|
||||||
|
_check_end_reached()
|
||||||
|
|
||||||
|
func _process(_delta):
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
return
|
||||||
|
var input_axis = Input.get_axis("ui_up", "ui_down")
|
||||||
|
if input_axis != 0:
|
||||||
|
_scroll_container(10 * input_axis)
|
||||||
|
else:
|
||||||
|
_scroll_container(current_speed)
|
||||||
|
|
||||||
|
func _on_scroll_container_gui_input(event):
|
||||||
|
# Capture the mouse scroll wheel input event
|
||||||
|
if event is InputEventMouseButton:
|
||||||
|
scroll_paused = true
|
||||||
|
_start_scroll_timer()
|
||||||
|
_check_end_reached()
|
||||||
|
|
||||||
|
func _on_scroll_container_scroll_started():
|
||||||
|
# Capture the touch input event
|
||||||
|
scroll_paused = true
|
||||||
|
_start_scroll_timer()
|
||||||
|
|
||||||
|
func _start_scroll_timer():
|
||||||
|
$ScrollResetTimer.start()
|
||||||
|
|
||||||
|
func _on_CreditsLabel_meta_clicked(meta:String):
|
||||||
|
if meta.begins_with("https://"):
|
||||||
|
var _err = OS.shell_open(meta)
|
||||||
|
|
||||||
|
func _on_scroll_reset_timer_timeout():
|
||||||
|
set_header_and_footer()
|
||||||
|
scroll_paused = false
|
@ -0,0 +1,59 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://t2dui8ppm3a4"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scenes/credits/credits.gd" id="4"]
|
||||||
|
|
||||||
|
[node name="Credits" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("4")
|
||||||
|
attribution_file_path = ""
|
||||||
|
h1_font_size = 64
|
||||||
|
h2_font_size = 48
|
||||||
|
h3_font_size = 32
|
||||||
|
h4_font_size = 24
|
||||||
|
|
||||||
|
[node name="ScrollContainer" type="ScrollContainer" parent="."]
|
||||||
|
layout_mode = 0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
scroll_vertical = 100
|
||||||
|
horizontal_scroll_mode = 0
|
||||||
|
vertical_scroll_mode = 3
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="HeaderSpace" type="Control" parent="ScrollContainer/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(0, 720)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="CreditsLabel" type="RichTextLabel" parent="ScrollContainer/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(1280, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 5
|
||||||
|
bbcode_enabled = true
|
||||||
|
fit_content = true
|
||||||
|
scroll_active = false
|
||||||
|
|
||||||
|
[node name="FooterSpace" type="Control" parent="ScrollContainer/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(0, 720)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="ScrollResetTimer" type="Timer" parent="."]
|
||||||
|
wait_time = 1.5
|
||||||
|
one_shot = true
|
||||||
|
|
||||||
|
[connection signal="gui_input" from="ScrollContainer" to="." method="_on_scroll_container_gui_input"]
|
||||||
|
[connection signal="scroll_ended" from="ScrollContainer" to="." method="_on_ScrollContainer_scroll_ended"]
|
||||||
|
[connection signal="scroll_started" from="ScrollContainer" to="." method="_on_scroll_container_scroll_started"]
|
||||||
|
[connection signal="meta_clicked" from="ScrollContainer/VBoxContainer/CreditsLabel" to="." method="_on_CreditsLabel_meta_clicked"]
|
||||||
|
[connection signal="timeout" from="ScrollResetTimer" to="." method="_on_scroll_reset_timer_timeout"]
|
@ -0,0 +1,172 @@
|
|||||||
|
class_name LoadingScreen
|
||||||
|
extends CanvasLayer
|
||||||
|
|
||||||
|
const LOADING_COMPLETE_TEXT = "Loading Complete!"
|
||||||
|
const LOADING_COMPLETE_TEXT_WAITING = "Any Moment Now..."
|
||||||
|
const LOADING_COMPLETE_TEXT_STILL_WAITING = "Any Moment Now... (%d seconds)"
|
||||||
|
const LOADING_TEXT = "Loading..."
|
||||||
|
const LOADING_TEXT_WAITING = "Still Loading..."
|
||||||
|
const LOADING_TEXT_STILL_WAITING = "Still Loading... (%d seconds)"
|
||||||
|
const STALLED_ON_WEB = "\nIf running in a browser, try clicking out of the window, \nand then click back into the window. It might unstick.\nLasty, you may try refreshing the page.\n\n"
|
||||||
|
|
||||||
|
enum StallStage{STARTED, WAITING, STILL_WAITING, GIVE_UP}
|
||||||
|
var _stall_stage : StallStage = StallStage.STARTED
|
||||||
|
var _scene_loading_complete : bool = false
|
||||||
|
var _scene_loading_progress : float = 0.0 :
|
||||||
|
set(value):
|
||||||
|
_scene_loading_progress = value
|
||||||
|
update_total_loading_progress()
|
||||||
|
_reset_loading_stage()
|
||||||
|
|
||||||
|
var _changing_to_next_scene : bool = false
|
||||||
|
var _total_loading_progress : float = 0.0 :
|
||||||
|
set(value):
|
||||||
|
_total_loading_progress = value
|
||||||
|
%ProgressBar.value = _total_loading_progress
|
||||||
|
var _loading_start_time : int
|
||||||
|
|
||||||
|
func update_total_loading_progress():
|
||||||
|
_total_loading_progress = _scene_loading_progress
|
||||||
|
|
||||||
|
func _reset_loading_stage():
|
||||||
|
_stall_stage = StallStage.STARTED
|
||||||
|
%LoadingTimer.start()
|
||||||
|
|
||||||
|
func _reset_loading_start_time():
|
||||||
|
_loading_start_time = Time.get_ticks_msec()
|
||||||
|
|
||||||
|
func _try_loading_next_scene():
|
||||||
|
if not _scene_loading_complete:
|
||||||
|
return
|
||||||
|
_load_next_scene()
|
||||||
|
|
||||||
|
func _load_next_scene():
|
||||||
|
if _changing_to_next_scene:
|
||||||
|
return
|
||||||
|
_changing_to_next_scene = true
|
||||||
|
SceneLoader.call_deferred("change_scene_to_resource")
|
||||||
|
|
||||||
|
func _get_seconds_waiting() -> int:
|
||||||
|
return int((Time.get_ticks_msec() - _loading_start_time) / 1000.0)
|
||||||
|
|
||||||
|
func _update_scene_loading_progress():
|
||||||
|
var new_progress = SceneLoader.get_progress()
|
||||||
|
if new_progress > _scene_loading_progress:
|
||||||
|
_scene_loading_progress = new_progress
|
||||||
|
|
||||||
|
func _set_scene_loading_complete():
|
||||||
|
_scene_loading_progress = 1.0
|
||||||
|
_scene_loading_complete = true
|
||||||
|
|
||||||
|
func _reset_scene_loading_progress():
|
||||||
|
_scene_loading_progress = 0.0
|
||||||
|
_scene_loading_complete = false
|
||||||
|
|
||||||
|
func _show_loading_stalled_error_message():
|
||||||
|
if %StalledMessage.visible:
|
||||||
|
return
|
||||||
|
if _scene_loading_progress == 0:
|
||||||
|
%StalledMessage.dialog_text = "Stalled at start. You may try waiting or restarting.\n"
|
||||||
|
else:
|
||||||
|
%StalledMessage.dialog_text = "Stalled at %d%%. You may try waiting or restarting.\n" % (_scene_loading_progress * 100.0)
|
||||||
|
if OS.has_feature("web"):
|
||||||
|
%StalledMessage.dialog_text += STALLED_ON_WEB
|
||||||
|
%StalledMessage.popup()
|
||||||
|
|
||||||
|
func _show_scene_switching_error_message():
|
||||||
|
if %ErrorMessage.visible:
|
||||||
|
return
|
||||||
|
%ErrorMessage.dialog_text = "Loading Error: Failed to switch scenes."
|
||||||
|
%ErrorMessage.popup()
|
||||||
|
|
||||||
|
func _hide_popups():
|
||||||
|
%ErrorMessage.hide()
|
||||||
|
%StalledMessage.hide()
|
||||||
|
|
||||||
|
func _update_in_progress_messaging():
|
||||||
|
match _stall_stage:
|
||||||
|
StallStage.STARTED:
|
||||||
|
_hide_popups()
|
||||||
|
%Title.text = LOADING_TEXT
|
||||||
|
StallStage.WAITING:
|
||||||
|
_hide_popups()
|
||||||
|
%Title.text = LOADING_TEXT_WAITING
|
||||||
|
StallStage.STILL_WAITING:
|
||||||
|
_hide_popups()
|
||||||
|
%Title.text = LOADING_TEXT_STILL_WAITING % _get_seconds_waiting()
|
||||||
|
StallStage.GIVE_UP:
|
||||||
|
_show_loading_stalled_error_message()
|
||||||
|
%Title.text = LOADING_TEXT_STILL_WAITING % _get_seconds_waiting()
|
||||||
|
|
||||||
|
func _update_loaded_messaging():
|
||||||
|
match _stall_stage:
|
||||||
|
StallStage.STARTED:
|
||||||
|
_hide_popups()
|
||||||
|
%Title.text = LOADING_COMPLETE_TEXT
|
||||||
|
StallStage.WAITING:
|
||||||
|
_hide_popups()
|
||||||
|
%Title.text = LOADING_COMPLETE_TEXT_WAITING
|
||||||
|
StallStage.STILL_WAITING:
|
||||||
|
_hide_popups()
|
||||||
|
%Title.text = LOADING_COMPLETE_TEXT_STILL_WAITING % _get_seconds_waiting()
|
||||||
|
StallStage.GIVE_UP:
|
||||||
|
_show_scene_switching_error_message()
|
||||||
|
%Title.text = LOADING_COMPLETE_TEXT_STILL_WAITING % _get_seconds_waiting()
|
||||||
|
|
||||||
|
func _process(_delta):
|
||||||
|
_try_loading_next_scene()
|
||||||
|
var status = SceneLoader.get_status()
|
||||||
|
match(status):
|
||||||
|
ResourceLoader.THREAD_LOAD_IN_PROGRESS:
|
||||||
|
_update_scene_loading_progress()
|
||||||
|
_update_in_progress_messaging()
|
||||||
|
ResourceLoader.THREAD_LOAD_LOADED:
|
||||||
|
_set_scene_loading_complete()
|
||||||
|
_update_loaded_messaging()
|
||||||
|
ResourceLoader.THREAD_LOAD_FAILED:
|
||||||
|
%ErrorMessage.dialog_text = "Loading Error: %d" % status
|
||||||
|
%ErrorMessage.popup()
|
||||||
|
set_process(false)
|
||||||
|
ResourceLoader.THREAD_LOAD_INVALID_RESOURCE:
|
||||||
|
_hide_popups()
|
||||||
|
set_process(false)
|
||||||
|
|
||||||
|
func _on_loading_timer_timeout():
|
||||||
|
var prev_stage : StallStage = _stall_stage
|
||||||
|
match prev_stage:
|
||||||
|
StallStage.STARTED:
|
||||||
|
_stall_stage = StallStage.WAITING
|
||||||
|
%LoadingTimer.start()
|
||||||
|
StallStage.WAITING:
|
||||||
|
_stall_stage = StallStage.STILL_WAITING
|
||||||
|
%LoadingTimer.start()
|
||||||
|
StallStage.STILL_WAITING:
|
||||||
|
_stall_stage = StallStage.GIVE_UP
|
||||||
|
|
||||||
|
func _reload_main_scene_or_quit():
|
||||||
|
var err = get_tree().change_scene_to_file(ProjectSettings.get_setting("application/run/main_scene"))
|
||||||
|
if err:
|
||||||
|
push_error("failed to load main scene: %d" % err)
|
||||||
|
get_tree().quit()
|
||||||
|
|
||||||
|
func _on_error_message_confirmed():
|
||||||
|
_reload_main_scene_or_quit()
|
||||||
|
|
||||||
|
func _on_confirmation_dialog_canceled():
|
||||||
|
_reload_main_scene_or_quit()
|
||||||
|
|
||||||
|
func _on_confirmation_dialog_confirmed():
|
||||||
|
_reset_loading_stage()
|
||||||
|
|
||||||
|
func reset():
|
||||||
|
show()
|
||||||
|
_reset_loading_stage()
|
||||||
|
_reset_scene_loading_progress()
|
||||||
|
_reset_loading_start_time()
|
||||||
|
_hide_popups()
|
||||||
|
set_process(true)
|
||||||
|
|
||||||
|
func close():
|
||||||
|
set_process(false)
|
||||||
|
_hide_popups()
|
||||||
|
hide()
|
@ -0,0 +1,89 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://cd0jbh4metflb"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scenes/loading_screen/loading_screen.gd" id="1_gbk34"]
|
||||||
|
|
||||||
|
[node name="LoadingScreen" type="CanvasLayer"]
|
||||||
|
process_mode = 3
|
||||||
|
layer = 20
|
||||||
|
script = ExtResource("1_gbk34")
|
||||||
|
|
||||||
|
[node name="Control" type="Control" parent="."]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="BackPanel" type="Panel" parent="Control"]
|
||||||
|
layout_mode = 0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
|
||||||
|
[node name="BackgroundColor" type="ColorRect" parent="Control"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
color = Color(0, 0, 0, 0.501961)
|
||||||
|
|
||||||
|
[node name="BackgroundTextureRect" type="TextureRect" parent="Control"]
|
||||||
|
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="VBoxContainer" type="VBoxContainer" parent="Control"]
|
||||||
|
layout_mode = 0
|
||||||
|
anchor_top = 0.5
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 0.5
|
||||||
|
offset_left = 30.0
|
||||||
|
offset_top = -23.0
|
||||||
|
offset_right = -30.0
|
||||||
|
offset_bottom = 98.0
|
||||||
|
theme_override_constants/separation = 50
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="Control/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Loading..."
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="ProgressBar" type="ProgressBar" parent="Control/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(0, 50)
|
||||||
|
layout_mode = 2
|
||||||
|
max_value = 1.0
|
||||||
|
|
||||||
|
[node name="ErrorMessage" type="AcceptDialog" parent="Control"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
title = "Loading Error"
|
||||||
|
initial_position = 2
|
||||||
|
size = Vector2i(360, 100)
|
||||||
|
|
||||||
|
[node name="StalledMessage" type="ConfirmationDialog" parent="Control"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
title = "Loading Stalled"
|
||||||
|
initial_position = 2
|
||||||
|
size = Vector2i(360, 100)
|
||||||
|
ok_button_text = "Try Waiting"
|
||||||
|
cancel_button_text = "Reload"
|
||||||
|
|
||||||
|
[node name="LoadingTimer" type="Timer" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
wait_time = 15.0
|
||||||
|
one_shot = true
|
||||||
|
autostart = true
|
||||||
|
|
||||||
|
[connection signal="confirmed" from="Control/ErrorMessage" to="." method="_on_error_message_confirmed"]
|
||||||
|
[connection signal="canceled" from="Control/StalledMessage" to="." method="_on_confirmation_dialog_canceled"]
|
||||||
|
[connection signal="confirmed" from="Control/StalledMessage" to="." method="_on_confirmation_dialog_confirmed"]
|
||||||
|
[connection signal="timeout" from="LoadingTimer" to="." method="_on_loading_timer_timeout"]
|
@ -0,0 +1,111 @@
|
|||||||
|
class_name MainMenu
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
const NO_VERSION_NAME : String = "0.0.0"
|
||||||
|
|
||||||
|
## Defines the path to the game scene. Hides the play button if empty.
|
||||||
|
@export_file("*.tscn") var game_scene_path : String
|
||||||
|
@export var options_packed_scene : PackedScene
|
||||||
|
@export var credits_packed_scene : PackedScene
|
||||||
|
@export_group("Version")
|
||||||
|
## Displays the value of `application/config/version`, set in project settings.
|
||||||
|
@export var show_version : bool = true
|
||||||
|
## Prefixes the value of `application/config/version` when displaying to the user.
|
||||||
|
@export var version_prefix : String = "v"
|
||||||
|
|
||||||
|
var options_scene
|
||||||
|
var credits_scene
|
||||||
|
var sub_menu
|
||||||
|
|
||||||
|
func load_scene(scene_path : String):
|
||||||
|
SceneLoader.load_scene(scene_path)
|
||||||
|
|
||||||
|
func play_game():
|
||||||
|
SceneLoader.load_scene(game_scene_path)
|
||||||
|
|
||||||
|
func _open_sub_menu(menu : Control):
|
||||||
|
sub_menu = menu
|
||||||
|
sub_menu.show()
|
||||||
|
%BackButton.show()
|
||||||
|
%MenuContainer.hide()
|
||||||
|
|
||||||
|
func _close_sub_menu():
|
||||||
|
if sub_menu == null:
|
||||||
|
return
|
||||||
|
sub_menu.hide()
|
||||||
|
sub_menu = null
|
||||||
|
%BackButton.hide()
|
||||||
|
%MenuContainer.show()
|
||||||
|
|
||||||
|
func _event_is_mouse_button_released(event : InputEvent):
|
||||||
|
return event is InputEventMouseButton and not event.is_pressed()
|
||||||
|
|
||||||
|
func _event_skips_intro(event : InputEvent):
|
||||||
|
return event.is_action_released("ui_accept") or \
|
||||||
|
event.is_action_released("ui_select") or \
|
||||||
|
event.is_action_released("ui_cancel") or \
|
||||||
|
_event_is_mouse_button_released(event)
|
||||||
|
|
||||||
|
func _input(event):
|
||||||
|
if event.is_action_released("ui_accept") and get_viewport().gui_get_focus_owner() == null:
|
||||||
|
%MenuButtons.focus_first()
|
||||||
|
|
||||||
|
func _setup_for_web():
|
||||||
|
if OS.has_feature("web"):
|
||||||
|
%ExitButton.hide()
|
||||||
|
|
||||||
|
func _setup_version_name():
|
||||||
|
var version_name : String = ProjectSettings.get_setting("application/config/version", NO_VERSION_NAME)
|
||||||
|
if version_name.is_empty():
|
||||||
|
version_name = NO_VERSION_NAME
|
||||||
|
AppLog.version_opened(version_name)
|
||||||
|
%VersionNameLabel.text = version_prefix + version_name
|
||||||
|
|
||||||
|
func _setup_play():
|
||||||
|
if game_scene_path.is_empty():
|
||||||
|
%PlayButton.hide()
|
||||||
|
|
||||||
|
func _setup_options():
|
||||||
|
if options_packed_scene == null:
|
||||||
|
%OptionsButton.hide()
|
||||||
|
else:
|
||||||
|
options_scene = options_packed_scene.instantiate()
|
||||||
|
options_scene.hide()
|
||||||
|
%OptionsContainer.call_deferred("add_child", options_scene)
|
||||||
|
|
||||||
|
func _setup_credits():
|
||||||
|
if credits_packed_scene == null:
|
||||||
|
%CreditsButton.hide()
|
||||||
|
else:
|
||||||
|
credits_scene = credits_packed_scene.instantiate()
|
||||||
|
credits_scene.hide()
|
||||||
|
if credits_scene.has_signal("end_reached"):
|
||||||
|
credits_scene.connect("end_reached", _on_credits_end_reached)
|
||||||
|
%CreditsContainer.call_deferred("add_child", credits_scene)
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
_setup_for_web()
|
||||||
|
_setup_version_name()
|
||||||
|
_setup_options()
|
||||||
|
_setup_credits()
|
||||||
|
_setup_play()
|
||||||
|
|
||||||
|
func _on_play_button_pressed():
|
||||||
|
play_game()
|
||||||
|
|
||||||
|
func _on_options_button_pressed():
|
||||||
|
_open_sub_menu(options_scene)
|
||||||
|
|
||||||
|
func _on_credits_button_pressed():
|
||||||
|
_open_sub_menu(credits_scene)
|
||||||
|
credits_scene.reset()
|
||||||
|
|
||||||
|
func _on_exit_button_pressed():
|
||||||
|
get_tree().quit()
|
||||||
|
|
||||||
|
func _on_credits_end_reached():
|
||||||
|
if sub_menu == credits_scene:
|
||||||
|
_close_sub_menu()
|
||||||
|
|
||||||
|
func _on_back_button_pressed():
|
||||||
|
_close_sub_menu()
|
@ -0,0 +1,176 @@
|
|||||||
|
[gd_scene load_steps=7 format=3 uid="uid://c6k5nnpbypshi"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scenes/menus/main_menu/main_menu.gd" id="1"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://hmx6o472ropw" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/master_options_menu_with_tabs.tscn" id="2_qvrrd"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://t2dui8ppm3a4" path="res://addons/maaacks_menus_template/base/scenes/credits/credits.tscn" id="3_5dhvp"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scripts/capture_focus.gd" id="4_l1ebe"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bkcsjsk2ciff" path="res://addons/maaacks_menus_template/base/scenes/music_players/background_music_player.tscn" id="4_w8sbm"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scripts/ui_sound_controller.gd" id="6_bs342"]
|
||||||
|
|
||||||
|
[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")
|
||||||
|
options_packed_scene = ExtResource("2_qvrrd")
|
||||||
|
credits_packed_scene = ExtResource("3_5dhvp")
|
||||||
|
|
||||||
|
[node name="UISoundController" type="Node" parent="."]
|
||||||
|
script = ExtResource("6_bs342")
|
||||||
|
|
||||||
|
[node name="BackgroundMusicPlayer" parent="." instance=ExtResource("4_w8sbm")]
|
||||||
|
|
||||||
|
[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="VersionNameLabel" type="Label" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 3
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = -96.0
|
||||||
|
offset_top = -33.0
|
||||||
|
offset_right = -8.0
|
||||||
|
offset_bottom = -7.0
|
||||||
|
grow_horizontal = 0
|
||||||
|
grow_vertical = 0
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[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="VBoxContainer" type="VBoxContainer" parent="MenuContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="TitlesMargin" type="MarginContainer" parent="MenuContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/margin_top = 16
|
||||||
|
|
||||||
|
[node name="TitlesContainer" type="VBoxContainer" parent="MenuContainer/VBoxContainer/TitlesMargin"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="MenuContainer/VBoxContainer/TitlesMargin/TitlesContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 48
|
||||||
|
text = "Title"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="SubTitle" type="Label" parent="MenuContainer/VBoxContainer/TitlesMargin/TitlesContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 24
|
||||||
|
text = "Subtitle"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="MenuMargin" type="MarginContainer" parent="MenuContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_constants/margin_top = 16
|
||||||
|
theme_override_constants/margin_bottom = 16
|
||||||
|
|
||||||
|
[node name="MenuButtons" type="VBoxContainer" parent="MenuContainer/VBoxContainer/MenuMargin"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme_override_constants/separation = 16
|
||||||
|
alignment = 1
|
||||||
|
script = ExtResource("4_l1ebe")
|
||||||
|
|
||||||
|
[node name="PlayButton" type="Button" parent="MenuContainer/VBoxContainer/MenuMargin/MenuButtons"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(128, 40)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Play"
|
||||||
|
|
||||||
|
[node name="OptionsButton" type="Button" parent="MenuContainer/VBoxContainer/MenuMargin/MenuButtons"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(128, 40)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Options
|
||||||
|
"
|
||||||
|
|
||||||
|
[node name="CreditsButton" type="Button" parent="MenuContainer/VBoxContainer/MenuMargin/MenuButtons"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(128, 40)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Credits"
|
||||||
|
|
||||||
|
[node name="ExitButton" type="Button" parent="MenuContainer/VBoxContainer/MenuMargin/MenuButtons"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(128, 40)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Exit"
|
||||||
|
|
||||||
|
[node name="OptionsContainer" type="MarginContainer" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="CreditsContainer" type="MarginContainer" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/margin_left = 16
|
||||||
|
theme_override_constants/margin_top = 32
|
||||||
|
theme_override_constants/margin_right = 16
|
||||||
|
theme_override_constants/margin_bottom = 32
|
||||||
|
|
||||||
|
[node name="FlowControlContainer" type="MarginContainer" parent="."]
|
||||||
|
layout_mode = 0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/margin_left = 16
|
||||||
|
theme_override_constants/margin_top = 16
|
||||||
|
theme_override_constants/margin_right = 16
|
||||||
|
theme_override_constants/margin_bottom = 16
|
||||||
|
|
||||||
|
[node name="FlowControl" type="Control" parent="FlowControlContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="BackButton" type="Button" parent="FlowControlContainer/FlowControl"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
custom_minimum_size = Vector2(62, 40)
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 2
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_top = -40.0
|
||||||
|
offset_right = 62.0
|
||||||
|
grow_vertical = 0
|
||||||
|
text = "Back"
|
||||||
|
|
||||||
|
[connection signal="pressed" from="MenuContainer/VBoxContainer/MenuMargin/MenuButtons/PlayButton" to="." method="_on_play_button_pressed"]
|
||||||
|
[connection signal="pressed" from="MenuContainer/VBoxContainer/MenuMargin/MenuButtons/OptionsButton" to="." method="_on_options_button_pressed"]
|
||||||
|
[connection signal="pressed" from="MenuContainer/VBoxContainer/MenuMargin/MenuButtons/CreditsButton" to="." method="_on_credits_button_pressed"]
|
||||||
|
[connection signal="pressed" from="MenuContainer/VBoxContainer/MenuMargin/MenuButtons/ExitButton" to="." method="_on_exit_button_pressed"]
|
||||||
|
[connection signal="pressed" from="FlowControlContainer/FlowControl/BackButton" to="." method="_on_back_button_pressed"]
|
@ -0,0 +1,23 @@
|
|||||||
|
extends HBoxContainer
|
||||||
|
|
||||||
|
signal bus_value_changed(bus_name, bus_value)
|
||||||
|
|
||||||
|
@export var bus_name : String :
|
||||||
|
set(value):
|
||||||
|
bus_name = value
|
||||||
|
if is_inside_tree():
|
||||||
|
$BusLabel.text = bus_name
|
||||||
|
|
||||||
|
@export var bus_value : float :
|
||||||
|
set(value):
|
||||||
|
bus_value = value
|
||||||
|
if is_inside_tree():
|
||||||
|
$BusHSlider.value = bus_value
|
||||||
|
|
||||||
|
func _on_bus_h_slider_value_changed(value):
|
||||||
|
bus_value = value
|
||||||
|
emit_signal("bus_value_changed", bus_name, value)
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
bus_name = bus_name
|
||||||
|
bus_value = bus_value
|
@ -0,0 +1,25 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://bosy6wwf0vleq"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scenes/Menus/OptionsMenu/Audio/AudioControl/AudioControl.gd" id="1_caab0"]
|
||||||
|
|
||||||
|
[node name="AudioControl" type="HBoxContainer"]
|
||||||
|
offset_right = 40.0
|
||||||
|
offset_bottom = 40.0
|
||||||
|
script = ExtResource("1_caab0")
|
||||||
|
|
||||||
|
[node name="BusLabel" type="Label" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "Audio Bus :"
|
||||||
|
|
||||||
|
[node name="BusHSlider" type="HSlider" parent="."]
|
||||||
|
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
|
||||||
|
|
||||||
|
[connection signal="value_changed" from="BusHSlider" to="." method="_on_bus_h_slider_value_changed"]
|
@ -0,0 +1,37 @@
|
|||||||
|
class_name AudioOptionsMenu
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
@export var audio_control_scene : PackedScene
|
||||||
|
@export var hide_busses : Array[String]
|
||||||
|
|
||||||
|
@onready var mute_control = %MuteControl
|
||||||
|
|
||||||
|
func _on_bus_changed(bus_value : float, bus_iter : int):
|
||||||
|
AppSettings.set_bus_volume(bus_iter, bus_value)
|
||||||
|
|
||||||
|
func _add_audio_control(bus_name : String, bus_value : float, bus_iter : int):
|
||||||
|
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():
|
||||||
|
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():
|
||||||
|
_add_audio_bus_controls()
|
||||||
|
mute_control.value = AppSettings.is_muted()
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
_update_ui()
|
||||||
|
|
||||||
|
func _on_mute_control_setting_changed(value):
|
||||||
|
AppSettings.set_mute(value)
|
@ -0,0 +1,42 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://c8vnncjwqcpab"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/audio/audio_options_menu.gd" id="1"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://cl416gdb1fgwr" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/slider_option_control.tscn" id="2_raehj"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scripts/capture_focus.gd" id="3_dtraq"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bsxh6v7j0257h" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/toggle_option_control.tscn" id="4_ojfec"]
|
||||||
|
|
||||||
|
[node name="Audio" type="MarginContainer"]
|
||||||
|
custom_minimum_size = Vector2(305, 0)
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
theme_override_constants/margin_top = 24
|
||||||
|
theme_override_constants/margin_bottom = 24
|
||||||
|
script = ExtResource("1")
|
||||||
|
audio_control_scene = ExtResource("2_raehj")
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
|
custom_minimum_size = Vector2(400, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
alignment = 1
|
||||||
|
script = ExtResource("3_dtraq")
|
||||||
|
search_depth = 3
|
||||||
|
|
||||||
|
[node name="AudioControlContainer" type="VBoxContainer" parent="VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
|
||||||
|
[node name="MuteControl" parent="VBoxContainer" instance=ExtResource("4_ojfec")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
option_name = "Mute"
|
||||||
|
option_section = 2
|
||||||
|
key = "Mute"
|
||||||
|
section = "AudioSettings"
|
||||||
|
|
||||||
|
[connection signal="setting_changed" from="VBoxContainer/MuteControl" to="." method="_on_mute_control_setting_changed"]
|
@ -0,0 +1,238 @@
|
|||||||
|
class_name InputOptionsMenu
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
const ALREADY_ASSIGNED_TEXT : String = "{key} already assigned to {action}."
|
||||||
|
const ONE_INPUT_MINIMUM_TEXT : String = "%s must have at least one key or button assigned."
|
||||||
|
const KEY_DELETION_TEXT : String = "Are you sure you want to remove {key} from {action}?"
|
||||||
|
|
||||||
|
## Maps the names of input actions to readable names for users.
|
||||||
|
@export var action_name_map : Dictionary = {
|
||||||
|
"move_up" : "Up",
|
||||||
|
"move_down" : "Down",
|
||||||
|
"move_left" : "Left",
|
||||||
|
"move_right" : "Right",
|
||||||
|
"interact" : "Interact"
|
||||||
|
}
|
||||||
|
## Show action names that are not explicitely listed in an action name map.
|
||||||
|
@export var show_all_actions : bool = false
|
||||||
|
@export_group("Icons")
|
||||||
|
@export var add_button_texture : Texture2D
|
||||||
|
@export var remove_button_texture : Texture2D
|
||||||
|
@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 : 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",
|
||||||
|
}
|
||||||
|
|
||||||
|
@onready var assignment_placeholder_text = $KeyAssignmentDialog.dialog_text
|
||||||
|
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 _popup_add_action_event(item : TreeItem) -> void:
|
||||||
|
if item not in tree_item_add_map:
|
||||||
|
return
|
||||||
|
editing_item = item
|
||||||
|
editing_action_name = tree_item_add_map[item]
|
||||||
|
var readable_action_name = tr(_get_action_readable_name(editing_action_name))
|
||||||
|
$KeyAssignmentDialog.title = tr("Assign Key for {action}").format({action = readable_action_name})
|
||||||
|
$KeyAssignmentDialog.dialog_text = assignment_placeholder_text
|
||||||
|
$KeyAssignmentDialog.get_ok_button().disabled = true
|
||||||
|
$KeyAssignmentDialog.popup_centered()
|
||||||
|
|
||||||
|
func _popup_remove_action_event(item : TreeItem) -> void:
|
||||||
|
if item not in tree_item_remove_map:
|
||||||
|
return
|
||||||
|
editing_item = item
|
||||||
|
editing_action_name = tree_item_action_map[item]
|
||||||
|
var readable_action_name = tr(_get_action_readable_name(editing_action_name))
|
||||||
|
$KeyDeletionDialog.title = tr("Remove Key for {action}").format({action = readable_action_name})
|
||||||
|
$KeyDeletionDialog.dialog_text = tr(KEY_DELETION_TEXT).format({key = item.get_text(0), action = readable_action_name})
|
||||||
|
$KeyDeletionDialog.popup_centered()
|
||||||
|
|
||||||
|
func _start_tree():
|
||||||
|
%Tree.clear()
|
||||||
|
%Tree.create_item()
|
||||||
|
|
||||||
|
func _add_input_event_as_tree_item(action_name : String, input_event : InputEvent, parent_item : TreeItem):
|
||||||
|
var input_tree_item : TreeItem = %Tree.create_item(parent_item)
|
||||||
|
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]):
|
||||||
|
var root_tree_item : TreeItem = %Tree.get_root()
|
||||||
|
var action_tree_item : TreeItem = %Tree.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] = []
|
||||||
|
var full_action_name_map = action_name_map.duplicate()
|
||||||
|
if include_built_in:
|
||||||
|
full_action_name_map.merge(built_in_action_name_map)
|
||||||
|
for action_name in full_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 = action_name
|
||||||
|
if readable_name in action_name_map:
|
||||||
|
readable_name = action_name_map[readable_name]
|
||||||
|
elif readable_name in built_in_action_name_map:
|
||||||
|
readable_name = built_in_action_name_map[readable_name]
|
||||||
|
else:
|
||||||
|
action_name_map[readable_name] = readable_name
|
||||||
|
return readable_name
|
||||||
|
|
||||||
|
func _build_ui_tree():
|
||||||
|
_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:
|
||||||
|
push_warning("%s action_name is empty" % action_name)
|
||||||
|
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):
|
||||||
|
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):
|
||||||
|
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():
|
||||||
|
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 _horizontally_align_popup_labels():
|
||||||
|
$KeyAssignmentDialog.get_label().horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
$KeyDeletionDialog.get_label().horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
$OneInputMinimumDialog.get_label().horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
$AlreadyAssignedDialog.get_label().horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
_build_assigned_input_events()
|
||||||
|
_build_ui_tree()
|
||||||
|
_horizontally_align_popup_labels()
|
||||||
|
|
||||||
|
func _add_action_event():
|
||||||
|
var last_input_event = $KeyAssignmentDialog.last_input_event
|
||||||
|
last_input_readable_name = $KeyAssignmentDialog.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))
|
||||||
|
$AlreadyAssignedDialog.dialog_text = tr(ALREADY_ASSIGNED_TEXT).format({key = last_input_readable_name, action = readable_action_name})
|
||||||
|
$AlreadyAssignedDialog.popup_centered()
|
||||||
|
else:
|
||||||
|
_assign_input_event_to_action(last_input_event, editing_action_name)
|
||||||
|
editing_action_name = ""
|
||||||
|
|
||||||
|
func _on_key_assignment_dialog_canceled():
|
||||||
|
editing_action_name = ""
|
||||||
|
|
||||||
|
func _remove_action_event(item : TreeItem):
|
||||||
|
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)
|
||||||
|
$OneInputMinimumDialog.dialog_text = ONE_INPUT_MINIMUM_TEXT % readable_action_name
|
||||||
|
$OneInputMinimumDialog.popup_centered()
|
||||||
|
return
|
||||||
|
_remove_input_event_from_action(input_event, action_name)
|
||||||
|
var parent_tree_item = item.get_parent()
|
||||||
|
parent_tree_item.remove_child(item)
|
||||||
|
|
||||||
|
func _check_item_actions(item):
|
||||||
|
if item in tree_item_add_map:
|
||||||
|
_popup_add_action_event(item)
|
||||||
|
elif item in tree_item_remove_map:
|
||||||
|
_popup_remove_action_event(item)
|
||||||
|
|
||||||
|
func _on_tree_button_clicked(item, _column, _id, _mouse_button_index):
|
||||||
|
_check_item_actions(item)
|
||||||
|
|
||||||
|
func _on_reset_button_pressed():
|
||||||
|
$ConfirmationDialog.popup_centered()
|
||||||
|
|
||||||
|
func _on_tree_item_activated():
|
||||||
|
var item = %Tree.get_selected()
|
||||||
|
_check_item_actions(item)
|
||||||
|
|
||||||
|
func _on_key_deletion_dialog_confirmed():
|
||||||
|
if is_instance_valid(editing_item):
|
||||||
|
_remove_action_event(editing_item)
|
||||||
|
|
||||||
|
func _on_key_assignment_dialog_confirmed():
|
||||||
|
_add_action_event()
|
||||||
|
|
||||||
|
func _on_confirmation_dialog_confirmed():
|
||||||
|
AppSettings.reset_to_default_inputs()
|
||||||
|
_build_assigned_input_events()
|
||||||
|
_build_ui_tree()
|
@ -0,0 +1,122 @@
|
|||||||
|
[gd_scene load_steps=6 format=3 uid="uid://dp3rgqaehb3xu"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/input/input_options_menu.gd" id="1"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://c1eqf1cse1hch" path="res://addons/maaacks_menus_template/base/assets/images/addition_symbol.png" id="2_dw35t"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scripts/capture_focus.gd" id="2_wft4x"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://bteq3ica74h30" path="res://addons/maaacks_menus_template/base/assets/images/subtraction_symbol.png" id="3_lngdd"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/input/key_assignment_dialog.gd" id="3_wsh2h"]
|
||||||
|
|
||||||
|
[node name="Controls" type="MarginContainer"]
|
||||||
|
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")
|
||||||
|
add_button_texture = ExtResource("2_dw35t")
|
||||||
|
remove_button_texture = ExtResource("3_lngdd")
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
|
custom_minimum_size = Vector2(400, 280)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
script = ExtResource("2_wft4x")
|
||||||
|
search_depth = 2
|
||||||
|
|
||||||
|
[node name="InputMappingContainer" type="VBoxContainer" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="VBoxContainer/InputMappingContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Actions & Inputs"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Tree" type="Tree" parent="VBoxContainer/InputMappingContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
hide_root = true
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/InputMappingContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="ResetButton" type="Button" parent="VBoxContainer/InputMappingContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Reset"
|
||||||
|
|
||||||
|
[node name="KeyAssignmentDialog" type="ConfirmationDialog" parent="."]
|
||||||
|
title = "Assign Key"
|
||||||
|
size = Vector2i(400, 158)
|
||||||
|
dialog_text = "
|
||||||
|
|
||||||
|
|
||||||
|
"
|
||||||
|
script = ExtResource("3_wsh2h")
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="KeyAssignmentDialog"]
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = 8.0
|
||||||
|
offset_top = 8.0
|
||||||
|
offset_right = -8.0
|
||||||
|
offset_bottom = -49.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="InputLabel" type="Label" parent="KeyAssignmentDialog/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "None"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="InputTextEdit" type="TextEdit" parent="KeyAssignmentDialog/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
placeholder_text = "Focus here to assign inputs."
|
||||||
|
context_menu_enabled = false
|
||||||
|
shortcut_keys_enabled = false
|
||||||
|
selecting_enabled = false
|
||||||
|
deselect_on_focus_loss_enabled = false
|
||||||
|
drag_and_drop_selection_enabled = false
|
||||||
|
middle_mouse_paste_enabled = false
|
||||||
|
caret_move_on_right_click = false
|
||||||
|
|
||||||
|
[node name="DelayTimer" type="Timer" parent="KeyAssignmentDialog"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
wait_time = 0.2
|
||||||
|
one_shot = true
|
||||||
|
|
||||||
|
[node name="KeyDeletionDialog" type="ConfirmationDialog" parent="."]
|
||||||
|
title = "Remove Key"
|
||||||
|
size = Vector2i(419, 100)
|
||||||
|
dialog_text = "Are you sure you want to remove KEY from ACTION?"
|
||||||
|
|
||||||
|
[node name="OneInputMinimumDialog" type="AcceptDialog" parent="."]
|
||||||
|
title = "Cannot Remove"
|
||||||
|
size = Vector2i(398, 100)
|
||||||
|
|
||||||
|
[node name="AlreadyAssignedDialog" type="AcceptDialog" parent="."]
|
||||||
|
title = "Already Assigned"
|
||||||
|
size = Vector2i(398, 100)
|
||||||
|
|
||||||
|
[node name="ConfirmationDialog" type="ConfirmationDialog" parent="."]
|
||||||
|
dialog_text = "Are you sure you want to reset controls back to the defaults?"
|
||||||
|
|
||||||
|
[connection signal="button_clicked" from="VBoxContainer/InputMappingContainer/Tree" to="." method="_on_tree_button_clicked"]
|
||||||
|
[connection signal="item_activated" from="VBoxContainer/InputMappingContainer/Tree" to="." method="_on_tree_item_activated"]
|
||||||
|
[connection signal="pressed" from="VBoxContainer/InputMappingContainer/HBoxContainer/ResetButton" to="." method="_on_reset_button_pressed"]
|
||||||
|
[connection signal="canceled" from="KeyAssignmentDialog" to="." method="_on_key_assignment_dialog_canceled"]
|
||||||
|
[connection signal="confirmed" from="KeyAssignmentDialog" to="." method="_on_key_assignment_dialog_confirmed"]
|
||||||
|
[connection signal="visibility_changed" from="KeyAssignmentDialog" to="KeyAssignmentDialog" method="_on_visibility_changed"]
|
||||||
|
[connection signal="focus_entered" from="KeyAssignmentDialog/VBoxContainer/InputTextEdit" to="KeyAssignmentDialog" method="_on_text_edit_focus_entered"]
|
||||||
|
[connection signal="focus_exited" from="KeyAssignmentDialog/VBoxContainer/InputTextEdit" to="KeyAssignmentDialog" method="_on_input_text_edit_focus_exited"]
|
||||||
|
[connection signal="gui_input" from="KeyAssignmentDialog/VBoxContainer/InputTextEdit" to="KeyAssignmentDialog" method="_on_input_text_edit_gui_input"]
|
||||||
|
[connection signal="confirmed" from="KeyDeletionDialog" to="." method="_on_key_deletion_dialog_confirmed"]
|
||||||
|
[connection signal="confirmed" from="ConfirmationDialog" to="." method="_on_confirmation_dialog_confirmed"]
|
@ -0,0 +1,88 @@
|
|||||||
|
extends ConfirmationDialog
|
||||||
|
|
||||||
|
const LISTENING_TEXT : String = "Listening for input..."
|
||||||
|
const FOCUS_HERE_TEXT : String = "Focus here to assign inputs."
|
||||||
|
const CONFIRM_INPUT_TEXT : String = "Press again to confirm..."
|
||||||
|
const NO_INPUT_TEXT : String = "None"
|
||||||
|
|
||||||
|
var last_input_event : InputEvent
|
||||||
|
var last_input_text : String
|
||||||
|
var listening : bool = false
|
||||||
|
var confirming : bool = false
|
||||||
|
|
||||||
|
func _record_input_event(event : InputEvent):
|
||||||
|
last_input_text = InputEventHelper.get_text(event)
|
||||||
|
if last_input_text.is_empty():
|
||||||
|
return
|
||||||
|
last_input_event = event
|
||||||
|
%InputLabel.text = last_input_text
|
||||||
|
get_ok_button().disabled = false
|
||||||
|
|
||||||
|
func _is_recordable_input(event : InputEvent):
|
||||||
|
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():
|
||||||
|
%InputTextEdit.placeholder_text = LISTENING_TEXT
|
||||||
|
listening = true
|
||||||
|
%DelayTimer.start()
|
||||||
|
|
||||||
|
func _stop_listening():
|
||||||
|
%InputTextEdit.placeholder_text = FOCUS_HERE_TEXT
|
||||||
|
listening = false
|
||||||
|
confirming = false
|
||||||
|
|
||||||
|
func _on_text_edit_focus_entered():
|
||||||
|
_start_listening.call_deferred()
|
||||||
|
|
||||||
|
func _on_input_text_edit_focus_exited():
|
||||||
|
_stop_listening()
|
||||||
|
|
||||||
|
func _focus_on_ok():
|
||||||
|
get_ok_button().grab_focus()
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
get_ok_button().focus_neighbor_top = ^"../../%InputTextEdit"
|
||||||
|
get_cancel_button().focus_neighbor_top = ^"../../%InputTextEdit"
|
||||||
|
|
||||||
|
func _input_matches_last(event : InputEvent) -> bool:
|
||||||
|
return last_input_text == InputEventHelper.get_text(event)
|
||||||
|
|
||||||
|
func _is_mouse_input(event : InputEvent) -> bool:
|
||||||
|
return event is InputEventMouse
|
||||||
|
|
||||||
|
func _input_confirms_choice(event : InputEvent) -> bool:
|
||||||
|
return confirming and not _is_mouse_input(event) and _input_matches_last(event)
|
||||||
|
|
||||||
|
func _should_process_input_event(event : InputEvent) -> bool:
|
||||||
|
return listening and _is_recordable_input(event) and %DelayTimer.is_stopped()
|
||||||
|
|
||||||
|
func _should_confirm_input_event(event : InputEvent) -> bool:
|
||||||
|
return not _is_mouse_input(event)
|
||||||
|
|
||||||
|
func _process_input_event(event : InputEvent):
|
||||||
|
if not _should_process_input_event(event):
|
||||||
|
return
|
||||||
|
if _input_confirms_choice(event):
|
||||||
|
confirming = false
|
||||||
|
_focus_on_ok.call_deferred()
|
||||||
|
return
|
||||||
|
_record_input_event(event)
|
||||||
|
if _should_confirm_input_event(event):
|
||||||
|
confirming = true
|
||||||
|
%DelayTimer.start()
|
||||||
|
%InputTextEdit.placeholder_text = CONFIRM_INPUT_TEXT
|
||||||
|
|
||||||
|
func _on_input_text_edit_gui_input(event):
|
||||||
|
%InputTextEdit.set_deferred("text", "")
|
||||||
|
_process_input_event(event)
|
||||||
|
|
||||||
|
func _on_visibility_changed():
|
||||||
|
if visible:
|
||||||
|
%InputLabel.text = NO_INPUT_TEXT
|
||||||
|
%InputTextEdit.grab_focus()
|
@ -0,0 +1,13 @@
|
|||||||
|
class_name MasterOptionsMenu
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
func _unhandled_input(event):
|
||||||
|
if not is_visible_in_tree():
|
||||||
|
return
|
||||||
|
if event.is_action_pressed("ui_page_down"):
|
||||||
|
$TabContainer.current_tab = ($TabContainer.current_tab+1) % $TabContainer.get_tab_count()
|
||||||
|
elif event.is_action_pressed("ui_page_up"):
|
||||||
|
if $TabContainer.current_tab == 0:
|
||||||
|
$TabContainer.current_tab = $TabContainer.get_tab_count()-1
|
||||||
|
else:
|
||||||
|
$TabContainer.current_tab = $TabContainer.current_tab-1
|
@ -0,0 +1,18 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://bvwl11s2p0hd"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/master_options_menu.gd" id="1_u08d5"]
|
||||||
|
|
||||||
|
[node name="MasterOptionsMenu" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_u08d5")
|
||||||
|
|
||||||
|
[node name="TabContainer" type="TabContainer" parent="."]
|
||||||
|
layout_mode = 0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
tab_alignment = 1
|
@ -0,0 +1,19 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://hmx6o472ropw"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bvwl11s2p0hd" path="res://addons/maaacks_menus_template/base/scenes/Menus/OptionsMenu/MasterOptionsMenu.tscn" id="1_uaidt"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dp3rgqaehb3xu" path="res://addons/maaacks_menus_template/base/scenes/Menus/OptionsMenu/Input/InputOptionsMenu.tscn" id="2_15wl6"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://c8vnncjwqcpab" path="res://addons/maaacks_menus_template/base/scenes/Menus/OptionsMenu/Audio/AudioOptionsMenu.tscn" id="3_qg4me"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b2numvphf2kau" path="res://addons/maaacks_menus_template/base/scenes/Menus/OptionsMenu/Video/VideoOptionsMenu.tscn" id="4_1t848"]
|
||||||
|
|
||||||
|
[node name="MasterOptionsMenu" instance=ExtResource("1_uaidt")]
|
||||||
|
|
||||||
|
[node name="Controls" parent="TabContainer" index="1" instance=ExtResource("2_15wl6")]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Audio" parent="TabContainer" index="2" instance=ExtResource("3_qg4me")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Video" parent="TabContainer" index="3" instance=ExtResource("4_1t848")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
@ -0,0 +1,45 @@
|
|||||||
|
class_name MiniOptionsMenu
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
@onready var mute_control = %MuteControl
|
||||||
|
@onready var fullscreen_control = %FullscreenControl
|
||||||
|
|
||||||
|
@export var audio_control_scene : PackedScene
|
||||||
|
@export var hide_busses : Array[String]
|
||||||
|
|
||||||
|
func _on_bus_changed(bus_value : float, bus_iter : int):
|
||||||
|
AppSettings.set_bus_volume(bus_iter, bus_value)
|
||||||
|
|
||||||
|
func _add_audio_control(bus_name : String, bus_value : float, bus_iter : int):
|
||||||
|
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():
|
||||||
|
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():
|
||||||
|
_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():
|
||||||
|
_sync_with_config()
|
||||||
|
|
||||||
|
func _on_mute_control_setting_changed(value):
|
||||||
|
AppSettings.set_mute(value)
|
||||||
|
|
||||||
|
func _on_fullscreen_control_setting_changed(value):
|
||||||
|
AppSettings.set_fullscreen_enabled(value, get_window())
|
@ -0,0 +1,51 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://vh1ucj2rfbby"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/mini_options_menu.gd" id="1_32vm2"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://cl416gdb1fgwr" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/slider_option_control.tscn" id="2_kpc65"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scripts/capture_focus.gd" id="3_7qt1o"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bsxh6v7j0257h" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/toggle_option_control.tscn" id="4_b20fb"]
|
||||||
|
|
||||||
|
[node name="MiniOptionsMenu" type="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(400, 260)
|
||||||
|
anchors_preset = 8
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_top = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_bottom = 0.5
|
||||||
|
offset_left = -200.0
|
||||||
|
offset_top = -130.0
|
||||||
|
offset_right = 200.0
|
||||||
|
offset_bottom = 130.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
alignment = 1
|
||||||
|
script = ExtResource("1_32vm2")
|
||||||
|
audio_control_scene = ExtResource("2_kpc65")
|
||||||
|
|
||||||
|
[node name="AudioControlContainer" type="VBoxContainer" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
script = ExtResource("3_7qt1o")
|
||||||
|
search_depth = 2
|
||||||
|
|
||||||
|
[node name="MuteControl" parent="." instance=ExtResource("4_b20fb")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
option_name = "Mute"
|
||||||
|
option_section = 2
|
||||||
|
key = "Mute"
|
||||||
|
section = "AudioSettings"
|
||||||
|
|
||||||
|
[node name="FullscreenControl" parent="." instance=ExtResource("4_b20fb")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
option_name = "Fullscreen"
|
||||||
|
option_section = 3
|
||||||
|
key = "FullscreenEnabled"
|
||||||
|
section = "VideoSettings"
|
||||||
|
|
||||||
|
[connection signal="setting_changed" from="MuteControl" to="." method="_on_mute_control_setting_changed"]
|
||||||
|
[connection signal="setting_changed" from="FullscreenControl" to="." method="_on_fullscreen_control_setting_changed"]
|
@ -0,0 +1,79 @@
|
|||||||
|
@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():
|
||||||
|
if option_values.is_empty(): return
|
||||||
|
custom_option_values = option_values.duplicate()
|
||||||
|
_set_titles_from_values()
|
||||||
|
|
||||||
|
func _on_setting_changed(value):
|
||||||
|
if value < option_values.size():
|
||||||
|
super._on_setting_changed(option_values[value])
|
||||||
|
|
||||||
|
func _set_titles_from_values():
|
||||||
|
if lock_titles: return
|
||||||
|
var mapped_titles : Array[String] = []
|
||||||
|
for option_value in custom_option_values:
|
||||||
|
mapped_titles.append(_value_title_map(option_value))
|
||||||
|
option_titles = mapped_titles
|
||||||
|
|
||||||
|
func _value_title_map(value : Variant) -> String:
|
||||||
|
return "%s" % value
|
||||||
|
|
||||||
|
func _match_value_to_other(value : Variant, other : Variant) -> Variant:
|
||||||
|
# Primarily for when the editor saves floats as ints instead
|
||||||
|
if value is int and other is float:
|
||||||
|
return float(value)
|
||||||
|
if value is float and other is int:
|
||||||
|
return int(round(value))
|
||||||
|
return value
|
||||||
|
|
||||||
|
func set_value(value : Variant):
|
||||||
|
if option_values.is_empty(): return
|
||||||
|
if value == null:
|
||||||
|
return super.set_value(-1)
|
||||||
|
custom_option_values = option_values.duplicate()
|
||||||
|
value = _match_value_to_other(value, custom_option_values.front())
|
||||||
|
if value not in custom_option_values:
|
||||||
|
custom_option_values.append(value)
|
||||||
|
custom_option_values.sort()
|
||||||
|
_set_titles_from_values()
|
||||||
|
if value not in option_values:
|
||||||
|
disable_option(custom_option_values.find(value))
|
||||||
|
value = custom_option_values.find(value)
|
||||||
|
super.set_value(value)
|
||||||
|
|
||||||
|
func _set_option_list(option_titles_list : Array):
|
||||||
|
%OptionButton.clear()
|
||||||
|
for option_title in option_titles_list:
|
||||||
|
%OptionButton.add_item(option_title)
|
||||||
|
|
||||||
|
func disable_option(option_index : int, disabled : bool = true):
|
||||||
|
%OptionButton.set_item_disabled(option_index, disabled)
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
lock_titles = lock_titles
|
||||||
|
option_titles = option_titles
|
||||||
|
option_values = option_values
|
||||||
|
super._ready()
|
@ -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_menus_template/base/scenes/menus/options_menu/option_control/option_control.tscn" id="1_blo3b"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/list_option_control.gd" id="2_kt4vl"]
|
||||||
|
|
||||||
|
[node name="OptionControl" instance=ExtResource("1_blo3b")]
|
||||||
|
script = ExtResource("2_kt4vl")
|
||||||
|
lock_titles = false
|
||||||
|
option_values = []
|
||||||
|
option_titles = []
|
||||||
|
|
||||||
|
[node name="OptionButton" type="OptionButton" parent="." index="1"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
@ -0,0 +1,134 @@
|
|||||||
|
@tool
|
||||||
|
class_name OptionControl
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
signal setting_changed(value)
|
||||||
|
|
||||||
|
enum OptionSections{
|
||||||
|
NONE,
|
||||||
|
INPUT,
|
||||||
|
AUDIO,
|
||||||
|
VIDEO,
|
||||||
|
GAME,
|
||||||
|
APPLICATION,
|
||||||
|
CUSTOM,
|
||||||
|
}
|
||||||
|
|
||||||
|
const OptionSectionNames : Dictionary = {
|
||||||
|
OptionSections.NONE : "",
|
||||||
|
OptionSections.INPUT : AppSettings.INPUT_SECTION,
|
||||||
|
OptionSections.AUDIO : AppSettings.AUDIO_SECTION,
|
||||||
|
OptionSections.VIDEO : AppSettings.VIDEO_SECTION,
|
||||||
|
OptionSections.GAME : AppSettings.GAME_SECTION,
|
||||||
|
OptionSections.APPLICATION : AppSettings.APPLICATION_SECTION,
|
||||||
|
OptionSections.CUSTOM : AppSettings.CUSTOM_SECTION,
|
||||||
|
}
|
||||||
|
|
||||||
|
## Locks config names in case of issues with inherited scenes.
|
||||||
|
## Intentionally put first for initialization.
|
||||||
|
@export var lock_config_names : bool = false
|
||||||
|
## Defines text displayed to the user.
|
||||||
|
@export var option_name : String :
|
||||||
|
set(value):
|
||||||
|
var _update_config : bool = option_name.to_pascal_case() == key and not lock_config_names
|
||||||
|
option_name = value
|
||||||
|
if is_inside_tree():
|
||||||
|
%OptionLabel.text = "%s%s" % [option_name, label_suffix]
|
||||||
|
if _update_config:
|
||||||
|
key = option_name.to_pascal_case()
|
||||||
|
## Defines what section in the config file this option belongs under.
|
||||||
|
@export var option_section : OptionSections :
|
||||||
|
set(value):
|
||||||
|
var _update_config : bool = OptionSectionNames[option_section] == section and not lock_config_names
|
||||||
|
option_section = value
|
||||||
|
if _update_config:
|
||||||
|
section = OptionSectionNames[option_section]
|
||||||
|
|
||||||
|
@export_group("Config Names")
|
||||||
|
## Defines the key for this option variable in the config file.
|
||||||
|
@export var key : String
|
||||||
|
## Defines the section for this option variable in the config file.
|
||||||
|
@export var section : String
|
||||||
|
@export_group("Format")
|
||||||
|
@export var label_suffix : String = " :"
|
||||||
|
@export_group("Properties")
|
||||||
|
## Defines whether the option is editable, or only visible by the user.
|
||||||
|
@export var editable : bool = true : set = set_editable
|
||||||
|
## Defines what kind of variable this option stores in the config file.
|
||||||
|
@export var property_type : Variant.Type = TYPE_BOOL
|
||||||
|
|
||||||
|
## It is advised to use an external editor to set the default value in the scene file.
|
||||||
|
## Godot can experience a bug (caching issue?) that may undo changes.
|
||||||
|
var default_value
|
||||||
|
var _connected_nodes : Array
|
||||||
|
|
||||||
|
func _on_setting_changed(value):
|
||||||
|
Config.set_config(section, key, value)
|
||||||
|
setting_changed.emit(value)
|
||||||
|
|
||||||
|
func _get_setting(default : Variant = null) -> Variant:
|
||||||
|
return Config.get_config(section, key, default)
|
||||||
|
|
||||||
|
func _connect_option_inputs(node):
|
||||||
|
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):
|
||||||
|
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):
|
||||||
|
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():
|
||||||
|
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():
|
||||||
|
return [
|
||||||
|
{ "name": "value", "type": property_type, "usage": PROPERTY_USAGE_NONE},
|
||||||
|
{ "name": "default_value", "type": property_type}
|
||||||
|
]
|
@ -0,0 +1,17 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://d7te75il06t7"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/option_control.gd" id="1_jvl5q"]
|
||||||
|
|
||||||
|
[node name="OptionControl" type="HBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(0, 40)
|
||||||
|
offset_right = 400.0
|
||||||
|
offset_bottom = 40.0
|
||||||
|
script = ExtResource("1_jvl5q")
|
||||||
|
default_value = false
|
||||||
|
|
||||||
|
[node name="OptionLabel" type="Label" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = " :"
|
||||||
|
vertical_alignment = 1
|
@ -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_menus_template/base/scenes/Menus/OptionsMenu/OptionControl/OptionControl.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_menus_template/base/scenes/Menus/OptionsMenu/OptionControl/OptionControl.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,7 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://c01ayjblhcg1t"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b6bl3n5mp3m1e" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/list_option_control.tscn" id="1_jqwiw"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/vector_2_list_option_control.gd" id="2_w33vs"]
|
||||||
|
|
||||||
|
[node name="OptionControl" instance=ExtResource("1_jqwiw")]
|
||||||
|
script = ExtResource("2_w33vs")
|
@ -0,0 +1,34 @@
|
|||||||
|
class_name VideoOptionsMenu
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
func _preselect_resolution(window : Window):
|
||||||
|
%ResolutionControl.value = window.size
|
||||||
|
|
||||||
|
func _update_resolution_options_enabled(window : Window):
|
||||||
|
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):
|
||||||
|
%FullscreenControl.value = AppSettings.is_fullscreen(window)
|
||||||
|
_preselect_resolution(window)
|
||||||
|
_update_resolution_options_enabled(window)
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
var window : Window = get_window()
|
||||||
|
_update_ui(window)
|
||||||
|
window.connect("size_changed", _preselect_resolution.bind(window))
|
||||||
|
|
||||||
|
func _on_fullscreen_control_setting_changed(value):
|
||||||
|
var window : Window = get_window()
|
||||||
|
AppSettings.set_fullscreen_enabled(value, window)
|
||||||
|
_update_resolution_options_enabled(window)
|
||||||
|
|
||||||
|
func _on_resolution_control_setting_changed(value):
|
||||||
|
AppSettings.set_resolution(value, get_window())
|
@ -0,0 +1,44 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://b2numvphf2kau"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/video/video_options_menu.gd" id="1"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scripts/capture_focus.gd" id="2_dgrai"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bsxh6v7j0257h" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/toggle_option_control.tscn" id="3_uded6"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://c01ayjblhcg1t" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/vector_2_list_option_control.tscn" id="4_gwtfq"]
|
||||||
|
|
||||||
|
[node name="Video" type="MarginContainer"]
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
theme_override_constants/margin_top = 24
|
||||||
|
theme_override_constants/margin_bottom = 24
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
|
custom_minimum_size = Vector2(400, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
alignment = 1
|
||||||
|
script = ExtResource("2_dgrai")
|
||||||
|
search_depth = 2
|
||||||
|
|
||||||
|
[node name="FullscreenControl" parent="VBoxContainer" instance=ExtResource("3_uded6")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
option_name = "Fullscreen"
|
||||||
|
option_section = 3
|
||||||
|
key = "FullscreenEnabled"
|
||||||
|
section = "VideoSettings"
|
||||||
|
|
||||||
|
[node name="ResolutionControl" parent="VBoxContainer" instance=ExtResource("4_gwtfq")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Select a screen size"
|
||||||
|
option_values = [Vector2i(640, 360), Vector2i(960, 540), Vector2i(1024, 576), Vector2i(1280, 720), Vector2i(1600, 900), Vector2i(1920, 1080), Vector2i(2048, 1152), Vector2i(2560, 1440), Vector2i(3200, 1800), Vector2i(3840, 2160)]
|
||||||
|
option_titles = Array[String](["640 x 360", "960 x 540", "1024 x 576", "1280 x 720", "1600 x 900", "1920 x 1080", "2048 x 1152", "2560 x 1440", "3200 x 1800", "3840 x 2160"])
|
||||||
|
option_name = "Resolution"
|
||||||
|
option_section = 3
|
||||||
|
key = "ScreenResolution"
|
||||||
|
section = "VideoSettings"
|
||||||
|
|
||||||
|
[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"]
|
@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene format=3 uid="uid://bkcsjsk2ciff"]
|
||||||
|
|
||||||
|
[node name="BackgroundMusicPlayer" type="AudioStreamPlayer"]
|
||||||
|
process_mode = 3
|
||||||
|
autoplay = true
|
||||||
|
bus = &"Music"
|
@ -0,0 +1,27 @@
|
|||||||
|
class_name AppLog
|
||||||
|
extends Node
|
||||||
|
## Logs application version and opened count through [Config].
|
||||||
|
|
||||||
|
const APP_LOG_SECTION = "AppLog"
|
||||||
|
const APP_OPENED = "AppOpened"
|
||||||
|
const FIRST_VERSION_OPENED = "FirstVersionOpened"
|
||||||
|
const LAST_VERSION_OPENED = "LastVersionOpened"
|
||||||
|
const UNKNOWN_VERSION = "unknown"
|
||||||
|
|
||||||
|
static func app_opened() -> void:
|
||||||
|
var total_app_opened = Config.get_config(APP_LOG_SECTION, APP_OPENED, 0)
|
||||||
|
total_app_opened += 1
|
||||||
|
Config.set_config(APP_LOG_SECTION, APP_OPENED, total_app_opened)
|
||||||
|
|
||||||
|
static func set_first_version_opened(value : String) -> void:
|
||||||
|
var first_version_opened = Config.get_config(APP_LOG_SECTION, FIRST_VERSION_OPENED, UNKNOWN_VERSION)
|
||||||
|
if first_version_opened != UNKNOWN_VERSION:
|
||||||
|
return
|
||||||
|
Config.set_config(APP_LOG_SECTION, FIRST_VERSION_OPENED, value)
|
||||||
|
|
||||||
|
static func set_last_version_opened(value : String) -> void:
|
||||||
|
Config.set_config(APP_LOG_SECTION, LAST_VERSION_OPENED, value)
|
||||||
|
|
||||||
|
static func version_opened(version : String) -> void:
|
||||||
|
set_first_version_opened(version)
|
||||||
|
set_last_version_opened(version)
|
@ -0,0 +1,158 @@
|
|||||||
|
class_name AppSettings
|
||||||
|
extends Node
|
||||||
|
## Interface to read/write general application settings through [Config].
|
||||||
|
|
||||||
|
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_ENABLED = &'FullscreenEnabled'
|
||||||
|
const SCREEN_RESOLUTION = &'ScreenResolution'
|
||||||
|
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 Config.get_config(INPUT_SECTION, action_name, default)
|
||||||
|
|
||||||
|
static func set_config_input_events(action_name : String, inputs : Array) -> void:
|
||||||
|
Config.set_config(INPUT_SECTION, action_name, inputs)
|
||||||
|
|
||||||
|
static func _clear_config_input_events():
|
||||||
|
Config.erase_section(INPUT_SECTION)
|
||||||
|
|
||||||
|
static func remove_action_input_event(action_name : String, input_event : InputEvent):
|
||||||
|
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):
|
||||||
|
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():
|
||||||
|
Config.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_")
|
||||||
|
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():
|
||||||
|
for bus_iter in AudioServer.bus_count:
|
||||||
|
var bus_name : String = get_audio_bus_name(bus_iter)
|
||||||
|
var bus_volume : float = get_bus_volume(bus_iter)
|
||||||
|
initial_bus_volumes.append(bus_volume)
|
||||||
|
bus_volume = Config.get_config(AUDIO_SECTION, bus_name, bus_volume)
|
||||||
|
if is_nan(bus_volume):
|
||||||
|
bus_volume = 1.0
|
||||||
|
Config.set_config(AUDIO_SECTION, bus_name, bus_volume)
|
||||||
|
set_bus_volume(bus_iter, bus_volume)
|
||||||
|
var mute_audio_flag : bool = is_muted()
|
||||||
|
mute_audio_flag = Config.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) -> void:
|
||||||
|
if value.x == 0 or value.y == 0:
|
||||||
|
return
|
||||||
|
window.size = 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
|
||||||
|
current_resolution = Config.get_config(VIDEO_SECTION, SCREEN_RESOLUTION, current_resolution)
|
||||||
|
return current_resolution
|
||||||
|
|
||||||
|
static func set_video_from_config(window : Window) -> void:
|
||||||
|
var fullscreen_enabled : bool = is_fullscreen(window)
|
||||||
|
fullscreen_enabled = Config.get_config(VIDEO_SECTION, FULLSCREEN_ENABLED, fullscreen_enabled)
|
||||||
|
set_fullscreen_enabled(fullscreen_enabled, window)
|
||||||
|
if not (fullscreen_enabled or OS.has_feature("web")):
|
||||||
|
var current_resolution : Vector2i = get_resolution(window)
|
||||||
|
set_resolution(current_resolution, 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,49 @@
|
|||||||
|
class_name CaptureFocus
|
||||||
|
extends Container
|
||||||
|
## Node that captures UI focus for joypad users.
|
||||||
|
##
|
||||||
|
## This script assists with capturing UI focus for joypad users when
|
||||||
|
## opening, closing, or switching between menus.
|
||||||
|
## When attached to a node, it will check if it was changed to visible
|
||||||
|
## and a joypad is being used. 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.
|
||||||
|
@export var search_depth : int = 1
|
||||||
|
@export var lock : bool = false :
|
||||||
|
set(value):
|
||||||
|
lock = value
|
||||||
|
if not lock:
|
||||||
|
update_focus()
|
||||||
|
|
||||||
|
func _focus_first_search(control_node : Control, levels : int = 1):
|
||||||
|
if control_node == null or !control_node.is_visible_in_tree():
|
||||||
|
return false
|
||||||
|
if control_node.focus_mode == FOCUS_ALL:
|
||||||
|
control_node.grab_focus()
|
||||||
|
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
|
||||||
|
|
||||||
|
func focus_first():
|
||||||
|
_focus_first_search(self, search_depth)
|
||||||
|
|
||||||
|
func update_focus():
|
||||||
|
if lock : return
|
||||||
|
if _is_visible_and_joypad():
|
||||||
|
focus_first()
|
||||||
|
|
||||||
|
func _is_visible_and_joypad():
|
||||||
|
return is_visible_in_tree() and Input.get_connected_joypads().size() > 0
|
||||||
|
|
||||||
|
func _on_visibility_changed():
|
||||||
|
call_deferred("update_focus")
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
if is_inside_tree():
|
||||||
|
update_focus()
|
||||||
|
connect("visibility_changed", _on_visibility_changed)
|
@ -0,0 +1,58 @@
|
|||||||
|
class_name Config
|
||||||
|
extends Node
|
||||||
|
## Interface for a single configuration file through [ConfigFile].
|
||||||
|
|
||||||
|
const CONFIG_FILE_LOCATION := "user://config.cfg"
|
||||||
|
const DEFAULT_CONFIG_FILE_LOCATION := "res://default_config.cfg"
|
||||||
|
|
||||||
|
static var config_file : ConfigFile
|
||||||
|
|
||||||
|
static func _init():
|
||||||
|
load_config_file()
|
||||||
|
|
||||||
|
static func _save_config_file() -> void:
|
||||||
|
var save_error : int = config_file.save(CONFIG_FILE_LOCATION)
|
||||||
|
if save_error:
|
||||||
|
print("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 load_default_error : int = config_file.load(DEFAULT_CONFIG_FILE_LOCATION)
|
||||||
|
if load_default_error:
|
||||||
|
print("loading default config file failed with error %d" % load_default_error)
|
||||||
|
var save_error : int = config_file.save(CONFIG_FILE_LOCATION)
|
||||||
|
if save_error:
|
||||||
|
print("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):
|
||||||
|
load_config_file()
|
||||||
|
return config_file.has_section(section)
|
||||||
|
|
||||||
|
static func erase_section(section: String):
|
||||||
|
if has_section(section):
|
||||||
|
config_file.erase_section(section)
|
||||||
|
_save_config_file()
|
||||||
|
|
||||||
|
static func erase_section_key(section: String, key: String):
|
||||||
|
if has_section(section):
|
||||||
|
config_file.erase_section_key(section, key)
|
||||||
|
_save_config_file()
|
||||||
|
|
||||||
|
static func get_section_keys(section: String):
|
||||||
|
load_config_file()
|
||||||
|
if config_file.has_section(section):
|
||||||
|
return config_file.get_section_keys(section)
|
||||||
|
return PackedStringArray()
|
@ -0,0 +1,70 @@
|
|||||||
|
class_name InputEventHelper
|
||||||
|
extends Node
|
||||||
|
## Helper class for organizing constants related to [InputEvent].
|
||||||
|
|
||||||
|
const JOYSTICK_LEFT_NAME = "Left Gamepad Joystick"
|
||||||
|
const JOYSTICK_RIGHT_NAME = "Right Gamepad Joystick"
|
||||||
|
const D_PAD_NAME = "Gamepad D-pad"
|
||||||
|
|
||||||
|
const JOY_BUTTON_NAMES : Dictionary = {
|
||||||
|
JOY_BUTTON_A: "A Gamepad Button",
|
||||||
|
JOY_BUTTON_B: "B Gamepad Button",
|
||||||
|
JOY_BUTTON_X: "X Gamepad Button",
|
||||||
|
JOY_BUTTON_Y: "Y Gamepad Button",
|
||||||
|
JOY_BUTTON_LEFT_SHOULDER: "Left Shoulder Gamepad Button",
|
||||||
|
JOY_BUTTON_RIGHT_SHOULDER: "Right Shoulder Gamepad Button",
|
||||||
|
JOY_BUTTON_LEFT_STICK: "Left Stick Gamepad Button",
|
||||||
|
JOY_BUTTON_RIGHT_STICK: "Right Stick Gamepad Button",
|
||||||
|
JOY_BUTTON_START : "Start Gamepad Button",
|
||||||
|
JOY_BUTTON_GUIDE : "Guide Gamepad Button",
|
||||||
|
JOY_BUTTON_BACK : "Back Gamepad Button",
|
||||||
|
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 Gamepad Button",
|
||||||
|
JOY_AXIS_TRIGGER_RIGHT: "Right Trigger Gamepad Button",
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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()
|
@ -0,0 +1,183 @@
|
|||||||
|
class_name MusicController
|
||||||
|
extends Node
|
||||||
|
## Controller for music playback across scenes.
|
||||||
|
##
|
||||||
|
## This node persistently checks for stream players added to the scene tree.
|
||||||
|
## It detects stream players that match the audio bus and have autoplay on.
|
||||||
|
## It then reparents the stream players to itself, and handles blending.
|
||||||
|
## The expected use-case is to attach this script to an autoloaded scene.
|
||||||
|
|
||||||
|
const BLEND_BUS_PREFIX : String = "Blend"
|
||||||
|
const MAX_DEPTH = 16
|
||||||
|
const MINIMUM_VOLUME_DB = -80
|
||||||
|
|
||||||
|
## Detect stream players with matching audio bus.
|
||||||
|
@export var audio_bus : StringName = &"Music"
|
||||||
|
|
||||||
|
@export_group("Blending")
|
||||||
|
@export var fade_out_duration : float = 0.0 :
|
||||||
|
set(value):
|
||||||
|
fade_out_duration = value
|
||||||
|
if fade_out_duration < 0:
|
||||||
|
fade_out_duration = 0
|
||||||
|
|
||||||
|
@export var fade_in_duration : float = 0.0 :
|
||||||
|
set(value):
|
||||||
|
fade_in_duration = value
|
||||||
|
if fade_in_duration < 0:
|
||||||
|
fade_in_duration = 0
|
||||||
|
|
||||||
|
## Matched stream players with no stream set will stop current playback.
|
||||||
|
@export var empty_streams_stop_player : bool = true
|
||||||
|
|
||||||
|
var music_stream_player : AudioStreamPlayer
|
||||||
|
var blend_audio_bus : StringName
|
||||||
|
var blend_audio_bus_idx : int
|
||||||
|
|
||||||
|
func fade_out( duration : float = 0.0 ):
|
||||||
|
if not is_zero_approx(duration):
|
||||||
|
music_stream_player.bus = audio_bus
|
||||||
|
var tween = create_tween()
|
||||||
|
tween.tween_property(music_stream_player, "volume_db", MINIMUM_VOLUME_DB, duration)
|
||||||
|
return tween
|
||||||
|
|
||||||
|
func _set_sub_audio_volume_db(sub_volume_db : float):
|
||||||
|
AudioServer.set_bus_volume_db(blend_audio_bus_idx, sub_volume_db)
|
||||||
|
|
||||||
|
func fade_in( duration : float = 0.0 ):
|
||||||
|
if not is_zero_approx(duration):
|
||||||
|
music_stream_player.bus = blend_audio_bus
|
||||||
|
AudioServer.set_bus_volume_db(blend_audio_bus_idx, MINIMUM_VOLUME_DB)
|
||||||
|
var tween = create_tween()
|
||||||
|
tween.tween_method(_set_sub_audio_volume_db, MINIMUM_VOLUME_DB, 0, duration)
|
||||||
|
return tween
|
||||||
|
|
||||||
|
func blend_to( target_volume_db : float, duration : float = 0.0 ):
|
||||||
|
if not is_zero_approx(duration):
|
||||||
|
var tween = create_tween()
|
||||||
|
tween.tween_property(music_stream_player, "volume_db", target_volume_db, duration)
|
||||||
|
return tween
|
||||||
|
music_stream_player.volume_db = target_volume_db
|
||||||
|
|
||||||
|
func stop():
|
||||||
|
if music_stream_player == null:
|
||||||
|
return
|
||||||
|
music_stream_player.stop()
|
||||||
|
|
||||||
|
func play( playback_position : float = 0.0 ):
|
||||||
|
if music_stream_player == null:
|
||||||
|
return
|
||||||
|
if is_zero_approx(playback_position) and not music_stream_player.playing:
|
||||||
|
music_stream_player.play()
|
||||||
|
else:
|
||||||
|
music_stream_player.play(playback_position)
|
||||||
|
|
||||||
|
func _fade_out_and_free():
|
||||||
|
if music_stream_player == null:
|
||||||
|
return
|
||||||
|
var stream_player = music_stream_player
|
||||||
|
var tween = fade_out( fade_out_duration )
|
||||||
|
if tween != null:
|
||||||
|
await( tween.finished )
|
||||||
|
stream_player.queue_free()
|
||||||
|
|
||||||
|
func _play_and_fade_in():
|
||||||
|
play()
|
||||||
|
fade_in( fade_in_duration )
|
||||||
|
|
||||||
|
func _is_matching_stream( stream_player : AudioStreamPlayer ) -> bool:
|
||||||
|
if stream_player.bus != audio_bus:
|
||||||
|
return false
|
||||||
|
if music_stream_player == null:
|
||||||
|
return false
|
||||||
|
return music_stream_player.stream == stream_player.stream
|
||||||
|
|
||||||
|
func _connect_stream_on_tree_exiting( stream_player : AudioStreamPlayer ):
|
||||||
|
if not stream_player.tree_exiting.is_connected(_on_removed_music_player.bind(stream_player)):
|
||||||
|
stream_player.tree_exiting.connect(_on_removed_music_player.bind(stream_player))
|
||||||
|
|
||||||
|
func _blend_and_remove_stream_player( stream_player : AudioStreamPlayer ):
|
||||||
|
var playback_position := music_stream_player.get_playback_position() + AudioServer.get_time_since_last_mix()
|
||||||
|
var old_stream_player = music_stream_player
|
||||||
|
music_stream_player = stream_player
|
||||||
|
music_stream_player.bus = blend_audio_bus
|
||||||
|
play(playback_position)
|
||||||
|
old_stream_player.stop()
|
||||||
|
old_stream_player.queue_free()
|
||||||
|
_connect_stream_on_tree_exiting(music_stream_player)
|
||||||
|
|
||||||
|
func _blend_and_connect_stream_player( stream_player : AudioStreamPlayer ):
|
||||||
|
stream_player.bus = blend_audio_bus
|
||||||
|
_fade_out_and_free()
|
||||||
|
music_stream_player = stream_player
|
||||||
|
_play_and_fade_in()
|
||||||
|
_connect_stream_on_tree_exiting(music_stream_player)
|
||||||
|
|
||||||
|
func play_stream_player( stream_player : AudioStreamPlayer ):
|
||||||
|
if stream_player == music_stream_player : return
|
||||||
|
if stream_player.stream == null and not empty_streams_stop_player:
|
||||||
|
return
|
||||||
|
if _is_matching_stream(stream_player) :
|
||||||
|
_blend_and_remove_stream_player(stream_player)
|
||||||
|
else:
|
||||||
|
_blend_and_connect_stream_player(stream_player)
|
||||||
|
|
||||||
|
func get_stream_player( audio_stream : AudioStream ) -> AudioStreamPlayer:
|
||||||
|
var stream_player := AudioStreamPlayer.new()
|
||||||
|
stream_player.stream = audio_stream
|
||||||
|
stream_player.bus = audio_bus
|
||||||
|
add_child(stream_player)
|
||||||
|
return stream_player
|
||||||
|
|
||||||
|
func play_stream( audio_stream : AudioStream ) -> AudioStreamPlayer:
|
||||||
|
var stream_player := get_stream_player(audio_stream)
|
||||||
|
stream_player.play.call_deferred()
|
||||||
|
play_stream_player( stream_player )
|
||||||
|
return stream_player
|
||||||
|
|
||||||
|
func _clone_music_player( stream_player : AudioStreamPlayer ):
|
||||||
|
var playback_position := stream_player.get_playback_position() + AudioServer.get_time_since_last_mix()
|
||||||
|
var audio_stream := stream_player.stream
|
||||||
|
music_stream_player = get_stream_player(audio_stream)
|
||||||
|
music_stream_player.volume_db = stream_player.volume_db
|
||||||
|
music_stream_player.max_polyphony = stream_player.max_polyphony
|
||||||
|
music_stream_player.pitch_scale = stream_player.pitch_scale
|
||||||
|
music_stream_player.play.call_deferred(playback_position)
|
||||||
|
|
||||||
|
func _reparent_music_player( stream_player : AudioStreamPlayer ):
|
||||||
|
var playback_position := stream_player.get_playback_position() + AudioServer.get_time_since_last_mix()
|
||||||
|
stream_player.owner = null
|
||||||
|
stream_player.reparent.call_deferred(self)
|
||||||
|
stream_player.play.call_deferred(playback_position)
|
||||||
|
|
||||||
|
func _node_matches_checks( node : Node ) -> bool:
|
||||||
|
return node is AudioStreamPlayer and node.autoplay and node.bus == audio_bus
|
||||||
|
|
||||||
|
func _on_removed_music_player( node: Node ) -> void:
|
||||||
|
if music_stream_player == node:
|
||||||
|
if node.owner == null:
|
||||||
|
_clone_music_player(node)
|
||||||
|
else:
|
||||||
|
_reparent_music_player(node)
|
||||||
|
if node.tree_exiting.is_connected(_on_removed_music_player.bind(node)):
|
||||||
|
node.tree_exiting.disconnect(_on_removed_music_player.bind(node))
|
||||||
|
|
||||||
|
func _on_added_music_player( node: Node ) -> void:
|
||||||
|
if node == music_stream_player : return
|
||||||
|
if not (_node_matches_checks(node)) : return
|
||||||
|
play_stream_player(node)
|
||||||
|
|
||||||
|
func _enter_tree() -> void:
|
||||||
|
AudioServer.add_bus()
|
||||||
|
blend_audio_bus_idx = AudioServer.bus_count - 1
|
||||||
|
blend_audio_bus = AppSettings.SYSTEM_BUS_NAME_PREFIX + BLEND_BUS_PREFIX + audio_bus
|
||||||
|
AudioServer.set_bus_send(blend_audio_bus_idx, audio_bus)
|
||||||
|
AudioServer.set_bus_name(blend_audio_bus_idx, blend_audio_bus)
|
||||||
|
var tree_node = get_tree()
|
||||||
|
if not tree_node.node_added.is_connected(_on_added_music_player):
|
||||||
|
tree_node.node_added.connect(_on_added_music_player)
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
var tree_node = get_tree()
|
||||||
|
if tree_node.node_added.is_connected(_on_added_music_player):
|
||||||
|
tree_node.node_added.disconnect(_on_added_music_player)
|
@ -0,0 +1,167 @@
|
|||||||
|
class_name UISoundController
|
||||||
|
extends Node
|
||||||
|
## Controller for managing all UI sounds in a scene from one place.
|
||||||
|
##
|
||||||
|
## This node manages all of the UI sounds under the provided node path.
|
||||||
|
## When attached just below the root node of a scene tree, it will manage
|
||||||
|
## all of the UI sounds in that scene.
|
||||||
|
|
||||||
|
const MAX_DEPTH = 16
|
||||||
|
|
||||||
|
@export var root_path : NodePath = ^".."
|
||||||
|
@export var audio_bus : StringName = &"SFX"
|
||||||
|
## Continually check any new nodes added to the scene tree.
|
||||||
|
@export var persistent : bool = true :
|
||||||
|
set(value):
|
||||||
|
persistent = value
|
||||||
|
_update_persistent_signals()
|
||||||
|
|
||||||
|
@export_group("Button Sounds")
|
||||||
|
@export var button_hovered : AudioStream
|
||||||
|
@export var button_focused : AudioStream
|
||||||
|
@export var button_pressed : AudioStream
|
||||||
|
|
||||||
|
@export_group("TabBar Sounds")
|
||||||
|
@export var tab_hovered : AudioStream
|
||||||
|
@export var tab_changed : AudioStream
|
||||||
|
@export var tab_selected : AudioStream
|
||||||
|
|
||||||
|
@export_group("Slider Sounds")
|
||||||
|
@export var slider_hovered : AudioStream
|
||||||
|
@export var slider_focused : AudioStream
|
||||||
|
@export var slider_drag_started : AudioStream
|
||||||
|
@export var slider_drag_ended : AudioStream
|
||||||
|
|
||||||
|
@export_group("LineEdit Sounds")
|
||||||
|
@export var line_hovered : AudioStream
|
||||||
|
@export var line_focused : AudioStream
|
||||||
|
@export var line_text_changed : AudioStream
|
||||||
|
@export var line_text_submitted : AudioStream
|
||||||
|
@export var line_text_change_rejected : AudioStream
|
||||||
|
|
||||||
|
@onready var root_node : Node = get_node(root_path)
|
||||||
|
|
||||||
|
var button_hovered_player : AudioStreamPlayer
|
||||||
|
var button_focused_player : AudioStreamPlayer
|
||||||
|
var button_pressed_player : AudioStreamPlayer
|
||||||
|
|
||||||
|
var tab_hovered_player : AudioStreamPlayer
|
||||||
|
var tab_changed_player : AudioStreamPlayer
|
||||||
|
var tab_selected_player : AudioStreamPlayer
|
||||||
|
|
||||||
|
var slider_hovered_player : AudioStreamPlayer
|
||||||
|
var slider_focused_player : AudioStreamPlayer
|
||||||
|
var slider_drag_started_player : AudioStreamPlayer
|
||||||
|
var slider_drag_ended_player : AudioStreamPlayer
|
||||||
|
|
||||||
|
var line_hovered_player : AudioStreamPlayer
|
||||||
|
var line_focused_player : AudioStreamPlayer
|
||||||
|
var line_text_changed_player : AudioStreamPlayer
|
||||||
|
var line_text_submitted_player : AudioStreamPlayer
|
||||||
|
var line_text_change_rejected_player : AudioStreamPlayer
|
||||||
|
|
||||||
|
func _update_persistent_signals():
|
||||||
|
if not is_inside_tree():
|
||||||
|
return
|
||||||
|
var tree_node = get_tree()
|
||||||
|
if persistent:
|
||||||
|
if not tree_node.node_added.is_connected(connect_ui_sounds):
|
||||||
|
tree_node.node_added.connect(connect_ui_sounds)
|
||||||
|
else:
|
||||||
|
if tree_node.node_added.is_connected(connect_ui_sounds):
|
||||||
|
tree_node.node_added.disconnect(connect_ui_sounds)
|
||||||
|
|
||||||
|
func _build_stream_player(stream : AudioStream, stream_name : String = ""):
|
||||||
|
var stream_player : AudioStreamPlayer
|
||||||
|
if stream != null:
|
||||||
|
stream_player = AudioStreamPlayer.new()
|
||||||
|
stream_player.stream = stream
|
||||||
|
stream_player.bus = audio_bus
|
||||||
|
stream_player.name = stream_name + "AudioStreamPlayer"
|
||||||
|
add_child(stream_player)
|
||||||
|
return stream_player
|
||||||
|
|
||||||
|
func _build_button_stream_players():
|
||||||
|
button_hovered_player = _build_stream_player(button_hovered, "ButtonHovered")
|
||||||
|
button_focused_player = _build_stream_player(button_focused, "ButtonFocused")
|
||||||
|
button_pressed_player = _build_stream_player(button_pressed, "ButtonClicked")
|
||||||
|
|
||||||
|
func _build_tab_stream_players():
|
||||||
|
tab_hovered_player = _build_stream_player(tab_hovered, "TabHovered")
|
||||||
|
tab_changed_player = _build_stream_player(tab_changed, "TabChanged")
|
||||||
|
tab_selected_player = _build_stream_player(tab_selected, "TabSelected")
|
||||||
|
|
||||||
|
func _build_slider_stream_players():
|
||||||
|
slider_hovered_player = _build_stream_player(slider_hovered, "SliderHovered")
|
||||||
|
slider_focused_player = _build_stream_player(slider_focused, "SliderFocused")
|
||||||
|
slider_drag_started_player = _build_stream_player(slider_drag_started, "SliderDragStarted")
|
||||||
|
slider_drag_ended_player = _build_stream_player(slider_drag_ended, "SliderDragEnded")
|
||||||
|
|
||||||
|
func _build_line_stream_players():
|
||||||
|
line_hovered_player = _build_stream_player(line_hovered, "LineHovered")
|
||||||
|
line_focused_player = _build_stream_player(line_focused, "LineFocused")
|
||||||
|
line_text_changed_player = _build_stream_player(line_text_changed, "LineTextChanged")
|
||||||
|
line_text_submitted_player = _build_stream_player(line_text_submitted, "LineTextSubmitted")
|
||||||
|
line_text_change_rejected_player = _build_stream_player(line_text_change_rejected, "LineTextChangeRejected")
|
||||||
|
|
||||||
|
func _build_all_stream_players():
|
||||||
|
_build_button_stream_players()
|
||||||
|
_build_tab_stream_players()
|
||||||
|
_build_slider_stream_players()
|
||||||
|
_build_line_stream_players()
|
||||||
|
|
||||||
|
func _play_stream(stream_player : AudioStreamPlayer):
|
||||||
|
if not stream_player.is_inside_tree():
|
||||||
|
return
|
||||||
|
stream_player.play()
|
||||||
|
|
||||||
|
func _tab_event_play_stream(_tab_idx : int, stream_player : AudioStreamPlayer):
|
||||||
|
_play_stream(stream_player)
|
||||||
|
|
||||||
|
func _slider_drag_ended_play_stream(_value_changed : bool, stream_player : AudioStreamPlayer):
|
||||||
|
_play_stream(stream_player)
|
||||||
|
|
||||||
|
func _line_event_play_stream(_new_text : String, stream_player : AudioStreamPlayer):
|
||||||
|
_play_stream(stream_player)
|
||||||
|
|
||||||
|
func _connect_stream_player(node : Node, stream_player : AudioStreamPlayer, signal_name : StringName, callable : Callable) -> void:
|
||||||
|
if stream_player != null and not node.is_connected(signal_name, callable.bind(stream_player)):
|
||||||
|
node.connect(signal_name, callable.bind(stream_player))
|
||||||
|
|
||||||
|
func connect_ui_sounds(node: Node) -> void:
|
||||||
|
if node is Button:
|
||||||
|
_connect_stream_player(node, button_hovered_player, &"mouse_entered", _play_stream)
|
||||||
|
_connect_stream_player(node, button_focused_player, &"focus_entered", _play_stream)
|
||||||
|
_connect_stream_player(node, button_pressed_player, &"pressed", _play_stream)
|
||||||
|
elif node is TabBar:
|
||||||
|
_connect_stream_player(node, tab_hovered_player, &"tab_hovered", _tab_event_play_stream)
|
||||||
|
_connect_stream_player(node, tab_changed_player, &"tab_changed", _tab_event_play_stream)
|
||||||
|
_connect_stream_player(node, tab_selected_player, &"tab_selected", _tab_event_play_stream)
|
||||||
|
elif node is Slider:
|
||||||
|
_connect_stream_player(node, slider_hovered_player, &"mouse_entered", _play_stream)
|
||||||
|
_connect_stream_player(node, slider_focused_player, &"focus_entered", _play_stream)
|
||||||
|
_connect_stream_player(node, slider_drag_started_player, &"drag_started", _play_stream)
|
||||||
|
_connect_stream_player(node, slider_drag_ended_player, &"drag_ended", _slider_drag_ended_play_stream)
|
||||||
|
elif node is LineEdit:
|
||||||
|
_connect_stream_player(node, line_hovered_player, &"mouse_entered", _play_stream)
|
||||||
|
_connect_stream_player(node, line_focused_player, &"focus_entered", _play_stream)
|
||||||
|
_connect_stream_player(node, line_text_changed_player, &"text_changed", _line_event_play_stream)
|
||||||
|
_connect_stream_player(node, line_text_submitted_player, &"text_submitted", _line_event_play_stream)
|
||||||
|
_connect_stream_player(node, line_text_change_rejected_player, &"text_change_rejected", _line_event_play_stream)
|
||||||
|
|
||||||
|
func _recursive_connect_ui_sounds(current_node: Node, current_depth : int = 0) -> void:
|
||||||
|
if current_depth >= MAX_DEPTH:
|
||||||
|
return
|
||||||
|
for node in current_node.get_children():
|
||||||
|
connect_ui_sounds(node)
|
||||||
|
_recursive_connect_ui_sounds(node, current_depth + 1)
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_build_all_stream_players()
|
||||||
|
_recursive_connect_ui_sounds(root_node)
|
||||||
|
persistent = persistent
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
var tree_node = get_tree()
|
||||||
|
if tree_node.node_added.is_connected(connect_ui_sounds):
|
||||||
|
tree_node.node_added.disconnect(connect_ui_sounds)
|
@ -0,0 +1,72 @@
|
|||||||
|
keys,en,fr
|
||||||
|
|
||||||
|
___ MAIN MENU,,
|
||||||
|
|
||||||
|
Title,Title,Titre
|
||||||
|
Subtitle,Subtitle,Sous-titre
|
||||||
|
Play,Play,Jouer
|
||||||
|
Options,Options,Options
|
||||||
|
Credits,Credits,Crédits
|
||||||
|
Exit,Exit,Quitter
|
||||||
|
|
||||||
|
___ LOADING SCREEN,,
|
||||||
|
Loading...,Loading...,Chargement...
|
||||||
|
|
||||||
|
___ DIALOGS IN GAME,,
|
||||||
|
|
||||||
|
You lose.,You lose.,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
|
||||||
|
|
||||||
|
___ 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,17 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="csv_translation"
|
||||||
|
type="Translation"
|
||||||
|
uid="uid://i6ihop1vp2ei"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
files=["res://addons/maaacks_menus_template/base/translations/menus_translations.en.translation", "res://addons/maaacks_menus_template/base/translations/menus_translations.fr.translation"]
|
||||||
|
|
||||||
|
source_file="res://addons/maaacks_menus_template/base/translations/menus_translations.csv"
|
||||||
|
dest_files=["res://addons/maaacks_menus_template/base/translations/menus_translations.en.translation", "res://addons/maaacks_menus_template/base/translations/menus_translations.fr.translation"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress=true
|
||||||
|
delimiter=0
|
@ -0,0 +1,13 @@
|
|||||||
|
# Screenshots
|
||||||
|
|
||||||
|
![Main Menu](/addons/maaacks_menus_template/media/screenshot-3-1.png)
|
||||||
|
![Input Controls](/addons/maaacks_menus_template/media/screenshot-3-2.png)
|
||||||
|
![Key Rebinding](/addons/maaacks_menus_template/media/screenshot-4-1.png)
|
||||||
|
![Key Confirmation](/addons/maaacks_menus_template/media/screenshot-4-2.png)
|
||||||
|
![Audio Controls](/addons/maaacks_menus_template/media/screenshot-3-4.png)
|
||||||
|
![Video Controls](/addons/maaacks_menus_template/media/screenshot-4-3.png)
|
||||||
|
![Credits Screen](/addons/maaacks_menus_template/media/screenshot-3-5.png)
|
||||||
|
![Loading Screen 50% Loaded](/addons/maaacks_menus_template/media/screenshot-3-7.png)
|
||||||
|
![Loading Screen Still Loading](/addons/maaacks_menus_template/media/screenshot-3-8.png)
|
||||||
|
![Loading Screen Error Message](/addons/maaacks_menus_template/media/screenshot-3-9.png)
|
||||||
|
![Loading Screen Loading Complete](/addons/maaacks_menus_template/media/screenshot-3-10.png)
|
@ -0,0 +1,6 @@
|
|||||||
|
# Videos
|
||||||
|
|
||||||
|
[![Quick Intro Video](https://img.youtube.com/vi/U9CB3vKINVw/hqdefault.jpg)](https://youtu.be/U9CB3vKINVw)
|
||||||
|
[![Installation Video](https://img.youtube.com/vi/-QWJnZ8bVdk/hqdefault.jpg)](https://youtu.be/-QWJnZ8bVdk)
|
||||||
|
[![UI Theming (1) Video](https://img.youtube.com/vi/SBE4icfXYRA/hqdefault.jpg)](https://youtu.be/SBE4icfXYRA)
|
||||||
|
[![UI Theming (2) Video](https://img.youtube.com/vi/wCc2QUnaBKo/hqdefault.jpg)](https://youtu.be/wCc2QUnaBKo)
|
@ -0,0 +1,32 @@
|
|||||||
|
# Attribution
|
||||||
|
## Collaborators
|
||||||
|
|
||||||
|
### Role
|
||||||
|
Person 1
|
||||||
|
Person 2
|
||||||
|
[Person w/ Link]()
|
||||||
|
|
||||||
|
|
||||||
|
## Sourced / Unaffiliated
|
||||||
|
### Asset Type
|
||||||
|
#### Use Case
|
||||||
|
Author: [Name]()
|
||||||
|
Source: [Domain : webpage.html]()
|
||||||
|
License: [License]()
|
||||||
|
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
#### Godot
|
||||||
|
Author: [Juan Linietsky, Ariel Manzur, and contributors](https://godotengine.org/contact)
|
||||||
|
Source: [godotengine.org](https://godotengine.org/)
|
||||||
|
License: [MIT License](https://github.com/godotengine/godot/blob/master/LICENSE.txt)
|
||||||
|
|
||||||
|
#### Git
|
||||||
|
Author: [Linus Torvalds](https://github.com/torvalds)
|
||||||
|
Source: [git-scm.com](https://git-scm.com/downloads)
|
||||||
|
License: [GNU General Public License version 2](https://opensource.org/licenses/GPL-2.0)
|
||||||
|
|
||||||
|
#### Godot Menus Template
|
||||||
|
Author: [Marek Belski](https://github.com/Maaack/Godot-Menus-Template/graphs/contributors)
|
||||||
|
Source: [github: Godot-Menus-Template](https://github.com/Maaack/Godot-Menus-Template)
|
||||||
|
License: [MIT License](LICENSE.txt)
|
@ -0,0 +1,2 @@
|
|||||||
|
@tool
|
||||||
|
extends Credits
|
@ -0,0 +1,42 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://c1g50h2avck3w"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://t2dui8ppm3a4" path="res://addons/maaacks_menus_template/base/scenes/credits/credits.tscn" id="1_n45le"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/examples/scenes/credits/credits.gd" id="2_q7msw"]
|
||||||
|
|
||||||
|
[node name="Credits" instance=ExtResource("1_n45le")]
|
||||||
|
script = ExtResource("2_q7msw")
|
||||||
|
attribution_file_path = "res://addons/maaacks_menus_template/examples/ATTRIBUTION.md"
|
||||||
|
|
||||||
|
[node name="CreditsLabel" parent="ScrollContainer/VBoxContainer" index="1"]
|
||||||
|
text = "[center][font_size=48]Collaborators[/font_size]
|
||||||
|
|
||||||
|
[font_size=32]Role[/font_size]
|
||||||
|
Person 1
|
||||||
|
Person 2
|
||||||
|
[url=]Person w/ Link[/url]
|
||||||
|
|
||||||
|
|
||||||
|
[font_size=48]Sourced / Unaffiliated[/font_size]
|
||||||
|
[font_size=32]Asset Type[/font_size]
|
||||||
|
[font_size=24]Use Case[/font_size]
|
||||||
|
Author: [url=]Name[/url]
|
||||||
|
Source: [url=]Domain : webpage.html[/url]
|
||||||
|
License: [url=]License[/url]
|
||||||
|
|
||||||
|
|
||||||
|
[font_size=48]Tools[/font_size]
|
||||||
|
[font_size=24]Godot[/font_size]
|
||||||
|
Author: [url=https://godotengine.org/contact]Juan Linietsky, Ariel Manzur, and contributors[/url]
|
||||||
|
Source: [url=https://godotengine.org/]godotengine.org[/url]
|
||||||
|
License: [url=https://github.com/godotengine/godot/blob/master/LICENSE.txt]MIT License[/url]
|
||||||
|
|
||||||
|
[font_size=24]Git[/font_size]
|
||||||
|
Author: [url=https://github.com/torvalds]Linus Torvalds[/url]
|
||||||
|
Source: [url=https://git-scm.com/downloads]git-scm.com[/url]
|
||||||
|
License: [url=https://opensource.org/licenses/GPL-2.0]GNU General Public License version 2[/url]
|
||||||
|
|
||||||
|
[font_size=24]Godot Menus Template[/font_size]
|
||||||
|
Author: [url=https://github.com/Maaack/Godot-Menus-Template/graphs/contributors]Marek Belski[/url]
|
||||||
|
Source: [url=https://github.com/Maaack/Godot-Menus-Template]github: Godot-Menus-Template[/url]
|
||||||
|
License: [url=LICENSE.txt]MIT License[/url]
|
||||||
|
[/center]"
|
@ -0,0 +1,37 @@
|
|||||||
|
@tool
|
||||||
|
extends Credits
|
||||||
|
|
||||||
|
@export_file("*.tscn") var main_menu_scene : String
|
||||||
|
@onready var init_mouse_filter = mouse_filter
|
||||||
|
|
||||||
|
func _end_reached():
|
||||||
|
%EndMessagePanel.show()
|
||||||
|
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||||
|
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||||
|
super._end_reached()
|
||||||
|
|
||||||
|
func _on_MenuButton_pressed():
|
||||||
|
SceneLoader.load_scene(main_menu_scene)
|
||||||
|
|
||||||
|
func _on_ExitButton_pressed():
|
||||||
|
get_tree().quit()
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
if main_menu_scene.is_empty():
|
||||||
|
%MenuButton.hide()
|
||||||
|
if OS.has_feature("web"):
|
||||||
|
%ExitButton.hide()
|
||||||
|
super._ready()
|
||||||
|
|
||||||
|
func reset():
|
||||||
|
super.reset()
|
||||||
|
%EndMessagePanel.hide()
|
||||||
|
mouse_filter = init_mouse_filter
|
||||||
|
|
||||||
|
func _unhandled_input(event):
|
||||||
|
if not enabled: return
|
||||||
|
if event.is_action_pressed("ui_cancel"):
|
||||||
|
if not %EndMessagePanel.visible:
|
||||||
|
_end_reached()
|
||||||
|
else:
|
||||||
|
get_tree().quit()
|
@ -0,0 +1,91 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://bagraegk311h0"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://c1g50h2avck3w" path="res://addons/maaacks_menus_template/examples/scenes/credits/credits.tscn" id="1_885d8"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/examples/scenes/end_credits/end_credits.gd" id="2_6pwj4"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/base/scripts/capture_focus.gd" id="3_aoilo"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bkcsjsk2ciff" path="res://addons/maaacks_menus_template/base/scenes/music_players/background_music_player.tscn" id="3_v7acr"]
|
||||||
|
|
||||||
|
[node name="EndCredits" instance=ExtResource("1_885d8")]
|
||||||
|
script = ExtResource("2_6pwj4")
|
||||||
|
main_menu_scene = "res://addons/maaacks_menus_template/examples/scenes/menus/main_menu/main_menu_with_animations.tscn"
|
||||||
|
|
||||||
|
[node name="BackgroundMusicPlayer" parent="." index="0" instance=ExtResource("3_v7acr")]
|
||||||
|
|
||||||
|
[node name="BackgroundColor" type="ColorRect" parent="." index="1"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
color = Color(0, 0, 0, 1)
|
||||||
|
|
||||||
|
[node name="BackgroundTextureRect" type="TextureRect" parent="." index="2"]
|
||||||
|
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="ScrollContainer" parent="." index="3"]
|
||||||
|
scroll_vertical = 0
|
||||||
|
|
||||||
|
[node name="CenterContainer" type="CenterContainer" parent="." index="4"]
|
||||||
|
layout_mode = 0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="EndMessagePanel" type="Panel" parent="CenterContainer" index="0"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
custom_minimum_size = Vector2(360, 120)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/EndMessagePanel" index="0"]
|
||||||
|
layout_mode = 0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
|
||||||
|
[node name="ThankPlayer" type="Label" parent="CenterContainer/EndMessagePanel/VBoxContainer" index="0"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
text = "Thanks for playing!"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="CenterContainer" type="CenterContainer" parent="CenterContainer/EndMessagePanel/VBoxContainer" index="1"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="CenterContainer/EndMessagePanel/VBoxContainer/CenterContainer" index="0"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_constants/separation = 24
|
||||||
|
script = ExtResource("3_aoilo")
|
||||||
|
|
||||||
|
[node name="ExitButton" type="Button" parent="CenterContainer/EndMessagePanel/VBoxContainer/CenterContainer/HBoxContainer" index="0"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(60, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
text = "Exit"
|
||||||
|
|
||||||
|
[node name="MenuButton" type="Button" parent="CenterContainer/EndMessagePanel/VBoxContainer/CenterContainer/HBoxContainer" index="1"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(60, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
text = "Menu"
|
||||||
|
|
||||||
|
[connection signal="pressed" from="CenterContainer/EndMessagePanel/VBoxContainer/CenterContainer/HBoxContainer/ExitButton" to="." method="_on_ExitButton_pressed"]
|
||||||
|
[connection signal="pressed" from="CenterContainer/EndMessagePanel/VBoxContainer/CenterContainer/HBoxContainer/MenuButton" to="." method="_on_MenuButton_pressed"]
|
@ -0,0 +1 @@
|
|||||||
|
extends LoadingScreen
|
@ -0,0 +1,7 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://ddeagbc67f52m"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://cd0jbh4metflb" path="res://addons/maaacks_menus_template/base/scenes/loading_screen/loading_screen.tscn" id="1_2dnq1"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/examples/scenes/loading_screen/loading_screen.gd" id="2_5h2y6"]
|
||||||
|
|
||||||
|
[node name="LoadingScreen" instance=ExtResource("1_2dnq1")]
|
||||||
|
script = ExtResource("2_5h2y6")
|
@ -0,0 +1,86 @@
|
|||||||
|
extends LoadingScreen
|
||||||
|
|
||||||
|
const QUADMESH_PLACEHOLDER = preload("res://addons/maaacks_menus_template/examples/scenes/loading_screen/quad_mesh_placeholder.tscn")
|
||||||
|
|
||||||
|
@export_dir var _spatial_shader_material_dir : String
|
||||||
|
@export_file("*.tscn") var _cache_shaders_scene : String
|
||||||
|
@export_group("Advanced")
|
||||||
|
@export var _matching_extensions : Array[String] = [".tres", ".material", ".res"]
|
||||||
|
@export var _ignore_subfolders : Array[String] = [".", ".."]
|
||||||
|
@export var _shader_delay_timer : float = 0.1
|
||||||
|
|
||||||
|
var _loading_shader_cache : bool = false
|
||||||
|
|
||||||
|
var _caching_progress : float = 0.0 :
|
||||||
|
set(value):
|
||||||
|
if value <= _caching_progress:
|
||||||
|
return
|
||||||
|
_caching_progress = value
|
||||||
|
update_total_loading_progress()
|
||||||
|
_reset_loading_stage()
|
||||||
|
|
||||||
|
func can_load_shader_cache():
|
||||||
|
return not _spatial_shader_material_dir.is_empty() and \
|
||||||
|
not _cache_shaders_scene.is_empty() and \
|
||||||
|
SceneLoader.is_loading_scene(_cache_shaders_scene)
|
||||||
|
|
||||||
|
func update_total_loading_progress():
|
||||||
|
var partial_total = _scene_loading_progress
|
||||||
|
if can_load_shader_cache():
|
||||||
|
partial_total += _caching_progress
|
||||||
|
partial_total /= 2
|
||||||
|
_total_loading_progress = partial_total
|
||||||
|
|
||||||
|
func _try_loading_next_scene():
|
||||||
|
if can_load_shader_cache() and not _loading_shader_cache:
|
||||||
|
_loading_shader_cache = true
|
||||||
|
_show_all_draw_passes_once()
|
||||||
|
if can_load_shader_cache() and _caching_progress < 1.0:
|
||||||
|
return
|
||||||
|
super._try_loading_next_scene()
|
||||||
|
|
||||||
|
func _show_all_draw_passes_once():
|
||||||
|
var all_materials = _traverse_folders(_spatial_shader_material_dir)
|
||||||
|
var total_material_count = all_materials.size()
|
||||||
|
var cached_material_count = 0
|
||||||
|
for material_path in all_materials:
|
||||||
|
_load_material(material_path)
|
||||||
|
cached_material_count += 1
|
||||||
|
_caching_progress = float(cached_material_count) / total_material_count
|
||||||
|
if _shader_delay_timer > 0:
|
||||||
|
await(get_tree().create_timer(_shader_delay_timer).timeout)
|
||||||
|
|
||||||
|
func _traverse_folders(dir_path:String) -> PackedStringArray:
|
||||||
|
var material_list:PackedStringArray = []
|
||||||
|
if not dir_path.ends_with("/"):
|
||||||
|
dir_path += "/"
|
||||||
|
var dir = DirAccess.open(dir_path)
|
||||||
|
if not dir:
|
||||||
|
push_error("failed to access the path ", dir_path)
|
||||||
|
return []
|
||||||
|
if dir.list_dir_begin() != OK:
|
||||||
|
push_error("failed to access the path ", dir_path)
|
||||||
|
return []
|
||||||
|
var file_name = dir.get_next()
|
||||||
|
while file_name != "":
|
||||||
|
if not dir.current_is_dir():
|
||||||
|
var matches : bool = false
|
||||||
|
for extension in _matching_extensions:
|
||||||
|
if file_name.ends_with(extension):
|
||||||
|
matches = true
|
||||||
|
break
|
||||||
|
if matches:
|
||||||
|
material_list.append(dir_path + file_name)
|
||||||
|
else:
|
||||||
|
var subfolder_name = file_name
|
||||||
|
if not subfolder_name in _ignore_subfolders:
|
||||||
|
material_list.append_array(_traverse_folders(dir_path + subfolder_name))
|
||||||
|
file_name = dir.get_next()
|
||||||
|
|
||||||
|
return material_list
|
||||||
|
|
||||||
|
func _load_material(path:String):
|
||||||
|
var material_shower = QUADMESH_PLACEHOLDER.instantiate()
|
||||||
|
var material := ResourceLoader.load(path) as Material
|
||||||
|
material_shower.set_surface_override_material(0, material)
|
||||||
|
%SpatialShaderTypeCaches.add_child(material_shower)
|
@ -0,0 +1,19 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://dvp3b2tdsbtmo"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://cd0jbh4metflb" path="res://addons/maaacks_menus_template/base/scenes/loading_screen/loading_screen.tscn" id="1_4k0mc"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/examples/scenes/loading_screen/loading_screen_with_shader_caching.gd" id="2_3xu6h"]
|
||||||
|
|
||||||
|
[node name="LoadingScreen" instance=ExtResource("1_4k0mc")]
|
||||||
|
script = ExtResource("2_3xu6h")
|
||||||
|
_spatial_shader_material_dir = ""
|
||||||
|
_cache_shaders_scene = "res://addons/maaacks_menus_template/examples/scenes/GameScene/GameUI.tscn"
|
||||||
|
_matching_extensions = Array[String]([".tres", ".material", ".res"])
|
||||||
|
_ignore_subfolders = Array[String]([".", ".."])
|
||||||
|
_shader_delay_timer = 0.1
|
||||||
|
|
||||||
|
[node name="SpatialShaderTypeCaches" type="Node3D" parent="." index="2"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
|
||||||
|
[node name="Camera3D" type="Camera3D" parent="SpatialShaderTypeCaches" index="0"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1.408)
|
||||||
|
current = true
|
@ -0,0 +1,7 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://b1nvjo55xpy3g"]
|
||||||
|
|
||||||
|
[sub_resource type="QuadMesh" id="QuadMesh_el52t"]
|
||||||
|
size = Vector2(0.001, 0.001)
|
||||||
|
|
||||||
|
[node name="MeshPlaceholder" type="MeshInstance3D"]
|
||||||
|
mesh = SubResource("QuadMesh_el52t")
|
@ -0,0 +1 @@
|
|||||||
|
extends MainMenu
|
@ -0,0 +1,11 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://byvydukidk6i2"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://c6k5nnpbypshi" path="res://addons/maaacks_menus_template/base/scenes/menus/main_menu/main_menu.tscn" id="1_pss7b"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/examples/scenes/menus/main_menu/main_menu.gd" id="2_lk0wa"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bdvdf5v87mmrr" path="res://addons/maaacks_menus_template/examples/scenes/menus/options_menu/master_options_menu_with_tabs.tscn" id="3_lqjmk"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://c1g50h2avck3w" path="res://addons/maaacks_menus_template/examples/scenes/credits/credits.tscn" id="3_vmius"]
|
||||||
|
|
||||||
|
[node name="MainMenu" instance=ExtResource("1_pss7b")]
|
||||||
|
script = ExtResource("2_lk0wa")
|
||||||
|
options_packed_scene = ExtResource("3_lqjmk")
|
||||||
|
credits_packed_scene = ExtResource("3_vmius")
|
@ -0,0 +1,35 @@
|
|||||||
|
extends MainMenu
|
||||||
|
|
||||||
|
var animation_state_machine : AnimationNodeStateMachinePlayback
|
||||||
|
|
||||||
|
func intro_done():
|
||||||
|
animation_state_machine.travel("OpenMainMenu")
|
||||||
|
|
||||||
|
func _is_in_intro():
|
||||||
|
return animation_state_machine.get_current_node() == "Intro"
|
||||||
|
|
||||||
|
func _event_is_mouse_button_released(event : InputEvent):
|
||||||
|
return event is InputEventMouseButton and not event.is_pressed()
|
||||||
|
|
||||||
|
func _event_skips_intro(event : InputEvent):
|
||||||
|
return event.is_action_released("ui_accept") or \
|
||||||
|
event.is_action_released("ui_select") or \
|
||||||
|
event.is_action_released("ui_cancel") or \
|
||||||
|
_event_is_mouse_button_released(event)
|
||||||
|
|
||||||
|
func _open_sub_menu(menu):
|
||||||
|
super._open_sub_menu(menu)
|
||||||
|
animation_state_machine.travel("OpenSubMenu")
|
||||||
|
|
||||||
|
func _close_sub_menu():
|
||||||
|
super._close_sub_menu()
|
||||||
|
animation_state_machine.travel("OpenMainMenu")
|
||||||
|
|
||||||
|
func _input(event):
|
||||||
|
if _is_in_intro() and _event_skips_intro(event):
|
||||||
|
intro_done()
|
||||||
|
super._input(event)
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
super._ready()
|
||||||
|
animation_state_machine = $MenuAnimationTree.get("parameters/playback")
|
@ -0,0 +1,374 @@
|
|||||||
|
[gd_scene load_steps=16 format=3 uid="uid://de7myf2ybhwk5"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://byvydukidk6i2" path="res://addons/maaacks_menus_template/examples/scenes/menus/main_menu/main_menu.tscn" id="1_0i2sc"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/examples/scenes/menus/main_menu/main_menu_with_animations.gd" id="2_ncvk7"]
|
||||||
|
|
||||||
|
[sub_resource type="Animation" id="1"]
|
||||||
|
resource_name = "Intro"
|
||||||
|
length = 2.4
|
||||||
|
tracks/0/type = "method"
|
||||||
|
tracks/0/imported = false
|
||||||
|
tracks/0/enabled = true
|
||||||
|
tracks/0/path = NodePath(".")
|
||||||
|
tracks/0/interp = 1
|
||||||
|
tracks/0/loop_wrap = true
|
||||||
|
tracks/0/keys = {
|
||||||
|
"times": PackedFloat32Array(2.4),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"values": [{
|
||||||
|
"args": [],
|
||||||
|
"method": &"intro_done"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
tracks/1/type = "value"
|
||||||
|
tracks/1/imported = false
|
||||||
|
tracks/1/enabled = true
|
||||||
|
tracks/1/path = NodePath("MenuContainer/VBoxContainer/TitlesMargin/TitlesContainer/Title:modulate")
|
||||||
|
tracks/1/interp = 1
|
||||||
|
tracks/1/loop_wrap = true
|
||||||
|
tracks/1/keys = {
|
||||||
|
"times": PackedFloat32Array(0, 0.8),
|
||||||
|
"transitions": PackedFloat32Array(1, 1),
|
||||||
|
"update": 0,
|
||||||
|
"values": [Color(1, 1, 1, 0), Color(1, 1, 1, 1)]
|
||||||
|
}
|
||||||
|
tracks/2/type = "value"
|
||||||
|
tracks/2/imported = false
|
||||||
|
tracks/2/enabled = true
|
||||||
|
tracks/2/path = NodePath("MenuContainer/VBoxContainer/TitlesMargin/TitlesContainer/SubTitle:modulate")
|
||||||
|
tracks/2/interp = 1
|
||||||
|
tracks/2/loop_wrap = true
|
||||||
|
tracks/2/keys = {
|
||||||
|
"times": PackedFloat32Array(0, 0.8, 1.6),
|
||||||
|
"transitions": PackedFloat32Array(1, 1, 1),
|
||||||
|
"update": 0,
|
||||||
|
"values": [Color(1, 1, 1, 0), Color(1, 1, 1, 0), Color(1, 1, 1, 1)]
|
||||||
|
}
|
||||||
|
tracks/3/type = "value"
|
||||||
|
tracks/3/imported = false
|
||||||
|
tracks/3/enabled = true
|
||||||
|
tracks/3/path = NodePath("MenuContainer/VBoxContainer/MenuMargin/MenuButtons:modulate")
|
||||||
|
tracks/3/interp = 1
|
||||||
|
tracks/3/loop_wrap = true
|
||||||
|
tracks/3/keys = {
|
||||||
|
"times": PackedFloat32Array(0, 1.6, 2.4),
|
||||||
|
"transitions": PackedFloat32Array(1, 1, 1),
|
||||||
|
"update": 0,
|
||||||
|
"values": [Color(1, 1, 1, 0), Color(1, 1, 1, 0), Color(1, 1, 1, 1)]
|
||||||
|
}
|
||||||
|
tracks/4/type = "value"
|
||||||
|
tracks/4/imported = false
|
||||||
|
tracks/4/enabled = true
|
||||||
|
tracks/4/path = NodePath("FlowControlContainer:mouse_filter")
|
||||||
|
tracks/4/interp = 1
|
||||||
|
tracks/4/loop_wrap = true
|
||||||
|
tracks/4/keys = {
|
||||||
|
"times": PackedFloat32Array(0, 2.4),
|
||||||
|
"transitions": PackedFloat32Array(1, 1),
|
||||||
|
"update": 1,
|
||||||
|
"values": [0, 2]
|
||||||
|
}
|
||||||
|
tracks/5/type = "value"
|
||||||
|
tracks/5/imported = false
|
||||||
|
tracks/5/enabled = true
|
||||||
|
tracks/5/path = NodePath("VersionNameLabel:modulate")
|
||||||
|
tracks/5/interp = 1
|
||||||
|
tracks/5/loop_wrap = true
|
||||||
|
tracks/5/keys = {
|
||||||
|
"times": PackedFloat32Array(0, 1.6, 2.4),
|
||||||
|
"transitions": PackedFloat32Array(1, 1, 1),
|
||||||
|
"update": 0,
|
||||||
|
"values": [Color(1, 1, 1, 0), Color(1, 1, 1, 0), Color(1, 1, 1, 1)]
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="Animation" id="6"]
|
||||||
|
resource_name = "OpenMainMenu"
|
||||||
|
length = 0.1
|
||||||
|
tracks/0/type = "value"
|
||||||
|
tracks/0/imported = false
|
||||||
|
tracks/0/enabled = true
|
||||||
|
tracks/0/path = NodePath("MenuContainer/VBoxContainer/TitlesMargin/TitlesContainer/Title:modulate")
|
||||||
|
tracks/0/interp = 1
|
||||||
|
tracks/0/loop_wrap = true
|
||||||
|
tracks/0/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 0,
|
||||||
|
"values": [Color(1, 1, 1, 1)]
|
||||||
|
}
|
||||||
|
tracks/1/type = "value"
|
||||||
|
tracks/1/imported = false
|
||||||
|
tracks/1/enabled = true
|
||||||
|
tracks/1/path = NodePath("MenuContainer/VBoxContainer/TitlesMargin/TitlesContainer/SubTitle:modulate")
|
||||||
|
tracks/1/interp = 1
|
||||||
|
tracks/1/loop_wrap = true
|
||||||
|
tracks/1/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 0,
|
||||||
|
"values": [Color(1, 1, 1, 1)]
|
||||||
|
}
|
||||||
|
tracks/2/type = "value"
|
||||||
|
tracks/2/imported = false
|
||||||
|
tracks/2/enabled = true
|
||||||
|
tracks/2/path = NodePath("MenuContainer/VBoxContainer/MenuMargin/MenuButtons:modulate")
|
||||||
|
tracks/2/interp = 1
|
||||||
|
tracks/2/loop_wrap = true
|
||||||
|
tracks/2/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 0,
|
||||||
|
"values": [Color(1, 1, 1, 1)]
|
||||||
|
}
|
||||||
|
tracks/3/type = "value"
|
||||||
|
tracks/3/imported = false
|
||||||
|
tracks/3/enabled = true
|
||||||
|
tracks/3/path = NodePath("FlowControlContainer/FlowControl/BackButton:visible")
|
||||||
|
tracks/3/interp = 1
|
||||||
|
tracks/3/loop_wrap = true
|
||||||
|
tracks/3/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 1,
|
||||||
|
"values": [false]
|
||||||
|
}
|
||||||
|
tracks/4/type = "value"
|
||||||
|
tracks/4/imported = false
|
||||||
|
tracks/4/enabled = true
|
||||||
|
tracks/4/path = NodePath("FlowControlContainer:mouse_filter")
|
||||||
|
tracks/4/interp = 1
|
||||||
|
tracks/4/loop_wrap = true
|
||||||
|
tracks/4/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 1,
|
||||||
|
"values": [2]
|
||||||
|
}
|
||||||
|
tracks/5/type = "value"
|
||||||
|
tracks/5/imported = false
|
||||||
|
tracks/5/enabled = true
|
||||||
|
tracks/5/path = NodePath("MenuContainer:modulate")
|
||||||
|
tracks/5/interp = 1
|
||||||
|
tracks/5/loop_wrap = true
|
||||||
|
tracks/5/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 0,
|
||||||
|
"values": [Color(1, 1, 1, 1)]
|
||||||
|
}
|
||||||
|
tracks/6/type = "value"
|
||||||
|
tracks/6/imported = false
|
||||||
|
tracks/6/enabled = true
|
||||||
|
tracks/6/path = NodePath("VersionNameLabel:modulate")
|
||||||
|
tracks/6/interp = 1
|
||||||
|
tracks/6/loop_wrap = true
|
||||||
|
tracks/6/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 0,
|
||||||
|
"values": [Color(1, 1, 1, 1)]
|
||||||
|
}
|
||||||
|
tracks/7/type = "value"
|
||||||
|
tracks/7/imported = false
|
||||||
|
tracks/7/enabled = true
|
||||||
|
tracks/7/path = NodePath("MenuContainer/VBoxContainer/MenuMargin/MenuButtons:lock")
|
||||||
|
tracks/7/interp = 1
|
||||||
|
tracks/7/loop_wrap = true
|
||||||
|
tracks/7/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 1,
|
||||||
|
"values": [false]
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="Animation" id="4"]
|
||||||
|
resource_name = "OpenSubMenu"
|
||||||
|
length = 0.2
|
||||||
|
tracks/0/type = "value"
|
||||||
|
tracks/0/imported = false
|
||||||
|
tracks/0/enabled = true
|
||||||
|
tracks/0/path = NodePath("FlowControlContainer/FlowControl/BackButton:visible")
|
||||||
|
tracks/0/interp = 1
|
||||||
|
tracks/0/loop_wrap = true
|
||||||
|
tracks/0/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 1,
|
||||||
|
"values": [true]
|
||||||
|
}
|
||||||
|
tracks/1/type = "value"
|
||||||
|
tracks/1/imported = false
|
||||||
|
tracks/1/enabled = true
|
||||||
|
tracks/1/path = NodePath("MenuContainer:modulate")
|
||||||
|
tracks/1/interp = 1
|
||||||
|
tracks/1/loop_wrap = true
|
||||||
|
tracks/1/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 0,
|
||||||
|
"values": [Color(1, 1, 1, 0)]
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="Animation" id="2"]
|
||||||
|
length = 0.001
|
||||||
|
tracks/0/type = "value"
|
||||||
|
tracks/0/imported = false
|
||||||
|
tracks/0/enabled = true
|
||||||
|
tracks/0/path = NodePath("FlowControlContainer/FlowControl/BackButton:visible")
|
||||||
|
tracks/0/interp = 1
|
||||||
|
tracks/0/loop_wrap = true
|
||||||
|
tracks/0/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 1,
|
||||||
|
"values": [false]
|
||||||
|
}
|
||||||
|
tracks/1/type = "value"
|
||||||
|
tracks/1/imported = false
|
||||||
|
tracks/1/enabled = true
|
||||||
|
tracks/1/path = NodePath("MenuContainer/VBoxContainer/TitlesMargin/TitlesContainer/Title:modulate")
|
||||||
|
tracks/1/interp = 1
|
||||||
|
tracks/1/loop_wrap = true
|
||||||
|
tracks/1/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 0,
|
||||||
|
"values": [Color(1, 1, 1, 0)]
|
||||||
|
}
|
||||||
|
tracks/2/type = "value"
|
||||||
|
tracks/2/imported = false
|
||||||
|
tracks/2/enabled = true
|
||||||
|
tracks/2/path = NodePath("MenuContainer/VBoxContainer/TitlesMargin/TitlesContainer/SubTitle:modulate")
|
||||||
|
tracks/2/interp = 1
|
||||||
|
tracks/2/loop_wrap = true
|
||||||
|
tracks/2/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 0,
|
||||||
|
"values": [Color(1, 1, 1, 0)]
|
||||||
|
}
|
||||||
|
tracks/3/type = "value"
|
||||||
|
tracks/3/imported = false
|
||||||
|
tracks/3/enabled = true
|
||||||
|
tracks/3/path = NodePath("MenuContainer/VBoxContainer/MenuMargin/MenuButtons:modulate")
|
||||||
|
tracks/3/interp = 1
|
||||||
|
tracks/3/loop_wrap = true
|
||||||
|
tracks/3/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 0,
|
||||||
|
"values": [Color(1, 1, 1, 0)]
|
||||||
|
}
|
||||||
|
tracks/4/type = "value"
|
||||||
|
tracks/4/imported = false
|
||||||
|
tracks/4/enabled = true
|
||||||
|
tracks/4/path = NodePath("FlowControlContainer:mouse_filter")
|
||||||
|
tracks/4/interp = 1
|
||||||
|
tracks/4/loop_wrap = true
|
||||||
|
tracks/4/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 1,
|
||||||
|
"values": [2]
|
||||||
|
}
|
||||||
|
tracks/5/type = "value"
|
||||||
|
tracks/5/imported = false
|
||||||
|
tracks/5/enabled = true
|
||||||
|
tracks/5/path = NodePath("MenuContainer:modulate")
|
||||||
|
tracks/5/interp = 1
|
||||||
|
tracks/5/loop_wrap = true
|
||||||
|
tracks/5/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 0,
|
||||||
|
"values": [Color(1, 1, 1, 1)]
|
||||||
|
}
|
||||||
|
tracks/6/type = "value"
|
||||||
|
tracks/6/imported = false
|
||||||
|
tracks/6/enabled = true
|
||||||
|
tracks/6/path = NodePath("VersionNameLabel:modulate")
|
||||||
|
tracks/6/interp = 1
|
||||||
|
tracks/6/loop_wrap = true
|
||||||
|
tracks/6/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 0,
|
||||||
|
"values": [Color(1, 1, 1, 0)]
|
||||||
|
}
|
||||||
|
tracks/7/type = "value"
|
||||||
|
tracks/7/imported = false
|
||||||
|
tracks/7/enabled = true
|
||||||
|
tracks/7/path = NodePath("MenuContainer/VBoxContainer/MenuMargin/MenuButtons:lock")
|
||||||
|
tracks/7/interp = 1
|
||||||
|
tracks/7/loop_wrap = true
|
||||||
|
tracks/7/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 1,
|
||||||
|
"values": [true]
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="AnimationLibrary" id="AnimationLibrary_2kqig"]
|
||||||
|
_data = {
|
||||||
|
"Intro": SubResource("1"),
|
||||||
|
"OpenMainMenu": SubResource("6"),
|
||||||
|
"OpenSubMenu": SubResource("4"),
|
||||||
|
"RESET": SubResource("2")
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="AnimationNodeAnimation" id="7"]
|
||||||
|
animation = &"Intro"
|
||||||
|
|
||||||
|
[sub_resource type="AnimationNodeAnimation" id="10"]
|
||||||
|
animation = &"OpenMainMenu"
|
||||||
|
|
||||||
|
[sub_resource type="AnimationNodeAnimation" id="13"]
|
||||||
|
animation = &"OpenSubMenu"
|
||||||
|
|
||||||
|
[sub_resource type="AnimationNodeStateMachineTransition" id="11"]
|
||||||
|
advance_condition = &"intro_done"
|
||||||
|
|
||||||
|
[sub_resource type="AnimationNodeStateMachineTransition" id="14"]
|
||||||
|
|
||||||
|
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_j0orr"]
|
||||||
|
advance_mode = 2
|
||||||
|
|
||||||
|
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_63dxc"]
|
||||||
|
|
||||||
|
[sub_resource type="AnimationNodeStateMachine" id="AnimationNodeStateMachine_vikuh"]
|
||||||
|
states/End/position = Vector2(958, 123)
|
||||||
|
states/Intro/node = SubResource("7")
|
||||||
|
states/Intro/position = Vector2(259, 123)
|
||||||
|
states/OpenMainMenu/node = SubResource("10")
|
||||||
|
states/OpenMainMenu/position = Vector2(472, 123)
|
||||||
|
states/OpenSubMenu/node = SubResource("13")
|
||||||
|
states/OpenSubMenu/position = Vector2(734, 123)
|
||||||
|
states/Start/position = Vector2(82, 123)
|
||||||
|
transitions = ["Intro", "OpenMainMenu", SubResource("11"), "OpenMainMenu", "OpenSubMenu", SubResource("14"), "Start", "Intro", SubResource("AnimationNodeStateMachineTransition_j0orr"), "OpenSubMenu", "OpenMainMenu", SubResource("AnimationNodeStateMachineTransition_63dxc")]
|
||||||
|
graph_offset = Vector2(-180.277, 49)
|
||||||
|
|
||||||
|
[node name="MainMenu" instance=ExtResource("1_0i2sc")]
|
||||||
|
script = ExtResource("2_ncvk7")
|
||||||
|
|
||||||
|
[node name="MenuAnimationPlayer" type="AnimationPlayer" parent="." index="1"]
|
||||||
|
libraries = {
|
||||||
|
"": SubResource("AnimationLibrary_2kqig")
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="MenuAnimationTree" type="AnimationTree" parent="." index="2"]
|
||||||
|
tree_root = SubResource("AnimationNodeStateMachine_vikuh")
|
||||||
|
anim_player = NodePath("../MenuAnimationPlayer")
|
||||||
|
parameters/conditions/intro_done = false
|
||||||
|
|
||||||
|
[node name="VersionNameLabel" parent="." index="5"]
|
||||||
|
modulate = Color(1, 1, 1, 0)
|
||||||
|
|
||||||
|
[node name="Title" parent="MenuContainer/VBoxContainer/TitlesMargin/TitlesContainer" index="0"]
|
||||||
|
modulate = Color(1, 1, 1, 0)
|
||||||
|
|
||||||
|
[node name="SubTitle" parent="MenuContainer/VBoxContainer/TitlesMargin/TitlesContainer" index="1"]
|
||||||
|
modulate = Color(1, 1, 1, 0)
|
||||||
|
|
||||||
|
[node name="MenuButtons" parent="MenuContainer/VBoxContainer/MenuMargin" index="0"]
|
||||||
|
modulate = Color(1, 1, 1, 0)
|
||||||
|
lock = true
|
@ -0,0 +1,37 @@
|
|||||||
|
extends ListOptionControl
|
||||||
|
|
||||||
|
func _set_input_device():
|
||||||
|
var current_setting = _get_setting(default_value)
|
||||||
|
if current_setting is bool:
|
||||||
|
current_setting = &"Default"
|
||||||
|
AudioServer.input_device = _get_setting(default_value)
|
||||||
|
|
||||||
|
func _add_microphone_audio_stream() -> void:
|
||||||
|
var instance = AudioStreamPlayer.new()
|
||||||
|
instance.stream = AudioStreamMicrophone.new()
|
||||||
|
instance.autoplay = true
|
||||||
|
add_child.call_deferred(instance)
|
||||||
|
instance.ready.connect(_set_input_device)
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
if ProjectSettings.get_setting("audio/driver/enable_input", false):
|
||||||
|
if AudioServer.input_device.is_empty():
|
||||||
|
_add_microphone_audio_stream()
|
||||||
|
else:
|
||||||
|
_set_input_device()
|
||||||
|
if not Engine.is_editor_hint():
|
||||||
|
option_values = AudioServer.get_input_device_list()
|
||||||
|
else:
|
||||||
|
hide()
|
||||||
|
super._ready()
|
||||||
|
|
||||||
|
func _on_setting_changed(value):
|
||||||
|
if value >= option_values.size(): return
|
||||||
|
AudioServer.input_device = option_values[value]
|
||||||
|
super._on_setting_changed(value)
|
||||||
|
|
||||||
|
func _value_title_map(value : Variant) -> String:
|
||||||
|
if value is String:
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
return super._value_title_map(value)
|
@ -0,0 +1,21 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://dxj1gsxtlp6v0"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b6bl3n5mp3m1e" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/list_option_control.tscn" id="1_0vgeo"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/examples/scenes/menus/options_menu/audio/audio_input_option_control.gd" id="2_6qeue"]
|
||||||
|
|
||||||
|
[node name="AudioInputOptionControl" instance=ExtResource("1_0vgeo")]
|
||||||
|
script = ExtResource("2_6qeue")
|
||||||
|
option_name = "Input Device"
|
||||||
|
option_section = 2
|
||||||
|
key = "InputDevice"
|
||||||
|
section = "AudioSettings"
|
||||||
|
property_type = 4
|
||||||
|
default_value = "Default"
|
||||||
|
|
||||||
|
[node name="OptionLabel" parent="." index="0"]
|
||||||
|
text = "Input Device :"
|
||||||
|
|
||||||
|
[node name="OptionButton" parent="." index="1"]
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text_overrun_behavior = 1
|
||||||
|
clip_text = true
|
@ -0,0 +1 @@
|
|||||||
|
extends AudioOptionsMenu
|
@ -0,0 +1,11 @@
|
|||||||
|
[gd_scene load_steps=4 format=3 uid="uid://dmgla7rq1g2cc"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://c8vnncjwqcpab" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/audio/audio_options_menu.tscn" id="1_ro573"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/examples/scenes/menus/options_menu/audio/audio_options_menu.gd" id="2_g4nfi"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dxj1gsxtlp6v0" path="res://addons/maaacks_menus_template/examples/scenes/menus/options_menu/audio/audio_input_option_control.tscn" id="3_orobk"]
|
||||||
|
|
||||||
|
[node name="Audio" instance=ExtResource("1_ro573")]
|
||||||
|
script = ExtResource("2_g4nfi")
|
||||||
|
|
||||||
|
[node name="AudioInputOptionControl" parent="VBoxContainer" index="2" instance=ExtResource("3_orobk")]
|
||||||
|
layout_mode = 2
|
@ -0,0 +1 @@
|
|||||||
|
extends InputOptionsMenu
|
@ -0,0 +1,7 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://1e3vf4u3brfm"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dp3rgqaehb3xu" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/input/input_options_menu.tscn" id="1_b6ygu"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/examples/scenes/menus/options_menu/input/input_options_menu.gd" id="2_gjulr"]
|
||||||
|
|
||||||
|
[node name="Controls" instance=ExtResource("1_b6ygu")]
|
||||||
|
script = ExtResource("2_gjulr")
|
@ -0,0 +1,39 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://b4r3gcnm31uo6"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://1e3vf4u3brfm" path="res://addons/maaacks_menus_template/examples/scenes/Menus/OptionsMenu/Input/InputOptionsMenu.tscn" id="1_pi4g6"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://cl416gdb1fgwr" path="res://addons/maaacks_menus_template/base/scenes/Menus/OptionsMenu/OptionControl/SliderOptionControl.tscn" id="2_ax2ge"]
|
||||||
|
|
||||||
|
[node name="Controls" instance=ExtResource("1_pi4g6")]
|
||||||
|
|
||||||
|
[node name="VBoxContainer" parent="." index="0"]
|
||||||
|
theme_override_constants/separation = 16
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer" index="0"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/margin_top = 32
|
||||||
|
theme_override_constants/margin_bottom = 32
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/MarginContainer" index="0"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="MouseSensitivityControl" parent="VBoxContainer/MarginContainer/VBoxContainer" index="0" instance=ExtResource("2_ax2ge")]
|
||||||
|
layout_mode = 2
|
||||||
|
option_name = "Mouse Sensitivity"
|
||||||
|
option_section = 1
|
||||||
|
key = "MouseSensitivity"
|
||||||
|
section = "InputSettings"
|
||||||
|
|
||||||
|
[node name="OptionLabel" parent="VBoxContainer/MarginContainer/VBoxContainer/MouseSensitivityControl" index="0"]
|
||||||
|
text = "Mouse Sensitivity :"
|
||||||
|
|
||||||
|
[node name="HSlider" parent="VBoxContainer/MarginContainer/VBoxContainer/MouseSensitivityControl" index="1"]
|
||||||
|
min_value = 0.5
|
||||||
|
max_value = 2.0
|
||||||
|
tick_count = 16
|
||||||
|
|
||||||
|
[node name="HSeparator" type="HSeparator" parent="VBoxContainer" index="1"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[editable path="VBoxContainer/MarginContainer/VBoxContainer/MouseSensitivityControl"]
|
@ -0,0 +1 @@
|
|||||||
|
extends MasterOptionsMenu
|
@ -0,0 +1,7 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://c3s46qrj7m87p"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bvwl11s2p0hd" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/master_options_menu.tscn" id="1_kgc1h"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/examples/scenes/menus/options_menu/master_options_menu.gd" id="2_4n0ab"]
|
||||||
|
|
||||||
|
[node name="MasterOptionsMenu" instance=ExtResource("1_kgc1h")]
|
||||||
|
script = ExtResource("2_4n0ab")
|
@ -0,0 +1,26 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://bdvdf5v87mmrr"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://c3s46qrj7m87p" path="res://addons/maaacks_menus_template/examples/scenes/menus/options_menu/master_options_menu.tscn" id="1_u1u8e"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b4r3gcnm31uo6" path="res://addons/maaacks_menus_template/examples/scenes/menus/options_menu/input/input_options_menu_with_mouse_sensitivity.tscn" id="2_p458j"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dmgla7rq1g2cc" path="res://addons/maaacks_menus_template/examples/scenes/menus/options_menu/audio/audio_options_menu.tscn" id="3_vs2ne"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://cck3omvlkhpix" path="res://addons/maaacks_menus_template/examples/scenes/menus/options_menu/video/video_options_menu_with_extras.tscn" id="4_v3ewd"]
|
||||||
|
|
||||||
|
[node name="MasterOptionsMenu" instance=ExtResource("1_u1u8e")]
|
||||||
|
|
||||||
|
[node name="TabContainer" parent="." index="0"]
|
||||||
|
current_tab = 0
|
||||||
|
|
||||||
|
[node name="Controls" parent="TabContainer" index="1" instance=ExtResource("2_p458j")]
|
||||||
|
layout_mode = 2
|
||||||
|
show_all_actions = true
|
||||||
|
metadata/_tab_index = 0
|
||||||
|
|
||||||
|
[node name="Audio" parent="TabContainer" index="2" instance=ExtResource("3_vs2ne")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
metadata/_tab_index = 1
|
||||||
|
|
||||||
|
[node name="Video" parent="TabContainer" index="3" instance=ExtResource("4_v3ewd")]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
metadata/_tab_index = 2
|
@ -0,0 +1 @@
|
|||||||
|
extends MiniOptionsMenu
|
@ -0,0 +1,7 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://c6obwfb7wbibn"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://vh1ucj2rfbby" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/mini_options_menu.tscn" id="1_f2l25"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/examples/scenes/menus/options_menu/mini_options_menu.gd" id="2_hcx71"]
|
||||||
|
|
||||||
|
[node name="MiniOptionsMenu" instance=ExtResource("1_f2l25")]
|
||||||
|
script = ExtResource("2_hcx71")
|
@ -0,0 +1 @@
|
|||||||
|
extends VideoOptionsMenu
|
@ -0,0 +1,7 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://b8kb81us6g3kr"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b2numvphf2kau" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/video/video_options_menu.tscn" id="1_4gigb"]
|
||||||
|
[ext_resource type="Script" path="res://addons/maaacks_menus_template/examples/scenes/menus/options_menu/video/video_options_menu.gd" id="2_v2jlu"]
|
||||||
|
|
||||||
|
[node name="Video" instance=ExtResource("1_4gigb")]
|
||||||
|
script = ExtResource("2_v2jlu")
|
@ -0,0 +1,30 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://cck3omvlkhpix"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b8kb81us6g3kr" path="res://addons/maaacks_menus_template/examples/scenes/Menus/OptionsMenu/Video/VideoOptionsMenu.tscn" id="1_nrerc"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b6bl3n5mp3m1e" path="res://addons/maaacks_menus_template/base/scenes/Menus/OptionsMenu/OptionControl/ListOptionControl.tscn" id="2_tyiki"]
|
||||||
|
|
||||||
|
[node name="Video" instance=ExtResource("1_nrerc")]
|
||||||
|
|
||||||
|
[node name="AntiAliasingControl" parent="VBoxContainer" index="2" instance=ExtResource("2_tyiki")]
|
||||||
|
layout_mode = 2
|
||||||
|
option_values = [0, 1, 2, 3]
|
||||||
|
option_titles = Array[String](["Disabled (Fastest)", "2x", "4x", "8x (Slowest)"])
|
||||||
|
lock_titles = true
|
||||||
|
option_name = "Anti-Aliasing"
|
||||||
|
option_section = 3
|
||||||
|
key = "Anti-aliasing"
|
||||||
|
section = "VideoSettings"
|
||||||
|
property_type = 2
|
||||||
|
default_value = 0
|
||||||
|
|
||||||
|
[node name="CameraShakeControl" parent="VBoxContainer" index="3" instance=ExtResource("2_tyiki")]
|
||||||
|
layout_mode = 2
|
||||||
|
option_values = [1.0, 0.75, 0.5, 0.0]
|
||||||
|
option_titles = Array[String](["Normal", "Reduced", "Minimal", "None"])
|
||||||
|
lock_titles = true
|
||||||
|
option_name = "Camera Shake"
|
||||||
|
option_section = 3
|
||||||
|
key = "CameraShake"
|
||||||
|
section = "VideoSettings"
|
||||||
|
property_type = 3
|
||||||
|
default_value = 1.0
|
@ -0,0 +1,14 @@
|
|||||||
|
[gd_scene format=3 uid="uid://cyx4i4v30bw4o"]
|
||||||
|
|
||||||
|
[node name="CopyConfirmationDialog" type="ConfirmationDialog"]
|
||||||
|
title = "Copy Examples"
|
||||||
|
initial_position = 2
|
||||||
|
size = Vector2i(400, 210)
|
||||||
|
visible = true
|
||||||
|
exclusive = false
|
||||||
|
ok_button_text = "Yes"
|
||||||
|
dialog_text = "Plugin enabled. It is recommended to copy the example scenes to a destination outside of the addons/ folder before editing them.
|
||||||
|
|
||||||
|
Would you like to copy the examples now?"
|
||||||
|
dialog_autowrap = true
|
||||||
|
cancel_button_text = "No"
|
@ -0,0 +1,15 @@
|
|||||||
|
[gd_scene format=3 uid="uid://vgdxevcnv0vx"]
|
||||||
|
|
||||||
|
[node name="DeleteExamplesConfirmationDialog" type="ConfirmationDialog"]
|
||||||
|
title = "Delete Source Examples"
|
||||||
|
initial_position = 2
|
||||||
|
size = Vector2i(400, 320)
|
||||||
|
visible = true
|
||||||
|
ok_button_text = "Yes"
|
||||||
|
dialog_text = "If the copied scenes work as expected, you may delete the source examples folder. This avoids confusing both developers and the Godot editor.
|
||||||
|
|
||||||
|
This will also remove the option to copy the examples again. However, one copy is enough for most use cases.
|
||||||
|
|
||||||
|
Would you like to delete the source examples folder now?"
|
||||||
|
dialog_autowrap = true
|
||||||
|
cancel_button_text = "No"
|
@ -0,0 +1,11 @@
|
|||||||
|
[gd_scene format=3 uid="uid://d03csqgcaxm0m"]
|
||||||
|
|
||||||
|
[node name="DeleteExamplesShortConfirmationDialog" type="ConfirmationDialog"]
|
||||||
|
title = "Delete Source Examples"
|
||||||
|
initial_position = 2
|
||||||
|
size = Vector2i(400, 106)
|
||||||
|
visible = true
|
||||||
|
ok_button_text = "Yes"
|
||||||
|
dialog_text = "Are you sure you would like to delete the source examples folder?"
|
||||||
|
dialog_autowrap = true
|
||||||
|
cancel_button_text = "No"
|
@ -0,0 +1,11 @@
|
|||||||
|
[gd_scene format=3 uid="uid://ckx50am7thhd2"]
|
||||||
|
|
||||||
|
[node name="DestinationDialog" type="FileDialog"]
|
||||||
|
title = "Select a Destination"
|
||||||
|
initial_position = 2
|
||||||
|
size = Vector2i(500, 400)
|
||||||
|
visible = true
|
||||||
|
exclusive = false
|
||||||
|
ok_button_text = "Select Current Folder"
|
||||||
|
mode_overrides_title = false
|
||||||
|
file_mode = 2
|
@ -0,0 +1,14 @@
|
|||||||
|
[gd_scene format=3 uid="uid://b8kr3y0cjxr8m"]
|
||||||
|
|
||||||
|
[node name="MainSceneConfirmationDialog" type="ConfirmationDialog"]
|
||||||
|
title = "Update Main Scene"
|
||||||
|
initial_position = 2
|
||||||
|
size = Vector2i(420, 210)
|
||||||
|
visible = true
|
||||||
|
exclusive = false
|
||||||
|
ok_button_text = "Yes"
|
||||||
|
dialog_text = "Would you like to update the project's main scene?
|
||||||
|
|
||||||
|
"
|
||||||
|
dialog_autowrap = true
|
||||||
|
cancel_button_text = "No"
|
@ -0,0 +1,36 @@
|
|||||||
|
; Project settings override file.
|
||||||
|
; Adds gamepad inputs to built-in actions.
|
||||||
|
;
|
||||||
|
; Format:
|
||||||
|
; [section] ; section goes between []
|
||||||
|
; param=value ; assign values to parameters
|
||||||
|
|
||||||
|
|
||||||
|
[input]
|
||||||
|
|
||||||
|
ui_accept={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194309,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194310,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":32,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":true,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
ui_cancel={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194305,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":true,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
ui_page_up={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194323,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":9,"pressure":0.0,"pressed":true,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
ui_page_down={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194324,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":10,"pressure":0.0,"pressed":true,"script":null)
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
[gd_scene format=3 uid="uid://b8808yj7a0ghj"]
|
||||||
|
|
||||||
|
[node name="PlayOpeningConfirmationDialog" type="ConfirmationDialog"]
|
||||||
|
title = "Run & Test"
|
||||||
|
initial_position = 2
|
||||||
|
size = Vector2i(400, 210)
|
||||||
|
visible = true
|
||||||
|
ok_button_text = "Yes"
|
||||||
|
dialog_text = "It is recommended to run the opening scene of the plugin and test if any issues occurred during the copying process.
|
||||||
|
|
||||||
|
Would you like to run and test the scenes now?"
|
||||||
|
dialog_autowrap = true
|
||||||
|
cancel_button_text = "No"
|
@ -0,0 +1,307 @@
|
|||||||
|
@tool
|
||||||
|
extends EditorPlugin
|
||||||
|
|
||||||
|
const PLUGIN_NAME = "Maaack's Menus Template"
|
||||||
|
const PROJECT_SETTINGS_PATH = "maaacks_menus_template/"
|
||||||
|
|
||||||
|
const EXAMPLES_RELATIVE_PATH = "examples/"
|
||||||
|
const MAIN_SCENE_RELATIVE_PATH = "scenes/menus/main_menu/main_menu_with_animations.tscn"
|
||||||
|
const MAIN_SCENE_UPDATE_TEXT = "Current:\n%s\n\nNew:\n%s\n"
|
||||||
|
const OVERRIDE_RELATIVE_PATH = "installer/override.cfg"
|
||||||
|
const SCENE_LOADER_RELATIVE_PATH = "base/scenes/autoloads/scene_loader.tscn"
|
||||||
|
const UID_PREG_MATCH = r'uid="uid:\/\/[0-9a-z]+" '
|
||||||
|
const RESAVING_DELAY : float = 0.5
|
||||||
|
const REIMPORT_FILE_DELAY : float = 0.2
|
||||||
|
const OPEN_EDITOR_DELAY : float = 0.1
|
||||||
|
const MAX_PHYSICS_FRAMES_FROM_START : int = 20
|
||||||
|
|
||||||
|
func _get_plugin_name():
|
||||||
|
return PLUGIN_NAME
|
||||||
|
|
||||||
|
func get_plugin_path() -> String:
|
||||||
|
return get_script().resource_path.get_base_dir() + "/"
|
||||||
|
|
||||||
|
func get_plugin_examples_path() -> String:
|
||||||
|
return get_plugin_path() + EXAMPLES_RELATIVE_PATH
|
||||||
|
|
||||||
|
func _update_main_scene(main_scene_path : String):
|
||||||
|
ProjectSettings.set_setting("application/run/main_scene", main_scene_path)
|
||||||
|
ProjectSettings.save()
|
||||||
|
|
||||||
|
func _check_main_scene_needs_updating(target_path : String):
|
||||||
|
var current_main_scene_path = ProjectSettings.get_setting("application/run/main_scene", "")
|
||||||
|
var new_main_scene_path = target_path + MAIN_SCENE_RELATIVE_PATH
|
||||||
|
if new_main_scene_path == current_main_scene_path:
|
||||||
|
return
|
||||||
|
_open_main_scene_confirmation_dialog(current_main_scene_path, new_main_scene_path)
|
||||||
|
|
||||||
|
func _open_main_scene_confirmation_dialog(current_main_scene : String, new_main_scene : String):
|
||||||
|
var main_confirmation_scene : PackedScene = load(get_plugin_path() + "installer/main_scene_confirmation_dialog.tscn")
|
||||||
|
var main_confirmation_instance : ConfirmationDialog = main_confirmation_scene.instantiate()
|
||||||
|
main_confirmation_instance.dialog_text += MAIN_SCENE_UPDATE_TEXT % [current_main_scene, new_main_scene]
|
||||||
|
main_confirmation_instance.confirmed.connect(_update_main_scene.bind(new_main_scene))
|
||||||
|
add_child(main_confirmation_instance)
|
||||||
|
|
||||||
|
func _open_play_opening_confirmation_dialog(target_path : String):
|
||||||
|
var play_confirmation_scene : PackedScene = load(get_plugin_path() + "installer/play_opening_confirmation_dialog.tscn")
|
||||||
|
var play_confirmation_instance : ConfirmationDialog = play_confirmation_scene.instantiate()
|
||||||
|
play_confirmation_instance.confirmed.connect(_run_opening_scene.bind(target_path))
|
||||||
|
play_confirmation_instance.canceled.connect(_check_main_scene_needs_updating.bind(target_path))
|
||||||
|
add_child(play_confirmation_instance)
|
||||||
|
|
||||||
|
func _open_delete_examples_confirmation_dialog(target_path : String):
|
||||||
|
var delete_confirmation_scene : PackedScene = load(get_plugin_path() + "installer/delete_examples_confirmation_dialog.tscn")
|
||||||
|
var delete_confirmation_instance : ConfirmationDialog = delete_confirmation_scene.instantiate()
|
||||||
|
delete_confirmation_instance.confirmed.connect(_delete_source_examples_directory.bind(target_path))
|
||||||
|
delete_confirmation_instance.canceled.connect(_check_main_scene_needs_updating.bind(target_path))
|
||||||
|
add_child(delete_confirmation_instance)
|
||||||
|
|
||||||
|
func _open_delete_examples_short_confirmation_dialog():
|
||||||
|
var delete_confirmation_scene : PackedScene = load(get_plugin_path() + "installer/delete_examples_short_confirmation_dialog.tscn")
|
||||||
|
var delete_confirmation_instance : ConfirmationDialog = delete_confirmation_scene.instantiate()
|
||||||
|
delete_confirmation_instance.confirmed.connect(_delete_source_examples_directory)
|
||||||
|
add_child(delete_confirmation_instance)
|
||||||
|
|
||||||
|
func _run_opening_scene(target_path : String):
|
||||||
|
var opening_scene_path = target_path + MAIN_SCENE_RELATIVE_PATH
|
||||||
|
EditorInterface.play_custom_scene(opening_scene_path)
|
||||||
|
var timer: Timer = Timer.new()
|
||||||
|
var callable := func():
|
||||||
|
if EditorInterface.is_playing_scene(): return
|
||||||
|
timer.stop()
|
||||||
|
_open_delete_examples_confirmation_dialog(target_path)
|
||||||
|
timer.queue_free()
|
||||||
|
timer.timeout.connect(callable)
|
||||||
|
add_child(timer)
|
||||||
|
timer.start(RESAVING_DELAY)
|
||||||
|
|
||||||
|
func _delete_directory_recursive(dir_path : String):
|
||||||
|
if not dir_path.ends_with("/"):
|
||||||
|
dir_path += "/"
|
||||||
|
var dir = DirAccess.open(dir_path)
|
||||||
|
if dir:
|
||||||
|
dir.list_dir_begin()
|
||||||
|
var file_name = dir.get_next()
|
||||||
|
var error : Error
|
||||||
|
while file_name != "" and error == 0:
|
||||||
|
var relative_path = dir_path.trim_prefix(get_plugin_examples_path())
|
||||||
|
var full_file_path = dir_path + file_name
|
||||||
|
if dir.current_is_dir():
|
||||||
|
_delete_directory_recursive(full_file_path)
|
||||||
|
else:
|
||||||
|
error = dir.remove(file_name)
|
||||||
|
file_name = dir.get_next()
|
||||||
|
if error:
|
||||||
|
push_error("plugin error - deleting path: %s" % error)
|
||||||
|
else:
|
||||||
|
push_error("plugin error - accessing path: %s" % dir)
|
||||||
|
dir.remove(dir_path)
|
||||||
|
|
||||||
|
func _delete_source_examples_directory(target_path : String = ""):
|
||||||
|
var examples_path = get_plugin_examples_path()
|
||||||
|
var dir := DirAccess.open("res://")
|
||||||
|
if dir.dir_exists(examples_path):
|
||||||
|
_delete_directory_recursive(examples_path)
|
||||||
|
EditorInterface.get_resource_filesystem().scan()
|
||||||
|
remove_tool_menu_item("Copy " + _get_plugin_name() + " Examples...")
|
||||||
|
remove_tool_menu_item("Delete " + _get_plugin_name() + " Examples...")
|
||||||
|
if not target_path.is_empty():
|
||||||
|
_check_main_scene_needs_updating(target_path)
|
||||||
|
|
||||||
|
func _replace_file_contents(file_path : String, target_path : String):
|
||||||
|
var extension : String = file_path.get_extension()
|
||||||
|
if extension == "import":
|
||||||
|
# skip import files
|
||||||
|
return OK
|
||||||
|
var file = FileAccess.open(file_path, FileAccess.READ)
|
||||||
|
var regex = RegEx.new()
|
||||||
|
regex.compile(UID_PREG_MATCH)
|
||||||
|
if file == null:
|
||||||
|
push_error("plugin error - null file: `%s`" % file_path)
|
||||||
|
return
|
||||||
|
var original_content = file.get_as_text()
|
||||||
|
var replaced_content = regex.sub(original_content, "", true)
|
||||||
|
replaced_content = replaced_content.replace(get_plugin_examples_path(), target_path)
|
||||||
|
file.close()
|
||||||
|
if replaced_content == original_content: return
|
||||||
|
file = FileAccess.open(file_path, FileAccess.WRITE)
|
||||||
|
file.store_string(replaced_content)
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
func _save_resource(resource_path : String, resource_destination : String, whitelisted_extensions : PackedStringArray = []) -> Error:
|
||||||
|
var extension : String = resource_path.get_extension()
|
||||||
|
if whitelisted_extensions.size() > 0:
|
||||||
|
if not extension in whitelisted_extensions:
|
||||||
|
return OK
|
||||||
|
if extension == "import":
|
||||||
|
# skip import files
|
||||||
|
return OK
|
||||||
|
var file_object = load(resource_path)
|
||||||
|
if file_object is Resource:
|
||||||
|
var possible_extensions = ResourceSaver.get_recognized_extensions(file_object)
|
||||||
|
if possible_extensions.has(extension):
|
||||||
|
return ResourceSaver.save(file_object, resource_destination, ResourceSaver.FLAG_CHANGE_PATH)
|
||||||
|
else:
|
||||||
|
return ERR_FILE_UNRECOGNIZED
|
||||||
|
else:
|
||||||
|
return ERR_FILE_UNRECOGNIZED
|
||||||
|
return OK
|
||||||
|
|
||||||
|
func _delayed_reimporting_file(file_path : String):
|
||||||
|
var timer: Timer = Timer.new()
|
||||||
|
var callable := func():
|
||||||
|
timer.stop()
|
||||||
|
var file_system = EditorInterface.get_resource_filesystem()
|
||||||
|
file_system.reimport_files([file_path])
|
||||||
|
timer.queue_free()
|
||||||
|
timer.timeout.connect(callable)
|
||||||
|
add_child(timer)
|
||||||
|
timer.start(REIMPORT_FILE_DELAY)
|
||||||
|
|
||||||
|
func _raw_copy_file_path(file_path : String, destination_path : String) -> Error:
|
||||||
|
var dir := DirAccess.open("res://")
|
||||||
|
var error := dir.copy(file_path, destination_path)
|
||||||
|
if not error:
|
||||||
|
EditorInterface.get_resource_filesystem().update_file(destination_path)
|
||||||
|
return error
|
||||||
|
|
||||||
|
func _copy_override_file():
|
||||||
|
var override_path : String = get_plugin_path() + OVERRIDE_RELATIVE_PATH
|
||||||
|
_raw_copy_file_path(override_path, "res://"+override_path.get_file())
|
||||||
|
|
||||||
|
func _copy_file_path(file_path : String, destination_path : String, target_path : String, raw_copy_file_extensions : PackedStringArray = []) -> Error:
|
||||||
|
if file_path.get_extension() in raw_copy_file_extensions:
|
||||||
|
# Markdown file format
|
||||||
|
return _raw_copy_file_path(file_path, destination_path)
|
||||||
|
var error = _save_resource(file_path, destination_path)
|
||||||
|
if error == ERR_FILE_UNRECOGNIZED:
|
||||||
|
# Copy image files and other assets
|
||||||
|
error = _raw_copy_file_path(file_path, destination_path)
|
||||||
|
# Reimport image files to create new .import
|
||||||
|
if not error:
|
||||||
|
_delayed_reimporting_file(destination_path)
|
||||||
|
return error
|
||||||
|
if not error:
|
||||||
|
_replace_file_contents(destination_path, target_path)
|
||||||
|
return error
|
||||||
|
|
||||||
|
func _copy_directory_path(dir_path : String, target_path : String, raw_copy_file_extensions : PackedStringArray = []):
|
||||||
|
if not dir_path.ends_with("/"):
|
||||||
|
dir_path += "/"
|
||||||
|
var dir = DirAccess.open(dir_path)
|
||||||
|
if dir:
|
||||||
|
dir.list_dir_begin()
|
||||||
|
var file_name = dir.get_next()
|
||||||
|
var error : Error
|
||||||
|
while file_name != "" and error == 0:
|
||||||
|
var relative_path = dir_path.trim_prefix(get_plugin_examples_path())
|
||||||
|
var destination_path = target_path + relative_path + file_name
|
||||||
|
var full_file_path = dir_path + file_name
|
||||||
|
if dir.current_is_dir():
|
||||||
|
if not dir.dir_exists(destination_path):
|
||||||
|
error = dir.make_dir(destination_path)
|
||||||
|
_copy_directory_path(full_file_path, target_path, raw_copy_file_extensions)
|
||||||
|
else:
|
||||||
|
error = _copy_file_path(full_file_path, destination_path, target_path, raw_copy_file_extensions)
|
||||||
|
file_name = dir.get_next()
|
||||||
|
if error:
|
||||||
|
push_error("plugin error - copying path: %s" % error)
|
||||||
|
else:
|
||||||
|
push_error("plugin error - accessing path: %s" % dir_path)
|
||||||
|
|
||||||
|
func _update_scene_loader_path(target_path : String):
|
||||||
|
var file_path : String = get_plugin_path() + SCENE_LOADER_RELATIVE_PATH
|
||||||
|
var file_text : String = FileAccess.get_file_as_string(file_path)
|
||||||
|
var prefix : String = "loading_screen_path = \""
|
||||||
|
var target_string = prefix + get_plugin_path() + "base/"
|
||||||
|
var replacing_string = prefix + target_path
|
||||||
|
file_text = file_text.replace(target_string, replacing_string)
|
||||||
|
var file = FileAccess.open(file_path, FileAccess.WRITE)
|
||||||
|
file.store_string(file_text)
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
func _delayed_saving_and_check_main_scene(target_path : String):
|
||||||
|
var timer: Timer = Timer.new()
|
||||||
|
var callable := func():
|
||||||
|
timer.stop()
|
||||||
|
EditorInterface.get_resource_filesystem().scan()
|
||||||
|
EditorInterface.save_all_scenes()
|
||||||
|
_open_play_opening_confirmation_dialog(target_path)
|
||||||
|
timer.queue_free()
|
||||||
|
timer.timeout.connect(callable)
|
||||||
|
add_child(timer)
|
||||||
|
timer.start(RESAVING_DELAY)
|
||||||
|
|
||||||
|
func _copy_to_directory(target_path : String):
|
||||||
|
ProjectSettings.set_setting(PROJECT_SETTINGS_PATH + "copy_path", target_path)
|
||||||
|
ProjectSettings.save()
|
||||||
|
if not target_path.ends_with("/"):
|
||||||
|
target_path += "/"
|
||||||
|
_copy_directory_path(get_plugin_examples_path(), target_path, ["md"])
|
||||||
|
_update_scene_loader_path(target_path)
|
||||||
|
_copy_override_file()
|
||||||
|
_delayed_saving_and_check_main_scene(target_path)
|
||||||
|
|
||||||
|
func _open_path_dialog():
|
||||||
|
var destination_scene : PackedScene = load(get_plugin_path() + "installer/destination_dialog.tscn")
|
||||||
|
var destination_instance : FileDialog = destination_scene.instantiate()
|
||||||
|
destination_instance.dir_selected.connect(_copy_to_directory)
|
||||||
|
destination_instance.canceled.connect(_check_main_scene_needs_updating.bind(get_plugin_examples_path()))
|
||||||
|
add_child(destination_instance)
|
||||||
|
|
||||||
|
func _open_confirmation_dialog():
|
||||||
|
var confirmation_scene : PackedScene = load(get_plugin_path() + "installer/copy_confirmation_dialog.tscn")
|
||||||
|
var confirmation_instance : ConfirmationDialog = confirmation_scene.instantiate()
|
||||||
|
confirmation_instance.confirmed.connect(_open_path_dialog)
|
||||||
|
confirmation_instance.canceled.connect(_check_main_scene_needs_updating.bind(get_plugin_examples_path()))
|
||||||
|
add_child(confirmation_instance)
|
||||||
|
|
||||||
|
func _show_plugin_dialogues():
|
||||||
|
if ProjectSettings.has_setting(PROJECT_SETTINGS_PATH + "disable_plugin_dialogues") :
|
||||||
|
if ProjectSettings.get_setting(PROJECT_SETTINGS_PATH + "disable_plugin_dialogues") :
|
||||||
|
return
|
||||||
|
_open_confirmation_dialog()
|
||||||
|
ProjectSettings.set_setting(PROJECT_SETTINGS_PATH + "disable_plugin_dialogues", true)
|
||||||
|
ProjectSettings.save()
|
||||||
|
|
||||||
|
func _resave_if_recently_opened():
|
||||||
|
if Engine.get_physics_frames() < MAX_PHYSICS_FRAMES_FROM_START:
|
||||||
|
var timer: Timer = Timer.new()
|
||||||
|
var callable := func():
|
||||||
|
if Engine.get_frames_per_second() >= 10:
|
||||||
|
timer.stop()
|
||||||
|
EditorInterface.save_scene()
|
||||||
|
timer.queue_free()
|
||||||
|
timer.timeout.connect(callable)
|
||||||
|
add_child(timer)
|
||||||
|
timer.start(OPEN_EDITOR_DELAY)
|
||||||
|
|
||||||
|
func _add_copy_tool_if_examples_exists():
|
||||||
|
var examples_path = get_plugin_examples_path()
|
||||||
|
var dir := DirAccess.open("res://")
|
||||||
|
if dir.dir_exists(examples_path):
|
||||||
|
add_tool_menu_item("Copy " + _get_plugin_name() + " Examples...", _open_path_dialog)
|
||||||
|
add_tool_menu_item("Delete " + _get_plugin_name() + " Examples...", _open_delete_examples_short_confirmation_dialog)
|
||||||
|
|
||||||
|
func _remove_copy_tool_if_examples_exists():
|
||||||
|
var examples_path = get_plugin_examples_path()
|
||||||
|
var dir := DirAccess.open("res://")
|
||||||
|
if dir.dir_exists(examples_path):
|
||||||
|
remove_tool_menu_item("Copy " + _get_plugin_name() + " Examples...")
|
||||||
|
remove_tool_menu_item("Delete " + _get_plugin_name() + " Examples...")
|
||||||
|
|
||||||
|
func _enter_tree():
|
||||||
|
add_autoload_singleton("AppConfig", get_plugin_path() + "base/scenes/autoloads/app_config.tscn")
|
||||||
|
add_autoload_singleton("SceneLoader", get_plugin_path() + "base/scenes/autoloads/scene_loader.tscn")
|
||||||
|
add_autoload_singleton("ProjectMusicController", get_plugin_path() + "base/scenes/autoloads/project_music_controller.tscn")
|
||||||
|
add_autoload_singleton("ProjectUISoundController", get_plugin_path() + "base/scenes/autoloads/project_ui_sound_controller.tscn")
|
||||||
|
_add_copy_tool_if_examples_exists()
|
||||||
|
_show_plugin_dialogues()
|
||||||
|
_resave_if_recently_opened()
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
remove_autoload_singleton("AppConfig")
|
||||||
|
remove_autoload_singleton("SceneLoader")
|
||||||
|
remove_autoload_singleton("ProjectMusicController")
|
||||||
|
remove_autoload_singleton("ProjectUISoundController")
|
||||||
|
_remove_copy_tool_if_examples_exists()
|
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 60 KiB |