re-implement dynamic loading during menu
This commit is contained in:
parent
b2fd6af9f4
commit
8244f85790
|
|
@ -6,7 +6,8 @@ class_name Room
|
|||
@onready var ui: Control = %UI
|
||||
|
||||
## Tells the main loop to proceed to the next scene
|
||||
signal proceed(next_scene_path: String)
|
||||
signal prepare_next(room_id: Room.ids)
|
||||
signal proceed_to(room_id: Room.ids)
|
||||
|
||||
enum ids {
|
||||
NULL,
|
||||
|
|
@ -49,19 +50,18 @@ func get_ready_async():
|
|||
|
||||
func start_room_async():
|
||||
prints("----------", "START_ROOM", self.name, "--------------")
|
||||
await Main.curtain.open()
|
||||
get_tree().paused = false
|
||||
await get_tree().process_frame # so this registers as a coroutine in IDE
|
||||
|
||||
|
||||
func play() -> String:
|
||||
for i in range(20): await get_tree().process_frame #HACK - can probably be removed
|
||||
|
||||
func play() -> void:
|
||||
await get_ready_async()
|
||||
await start_room_async()
|
||||
|
||||
var next_room : StringName = await proceed
|
||||
prints("----------", "PROCEEDING", next_room, "--------------")
|
||||
return next_room
|
||||
|
||||
func prepare_for_unload(next_room: Room.ids):
|
||||
prints("----------", "PREPARE_UNLOAD", Room.ids.keys()[id], "PREPARE_LOAD", Room.ids.keys()[next_room], "--------------")
|
||||
prepare_next.emit(next_room)
|
||||
|
||||
func pull_save_state(save: SaveGame) -> void:
|
||||
# Override this function to load the state of the chapter from State.save_game
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ class_name SaveGame extends Resource
|
|||
|
||||
@export var unique_save_name: String = ""
|
||||
@export var current_room: Room.ids = Room.ids.NULL
|
||||
@export_flags("Intro", "Childhood", "Voice Training", "Jui Jutsu") var mementos_complete: int = 0
|
||||
@export_flags_2d_physics var sequences_enabled: int = 63
|
||||
@export_flags_2d_physics var mementos_complete: int = 0
|
||||
@export_flags_2d_physics var sequences_enabled: int = 0
|
||||
|
||||
# Board state - properly typed fields
|
||||
@export var board_positions: Dictionary[StringName, Vector2] = {} # Position of all cards and stickies
|
||||
|
|
@ -19,7 +19,7 @@ class_name SaveGame extends Resource
|
|||
@export var seen : Array[StringName] = []
|
||||
|
||||
@export var childhood_board_complete: bool = false
|
||||
@export var subway_burnout : bool = false
|
||||
@export var subway_uni : bool = false
|
||||
|
||||
@export var player_position : Vector3 = Vector3.ZERO
|
||||
@export var player_yaw : float = 0.0
|
||||
|
|
@ -35,7 +35,7 @@ var current_room_path: String:
|
|||
get: return Main.room_paths[current_room] if Main else ""
|
||||
|
||||
var is_empty: bool:
|
||||
get: return not FileAccess.file_exists(file_name) or (current_room == State.rooms.NULL)
|
||||
get: return not FileAccess.file_exists(file_name) or (current_room == Room.ids.NULL)
|
||||
|
||||
var completed_sequences: int:
|
||||
get:
|
||||
|
|
@ -53,7 +53,8 @@ var total_connections: int:
|
|||
# === State Variables / External Data ===
|
||||
## Where to save the savegame to / where it was loaded from
|
||||
var file_name: String = ""
|
||||
|
||||
## This ensures compatibility with Steam, because it will give absolute paths only.
|
||||
var absolute_path: String = ""
|
||||
## Screenshot or placeholder image
|
||||
var thumbnail: Texture = preload("res://import/interface-elements/empty_save_slot.png")
|
||||
|
||||
|
|
@ -102,7 +103,16 @@ static func load_from_file(save_filepath: String) -> SaveGame:
|
|||
if not loaded.is_valid:
|
||||
push_error("SaveGame: Validation failed: %s" % save_filepath)
|
||||
return null
|
||||
|
||||
|
||||
if not save_filepath.is_absolute_path():
|
||||
var helper:= FileAccess.open(save_filepath, FileAccess.READ)
|
||||
loaded.absolute_path = helper.get_path_absolute()
|
||||
helper.close()
|
||||
else:
|
||||
loaded.absolute_path = save_filepath
|
||||
|
||||
Steamworks.save_list[loaded.absolute_path] = loaded
|
||||
|
||||
return loaded
|
||||
|
||||
## Helper to load thumbnail from separate PNG file
|
||||
|
|
@ -148,6 +158,9 @@ func save_to_file(screen_shot: Texture2D) -> void:
|
|||
capture_player_state()
|
||||
last_saved = int(Time.get_unix_time_from_system())
|
||||
|
||||
if Steamworks.steam_cloud_on:
|
||||
Steam.beginFileWriteBatch()
|
||||
|
||||
# Save thumbnail
|
||||
_save_thumbnail(screen_shot)
|
||||
|
||||
|
|
@ -157,6 +170,16 @@ func save_to_file(screen_shot: Texture2D) -> void:
|
|||
push_error("Failed to save resource to: %s (Error: %d)" % [file_name, result])
|
||||
else:
|
||||
print("Successfully saved to: %s" % file_name)
|
||||
|
||||
if not file_name.is_absolute_path():
|
||||
var helper:= FileAccess.open(file_name, FileAccess.READ)
|
||||
absolute_path = helper.get_path_absolute()
|
||||
helper.close()
|
||||
else:
|
||||
absolute_path = file_name
|
||||
|
||||
if Steamworks.steam_cloud_on:
|
||||
Steam.endFileWriteBatch()
|
||||
|
||||
## Processes and saves thumbnail as PNG
|
||||
func _save_thumbnail(screen_shot: Texture2D) -> void:
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ func _ready():
|
|||
|
||||
func vanish():
|
||||
super.vanish()
|
||||
await Main.curtain.black() # Go straight to loading screen
|
||||
State.room.proceed.emit(Main.transition_room_path)
|
||||
await Main.curtain.blackout() # Go straight to loading screen
|
||||
State.active_room.proceed_to.emit(Room.ids.TRANSITION)
|
||||
|
||||
|
||||
## Main play coroutine - simple linear flow for burning a card
|
||||
|
|
@ -43,6 +43,7 @@ func play() -> void:
|
|||
await $AnimationPlayer.animation_finished
|
||||
|
||||
print("CardBurner: Sequence complete")
|
||||
vanish()
|
||||
|
||||
|
||||
func _populate() -> void:
|
||||
|
|
|
|||
|
|
@ -229,7 +229,7 @@ func pick_cards(id: Scenes.id):
|
|||
|
||||
if id == Scenes.id.YOUTH_DRAVEN:
|
||||
$Meaning.play()
|
||||
State.room.scene_player.play("intro")
|
||||
State.active_room.scene_player.play("intro")
|
||||
|
||||
await cards_picked
|
||||
hide()
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@ var all_ready: bool:
|
|||
return _settings_initialized and _savegame_initialized
|
||||
|
||||
func saves_loaded(save: SaveGame):
|
||||
save_game = save
|
||||
_savegame_initialized = true
|
||||
if not _savegame_initialized:
|
||||
save_game = save
|
||||
_savegame_initialized = true
|
||||
|
||||
|
||||
var active_room: Room
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
extends Control
|
||||
|
||||
var normal_boot : bool = false
|
||||
@onready var menu_animation: AnimationNodeStateMachinePlayback = %MenuAnimationTree.get("parameters/playback")
|
||||
@onready var menu_animation: AnimationPlayer = %MenuAnimationPlayer
|
||||
@onready var load_animation: AnimationPlayer = %LoadAnimation
|
||||
|
||||
@export_file(".tscn") var youth_room_path: String
|
||||
@export_file(".tscn") var transition_room_path: String
|
||||
@export_file(".tscn") var adulthood_room_path: String
|
||||
@export_file(".tscn") var ending_path: String
|
||||
|
||||
@onready var curtain: Panel = %Curtain
|
||||
@onready var curtain: Curtain = %Curtain
|
||||
@onready var credits_roll: Control = %CreditsRoll
|
||||
@onready var main_menu: MainMenu = %MainMenu
|
||||
@onready var pause_menu: PauseMenu = %PauseMenu
|
||||
|
|
@ -25,24 +26,39 @@ enum AppState {INIT, LOADING, MENU, PLAY, PAUSE, CREDITS}
|
|||
|
||||
var state: AppState = AppState.INIT:
|
||||
set(value):
|
||||
print("main.gd: app_state changing to: %s" % str(state))
|
||||
print("main.gd: app_state changing to: %s" % str(value))
|
||||
match value:
|
||||
AppState.INIT:
|
||||
pass
|
||||
AppState.LOADING:
|
||||
%MenuAnimationTree["parameters/conditions/loading_done"] = false
|
||||
credits_roll.hide()
|
||||
main_menu.hide()
|
||||
pause_menu.hide()
|
||||
AppState.MENU:
|
||||
menu_animation.travel("loading_menu")
|
||||
await main_menu.execute()
|
||||
credits_roll.hide()
|
||||
pause_menu.hide()
|
||||
main_menu.execute()
|
||||
hotswap_ready = true
|
||||
AppState.LOADING:
|
||||
credits_roll.hide()
|
||||
main_menu.hide()
|
||||
pause_menu.hide()
|
||||
AppState.PLAY:
|
||||
menu_animation.travel("start_game")
|
||||
await_ui_clear()
|
||||
hide()
|
||||
credits_roll.hide()
|
||||
main_menu.hide()
|
||||
pause_menu.hide()
|
||||
hotswap_ready = false
|
||||
menu_animation.play("hide_pause_menu")
|
||||
if state == AppState.PAUSE:
|
||||
menu_animation.play("hide_pause_menu")
|
||||
AppState.PAUSE:
|
||||
menu_animation.travel("reveal_pause_menu")
|
||||
credits_roll.hide()
|
||||
main_menu.hide()
|
||||
pause_menu.appear()
|
||||
menu_animation.play("reveal_pause_menu")
|
||||
AppState.CREDITS:
|
||||
menu_animation.travel("credits_roll")
|
||||
|
||||
main_menu.hide()
|
||||
pause_menu.hide()
|
||||
credits_roll.play()
|
||||
|
||||
state = value
|
||||
|
||||
func _enter_tree() -> void:
|
||||
|
|
@ -52,94 +68,164 @@ func _ready() -> void:
|
|||
print("main.gd: _ready()")
|
||||
main_menu.continue_button.pressed.connect(func(): state = AppState.PLAY)
|
||||
main_menu.credits_button.pressed.connect(func(): state = AppState.CREDITS)
|
||||
State.savegame_changed.connect(_on_savegame_changed)
|
||||
|
||||
#await get_tree().process_frame
|
||||
await await_boot_completed()
|
||||
await _boot_completed()
|
||||
|
||||
if normal_boot:
|
||||
print("main.gd: normal boot (loading last save and showing main menu)")
|
||||
call_deferred("start_menu")
|
||||
startup.call_deferred()
|
||||
|
||||
else:
|
||||
curtain.open()
|
||||
print("main.gd: direct boot (hiding menus and entering main loop)")
|
||||
state = AppState.PLAY
|
||||
|
||||
func start_menu():
|
||||
|
||||
if Steam.resume_from_steamdeck():
|
||||
start_game()
|
||||
|
||||
initialise_room(State.save_game.current_room)
|
||||
state = AppState.MENU
|
||||
|
||||
#region sequence handling
|
||||
|
||||
func await_boot_completed():
|
||||
func _boot_completed():
|
||||
print("main.gd: Awaiting Boot Completion ...")
|
||||
while not State.all_ready:
|
||||
await get_tree().process_frame
|
||||
print("main.gd: Boot Completed.")
|
||||
|
||||
func await_ui_clear():
|
||||
print("main.gd: Awaiting Menu Clear ...")
|
||||
while not menu_animation.get_current_node() == "start_game":
|
||||
await get_tree().process_frame
|
||||
print("main.gd: Menu Cleared.")
|
||||
func startup():
|
||||
state = AppState.MENU
|
||||
initialise_room(State.get_room_id())
|
||||
|
||||
func start_game(save: SaveGame = State.save_game) -> void:
|
||||
print("main.gd: play_game()")
|
||||
initialise_room(save.current_room)
|
||||
State.active_room.play()
|
||||
state = AppState.PLAY
|
||||
if await initialise_room(save.current_room):
|
||||
State.active_room.play()
|
||||
State.active_room.prepare_next.connect(prepare_next_room)
|
||||
State.active_room.proceed_to.connect(swap_room)
|
||||
state = AppState.PLAY
|
||||
else:
|
||||
push_error("main.gd: Room failed to load during game startup!")
|
||||
|
||||
# Ending? Roll credits?
|
||||
func prepare_next_room(new_room: Room.ids):
|
||||
_load_room(room_paths.get(new_room, youth_room_path) as String)
|
||||
|
||||
func swap_room(new_room: Room.ids):
|
||||
print("main.gd: Swapping rooms ...")
|
||||
get_tree().paused = true
|
||||
state = AppState.LOADING
|
||||
State.save_game.current_room = new_room
|
||||
# initialize_room is blocked from completing because of hotswap being false
|
||||
start_game()
|
||||
await curtain.blackout()
|
||||
hotswap_ready = true
|
||||
# room will start as soon as loading is done.
|
||||
|
||||
|
||||
func is_game_active() -> bool:
|
||||
return state == AppState.PLAY or state == AppState.PAUSE
|
||||
|
||||
|
||||
func initialise_room(room_id: Room.ids):
|
||||
func initialise_room(room_id: Room.ids) -> bool:
|
||||
if State.active_room:
|
||||
if State.active_room.id == room_id:
|
||||
return
|
||||
else:
|
||||
menu_animation.travel("change_savegame")
|
||||
_load_room(room_paths.get(room_id, youth_room_path) as String)
|
||||
return true
|
||||
return await _load_room(room_paths.get(room_id, youth_room_path) as String)
|
||||
|
||||
|
||||
func _load_room(scene_path: String) -> void:
|
||||
ResourceLoader.load_threaded_request(scene_path, "PackedScene", true)
|
||||
## Loops for at least one frame and until a room is safely loaded.
|
||||
func assure_room_initialized():
|
||||
# just to make extra sure there is no room put to queue_free.
|
||||
await get_tree().physics_frame
|
||||
while not (current_loadpath == null and State.active_room):
|
||||
await get_tree().physics_frame
|
||||
|
||||
await get_tree().create_timer(0.1).timeout
|
||||
|
||||
func handle_save_update(files_changed: PackedStringArray, files_deleted: PackedStringArray):
|
||||
main_menu.save_game_list.reload_all_saves()
|
||||
|
||||
if files_deleted.has(State.save_game.absolute_path):
|
||||
State.save_game = main_menu.save_game_list.get_most_recent_save()
|
||||
elif files_changed.has(State.save_game.absolute_path):
|
||||
State.save_game = Steamworks.save_list[State.save_game.absolute_path]
|
||||
|
||||
#region load handling
|
||||
|
||||
var load_id: int
|
||||
var current_loadpath: String
|
||||
var hotswap_ready: bool = true
|
||||
|
||||
## Unloads the current room, there is no harm in calling this multiple times.
|
||||
func _unload_current_room():
|
||||
if State.active_room:
|
||||
State.active_room.unload()
|
||||
State.active_room.queue_free()
|
||||
State.active_room = null
|
||||
|
||||
while true:
|
||||
## This function starts loading a new room in the background. Calling it twice will result in the previous load being abandoned.
|
||||
func _load_room(scene_path: String) -> bool:
|
||||
var this_load_id := randi()
|
||||
|
||||
var error:= ResourceLoader.load_threaded_request(scene_path, "PackedScene", true)
|
||||
|
||||
if error == Error.OK:
|
||||
%LoadAnimation.play("LoadAnimation/loading_idle")
|
||||
load_id = this_load_id
|
||||
current_loadpath = scene_path
|
||||
|
||||
while true:
|
||||
await get_tree().process_frame
|
||||
var load_state := ResourceLoader.load_threaded_get_status(scene_path)
|
||||
if this_load_id != load_id:
|
||||
push_warning("main.gd: Interrupt while loading a room, aborting.")
|
||||
if current_loadpath != scene_path:
|
||||
call_deferred("_clear_loader_cache", scene_path)
|
||||
return false
|
||||
match load_state:
|
||||
ResourceLoader.THREAD_LOAD_LOADED:
|
||||
while not hotswap_ready:
|
||||
await get_tree().process_frame
|
||||
_unload_current_room()
|
||||
var next_scene := ResourceLoader.load_threaded_get(scene_path) as PackedScene
|
||||
current_loadpath = ""
|
||||
State.active_room = next_scene.instantiate() as Room
|
||||
%Stage.add_child(State.active_room)
|
||||
await get_tree().process_frame
|
||||
load_animation.play("LoadAnimation/loading_done")
|
||||
curtain.open()
|
||||
return true
|
||||
ResourceLoader.THREAD_LOAD_FAILED:
|
||||
break
|
||||
|
||||
push_error("main.gd: Couldn't load room %s" % scene_path)
|
||||
else:
|
||||
push_error(error_string(error))
|
||||
return false
|
||||
|
||||
# As a load request cannot be aborted, this makes sure the Cache is cleared.
|
||||
func _clear_loader_cache(path):
|
||||
while ResourceLoader.load_threaded_get_status(path) == ResourceLoader.THREAD_LOAD_IN_PROGRESS:
|
||||
await get_tree().process_frame
|
||||
var load_state := ResourceLoader.load_threaded_get_status(scene_path)
|
||||
match load_state:
|
||||
ResourceLoader.THREAD_LOAD_LOADED:
|
||||
var next_scene := ResourceLoader.load_threaded_get(scene_path) as PackedScene
|
||||
State.active_room = next_scene.instantiate() as Room
|
||||
%Stage.add_child(State.active_room)
|
||||
await get_tree().process_frame
|
||||
%MenuAnimationTree["parameters/conditions/loading_done"] = true
|
||||
return
|
||||
ResourceLoader.THREAD_LOAD_FAILED:
|
||||
push_error("Failed to load room.")
|
||||
break
|
||||
|
||||
assert(false, "Couldn't load room %s" % scene_path)
|
||||
if ResourceLoader.load_threaded_get_status(path) == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
var dump = ResourceLoader.load_threaded_get(path)
|
||||
|
||||
func _on_savegame_changed():
|
||||
if is_game_active():
|
||||
push_warning("SaveGame change during active play. Might be unintentional.")
|
||||
get_tree().paused = true
|
||||
await curtain.blackout()
|
||||
if State.active_room.id != State.save_game.current_room:
|
||||
state = AppState.MENU
|
||||
_unload_current_room()
|
||||
else:
|
||||
Prompts.display_hint("reload_hint", 5)
|
||||
await State.active_room.play()
|
||||
return
|
||||
|
||||
initialise_room(State.save_game.current_room)
|
||||
|
||||
var last_mode := DisplayServer.WINDOW_MODE_WINDOWED
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
#if event.is_actionxxx_type(): print_debug("Unhandled Input", event)
|
||||
|
||||
if event.is_action_pressed("ui_pause") and state == AppState.PLAY:
|
||||
if event.is_action_pressed("pause") and state == AppState.PLAY:
|
||||
state = AppState.PAUSE
|
||||
|
||||
if not Engine.is_editor_hint():
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -6,34 +6,62 @@ var _tween : Tween = null
|
|||
func _ready() -> void:
|
||||
print("curtain.gd: ready()")
|
||||
visible = true
|
||||
_check_boot.call_deferred()
|
||||
_check_boot()
|
||||
|
||||
func _check_boot():
|
||||
self.visible = Main.normal_boot
|
||||
if visible:
|
||||
self.modulate = Color.BLACK
|
||||
_tween_with_interrupt(Color.WHITE, 1.0, true)
|
||||
|
||||
## Conceals the Game Stage
|
||||
func close() -> void:
|
||||
visible = true
|
||||
print("curtain.gd: show()")
|
||||
if _tween: _tween.kill()
|
||||
_tween = create_tween()
|
||||
_tween.tween_property(self, "modulate", Color.WHITE, 0.7)
|
||||
await _tween.finished
|
||||
func close() -> bool:
|
||||
if visible and modulate == Color.WHITE:
|
||||
return true
|
||||
if await blackout():
|
||||
return await _tween_with_interrupt(Color.WHITE, 0.7, true)
|
||||
return false
|
||||
|
||||
## Conceals the Game Stage
|
||||
func black() -> void:
|
||||
func blackout() -> bool:
|
||||
if visible and modulate == Color.BLACK:
|
||||
return true
|
||||
visible = true
|
||||
print("curtain.gd: show()")
|
||||
if _tween: _tween.kill()
|
||||
_tween = create_tween()
|
||||
_tween.tween_property(self, "modulate", Color.BLACK, 0.7)
|
||||
await _tween.finished
|
||||
return await _tween_with_interrupt(Color.BLACK, 0.2, true)
|
||||
|
||||
## Makes the Game Stage Visible
|
||||
func open() -> void:
|
||||
func open() -> bool:
|
||||
if not visible:
|
||||
return true
|
||||
print("curtain.gd: hide()")
|
||||
if _tween: _tween.kill()
|
||||
if not visible: return true
|
||||
var no_interrupt = await _tween_with_interrupt(Color.TRANSPARENT, 0.2, false)
|
||||
if no_interrupt:
|
||||
visible = false
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
signal _tween_finished(successful: bool)
|
||||
var _target_visibility: bool = false
|
||||
## This allows multiple places to call the curtain close to each other with the only interference caused by mismatched visibility requirements.
|
||||
func _tween_with_interrupt(to_color: Color, for_duration: float, target_visible: bool) -> bool:
|
||||
if _tween:
|
||||
if target_visible and _target_visibility:
|
||||
return await _tween_finished
|
||||
else:
|
||||
_tween.kill()
|
||||
|
||||
_tween = create_tween()
|
||||
_tween.tween_property(self, "modulate", Color.TRANSPARENT, 0.7)
|
||||
await _tween.finished
|
||||
visible = false
|
||||
_tween.tween_property(self, "modulate", to_color, for_duration)
|
||||
while true:
|
||||
if _tween.is_valid():
|
||||
if not _tween.is_running():
|
||||
_tween_finished.emit(true)
|
||||
return true
|
||||
await get_tree().process_frame
|
||||
else:
|
||||
_tween_finished.emit(false)
|
||||
return false
|
||||
return false
|
||||
|
|
|
|||
Loading…
Reference in New Issue