diff --git a/Autoload/LoadingContext.gd b/Autoload/LoadingContext.gd new file mode 100644 index 0000000..307dea2 --- /dev/null +++ b/Autoload/LoadingContext.gd @@ -0,0 +1,26 @@ +# LoadingContext.gd +# Autoload Singleton +# 负责在 start 场景和 LoadingScene 之间传递加载指令和必要的存档数据。 + +extends Node + +# 加载操作的类型 +# 可能的值: "new_game", "load_game", 或空字符串 (表示无操作或已处理) +var action_to_perform: String = "" + +# 当 action_to_perform 为 "load_game" 时,这里会存储从 SaveLoadManager 加载到的存档数据字典。 +# 其他情况下应为空字典。 +var save_data_for_loading: Dictionary = {} + + +# 清理上下文,在 LoadingScene 读取完数据后调用,避免旧数据影响下一次加载。 +func clear_context() -> void: + action_to_perform = "" + save_data_for_loading = {} + print("LoadingContext: Context cleared.") + +# (可选) 一个辅助函数,用于在设置上下文时打印信息,方便调试 +func set_context(action: String, data: Dictionary = {}) -> void: + action_to_perform = action + save_data_for_loading = data.duplicate(true) # 使用深拷贝以防外部修改 + print("LoadingContext: Context set - Action: ", action_to_perform, ", Data keys: ", save_data_for_loading.keys()) diff --git a/Autoload/LoadingContext.gd.uid b/Autoload/LoadingContext.gd.uid new file mode 100644 index 0000000..888f4e7 --- /dev/null +++ b/Autoload/LoadingContext.gd.uid @@ -0,0 +1 @@ +uid://cmx5osjpib4n5 diff --git a/Autoload/[文档]核心管理器说明文档.md b/Autoload/[文档]核心管理器说明文档.md index 5eb8555..3ff6fe9 100644 --- a/Autoload/[文档]核心管理器说明文档.md +++ b/Autoload/[文档]核心管理器说明文档.md @@ -1,6 +1,6 @@ -# 核心 Autoload 管理器说明文档 (v1.4 - 基于代码审查) +# 核心 Autoload 管理器说明文档 (v1.5 - 基于代码审查与新增 LoadingContext) -**文档目的:** 本文档旨在详细说明游戏核心的四个 Autoload 管理器 (`InitializationManager`, `InitialDataManager`, `GameState`, `SaveLoadManager`) 的设计、职责、交互方式以及对外提供的接口(API)。**特别注意:本文档描述的是基于“数据枢纽”模型的架构,并已根据最新代码实现进行更新。** 它是后续开发其他游戏玩法模块(如员工管理、项目管理、UI 等)时的**主要参考依据**,确保各模块能正确、高效地与核心系统协作。 +**文档目的:** 本文档旨在详细说明游戏核心的五个 Autoload 管理器 (`InitializationManager`, `InitialDataManager`, `GameState`, `SaveLoadManager`, `LoadingContext`) 的设计、职责、交互方式以及对外提供的接口(API)。**特别注意:本文档描述的是基于“数据枢纽”模型的架构,并已根据最新代码实现进行更新。** 它是后续开发其他游戏玩法模块(如员工管理、项目管理、UI 等)时的**主要参考依据**,确保各模块能正确、高效地与核心系统协作。 --- @@ -11,8 +11,8 @@ 1. **Key 管理:** 定义并维护模块自身所需状态数据在 `GameState` 中的唯一 `key`。建议采用命名空间或前缀避免冲突 (e.g., `"employee.list"`, `"project.active_ids"`)。 2. **数据结构定义:** 定义与模块相关的 `key` 所对应的 `value` 的具体数据结构(字典、数组、基础类型等)。 3. **初始化/状态恢复:** - * **必须**监听 `GameState` 的 `state_restored` 信号。 - * 在该信号的回调函数中,调用 `GameState.get_value(key)` 来获取本模块所需的所有数据。 + * **必须**监听 `GameState` 的 `state_restored` 信号 (用于读档后恢复) 以及 `state_initialized` 信号 (用于新游戏初始化后,如果模块需要在此时进行特定设置)。 + * 在这些信号的回调函数中,调用 `GameState.get_value(key)` 来获取本模块所需的所有数据。 * 使用获取到的数据完成模块自身的初始化或状态恢复(更新内部变量、刷新 UI 等)。 4. **数据读取与更新:** 通过 `GameState.get_value(key)` 读取状态,通过 `GameState.set_value(key, new_value)` 更新状态。 5. **业务逻辑验证:** **所有**业务逻辑相关的验证(如花钱前检查余额、操作是否允许等)**必须**在调用 `GameState.set_value` **之前**由模块自身完成。`GameState` 不进行任何业务逻辑校验。 @@ -25,40 +25,40 @@ ### 目标 (Purpose/Goal) * 作为游戏启动流程的总控制器,统一协调新游戏开始或存档加载的准备工作。 * 确保在通知模块进行状态恢复前,`GameState` 已被正确的数据填充。 -* 提供一个清晰的入口点来响应用户的启动选择(新游戏/加载)。 +* 提供一个清晰的入口点来响应用户的启动选择(新游戏/加载),通常由加载场景(Loading Scene)在获取必要数据后调用。 ### 核心职责 (Responsibilities) -* **流程决策:** 根据用户输入(来自主菜单 UI),决定启动“新游戏”流程还是“载入存档”流程。 +* **流程决策:** 根据传递的指令(通常来自加载场景,该场景会根据 `LoadingContext` 的信息决定)启动“新游戏”流程还是“载入存档”流程。 * **数据准备:** * **新游戏:** 确保 `InitialDataManager` 数据加载完成 -> 调用 `InitialDataManager` 获取初始数据 (`settings_data`, `task_dev_data`, `npc_initial_data`) -> **调用 `_build_initial_state` 方法,根据获取的数据构建注入 `GameState` 的初始状态字典。此方法会:** - * **合并基础设置 (`Init_Base.json`) 到状态字典的顶层。** - * **处理任务开发相关数据 (`task_development.json`),提取其中需要动态追踪的状态(如平台的 `enabled`, `Market_share`;玩法/主题的 `enabled`, `Experience`;策略的 `enabled`;产品侧重点的完整结构),并将这些动态状态组织在一个名为 `"task_development"` 的子字典内,再将该子字典存入主状态字典。** - * **处理 NPC 数据 (`npc.json`),将从 `npc_initial_data` 中获取的 NPC 定义(通常是 `npc_defs["npc"]` 部分,即 `npc.json` 文件中 "npc" 键对应的值)存入主状态字典的 `"npcs"` 键下。** - * **载入存档:** **接收**由外部(例如存档选择 UI,它会调用 `SaveLoadManager.load_game`)加载并传入的存档数据字典。 + * **合并基础设置 (`Init_Base.json` 的内容,通过 `settings_data` 传入) 到状态字典的顶层 (进行深拷贝)。** + * **处理任务开发相关数据 (`task_development.json` 的内容,通过 `task_dev_data` 传入),将其内部定义的 "platforms", "gameplays", "themes", "strategies", "product_focus_points" 等数据(经过深拷贝)组织在一个名为 `"task_development"` 的子字典内,再将该子字典存入主状态字典。** + * **处理 NPC 数据 (`npc.json` 的内容,通过 `npc_initial_data` 传入),将从 `npc_initial_data` 中获取的 NPC 定义 (通常是 `npc_initial_data["npc"]` 部分,即 `npc.json` 文件中 "npc" 键对应的值,进行深拷贝) 存入主状态字典的 `"npcs"` 键下。** + * **载入存档:** **接收**由外部(例如加载场景,它会从 `LoadingContext` 获取由 `SaveLoadManager.load_game` 加载的存档数据)传入的存档数据字典 (`save_data`)。 * **数据注入:** * **新游戏:** 将构建好的初始状态字典传递给 `GameState.initialize_for_new_game()`。 * **载入存档:** 将接收到的存档数据字典传递给 `GameState.restore_state()`。 -* **流程控制:** 管理初始化过程中的用户反馈(如显示/隐藏加载界面),并在 `GameState` 准备就绪后(通过信号通知),触发到主游戏场景的切换(此部分逻辑通常在此管理器或调用它的 UI 中实现)。 -* **错误处理:** 捕获并处理在初始化过程中(如 `InitialDataManager` 加载失败、构建初始状态失败)发生的错误,向用户提供反馈并阻止进入游戏。 +* **流程控制:** 管理初始化过程中的用户反馈(如显示/隐藏加载界面,通常由调用此管理器的加载场景处理),并在 `GameState` 准备就绪后(通过信号通知),触发到主游戏场景的切换(此部分逻辑通常在此管理器触发信号后,由加载场景或主UI控制器实现)。 +* **错误处理:** 捕获并处理在初始化过程中(如 `InitialDataManager` 加载失败、构建初始状态失败、获取的初始数据为空)发生的错误,向用户提供反馈并阻止进入游戏。 ### **对外接口 (Public API)** -* `start_new_game_process() -> bool`: 由主菜单调用,启动新游戏流程。成功返回 `true`,失败返回 `false`。 -* `load_game_process(save_data: Dictionary) -> bool`: **接收**已加载的存档数据字典 `save_data`,启动加载流程。成功返回 `true`,失败返回 `false`。(**注意:** 此函数**不负责**从文件加载数据,它期望 `save_data` 已经被加载好并传入)。 -* **注意:** 其他游戏玩法模块通常**不需要**在游戏运行过程中直接调用此管理器。其主要交互对象是启动阶段的 UI。 +* `start_new_game_process() -> bool`: 由加载场景或等效的流程控制器调用,启动新游戏流程。成功返回 `true`,失败返回 `false`。 +* `load_game_process(save_data: Dictionary) -> bool`: 由加载场景或等效的流程控制器调用,**接收**已加载的存档数据字典 `save_data`,启动加载流程。成功返回 `true`,失败返回 `false`。(**注意:** 此函数**不负责**从文件加载数据,它期望 `save_data` 已经被加载好并传入)。 +* **注意:** 其他游戏玩法模块通常**不需要**在游戏运行过程中直接调用此管理器。其主要交互对象是启动阶段的加载场景或主菜单后的流程控制逻辑。 ### **对外信号 (Public Signals)** * `new_game_initialized`: 在 `start_new_game_process` 成功执行,`GameState` 被初始化后发出。 * `game_loaded`: 在 `load_game_process` 成功执行,`GameState` 被恢复后发出。 ### 交互关键点 (Key Interactions) -* **接收:** 来自 UI 的调用 (`start_new_game_process`, `load_game_process`)。 +* **接收:** 来自加载场景 (Loading Scene) 或类似流程控制器的调用 (`start_new_game_process`, `load_game_process(save_data)`). `save_data` 通常通过 `LoadingContext` 传递给加载场景。 * **调用:** `InitialDataManager` (在新游戏流程中调用 `ensure_data_loaded`, `get_starting_settings`, `get_task_development_data`, `get_npc_initial_data`), `GameState` (调用 `initialize_for_new_game` 或 `restore_state`)。 * **发出:** `new_game_initialized`, `game_loaded` 信号。 -* **(可能)** `SceneTree` (用于切换场景,虽然未在提供的代码片段中直接显示,但通常是其职责一部分)。 +* **(可能)** 间接影响 `SceneTree` (通过信号触发场景切换,由其他脚本执行)。 ### 核心逻辑流程 (Core Logic/Flow) -* **新游戏:** (UI 调用 `start_new_game_process`) -> 显示加载 -> 确保 `InitialDataManager` 就绪 -> 获取初始数据 (`settings`, `task_dev`, `npc_initial_data`) -> **调用 `_build_initial_state` 构建包含 `"task_development"` 和 `"npcs"` 结构的初始状态字典** -> 调用 `GameState.initialize_for_new_game()` -> 发出 `new_game_initialized` 信号 -> (后续逻辑) 切换场景 -> 隐藏加载。 -* **载入存档:** (UI 调用 `SaveLoadManager.load_game(slot_id)` 得到 `save_data` 字典) -> (UI 调用 `load_game_process(save_data)`) -> 显示加载 -> (可选:确保 `InitialDataManager` 就绪) -> 调用 `GameState.restore_state(save_data)` -> 发出 `game_loaded` 信号 -> (后续逻辑) 切换场景 -> 隐藏加载。 +* **新游戏:** (UI 设置 `LoadingContext.action_to_perform = "new_game"`) -> (切换到加载场景) -> (加载场景调用 `start_new_game_process`) -> 显示加载 -> 确保 `InitialDataManager` 就绪 -> 获取初始数据 (`settings`, `task_dev`, `npc_initial_data`) -> **调用 `_build_initial_state` 构建包含 `"task_development"` 和 `"npcs"` 结构的初始状态字典** -> 调用 `GameState.initialize_for_new_game()` -> 发出 `new_game_initialized` 信号 -> (后续逻辑,如加载场景处理) 切换到主游戏场景 -> 隐藏加载。 +* **载入存档:** (UI 调用 `SaveLoadManager.load_game(slot_id)` 得到 `save_data` 字典) -> (UI 设置 `LoadingContext.set_context("load_game", save_data)`) -> (切换到加载场景) -> (加载场景读取 `LoadingContext` 并调用 `InitializationManager.load_game_process(LoadingContext.save_data_for_loading)`) -> 显示加载 -> (可选:确保 `InitialDataManager` 就绪,以防需要参考静态数据) -> 调用 `GameState.restore_state(save_data)` -> 发出 `game_loaded` 信号 -> (后续逻辑,如加载场景处理) 切换到主游戏场景 -> 隐藏加载。 * 在每个关键步骤后检查成功/失败状态,失败则中止流程并反馈用户。 ### 配置 (Configuration) @@ -80,11 +80,11 @@ * **数据提供:** 通过函数接口提供数据的**深拷贝**副本。 ### **对外接口 (Public API / Data Access Functions)** -* `ensure_data_loaded() -> bool`: 确保所有必需的初始数据 (`Init_Base.json`, `task_development.json`, `npc.json`) 已加载。通常由 `InitializationManager` 在新游戏流程开始时调用。返回加载是否成功。 +* `ensure_data_loaded() -> bool`: 确保所有必需的初始数据 (`Init_Base.json`, `task_development.json`, `npc.json`) 已加载。通常由 `InitializationManager` 在新游戏流程开始时或加载游戏流程中调用。返回加载是否成功。 * `get_starting_settings() -> Dictionary`: 获取 `Init_Base.json` 中的初始游戏设定。返回数据的深拷贝。 * `get_task_development_data() -> Dictionary`: 获取 `task_development.json` 中的所有数据。返回数据的深拷贝。 * `get_npc_initial_data() -> Dictionary`: 获取 `npc.json` 中的所有数据。返回数据的深拷贝。 -* **数据规范:** 返回的数据结构遵循 JSON 文件结构。所有通过此管理器获取的数据都应视为**只读**。 +* **数据规范:** 返回的数据结构遵循 JSON 文件结构。所有通过此管理器获取的数据都应视为**只读**。`npc.json` 文件应为字典结构,`task_development.json` 和 `Init_Base.json` 也应为字典结构。 ### **对外信号 (Public Signals)** * 通常不发出信号,因为它主要提供静态数据查询。 @@ -113,21 +113,21 @@ ### 核心职责 (Responsibilities) * **状态存储:** 内部维护一个核心字典 (`_state_data`),用于存储所有动态游戏状态的 Key-Value 对。 * **通用状态访问:** 提供通用的 `set_value`, `get_value` 接口。 -* **状态初始化:** 提供 `initialize_for_new_game` 方法,使用初始设置数据填充状态字典,**并在此过程中调用内部方法(如 `add_init_data`)根据已填充的 `_state_data` 的一部分(例如 `"task_development"`)来进一步初始化其他状态数据(例如 `"task_info"`),** 完成后发出 `state_initialized` 信号。 -* **状态恢复与通知:** 提供 `restore_state` 方法用于接收并覆盖整个状态字典,完成后发出 `state_restored` 信号。 +* **状态初始化:** 提供 `initialize_for_new_game` 方法,使用初始设置数据填充状态字典,**并在此过程中调用内部方法 (`add_init_data`),该方法会读取 `_state_data["task_development"]` 中的数据(如平台、玩法、主题、策略的启用状态和产品侧重点配置),用于构建并填充 `_state_data["task_development_info"]` 字典,该字典包含了当前选定的开发相关初始信息。** 完成后发出 `state_initialized` 信号。 +* **状态恢复与通知:** 提供 `restore_state` 方法用于接收并覆盖整个状态字典(进行深拷贝),完成后发出 `state_restored` 信号。 * **存档数据提供:** 提供 `get_savable_state` 方法,返回内部状态字典的**深拷贝**副本,供 `SaveLoadManager` 使用。 * **系统状态管理:** 通过内部的 `"system_status"` 字典(存储在 `_state_data["system_status"]`)管理如游戏暂停 (`game_pause`)、是否在任务中 (`is_on_task`) 等状态。提供方法来修改和查询这些特定状态。 ### **对外接口 (Public API / Generic State Access)** -* `initialize_for_new_game(initial_settings: Dictionary) -> void`: **(供 InitializationManager 调用)** 使用 `initial_settings` 字典(进行深拷贝)来初始化内部的 `_state_data`。**随后,会调用一个内部函数 (`add_init_data`),该函数读取 `_state_data` 中已存在的 `"task_development"` 数据,并用其来填充 `_state_data` 中的 `"task_info"` 键。** 完成后发出 `state_initialized` 信号。 -* `restore_state(loaded_data: Dictionary) -> void`: **(供 InitializationManager 调用)** 用 `loaded_data` 字典(进行深拷贝)**完全替换**内部的 `_state_data` 字典,然后发出 `state_restored` 信号。 +* `initialize_for_new_game(initial_settings: Dictionary) -> void`: **(供 InitializationManager 调用)** 使用 `initial_settings` 字典(进行深拷贝)来初始化内部的 `_state_data`。**随后,会调用一个内部函数 (`add_init_data`),该函数基于 `_state_data["task_development"]` 的内容(例如,查找各分类如平台、玩法、主题、开发策略中首个标记为 `enabled:true` 的条目,并获取产品侧重点数据)来构建和填充 `_state_data` 中的 `"task_development_info"` 字典(其键包括:"平台"、"玩法"、"题材"、"开发策略"、"预算"、"产品侧重点"等)。** 完成后发出 `state_initialized` 信号。如果 `_state_data` 中存在 `"time"` 键且其值为字典,还会发出 `date_changed` 兼容性信号。 +* `restore_state(loaded_data: Dictionary) -> void`: **(供 InitializationManager 调用)** 用 `loaded_data` 字典(进行深拷贝)**完全替换**内部的 `_state_data` 字典,然后发出 `state_restored` 信号。如果 `_state_data` 中存在 `"time"` 键且其值为字典,还会发出 `date_changed` 兼容性信号。 * `get_savable_state() -> Dictionary`: **(供 SaveLoadManager 调用)** 返回内部 `_state_data` 字典的**深拷贝** (`duplicate(true)`),用于存档,确保返回的是当前状态的一个独立快照。 * `get_value(key: String, default = null) -> Variant`: 根据 `key` 从内部字典检索 `value`。如果 `key` 不存在,返回 `default` 值。 * `set_value(key: String, value: Variant) -> void`: 向内部字典设置或更新一个 Key-Value 对。**注意:调用者负责确保 `value` 是可序列化的,并进行必要的业务逻辑验证。** 只有当新值与旧值**通过 `!=` (浅比较) 判断为不同**时,才会实际更新并发出 `state_value_changed` 信号。如果 `key` 是 `"time"` 且 `value` 是字典,还会额外发出 `date_changed` 兼容性信号。 * `pause_game() -> void`: **通过 `set_system_status_value("game_pause", true)` 修改状态,将 `"system_status"` 中的 `game_pause` 设置为 `true`。** * `resume_game() -> void`: **通过 `set_system_status_value("game_pause", false)` 修改状态,将 `"system_status"` 中的 `game_pause` 设置为 `false`。** -* `set_system_status_value(key: String, value: bool) -> void`: 设置 `"system_status"` 字典中指定 `key` (`"game_pause"` 或 `"is_on_task"`) 的布尔值。会触发 `state_value_changed` 信号(针对 `"system_status"` 键,传递更新后的整个 `"system_status"` 字典)。 +* `set_system_status_value(key: String, value: bool) -> void`: 设置 `"system_status"` 字典中指定 `key` (`"game_pause"` 或 `"is_on_task"`) 的布尔值。会触发 `state_value_changed` 信号(针对 `"system_status"` 键,传递更新后的整个 `"system_status"` 字典的副本)。 * `is_game_paused() -> bool`: 返回游戏是否暂停 (从 `"system_status"` 读取 `game_pause`,若 `"system_status"` 或键不存在则返回 `false`)。 * `is_on_task_status() -> bool`: 返回是否处于任务中状态 (从 `"system_status"` 读取 `is_on_task`,若 `"system_status"` 或键不存在则返回 `false`)。 @@ -135,10 +135,10 @@ * `state_initialized(initial_state: Dictionary)`: 在 `initialize_for_new_game` 方法成功执行,内部数据被初始化后发出。传递初始化后的状态字典的副本。 * `state_restored(restored_state: Dictionary)`: 在 `restore_state` 方法成功执行,内部数据被完全替换后发出。**所有需要状态的功能模块都应监听此信号**,以触发它们的初始化/状态恢复逻辑。传递恢复后的状态字典的副本。 * `state_value_changed(key: String, new_value: Variant)`: 在 `set_value` 被调用且值发生改变时发出。可用于需要对特定状态变化做出实时反应的模块。 -* `date_changed(new_date: Dictionary)`: **[兼容性信号]** 当 `set_value` 更新了键为 `"time"` 的状态,并且其新值为字典类型时发出。主要用于保持与旧代码或特定UI(如日期显示)的兼容性。传递新日期字典的副本。 +* `date_changed(new_date: Dictionary)`: **[兼容性信号]** 当 `set_value` 更新了键为 `"time"` 的状态,并且其新值为字典类型时发出,或者在 `initialize_for_new_game` / `restore_state` 后如果存在 `"time"` 键时发出。主要用于保持与旧代码或特定UI(如日期显示)的兼容性。传递新日期字典的副本。 ### 数据处理 (Data Handled) -* **核心:** 管理一个包含所有动态游戏状态的 Key-Value 字典 (`_state_data`)。**系统级状态如暂停 (`game_pause`) 和是否在任务中 (`is_on_task`) 被存储在 `_state_data` 下的 `"system_status"` 子字典中。** +* **核心:** 管理一个包含所有动态游戏状态的 Key-Value 字典 (`_state_data`)。**系统级状态如暂停 (`game_pause`) 和是否在任务中 (`is_on_task`) 被存储在 `_state_data` 下的 `"system_status"` 子字典中 (如果该子字典不存在,访问时会提供默认值)。** `_state_data` 中的 `"task_development_info"` 键则存储了当前选定的开发相关初始信息,由 `add_init_data` 方法在初始化时基于 `_state_data["task_development"]` 构建。 * **数据规范:** 不强制规定具体 Key 或 Value 的结构,由各个功能模块自行定义和管理。仅要求 Value 是可序列化的 `Variant`。 ### 配置 (Configuration) @@ -153,14 +153,14 @@ * 封装文件操作和 JSON 序列化/反序列化。 ### 核心职责 (Responsibilities) -* **存档 (Save):** 调用 `GameState.get_savable_state()` 获取状态字典 -> 添加元数据 (存档时间, 游戏版本) -> 序列化为 JSON -> 写入 `user://` 目录下的存档文件。 -* **读档 (Load):** 从 `user://` 目录读取存档文件 -> 解析 JSON 为字典 -> 验证基本结构 -> **返回**提取出的 `game_state` 部分字典给调用者 (例如 `InitializationManager` 或调用它的 UI)。 -* **文件管理:** 管理 `user://` 目录下的存档文件命名和存在性检查。 -* **错误处理:** 处理文件读写和 JSON 解析过程中的错误。 +* **存档 (Save):** 调用 `GameState.get_savable_state()` 获取状态字典 -> 添加元数据 (存档时间 ISO 8601 格式, 项目版本号) -> 序列化为 JSON (使用制表符缩进以提高可读性) -> 写入 `user://` 目录下的存档文件 (文件名如 `save_1.json`)。 +* **读档 (Load):** 从 `user://` 目录读取存档文件 -> 解析 JSON 为字典 -> 验证基本结构 (如存在 `game_state` 键且其值为字典) -> **返回**提取出的 `game_state` 部分字典给调用者 (例如主菜单UI,它随后会将此数据通过 `LoadingContext` 传递给加载流程)。如果失败(文件不存在、无法解析、格式错误等),则返回 `null`。 +* **文件管理:** 管理 `user://` 目录下的存档文件命名 (基于槽位 ID,如 `save_1.json`) 和存在性检查。 +* **错误处理:** 处理文件读写和 JSON 解析过程中的错误,并通过返回值或打印错误信息 (`printerr`) 进行反馈。 ### **对外接口 (Public API)** * `save_game(slot_id: int) -> bool`: 执行存档操作。调用 `GameState.get_savable_state()` 获取数据,然后写入到指定槽位的文件。返回是否成功。 (通常由存档 UI 调用) -* `load_game(slot_id: int) -> Variant`: 执行读档操作。读取指定槽位的文件,解析 JSON。**成功时返回包含 `game_state` 数据的字典 (Dictionary),失败时返回 `null`。** (通常由需要加载存档的 UI 或流程控制器调用,其结果再传递给 `InitializationManager.load_game_process`) +* `load_game(slot_id: int) -> Variant`: 执行读档操作。读取指定槽位的文件,解析 JSON。**成功时返回包含 `game_state` 数据的字典 (Dictionary),失败时返回 `null`。** (通常由需要加载存档的 UI 或流程控制器调用,其结果随后通过 `LoadingContext` 传递给 `InitializationManager.load_game_process`) * `does_save_exist(slot_id: int) -> bool`: 检查指定槽位是否存在存档文件。 ### **对外信号 (Public Signals)** @@ -168,13 +168,51 @@ ### 交互关键点 (Key Interactions) * **被调用:** UI (存档菜单调用 `save_game`, `does_save_exist`; 读档菜单调用 `load_game`)。 -* **调用:** `GameState` (仅在 `save_game` 时调用 `get_savable_state()`), `FileAccess` API, `JSON` API, `Time` API, `ProjectSettings` API, `OS` API。 +* **调用:** `GameState` (仅在 `save_game` 时调用 `get_savable_state()`), `FileAccess` API, `JSON` API, `Time` API, `ProjectSettings` API, `OS` API (用于 `is_debug_build` 检查)。 * **重要:** 开发者在编写其他模块时,**无需直接与 `SaveLoadManager` 交互**。模块的状态通过 `GameState` 间接被保存和加载。 ### 数据处理 (Data Handled) * **输入 (Save):** 从 `GameState` 获取的状态字典。 -* **输出 (Save):** `user://` 目录下的 JSON 文件 (包含 metadata 和 game_state)。 +* **输出 (Save):** `user://` 目录下的 JSON 文件 (包含 `metadata` 和 `game_state`)。 * **输入 (Load):** `user://` 目录下的 JSON 文件。 * **输出 (Load):** 解析后的 `game_state` 字典 (返回给调用者) 或 `null`。 --- + +## 5. 加载上下文管理器 (LoadingContext) + +### 目标 (Purpose/Goal) +* 作为在不同场景之间(尤其是从启动界面/主菜单到加载场景)传递加载指令和必要数据的临时容器。 +* 确保加载场景能够获取执行新游戏或加载存档所需的信息。 + +### 核心职责 (Responsibilities) +* **数据传递:** 临时存储加载操作的类型 (`"new_game"` 或 `"load_game"`)。 +* **存档数据暂存:** 当操作类型为 `"load_game"` 时,暂存从 `SaveLoadManager` 加载到的存档数据字典。 +* **上下文清理:** 提供方法以在数据使用完毕后(通常在加载场景完成其任务后)清除上下文,避免旧数据影响后续操作。 + +### **对外属性 (Public Properties)** +* `action_to_perform: String`: 存储当前的加载操作指令。 + * 可能的值: `"new_game"`, `"load_game"`, 或空字符串 (表示无操作或已处理)。 +* `save_data_for_loading: Dictionary`: 当 `action_to_perform` 为 `"load_game"` 时,这里存储从 `SaveLoadManager.load_game()` 获取到的存档数据字典。其他情况下为空字典。 + +### **对外接口 (Public API)** +* `clear_context() -> void`: 清理 `action_to_perform` 和 `save_data_for_loading`,将它们重置为空字符串和空字典。通常在加载场景使用完上下文信息后调用。 +* `set_context(action: String, data: Dictionary = {}) -> void`: 设置加载操作类型和可选的存档数据。传入的 `data` 会被深拷贝 (`duplicate(true)`) 以防止外部修改影响存储的上下文。 + +### **对外信号 (Public Signals)** +* *此管理器不发出信号。* + +### 交互关键点 (Key Interactions) +* **被设置:** 通常由主菜单或启动场景中的 UI 逻辑设置。 + * 例如,点击“新游戏”按钮时,调用 `set_context("new_game")`。 + * 例如,选择存档并成功调用 `SaveLoadManager.load_game(slot_id)` 后,调用 `set_context("load_game", loaded_save_data)`。 +* **被读取:** 主要由加载场景 (Loading Scene) 读取。加载场景根据 `action_to_perform` 的值来决定是调用 `InitializationManager.start_new_game_process()` 还是 `InitializationManager.load_game_process(save_data_for_loading)`。 +* **调用:** 无直接调用其他核心管理器,它是一个被动的数据容器。 +* **清理:** 加载场景在完成对 `InitializationManager` 的调用并准备切换到主游戏场景之前,应调用 `clear_context()`。 + +### 数据处理 (Data Handled) +* **输入:** 加载操作类型 (字符串) 和可选的存档数据 (字典)。 +* **存储:** 内部临时存储这些输入。 +* **输出:** 加载场景通过直接访问其公共属性来读取这些数据。 + +--- diff --git a/Entity/Level/LoadingScene.gd b/Entity/Level/LoadingScene.gd new file mode 100644 index 0000000..ec6cf4a --- /dev/null +++ b/Entity/Level/LoadingScene.gd @@ -0,0 +1,189 @@ +# LoadingScene.gd +# 负责驱动加载流程,更新UI,并在完成后切换到游戏主场景。 + +extends Control + +# --- 节点引用 --- +@onready var progress_bar: ProgressBar = $MainContainer/LoadingProgressBar +@onready var loading_label: Label = $MainContainer/LoadingLabel + +# --- 常量 --- +const GAME_SCENE_PATH = "res://Entity/Level/GameScene.tscn" # 请确保这个路径是正确的! + +# --- 内部状态变量 --- +var _current_action: String = "" +var _save_data_to_process: Dictionary = {} + +var _target_animation_progress: float = 0.0 # 进度条动画的目标值 (用于20%-80%的平滑过渡) +var _current_animation_progress: float = 0.0 # 进度条动画的当前值 +var _animation_speed: float = 30.0 # 进度条动画速度 (百分比/秒),可以根据需要调整 + +var _initialization_started: bool = false # 标记核心初始化流程是否已开始 +var _initialization_complete: bool = false # 标记核心初始化流程是否已完成 + + +func _ready(): + print("LoadingScene: Scene Ready.") + # 1. 初始化UI + _update_progress(0.0, "准备加载...") + + # 2. 从 LoadingContext 获取参数 + if not ProjectSettings.has_setting("autoload/LoadingContext"): + _handle_critical_error("LoadingContext Autoload 未配置!") + return + + _current_action = LoadingContext.action_to_perform + # 对存档数据进行深拷贝,以防万一LoadingContext中的原始数据被意外修改 + _save_data_to_process = LoadingContext.save_data_for_loading.duplicate(true) + + # 3. 清空 LoadingContext (这是一个好习惯) + LoadingContext.clear_context() + + # 验证获取到的action是否有效 + if _current_action != "new_game" and _current_action != "load_game": + _handle_critical_error("无效的加载指令: " + _current_action) + return + + print("LoadingScene: Action to perform - ", _current_action) + if _current_action == "load_game": + print("LoadingScene: Save data keys to process - ", _save_data_to_process.keys()) + + # 4. 更新进度到阶段1 (10%) + _update_progress(10.0, "加载参数读取完毕...") + + # 5. 连接 InitializationManager 的信号 + # 使用 CONNECT_ONE_SHOT 确保信号只触发一次,处理后自动断开,避免重复处理或内存泄漏 + if not ProjectSettings.has_setting("autoload/InitializationManager"): + _handle_critical_error("InitializationManager Autoload 未配置!") + return + + if _current_action == "new_game": + if InitializationManager.is_connected("new_game_initialized", Callable(self, "_on_initialization_manager_finished")): + printerr("LoadingScene: Warning - new_game_initialized signal already connected by this instance. This might indicate a logic error if _ready is called multiple times unexpectedly.") + else: + InitializationManager.new_game_initialized.connect(self._on_initialization_manager_finished, CONNECT_ONE_SHOT) + print("LoadingScene: Connected to InitializationManager.new_game_initialized.") + elif _current_action == "load_game": + if InitializationManager.is_connected("game_loaded", Callable(self, "_on_initialization_manager_finished")): + printerr("LoadingScene: Warning - game_loaded signal already connected by this instance.") + else: + InitializationManager.game_loaded.connect(self._on_initialization_manager_finished, CONNECT_ONE_SHOT) + print("LoadingScene: Connected to InitializationManager.game_loaded.") + + # 6. 使用 call_deferred 延迟调用实际的初始化启动函数 + # 这可以确保当前 _ready() 函数中的UI更新等操作完成后再开始耗时操作。 + call_deferred("_start_actual_initialization") + + +func _start_actual_initialization() -> void: + print("LoadingScene: Starting actual initialization...") + # 更新进度到阶段2 (20%) + _update_progress(20.0, "开始初始化核心数据...") + + _initialization_started = true + _current_animation_progress = 20.0 # 动画从20%开始 + _target_animation_progress = 80.0 # 动画目标到80% + + var success: bool = false + if _current_action == "new_game": + success = InitializationManager.start_new_game_process() + if not success: + _handle_initialization_failure("新游戏流程启动失败 (InitializationManager.start_new_game_process 返回 false)。") + elif _current_action == "load_game": + if _save_data_to_process.is_empty() and _current_action == "load_game": + # 这是一个潜在问题,如果我们要加载游戏,但没有存档数据 + _handle_initialization_failure("加载游戏失败:没有提供有效的存档数据。") + return + success = InitializationManager.load_game_process(_save_data_to_process) + if not success: + _handle_initialization_failure("加载游戏流程启动失败 (InitializationManager.load_game_process 返回 false)。") + + if success: + print("LoadingScene: Initialization process started via InitializationManager.") + # 如果 success 为 false,_handle_initialization_failure 已经被调用 + + +func _process(delta: float) -> void: + # 如果核心初始化已开始且未完成,则驱动进度条动画 + if _initialization_started and not _initialization_complete: + if _current_animation_progress < _target_animation_progress: + _current_animation_progress = move_toward(_current_animation_progress, _target_animation_progress, _animation_speed * delta) + # 只更新进度条的值,不频繁更新文本,避免性能开销和视觉跳动 + progress_bar.value = _current_animation_progress + # (可选) 如果动画已达到80%但仍在等待信号,可以添加一个小的等待动画或文本提示 + # else: + # loading_label.text = "正在等待核心数据处理完成..." (确保只设置一次) + + +# 当 InitializationManager 完成新游戏或加载游戏流程后,此函数被调用 +func _on_initialization_manager_finished() -> void: # success 参数已移除,因为信号发出即代表成功 + print("LoadingScene: Received signal from InitializationManager (initialization finished).") + _initialization_complete = true + + # 确保动画进度至少达到目标,或者直接跳到90% + _current_animation_progress = max(_current_animation_progress, _target_animation_progress) + + # 更新进度到阶段3 (90%) + _update_progress(90.0, "核心数据准备完毕!") + + # 【增加延时】在切换到游戏场景之前 + #print("LoadingScene: Adding a DEBUG delay before transitioning to GameScene...") + #await get_tree().create_timer(3.0).timeout # <--- 增加3秒延时 (用于调试) + #print("LoadingScene: DEBUG delay finished.") + + # 延迟切换到游戏场景 + call_deferred("_transition_to_game_scene") + + +func _transition_to_game_scene() -> void: + print("LoadingScene: Preparing to transition to GameScene...") + # 更新进度到阶段4 (100%) + _update_progress(100.0, "正在进入游戏...") + + # (可选) 给用户一个非常短暂的时间看到100% + # await get_tree().create_timer(0.1).timeout + # 注意: 上面的 await 会使函数变成异步,如果后续有更多同步代码,需要注意。 + # 对于简单的场景切换,直接调用通常没问题。 + + var err = get_tree().change_scene_to_file(GAME_SCENE_PATH) + if err != OK: + _handle_critical_error("切换到 GameScene 失败! 错误码: " + str(err) + "路径: " + GAME_SCENE_PATH) + # 此时游戏可能处于一个无法恢复的状态,可以考虑显示一个永久的错误信息 + + +# --- 辅助函数 --- + +func _update_progress(value: float, message: String = "") -> void: + progress_bar.value = value + if not message.is_empty(): + # 为了避免过于频繁地更新Label内容导致潜在的性能问题或闪烁, + # 我们可以只在关键阶段更新文本,或者确保文本内容确实改变了再更新。 + # 这里我们假设在关键阶段更新文本是可以接受的。 + loading_label.text = message # +" " + str(int(value)) + "%" + # ProgressBar的show_percentage=true会自动显示百分比 + print("LoadingScene: Progress updated - ", int(value), "% - ", message) + + +func _handle_initialization_failure(error_message: String) -> void: + printerr("LoadingScene: Initialization Failed - ", error_message) + _initialization_started = false # 停止任何进行中的动画逻辑 + _initialization_complete = true # 标记流程结束(虽然是失败的结束) + + # 更新UI以反映错误状态 + progress_bar.value = progress_bar.min_value # 或者一个特定的错误值/样式 + # 可以考虑给ProgressBar添加一个自定义主题,当发生错误时改变其颜色 + # progress_bar.add_theme_stylebox_override("fill", preload("res://error_progress_bar_fill_style.tres")) + loading_label.text = "错误: " + error_message + "\n请尝试返回主菜单或重启游戏。" + # 在这里,可以考虑启用一个“返回主菜单”的按钮,或者其他用户可进行的操作。 + # 例如: get_node("Path/To/RetryButton").disabled = false + + +func _handle_critical_error(error_message: String) -> void: + printerr("LoadingScene: CRITICAL ERROR - ", error_message) + _initialization_started = false + _initialization_complete = true # 阻止进一步操作 + if progress_bar: progress_bar.value = 0 + if loading_label: loading_label.text = "严重错误: " + error_message + "\n游戏可能无法继续。" + # 对于严重错误,可能除了显示信息外做不了太多 + # 可以禁用所有交互,或者尝试安全退出游戏 + # get_tree().quit() # 如果错误非常严重 diff --git a/Entity/Level/LoadingScene.gd.uid b/Entity/Level/LoadingScene.gd.uid new file mode 100644 index 0000000..8c818ad --- /dev/null +++ b/Entity/Level/LoadingScene.gd.uid @@ -0,0 +1 @@ +uid://dlvo4fwjsy7r5 diff --git a/Entity/Level/LoadingScene.tscn b/Entity/Level/LoadingScene.tscn new file mode 100644 index 0000000..05a3c7a --- /dev/null +++ b/Entity/Level/LoadingScene.tscn @@ -0,0 +1,47 @@ +[gd_scene load_steps=3 format=3 uid="uid://4wbnffj2cbmc"] + +[ext_resource type="FontFile" uid="uid://egugs822n8gr" path="res://UI/font/AlimamaFangYuanTiVF-Thin.ttf" id="1_7week"] +[ext_resource type="Script" uid="uid://dlvo4fwjsy7r5" path="res://Entity/Level/LoadingScene.gd" id="1_sd15f"] + +[node name="LoadingScene" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_sd15f") + +[node name="BackgroundColor" type="ColorRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0, 0, 0, 1) + +[node name="MainContainer" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -150.0 +offset_top = -23.5 +offset_right = 150.0 +offset_bottom = 23.5 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="LoadingLabel" type="Label" parent="MainContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +theme_override_fonts/font = ExtResource("1_7week") +text = "加载中..." +horizontal_alignment = 1 + +[node name="LoadingProgressBar" type="ProgressBar" parent="MainContainer"] +custom_minimum_size = Vector2(300, 0) +layout_mode = 2 diff --git a/Entity/Level/start.gd b/Entity/Level/start.gd index 6bb6d67..09163ba 100644 --- a/Entity/Level/start.gd +++ b/Entity/Level/start.gd @@ -3,13 +3,13 @@ extends Node2D # --- 节点引用 --- -# !! 重要:请根据你的实际场景结构调整以下节点路径 !! -@onready var new_game_button = $UI/Select_List/New -@onready var load_game_button = $UI/Select_List/Load +@onready var new_game_button: Button = $UI/Select_List/New +@onready var load_game_button: Button = $UI/Select_List/Load # @onready var error_message_label = $UI/ErrorMessageLabel # 如果有错误提示标签 # --- 场景路径 --- -var GameScene_path = "res://Entity/Level/GameScene.tscn" +# const GameScene_path = "res://Entity/Level/GameScene.tscn" # 不再由此脚本直接切换 +const LOADING_SCENE_PATH = "res://Entity/Level/LoadingScene.tscn" # <--- 新增:LoadingScene的路径,请确保正确! # --- Godot 生命周期方法 --- @@ -17,13 +17,15 @@ func _ready(): print("Start Scene: Initializing...") # --- 连接 UI 信号 --- if new_game_button: - new_game_button.pressed.connect(_on_new_game_pressed) + if not new_game_button.is_connected("pressed", Callable(self, "_on_new_game_pressed")): + new_game_button.pressed.connect(self._on_new_game_pressed) print("Start Scene: Connected 'pressed' signal for New Game button.") else: printerr("Start Scene Error: New Game button node not found at path specified in @onready var.") if load_game_button: - load_game_button.pressed.connect(_on_load_game_pressed) + if not load_game_button.is_connected("pressed", Callable(self, "_on_load_game_pressed")): + load_game_button.pressed.connect(self._on_load_game_pressed) print("Start Scene: Connected 'pressed' signal for Load Game button.") # 示例:根据存档是否存在禁用加载按钮 (需要 SaveLoadManager Autoload) # if ProjectSettings.has_setting("autoload/SaveLoadManager"): @@ -33,146 +35,124 @@ func _ready(): else: printerr("Start Scene Error: Load Game button node not found at path specified in @onready var.") - # --- 连接 InitializationManager 信号 --- - # 确保 InitializationManager 是 Autoload 并且已经注册 - if ProjectSettings.has_setting("autoload/InitializationManager"): - # 连接新游戏初始化完成信号 - if not InitializationManager.is_connected("new_game_initialized", Callable(self, "_on_initialization_complete")): - InitializationManager.new_game_initialized.connect(_on_initialization_complete) - print("Start Scene: Connected to InitializationManager.new_game_initialized signal.") + # --- 移除 InitializationManager 信号连接 --- + # 不再需要在此处连接 InitializationManager 的信号,这些将由 LoadingScene 处理。 + # 例如,以下代码需要被移除或注释掉: + # if ProjectSettings.has_setting("autoload/InitializationManager"): + # if not InitializationManager.is_connected("new_game_initialized", Callable(self, "_on_initialization_complete")): + # InitializationManager.new_game_initialized.connect(self._on_initialization_complete) + # if not InitializationManager.is_connected("game_loaded", Callable(self, "_on_initialization_complete")): + # InitializationManager.game_loaded.connect(self._on_initialization_complete) + # else: + # printerr("Start Scene Error: InitializationManager Autoload is not configured...") - # 连接加载游戏完成信号 - if not InitializationManager.is_connected("game_loaded", Callable(self, "_on_initialization_complete")): - InitializationManager.game_loaded.connect(_on_initialization_complete) - print("Start Scene: Connected to InitializationManager.game_loaded signal.") - else: - # 如果没有配置 Autoload,这是一个严重错误 - printerr("Start Scene Error: InitializationManager Autoload is not configured in Project Settings!") - # 在这种情况下,游戏可能无法正常启动新游戏或加载 - if new_game_button: new_game_button.disabled = true - if load_game_button: load_game_button.disabled = true # 当此节点从场景树中移除时调用 func _exit_tree(): - # 断开与 Autoload 信号的连接,这是一个良好的实践 - if ProjectSettings.has_setting("autoload/InitializationManager"): - # 断开新游戏信号 - if InitializationManager.is_connected("new_game_initialized", Callable(self, "_on_initialization_complete")): - InitializationManager.new_game_initialized.disconnect(Callable(self, "_on_initialization_complete")) - print("Start Scene: Disconnected from InitializationManager.new_game_initialized signal.") - # 断开加载游戏信号 - if InitializationManager.is_connected("game_loaded", Callable(self, "_on_initialization_complete")): - InitializationManager.game_loaded.disconnect(Callable(self, "_on_initialization_complete")) - print("Start Scene: Disconnected from InitializationManager.game_loaded signal.") + # --- 移除 InitializationManager 信号断开 --- + # 同样,不再需要在此处断开 InitializationManager 的信号。 + # if ProjectSettings.has_setting("autoload/InitializationManager"): + # if InitializationManager.is_connected("new_game_initialized", Callable(self, "_on_initialization_complete")): + # InitializationManager.new_game_initialized.disconnect(Callable(self, "_on_initialization_complete")) + # if InitializationManager.is_connected("game_loaded", Callable(self, "_on_initialization_complete")): + # InitializationManager.game_loaded.disconnect(Callable(self, "_on_initialization_complete")) + pass # 如果没有其他需要在退出时清理的,可以留空或移除此函数 + # --- UI 信号回调函数 --- # 当“新游戏”按钮被按下时调用 func _on_new_game_pressed(): - print("Start Scene: 'New Game' button pressed. Requesting new game initialization...") - if new_game_button: new_game_button.disabled = true + print("Start Scene: 'New Game' button pressed. Transitioning to Loading Scene...") + if new_game_button: new_game_button.disabled = true # 禁用按钮防止重复点击 if load_game_button: load_game_button.disabled = true - if ProjectSettings.has_setting("autoload/InitializationManager"): - # 调用 InitializationManager 启动流程 - # start_new_game_process 返回 true/false 表示调用是否成功启动(例如数据加载失败) - if not InitializationManager.start_new_game_process(): - # 如果启动流程本身就失败了(例如 InitialDataManager 无法加载数据) - printerr("Start Scene: InitializationManager.start_new_game_process() failed immediately.") - # 显示错误信息给用户 - # if error_message_label: error_message_label.text = "无法启动新游戏,请检查配置。" - # 重新启用按钮 - if new_game_button: new_game_button.disabled = false - if load_game_button: load_game_button.disabled = false - # else: # 如果启动成功,则等待 _on_initialization_complete 信号 - # print("Start Scene: New game process started, waiting for completion signal...") - else: - printerr("Start Scene Error: Cannot start new game - InitializationManager Autoload not found!") + # 检查 LoadingContext Autoload 是否存在 + if not ProjectSettings.has_setting("autoload/LoadingContext"): + printerr("Start Scene CRITICAL Error: LoadingContext Autoload not configured!") + # 重新启用按钮,让用户可以重试或意识到问题 if new_game_button: new_game_button.disabled = false if load_game_button: load_game_button.disabled = false + # (可选) 显示错误信息给用户 + # if error_message_label: error_message_label.text = "错误:无法准备加载流程!" + return + + # 设置 LoadingContext + LoadingContext.action_to_perform = "new_game" + LoadingContext.save_data_for_loading = {} # 确保对于新游戏,存档数据为空 + + # 切换到 LoadingScene + var err = get_tree().change_scene_to_file(LOADING_SCENE_PATH) + if err != OK: + printerr("Start Scene CRITICAL ERROR: Failed to switch to Loading Scene! Error code: " + str(err)) + if new_game_button: new_game_button.disabled = false + if load_game_button: load_game_button.disabled = false + # (可选) 显示错误信息给用户 + # if error_message_label: error_message_label.text = "错误:无法打开加载界面!" # 当“加载游戏”按钮被按下时调用 func _on_load_game_pressed(): - var slot_to_load = 1 # <-- 硬编码加载的存档槽位 ID + var slot_to_load = 1 # <-- 您可以根据需要修改或添加选择存档槽的逻辑 print("Start Scene: 'Load Game' button pressed. Requesting load for slot " + str(slot_to_load) + "...") if new_game_button: new_game_button.disabled = true if load_game_button: load_game_button.disabled = true - # --- 需要 SaveLoadManager 来获取存档数据 --- + # 检查必要的 Autoloads if not ProjectSettings.has_setting("autoload/SaveLoadManager"): - printerr("Start Scene Error: Cannot load game - SaveLoadManager Autoload not found!") + printerr("Start Scene CRITICAL Error: SaveLoadManager Autoload not configured!") if new_game_button: new_game_button.disabled = false if load_game_button: load_game_button.disabled = false - return # 无法继续 + return + if not ProjectSettings.has_setting("autoload/LoadingContext"): + printerr("Start Scene CRITICAL Error: LoadingContext Autoload not configured!") + if new_game_button: new_game_button.disabled = false + if load_game_button: load_game_button.disabled = false + return - # 1. 尝试从 SaveLoadManager 加载数据 (现在期望返回字典或 null) + # 1. 尝试从 SaveLoadManager 加载数据 var loaded_data: Variant = SaveLoadManager.load_game(slot_to_load) - # 2. 检查加载是否成功 (返回 null 表示失败) + # 2. 检查加载是否成功 if loaded_data == null: printerr("Start Scene: Failed to load save data from slot " + str(slot_to_load) + " (SaveLoadManager returned null).") - # 显示错误信息给用户 - # if error_message_label: error_message_label.text = "加载存档失败!" - # 重新启用按钮 if new_game_button: new_game_button.disabled = false if load_game_button: load_game_button.disabled = false - return # 加载失败,中止 + # (可选) 显示错误信息给用户 + # if error_message_label: error_message_label.text = "加载存档失败!" + return - # 2.1 (可选但推荐) 再次确认加载的数据是字典类型 if not loaded_data is Dictionary: printerr("Start Scene Error: Loaded data from SaveLoadManager is not a Dictionary. Type:", typeof(loaded_data)) if new_game_button: new_game_button.disabled = false if load_game_button: load_game_button.disabled = false - return # 数据类型错误,中止 + return - # 3. 如果存档数据加载成功 (是字典),再调用 InitializationManager 处理 - if ProjectSettings.has_setting("autoload/InitializationManager"): - # 将加载到的字典传递给 load_game_process - if not InitializationManager.load_game_process(loaded_data): # <--- 传递字典 - printerr("Start Scene: InitializationManager.load_game_process() failed.") - # 显示错误信息给用户 - # if error_message_label: error_message_label.text = "应用存档状态时出错。" - # 重新启用按钮 - if new_game_button: new_game_button.disabled = false - if load_game_button: load_game_button.disabled = false - # else: # 如果启动成功,则等待 _on_initialization_complete 信号 - # print("Start Scene: Load game process started, waiting for completion signal...") - else: - printerr("Start Scene Error: Cannot process loaded game - InitializationManager Autoload not found!") + # 3. 设置 LoadingContext + LoadingContext.action_to_perform = "load_game" + LoadingContext.save_data_for_loading = loaded_data # 将加载到的字典传递过去 + + # 4. 切换到 LoadingScene + var err = get_tree().change_scene_to_file(LOADING_SCENE_PATH) + if err != OK: + printerr("Start Scene CRITICAL ERROR: Failed to switch to Loading Scene! Error code: " + str(err)) if new_game_button: new_game_button.disabled = false if load_game_button: load_game_button.disabled = false # --- InitializationManager 信号回调函数 --- - -# 当 InitializationManager 完成新游戏或加载游戏流程后,此函数被调用 -func _on_initialization_complete(): # 移除了 success 参数 - print("Start Scene: Received initialization complete signal (New Game or Load).") - # 既然信号被发出,我们认为初始化流程是成功的 - print("--------------------------------------------------") - print("Start Scene: Initialization successful!") - print("--------------------------------------------------") - - # 切换到主游戏场景 - Go_To_GameScene() # 保持不变 - - # 注意:这里没有处理初始化失败的情况,因为失败应该在 - # start_new_game_process 或 load_game_process 返回 false 时, - # 或者在 SaveLoadManager.load_game 失败时,在调用处直接处理。 - # 如果 InitializationManager 内部在发出信号前还可能失败, - # 则需要重新考虑信号设计或错误处理流程。 +# func _on_initialization_complete(): # <--- 此方法不再需要,可以安全移除或注释掉 +# print("Start Scene: Received initialization complete signal (New Game or Load).") +# print("--------------------------------------------------") +# print("Start Scene: Initialization successful!") +# print("--------------------------------------------------") +# Go_To_GameScene() # --- 辅助函数 --- - -# 切换到主游戏场景 (保持不变) -func Go_To_GameScene(): - print("Start Scene: Attempting to switch to Game Scene:", GameScene_path) - var err = get_tree().change_scene_to_file(GameScene_path) - if err != OK: - printerr("Start Scene CRITICAL ERROR: Failed to switch to main game scene!") - printerr(" Path: " + GameScene_path) - printerr(" Error code: " + str(err)) - # if error_message_label: error_message_label.text = "无法加载主游戏场景!" +# func Go_To_GameScene(): # <--- 此方法不再需要,因为场景切换由 LoadingScene 处理 +# print("Start Scene: Attempting to switch to Game Scene:", GameScene_path) +# var err = get_tree().change_scene_to_file(GameScene_path) +# if err != OK: +# printerr("Start Scene CRITICAL ERROR: Failed to switch to main game scene!") diff --git a/project.godot b/project.godot index b159554..36d8929 100644 --- a/project.godot +++ b/project.godot @@ -23,6 +23,7 @@ InitialDataManager="*res://Autoload/InitialDataManager.gd" GameState="*res://Autoload/GameState.gd" SaveLoadManager="*res://Autoload/SaveLoadManager.gd" InitializationManager="*res://Autoload/InitializationManager.gd" +LoadingContext="*res://Autoload/LoadingContext.gd" [display]