Improved the level loading system

This commit is contained in:
SchimmelSpreu83 2025-09-21 23:45:58 +02:00
parent b3b73433a2
commit 8a5373c340
13 changed files with 146 additions and 12 deletions

View File

@ -62,6 +62,20 @@ folder_colors={
[input] [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,"location":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,"location":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,"location":0,"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,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":true,"script":null)
]
}
move_forward={ move_forward={
"deadzone": 0.2, "deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)

View File

@ -1,6 +1,8 @@
class_name InteractiveLoader class_name InteractiveLoader
extends Node extends Node
@export var auto_free: bool = false
var load_progress: Array = [] var load_progress: Array = []
@ -26,4 +28,7 @@ func load_threaded(path: String) -> Resource:
else: else:
SPrint.print_msg("[Interactive Loader] is no longer in the scene tree. We can't wait for a process frame anymore, resulting in a freeze.", -1.0, SPrint.WARNING) SPrint.print_msg("[Interactive Loader] is no longer in the scene tree. We can't wait for a process frame anymore, resulting in a freeze.", -1.0, SPrint.WARNING)
if auto_free:
queue_free()
return ResourceLoader.load_threaded_get(path) return ResourceLoader.load_threaded_get(path)

View File

@ -10,6 +10,7 @@ func _enter_tree() -> void:
for spawnpoint: PlayerSpawnPoint in loaded_if_spawnpoint: for spawnpoint: PlayerSpawnPoint in loaded_if_spawnpoint:
if GameGlobals.spawn_id == spawnpoint.spawn_id: if GameGlobals.spawn_id == spawnpoint.spawn_id:
var was_threaded: bool = level_loader.load_threaded var was_threaded: bool = level_loader.load_threaded
level_loader.load_threaded = false
load_level() load_level()
level_loader.load_threaded = was_threaded level_loader.load_threaded = was_threaded
return return
@ -34,14 +35,14 @@ func unload_level() -> void:
if keep_loaded_duration > 0.0: if keep_loaded_duration > 0.0:
var duration: float = keep_loaded_duration var duration: float = keep_loaded_duration
while is_inside_tree() and duration > 0.0: while duration > 0.0:
if has_overlapping_bodies(): if has_overlapping_bodies():
return return
duration -= get_process_delta_time() * float(not get_tree().paused) duration -= get_process_delta_time() * float(not get_tree().paused)
await get_tree().process_frame await get_tree().process_frame
if not is_instance_valid(get_tree()): if not is_inside_tree() or not is_instance_valid(get_tree()):
return return
level_loader.unload_level() level_loader.unload_level()

View File

@ -5,22 +5,36 @@ extends Marker3D
signal level_loaded signal level_loaded
signal level_unloaded signal level_unloaded
static var initial_level_reference: PackedScene
@export_file("*.tscn", "*.scn") var scene_path: String @export_file("*.tscn", "*.scn") var scene_path: String
@export var load_threaded: bool = true @export var load_threaded: bool = true
@export_tool_button("Load Level", "Slot") var editor_load: Callable = load_level @export_tool_button("Load Level", "Slot") var editor_load: Callable = load_level
@export_tool_button("Unload Level", "Clear") var editor_unload: Callable = unload_level @export_tool_button("Unload Level", "Clear") var editor_unload: Callable = unload_level
@export_group("Level ID")
@export var override_level_id: bool = true
@export var level_id: StringName = &""
var level: Node var level: Node
var is_loading: bool = false
func load_level() -> void: func load_level() -> void:
if is_loading:
return
if not Engine.is_editor_hint() and override_level_id:
GameGlobals.set_level(level_id)
if is_instance_valid(level): if is_instance_valid(level):
if level.scene_file_path == scene_path: if _get_uid(level.scene_file_path) == _get_uid(scene_path):
push_warning("Level already loaded") push_warning("Level already loaded")
return return
unload_level() unload_level()
is_loading = true
var scene: PackedScene var scene: PackedScene
if load_threaded: if load_threaded:
@ -33,6 +47,8 @@ func load_level() -> void:
level = scene.instantiate() level = scene.instantiate()
add_child(level) add_child(level)
is_loading = false
initial_level_reference = null
level_loaded.emit() level_loaded.emit()
@ -45,11 +61,21 @@ func unload_level() -> void:
if child.owner == null: if child.owner == null:
child.queue_free() child.queue_free()
initial_level_reference = null
level_unloaded.emit() level_unloaded.emit()
func _load_level_threaded() -> PackedScene: func _load_level_threaded() -> PackedScene:
var interactive_loader := InteractiveLoader.new() var interactive_loader := InteractiveLoader.new()
interactive_loader.auto_free = true
add_child(interactive_loader) add_child(interactive_loader)
return await interactive_loader.load_threaded(scene_path) var resource: Resource = await interactive_loader.load_threaded(scene_path)
#interactive_loader.queue_free()
return resource
func _get_uid(path: String) -> int:
var uid: int = ResourceUID.text_to_id(ResourceUID.path_to_uid(path))
#print("UID: ", uid, " Path: ", path)
return uid

View File

@ -0,0 +1,31 @@
class_name LevelThresholdArea
extends Area3D
static var is_level_loading: bool = false
@export var level_loaders: Array[LevelLoader] = []
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
body_entered.connect(prevent_if_any_loading.unbind(1))
func prevent_if_any_loading() -> bool:
for loader: LevelLoader in level_loaders:
if not loader.is_loading:
continue
get_tree().paused = true
is_level_loading = true
SceneFader.set_loading_hints_visible(true)
while loader.is_loading:
await get_tree().process_frame
SceneFader.set_loading_hints_visible(false)
get_tree().paused = false
is_level_loading = false
return true
return false

View File

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

View File

@ -3,6 +3,8 @@ extends Node
const PLAYER: PackedScene = preload(GameGlobals.PLAYER_PATH) const PLAYER: PackedScene = preload(GameGlobals.PLAYER_PATH)
@export_group("Debug", "debug_")
@export_custom(PROPERTY_HINT_GROUP_ENABLE, "checkbox_only") var debug_enabled: bool = false
@export var debug_spawn_point: StringName = &"" @export var debug_spawn_point: StringName = &""
@export_range(-1, 0, 1, "or_greater") var debug_chapter_idx: int = -1 @export_range(-1, 0, 1, "or_greater") var debug_chapter_idx: int = -1
@ -10,14 +12,17 @@ var player_character: PlayerCharacter
func _init() -> void: func _init() -> void:
if OS.has_feature("editor") and not debug_spawn_point.is_empty(): GameGlobals.game = self
func _enter_tree() -> void:
if OS.has_feature("editor") and debug_enabled:
if not debug_spawn_point.is_empty():
GameGlobals.set_spawn_id(debug_spawn_point) GameGlobals.set_spawn_id(debug_spawn_point)
if debug_chapter_idx >= 0: if debug_chapter_idx >= 0:
GameGlobals.set_chapter_index(debug_chapter_idx) GameGlobals.set_chapter_index(debug_chapter_idx)
GameGlobals.game = self
# Called when the node enters the scene tree for the first time. # Called when the node enters the scene tree for the first time.
func _ready() -> void: func _ready() -> void:

View File

@ -3,15 +3,21 @@ extends Node
signal player_died(death_type: DeathTypes) signal player_died(death_type: DeathTypes)
const MAIN_MENU_PATH: String = "uid://dwd8wf02j2epn" const MAIN_MENU_PATH: String = "uid://dwd8wf02j2epn"
const INITIAL_LEVEL_ID: StringName = &"OutsideArea"
const GAME_PATH: String = "uid://s7cw6ulb7kh7" const GAME_PATH: String = "uid://s7cw6ulb7kh7"
const PLAYER_PATH: String = "uid://clhy3kiceqf2o" const PLAYER_PATH: String = "uid://clhy3kiceqf2o"
const CHAPTER_LIST: ChapterList = preload("uid://d2mcwotliowv7") const CHAPTER_LIST: ChapterList = preload("uid://d2mcwotliowv7")
const LEVELS: Dictionary[StringName, String] = {
&"OutsideArea": "uid://lraild3yetsh",
&"EntranceHall": "uid://cmdy6f6kesmbj",
}
enum DeathTypes { enum DeathTypes {
SAW, SAW,
} }
var game: Game var game: Game
var level: StringName = &""
var player: PlayerCharacter var player: PlayerCharacter
var player_alive: bool = true var player_alive: bool = true
var spawn_id: StringName = &"": set = set_spawn_id var spawn_id: StringName = &"": set = set_spawn_id
@ -26,6 +32,7 @@ func get_player() -> PlayerCharacter:
func reset_game() -> void: func reset_game() -> void:
player_alive = true player_alive = true
spawn_id = &"" spawn_id = &""
level = INITIAL_LEVEL_ID
chapter_index = 0 chapter_index = 0
in_cutscene = false in_cutscene = false
@ -36,6 +43,7 @@ func load_from_save() -> void:
var data: Dictionary[StringName, Variant] = SaveManager.persistent_data var data: Dictionary[StringName, Variant] = SaveManager.persistent_data
spawn_id = data.get(&"spawn_id") spawn_id = data.get(&"spawn_id")
chapter_index = data.get(&"chapter_index") chapter_index = data.get(&"chapter_index")
level = data.get(&"level", &"")
func set_spawn_id(id: StringName) -> void: func set_spawn_id(id: StringName) -> void:
@ -50,6 +58,12 @@ func set_chapter_index(index: int) -> void:
SaveManager.persistent_data[&"chapter_index"] = chapter_index SaveManager.persistent_data[&"chapter_index"] = chapter_index
func set_level(level_id: StringName) -> void:
level = level_id
SaveManager.persistent_data[&"level"] = level
func kill_player(death_type: DeathTypes) -> void: func kill_player(death_type: DeathTypes) -> void:
if not player_alive: if not player_alive:
return return

View File

@ -8,6 +8,7 @@ var persistent_data: Dictionary[StringName, Variant] = {
&"spawn_id": &"", &"spawn_id": &"",
&"sequence_index": 0, &"sequence_index": 0,
&"chapter_index": 0, &"chapter_index": 0,
&"level": &"",
&"level_data": { &"level_data": {
} }

View File

@ -21,17 +21,24 @@ func _ready() -> void:
color_rect.color.a = 0.0 color_rect.color.a = 0.0
func load_to_path(path: String) -> void: func load_to_path(path: String, initial_level: String = "") -> void:
if is_loading: if is_loading:
return return
is_loading = true is_loading = true
await fade_in() await fade_in()
await get_tree().create_timer(1.0).timeout #await get_tree().create_timer(1.0).timeout
# Load the initial level into memory first, so it can be retrived by other things later.
if not initial_level.is_empty():
LevelLoader.initial_level_reference = await interactive_loader.load_threaded(initial_level)
print("Initial Level Reference Loaded: ", LevelLoader.initial_level_reference)
var scene: PackedScene = await interactive_loader.load_threaded(path) var scene: PackedScene = await interactive_loader.load_threaded(path)
print("Scene Loaded: ", scene)
assert(is_instance_valid(scene), "Scene is invalid.") assert(is_instance_valid(scene), "Scene is invalid.")
get_tree().change_scene_to_packed(scene) get_tree().change_scene_to_packed(scene)

View File

@ -1,8 +1,9 @@
[gd_scene load_steps=8 format=3 uid="uid://dspysc2bld6eu"] [gd_scene load_steps=10 format=3 uid="uid://dspysc2bld6eu"]
[ext_resource type="Script" uid="uid://dfnb036hysorj" path="res://src/core/level/level_loader.gd" id="1_kntr3"] [ext_resource type="Script" uid="uid://dfnb036hysorj" path="res://src/core/level/level_loader.gd" id="1_kntr3"]
[ext_resource type="PackedScene" uid="uid://drr80goa61wrx" path="res://src/core/level/level_area.tscn" id="2_5mqkb"] [ext_resource type="PackedScene" uid="uid://drr80goa61wrx" path="res://src/core/level/level_area.tscn" id="2_5mqkb"]
[ext_resource type="Script" uid="uid://3hlvt5k34xva" path="res://src/core/player_spawn_point.gd" id="3_aoi14"] [ext_resource type="Script" uid="uid://3hlvt5k34xva" path="res://src/core/player_spawn_point.gd" id="3_aoi14"]
[ext_resource type="Script" uid="uid://dhrqjudaignuc" path="res://src/core/level/load_threshold_area.gd" id="3_mcc85"]
[ext_resource type="Script" uid="uid://dgsfc4i6bovwa" path="res://src/core/chapter/chapter_area.gd" id="4_sujqt"] [ext_resource type="Script" uid="uid://dgsfc4i6bovwa" path="res://src/core/chapter/chapter_area.gd" id="4_sujqt"]
[sub_resource type="BoxShape3D" id="BoxShape3D_aoi14"] [sub_resource type="BoxShape3D" id="BoxShape3D_aoi14"]
@ -11,6 +12,9 @@ size = Vector3(17, 9, 36)
[sub_resource type="BoxShape3D" id="BoxShape3D_5mqkb"] [sub_resource type="BoxShape3D" id="BoxShape3D_5mqkb"]
size = Vector3(31, 19, 37) size = Vector3(31, 19, 37)
[sub_resource type="BoxShape3D" id="BoxShape3D_mcc85"]
size = Vector3(4, 3, 0.5)
[sub_resource type="BoxShape3D" id="BoxShape3D_sujqt"] [sub_resource type="BoxShape3D" id="BoxShape3D_sujqt"]
size = Vector3(4, 3, 1) size = Vector3(4, 3, 1)
@ -21,12 +25,14 @@ size = Vector3(4, 3, 1)
[node name="OutsideArea" type="Marker3D" parent="LevelLoaders"] [node name="OutsideArea" type="Marker3D" parent="LevelLoaders"]
script = ExtResource("1_kntr3") script = ExtResource("1_kntr3")
scene_path = "uid://lraild3yetsh" scene_path = "uid://lraild3yetsh"
level_id = &"OutsideArea"
metadata/_custom_type_script = "uid://dfnb036hysorj" metadata/_custom_type_script = "uid://dfnb036hysorj"
[node name="EntranceHall" type="Marker3D" parent="LevelLoaders"] [node name="EntranceHall" type="Marker3D" parent="LevelLoaders"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.3, 3.5, 33) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.3, 3.5, 33)
script = ExtResource("1_kntr3") script = ExtResource("1_kntr3")
scene_path = "uid://cmdy6f6kesmbj" scene_path = "uid://cmdy6f6kesmbj"
level_id = &"EntranceHall"
metadata/_custom_type_script = "uid://dfnb036hysorj" metadata/_custom_type_script = "uid://dfnb036hysorj"
[node name="LevelAreas" type="Node" parent="."] [node name="LevelAreas" type="Node" parent="."]
@ -49,6 +55,25 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 9, 53)
shape = SubResource("BoxShape3D_5mqkb") shape = SubResource("BoxShape3D_5mqkb")
debug_color = Color(1, 1, 0, 0.41960785) debug_color = Color(1, 1, 0, 0.41960785)
[node name="LevelLoadThresholds" type="Node" parent="."]
[node name="LevelThresholdArea" type="Area3D" parent="LevelLoadThresholds" node_paths=PackedStringArray("level_loaders")]
collision_layer = 0
collision_mask = 2
script = ExtResource("3_mcc85")
level_loaders = [NodePath("../../LevelLoaders/OutsideArea"), NodePath("../../LevelLoaders/EntranceHall")]
metadata/_custom_type_script = "uid://dhrqjudaignuc"
[node name="CollisionShape" type="CollisionShape3D" parent="LevelLoadThresholds/LevelThresholdArea"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.8, 5, 36.2)
shape = SubResource("BoxShape3D_mcc85")
debug_color = Color(1, 0.5176471, 0, 0.41960785)
[node name="CollisionShape2" type="CollisionShape3D" parent="LevelLoadThresholds/LevelThresholdArea"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.8, 5, 33)
shape = SubResource("BoxShape3D_mcc85")
debug_color = Color(1, 0.5176471, 0, 0.41960785)
[node name="Spawnpoints" type="Node" parent="."] [node name="Spawnpoints" type="Node" parent="."]
[node name="OutsideSpawnPoint" type="Marker3D" parent="Spawnpoints"] [node name="OutsideSpawnPoint" type="Marker3D" parent="Spawnpoints"]

View File

@ -12,6 +12,7 @@ const AMBIENCE_STREAM: AudioStream = preload("uid://cyxvsy5wjxhl7")
func _ready() -> void: func _ready() -> void:
continue_button.disabled = not SaveManager.save_exists() continue_button.disabled = not SaveManager.save_exists()
(continue_button if not continue_button.disabled else new_game_button).grab_focus()
AudioManager.play_audio(AMBIENCE_STREAM, AudioManager.AMBIENCE) AudioManager.play_audio(AMBIENCE_STREAM, AudioManager.AMBIENCE)
@ -24,7 +25,7 @@ func _unhandled_input(event: InputEvent) -> void:
func load_game() -> void: func load_game() -> void:
AudioManager.fade_audio(AMBIENCE_STREAM, -80.0, SceneFader.fade_in_duration, AudioManager.AMBIENCE, true) AudioManager.fade_audio(AMBIENCE_STREAM, -80.0, SceneFader.fade_in_duration, AudioManager.AMBIENCE, true)
SceneFader.load_to_path(GameGlobals.GAME_PATH) SceneFader.load_to_path(GameGlobals.GAME_PATH, GameGlobals.LEVELS.get(GameGlobals.level, &""))
func _on_new_game_button_pressed() -> void: func _on_new_game_button_pressed() -> void:

View File

@ -10,6 +10,9 @@ var can_pause: bool = true
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
if LevelThresholdArea.is_level_loading:
return
if event.is_action_pressed(ACTION_PAUSE): if event.is_action_pressed(ACTION_PAUSE):
if options_menu.is_visible_in_tree(): if options_menu.is_visible_in_tree():
options_menu.close_menu() options_menu.close_menu()