MagicNStuff/source/components/collision/areas/dynamic_area_loader.gd
SchimmelSpreu83 c7f96c78c4 Huge changes
Implemented spawn points correctly.
Added saving & loading (currently only implemented with the spawn_index).
You can now set spawn indicies in the DynamicAreaLoader to automatically load when the game is set to that value (on ready).
Added configuration warnings to DynamicAreaLoader & PlayerSpawnPoint

Added some generic translation (pot) files.
Added AudioManager autoload to play, stop, fade audio streams (and set the bus directly).
Added SceneFader autoload.
Added SaveManager autoload.
Added OptionsMenu with currently only volume sliders and a fullscreen toggle button.
Added PauseMenu
2025-03-01 13:28:21 +01:00

132 lines
5.0 KiB
GDScript

@tool
@icon("dynamic_area_loader.svg")
class_name DynamicAreaLoader3D
extends Area3D
## An [Area3D] that dynamicly load a scene based on proximity.
##
## [b]Note:[/b] By default the [signal body_entered] and [signal body_exited] signals are linked
## to private [method _on_body_entered] and [method _on_body_exited] functions that check
## if the [param body] is the [member GameGlobals.player] and (un)load the area based on that.
## The signal emitted when the area to be loaded finished loading and was added to the scene tree.[br]
## You can combine this for eg. a door that only opens once the area behind it finished loading.
signal load_finished(loaded_node: Node)
## The node to load and add.[br][br]
## [b]IMPORTANT:[/b] The node needs to be marked as [b]Load as Placeholder[/b], otherwise nothing
## happens when loading (see [InstancePlaceholder]).
@export var placeholder_node: Node:
set(value):
placeholder_node = value
update_configuration_warnings()
## If [code]true[/code], the actual loading will be done separately in a thread.[br]
## This prevents the game from freezing for a moment when loading a large scene.[br]
## However, loading can be slower on lower-end hardware.
@export var threaded: bool = true
## How long [i](in seconds)[/i] the area will still be loaded and inside the scene tree
## when the [PlayerCharacter] exits this area.[br]This is to prevent the [PlayerCharacter]
## from keep re-loading the area by just walking back and fourth through the load zones.
@export_range(0.0, 10.0, 0.01, "or_greater", "suffix:s") var keep_loaded_duration: float = 3.0
## Automatically load the area when entering the scene tree, if the [member GameGlobals.spawn_point]
## is set to any of these values.
@export var loaded_if_spawnpoint: Array[int] = []
## The reference to the currently loaded node that was loaded.
var loaded_node: Node
func _ready() -> void:
if Engine.is_editor_hint():
return
if loaded_if_spawnpoint.has(GameGlobals.spawn_index):
load_area(null, true)
body_entered.connect(_on_body_entered)
body_exited.connect(_on_body_exited)
## Instances the area defined by [member placeholder_node].[br]
## See [method load_area_threaded] if you want to load the area in a thread instead
## of having a freeze during load.[br][br]
## If [param forced] is [code]true[/code], don't check if the player is colliding when calling this function.
func load_area(custom_scene: PackedScene = null, forced: bool = false) -> void:
assert(is_instance_valid(placeholder_node), "[placeholder_node] needs to be set.")
assert(placeholder_node is InstancePlaceholder, "[placeholder_node] needs to be marked as 'Load As Placeholder'.")
if is_instance_valid(loaded_node) or (not forced and not overlaps_body(GameGlobals.get_player())):
return
loaded_node = placeholder_node.create_instance(false, custom_scene)
load_finished.emit(loaded_node)
## Loads the scene defined in
## [member placeholder_node] ([member InstancePlaceholder.get_instance_path]) threaded.[br]
## Calls [method load_area] with the [param custom_scene] set to the loaded scene.[br][br]
## [param forced] does the same thing as in [method load_area].
func load_area_threaded(forced: bool = false) -> void:
assert(is_instance_valid(placeholder_node), "[placeholder_node] needs to be set.")
assert(placeholder_node is InstancePlaceholder, "[placeholder_node] needs to be marked as 'Load As Placeholder'.")
placeholder_node = placeholder_node as InstancePlaceholder
var path: String = placeholder_node.get_instance_path()
ResourceLoader.load_threaded_request(path, "", true)
while ResourceLoader.load_threaded_get_status(path) == ResourceLoader.THREAD_LOAD_IN_PROGRESS:
if not is_inside_tree():
return
await get_tree().process_frame
var scene: PackedScene = ResourceLoader.load_threaded_get(path)
load_area(scene, forced)
## Unloads ([method Node.queue_free]) the [member loaded_node].[br]
## If [param instantly_unload] is [code]true[/code], don't wait for the
## [member keep_loaded_duration] to end and instantly free the node.
func unload_area(instantly_unload: bool = false) -> void:
if not is_instance_valid(loaded_node):
return
if not instantly_unload and keep_loaded_duration > 0.0:
var duration: float = keep_loaded_duration
while is_inside_tree() and duration > 0.0:
if overlaps_body(GameGlobals.get_player()):
return
duration -= get_process_delta_time()
await get_tree().process_frame
loaded_node.queue_free()
func _on_body_entered(body: Node3D) -> void:
if body == GameGlobals.get_player():
if threaded:
load_area_threaded()
else:
load_area()
func _on_body_exited(body: Node3D) -> void:
if body == GameGlobals.get_player():
unload_area()
func _get_configuration_warnings() -> PackedStringArray:
if not is_instance_valid(placeholder_node):
return ["'placeholder_node' is not set."]
# TODO: Find a way to detect that in the editor.
if placeholder_node is not InstancePlaceholder and false:
return ["'placeholder_node' needs to be marked as 'Load As Placeholder'"]
return []