107 lines
3.8 KiB
GDScript
107 lines
3.8 KiB
GDScript
@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
|
|
## 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
|
|
|
|
## The reference to the currently loaded node that was loaded.
|
|
var loaded_node: Node
|
|
|
|
|
|
func _ready() -> void:
|
|
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.
|
|
func load_area(custom_scene: PackedScene = null) -> void:
|
|
if is_instance_valid(placeholder_node) and placeholder_node is InstancePlaceholder:
|
|
if is_instance_valid(loaded_node) or 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.
|
|
func load_area_threaded() -> void:
|
|
if is_instance_valid(loaded_node):
|
|
return
|
|
|
|
if not is_instance_valid(placeholder_node) or placeholder_node is not InstancePlaceholder:
|
|
return
|
|
|
|
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)
|
|
|
|
|
|
## 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()
|