WIP: refactor boot order, re-introduce menu animations

This commit is contained in:
betalars 2026-06-03 14:40:20 +02:00
parent 8bdd48a365
commit b2fd6af9f4
4 changed files with 113 additions and 59 deletions

View File

@ -1,5 +1,4 @@
extends Node extends Node
func _ready() -> void: func _enter_tree() -> void:
Main.normal_boot = true # Tell the system this is a normal game start Main.normal_boot = true # Tell the system this is a normal game start

View File

@ -3,9 +3,24 @@ class_name GlobalState
#region configuration #region configuration
signal settings_changed signal settings_changed
var _settings_initialized signal savegame_changed
var _settings_initialized: bool = false
var _savegame_initialized: bool = false
var all_ready: bool:
get():
return _settings_initialized and _savegame_initialized
func saves_loaded(save: SaveGame):
save_game = save
_savegame_initialized = true
var active_room: Room var active_room: Room
var save_game: SaveGame:
set(save):
save_game = save
savegame_changed.emit()
signal environment_settings_changed signal environment_settings_changed
# FIXME find a better way to switch fonts and maybe emit the theme_changed signal! # FIXME find a better way to switch fonts and maybe emit the theme_changed signal!
@ -167,10 +182,10 @@ func load_user_settings():
file.close() file.close()
var parsed: Dictionary = JSON.parse_string(raw_json) var parsed: Dictionary = JSON.parse_string(raw_json)
for kategory in parsed.values(): for kategory in parsed.keys():
for key in kategory.keys(): for key in parsed[kategory].keys():
if key in self: if key in self:
set(key, parsed[key]) set(key, parsed[kategory][key])
else: else:
if OS.has_feature("macos"): if OS.has_feature("macos"):
@ -225,14 +240,13 @@ func save_settings():
file.store_string(JSON.stringify(out_dict)) file.store_string(JSON.stringify(out_dict))
file.close() file.close()
_settings_initialized = true
settings_changed.emit() settings_changed.emit()
func _ready(): func _ready():
load_user_settings()
await get_tree().process_frame await get_tree().process_frame
music_volume = music_volume music_volume = music_volume
#region focus handling (called staging to avoid name colisions) #region focus handling (called staging to avoid name colisions)
# CAUTION: scene_reference directly accesses stage list to play sequences. # CAUTION: scene_reference directly accesses stage list to play sequences.

View File

@ -1,50 +1,49 @@
extends Node extends Control
var normal_boot : bool = false var normal_boot : bool = false
@onready var menu_animation: AnimationNodeStateMachinePlayback = %MenuAnimationTree.get("parameters/playback")
@export_file(".tscn") var youth_room_path: String @export_file(".tscn") var youth_room_path: String
@export_file(".tscn") var transition_room_path: String @export_file(".tscn") var transition_room_path: String
@export_file(".tscn") var adulthood_room_path: String @export_file(".tscn") var adulthood_room_path: String
@export_file(".tscn") var ending_path: String @export_file(".tscn") var ending_path: String
@onready var curtain: Curtain = %Curtain @onready var curtain: Panel = %Curtain
@onready var credits_roll: Control = %CreditsRoll @onready var credits_roll: Control = %CreditsRoll
@onready var main_menu: MainMenu = %MainMenu @onready var main_menu: MainMenu = %MainMenu
@onready var pause_menu: PauseMenu = %PauseMenu @onready var pause_menu: PauseMenu = %PauseMenu
@onready var room_paths := { @onready var room_paths := {
State.rooms.NULL: youth_room_path, # Maybe Draven story? Room.ids.NULL: youth_room_path, # Maybe Draven story?
State.rooms.YOUTH: youth_room_path, Room.ids.YOUTH: youth_room_path,
State.rooms.TRANSITION: transition_room_path, Room.ids.TRANSITION: transition_room_path,
State.rooms.ADULTHOOD: adulthood_room_path, Room.ids.ADULTHOOD: adulthood_room_path,
State.rooms.ENDING: ending_path Room.ids.ENDING: ending_path
} }
enum AppState {BOOT, MENU, PLAY, PAUSE, CREDITS} enum AppState {INIT, LOADING, MENU, PLAY, PAUSE, CREDITS}
var state: AppState = AppState.BOOT: var state: AppState = AppState.INIT:
set(value): set(value):
state = value
print("main.gd: app_state changing to: %s" % str(state)) print("main.gd: app_state changing to: %s" % str(state))
match state: match value:
AppState.BOOT: AppState.INIT:
credits_roll.hide() pass
main_menu.hide() AppState.LOADING:
pause_menu.hide() %MenuAnimationTree["parameters/conditions/loading_done"] = false
AppState.MENU: AppState.MENU:
credits_roll.hide() menu_animation.travel("loading_menu")
pause_menu.hide()
await main_menu.execute() await main_menu.execute()
AppState.PLAY: AppState.PLAY:
pass menu_animation.travel("start_game")
await_ui_clear()
hide()
AppState.PAUSE: AppState.PAUSE:
credits_roll.hide() menu_animation.travel("reveal_pause_menu")
main_menu.hide()
pause_menu.appear()
AppState.CREDITS: AppState.CREDITS:
main_menu.hide() menu_animation.travel("credits_roll")
pause_menu.hide()
credits_roll.play() state = value
func _enter_tree() -> void: func _enter_tree() -> void:
print("main.gd: _enter_tree()") print("main.gd: _enter_tree()")
@ -54,50 +53,79 @@ func _ready() -> void:
main_menu.continue_button.pressed.connect(func(): state = AppState.PLAY) main_menu.continue_button.pressed.connect(func(): state = AppState.PLAY)
main_menu.credits_button.pressed.connect(func(): state = AppState.CREDITS) main_menu.credits_button.pressed.connect(func(): state = AppState.CREDITS)
#TODO: Load the last savegame(?) #await get_tree().process_frame
await %Loading.stop() await await_boot_completed()
if normal_boot: if normal_boot:
print("main.gd: normal boot (loading last save and showing main menu)") print("main.gd: normal boot (loading last save and showing main menu)")
state = AppState.MENU call_deferred("start_menu")
else: else:
print("main.gd: direct boot (hiding menus and entering main loop)") print("main.gd: direct boot (hiding menus and entering main loop)")
state = AppState.PLAY state = AppState.PLAY
func start_menu():
func start_game(save: SaveGame) -> void: if Steam.resume_from_steamdeck():
start_game()
initialise_room(State.save_game.current_room)
state = AppState.MENU
func await_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 start_game(save: SaveGame = State.save_game) -> void:
print("main.gd: play_game()") print("main.gd: play_game()")
var room_path := room_paths.get(save.current_room, youth_room_path) as String initialise_room(save.current_room)
State.active_room.play()
state = AppState.PLAY state = AppState.PLAY
while room_path:
await _load_room(room_path)
room_path = await State.room.play()
# Ending? Roll credits? # Ending? Roll credits?
func is_game_active() -> bool:
return state == AppState.PLAY or state == AppState.PAUSE
func initialise_room(room_id: Room.ids):
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)
func _load_room(scene_path: String) -> void: func _load_room(scene_path: String) -> void:
await curtain.close()
%Loading.play()
if State.room:
State.room.unload()
State.room.queue_free()
State.room = null
ResourceLoader.load_threaded_request(scene_path, "PackedScene", true) ResourceLoader.load_threaded_request(scene_path, "PackedScene", true)
await get_tree().create_timer(0.1).timeout
if State.active_room:
State.active_room.unload()
State.active_room.queue_free()
State.active_room = null
while true: while true:
await get_tree().process_frame await get_tree().process_frame
var load_state := ResourceLoader.load_threaded_get_status(scene_path) var load_state := ResourceLoader.load_threaded_get_status(scene_path)
match load_state: match load_state:
ResourceLoader.THREAD_LOAD_LOADED: ResourceLoader.THREAD_LOAD_LOADED:
var next_scene := ResourceLoader.load_threaded_get(scene_path) as PackedScene var next_scene := ResourceLoader.load_threaded_get(scene_path) as PackedScene
State.room = next_scene.instantiate() as Room State.active_room = next_scene.instantiate() as Room
%Stage.add_child(State.room) %Stage.add_child(State.active_room)
await get_tree().process_frame await get_tree().process_frame
%Loading.stop() %MenuAnimationTree["parameters/conditions/loading_done"] = true
return return
ResourceLoader.THREAD_LOAD_FAILED: ResourceLoader.THREAD_LOAD_FAILED:
push_error("Failed to load room.") push_error("Failed to load room.")
@ -109,7 +137,10 @@ func _load_room(scene_path: String) -> void:
var last_mode := DisplayServer.WINDOW_MODE_WINDOWED var last_mode := DisplayServer.WINDOW_MODE_WINDOWED
func _unhandled_input(event: InputEvent) -> void: func _unhandled_input(event: InputEvent) -> void:
#if event.is_action_type(): print_debug("Unhandled Input", event) #if event.is_actionxxx_type(): print_debug("Unhandled Input", event)
if event.is_action_pressed("ui_pause") and state == AppState.PLAY:
state = AppState.PAUSE
if not Engine.is_editor_hint(): if not Engine.is_editor_hint():
if event.is_action_pressed("toggle_fullscreen"): if event.is_action_pressed("toggle_fullscreen"):

View File

@ -19,12 +19,14 @@ func _validate_property(property: Dictionary) -> void:
var _tween: Tween = null var _tween: Tween = null
func _ready() -> void: func _ready() -> void:
_load_games() _load_games()
hide() hide()
set_process_input(false) set_process_input(false)
back_button.pressed.connect(cancel) back_button.pressed.connect(cancel)
func _ensure_directory() -> void: func _ensure_directory() -> void:
var dir := DirAccess.open(State.user_saves_path) var dir := DirAccess.open(State.user_saves_path)
@ -49,8 +51,7 @@ func _load_games():
# Skip invalid/empty saves # Skip invalid/empty saves
if save != null and not save.is_empty: if save != null and not save.is_empty:
saves.append(save) saves.append(save)
State.saves_loaded(get_most_recent_save())
_sort_saves()
_rebuild_buttons() _rebuild_buttons()
@ -69,6 +70,7 @@ func _get_slot_number(save: SaveGame) -> int:
# Find this save's position in the creation-order list # Find this save's position in the creation-order list
return saves_by_creation.find(save) + 1 return saves_by_creation.find(save) + 1
func _rebuild_buttons() -> void: func _rebuild_buttons() -> void:
save_buttons = [] save_buttons = []
for child in list_container.get_children(): for child in list_container.get_children():
@ -93,6 +95,7 @@ func _rebuild_buttons() -> void:
func _on_game_picked(id: int) -> void: func _on_game_picked(id: int) -> void:
_picked.emit(saves[id]) _picked.emit(saves[id])
func _on_delete_requested(id: int) -> void: func _on_delete_requested(id: int) -> void:
var save_to_delete := saves[id] var save_to_delete := saves[id]
var save_path := save_to_delete.file_name var save_path := save_to_delete.file_name
@ -116,23 +119,28 @@ func _on_delete_requested(id: int) -> void:
var focus_index := mini(id, save_buttons.size() - 1) var focus_index := mini(id, save_buttons.size() - 1)
save_buttons[focus_index].grab_focus() save_buttons[focus_index].grab_focus()
func get_most_recent_save() -> SaveGame: func get_most_recent_save() -> SaveGame:
_sort_saves() _sort_saves()
return saves[0] if saves.size() > 0 else SaveGame.new() return saves[0] if saves.size() > 0 else SaveGame.new()
func has_more_saves() -> bool: func has_more_saves() -> bool:
for save in saves: for save in saves:
if save != State.save_game: if save != State.save_game:
return true return true
return false return false
func _gui_input(event: InputEvent) -> void: func _gui_input(event: InputEvent) -> void:
if event.is_action_pressed("ui_cancel"): if event.is_action_pressed("ui_cancel"):
cancel() cancel()
func cancel()->void: func cancel()->void:
_picked.emit(State.save_game) _picked.emit(State.save_game)
# This function is called when the user us supposed to choose a slot to load or create a new game. # This function is called when the user us supposed to choose a slot to load or create a new game.
func pick_save_slot() -> SaveGame: func pick_save_slot() -> SaveGame:
await open() await open()
@ -140,6 +148,7 @@ func pick_save_slot() -> SaveGame:
await close() await close()
return result return result
# TODO: ugh, godot tweens are the wurst # TODO: ugh, godot tweens are the wurst
func open() -> void: func open() -> void:
show() show()
@ -156,6 +165,7 @@ func open() -> void:
_tween.tween_property(self, "modulate", Color.WHITE, 0.5) _tween.tween_property(self, "modulate", Color.WHITE, 0.5)
await _tween.finished await _tween.finished
func close() -> void: func close() -> void:
if _tween != null: if _tween != null:
_tween.kill() _tween.kill()