Compare commits

...

2 Commits

Author SHA1 Message Date
jkboy
c58b1f8ed8 新的ui工作流框架 2025-05-10 21:20:19 +08:00
jkboy
3bb02fcde6 使用新的ui工作流框架,并对task_development进行了实装。 2025-05-10 21:17:07 +08:00
69 changed files with 17472 additions and 375 deletions

View File

@ -30,13 +30,14 @@ var _state_data: Dictionary = {}
# 为新游戏初始化状态。 # 为新游戏初始化状态。
# 直接使用传入的 initial_settings 字典填充状态。 # 直接使用传入的 initial_settings 字典填充状态。
# 这个函数无需修改,因为它已经正确处理了包含 "system_status" 的 _state_data。
func initialize_for_new_game(initial_settings: Dictionary) -> void: func initialize_for_new_game(initial_settings: Dictionary) -> void:
print("GameState: Initializing state directly from initial settings...") print("GameState: Initializing state directly from initial settings...")
# 使用深拷贝 (duplicate(true)),确保 GameState 拥有独立的数据副本 # 使用深拷贝 (duplicate(true)),确保 GameState 拥有独立的数据副本
_state_data = initial_settings.duplicate(true) _state_data = initial_settings.duplicate(true)
# 在 _state_data 初始化后,调用 add_init_data 来填充 "task_info"
add_init_data() # 调用修改后的函数
print("GameState: State initialized. Data:", _state_data) print("GameState: State initialized. Data:", _state_data)
# 发出通用初始化完成信号,传递状态副本 # 发出通用初始化完成信号,传递状态副本
@ -48,7 +49,6 @@ func initialize_for_new_game(initial_settings: Dictionary) -> void:
# 从加载的数据恢复状态。 # 从加载的数据恢复状态。
# 直接使用 loaded_data 覆盖当前状态。 # 直接使用 loaded_data 覆盖当前状态。
# 这个函数无需修改,因为它已经正确处理了包含 "system_status" 的 _state_data。
func restore_state(loaded_data: Dictionary) -> void: func restore_state(loaded_data: Dictionary) -> void:
print("GameState: Restoring state from loaded data...") print("GameState: Restoring state from loaded data...")
@ -66,7 +66,6 @@ func restore_state(loaded_data: Dictionary) -> void:
# 获取需要保存到存档的状态。 # 获取需要保存到存档的状态。
# 直接返回核心状态字典的副本。 # 直接返回核心状态字典的副本。
# 这个函数无需修改,因为它返回的是完整的 _state_data。
func get_savable_state() -> Dictionary: func get_savable_state() -> Dictionary:
print("GameState: Gathering savable state...") print("GameState: Gathering savable state...")
# 返回深拷贝,确保存档系统拿到的是快照,不会意外修改当前状态 # 返回深拷贝,确保存档系统拿到的是快照,不会意外修改当前状态
@ -138,23 +137,90 @@ func set_system_status_value(key: String, value: bool) -> void:
# 检查游戏是否暂停 # 检查游戏是否暂停
# 返回: bool - true 如果游戏已暂停, false 否则 # 返回: bool - true 如果游戏已暂停, false 否则
func is_game_paused() -> bool: func is_game_paused() -> bool:
# 直接从 _state_data 中读取,通过辅助函数获取字典并访问键值
# 使用 .get() 并提供默认值 false增加健壮性
return _get_system_status_dict().get("game_pause", false) return _get_system_status_dict().get("game_pause", false)
# 检查是否处于任务中状态 # 检查是否处于任务中状态
# 返回: bool - true 如果正在进行任务, false 否则 # 返回: bool - true 如果正在进行任务, false 否则
func is_on_task_status() -> bool: func is_on_task_status() -> bool:
# 直接从 _state_data 中读取
# 使用 .get() 并提供默认值 false增加健壮性
return _get_system_status_dict().get("is_on_task", false) return _get_system_status_dict().get("is_on_task", false)
# 暂停游戏 # 暂停游戏
func pause_game(): func pause_game():
# 调用新的辅助函数来修改 _state_data 中的 "game_pause" 值
set_system_status_value("game_pause", true) set_system_status_value("game_pause", true)
# 恢复游戏 # 恢复游戏
func resume_game(): func resume_game():
# 调用新的辅助函数来修改 _state_data 中的 "game_pause" 值
set_system_status_value("game_pause", false) set_system_status_value("game_pause", false)
# --- 辅助函数:查找字典中第一个 enabled:true 的条目的键 ---
# data_dict: 期望是一个结构为 { "key1": {"enabled": bool, ...}, "key2": {...} } 的字典
# 返回: 第一个找到的 enabled 为 true 的条目的键名 (String),如果未找到则返回空字符串
func _find_first_enabled_key(data_dict: Dictionary) -> String:
if typeof(data_dict) != TYPE_DICTIONARY:
printerr("GameState (_find_first_enabled_key): Expected a Dictionary, got ", typeof(data_dict))
return ""
# GDScript 字典从 Godot 3.1 开始保持插入顺序,
# 因此迭代将遵循 JSON 中定义的顺序或它们被添加到字典中的顺序。
for key in data_dict:
var item = data_dict[key]
if typeof(item) == TYPE_DICTIONARY and item.has("enabled") and item["enabled"] == true:
return key # 返回第一个找到的键名
# print("GameState (_find_first_enabled_key): No enabled item found in dictionary with keys: ", data_dict.keys()) # 可选的调试信息
return "" # 如果没有找到 enabled:true 的条目
# --- 初始化 task_info ---
# MODIFIED: 此函数现在会从 _state_data["task_development"] 获取初始值
func add_init_data() -> void:
var initial_platform: String = ""
var initial_gameplay: String = ""
var initial_theme: String = ""
var initial_strategy: String = ""
var initial_product_focus_points: Dictionary = {}
# 确保 _state_data["task_development"] 存在并且是字典类型
if _state_data.has("task_development") and typeof(_state_data["task_development"]) == TYPE_DICTIONARY:
var task_dev_data = _state_data["task_development"]
# 获取平台初始值
if task_dev_data.has("platforms") and typeof(task_dev_data["platforms"]) == TYPE_DICTIONARY:
initial_platform = _find_first_enabled_key(task_dev_data["platforms"])
else:
printerr("GameState (add_init_data): 'task_development.platforms' is missing or not a Dictionary.")
# 获取玩法初始值
if task_dev_data.has("gameplays") and typeof(task_dev_data["gameplays"]) == TYPE_DICTIONARY:
initial_gameplay = _find_first_enabled_key(task_dev_data["gameplays"])
else:
printerr("GameState (add_init_data): 'task_development.gameplays' is missing or not a Dictionary.")
# 获取题材初始值
if task_dev_data.has("themes") and typeof(task_dev_data["themes"]) == TYPE_DICTIONARY:
initial_theme = _find_first_enabled_key(task_dev_data["themes"])
else:
printerr("GameState (add_init_data): 'task_development.themes' is missing or not a Dictionary.")
# 获取开发策略初始值
if task_dev_data.has("strategies") and typeof(task_dev_data["strategies"]) == TYPE_DICTIONARY:
initial_strategy = _find_first_enabled_key(task_dev_data["strategies"])
else:
printerr("GameState (add_init_data): 'task_development.strategies' is missing or not a Dictionary.")
# 获取产品侧重点初始值
if task_dev_data.has("product_focus_points") and typeof(task_dev_data["product_focus_points"]) == TYPE_DICTIONARY:
initial_product_focus_points = task_dev_data["product_focus_points"]
else:
printerr("GameState (add_init_data): 'task_development.product_focus_points' is missing or not a Dictionary.")
else:
printerr("GameState (add_init_data): 'task_development' key is missing in _state_data or is not a Dictionary.")
# 初始化 _state_data["task_info"]
_state_data["task_info"] = {
"平台": initial_platform,
"玩法": initial_gameplay,
"题材": initial_theme,
"开发策略": initial_strategy,
"预算": 0, # 保持原有的默认值
"产品侧重点": initial_product_focus_points,
#"关键环节负责人": [] # 保持原有的默认值
}

View File

@ -33,7 +33,7 @@
* **新游戏:** 确保 `InitialDataManager` 数据加载完成 -> 调用 `InitialDataManager` 获取初始数据 (`settings_data`, `task_dev_data`, `npc_initial_data`) -> **调用 `_build_initial_state` 方法,根据获取的数据构建注入 `GameState` 的初始状态字典。此方法会:** * **新游戏:** 确保 `InitialDataManager` 数据加载完成 -> 调用 `InitialDataManager` 获取初始数据 (`settings_data`, `task_dev_data`, `npc_initial_data`) -> **调用 `_build_initial_state` 方法,根据获取的数据构建注入 `GameState` 的初始状态字典。此方法会:**
* **合并基础设置 (`Init_Base.json`) 到状态字典的顶层。** * **合并基础设置 (`Init_Base.json`) 到状态字典的顶层。**
* **处理任务开发相关数据 (`task_development.json`),提取其中需要动态追踪的状态(如平台的 `enabled`, `Market_share`;玩法/主题的 `enabled`, `Experience`;策略的 `enabled`;产品侧重点的完整结构),并将这些动态状态组织在一个名为 `"task_development"` 的子字典内,再将该子字典存入主状态字典。** * **处理任务开发相关数据 (`task_development.json`),提取其中需要动态追踪的状态(如平台的 `enabled`, `Market_share`;玩法/主题的 `enabled`, `Experience`;策略的 `enabled`;产品侧重点的完整结构),并将这些动态状态组织在一个名为 `"task_development"` 的子字典内,再将该子字典存入主状态字典。**
* **处理 NPC 数据 (`npc.json`),将从 `npc_initial_data` 中获取的 NPC 定义(通常是 `npc_defs["npc"]` 部分)存入主状态字典的 `"npcs"` 键下。** * **处理 NPC 数据 (`npc.json`),将从 `npc_initial_data` 中获取的 NPC 定义(通常是 `npc_defs["npc"]` 部分,即 `npc.json` 文件中 "npc" 键对应的值)存入主状态字典的 `"npcs"` 键下。**
* **载入存档:** **接收**由外部(例如存档选择 UI它会调用 `SaveLoadManager.load_game`)加载并传入的存档数据字典。 * **载入存档:** **接收**由外部(例如存档选择 UI它会调用 `SaveLoadManager.load_game`)加载并传入的存档数据字典。
* **数据注入:** * **数据注入:**
* **新游戏:** 将构建好的初始状态字典传递给 `GameState.initialize_for_new_game()` * **新游戏:** 将构建好的初始状态字典传递给 `GameState.initialize_for_new_game()`
@ -113,14 +113,14 @@
### 核心职责 (Responsibilities) ### 核心职责 (Responsibilities)
* **状态存储:** 内部维护一个核心字典 (`_state_data`),用于存储所有动态游戏状态的 Key-Value 对。 * **状态存储:** 内部维护一个核心字典 (`_state_data`),用于存储所有动态游戏状态的 Key-Value 对。
* **通用状态访问:** 提供通用的 `set_value`, `get_value` 接口。 * **通用状态访问:** 提供通用的 `set_value`, `get_value` 接口。
* **状态初始化:** 提供 `initialize_for_new_game` 方法,使用初始设置数据填充状态字典,完成后发出 `state_initialized` 信号。 * **状态初始化:** 提供 `initialize_for_new_game` 方法,使用初始设置数据填充状态字典,**并在此过程中调用内部方法(如 `add_init_data`)根据已填充的 `_state_data` 的一部分(例如 `"task_development"`)来进一步初始化其他状态数据(例如 `"task_info"`** 完成后发出 `state_initialized` 信号。
* **状态恢复与通知:** 提供 `restore_state` 方法用于接收并覆盖整个状态字典,完成后发出 `state_restored` 信号。 * **状态恢复与通知:** 提供 `restore_state` 方法用于接收并覆盖整个状态字典,完成后发出 `state_restored` 信号。
* **存档数据提供:** 提供 `get_savable_state` 方法,返回内部状态字典的**深拷贝**副本,供 `SaveLoadManager` 使用。 * **存档数据提供:** 提供 `get_savable_state` 方法,返回内部状态字典的**深拷贝**副本,供 `SaveLoadManager` 使用。
* **系统状态管理:** 通过内部的 `"system_status"` 字典(存储在 `_state_data["system_status"]`)管理如游戏暂停 (`game_pause`)、是否在任务中 (`is_on_task`) 等状态。提供方法来修改和查询这些特定状态。 * **系统状态管理:** 通过内部的 `"system_status"` 字典(存储在 `_state_data["system_status"]`)管理如游戏暂停 (`game_pause`)、是否在任务中 (`is_on_task`) 等状态。提供方法来修改和查询这些特定状态。
### **对外接口 (Public API / Generic State Access)** ### **对外接口 (Public API / Generic State Access)**
* `initialize_for_new_game(initial_settings: Dictionary) -> void`: **(供 InitializationManager 调用)** 使用 `initial_settings` 字典(进行深拷贝)来初始化内部的 `_state_data`。完成后发出 `state_initialized` 信号。 * `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` 信号。 * `restore_state(loaded_data: Dictionary) -> void`: **(供 InitializationManager 调用)** 用 `loaded_data` 字典(进行深拷贝)**完全替换**内部的 `_state_data` 字典,然后发出 `state_restored` 信号。
* `get_savable_state() -> Dictionary`: **(供 SaveLoadManager 调用)** 返回内部 `_state_data` 字典的**深拷贝** (`duplicate(true)`),用于存档,确保返回的是当前状态的一个独立快照。 * `get_savable_state() -> Dictionary`: **(供 SaveLoadManager 调用)** 返回内部 `_state_data` 字典的**深拷贝** (`duplicate(true)`),用于存档,确保返回的是当前状态的一个独立快照。
* `get_value(key: String, default = null) -> Variant`: 根据 `key` 从内部字典检索 `value`。如果 `key` 不存在,返回 `default` 值。 * `get_value(key: String, default = null) -> Variant`: 根据 `key` 从内部字典检索 `value`。如果 `key` 不存在,返回 `default` 值。
@ -177,7 +177,4 @@
* **输入 (Load):** `user://` 目录下的 JSON 文件。 * **输入 (Load):** `user://` 目录下的 JSON 文件。
* **输出 (Load):** 解析后的 `game_state` 字典 (返回给调用者) 或 `null` * **输出 (Load):** 解析后的 `game_state` 字典 (返回给调用者) 或 `null`
### 配置 (Configuration)
* 必须配置为 Godot 的 Autoload (单例)。
--- ---

View File

@ -0,0 +1,235 @@
# UI 框架开发指南
**1. 简介**
本文档旨在为使用本 UI 框架的开发人员提供指导,帮助大家快速、规范地创建和管理游戏中的 UI 页面及工作流。本框架的核心是通过一个中央控制器 (`MainController`) 来管理各个 UI 页面 (`UIPage` 的派生类) 的显示、隐藏、导航以及数据共享。
核心组件:
* **`main_controller.gd` (`MainController`)**: 全局 UI 工作流管理器。负责页面切换、维护工作流共享数据、处理工作流的开始与结束。
* **`ui_base.gd` (`UIPage`)**: 所有具体 UI 页面的基类。提供了基础的导航逻辑、与 `MainController` 的通信接口以及页面生命周期管理。
* **具体页面脚本 **: 继承自 `UIPage`,实现特定页面的内容展示和交互逻辑。
**2. 核心概念**
* **MainController (`main_controller.gd`)**
* **职责**:
* 管理当前激活的 UI 页面。
* 根据 `UIPage` 发出的请求进行页面切换,并处理页面间的过渡逻辑(如隐藏旧页面、显示新页面及其父页面)。
* 持有一个 `shared_workflow_data` 字典,用于在工作流中的不同页面间共享数据。此数据可在启动时从全局状态管理(例如 `GameState`)初始化。
* 定义工作流的起点 (`initial_page_path`)。
* 发出工作流结束信号 (`workflow_confirmed`, `workflow_cancelled`)。
* **导出变量**:
* `initial_page_path: NodePath`: 指向工作流初始页面的节点路径。
* `gamestate_data_key: String`: 用于从全局状态管理(如 `GameState`)中读取/初始化 `shared_workflow_data` 的键名。
* **启动与显示工作流**:
* `_ready()`: 控制器准备就绪时,会尝试从 `GameState` 使用 `gamestate_data_key` 加载共享数据 (如果 `gamestate_data_key` 已配置)。然后,它会遍历其直接子节点:如果是 `UIPage` 类型,则隐藏它们并连接必要的信号。接着,对于每个作为 `UIPage` 的直接子节点,它还会遍历该 `UIPage` 的直接子节点(即 `MainController` 的孙子节点):如果这些孙子节点也是 `UIPage` 类型,则同样会隐藏它们并连接信号。**注意**`show_up()` 方法需要被外部调用以启动工作流,它不会在 `_ready()` 中自动调用。
* `show_up()`: 此方法用于显示和启动 UI 工作流。它首先会调用 `GameState.pause_game()` 来暂停游戏,然后重新从 `GameState` 使用 `gamestate_data_key` 获取最新的共享数据,并根据 `initial_page_path` 找到初始页面,然后调用 `start_new_workflow` 并将获取的数据传递给新工作流。如果初始页面路径无效或未找到对应节点,则控制器自身将保持不可见。
* `start_new_workflow(start_page: UIPage, initial_data: Dictionary = {})` 方法启动一个新的 UI 工作流。它会复制 `initial_data` (通常是从 `GameState``show_up()` 传递的) 到 `shared_workflow_data`,隐藏所有非起始的顶级页面及其子 `UIPage`(具体来说,它遍历 `MainController` 的直接子节点,如果子节点是 `UIPage` 且不是 `start_page`,则隐藏它;然后遍历该子节点的子节点,如果是 `UIPage`,也隐藏它),然后调用 `_switch_to_page` 切换到指定的 `start_page` 并使控制器可见。
* **共享数据**:
* `get_shared_data(key: String, default = null)`: 从共享数据字典中获取值。
* `set_shared_data(key: String, value)`: 设置共享数据字典中的值。
* **页面切换逻辑 (`_switch_to_page`)**:
* **处理前一页面**:
* 如果存在前一个活动页面 (`previous_page`),控制器会决定是否隐藏它。默认行为是隐藏 (`should_hide_previous_page = true`)。控制器会按以下顺序检查 `previous_page``hide_on_next` 行为:
1. 尝试通过元数据 `previous_page.get_meta("hide_on_next")` 获取布尔值。
2. 如果元数据不存在,并且 `previous_page` 具有名为 "about_to_show" 的信号 (这是一个特定条件,可能用于特定页面类型),则会尝试直接访问 `previous_page.hide_on_next` 脚本变量(`UIPage` 基类将 `hide_on_next` 定义为一个导出变量,默认为 `true`)。如果此时 `hide_on_next` 属性未找到,会打印错误并默认隐藏。
3. 如果上述条件都不满足,则使用默认行为(隐藏)。
* 如果最终决定需要隐藏前一页面,则调用 `previous_page.hide_page()`
* **父页面清理**: 如果前一页面 (`previous_page`) 是一个子页面(即其 `parent_page_node_path` 已配置),并且导航的目标页面 (`target_page`) 满足以下两个条件1) `target_page` 不是 `previous_page` 的逻辑父页面 (`old_page_parent_node`),并且 2) `target_page` 的场景树父节点不是 `old_page_parent_node`(即 `target_page` 不是 `old_page_parent_node` 的直接子节点,意味着它不是 `previous_page` 在该逻辑父页面下的兄弟页面),则前一子页面的逻辑父页面 (`old_page_parent_node`) 将被隐藏,以清理不再相关的上下文。
* **激活新页面**: 将 `current_active_page` 更新为目标页面。调用 `current_active_page.show_page()`(此方法在 `UIPage` 中仅负责设置 `visible = true`),然后关键地,`MainController` 直接调用 `current_active_page._on_page_activated()` 来确保页面的激活逻辑被执行。
* **确保新子页面的父页面可见并已激活**: 如果新的当前活动页面 (`current_active_page`) 是一个子页面(配置了 `parent_page_node_path`),控制器会确保其在 `parent_page_node_path` 中指定的父 `UIPage` 节点 (`new_page_parent_node`) 也是可见且(如果需要)已激活的。
* 如果该父页面 (`new_page_parent_node`) 之前不可见,则会调用其 `show_page()` 方法(使其可见),然后调用其 `_on_page_activated()` 方法(激活它)。
* 如果父页面已经可见,则仍会调用其 `show_page()` 方法(尽管它可能仅确认可见性)。
* **信号处理器**:
* `_on_page_navigate_requested(target_page_node: Control)`: 当 `UIPage` 发出 `navigate_requested` 信号时调用。如果 `target_page_node``UIPage` 的实例,则负责执行到目标页面的切换(通过 `_switch_to_page`)。否则会打印错误。
* `_on_page_workflow_end_requested(is_confirm_and_trigger_action: bool)`: 当 `UIPage` 发出 `workflow_end_requested` 信号时调用。
* 如果 `is_confirm_and_trigger_action``true`,则发出 `workflow_confirmed` 信号并携带最终的 `shared_workflow_data`
* 否则,发出 `workflow_cancelled` 信号。
* 之后,如果存在当前活动页面 (`current_active_page`),则调用其 `hide_page()`。如果当前活动页面配置了 `parent_page_node_path`,其对应的父页面也会被调用 `hide_page()`
* `current_active_page` 被设为 `null`
* 当前的 `shared_workflow_data` 会使用 `GameState.set_value(gamestate_data_key, shared_workflow_data)` 保存到全局状态。注意:`MainController` 内部的 `shared_workflow_data` 不会被清空。随后调用 `GameState.resume_game()` 来恢复游戏。
* 最后,`MainController` 自身设置为不可见。
* **UIPage (`ui_base.gd`)**
* **页面基类**: 所有具体的 UI 页面场景的根节点所附加的脚本都应继承自此类。
* **初始化 (`_ready`)**:
* `UIPage` 在其 `_ready` 方法中会尝试获取 `MainController` 的引用。它会检查其 `owner` 节点是否为 `MainController`。这是通过判断 `owner` 是否存在并且是否拥有一个名为 `get_shared_data` 的方法来实现的。如果条件满足,则将 `owner` 赋值给 `main_controller` 变量。
* **输入处理 (`_input`)**:
* 如果页面可见,它会监听鼠标右键点击事件 (`MOUSE_BUTTON_RIGHT`)。当右键被按下时,会尝试执行 `go_back()` 操作,并调用 `get_viewport().set_input_as_handled()` 来消费该输入事件。
* **导航功能**:
* `go_next()`: 尝试导航到 `next_ui_node_path` 配置的页面。若路径有效且目标节点是 `Control` 类型,则发出 `navigate_requested` 信号。若路径为空或目标节点无效(或不是 `Control`),则打印错误并发出 `workflow_end_requested` 信号并传递 `true` (表示确认结束)。
* `go_back()`: 优先尝试导航到 `back_ui_node_path` 配置的页面。如果 `back_ui_node_path` 为空,则尝试导航到 `parent_page_node_path` 配置的父页面。若选中的路径有效且目标节点是 `Control` 类型,则发出 `navigate_requested` 信号。若两个路径均为空或目标节点无效(或不是 `Control`),则打印错误并发出 `workflow_end_requested` 信号并传递 `false` (表示取消结束)。
* `go_to_child_page(child_page_node: Control)`: 导航到指定的子页面。此方法会进行检查:
* 确保 `child_page_node` 不为 null。
* 确保 `child_page_node` 不是当前页面的祖先节点 (使用 `_is_ancestor_of` 辅助函数判断,以防止循环导航到父级)。
* 确保 `child_page_node` 是一个 `Control` 节点。
如果检查通过,则发出 `navigate_requested` 信号。否则打印错误。
* **Inspector 配置项**:
* `next_ui_node_path: NodePath`: “下一个”页面节点的路径。
* `back_ui_node_path: NodePath`: “上一个”页面节点的路径。
* `parent_page_node_path: NodePath`: (主要用于子页面)父页面节点的路径,当 `back_ui_node_path` 为空时,子页面会尝试通过 `go_back()` 返回此父页面。
* `hide_on_next: bool`: (默认值: `true`) 如果此属性为 `true`,当通过 `go_next()` 方法成功导航到 `next_ui_node_path` 所配置的页面时,`MainController` 在处理前一页面(即当前页面)时,会倾向于隐藏当前页面(具体逻辑见 `MainController._switch_to_page` 中对 `hide_on_next` 的处理)。如果设置为 `false`,当前页面在导航后有更大可能性保持可见。
* **生命周期方法 (可覆写)**:
* `show_page()`: 公开方法,用于显示页面。它**仅**负责将节点的 `visible` 属性设为 `true` (如果之前是 `false`)。**此方法本身不调用 `_on_page_activated()`**。
* `hide_page()`: 公开方法,用于隐藏页面。**仅当页面之前是可见状态时**,此方法会先调用 `_on_page_deactivated()`,然后再将节点的 `visible` 属性设为 `false`
* `_on_page_activated()`: 当页面被 `MainController` 激活时调用(通常在 `_switch_to_page` 期间。用于加载数据、更新UI、启动动画等。基类实现会打印一条日志`UIPage activated: [PageName]`
* `_on_page_deactivated()`: 当页面通过 `hide_page()` **即将变为不可见** (即其 `visible` 属性从 `true` 变为 `false`) 之前调用。用于保存临时状态、清理资源或停止动画等。如果页面已不可见,再次调用 `hide_page()` 不会重复触发此方法。基类实现会打印一条日志,如 `UIPage deactivated: [PageName]`
* **与 MainController 通信**:
* 信号 `navigate_requested(target_page_node: Control)`: 请求 `MainController` 切换到目标页面。
* 信号 `workflow_end_requested(is_confirm_and_trigger_action: bool)`: 请求 `MainController` 结束当前工作流。
* 方法 `get_main_data(key: String, default = null)``set_main_data(key: String, value)`: 分别用于从 `main_controller` 获取和设置共享数据。如果 `main_controller` 未被正确引用,会发出警告并返回 `default` 值 (对于 `get_main_data`) 或不设置数据 (对于 `set_main_data`)。
**3. 创建新的 UI 页面**
以下步骤详细说明了如何创建一个新的 UI 页面并将其集成到框架中。
**3.1. 创建场景文件 (.tscn)**
1. 在 Godot 编辑器的文件系统中,右键点击目标文件夹,选择“新建” -> “场景”。
2. **根节点类型**: 选择 `Control` 作为根节点类型。你也可以根据需要选择 `PanelContainer``Control` 的派生类。
3. **命名规范**: 场景文件建议使用清晰的命名,例如 `staff_management_screen.tscn``project_details_dialog.tscn`
4. 保存场景。
**3.2. 场景节点结构与命名规范**
* **页面根节点 (即场景的根 Control 节点)**:
* 此节点将附加一个继承自 `UIPage` 的特定页面脚本 (见 3.3)。
* 在 `Main.tscn` 场景中(或包含 `MainController` 的场景),此页面场景的实例将作为 `MainController` 节点的直接子节点(如果是顶级页面)或另一个 `UIPage` 实例的子节点(如果是子页面)。
* **内部 UI 控件**:
* 在页面根节点下,根据需求添加各种 Godot UI 控件,如 `Label`, `Button`, `LineEdit`, `TextureRect`, `ProgressBar`, `ItemList`, `HBoxContainer`, `VBoxContainer`, `GridContainer` 等。
* **命名规范**:
* 为重要的、需要在脚本中引用的控件赋予清晰、一致的名称。
* 建议使用驼峰式命名或下划线命名,并能体现控件类型和功能,例如:
* `PlayerNameLabel`
* `ConfirmButton``confirm_button`
* `ProjectListVBoxContainer`
* `CancelChangesButton`
* **布局**: 强烈建议使用 Godot 的容器节点 (`HBoxContainer`, `VBoxContainer`, `GridContainer`, `MarginContainer`, `PanelContainer`, `ScrollContainer` 等) 来组织和布局内部 UI 控件,以实现响应式和易于维护的界面。
**3.3. 创建页面脚本 (.gd)**
1. 在 Godot 编辑器的文件系统中,右键点击目标文件夹,选择“新建” -> “脚本”。
2. **命名规范**: 脚本名称应与其管理的页面场景相对应,例如 `staff_management_page.gd`
3. **继承 `UIPage`**:
```gdscript
# staff_management_page.gd
class_name StaffManagementPage # 使用 class_name 方便类型提示
extends UIPage
```
4. **`@onready` 变量获取控件引用**:
在脚本顶部,使用 `@onready` 关键字获取场景中需要交互的 UI 控件的引用。
```gdscript
@onready var employee_list: ItemList = $ScrollContainer/VBoxContainer/EmployeeList
@onready var hire_button: Button = $ControlsHBox/HireButton
@onready var details_panel: PanelContainer = $DetailsPanel/ContentPanel
@onready var employee_name_label: Label = $DetailsPanel/ContentPanel/NameLabel
```
5. **实现 `_ready()` 方法**:
* **必须调用 `super._ready()`**: 确保基类 `UIPage``_ready()` 逻辑(如查找 `main_controller`)被执行。
* **连接页面内部控件的信号**: 将按钮的 `pressed` 信号、`LineEdit``text_changed` 信号等连接到此脚本中的处理函数。
```gdscript
func _ready():
super._ready() # !! 非常重要 !!确保 main_controller 被初始化
if hire_button: # 良好的习惯是检查节点是否存在
hire_button.pressed.connect(_on_hire_button_pressed)
if employee_list:
employee_list.item_selected.connect(_on_employee_list_item_selected)
# ... 其他信号连接
```
6. **覆写 `_on_page_activated()` 方法**:
此方法在页面被 `MainController` 激活时调用。
* **建议调用 `super._on_page_activated()`**: 基类实现会打印日志 (`UIPage activated: [PageName]`), 方便调试。
* **加载数据**: 使用 `get_main_data("data_key", default_value)``MainController` 的共享数据中获取当前工作流所需的数据。
* **更新 UI**: 根据获取的数据,更新页面上的 `Label.text`, `LineEdit.text`, `ProgressBar.value`,填充列表,设置按钮的 `disabled` 状态等。
```gdscript
func _on_page_activated():
super._on_page_activated() # 基类打印日志
var current_staff = get_main_data("company_staff", [])
_populate_employee_list(current_staff)
var can_hire = get_main_data("can_afford_new_hire", true)
if hire_button:
hire_button.disabled = not can_hire
```
7. **覆写 `_on_page_deactivated()` 方法 (可选)**:
此方法在页面每次通过 `hide_page()` 即将变为非活动状态(隐藏)之前调用。
* **建议调用 `super._on_page_deactivated()`**: 基类实现会打印日志 (`UIPage deactivated: [PageName]`)。
* 用于执行页面隐藏前的清理工作,例如保存未提交的临时输入到 `shared_workflow_data`,或者重置页面状态以便下次激活时重新初始化。
```gdscript
func _on_page_deactivated():
super._on_page_deactivated() # 基类打印日志
# 如果有临时输入未保存,可以在这里处理
# if name_input_field.text != get_main_data("current_editing_name"):
# set_main_data("temp_unsaved_name", name_input_field.text)
```
8. **实现自定义方法**:
编写处理页面特定交互逻辑的函数(例如上面 `_ready` 中连接的 `_on_hire_button_pressed`)。
* 这些方法可能会读取页面输入、执行计算、调用 `set_main_data(key, value)` 来更新 `MainController` 中的共享数据。
```gdscript
func _on_hire_button_pressed():
print("Hire button pressed on " + name)
# 可能需要更新一些共享数据,表明要进入雇佣流程
set_main_data("entering_hire_mode", true)
# 然后导航到下一个页面(例如一个详细的雇佣信息填写页面)
go_next() # go_next() 是从 UIPage 继承的方法
func _populate_employee_list(staff_data: Array):
if employee_list:
employee_list.clear()
for employee_info in staff_data:
employee_list.add_item(employee_info.get("name", "Unknown"))
# 可以存储更复杂的数据
# employee_list.set_item_metadata(employee_list.get_item_count() - 1, employee_info)
```
9. **调用导航方法**:
在适当的时候(例如,用户点击确认按钮后),调用从 `UIPage` 继承的导航方法:
* `go_next()`: 前往流程中的下一个页面或确认结束工作流。
* `go_back()`: 返回上一个页面、父页面或取消结束工作流。
* `go_to_child_page(child_node)`: 打开当前页面的一个子页面。
10. **附加脚本**: 将创建好的页面脚本附加到对应 UI 场景的根节点上。
**3.4. 配置页面导航 (Inspector)**
1. 打开包含 `MainController` 和 UI 页面实例的场景(例如 `Main.tscn`)。
2. 确保你的新 UI 页面场景已经被实例化并添加为 `MainController` 节点的子节点(或相应父页面的子节点)。
3. 选中新页面实例。
4. 在 Godot 编辑器的 **Inspector** 面板中,找到并配置从 `UIPage` 继承来的导出变量:
* **Next Ui Node Path**: 拖拽流程中的下一个页面节点到此属性。如果这是流程的最后一步(确认并结束),则留空 (使其 `NodePath` 为空)。
* **Back Ui Node Path**: 拖拽流程中的上一个页面节点到此属性。如果这是流程的起点或返回即取消工作流,则留空。
* **Parent Page Node Path**: **仅当此页面是作为另一个页面的子页面时配置**。拖拽其直接的父 `UIPage` 节点到此属性。这用于子页面在没有特定 `Back Ui Node Path` 时,通过 `go_back()` 返回其父级。
* **Hide On Next**: (布尔值,默认为 `true`) 勾选此项(或设置为 `true`)表示当通过 `go_next()` 导航到下一个页面时,`MainController` 更倾向于隐藏此页面。取消勾选(或设置为 `false`)则此页面在导航后有更大可能性保持可见(具体行为取决于 `MainController``_switch_to_page` 的逻辑)。
**4. 将新页面集成到工作流**
* **4.1. 作为顶级页面 (例如 A, B, C)**:
1. 将新创建的 UI 页面场景实例化,并使其成为 `MainController` 节点的直接子节点。
2. `MainController``_ready()` 函数会自动查找并处理其场景树下两层内的 UI 页面(即 `MainController` 的直接子 `UIPage`,以及这些 `UIPage` 的直接子 `UIPage`)。它会隐藏这些页面并连接它们的信号。确保你的新页面是 `UIPage` 类型(即附加了继承自 `UIPage` 的脚本)。
3. 根据工作流逻辑,配置 `MainController``Initial Page Path` 指向此新页面(如果它是起始页),或者修改其他页面的 `Next Ui Node Path``Back Ui Node Path` 以包含此新页面。
* **4.2. 作为子页面 (例如 A_1 是 A 的子页面)**:
1. 将新创建的 UI 页面场景实例化,并使其成为其父 `UIPage` 节点的子节点。例如,`PageA1.tscn` 的实例是 `PageA` 节点的子节点。
2. 在父页面 (例如 `PageA.gd`) 的脚本中,你需要:
* 添加一个按钮或交互元素来触发打开此子页面。
* 在该按钮的响应函数中,获取子页面节点的引用,并调用 `go_to_child_page(get_node("Path/To/YourChildPageNode"))`
3. 配置新子页面的 `Parent Page Node Path` (在 Inspector 中) 指向其父 `UIPage` 节点。
4. `MainController``_ready()` 中的信号连接和初始隐藏逻辑会处理 `MainController` 的直接子 `UIPage` 以及这些 `UIPage` 的直接子 `UIPage`。如果你的子页面嵌套更深(例如,一个 `UIPage` 的孙子节点也是 `UIPage`,即相对于 `MainController` 三层或更深),则需要调整 `main_controller.gd` 中的 `_ready()` 循环以遍历更深层级,或者手动为更深层级的 `UIPage` 调用 `hide_page()` 并使用 `_connect_page_signals()` 连接其信号到 `MainController` 的相应处理方法。
**5. 注意事项与最佳实践**
* **单一职责**: 保持 `UIPage` 基类脚本的通用性。特定于某个页面的复杂逻辑应该放在该页面的派生脚本中。
* **命名一致性**: 对场景、节点和脚本使用清晰、一致的命名约定。
* **响应式布局**: 优先使用 Godot 的容器节点进行布局,以适应不同的屏幕尺寸和分辨率。
* **数据流**: 所有跨页面的工作流数据都应通过 `MainController``shared_workflow_data` 进行管理。避免页面脚本之间直接相互引用以获取或修改状态。
* **`_ready()` vs `_on_page_activated()`**:
* `_ready()`: 用于一次性的初始化设置,如信号连接、节点引用获取、`main_controller` 的查找。它在节点进入场景树时调用一次。
* `_on_page_activated()`: 由 `MainController` 在页面成为当前活动页面时调用。用于执行每次页面激活时需要执行的逻辑,如从 `MainController` 拉取最新数据并刷新 UI 显示。
* **错误处理与健壮性**: 在获取节点引用 (`get_node_or_null`) 或处理路径时,考虑添加空检查,以避免运行时错误。`UIPage``MainController` 中的导航方法已经包含了一些基本的错误打印。
* **测试**: 彻底测试所有导航路径,包括前进、后退、进入/退出子页面以及各种工作流结束条件(确认或取消)。
* **处理多页面同时可见的情况**:
* 当使用 `hide_on_next = false` 使得多个页面同时显示时需要特别注意UI布局确保它们不会相互不当重叠或干扰用户操作。
* 考虑输入焦点管理。Godot的事件系统通常会将输入传递给最上层渲染顺序且可见的控件。如果多个可见页面都有可交互元素请仔细规划和测试以确保行为符合预期。
* 请记住,`MainController` 中的 `current_active_page` 始终指向最新导航到的(即“最活动的”)页面。其他因 `hide_on_next = false` (或类似逻辑)而保持可见的页面虽然显示在屏幕上,但它们不是 `MainController` 当前主要管理的页面。**重要:当从一个设置了 `hide_on_next = false` 的页面导航离开时,如果 `MainController` 决定不隐藏该页面,那么该页面的 `hide_page()` 方法将不会被调用,因此其 `_on_page_deactivated()` 方法也不会被调用。**

View File

@ -4,7 +4,7 @@
[ext_resource type="Script" uid="uid://b5m1sxtrk04ut" path="res://UI/main/ui_main.gd" id="1_memhs"] [ext_resource type="Script" uid="uid://b5m1sxtrk04ut" path="res://UI/main/ui_main.gd" id="1_memhs"]
[ext_resource type="PackedScene" uid="uid://bsmybl7xmr85u" path="res://UI/main/ui_bottom_info.tscn" id="2_q6yvl"] [ext_resource type="PackedScene" uid="uid://bsmybl7xmr85u" path="res://UI/main/ui_bottom_info.tscn" id="2_q6yvl"]
[ext_resource type="PackedScene" uid="uid://dlwl5hk5oadpj" path="res://UI/popup/play_menu/play_menu.tscn" id="9_6fpfi"] [ext_resource type="PackedScene" uid="uid://dlwl5hk5oadpj" path="res://UI/popup/play_menu/play_menu.tscn" id="9_6fpfi"]
[ext_resource type="PackedScene" uid="uid://b4ll7wwg1s0qc" path="res://UI/popup/task_development/task_development.tscn" id="11_ae6v5"] [ext_resource type="PackedScene" uid="uid://b5npsmec2fo2w" path="res://UI/popup/task_development/task_development_start.tscn" id="11_ae6v5"]
[ext_resource type="PackedScene" uid="uid://dtywh0m5odikx" path="res://UI/popup/dialogue.tscn" id="12_t4kna"] [ext_resource type="PackedScene" uid="uid://dtywh0m5odikx" path="res://UI/popup/dialogue.tscn" id="12_t4kna"]
[ext_resource type="PackedScene" uid="uid://b70jleefiaiwi" path="res://UI/popup/notice.tscn" id="15_xupgg"] [ext_resource type="PackedScene" uid="uid://b70jleefiaiwi" path="res://UI/popup/notice.tscn" id="15_xupgg"]
@ -29,7 +29,6 @@ visible = false
layout_mode = 1 layout_mode = 1
[node name="task_development" parent="Pop_Up" instance=ExtResource("11_ae6v5")] [node name="task_development" parent="Pop_Up" instance=ExtResource("11_ae6v5")]
visible = false
layout_mode = 1 layout_mode = 1
[node name="notice" parent="Pop_Up" instance=ExtResource("15_xupgg")] [node name="notice" parent="Pop_Up" instance=ExtResource("15_xupgg")]

View File

@ -1,28 +1,27 @@
extends Control extends UIPage
# 1. 预加载 TypewriterLogic 类 (请替换为你的实际路径!) # 预加载 TypewriterLogic 类 (请替换为你的实际路径!)
const TypewriterLogic = preload("res://UI/class/TypewriterLogic.gd") const _TypewriterLogic = preload("res://UI/class/TypewriterLogic.gd")
# 导出变量保持不变 @export var dialogues: Array
@export var typing_speed: float = 20.0 # 每秒显示的字符数
@export var label_node_path: NodePath = "Part_2/Part_3/Label" # Label节点的路径 var typing_speed: float = 20.0 # 每秒显示的字符数
var label_node_path: NodePath = "Part_2/Part_3/Label" # Label节点的路径
# --- 基础变量 --- # --- 基础变量 ---
var node_name: String var node_name: String
var next_ui_node = null # var next_ui_node = null # next_ui_node_path 从 UIPage 继承,这里不需要重复定义
var dialogue_queue = [] var dialogue_queue = []
var current_dialogue_index = 0 var current_dialogue_index = 0
var is_active: bool = false
# --- 节点和模块引用 --- # --- 节点和模块引用 ---
var label: Label = null # 缓存Label节点引用 var label: Label = null # 缓存Label节点引用
var typewriter: TypewriterLogic = null # 用于存储 TypewriterLogic 实例 var typewriter: TypewriterLogic = null # 用于存储 TypewriterLogic 实例
func _ready() -> void: func _ready() -> void:
super._ready() # 确保调用父类的 _ready 方法
node_name = str(self.name) node_name = str(self.name)
visible = false print(node_name + ": _ready executed.")
set_process_input(false)
print(node_name + ": _ready executed, visibility set to false.")
# 尝试获取 Label 节点引用 # 尝试获取 Label 节点引用
label = get_node_or_null(label_node_path) label = get_node_or_null(label_node_path)
@ -31,137 +30,113 @@ func _ready() -> void:
else: else:
# 2. 创建 TypewriterLogic 实例 # 2. 创建 TypewriterLogic 实例
print(node_name + ": Label found. Creating TypewriterLogic instance.") print(node_name + ": Label found. Creating TypewriterLogic instance.")
typewriter = TypewriterLogic.new(label, typing_speed) typewriter = _TypewriterLogic.new(label, typing_speed)
# 检查实例是否成功创建 (TypewriterLogic 内部会检查 Label 有效性) # 检查实例是否成功创建 (TypewriterLogic 内部会检查 Label 有效性)
if typewriter != null: if typewriter != null:
# 3. 连接 TypewriterLogic 的 finished 信号 (如果需要响应) # 3. 连接 TypewriterLogic 的 finished 信号 (如果需要响应)
typewriter.finished.connect(_on_dialogue_typing_finished) if not typewriter.is_connected("finished", Callable(self, "_on_dialogue_typing_finished")):
typewriter.finished.connect(_on_dialogue_typing_finished)
print(node_name + ": TypewriterLogic instance created and signal connected.") print(node_name + ": TypewriterLogic instance created and signal connected.")
else: else:
printerr(node_name + ": Failed to create TypewriterLogic instance.") printerr(node_name + ": Failed to create TypewriterLogic instance.")
func _on_page_activated():
super._on_page_activated() # 调用父类的方法
print(node_name + ": Page activated.")
_start(dialogues)
# 启动对话框显示 (逻辑基本不变) func _on_page_deactivated():
func _start(dialogues, next_node = null) -> void: super._on_page_deactivated() # 调用父类的方法
print(node_name + ": _start function called. Making visible.") print(node_name + ": Page deactivated.")
if dialogues is String: # 确保在页面停用时也停止输入处理和可能的游戏暂停状态
dialogue_queue = [dialogues] if typewriter != null:
typewriter.stop_typing()
set_process_input(false)
# 启动对话框显示
func _start(p_dialogues) -> void: # Renamed parameter to avoid conflict
print(node_name + ": _start function called.")
if p_dialogues is String:
dialogue_queue = [p_dialogues]
elif p_dialogues is Array:
dialogue_queue = p_dialogues.duplicate() # 使用 duplicate 以免修改原始导出数组
else: else:
dialogue_queue = dialogues printerr(node_name + ": Invalid dialogues data type.")
dialogue_queue = [] # 置空以避免后续错误
current_dialogue_index = 0 current_dialogue_index = 0
next_ui_node = next_node
# 显示第一段对话 (现在会调用 typewriter)
_show_current_dialogue() _show_current_dialogue()
self.visible = true set_process_input(true) # 在页面激活时启用输入处理
is_active = true
if GameState: GameState.pause_game()
set_process_input(true)
# 4. 使用 TypewriterLogic 显示当前对话 # 使用 TypewriterLogic 显示当前对话
func _show_current_dialogue() -> void: func _show_current_dialogue() -> void:
if current_dialogue_index < dialogue_queue.size(): if current_dialogue_index < dialogue_queue.size():
# 检查 typewriter 实例是否有效
if typewriter == null: if typewriter == null:
printerr(node_name + ": TypewriterLogic instance is null. Cannot display text with effect.") printerr(node_name + ": TypewriterLogic instance is null. Cannot display text with effect.")
# 提供后备:如果 typewriter 无效,直接设置文本
if label != null: if label != null:
label.text = dialogue_queue[current_dialogue_index] label.text = dialogue_queue[current_dialogue_index]
label.visible_characters = label.text.length() # 确保完全可见 label.visible_characters = label.text.length()
return return
var full_text = dialogue_queue[current_dialogue_index] var full_text = dialogue_queue[current_dialogue_index]
print(node_name + ": Requesting typewriter to start typing.") print(node_name + ": Requesting typewriter to start typing: '", full_text, "'")
# 调用 TypewriterLogic 的方法来开始打字
typewriter.start_typing(full_text) typewriter.start_typing(full_text)
else: else:
# 对话队列结束 # 对话队列结束
print(node_name + ": Dialogue queue finished.")
_close_dialogue() _close_dialogue()
# 5. 修改:使用 TypewriterLogic 处理输入事件 # 关闭对话框
func _input(event):
if not is_active: return
# 检测鼠标左键点击事件
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed():
# 检查 typewriter 是否有效并且正在打字
if typewriter != null and typewriter.is_typing():
print(node_name + ": Input detected while typing. Skipping.")
# 调用 TypewriterLogic 的方法来跳过
typewriter.skip_typing()
get_viewport().set_input_as_handled()
# 如果没有在打字,则进入下一段对话或关闭
else:
print(node_name + ": Input detected. Advancing dialogue.")
current_dialogue_index += 1
if current_dialogue_index < dialogue_queue.size():
_show_current_dialogue() # 显示下一段对话
else:
_close_dialogue() # 所有对话结束,关闭
get_viewport().set_input_as_handled()
# 检测鼠标右键点击事件 - 直接关闭对话框
elif event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed():
print(node_name + ": Right-click detected. Closing dialogue.")
_close_dialogue() # 关闭会调用 typewriter.stop_typing()
get_viewport().set_input_as_handled()
# 6. 修改:关闭对话框时,调用 TypewriterLogic 的停止方法
func _close_dialogue(): func _close_dialogue():
if not is_active: return
print(node_name + ": Closing dialogue. Stopping typewriter if active.") print(node_name + ": Closing dialogue. Stopping typewriter if active.")
# --- 修改:调用 TypewriterLogic 的停止方法 ---
if typewriter != null: if typewriter != null:
typewriter.stop_typing() typewriter.stop_typing()
# --- 修改结束 ---
self.visible = false set_process_input(false) # 在关闭时禁用输入处理
print(node_name + ": Dialogue UI hidden.") print(node_name + ": Dialogue UI processing stopped.")
# 恢复游戏并停止输入处理
if GameState:
print(node_name + ": Resuming game.")
GameState.resume_game()
set_process_input(false)
is_active = false # 确保在处理后续逻辑前标记为非活动
var parent = get_parent() # 获取父节点
# 检查是否有下一个UI需要显示 (逻辑保持不变)
if next_ui_node != null and is_instance_valid(next_ui_node):
print(node_name + ": Showing next UI: ", next_ui_node.name)
next_ui_node.visible = true
if next_ui_node.has_method("show_up"):
next_ui_node.show_up()
next_ui_node = null # 重置引用
else:
# 没有下一个UI节点 (逻辑保持不变)
if parent != null and parent.has_method("close_all_popups"):
print(node_name + ": No next UI node, calling parent's close_all_popups.")
parent.close_all_popups()
# ... (其他父节点检查逻辑) ...
next_ui_node = null
# 7. 添加:处理 TypewriterLogic 发出的 finished 信号的回调函数 # 处理 TypewriterLogic 发出的 finished 信号的回调函数
func _on_dialogue_typing_finished(): func _on_dialogue_typing_finished():
# 这个函数会在 TypewriterLogic 自然完成一次打字动画时被调用
print(node_name + ": Received 'finished' signal from TypewriterLogic.") print(node_name + ": Received 'finished' signal from TypewriterLogic.")
# 你可以在这里添加任何需要在文本 *自然* 显示完毕后执行的逻辑 # 文本自然显示完毕后的逻辑。
# 例如:显示一个 "继续" 图标,或者允许玩家在不点击的情况下自动前进等。 # 例如,可以显示一个“点击继续”的提示图标。
# 对于当前的点击继续逻辑,这个函数可能不需要做太多事情。 # 对于当前的“点击任意处继续”逻辑,此函数可能不需要执行太多操作,
pass # 暂时留空 # 因为 _input 函数会处理点击事件。
pass
# 8. 移除旧的 _on_typing_finished 函数 (如果它只用于处理旧的 Tween) # --- 输入处理 ---
# func _on_typing_finished(): # 这个函数不再需要了 # 新增/修改: 处理输入事件,特别是鼠标左键点击
# is_typing = false func _input(event: InputEvent) -> void:
# current_tween = null # 只有当页面可见且输入处理已启用时才处理输入
# print(node_name + ": Typing finished.") # (set_process_input(true) 会在 _start 中调用)
# (visible 状态由 MainController 和 UIPage 的 show_page/hide_page 管理)
if not visible:
return
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed():
print(node_name + ": Left-click detected.")
# 行为1: 如果打字机正在打字,则立即完成当前行打字 (如果TypewriterLogic支持)
# 行为2: 如果打字机已完成或未激活,则调用 go_next()
# 当前的 TypewriterLogic 没有明确的 is_typing 或 skip_to_end 方法,
# 所以我们直接调用 go_next()。
# go_next() -> _close_dialogue() -> typewriter.stop_typing() 会停止打字。
print(node_name + ": Calling go_next() due to left-click.")
go_next()
get_viewport().set_input_as_handled() # 消费事件,防止其他节点处理
return # 事件已处理
# 调用父类 (UIPage) 的 _input 方法,以保留其功能 (例如右键返回)
# 如果此处的左键点击事件被处理并返回,则父类的 _input 不会再收到此特定事件。
super._input(event)

View File

@ -0,0 +1,387 @@
extends UIPage
# UI Node References
@onready var list_container = $BG/Option/Info
@onready var previous_button = $BG/Title/previous/Button
@onready var next_button = $BG/Title/next/Button
@onready var title_label = $BG/Title/Title
# Configuration
var items_per_page: int = 7 # 每页显示的选项数量
# Data Storage
var gameplay_list_nodes: Array[VBoxContainer] = [] # 存储 List_1, List_2 等节点
var option_nodes_per_list: Array = [] # 存储每个 List 对应的 option_X 按钮数组
var all_enabled_gameplays: Array = [] # 存储筛选后的玩法数据 { "name": String, "data": Dictionary }
# State
var total_pages: int = 0
var current_page_index: int = 0
var selected_gameplay_key: String = "" # 当前逻辑上选中的项的 Key (用于确认)
var currently_selected_button: Button = null # 当前高亮/聚焦的按钮节点引用
var initial_key_to_highlight: String = "" # 弹窗打开时需要高亮的 Key (由父节点设置)
func _ready():
super._ready() # 调用父类的 _ready() (如果 UIPage 中有 _ready() 逻辑)
# 1. 获取 List 节点 (与原版一致)
for i in range(1, 4): # 检查 List_1, List_2, List_3
var list_node = list_container.get_node_or_null("List_" + str(i))
if list_node:
gameplay_list_nodes.append(list_node)
else:
break
if gameplay_list_nodes.is_empty():
printerr("错误:在 gameplay.tscn 中未找到 List 节点!无法显示选项。")
set_process(false)
return
# 2. 获取每个 List 内的 Option 节点 (与原版一致)
for list_node in gameplay_list_nodes:
var current_list_options: Array = []
for i in range(1, items_per_page + 1):
var option_node = list_node.get_node_or_null("option_" + str(i))
if option_node and option_node is Button:
current_list_options.append(option_node)
else:
push_warning("警告:在 %s 中未能找到 option_%d 按钮" % [list_node.name, i])
option_nodes_per_list.append(current_list_options)
# 3. 连接按钮信号 (与原版一致)
if previous_button:
if not previous_button.pressed.is_connected(_on_previous_pressed):
previous_button.pressed.connect(_on_previous_pressed)
else: printerr("错误:在 gameplay.tscn 中未找到 Previous 按钮")
if next_button:
if not next_button.pressed.is_connected(_on_next_pressed):
next_button.pressed.connect(_on_next_pressed)
else: printerr("错误:在 gameplay.tscn 中未找到 Next 按钮")
# 4. 连接 visibility_changed 信号 (与原版一致)
if not is_connected("visibility_changed", _on_visibility_changed):
visibility_changed.connect(_on_visibility_changed)
# 5. 移除 _ready 中的初始数据加载和显示更新
# 这些逻辑现在完全由 _on_visibility_changed -> reset_display 处理
# --- Visibility Change Handler ---
func _on_visibility_changed():
if is_visible_in_tree():
print("Gameplay: 弹窗变为可见,调用 reset_display()")
reset_display() # 当变为可见时调用 reset
# else: # 可选:隐藏时的清理逻辑
# print("Gameplay: 弹窗变为隐藏。")
# --- 新增:用于父节点设置初始选中项 ---
func set_initial_selection(key: String):
"""
(task_development)
task_info Key
"""
initial_key_to_highlight = key
print("Gameplay: 父节点设置初始高亮 Key 为: '%s'" % initial_key_to_highlight)
# --- Data Processing and Display Logic ---
func _process_gameplay_data(all_gameplays_data: Dictionary):
"""【修改】根据从 GameState 获取的玩法数据,筛选出启用的玩法,并计算总页数。"""
all_enabled_gameplays.clear()
if all_gameplays_data.is_empty():
push_warning("警告:接收到的玩法数据为空。")
total_pages = 0
return
# 遍历从 GameState 获取的玩法字典
for gameplay_name in all_gameplays_data:
var gameplay_info = all_gameplays_data[gameplay_name]
# 检查是否为字典且 "enabled" 字段为 true
if typeof(gameplay_info) == TYPE_DICTIONARY and gameplay_info.get("enabled", false) == true:
all_enabled_gameplays.append({"name": gameplay_name, "data": gameplay_info})
# else: # Debugging: 可以取消注释来看哪些被过滤了
# print("Gameplay: 过滤掉未启用或格式错误的玩法: ", gameplay_name)
# 根据筛选结果计算总页数
if items_per_page > 0:
total_pages = ceil(float(all_enabled_gameplays.size()) / items_per_page)
else:
total_pages = 0
printerr("错误items_per_page 为零,无法计算页数。")
print("Gameplay: 处理完成,找到 %d 个启用的玩法,共 %d 页。" % [all_enabled_gameplays.size(), total_pages])
func reset_display():
# 1. 重置本地状态变量
currently_selected_button = null
selected_gameplay_key = ""
# 1b. 【修改】从 MainController 的共享数据中获取初始应选中的玩法 Key
# 这个值将覆盖之前可能通过 set_initial_selection 设置的值(如果该方法在 reset_display 前被调用)。
# 请确保 "initial_selected_gameplay" 是您在 shared_workflow_data 中用于此目的的正确键名。
initial_key_to_highlight = get_main_data("玩法", "")
if not initial_key_to_highlight.is_empty():
print("Gameplay (reset_display): 从 get_main_data 获取到初始高亮 Key: '%s'" % initial_key_to_highlight)
else:
# 如果 get_main_data 没有提供有效的 Key (返回空字符串或默认值),
# initial_key_to_highlight 将是空字符串。
# 如果之前 set_initial_selection 被调用并设置了一个非空值,该值已被这里的空字符串覆盖。
# 如果希望保留 set_initial_selection 的值作为后备,逻辑需要调整,
# 但根据“来自get_main_data”的表述这里直接取用 get_main_data 的结果。
print("Gameplay (reset_display): 未从 get_main_data 获取到初始高亮 Key或 Key 为空。")
# 2. 【保持不变】从 GameState 加载并处理所有可显示的玩法数据
if not GameState:
printerr("错误GameState 在 reset_display 期间不可用。")
total_pages = 0
all_enabled_gameplays.clear()
_update_display() # 即使没有数据也要更新显示(显示空状态)
return
var task_dev_data = GameState.get_value("task_development", {})
if task_dev_data.is_empty():
printerr("错误:从 GameState 获取 'task_development' 数据失败。")
total_pages = 0
all_enabled_gameplays.clear()
else:
var all_gameplays_data = task_dev_data.get("gameplays", {})
_process_gameplay_data(all_gameplays_data) # 重新处理数据,计算 total_pages
# 3. 【逻辑不变,但使用来自 get_main_data 的 initial_key_to_highlight】
# 根据 initial_key_to_highlight 确定初始页面
current_page_index = 0 # 默认为第一页
var found_initial_key = false
if total_pages > 0 and not initial_key_to_highlight.is_empty():
# 查找 initial_key_to_highlight 所在的索引和页面
for i in range(all_enabled_gameplays.size()):
if all_enabled_gameplays[i].name == initial_key_to_highlight:
current_page_index = int(floor(float(i) / items_per_page))
found_initial_key = true
print("Gameplay: 找到需要初始高亮的 Key '%s' (来自 get_main_data 或后续处理) 在索引 %d, 目标页面 %d" % [initial_key_to_highlight, i, current_page_index])
break # 找到了,停止循环
if not found_initial_key:
print("Gameplay: 需要初始高亮的 Key '%s' (来自 get_main_data) 未在启用的玩法中找到,将显示第一页。" % initial_key_to_highlight)
# 保持 current_page_index 为 0
initial_key_to_highlight = "" # 清空,避免后续 _update_display 尝试高亮不存在的项
# 确保页面索引有效
if total_pages == 0:
current_page_index = 0
elif current_page_index >= total_pages:
current_page_index = total_pages - 1
elif current_page_index < 0:
current_page_index = 0
# 4. 触发显示更新
_update_display()
func _update_display():
"""【更新版】更新列表的可见性并填充当前页面的内容。
"""
# --- 处理无数据情况 ---
if gameplay_list_nodes.is_empty():
print("Gameplay: 无可用的 List 节点。")
if title_label: title_label.text = "玩法选择"
return
if total_pages == 0:
for list_node in gameplay_list_nodes: list_node.visible = false
print("Gameplay: 没有启用的玩法选项可供显示。")
if title_label: title_label.text = "玩法选择 (无可用)"
# 确保按钮也禁用或隐藏
if previous_button: previous_button.disabled = true
if next_button: next_button.disabled = true
return
else:
# 确保按钮状态正确 (启用/禁用基于页数)
if previous_button: previous_button.disabled = (total_pages <= 1)
if next_button: next_button.disabled = (total_pages <= 1)
# --- 更新页面索引 (循环) ---
# 注意: reset_display 设置了初始页, 取模主要用于翻页
if total_pages > 0 : # 避免除以零
current_page_index = current_page_index % total_pages
if current_page_index < 0:
current_page_index += total_pages
else:
current_page_index = 0
# --- 更新标题 ---
if title_label:
var display_page_number = current_page_index + 1 # 显示给用户的页码从1开始
title_label.text = "玩法选择 %d/%d" % [display_page_number, total_pages]
# --- 更新 List 可见性 ---
# 确保只有一个 List 节点是可见的,即当前页对应的 List
for i in range(gameplay_list_nodes.size()):
gameplay_list_nodes[i].visible = (i == current_page_index)
# --- 填充可见列表并查找要高亮的按钮 ---
var button_to_highlight: Button = null
var first_enabled_button_on_page: Button = null # 如果 initial_key 不在本页,则使用此按钮
# 检查当前页索引是否在有效范围内
if current_page_index < option_nodes_per_list.size():
var current_options = option_nodes_per_list[current_page_index] # 获取当前页的所有按钮节点
var start_index = current_page_index * items_per_page # 计算当前页数据在 all_enabled_gameplays 中的起始索引
# 遍历当前页的按钮槽位
for i in range(current_options.size()):
var option_button = current_options[i] # 获取具体的按钮节点
var item_index = start_index + i # 计算此按钮对应的数据索引
# --- 断开旧信号 ---
# 确保在连接新信号前断开旧的,防止重复连接
if option_button.pressed.is_connected(_on_option_selected):
option_button.pressed.disconnect(_on_option_selected)
# 检查数据索引是否在有效范围内
if item_index < all_enabled_gameplays.size():
# 获取对应的玩法数据
var gameplay_entry = all_enabled_gameplays[item_index]
var gameplay_name = gameplay_entry.name
var gameplay_data = gameplay_entry.data # 这是包含 Cost, Experience, Popularity 等信息的字典
# 获取按钮内部用于显示信息的 Row 节点
var row = option_button.get_node_or_null("Row")
if row:
# 填充标签文本
var title_label_in_row = row.get_node_or_null("Title")
var exp_label = row.get_node_or_null("Experience")
var pop_label = row.get_node_or_null("Popularity")
# 查找成本标签,兼容 "Cost" 和 "cost" 两种可能的节点名
var cost_label = row.get_node_or_null("Cost") if row.has_node("Cost") else row.get_node_or_null("cost")
# 设置标题
if title_label_in_row: title_label_in_row.text = gameplay_name
# 【修改】设置经验值,显示为整数
if exp_label:
var experience_value = gameplay_data.get("Experience", 0.0) # 获取原始值
exp_label.text = str(int(experience_value)) # 转为整数再转为字符串
# 【保持】设置流行度,保持原始字符串显示
if pop_label:
var popularity_value = gameplay_data.get("Popularity", "未知") # 获取原始值
pop_label.text = str(popularity_value) # 直接转为字符串
# 【修改】设置成本,显示为整数
if cost_label:
var cost_value = gameplay_data.get("Cost", 0.0) # 获取原始值
cost_label.text = str(int(cost_value)) # 转为整数再转为字符串
# 设置按钮可见、可用,并存储玩法名称到元数据
option_button.visible = true
option_button.disabled = false
option_button.set_meta("gameplay_name", gameplay_name)
# --- 连接新信号 (使用 bind) ---
# 传递玩法名称和按钮节点本身给处理函数
option_button.pressed.connect(_on_option_selected.bind(gameplay_name, option_button))
# 记录本页第一个可用的按钮,作为备选高亮目标
if first_enabled_button_on_page == null:
first_enabled_button_on_page = option_button
# 检查此按钮是否是需要初始高亮的按钮
if not initial_key_to_highlight.is_empty() and gameplay_name == initial_key_to_highlight:
button_to_highlight = option_button
# print("Gameplay: 找到需要初始高亮的按钮: ", button_to_highlight.name) # Debug
else: # 未找到 Row 节点
push_warning("警告:在按钮 %s 中未找到 Row 节点" % option_button.get_path())
option_button.visible = false # 隐藏按钮以避免显示不完整
option_button.disabled = true
else: # 此槽位没有对应的启用玩法数据 (例如最后一页不满)
option_button.visible = false # 隐藏按钮
option_button.disabled = true
else:
# 这种情况理论上不应发生,除非页面计算或节点结构有问题
printerr("错误:当前页面索引 %d 超出 option_nodes_per_list 的范围 (大小 %d)" % [current_page_index, option_nodes_per_list.size()])
# --- 设置初始高亮和焦点 ---
# 优先高亮 initial_key_to_highlight 对应的按钮
if button_to_highlight:
# print("Gameplay: 尝试高亮指定按钮: ", button_to_highlight.name) # Debug
_set_initial_highlight(button_to_highlight)
# 如果没有指定按钮或指定按钮不在当前页,则高亮当前页的第一个可用按钮
elif first_enabled_button_on_page:
print("Gameplay: 初始 Key '%s' 不在本页或为空。高亮本页第一个按钮: %s" % [initial_key_to_highlight, first_enabled_button_on_page.name])
_set_initial_highlight(first_enabled_button_on_page)
# 如果当前页没有任何可用按钮
else:
print("Gameplay: 本页没有可供高亮的按钮。")
currently_selected_button = null # 确保没有按钮被标记为选中
selected_gameplay_key = "" # 确保没有 Key 被标记为选中
# (Helper) 设置初始高亮和焦点 (与原版类似,增加日志)
func _set_initial_highlight(button_to_highlight: Button):
if not is_instance_valid(button_to_highlight):
printerr("错误:传递给 _set_initial_highlight 的按钮无效。")
currently_selected_button = null
selected_gameplay_key = ""
return
# print("Gameplay: 设置高亮按钮: ", button_to_highlight.name) # Debug
currently_selected_button = button_to_highlight
selected_gameplay_key = button_to_highlight.get_meta("gameplay_name", "") # 同时设置 Key
# print("Gameplay: currently_selected_button 设置为: ", currently_selected_button.name) # Debug
# print("Gameplay: selected_gameplay_key 设置为: '", selected_gameplay_key, "'") # Debug
# 延迟调用 grab_focus 确保按钮已准备好接收焦点
if currently_selected_button and currently_selected_button.is_inside_tree():
# print("Gameplay: 尝试为按钮 call_deferred('grab_focus'): ", currently_selected_button.name) # Debug
currently_selected_button.call_deferred("grab_focus")
else:
print("Gameplay: 按钮无效或不在场景树中,无法获取焦点。")
# --- Signal Handlers ---
func _on_previous_pressed():
if total_pages > 1: # 仅当有多页时才翻页
current_page_index -= 1
# 循环由 _update_display 处理
_update_display()
func _on_next_pressed():
if total_pages > 1: # 仅当有多页时才翻页
current_page_index += 1
# 循环由 _update_display 处理
_update_display()
# 【修改】处理选项按钮按下事件 (与原版类似,增加日志)
func _on_option_selected(gameplay_name: String, button_node: Button):
"""当选项按钮被按下时调用。"""
if not is_instance_valid(button_node):
printerr("错误:在 _on_option_selected 中收到无效按钮节点Key: " + gameplay_name)
return
if button_node == currently_selected_button:
# --- 双击 (或点击已选中的按钮) ---
print("Gameplay: 检测到双击或确认点击: ", gameplay_name)
_confirm_and_close(gameplay_name) # 确认此选择
else:
# --- 首次点击或点击不同按钮 ---
print("Gameplay: 选中 (高亮) 玩法: ", gameplay_name)
currently_selected_button = button_node
selected_gameplay_key = gameplay_name # 更新 Key以便后续可能的确认
# 更新视觉焦点
button_node.grab_focus()
# 【修改】确认选择并关闭弹窗的逻辑
func _confirm_and_close(key_to_confirm: String):
"""更新父节点并关闭此弹窗。"""
if key_to_confirm.is_empty():
printerr("错误:尝试确认一个空的玩法 Key。")
return
print("Gameplay: 确认选择: ", key_to_confirm)
set_main_data("玩法", key_to_confirm)
go_next()

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
[gd_resource type="Theme" load_steps=5 format=3 uid="uid://ddq54ba6vwyn0"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kemom"]
bg_color = Color(0.513883, 0.609153, 0.731076, 1)
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ao15e"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_kemom"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_mp8x4"]
bg_color = Color(0.513883, 0.609153, 0.731076, 1)
[resource]
Button/styles/focus = SubResource("StyleBoxFlat_kemom")
Button/styles/hover = SubResource("StyleBoxEmpty_ao15e")
Button/styles/normal = SubResource("StyleBoxEmpty_kemom")
Button/styles/pressed = SubResource("StyleBoxFlat_mp8x4")

View File

@ -0,0 +1,179 @@
extends UIPage # 继承自我们之前创建的 UIPage
@onready var platform_page_path = $platform
@onready var gameplay_page_path: Control = $gameplay
@onready var theme_page_path: Control = $theme
@onready var strategy_page_path: Control = $strategy
# Part_2: 预算
@onready var budget_button: Button = $BG/Part_2/HBoxContainer/Button
# Part_3: 选择列表中的按钮
@onready var platform_select_button: Button = $"BG/Part_3/Select_List/1_right/Button"
@onready var gameplay_select_button: Button = $"BG/Part_3/Select_List/2_right/Button"
@onready var theme_select_button: Button = $"BG/Part_3/Select_List/3_right/Button"
@onready var strategy_select_button: Button = $"BG/Part_3/Select_List/4_right/Button"
# 确认按钮
@onready var confirm_button: Button = $BG/Confirm
func _ready():
super._ready() # 调用父类的 _ready() (如果 UIPage 中有 _ready() 逻辑)
if platform_select_button:
platform_select_button.pressed.connect(_on_platform_select_button_pressed)
if gameplay_select_button:
gameplay_select_button.pressed.connect(_on_gameplay_select_button_pressed)
if theme_select_button:
theme_select_button.pressed.connect(_on_theme_select_button_pressed)
if strategy_select_button:
strategy_select_button.pressed.connect(_on_strategy_select_button_pressed)
if confirm_button:
confirm_button.pressed.connect(_on_confirm_button_pressed)
func _on_page_activated():
super._on_page_activated() # 调用父类的激活逻辑
print(name + " 页面已激活")
_calculate_and_update_budget()
# 从 MainController 加载并显示数据
# 例如,加载项目预算
var current_budget = get_main_data("预算", 0) # 假设默认预算为500
if budget_button:
budget_button.text = str(current_budget)
# 加载并显示已选的平台、玩法、题材、开发策略
# 这些值可能在其他子页面选择后被设置到 shared_workflow_data 中,
# 或者在此页面有默认值/初始值。
var selected_platform = get_main_data("平台", "") # 默认值来自场景
if platform_select_button:
platform_select_button.text = selected_platform
var selected_gameplay = get_main_data("玩法", "") # 默认值来自场景
if gameplay_select_button:
gameplay_select_button.text = selected_gameplay
var selected_theme = get_main_data("题材", "") # 默认值来自场景
if theme_select_button:
theme_select_button.text = selected_theme
var selected_strategy = get_main_data("开发策略", "") # 默认值来自场景
if strategy_select_button:
strategy_select_button.text = selected_strategy
# 可以在这里根据条件设置按钮是否可用等
# 例如: if some_condition: confirm_button.disabled = true
func _on_page_deactivated():
super._on_page_deactivated() # 调用父类的停用逻辑
print(name + " 页面已停用")
# 可以在这里保存任何未提交的临时数据
# 例如: set_main_data("temp_project_name", project_name_line_edit.text)
# 辅助函数:安全地从嵌套字典中获取值
# source_dict: 要从中查找的源字典
# path_keys: 一个包含路径中各个键的数组 (e.g., ["platforms", "Switch", "Cost"])
# default_value: 如果路径无效或键未找到,则返回此值
func _get_nested_value_from_dict(source_dict: Dictionary, path_keys: Array, default_value = null):
var current_item = source_dict
for key_part in path_keys:
if not (current_item is Dictionary and current_item.has(key_part)):
#printerr("Debug: Key '", key_part, "' not found or item not a dict at path: ", path_keys)
return default_value
current_item = current_item[key_part]
return current_item
# 根据选择计算并更新预算的函数
func _calculate_and_update_budget() -> void:
# 1. 从 shared_workflow_data 获取当前的选择
var selected_platform = get_main_data("平台", "")
var selected_gameplay = get_main_data("玩法", "")
var selected_theme = get_main_data("题材", "")
var selected_strategy = get_main_data("开发策略", "")
var platform_cost: float = 0.0
var gameplay_cost: float = 0.0
var theme_cost: float = 0.0
var strategy_cost_multiplier: float = 0.0 # 注意:策略的 "Cost" 是一个成本系数
# 2. 从 GameState 获取 "task_development" 数据
# 注意:根据提供的 GameState.gdget_value() 仅获取顶层键。
var task_dev_data = GameState.get_value("task_development")
if not (task_dev_data is Dictionary):
printerr("MainPage: 'task_development' key not found in GameState or is not a Dictionary.")
if budget_button:
budget_button.text = "错误: 数据缺失"
set_main_data("预算", 0.0) # 在共享数据中将预算设为0
return
# 3. 获取各项成本
# 平台成本
if not selected_platform.is_empty():
var cost_val = _get_nested_value_from_dict(task_dev_data, ["platforms", selected_platform, "Cost"])
if cost_val != null:
platform_cost = float(cost_val)
else:
printerr("MainPage: 平台 '", selected_platform, "' 的成本未找到或路径无效。使用 0.0。")
platform_cost = 0.0 # 确保为 float
# 玩法成本
if not selected_gameplay.is_empty():
var cost_val = _get_nested_value_from_dict(task_dev_data, ["gameplays", selected_gameplay, "Cost"])
if cost_val != null:
gameplay_cost = float(cost_val)
else:
printerr("MainPage: 玩法 '", selected_gameplay, "' 的成本未找到或路径无效。使用 0.0。")
gameplay_cost = 0.0
# 题材成本
if not selected_theme.is_empty():
var cost_val = _get_nested_value_from_dict(task_dev_data, ["themes", selected_theme, "Cost"])
if cost_val != null:
theme_cost = float(cost_val)
else:
printerr("MainPage: 题材 '", selected_theme, "' 的成本未找到或路径无效。使用 0.0。")
theme_cost = 0.0
# 开发策略成本系数
if not selected_strategy.is_empty():
var cost_val = _get_nested_value_from_dict(task_dev_data, ["strategies", selected_strategy, "Cost"])
if cost_val != null:
strategy_cost_multiplier = float(cost_val)
else:
printerr("MainPage: 开发策略 '", selected_strategy, "' 的成本系数未找到或路径无效。使用 0.0。")
strategy_cost_multiplier = 0.0
# 4. 计算预算
# 公式: (平台成本 + 玩法成本 + 题材成本) * (1 + 策略成本系数)
var base_cost = platform_cost + gameplay_cost + theme_cost
var calculated_budget = base_cost * (1.0 + strategy_cost_multiplier)
# 5. 更新预算按钮的文本和共享数据中的预算值
if budget_button:
budget_button.text = "¥" + str(snapped(calculated_budget, 0.01)) # 显示为货币格式,保留两位小数
set_main_data("预算", int(calculated_budget)) # 将计算得到的预算存回 shared_workflow_data
print("MainPage: 计算得到的预算: ", calculated_budget,
" (平台:", platform_cost, " 玩法:", gameplay_cost, " 题材:", theme_cost, " 策略系数:", strategy_cost_multiplier, ")")
# --- 信号处理函数 ---
func _on_platform_select_button_pressed():
go_to_child_page(platform_page_path)
func _on_gameplay_select_button_pressed():
go_to_child_page(gameplay_page_path)
func _on_theme_select_button_pressed():
go_to_child_page(theme_page_path)
func _on_strategy_select_button_pressed():
go_to_child_page(strategy_page_path)
func _on_confirm_button_pressed():
# 导航到下一个页面或结束工作流
# 由于 main_page 在 task_development.tscn 中配置的 next_ui_node_path 为空,
# 调用 go_next() 将触发 workflow_end_requested(true) 信号。
go_next()

View File

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

View File

@ -0,0 +1,501 @@
[gd_scene load_steps=26 format=3 uid="uid://bfmnpiqcyi83h"]
[ext_resource type="Script" uid="uid://c5ee63mwjjx7" path="res://UI/popup/task_development/main_plan/main_plan.gd" id="1_1p8wx"]
[ext_resource type="Texture2D" uid="uid://pucudatqrcrq" path="res://UI/popup/popup_bg_1.png" id="1_fdlfp"]
[ext_resource type="Texture2D" uid="uid://bk8155f5le6k4" path="res://UI/main/icon_money.png" id="2_1p8wx"]
[ext_resource type="LabelSettings" uid="uid://cvwpqds25xnfs" path="res://UI/tres/ui_main_label_number.tres" id="3_n0vbh"]
[ext_resource type="FontFile" uid="uid://egugs822n8gr" path="res://UI/font/AlimamaFangYuanTiVF-Thin.ttf" id="4_fdt2j"]
[ext_resource type="Theme" uid="uid://bau80ps6kx783" path="res://UI/tres/Bottom_Info_button_theme.tres" id="5_edtgu"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_xfbvu"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_nckjb"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_6r1e0"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_at31c"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_qrj6y"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_v0qi7"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_kctqw"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ibc02"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bd6ki"]
bg_color = Color(0.639216, 0.639216, 0.639216, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_vhs2i"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_uiuum"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_bq7yg"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ej27o"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_qo358"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_6xfhm"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k3c3w"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3etuv"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_e0qho"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_qljg5"]
[node name="main_plan" 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_1p8wx")
[node name="BG" type="NinePatchRect" parent="."]
custom_minimum_size = Vector2(500, 350)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -250.0
offset_top = -200.0
offset_right = 250.0
offset_bottom = 150.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("1_fdlfp")
region_rect = Rect2(1, 35, 86, 53)
patch_margin_left = 8
patch_margin_top = 35
patch_margin_right = 8
patch_margin_bottom = 32
[node name="Part_1" type="NinePatchRect" parent="BG"]
custom_minimum_size = Vector2(500, 0)
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -250.0
offset_right = 250.0
offset_bottom = 44.0
grow_horizontal = 2
texture = ExtResource("1_fdlfp")
region_rect = Rect2(1, 27, 86, 8)
patch_margin_left = 8
patch_margin_top = 7
patch_margin_right = 8
patch_margin_bottom = 6
[node name="Title" type="Label" parent="BG/Part_1"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -34.5
offset_top = -18.0
offset_right = 34.5
offset_bottom = 18.0
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 27
text = "新项目"
vertical_alignment = 1
[node name="Part_2" type="Control" parent="BG"]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -20.0
offset_top = 34.0
offset_right = 20.0
offset_bottom = 53.0
grow_horizontal = 2
[node name="HBoxContainer" type="HBoxContainer" parent="BG/Part_2"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -24.0
offset_top = 5.0
offset_right = 208.0
offset_bottom = 39.0
grow_horizontal = 2
grow_vertical = 2
[node name="Label" type="Label" parent="BG/Part_2/HBoxContainer"]
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 24
text = "预算"
horizontal_alignment = 1
[node name="Button" type="Button" parent="BG/Part_2/HBoxContainer"]
custom_minimum_size = Vector2(180, 0)
layout_mode = 2
theme_override_colors/font_disabled_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_pressed_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_color = Color(0, 0, 0, 1)
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_colors/font_pressed_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
theme_override_styles/focus = SubResource("StyleBoxEmpty_xfbvu")
theme_override_styles/disabled = SubResource("StyleBoxEmpty_nckjb")
theme_override_styles/hover_pressed = SubResource("StyleBoxEmpty_6r1e0")
theme_override_styles/hover = SubResource("StyleBoxEmpty_at31c")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_qrj6y")
theme_override_styles/normal = SubResource("StyleBoxEmpty_v0qi7")
text = "500"
icon = ExtResource("2_1p8wx")
flat = true
alignment = 2
icon_alignment = 2
[node name="Part_3" type="NinePatchRect" parent="BG"]
custom_minimum_size = Vector2(440, 200)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -220.0
offset_top = -91.0
offset_right = 220.0
offset_bottom = 131.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("1_fdlfp")
region_rect = Rect2(92, 38, 76, 34)
patch_margin_left = 2
patch_margin_top = 33
patch_margin_right = 2
patch_margin_bottom = 33
[node name="BG" type="NinePatchRect" parent="BG/Part_3"]
custom_minimum_size = Vector2(440, 0)
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -220.0
offset_top = -127.0
offset_right = 220.0
offset_bottom = 2.0
grow_horizontal = 2
grow_vertical = 0
texture = ExtResource("1_fdlfp")
region_rect = Rect2(100, 95, 240, 75)
[node name="Select_List" type="GridContainer" parent="BG/Part_3"]
custom_minimum_size = Vector2(320, 0)
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -180.0
offset_top = 7.0
offset_right = 180.0
offset_bottom = 179.0
grow_horizontal = 2
size_flags_horizontal = 4
theme_override_constants/h_separation = 0
columns = 2
[node name="1_left" type="NinePatchRect" parent="BG/Part_3/Select_List"]
custom_minimum_size = Vector2(140, 40)
layout_mode = 2
size_flags_horizontal = 4
texture = ExtResource("1_fdlfp")
region_rect = Rect2(1, 95, 36, 24)
patch_margin_left = 7
patch_margin_top = 6
patch_margin_right = 2
patch_margin_bottom = 6
[node name="Label" type="Label" parent="BG/Part_3/Select_List/1_left"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -60.0
offset_top = -12.0
offset_right = 60.0
offset_bottom = 24.0
grow_horizontal = 2
grow_vertical = 2
text = "平台"
label_settings = ExtResource("3_n0vbh")
horizontal_alignment = 1
[node name="1_right" type="NinePatchRect" parent="BG/Part_3/Select_List"]
custom_minimum_size = Vector2(220, 40)
layout_mode = 2
size_flags_horizontal = 4
texture = ExtResource("1_fdlfp")
region_rect = Rect2(38, 95, 50, 24)
patch_margin_top = 6
patch_margin_right = 6
patch_margin_bottom = 6
[node name="Button" type="Button" parent="BG/Part_3/Select_List/1_right"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -70.0
offset_top = -17.5
offset_right = 70.0
offset_bottom = 17.5
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_fonts/font = ExtResource("4_fdt2j")
theme_override_font_sizes/font_size = 18
theme_override_styles/focus = SubResource("StyleBoxEmpty_kctqw")
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_ibc02")
theme_override_styles/hover = SubResource("StyleBoxFlat_bd6ki")
theme_override_styles/pressed = SubResource("StyleBoxFlat_vhs2i")
theme_override_styles/normal = SubResource("StyleBoxEmpty_uiuum")
text = "Steam & Epic"
[node name="2_left" type="NinePatchRect" parent="BG/Part_3/Select_List"]
custom_minimum_size = Vector2(140, 40)
layout_mode = 2
size_flags_horizontal = 4
texture = ExtResource("1_fdlfp")
region_rect = Rect2(1, 95, 36, 24)
patch_margin_left = 7
patch_margin_top = 6
patch_margin_right = 2
patch_margin_bottom = 6
[node name="Label" type="Label" parent="BG/Part_3/Select_List/2_left"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -60.0
offset_top = -12.0
offset_right = 60.0
offset_bottom = 24.0
grow_horizontal = 2
grow_vertical = 2
text = "玩法"
label_settings = ExtResource("3_n0vbh")
horizontal_alignment = 1
[node name="2_right" type="NinePatchRect" parent="BG/Part_3/Select_List"]
custom_minimum_size = Vector2(220, 40)
layout_mode = 2
size_flags_horizontal = 4
texture = ExtResource("1_fdlfp")
region_rect = Rect2(38, 95, 50, 24)
patch_margin_top = 6
patch_margin_right = 6
patch_margin_bottom = 6
[node name="Button" type="Button" parent="BG/Part_3/Select_List/2_right"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -70.0
offset_top = -17.5
offset_right = 70.0
offset_bottom = 17.5
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_fonts/font = ExtResource("4_fdt2j")
theme_override_font_sizes/font_size = 18
theme_override_styles/focus = SubResource("StyleBoxEmpty_bq7yg")
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_ibc02")
theme_override_styles/hover = SubResource("StyleBoxFlat_bd6ki")
theme_override_styles/pressed = SubResource("StyleBoxFlat_vhs2i")
theme_override_styles/normal = SubResource("StyleBoxEmpty_uiuum")
text = "休闲放置"
[node name="3_left" type="NinePatchRect" parent="BG/Part_3/Select_List"]
custom_minimum_size = Vector2(140, 40)
layout_mode = 2
size_flags_horizontal = 4
texture = ExtResource("1_fdlfp")
region_rect = Rect2(1, 95, 36, 24)
patch_margin_left = 7
patch_margin_top = 6
patch_margin_right = 2
patch_margin_bottom = 6
[node name="Label" type="Label" parent="BG/Part_3/Select_List/3_left"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -60.0
offset_top = -12.0
offset_right = 60.0
offset_bottom = 24.0
grow_horizontal = 2
grow_vertical = 2
text = "题材"
label_settings = ExtResource("3_n0vbh")
horizontal_alignment = 1
[node name="3_right" type="NinePatchRect" parent="BG/Part_3/Select_List"]
custom_minimum_size = Vector2(220, 40)
layout_mode = 2
size_flags_horizontal = 4
texture = ExtResource("1_fdlfp")
region_rect = Rect2(38, 95, 50, 24)
patch_margin_top = 6
patch_margin_right = 6
patch_margin_bottom = 6
[node name="Button" type="Button" parent="BG/Part_3/Select_List/3_right"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -70.0
offset_top = -17.5
offset_right = 70.0
offset_bottom = 17.5
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_fonts/font = ExtResource("4_fdt2j")
theme_override_font_sizes/font_size = 18
theme_override_styles/focus = SubResource("StyleBoxEmpty_ej27o")
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_ibc02")
theme_override_styles/hover = SubResource("StyleBoxFlat_bd6ki")
theme_override_styles/pressed = SubResource("StyleBoxFlat_vhs2i")
theme_override_styles/normal = SubResource("StyleBoxEmpty_uiuum")
text = "古装仙侠"
[node name="4_left" type="NinePatchRect" parent="BG/Part_3/Select_List"]
custom_minimum_size = Vector2(140, 40)
layout_mode = 2
size_flags_horizontal = 4
texture = ExtResource("1_fdlfp")
region_rect = Rect2(1, 95, 36, 24)
patch_margin_left = 7
patch_margin_top = 6
patch_margin_right = 2
patch_margin_bottom = 6
[node name="Label" type="Label" parent="BG/Part_3/Select_List/4_left"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -60.0
offset_top = -12.0
offset_right = 60.0
offset_bottom = 24.0
grow_horizontal = 2
grow_vertical = 2
text = "开发策略"
label_settings = ExtResource("3_n0vbh")
horizontal_alignment = 1
[node name="4_right" type="NinePatchRect" parent="BG/Part_3/Select_List"]
custom_minimum_size = Vector2(220, 40)
layout_mode = 2
size_flags_horizontal = 4
texture = ExtResource("1_fdlfp")
region_rect = Rect2(38, 95, 50, 24)
patch_margin_top = 6
patch_margin_right = 6
patch_margin_bottom = 6
[node name="Button" type="Button" parent="BG/Part_3/Select_List/4_right"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -70.0
offset_top = -17.5
offset_right = 70.0
offset_bottom = 17.5
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_fonts/font = ExtResource("4_fdt2j")
theme_override_font_sizes/font_size = 18
theme_override_styles/focus = SubResource("StyleBoxEmpty_qo358")
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_ibc02")
theme_override_styles/hover = SubResource("StyleBoxFlat_bd6ki")
theme_override_styles/pressed = SubResource("StyleBoxFlat_vhs2i")
theme_override_styles/normal = SubResource("StyleBoxEmpty_uiuum")
text = "普通开发"
[node name="Confirm" type="Button" parent="BG"]
custom_minimum_size = Vector2(70, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -25.0
offset_top = 131.0
offset_right = 25.0
offset_bottom = 174.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("5_edtgu")
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 23
theme_override_styles/focus = SubResource("StyleBoxEmpty_6xfhm")
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_k3c3w")
theme_override_styles/hover = SubResource("StyleBoxFlat_3etuv")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_e0qho")
theme_override_styles/normal = SubResource("StyleBoxEmpty_qljg5")
text = "确定"

View File

@ -0,0 +1,256 @@
extends UIPage
# References to UI elements
@onready var title_label = $BG/Title/Title
@onready var name_label = $BG/Option/Name
@onready var icon_rect = $BG/Option/Info/Icon
@onready var info1_label = $BG/Option/Info/Info_List_1/Info_1/info # 制造商 (Maker)
@onready var info2_label = $BG/Option/Info/Info_List_1/Info_2/info # 发售 (Sales)
@onready var info3_label = $BG/Option/Info/Info_List_1/Info_3/info # 市场占比 (Market_share)
@onready var info4_label = $BG/Option/Info/Info_List_2/info_4/info # 开发费用 (Cost)
# @onready var info5_label = $BG/Option/Info/Info_List_2/info_5/info # 开发次数 - 暂不处理,数据源未知
@onready var prev_button = $BG/Title/previous/Button
@onready var next_button = $BG/Title/next/Button
@onready var confirm_button = $BG/Confirm
# Internal state
var enabled_platform_keys: Array[String] = [] # Store keys (names) of enabled platforms
var current_platform_index: int = -1 # Index in the enabled_platform_keys array
const PLATFORM_ICON_PATH = "res://UI/popup/task_development/main_plan/platform/"
# --- 新增:用于存储从 GameState 获取的平台数据 ---
var _platforms_data: Dictionary = {}
func _ready():
super._ready() # 调用父类的 _ready() (如果 UIPage 中有 _ready() 逻辑)
add_to_group(str(name)) # 保持不变
# Connect button signals (保持不变)
if prev_button:
if not prev_button.pressed.is_connected(_on_previous_pressed):
prev_button.pressed.connect(_on_previous_pressed)
if next_button:
if not next_button.pressed.is_connected(_on_next_pressed):
next_button.pressed.connect(_on_next_pressed)
if confirm_button:
if not confirm_button.pressed.is_connected(_on_confirm_pressed):
confirm_button.pressed.connect(_on_confirm_pressed)
# Connect visibility changed signal (保持不变)
if not is_connected("visibility_changed", _on_visibility_changed):
visibility_changed.connect(_on_visibility_changed)
# Initial call if already visible on ready (保持不变)
if is_visible_in_tree():
reset_display()
# Visibility Change Handler (保持不变)
func _on_visibility_changed():
if is_visible_in_tree():
reset_display()
# 重置显示状态,在窗口可见时调用
func reset_display():
print("Platform: Resetting display state.")
# 1. 加载并过滤平台数据
_load_and_filter_platforms() # 这会更新 _platforms_data 和 enabled_platform_keys
# 2. 检查是否有可用的平台
if enabled_platform_keys.is_empty():
print("Platform: No enabled platforms found during reset.")
current_platform_index = -1
_display_current_platform() # 显示 "无可用平台" 状态
return
# 3. 获取主任务数据中当前已选择的平台 Key
# 假设 get_main_data("键名", "默认值") 是 UIPage 基类或全局提供的方法,
# 用于获取与 task_development.gd 中 task_info["平台"] 对应的值。
var current_main_task_platform = ""
if has_method("get_main_data"): # 检查方法是否存在以避免错误
current_main_task_platform = get_main_data("平台", "")
else:
push_error("Platform: 'get_main_data' method not found. Cannot retrieve current platform from main task data.")
# 作为后备,可以尝试默认第一个,或者保持之前的 GameState 逻辑(如果还需要)
# 这里我们假设如果 get_main_data 不可用,则无法确定上次选择,行为类似首次打开
current_main_task_platform = "" # 明确设置为空,将导致默认选中第一个
# 4. 查找主任务数据中的平台 Key 在当前可用列表中的索引
if not current_main_task_platform.is_empty():
var found_index = enabled_platform_keys.find(current_main_task_platform)
if found_index != -1:
# 5. 如果找到,并且该平台仍然可用,则更新当前索引
current_platform_index = found_index
print("Platform: Defaulting to platform '%s' from main task data (found at index %d)." % [current_main_task_platform, found_index])
else:
# 6. 如果在主任务数据中记录的平台 Key 在当前启用的平台列表中找不到
# (例如,该平台可能已被禁用或数据不一致),则默认使用第一个可用的平台。
print("Platform: Platform '%s' (from main task data) not found in current enabled list. Defaulting to index 0." % current_main_task_platform)
current_platform_index = 0 # 确保至少有一个有效索引,如果 enabled_platform_keys 不为空
else:
# 7. 如果主任务数据中没有记录平台选择 (例如首次配置),则默认使用第一个可用的平台。
print("Platform: No platform selection found in main task data for key '平台'. Defaulting to index 0.")
current_platform_index = 0 # 确保至少有一个有效索引,如果 enabled_platform_keys 不为空
# 8. 更新显示
# (在步骤 2 中,如果 enabled_platform_keys 为空current_platform_index 会是 -1_display_current_platform 会处理这种情况)
_display_current_platform()
# 从 GameState 获取平台数据并筛选出启用的平台
func _load_and_filter_platforms():
enabled_platform_keys.clear()
_platforms_data = {} # 清空旧数据
if not GameState:
push_error("GameState not available in _load_and_filter_platforms.")
return
# --- 修改:从 GameState 获取 task_development 数据 ---
var task_dev_data = GameState.get_value("task_development", {})
if task_dev_data.is_empty():
printerr("Platform: Failed to get 'task_development' data from GameState.")
return
# --- 修改:获取 platforms 字典 ---
var all_platforms = task_dev_data.get("platforms", {})
if all_platforms.is_empty():
print("Platform: 'platforms' data in GameState is empty.")
return
# --- 修改:将获取到的平台数据存储到内部变量 ---
_platforms_data = all_platforms
# 遍历平台数据,筛选出启用的平台 Key
for platform_key in _platforms_data:
var platform_info = _platforms_data[platform_key]
# 确保 platform_info 是字典并且 'enabled' 键存在且为 true
if typeof(platform_info) == TYPE_DICTIONARY and platform_info.get("enabled", false) == true:
enabled_platform_keys.append(platform_key)
# 可选:按键名排序,保持一致性
#if not enabled_platform_keys.is_empty():
#enabled_platform_keys.sort() # 简单字符串排序
## current_platform_index 会在 reset_display 中设置
#else:
#print("Platform: No enabled platforms found.")
#current_platform_index = -1 # 明确设置无可用平台
# 根据 current_platform_index 更新 UI 显示
func _display_current_platform():
# 处理无可用平台或索引无效的情况
if current_platform_index < 0 or current_platform_index >= enabled_platform_keys.size():
title_label.text = "游戏平台"
name_label.text = "无可用平台"
if icon_rect: icon_rect.texture = null
if info1_label: info1_label.text = "-" # Maker
if info2_label: info2_label.text = "-" # Sales
if info3_label: info3_label.text = "-" # Market_share
if info4_label: info4_label.text = "-" # Cost
if prev_button: prev_button.disabled = true
if next_button: next_button.disabled = true
if confirm_button: confirm_button.disabled = true
return
# --- 如果有有效平台 ---
if confirm_button: confirm_button.disabled = false # 启用确认按钮
# 更新标题
var display_index = current_platform_index + 1
var total_count = enabled_platform_keys.size()
title_label.text = "游戏平台 %d/%d" % [display_index, total_count]
# 获取当前平台的 Key 和数据
var platform_key = enabled_platform_keys[current_platform_index]
# --- 从内部缓存 _platforms_data 获取平台信息 ---
if not _platforms_data.has(platform_key):
push_error("Current platform key '%s' not found in cached _platforms_data!" % platform_key)
name_label.text = "错误:数据丢失"
# ... 清空其他信息 ...
return
var platform_info = _platforms_data[platform_key]
# 更新平台名称
name_label.text = platform_key
# --- 修改:从 platform_info 读取所有需要的信息,数值转为整数显示 ---
# 使用 .get(key, default) 确保安全访问
# 制造商 (Maker) - 通常是字符串,保持不变
info1_label.text = platform_info.get("Maker", "-")
# 发售 (Sales) - 转换为整数显示
var sales_value = platform_info.get("Sales", null)
if typeof(sales_value) == TYPE_INT or typeof(sales_value) == TYPE_FLOAT:
info2_label.text = str(int(sales_value)) # 转换为整数再转字符串
else:
info2_label.text = "-" # 如果不是数字或不存在,显示 "-"
# 市场占比 (Market_share) - 转换为整数显示
var market_share_value = platform_info.get("Market_share", null)
if typeof(market_share_value) == TYPE_INT or typeof(market_share_value) == TYPE_FLOAT:
info3_label.text = str(int(market_share_value)) # 转换为整数再转字符串
else:
info3_label.text = "-"
# 开发费用 (Cost) - 转换为整数显示
var cost_value = platform_info.get("Cost", null)
if typeof(cost_value) == TYPE_INT or typeof(cost_value) == TYPE_FLOAT:
info4_label.text = str(int(cost_value)) # 转换为整数再转字符串
else:
info4_label.text = "-"
# info5 (开发次数) 暂不处理
# 更新图标 (逻辑不变)
var icon_name = platform_info.get("Icon", "")
if not icon_name.is_empty():
var icon_path = PLATFORM_ICON_PATH + icon_name + ".png"
if ResourceLoader.exists(icon_path):
icon_rect.texture = ResourceLoader.load(icon_path)
else:
push_error("Failed to load platform icon: " + icon_path)
icon_rect.texture = null
else:
print("Platform icon name is empty for key: ", platform_key)
icon_rect.texture = null
# 更新导航按钮状态 (逻辑不变)
var can_navigate = enabled_platform_keys.size() > 1
if prev_button: prev_button.disabled = not can_navigate
if next_button: next_button.disabled = not can_navigate
# --- _on_previous_pressed (逻辑不变) ---
func _on_previous_pressed():
if enabled_platform_keys.size() > 1:
current_platform_index -= 1
if current_platform_index < 0:
current_platform_index = enabled_platform_keys.size() - 1
_display_current_platform()
# --- _on_next_pressed (逻辑不变) ---
func _on_next_pressed():
if enabled_platform_keys.size() > 1:
current_platform_index += 1
if current_platform_index >= enabled_platform_keys.size():
current_platform_index = 0
_display_current_platform()
# --- _on_confirm_pressed ---
# 处理确认选择
func _on_confirm_pressed():
if current_platform_index != -1 and current_platform_index < enabled_platform_keys.size():
# 1. 获取选中的平台 Key
var selected_platform_key = enabled_platform_keys[current_platform_index]
# 2. 获取父节点 (task_development)
set_main_data("平台", selected_platform_key)
go_next()
else:
# 处理没有有效平台被选中的情况
print("Platform: No valid platform selected to confirm.")

View File

@ -0,0 +1 @@
uid://4lkw1wtwbul8

View File

@ -0,0 +1,497 @@
[gd_scene load_steps=29 format=3 uid="uid://hidwgnr45bw7"]
[ext_resource type="Script" uid="uid://4lkw1wtwbul8" path="res://UI/popup/task_development/main_plan/platform/platform.gd" id="1_ne7vr"]
[ext_resource type="Texture2D" uid="uid://pucudatqrcrq" path="res://UI/popup/popup_bg_1.png" id="1_uw2bc"]
[ext_resource type="Texture2D" uid="uid://dgcugleiv7sfw" path="res://UI/popup/task_development/arrow.png" id="2_ne7vr"]
[ext_resource type="Texture2D" uid="uid://b2n1f22bd06tc" path="res://UI/popup/task_development/main_plan/platform/platform_1.png" id="3_kmbuj"]
[ext_resource type="LabelSettings" uid="uid://qcn0aduvrgvw" path="res://UI/tres/task_development_platform_label_settings.tres" id="4_83vap"]
[ext_resource type="Theme" uid="uid://bau80ps6kx783" path="res://UI/tres/Bottom_Info_button_theme.tres" id="5_lyw5j"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_6a8uh"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_bu5lv"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_mue51"]
[sub_resource type="Animation" id="Animation_bu5lv"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Title/previous:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(9, 7)]
}
[sub_resource type="Animation" id="Animation_6a8uh"]
resource_name = "base"
length = 0.6
loop_mode = 1
step = 0.2
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Title/previous:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 1,
"values": [Vector2(16, 7), Vector2(12, 7), Vector2(9, 7), Vector2(16, 7)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_mue51"]
_data = {
&"RESET": SubResource("Animation_bu5lv"),
&"base": SubResource("Animation_6a8uh")
}
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_wpgbx"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_r6b7x"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_hsiy4"]
[sub_resource type="Animation" id="Animation_wpgbx"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(461, 7)]
}
[sub_resource type="Animation" id="Animation_mue51"]
resource_name = "base"
length = 0.6
loop_mode = 1
step = 0.2
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 1,
"values": [Vector2(455, 7), Vector2(458, 7), Vector2(461, 7), Vector2(455, 7)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_r6b7x"]
_data = {
&"RESET": SubResource("Animation_wpgbx"),
&"base": SubResource("Animation_mue51")
}
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_nckjb"]
bg_color = Color(0.913725, 0.913725, 0.913725, 0.913725)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6r1e0"]
bg_color = Color(0.913725, 0.913725, 0.913725, 0.913725)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_at31c"]
bg_color = Color(0.913725, 0.913725, 0.913725, 0.913725)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qrj6y"]
bg_color = Color(0.913725, 0.913725, 0.913725, 0.913725)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_v0qi7"]
bg_color = Color(0.913725, 0.913725, 0.913725, 0.913725)
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_rl25l"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tndxe"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xx8ge"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ep6fu"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ci4su"]
[node name="platform" 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_ne7vr")
[node name="BG" type="NinePatchRect" parent="."]
custom_minimum_size = Vector2(500, 350)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -250.0
offset_top = -200.0
offset_right = 250.0
offset_bottom = 150.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("1_uw2bc")
region_rect = Rect2(1, 35, 86, 53)
patch_margin_left = 8
patch_margin_top = 35
patch_margin_right = 8
patch_margin_bottom = 32
[node name="Title" type="NinePatchRect" parent="BG"]
custom_minimum_size = Vector2(500, 0)
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -250.0
offset_right = 250.0
offset_bottom = 44.0
grow_horizontal = 2
texture = ExtResource("1_uw2bc")
region_rect = Rect2(1, 27, 86, 8)
patch_margin_left = 8
patch_margin_top = 7
patch_margin_right = 8
patch_margin_bottom = 6
[node name="Title" type="Label" parent="BG/Title"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -34.5
offset_top = -18.0
offset_right = 34.5
offset_bottom = 18.0
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 27
text = "游戏平台"
vertical_alignment = 1
[node name="previous" type="NinePatchRect" parent="BG/Title"]
layout_mode = 1
anchors_preset = 4
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 9.0
offset_top = -15.0
offset_right = 39.0
offset_bottom = 15.0
grow_vertical = 2
texture = ExtResource("2_ne7vr")
region_rect = Rect2(0, 0, 6, 9)
[node name="Button" type="Button" parent="BG/Title/previous"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_6a8uh")
theme_override_styles/hover = SubResource("StyleBoxEmpty_bu5lv")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_mue51")
flat = true
[node name="AnimationPlayer" type="AnimationPlayer" parent="BG/Title/previous"]
root_node = NodePath("../../..")
libraries = {
&"": SubResource("AnimationLibrary_mue51")
}
autoplay = "base"
[node name="next" type="NinePatchRect" parent="BG/Title"]
layout_mode = 1
anchors_preset = 6
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -39.0
offset_top = -15.0
offset_right = -9.0
offset_bottom = 15.0
grow_horizontal = 0
grow_vertical = 2
texture = ExtResource("2_ne7vr")
region_rect = Rect2(6, 0, 6, 9)
[node name="Button" type="Button" parent="BG/Title/next"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_wpgbx")
theme_override_styles/hover = SubResource("StyleBoxEmpty_r6b7x")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_hsiy4")
flat = true
[node name="AnimationPlayer" type="AnimationPlayer" parent="BG/Title/next"]
libraries = {
&"": SubResource("AnimationLibrary_r6b7x")
}
autoplay = "base"
[node name="Option" type="Control" parent="BG"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="Name" type="Label" parent="BG/Option"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -75.0
offset_top = -129.5
offset_right = 75.0
offset_bottom = -95.5
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 24
text = "台式 & 笔记本"
horizontal_alignment = 1
[node name="Info" type="NinePatchRect" parent="BG/Option"]
custom_minimum_size = Vector2(440, 200)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -220.0
offset_top = -92.0
offset_right = 220.0
offset_bottom = 130.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("1_uw2bc")
region_rect = Rect2(92, 38, 76, 34)
patch_margin_left = 2
patch_margin_top = 33
patch_margin_right = 2
patch_margin_bottom = 33
[node name="Icon" type="NinePatchRect" parent="BG/Option/Info"]
custom_minimum_size = Vector2(144, 144)
layout_mode = 1
anchors_preset = 4
anchor_top = 0.5
anchor_bottom = 0.5
offset_top = -108.0
offset_right = 190.0
offset_bottom = 82.0
grow_vertical = 2
texture = ExtResource("3_kmbuj")
[node name="Info_List_1" type="GridContainer" parent="BG/Option/Info"]
layout_mode = 1
anchors_preset = 6
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -225.0
offset_top = -82.0
offset_right = -9.0
offset_bottom = 19.0
grow_horizontal = 0
grow_vertical = 2
theme_override_constants/v_separation = 7
[node name="Info_1" type="HBoxContainer" parent="BG/Option/Info/Info_List_1"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="start" type="Label" parent="BG/Option/Info/Info_List_1/Info_1"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_nckjb")
text = " "
label_settings = ExtResource("4_83vap")
[node name="title" type="Label" parent="BG/Option/Info/Info_List_1/Info_1"]
custom_minimum_size = Vector2(90, 0)
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_nckjb")
text = "制造商"
label_settings = ExtResource("4_83vap")
[node name="info" type="Label" parent="BG/Option/Info/Info_List_1/Info_1"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 2
text = "不定"
label_settings = ExtResource("4_83vap")
horizontal_alignment = 1
[node name="Info_2" type="HBoxContainer" parent="BG/Option/Info/Info_List_1"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="start" type="Label" parent="BG/Option/Info/Info_List_1/Info_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_nckjb")
text = " "
label_settings = ExtResource("4_83vap")
[node name="title" type="Label" parent="BG/Option/Info/Info_List_1/Info_2"]
custom_minimum_size = Vector2(90, 0)
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_6r1e0")
text = "发售"
label_settings = ExtResource("4_83vap")
[node name="info" type="Label" parent="BG/Option/Info/Info_List_1/Info_2"]
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
text = "50"
label_settings = ExtResource("4_83vap")
horizontal_alignment = 2
[node name="unit" type="Label" parent="BG/Option/Info/Info_List_1/Info_2"]
layout_mode = 2
text = "万台"
label_settings = ExtResource("4_83vap")
horizontal_alignment = 2
[node name="Info_3" type="HBoxContainer" parent="BG/Option/Info/Info_List_1"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="start" type="Label" parent="BG/Option/Info/Info_List_1/Info_3"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_nckjb")
text = " "
label_settings = ExtResource("4_83vap")
[node name="title" type="Label" parent="BG/Option/Info/Info_List_1/Info_3"]
custom_minimum_size = Vector2(90, 0)
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_at31c")
text = "市场占比"
label_settings = ExtResource("4_83vap")
[node name="info" type="Label" parent="BG/Option/Info/Info_List_1/Info_3"]
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
text = "38"
label_settings = ExtResource("4_83vap")
horizontal_alignment = 2
[node name="unit" type="Label" parent="BG/Option/Info/Info_List_1/Info_3"]
layout_mode = 2
text = "%"
label_settings = ExtResource("4_83vap")
horizontal_alignment = 2
[node name="Info_List_2" type="HBoxContainer" parent="BG/Option/Info"]
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -191.5
offset_top = -38.0
offset_right = 191.5
offset_bottom = -10.0
grow_horizontal = 2
grow_vertical = 0
alignment = 1
[node name="info_4" type="HBoxContainer" parent="BG/Option/Info/Info_List_2"]
layout_mode = 2
[node name="title" type="Label" parent="BG/Option/Info/Info_List_2/info_4"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_qrj6y")
text = "开发费用"
label_settings = ExtResource("4_83vap")
horizontal_alignment = 1
[node name="info" type="Label" parent="BG/Option/Info/Info_List_2/info_4"]
custom_minimum_size = Vector2(70, 0)
layout_mode = 2
text = "500"
label_settings = ExtResource("4_83vap")
horizontal_alignment = 1
[node name="info_5" type="HBoxContainer" parent="BG/Option/Info/Info_List_2"]
layout_mode = 2
[node name="title" type="Label" parent="BG/Option/Info/Info_List_2/info_5"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_v0qi7")
text = "开发次数"
label_settings = ExtResource("4_83vap")
horizontal_alignment = 1
[node name="info" type="Label" parent="BG/Option/Info/Info_List_2/info_5"]
custom_minimum_size = Vector2(30, 0)
layout_mode = 2
text = "0"
label_settings = ExtResource("4_83vap")
horizontal_alignment = 2
[node name="unit" type="Label" parent="BG/Option/Info/Info_List_2/info_5"]
layout_mode = 2
text = "次"
label_settings = ExtResource("4_83vap")
horizontal_alignment = 2
[node name="Confirm" type="Button" parent="BG"]
custom_minimum_size = Vector2(70, 0)
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -35.0
offset_top = -43.0
offset_right = 35.0
grow_horizontal = 2
grow_vertical = 0
theme = ExtResource("5_lyw5j")
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 23
theme_override_styles/focus = SubResource("StyleBoxEmpty_rl25l")
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_tndxe")
theme_override_styles/hover = SubResource("StyleBoxFlat_xx8ge")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_ep6fu")
theme_override_styles/normal = SubResource("StyleBoxEmpty_ci4su")
text = "确定"

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b2n1f22bd06tc"
path="res://.godot/imported/platform_1.png-cfa80f103262e4341156e5985a03ca68.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://UI/popup/task_development/main_plan/platform/platform_1.png"
dest_files=["res://.godot/imported/platform_1.png-cfa80f103262e4341156e5985a03ca68.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c53cngwtsi5hb"
path="res://.godot/imported/platform_2.png-83b257c4756ce323c8ad5870c81381f7.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://UI/popup/task_development/main_plan/platform/platform_2.png"
dest_files=["res://.godot/imported/platform_2.png-83b257c4756ce323c8ad5870c81381f7.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

@ -0,0 +1,268 @@
extends UIPage
# --- 节点引用 ---
@onready var options_container: VBoxContainer = $BG/Option/Options/List_1
@onready var explanation_label: Label = $BG/Option/Explaination
# --- 内部状态 ---
var option_buttons: Array[Button] = [] # 存储选项按钮节点的数组
var highlighted_button: Button = null # 当前逻辑上选中的按钮 (用于区分首次点击和确认点击)
var current_strategy_data: Dictionary = {} # 存储从 GameState 获取的原始策略数据
var initial_selection_key: String = "" # 由父节点传入的初始/当前选择的策略 Key (中文名)
# --- 初始化 ---
func _ready() -> void:
super._ready() # 调用父类的 _ready() (如果 UIPage 中有 _ready() 逻辑)
add_to_group(str(name)) # 便于父节点或其他系统查找
# --- 获取按钮引用 ---
option_buttons.clear()
for child in options_container.get_children():
if child is Button:
option_buttons.append(child)
# 断开旧连接以防万一 (虽然理论上 _ready 只执行一次)
if child.is_connected("pressed", _on_option_button_pressed):
child.disconnect("pressed", _on_option_button_pressed)
# --- 连接 GameState 通用值变化信号 ---
# 用于响应策略数据可能的动态变化 (如解锁新策略)
if GameState and not GameState.state_value_changed.is_connected(_on_game_state_value_changed):
GameState.state_value_changed.connect(_on_game_state_value_changed)
# --- 连接 visibility_changed 信号 ---
# 确保每次界面可见时都刷新列表内容
if not is_connected("visibility_changed", _on_visibility_changed):
visibility_changed.connect(_on_visibility_changed)
# _update_strategy_list() 不再在 _ready 中调用,移至 _on_visibility_changed
# --- 父节点调用接口 ---
# 由父节点 (task_development.gd) 在显示此弹窗前调用,传入当前的临时选择
func set_initial_selection(strategy_key: String) -> void:
initial_selection_key = strategy_key
print("Strategy: Initial selection key set by parent: '", initial_selection_key, "'")
# 注意:这里只存储值,实际的高亮逻辑仍在 _update_strategy_list 中处理
# --- 信号处理 ---
# 处理可见性变化的函数
func _on_visibility_changed() -> void:
print("Strategy: _on_visibility_changed called, visible = ", is_visible_in_tree())
# 当节点变为可见时,更新列表并设置初始焦点
if is_visible_in_tree():
print("Strategy popup became visible. Updating list and setting focus...")
# 确保在更新列表前initial_selection_key 已被父节点设置
_update_strategy_list()
# else: # 可选:如果需要在隐藏时做清理,可以在这里添加逻辑
# print("Strategy popup became hidden.")
# 当 GameState 的任何值发生变化时调用
func _on_game_state_value_changed(key: String, new_value) -> void:
# 检查是否是 task_development 数据发生了变化,这可能影响可用策略
# 注意:这是一个比较宽泛的检查。如果 task_development 下其他不相关数据变化也会触发刷新。
if key == "task_development":
# 并且确保当前界面是可见的避免在后台刷新不可见的UI
if is_visible_in_tree():
print("Strategy: Detected change in 'task_development', refreshing list...")
# 重新执行列表更新,它会使用最新的 GameState 数据
# 并尝试根据 initial_selection_key (由父节点设定,理论上不变) 恢复高亮
_update_strategy_list()
# --- 核心 UI 更新逻辑 ---
# 更新策略列表UI的函数
func _update_strategy_list() -> void:
# 0. 重置界面逻辑状态 (清除上次的高亮和说明)
_reset_highlight_state()
# 0b. 【修改】从 MainController 的共享数据中获取初始应选中的策略 Key
# 使用键名 "开发策略"。如果未找到该键,默认为空字符串。
# 这将覆盖之前可能通过 set_initial_selection 设置的值。
initial_selection_key = get_main_data("开发策略", "") # <--- 从共享数据获取初始高亮项
if not initial_selection_key.is_empty():
print("Strategy (_update_strategy_list): 从 get_main_data(\"开发策略\") 获取到初始选择 Key: '%s'" % initial_selection_key)
else:
print("Strategy (_update_strategy_list): 未从 get_main_data(\"开发策略\") 获取到初始选择 Key或 Key 为空。")
# 1. 从 GameState 获取最新的 task_development 数据
if not GameState:
printerr("Strategy: GameState not available during _update_strategy_list.")
for button in option_buttons: button.visible = false
explanation_label.text = "错误:无法访问游戏状态。"
return
var task_dev_data: Dictionary = GameState.get_value("task_development", {})
if task_dev_data.is_empty():
printerr("Strategy: Failed to get 'task_development' data from GameState.")
for button in option_buttons: button.visible = false
explanation_label.text = "错误:无法获取开发数据。"
return
# 提取策略子字典
current_strategy_data = task_dev_data.get("strategies", {})
if current_strategy_data.is_empty():
printerr("Strategy: 'strategies' data not found or empty in task_development.")
for button in option_buttons: button.visible = false
explanation_label.text = "没有可用的开发策略。"
return
# 2. 筛选出已启用的策略
var enabled_strategies = []
for strategy_name in current_strategy_data: # 遍历字典的键 (策略中文名)
var details: Dictionary = current_strategy_data[strategy_name]
# 确保 details 是字典并且 'enabled' 键存在且为 true
if typeof(details) == TYPE_DICTIONARY and details.get("enabled", false) == true:
enabled_strategies.append({"name": strategy_name, "details": details})
# 如果需要固定顺序,可以排序
# enabled_strategies.sort_custom(func(a, b): return a["name"] < b["name"])
# 3. 填充按钮
var strategy_index: int = 0
var first_enabled_button: Button = null # 用于没有初始选择时的默认高亮
# --- 确保每次更新时正确设置按钮状态和信号连接 ---
for button in option_buttons:
button.visible = false
button.disabled = true
if button.is_connected("pressed", _on_option_button_pressed):
button.disconnect("pressed", _on_option_button_pressed)
if button.has_meta("strategy_name"):
button.remove_meta("strategy_name")
while strategy_index < enabled_strategies.size() and strategy_index < option_buttons.size():
var button: Button = option_buttons[strategy_index]
var strategy_data: Dictionary = enabled_strategies[strategy_index]
var strategy_name: String = strategy_data["name"]
var strategy_details: Dictionary = strategy_data["details"]
var title_label: Label = button.find_child("Title", true, false) as Label
var cost_label: Label = button.find_child("Cost", true, false) as Label
if title_label and cost_label:
title_label.text = strategy_name
var cost_value = strategy_details.get("Cost", null)
if typeof(cost_value) == TYPE_FLOAT or typeof(cost_value) == TYPE_INT:
var percentage: int = int(cost_value * 100)
cost_label.text = "+%d%%" % percentage
else:
cost_label.text = "N/A"
button.set_meta("strategy_name", strategy_name)
button.visible = true
button.disabled = false
button.pressed.connect(_on_option_button_pressed.bind(button))
if first_enabled_button == null:
first_enabled_button = button
else:
printerr("Strategy: 在按钮 '%s' 下未找到 Title 或 Cost 标签。" % button.name)
strategy_index += 1
# 4. 设置初始逻辑选中状态和焦点
# (此部分逻辑不变,但现在 initial_selection_key 是从 get_main_data 获取的)
# print("--- Updating Strategy List (Post GameState Load) ---") # Debugging
# print("Initial selection key (potentially from get_main_data): '", initial_selection_key, "'") # Debugging
var initial_highlight_button: Button = null
if not initial_selection_key.is_empty():
# print("Searching for matching button for key: '", initial_selection_key, "'") # Debugging
for button in option_buttons:
if button.visible and not button.disabled:
var meta_name = button.get_meta("strategy_name", "")
# print(" Checking button: '", button.name, "' with meta: '", meta_name, "'") # Debugging
if meta_name == initial_selection_key:
# print(" Match found!: ", button.name) # Debugging
initial_highlight_button = button
break
if initial_highlight_button == null:
# print("No match found for initial key or key was empty. Trying first enabled button.") # Debugging
initial_highlight_button = first_enabled_button
if initial_highlight_button:
# print("Button to initially highlight: ", initial_highlight_button.name) # Debugging
_set_initial_highlight(initial_highlight_button)
else:
# print("No button available to highlight.") # Debugging
explanation_label.text = "没有可用的开发策略。"
# print("--- Finished Updating Strategy List (Post GameState Load) ---") # Debugging
# --- 交互处理 ---
# 处理选项按钮点击事件的函数
func _on_option_button_pressed(button_node: Button) -> void:
# 从按钮元数据获取对应的策略名称
var strategy_name: String = button_node.get_meta("strategy_name", "")
if strategy_name.is_empty():
printerr("Strategy: 无法从按钮 %s 获取 strategy_name 元数据。" % button_node.name)
return
# 检查点击的按钮是否就是当前已逻辑高亮的按钮
if button_node == highlighted_button:
# --- 第二次点击 (确认选择) ---
print("Strategy: Confirmed selection - ", strategy_name)
set_main_data("开发策略", strategy_name)
go_next()
else:
# --- 第一次点击 或 点击了不同的按钮 (更新逻辑选择和说明) ---
print("Strategy: Logically selected - ", strategy_name)
# 更新当前逻辑高亮的按钮引用
highlighted_button = button_node
# 更新底部的说明文本
# 确保 current_strategy_data 是最新的 (理论上 _update_strategy_list 刚更新过)
if current_strategy_data.has(strategy_name):
var details: Dictionary = current_strategy_data[strategy_name]
explanation_label.text = details.get("Explaination", "暂无说明。") # 使用 .get 提供默认值
else:
# 如果因为某些原因数据不同步,显示错误
explanation_label.text = "错误:找不到策略详情。"
printerr("Strategy: Could not find details for strategy '", strategy_name, "' in current_strategy_data.")
# 注意:此时按钮的视觉焦点可能还未改变,依赖于 Button 主题的 focus 样式
# _set_initial_highlight 内部会处理 grab_focus
# --- 辅助函数 ---
# (辅助函数) 重置界面逻辑高亮状态
func _reset_highlight_state() -> void:
highlighted_button = null # 清除逻辑高亮引用
explanation_label.text = "" # 清空说明文本
# (辅助函数) 设置初始逻辑选中按钮并赋予焦点
func _set_initial_highlight(button_to_highlight: Button) -> void:
print("--- Setting Initial Highlight ---")
if not is_instance_valid(button_to_highlight):
printerr("Strategy: Invalid button passed to _set_initial_highlight.")
return
print("Button received: ", button_to_highlight.name)
# 设置逻辑高亮引用
highlighted_button = button_to_highlight
print("highlighted_button variable set to: ", highlighted_button.name)
# 更新说明文本
var strategy_name = highlighted_button.get_meta("strategy_name", "")
if not strategy_name.is_empty() and current_strategy_data.has(strategy_name):
var details: Dictionary = current_strategy_data[strategy_name]
explanation_label.text = details.get("Explaination", "暂无说明。")
elif not strategy_name.is_empty():
explanation_label.text = "错误:找不到策略详情。"
printerr("Strategy: Could not find details for highlighted strategy '", strategy_name, "'")
else:
explanation_label.text = "错误:无法获取策略名称。" # 如果连元数据都没有
# 尝试让按钮获得实际输入焦点 (使用 call_deferred 确保在安全的时机执行)
# 这会让按钮应用其主题中的 "focus" 样式
if highlighted_button.is_inside_tree() and highlighted_button.visible and not highlighted_button.disabled:
print("Attempting call_deferred('grab_focus') for: ", highlighted_button.name)
highlighted_button.call_deferred("grab_focus")
else:
print("Button '%s' not valid, not in tree, hidden, or disabled. Cannot grab focus." % highlighted_button.name)
print("--- Finished Setting Initial Highlight ---")

View File

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

View File

@ -0,0 +1,361 @@
[gd_scene load_steps=5 format=3 uid="uid://cx5nui56begu8"]
[ext_resource type="Script" uid="uid://by3rdgo6jlbff" path="res://UI/popup/task_development/main_plan/strategy/strategy.gd" id="1_pbd5q"]
[ext_resource type="Texture2D" uid="uid://pucudatqrcrq" path="res://UI/popup/popup_bg_1.png" id="2_eh5ff"]
[ext_resource type="Theme" uid="uid://ddq54ba6vwyn0" path="res://UI/popup/task_development/main_plan/gameplay/gameplay_option_button.tres" id="3_rq5uk"]
[ext_resource type="Texture2D" uid="uid://bb3kyiufyyj05" path="res://Entity/NPC/NPC_1_UI.png" id="4_vx267"]
[node name="strategy" type="Control"]
visible = false
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_pbd5q")
[node name="BG" type="NinePatchRect" parent="."]
custom_minimum_size = Vector2(500, 350)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -250.0
offset_top = -200.0
offset_right = 250.0
offset_bottom = 150.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("2_eh5ff")
region_rect = Rect2(1, 35, 86, 53)
patch_margin_left = 8
patch_margin_top = 35
patch_margin_right = 8
patch_margin_bottom = 32
[node name="Title" type="NinePatchRect" parent="BG"]
custom_minimum_size = Vector2(500, 0)
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -250.0
offset_right = 250.0
offset_bottom = 44.0
grow_horizontal = 2
texture = ExtResource("2_eh5ff")
region_rect = Rect2(1, 27, 86, 8)
patch_margin_left = 8
patch_margin_top = 7
patch_margin_right = 8
patch_margin_bottom = 6
[node name="Title" type="Label" parent="BG/Title"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -34.5
offset_top = -18.0
offset_right = 34.5
offset_bottom = 18.0
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 27
text = "开发策略"
vertical_alignment = 1
[node name="Option" type="Control" parent="BG"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="Column_Headers" type="HBoxContainer" parent="BG/Option"]
custom_minimum_size = Vector2(420, 0)
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -210.0
offset_top = 51.0
offset_right = 210.0
offset_bottom = 82.0
grow_horizontal = 2
theme_override_constants/separation = 0
[node name="Title" type="Label" parent="BG/Option/Column_Headers"]
custom_minimum_size = Vector2(320, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 22
text = "开发方针"
[node name="Cost" type="Label" parent="BG/Option/Column_Headers"]
custom_minimum_size = Vector2(80, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 22
text = "成本"
horizontal_alignment = 2
[node name="Options" type="NinePatchRect" parent="BG/Option"]
custom_minimum_size = Vector2(440, 170)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -220.0
offset_top = -92.0
offset_right = 220.0
offset_bottom = 78.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("2_eh5ff")
region_rect = Rect2(92, 38, 76, 34)
patch_margin_left = 2
patch_margin_top = 33
patch_margin_right = 2
patch_margin_bottom = 33
[node name="List_1" type="VBoxContainer" parent="BG/Option/Options"]
custom_minimum_size = Vector2(436, 160)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -210.0
offset_top = -80.0
offset_right = 210.0
offset_bottom = 80.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 0
[node name="option_1" type="Button" parent="BG/Option/Options/List_1"]
custom_minimum_size = Vector2(0, 32)
layout_mode = 2
theme = ExtResource("3_rq5uk")
disabled = true
flat = true
[node name="Row" type="HBoxContainer" parent="BG/Option/Options/List_1/option_1"]
custom_minimum_size = Vector2(420, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -210.0
offset_top = -15.5
offset_right = 210.0
offset_bottom = 15.5
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 0
[node name="Title" type="Label" parent="BG/Option/Options/List_1/option_1/Row"]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
text = "正常开发"
[node name="Cost" type="Label" parent="BG/Option/Options/List_1/option_1/Row"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
text = "0"
horizontal_alignment = 2
[node name="option_2" type="Button" parent="BG/Option/Options/List_1"]
custom_minimum_size = Vector2(0, 32)
layout_mode = 2
theme = ExtResource("3_rq5uk")
disabled = true
flat = true
[node name="Row" type="HBoxContainer" parent="BG/Option/Options/List_1/option_2"]
custom_minimum_size = Vector2(420, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -210.0
offset_top = -15.5
offset_right = 210.0
offset_bottom = 15.5
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 0
[node name="Title" type="Label" parent="BG/Option/Options/List_1/option_2/Row"]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
[node name="Cost" type="Label" parent="BG/Option/Options/List_1/option_2/Row"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
horizontal_alignment = 2
[node name="option_3" type="Button" parent="BG/Option/Options/List_1"]
custom_minimum_size = Vector2(0, 32)
layout_mode = 2
theme = ExtResource("3_rq5uk")
disabled = true
flat = true
[node name="Row" type="HBoxContainer" parent="BG/Option/Options/List_1/option_3"]
custom_minimum_size = Vector2(420, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -210.0
offset_top = -15.5
offset_right = 210.0
offset_bottom = 15.5
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 0
[node name="Title" type="Label" parent="BG/Option/Options/List_1/option_3/Row"]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
[node name="Cost" type="Label" parent="BG/Option/Options/List_1/option_3/Row"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
horizontal_alignment = 2
[node name="option_4" type="Button" parent="BG/Option/Options/List_1"]
custom_minimum_size = Vector2(0, 32)
layout_mode = 2
theme = ExtResource("3_rq5uk")
disabled = true
flat = true
[node name="Row" type="HBoxContainer" parent="BG/Option/Options/List_1/option_4"]
custom_minimum_size = Vector2(420, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -210.0
offset_top = -15.5
offset_right = 210.0
offset_bottom = 15.5
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 0
[node name="Title" type="Label" parent="BG/Option/Options/List_1/option_4/Row"]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
[node name="Cost" type="Label" parent="BG/Option/Options/List_1/option_4/Row"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
horizontal_alignment = 2
[node name="option_5" type="Button" parent="BG/Option/Options/List_1"]
custom_minimum_size = Vector2(0, 32)
layout_mode = 2
theme = ExtResource("3_rq5uk")
disabled = true
flat = true
[node name="Row" type="HBoxContainer" parent="BG/Option/Options/List_1/option_5"]
custom_minimum_size = Vector2(420, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -210.0
offset_top = -15.5
offset_right = 210.0
offset_bottom = 15.5
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 0
[node name="Title" type="Label" parent="BG/Option/Options/List_1/option_5/Row"]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
[node name="Cost" type="Label" parent="BG/Option/Options/List_1/option_5/Row"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
horizontal_alignment = 2
[node name="Explaination" type="Label" parent="BG/Option"]
custom_minimum_size = Vector2(200, 60)
layout_mode = 1
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 47.0
offset_top = -83.0
offset_right = 358.0
offset_bottom = -23.0
grow_vertical = 0
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
text = "说明"
horizontal_alignment = 1
vertical_alignment = 1
autowrap_mode = 3
[node name="Human" type="TextureRect" parent="BG/Option"]
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -128.0
offset_top = -90.0
offset_right = -55.0
offset_bottom = -17.0
grow_horizontal = 0
grow_vertical = 0
texture = ExtResource("4_vx267")

View File

@ -0,0 +1,368 @@
extends UIPage
# UI Node References
@onready var list_container = $BG/Option/Info
@onready var previous_button = $BG/Title/previous/Button
@onready var next_button = $BG/Title/next/Button
@onready var title_label = $BG/Title/Title
# Configuration
var items_per_page: int = 7 # 每页显示的选项数量 (保持与原 theme.gd 一致)
# Data Storage
var theme_list_nodes: Array[VBoxContainer] = [] # 存储 List_1, List_2 等节点
var option_nodes_per_list: Array = [] # 存储每个 List 对应的 option_X 按钮数组
var all_enabled_themes: Array = [] # 存储筛选后的题材数据 { "name": String, "data": Dictionary }
# State
var total_pages: int = 0
var current_page_index: int = 0
var selected_theme_key: String = "" # 当前逻辑上选中的项的 Key (用于确认)
var currently_selected_button: Button = null # 当前高亮/聚焦的按钮节点引用
var initial_key_to_highlight: String = "" # 弹窗打开时需要高亮的 Key (由父节点设置)
func _ready():
super._ready() # 调用父类的 _ready() (如果 UIPage 中有 _ready() 逻辑)
# 1. 获取 List 节点 (与原版 theme.gd 类似,但使用 gameplay.gd 的错误处理)
for i in range(1, 7): # 假设最多有 List_6 (与原 theme.gd 匹配)
var list_node = list_container.get_node_or_null("List_" + str(i))
if list_node:
theme_list_nodes.append(list_node)
else:
break # 假设 List 节点是连续的
if theme_list_nodes.is_empty():
printerr("错误:在 theme.tscn 中未找到 List 节点!无法显示选项。")
set_process(false) # 禁用处理因为没有UI元素
return
# 2. 获取每个 List 内的 Option 节点 (与原版 theme.gd 类似)
for list_node in theme_list_nodes:
var current_list_options: Array = []
for i in range(1, items_per_page + 1):
var option_node = list_node.get_node_or_null("option_" + str(i))
if option_node and option_node is Button:
current_list_options.append(option_node)
else:
# 使用 gameplay.gd 的警告格式
push_warning("警告:在 %s 中未能找到 option_%d 按钮" % [list_node.name, i])
option_nodes_per_list.append(current_list_options)
# 3. 连接按钮信号 (与 gameplay.gd 一致)
if previous_button:
if not previous_button.pressed.is_connected(_on_previous_pressed):
previous_button.pressed.connect(_on_previous_pressed)
else: printerr("错误:在 theme.tscn 中未找到 Previous 按钮")
if next_button:
if not next_button.pressed.is_connected(_on_next_pressed):
next_button.pressed.connect(_on_next_pressed)
else: printerr("错误:在 theme.tscn 中未找到 Next 按钮")
# 4. 连接 visibility_changed 信号 (与 gameplay.gd 一致)
if not is_connected("visibility_changed", _on_visibility_changed):
visibility_changed.connect(_on_visibility_changed)
# 5. 移除 _ready 中的初始数据加载和显示更新
# 这些逻辑现在完全由 _on_visibility_changed -> reset_display 处理
# --- Visibility Change Handler (与 gameplay.gd 一致) ---
func _on_visibility_changed():
if is_visible_in_tree():
print("Theme: 弹窗变为可见,调用 reset_display()")
reset_display() # 当变为可见时调用 reset
# else: # 可选:隐藏时的清理逻辑
# print("Theme: 弹窗变为隐藏。")
# --- 新增:用于父节点设置初始选中项 (与 gameplay.gd 一致) ---
func set_initial_selection(key: String):
"""
(task_development)
task_info Key
"""
initial_key_to_highlight = key
print("Theme: 父节点设置初始高亮 Key 为: '%s'" % initial_key_to_highlight)
# --- Data Processing and Display Logic ---
func _process_theme_data(all_themes_data: Dictionary):
"""【修改】根据从 GameState 获取的题材数据,筛选出启用的题材,并计算总页数。"""
all_enabled_themes.clear()
if all_themes_data.is_empty():
push_warning("警告:接收到的题材数据为空。")
total_pages = 0
return
# 遍历从 GameState 获取的题材字典
for theme_name in all_themes_data:
var theme_info = all_themes_data[theme_name]
# 检查是否为字典且 "enabled" 字段为 true
if typeof(theme_info) == TYPE_DICTIONARY and theme_info.get("enabled", false) == true:
all_enabled_themes.append({"name": theme_name, "data": theme_info})
# else: # Debugging
# print("Theme: 过滤掉未启用或格式错误的题材: ", theme_name)
# 根据筛选结果计算总页数
if items_per_page > 0:
total_pages = ceil(float(all_enabled_themes.size()) / items_per_page)
else:
total_pages = 0
printerr("错误items_per_page 为零,无法计算页数。")
print("Theme: 处理完成,找到 %d 个启用的题材,共 %d 页。" % [all_enabled_themes.size(), total_pages])
func reset_display():
"""【修改】重置状态,从共享数据获取初始选中项,从 GameState 加载所有可选数据,并准备更新显示。"""
print("Theme: 重置显示状态 (reset_display)。")
# 1. 重置本地状态变量
currently_selected_button = null
selected_theme_key = "" # 当前逻辑上选中的项的 Key
# 1b. 【修改】从 MainController 的共享数据中获取初始应选中的题材 Key
# 使用键名 "题材"。如果未找到该键,默认为空字符串。
initial_key_to_highlight = get_main_data("题材", "") # <--- 从共享数据获取初始高亮项
if not initial_key_to_highlight.is_empty():
print("Theme (reset_display): 从 get_main_data(\"题材\") 获取到初始高亮 Key: '%s'" % initial_key_to_highlight)
else:
# 如果 get_main_data 没有提供有效的 Key (例如 "题材" 键不存在或其值为空字符串),
# initial_key_to_highlight 将是空字符串。
print("Theme (reset_display): 未从 get_main_data(\"题材\") 获取到初始高亮 Key或 Key 为空。将尝试高亮第一页的第一个项目。")
# 2. 【保持不变】从 GameState 重新加载并处理所有可显示的题材数据
if not GameState:
printerr("错误GameState 在 reset_display 期间不可用。")
total_pages = 0
all_enabled_themes.clear()
_update_display() # 即使没有数据也要更新显示(显示空状态)
return
# --- 获取题材数据 ---
var task_dev_data = GameState.get_value("task_development", {})
if task_dev_data.is_empty():
printerr("错误:从 GameState 获取 'task_development' 数据失败。")
total_pages = 0
all_enabled_themes.clear()
else:
# --- 从 task_development 中获取 themes ---
var all_themes_data = task_dev_data.get("themes", {})
_process_theme_data(all_themes_data) # 重新处理数据,计算 total_pages
# 3. 【逻辑不变,但使用来自 get_main_data("题材", "") 的 initial_key_to_highlight】
# 根据 initial_key_to_highlight 确定初始页面
current_page_index = 0 # 默认为第一页
var found_initial_key = false
if total_pages > 0 and not initial_key_to_highlight.is_empty():
# 查找 initial_key_to_highlight 所在的索引和页面
for i in range(all_enabled_themes.size()):
if all_enabled_themes[i].name == initial_key_to_highlight:
current_page_index = int(floor(float(i) / items_per_page)) # 设置目标页面
found_initial_key = true
print("Theme: 找到需要初始高亮的 Key '%s' (来自 get_main_data(\"题材\")) 在索引 %d, 目标页面 %d" % [initial_key_to_highlight, i, current_page_index])
break # 找到了,停止循环
if not found_initial_key:
print("Theme: 需要初始高亮的 Key '%s' (来自 get_main_data(\"题材\")) 未在启用的题材中找到。将显示第一页,并尝试高亮该页的第一个项目。" % initial_key_to_highlight)
# 保持 current_page_index 为 0
initial_key_to_highlight = "" # 清空,因为未找到,避免后续 _update_display 尝试高亮不存在的项
# else: initial_key_to_highlight 为空, current_page_index 保持为 0 (第一页)
# 确保页面索引有效 (与 gameplay.gd 一致)
if total_pages == 0:
current_page_index = 0
elif current_page_index >= total_pages: # 如果计算出的页面超出范围
current_page_index = total_pages - 1 if total_pages > 0 else 0
elif current_page_index < 0: # 理论上不应发生
current_page_index = 0
# 4. 触发显示更新
_update_display()
func _update_display():
"""【更新版】更新列表的可见性并填充当前页面的内容。
"""
# --- 处理无数据情况 (与 gameplay.gd 一致) ---
if theme_list_nodes.is_empty():
print("Theme: 无可用的 List 节点。")
if title_label: title_label.text = "题材选择"
return
if total_pages == 0:
for list_node in theme_list_nodes: list_node.visible = false
print("Theme: 没有启用的题材选项可供显示。")
if title_label: title_label.text = "题材选择 (无可用)"
if previous_button: previous_button.disabled = true
if next_button: next_button.disabled = true
return
else:
if previous_button: previous_button.disabled = (total_pages <= 1)
if next_button: next_button.disabled = (total_pages <= 1)
# --- 更新页面索引 (循环) (与 gameplay.gd 一致) ---
if total_pages > 0 :
current_page_index = current_page_index % total_pages
if current_page_index < 0:
current_page_index += total_pages
else:
current_page_index = 0
# --- 更新标题 (与 gameplay.gd 一致) ---
if title_label:
var display_page_number = current_page_index + 1
title_label.text = "题材选择 %d/%d" % [display_page_number, total_pages]
# --- 更新 List 可见性 (与 gameplay.gd 一致) ---
for i in range(theme_list_nodes.size()):
theme_list_nodes[i].visible = (i == current_page_index)
# --- 填充可见列表并查找要高亮的按钮 (与 gameplay.gd 类似) ---
var button_to_highlight: Button = null
var first_enabled_button_on_page: Button = null
if current_page_index < option_nodes_per_list.size():
var current_options = option_nodes_per_list[current_page_index]
var start_index = current_page_index * items_per_page
for i in range(current_options.size()):
var option_button = current_options[i]
var item_index = start_index + i
# --- 断开旧信号 ---
if option_button.pressed.is_connected(_on_option_selected):
option_button.pressed.disconnect(_on_option_selected)
if item_index < all_enabled_themes.size():
var theme_entry = all_enabled_themes[item_index]
var theme_name = theme_entry.name
var theme_data = theme_entry.data # 包含 Cost, Experience, Popularity 等
var row = option_button.get_node_or_null("Row")
if row:
# 填充标签文本
var title_label_in_row = row.get_node_or_null("Title")
var exp_label = row.get_node_or_null("Experience")
var pop_label = row.get_node_or_null("Popularity")
var cost_label = row.get_node_or_null("Cost") if row.has_node("Cost") else row.get_node_or_null("cost")
if title_label_in_row: title_label_in_row.text = theme_name
# 【修改】设置经验值,显示为整数
if exp_label:
var experience_value = theme_data.get("Experience", 0.0)
exp_label.text = str(int(experience_value))
# 【保持】设置流行度,保持原始字符串显示
if pop_label:
var popularity_value = theme_data.get("Popularity", "未知")
pop_label.text = str(popularity_value)
# 【修改】设置成本,显示为整数
if cost_label:
var cost_value = theme_data.get("Cost", 0.0)
cost_label.text = str(int(cost_value))
option_button.visible = true
option_button.disabled = false
option_button.set_meta("theme_name", theme_name) # 存储题材名称
# --- 连接新信号 (使用 bind) ---
option_button.pressed.connect(_on_option_selected.bind(theme_name, option_button))
if first_enabled_button_on_page == null:
first_enabled_button_on_page = option_button
if not initial_key_to_highlight.is_empty() and theme_name == initial_key_to_highlight:
button_to_highlight = option_button
# print("Theme: 找到需要初始高亮的按钮: ", button_to_highlight.name) # Debug
else: # 未找到 Row 节点
push_warning("警告:在按钮 %s 中未找到 Row 节点" % option_button.get_path())
option_button.visible = false
option_button.disabled = true
else: # 此槽位没有对应的启用题材数据
option_button.visible = false
option_button.disabled = true
else:
printerr("错误:当前页面索引 %d 超出 option_nodes_per_list 的范围 (大小 %d)" % [current_page_index, option_nodes_per_list.size()])
# --- 设置初始高亮和焦点 (与 gameplay.gd 一致) ---
if button_to_highlight:
_set_initial_highlight(button_to_highlight)
elif first_enabled_button_on_page:
print("Theme: 初始 Key '%s' 不在本页或为空。高亮本页第一个按钮: %s" % [initial_key_to_highlight, first_enabled_button_on_page.name])
_set_initial_highlight(first_enabled_button_on_page)
else:
print("Theme: 本页没有可供高亮的按钮。")
currently_selected_button = null
selected_theme_key = ""
# (Helper) 设置初始高亮和焦点 (与 gameplay.gd 一致)
func _set_initial_highlight(button_to_highlight: Button):
if not is_instance_valid(button_to_highlight):
printerr("错误:传递给 _set_initial_highlight 的按钮无效。")
currently_selected_button = null
selected_theme_key = ""
return
# print("Theme: 设置高亮按钮: ", button_to_highlight.name) # Debug
currently_selected_button = button_to_highlight
# --- 修改元数据键名 ---
selected_theme_key = button_to_highlight.get_meta("theme_name", "") # 同时设置 Key
# print("Theme: currently_selected_button 设置为: ", currently_selected_button.name) # Debug
# print("Theme: selected_theme_key 设置为: '", selected_theme_key, "'") # Debug
if currently_selected_button and currently_selected_button.is_inside_tree():
# print("Theme: 尝试为按钮 call_deferred('grab_focus'): ", currently_selected_button.name) # Debug
currently_selected_button.call_deferred("grab_focus")
else:
print("Theme: 按钮无效或不在场景树中,无法获取焦点。")
# --- Signal Handlers ---
# (与 gameplay.gd 一致)
func _on_previous_pressed():
if total_pages > 1:
current_page_index -= 1
_update_display()
# (与 gameplay.gd 一致)
func _on_next_pressed():
if total_pages > 1:
current_page_index += 1
_update_display()
# 【修改】处理选项按钮按下事件 (与 gameplay.gd 一致)
func _on_option_selected(theme_name: String, button_node: Button):
"""当选项按钮被按下时调用。"""
if not is_instance_valid(button_node):
# --- 修改错误信息中的 Key 类型 ---
printerr("错误:在 _on_option_selected 中收到无效按钮节点Key: " + theme_name)
return
if button_node == currently_selected_button:
# --- 双击 (或点击已选中的按钮) ---
print("Theme: 检测到双击或确认点击: ", theme_name)
_confirm_and_close(theme_name) # 确认此选择
else:
# --- 首次点击或点击不同按钮 ---
print("Theme: 选中 (高亮) 题材: ", theme_name)
currently_selected_button = button_node
selected_theme_key = theme_name # 更新 Key
# 更新视觉焦点
button_node.grab_focus()
# 【修改】确认选择并关闭弹窗的逻辑 (与 gameplay.gd 一致)
func _confirm_and_close(key_to_confirm: String):
"""更新父节点并关闭此弹窗。"""
if key_to_confirm.is_empty():
printerr("错误:尝试确认一个空的题材 Key。")
return
print("Theme: 确认选择: ", key_to_confirm)
set_main_data("题材", key_to_confirm)
go_next()

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
[gd_resource type="Theme" load_steps=5 format=3 uid="uid://r5e0kacnb3xf"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kemom"]
bg_color = Color(0.513883, 0.609153, 0.731076, 1)
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ao15e"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_kemom"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_mp8x4"]
bg_color = Color(0.513883, 0.609153, 0.731076, 1)
[resource]
Button/styles/focus = SubResource("StyleBoxFlat_kemom")
Button/styles/hover = SubResource("StyleBoxEmpty_ao15e")
Button/styles/normal = SubResource("StyleBoxEmpty_kemom")
Button/styles/pressed = SubResource("StyleBoxFlat_mp8x4")

View File

@ -0,0 +1,232 @@
extends UIPage
# --- UI 节点引用 ---
@onready var title_label: Label = $BG/Title/Title
@onready var npc_name_label: Label = $BG/Option/NPC_Info_1/Name
@onready var npc_staff_type_label: Label = $BG/Option/NPC_Info_1/Staff # 通常显示 "员工"
@onready var npc_job_label: Label = $BG/Option/NPC_Info_2/Info_List_0/Job/Title
@onready var npc_level_label: Label = $BG/Option/NPC_Info_2/Info_List_0/Job/Level
@onready var npc_icon_rect: TextureRect = $BG/Option/NPC_Info_2/Info_List_0/Icon
@onready var stat_design_label: Label = $BG/Option/NPC_Info_2/Info_List_1/Info_1/info
@onready var stat_code_label: Label = $BG/Option/NPC_Info_2/Info_List_1/Info_2/info
@onready var stat_art_label: Label = $BG/Option/NPC_Info_2/Info_List_1/Info_3/info
@onready var stat_audio_label: Label = $BG/Option/NPC_Info_2/Info_List_1/Info_4/info
@onready var cost_label: Label = $BG/Option/NPC_Info_2/Info_List_3/info_4/info
@onready var power_bar_segments_container: HBoxContainer = $BG/Option/NPC_Info_2/Info_List_2
var _power_bar_segments: Array[ColorRect] = [] # 用于存储体力条的各个片段
@onready var prev_button: Button = $BG/Title/previous/Button
@onready var next_button: Button = $BG/Title/next/Button
@onready var confirm_button: Button = $BG/Confirm
# --- 内部状态 ---
var _all_npcs_data: Dictionary = {} # 存储从 GameState 加载的所有NPC原始数据
var enabled_npc_keys: Array[String] = [] # 存储所有当前可选的NPC的键 (名字)
var current_npc_index: int = -1 # 当前在 enabled_npc_keys 数组中选中的NPC的索引
# --- 常量 ---
const NPC_ICON_BASE_PATH = "res://Entity/NPC/" # NPC 图标的基础路径 (如果后续有 Icon 字段)
const DEFAULT_NPC_ICON_PATH = "res://Entity/NPC/NPC_1_UI.png" # 默认/占位NPC图标
const POWER_CURRENT_COLOR = Color.GREEN_YELLOW # 当前体力颜色
const POWER_MAX_POTENTIAL_COLOR = Color(0.2, 0.4, 0.2, 0.8) # 最大潜力槽颜色 (稍暗的绿色)
const POWER_DISABLED_COLOR = Color(0.1, 0.1, 0.1, 0.3) # 体力条禁用/未达到部分的颜色
func _ready() -> void:
super._ready()
var node_name = str(self.name)
add_to_group(node_name) # 方便父节点或其他系统查找
# 收集体力条的片段
for child in power_bar_segments_container.get_children():
if child is ColorRect:
_power_bar_segments.append(child)
if _power_bar_segments.size() != 20:
printerr(node_name + ": Expected 20 power bar segments, found " + str(_power_bar_segments.size()))
# 连接信号
if prev_button and not prev_button.pressed.is_connected(_on_previous_pressed):
prev_button.pressed.connect(_on_previous_pressed)
if next_button and not next_button.pressed.is_connected(_on_next_pressed):
next_button.pressed.connect(_on_next_pressed)
if confirm_button and not confirm_button.pressed.is_connected(_on_confirm_pressed):
confirm_button.pressed.connect(_on_confirm_pressed)
if not is_connected("visibility_changed",Callable(self,"_on_visibility_changed")):
visibility_changed.connect(Callable(self,"_on_visibility_changed"))
# 如果节点在 _ready 时就可见,则立即刷新显示
if is_visible_in_tree():
reset_display()
func _on_visibility_changed() -> void:
if is_visible_in_tree():
reset_display()
# 重置显示状态,在窗口可见时调用
func reset_display() -> void:
print(str(self.name) + ": Resetting display state.")
_load_and_filter_npcs()
if enabled_npc_keys.is_empty():
current_npc_index = -1
print(str(self.name) + ": No enabled NPCs found.")
else:
current_npc_index = 0 # 默认显示第一个可用的NPC
print(str(self.name) + ": Found %d enabled NPCs. Defaulting to index 0." % enabled_npc_keys.size())
_display_current_npc()
# 从 GameState 加载NPC数据并筛选出可用的负责人
func _load_and_filter_npcs() -> void:
_all_npcs_data.clear()
enabled_npc_keys.clear()
if not GameState:
printerr(str(self.name) + ": GameState not available in _load_and_filter_npcs.")
return
var raw_npcs_data = GameState.get_value("npcs", {})
if raw_npcs_data.is_empty():
print(str(self.name) + ": 'npcs' data in GameState is empty or not found.")
return
_all_npcs_data = raw_npcs_data.duplicate(true)
for npc_key in _all_npcs_data:
var npc_info = _all_npcs_data[npc_key]
if typeof(npc_info) == TYPE_DICTIONARY and npc_info.get("type") == "员工":
enabled_npc_keys.append(npc_key)
# 可选:对 enabled_npc_keys进行排序以确保显示顺序一致
# enabled_npc_keys.sort()
print(str(self.name) + ": Loaded and filtered NPCs. Enabled count: " + str(enabled_npc_keys.size()))
# 根据 current_npc_index 更新UI显示
func _display_current_npc() -> void:
if current_npc_index < 0 or current_npc_index >= enabled_npc_keys.size():
# --- 处理没有可用NPC或索引无效的情况 ---
title_label.text = "策划案负责人"
npc_name_label.text = "无可用负责人"
npc_staff_type_label.text = "-" # 通常显示 "员工"
npc_job_label.text = "-"
npc_level_label.text = "-"
npc_icon_rect.texture = null # 或者一个“无”的占位图
stat_design_label.text = "-"
stat_code_label.text = "-"
stat_art_label.text = "-"
stat_audio_label.text = "-"
cost_label.text = "-"
# 隐藏所有体力条片段
for segment in _power_bar_segments:
segment.visible = false
if prev_button: prev_button.disabled = true
if next_button: next_button.disabled = true
if confirm_button: confirm_button.disabled = true
return
# --- 如果有有效NPC ---
if prev_button: prev_button.disabled = enabled_npc_keys.size() <= 1
if next_button: next_button.disabled = enabled_npc_keys.size() <= 1
if confirm_button: confirm_button.disabled = false
var display_index = current_npc_index + 1
var total_count = enabled_npc_keys.size()
title_label.text = "策划案负责人 %d/%d" % [display_index, total_count]
var npc_key = enabled_npc_keys[current_npc_index]
if not _all_npcs_data.has(npc_key):
printerr(str(self.name) + ": Current NPC key '%s' not found in cached _all_npcs_data!" % npc_key)
# 显示错误状态,可以做得更友好
npc_name_label.text = "错误:数据丢失"
# 隐藏所有体力条片段
for segment in _power_bar_segments:
segment.visible = false
return
var npc_info = _all_npcs_data[npc_key]
# 更新基本信息
npc_name_label.text = npc_key
npc_staff_type_label.text = npc_info.get("type", "员工") # 根据筛选,这里应该是"员工"
npc_job_label.text = npc_info.get("title", "-")
npc_level_label.text = "Lv" + str(int(npc_info.get("level", 1)))
# 更新图标 (后续会从npc_info.Icon获取)
var icon_name_from_data = npc_info.get("Icon", "") # 假设后续会有这个字段
if not icon_name_from_data.is_empty():
var dynamic_icon_path = NPC_ICON_BASE_PATH + icon_name_from_data + ".png" # 假设文件名规则
if ResourceLoader.exists(dynamic_icon_path):
npc_icon_rect.texture = ResourceLoader.load(dynamic_icon_path)
else:
printerr(str(self.name) + ": Failed to load NPC icon: " + dynamic_icon_path + ". Falling back to default.")
npc_icon_rect.texture = ResourceLoader.load(DEFAULT_NPC_ICON_PATH)
else:
# print(str(self.name) + ": NPC icon name is empty for key '%s'. Using default." % npc_key)
npc_icon_rect.texture = ResourceLoader.load(DEFAULT_NPC_ICON_PATH)
# 更新能力值 (确保转换为整数显示)
stat_design_label.text = str(int(npc_info.get("design", 0.0)))
stat_code_label.text = str(int(npc_info.get("code", 0.0)))
stat_art_label.text = str(int(npc_info.get("art", 0.0)))
stat_audio_label.text = str(int(npc_info.get("audio", 0.0)))
# 更新费用
cost_label.text = str(int(npc_info.get("outsourcing", 0.0)))
# --- 更新体力条 (包含 visible 控制) ---
var power = float(npc_info.get("power", 0.0))
var power_max = float(npc_info.get("power_max", 0.0))
if power_max <= 0: # 防止除零错误或无效数据
for segment in _power_bar_segments:
segment.visible = false # 如果最大体力为0或无效所有格段都不可见
else:
var total_visible_segments = floori(power_max / 10.0) # 根据 power_max 计算总共应显示的格数
var current_power_segments = floori(power / 10.0) # 当前体力对应的格数
# 确保 current_power_segments 不会超过 total_visible_segments (逻辑上 power 不应大于 power_max)
current_power_segments = mini(current_power_segments, total_visible_segments)
for i in range(_power_bar_segments.size()): # 遍历所有20个可能的格段
var segment = _power_bar_segments[i]
if i < total_visible_segments:
segment.visible = true # 此格段在NPC的最大体力范围内设为可见
if i < current_power_segments:
segment.color = POWER_CURRENT_COLOR # 当前体力部分
else:
segment.color = POWER_MAX_POTENTIAL_COLOR # 最大潜力中未充满的部分
else:
segment.visible = false # 此格段超出NPC的最大体力范围设为不可见
func _on_previous_pressed() -> void:
if enabled_npc_keys.size() > 1:
current_npc_index -= 1
if current_npc_index < 0:
current_npc_index = enabled_npc_keys.size() - 1
_display_current_npc()
func _on_next_pressed() -> void:
if enabled_npc_keys.size() > 1:
current_npc_index += 1
if current_npc_index >= enabled_npc_keys.size():
current_npc_index = 0
_display_current_npc()
func _on_confirm_pressed() -> void:
if current_npc_index != -1 and current_npc_index < enabled_npc_keys.size():
var selected_npc_key = enabled_npc_keys[current_npc_index]
#set_main_data("关键环节负责人", key_to_confirm)
go_next()

View File

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

View File

@ -0,0 +1,640 @@
[gd_scene load_steps=30 format=3 uid="uid://b5nxlnb6xr2oc"]
[ext_resource type="Script" uid="uid://1emqepctick7" path="res://UI/popup/task_development/npc_select_1/npc_select_1.gd" id="1_sgs0r"]
[ext_resource type="Texture2D" uid="uid://pucudatqrcrq" path="res://UI/popup/popup_bg_1.png" id="2_oyv27"]
[ext_resource type="Texture2D" uid="uid://dgcugleiv7sfw" path="res://UI/popup/task_development/arrow.png" id="3_qga8r"]
[ext_resource type="FontFile" uid="uid://egugs822n8gr" path="res://UI/font/AlimamaFangYuanTiVF-Thin.ttf" id="4_mk7go"]
[ext_resource type="Texture2D" uid="uid://hdidwixuokxn" path="res://Entity/NPC/npc_ability_icon.png" id="5_8ts75"]
[ext_resource type="LabelSettings" uid="uid://qcn0aduvrgvw" path="res://UI/tres/task_development_platform_label_settings.tres" id="6_3iojh"]
[ext_resource type="Theme" uid="uid://bau80ps6kx783" path="res://UI/tres/Bottom_Info_button_theme.tres" id="7_mchvh"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_chj15"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_c3aay"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_yswoy"]
[sub_resource type="Animation" id="Animation_bu5lv"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Title/previous:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(9, 7)]
}
[sub_resource type="Animation" id="Animation_6a8uh"]
resource_name = "base"
length = 0.6
loop_mode = 1
step = 0.2
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Title/previous:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 1,
"values": [Vector2(16, 7), Vector2(12, 7), Vector2(9, 7), Vector2(16, 7)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_mue51"]
_data = {
&"RESET": SubResource("Animation_bu5lv"),
&"base": SubResource("Animation_6a8uh")
}
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_t3kh8"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_xk4rn"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_xp2hk"]
[sub_resource type="Animation" id="Animation_wpgbx"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(461, 7)]
}
[sub_resource type="Animation" id="Animation_mue51"]
resource_name = "base"
length = 0.6
loop_mode = 1
step = 0.2
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 1,
"values": [Vector2(455, 7), Vector2(458, 7), Vector2(461, 7), Vector2(455, 7)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_r6b7x"]
_data = {
&"RESET": SubResource("Animation_wpgbx"),
&"base": SubResource("Animation_mue51")
}
[sub_resource type="LabelSettings" id="LabelSettings_w5gvb"]
font = ExtResource("4_mk7go")
font_size = 23
font_color = Color(0, 0, 0, 1)
[sub_resource type="LabelSettings" id="LabelSettings_kgbbm"]
font = ExtResource("4_mk7go")
font_size = 21
font_color = Color(0, 0, 0, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bktga"]
bg_color = Color(0.913725, 0.913725, 0.913725, 0.913725)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_x70d5"]
bg_color = Color(0.913725, 0.913725, 0.913725, 0.913725)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lnvfu"]
bg_color = Color(0.913725, 0.913725, 0.913725, 0.913725)
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_7vjot"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dm4ce"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hgp08"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_v40qy"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_cfg1r"]
[node name="npc_select_1" 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_sgs0r")
[node name="BG" type="NinePatchRect" parent="."]
custom_minimum_size = Vector2(500, 350)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -250.0
offset_top = -175.0
offset_right = 250.0
offset_bottom = 175.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("2_oyv27")
region_rect = Rect2(1, 35, 86, 53)
patch_margin_left = 8
patch_margin_top = 35
patch_margin_right = 8
patch_margin_bottom = 32
[node name="Title" type="NinePatchRect" parent="BG"]
custom_minimum_size = Vector2(500, 0)
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -250.0
offset_right = 250.0
offset_bottom = 44.0
grow_horizontal = 2
texture = ExtResource("2_oyv27")
region_rect = Rect2(1, 27, 86, 8)
patch_margin_left = 8
patch_margin_top = 7
patch_margin_right = 8
patch_margin_bottom = 6
[node name="Title" type="Label" parent="BG/Title"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -34.5
offset_top = -18.0
offset_right = 34.5
offset_bottom = 18.0
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 27
text = "策划案负责人"
vertical_alignment = 1
[node name="previous" type="NinePatchRect" parent="BG/Title"]
layout_mode = 1
anchors_preset = 4
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 9.0
offset_top = -15.0
offset_right = 39.0
offset_bottom = 15.0
grow_vertical = 2
texture = ExtResource("3_qga8r")
region_rect = Rect2(0, 0, 6, 9)
[node name="Button" type="Button" parent="BG/Title/previous"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_chj15")
theme_override_styles/hover = SubResource("StyleBoxEmpty_c3aay")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_yswoy")
flat = true
[node name="AnimationPlayer" type="AnimationPlayer" parent="BG/Title/previous"]
root_node = NodePath("../../..")
libraries = {
&"": SubResource("AnimationLibrary_mue51")
}
autoplay = "base"
[node name="next" type="NinePatchRect" parent="BG/Title"]
layout_mode = 1
anchors_preset = 6
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -39.0
offset_top = -15.0
offset_right = -9.0
offset_bottom = 15.0
grow_horizontal = 0
grow_vertical = 2
texture = ExtResource("3_qga8r")
region_rect = Rect2(6, 0, 6, 9)
[node name="Button" type="Button" parent="BG/Title/next"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_t3kh8")
theme_override_styles/hover = SubResource("StyleBoxEmpty_xk4rn")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_xp2hk")
flat = true
[node name="AnimationPlayer" type="AnimationPlayer" parent="BG/Title/next"]
libraries = {
&"": SubResource("AnimationLibrary_r6b7x")
}
autoplay = "base"
[node name="Option" type="Control" parent="BG"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="NPC_Info_1" type="HBoxContainer" parent="BG/Option"]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -170.0
offset_top = 47.0
offset_right = 202.0
offset_bottom = 87.0
grow_horizontal = 2
theme_override_constants/separation = 160
[node name="Name" type="Label" parent="BG/Option/NPC_Info_1"]
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 24
text = "米子一"
label_settings = SubResource("LabelSettings_w5gvb")
horizontal_alignment = 1
[node name="Staff" type="Label" parent="BG/Option/NPC_Info_1"]
custom_minimum_size = Vector2(140, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_fonts/font = ExtResource("4_mk7go")
theme_override_font_sizes/font_size = 20
text = "员工"
horizontal_alignment = 2
[node name="NPC_Info_2" type="NinePatchRect" parent="BG/Option"]
custom_minimum_size = Vector2(440, 200)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -220.0
offset_top = -92.0
offset_right = 220.0
offset_bottom = 130.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("2_oyv27")
region_rect = Rect2(92, 38, 76, 34)
patch_margin_left = 2
patch_margin_top = 33
patch_margin_right = 2
patch_margin_bottom = 33
[node name="Info_List_0" type="VBoxContainer" parent="BG/Option/NPC_Info_2"]
custom_minimum_size = Vector2(0, 95)
layout_mode = 0
offset_left = 26.0
offset_top = 23.0
offset_right = 185.0
offset_bottom = 147.0
theme_override_constants/separation = 14
[node name="Job" type="HBoxContainer" parent="BG/Option/NPC_Info_2/Info_List_0"]
custom_minimum_size = Vector2(180, 0)
layout_mode = 2
theme_override_constants/separation = 32
[node name="Title" type="Label" parent="BG/Option/NPC_Info_2/Info_List_0/Job"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "产品总监"
label_settings = SubResource("LabelSettings_kgbbm")
[node name="Level" type="Label" parent="BG/Option/NPC_Info_2/Info_List_0/Job"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
text = "Lv1"
label_settings = SubResource("LabelSettings_kgbbm")
horizontal_alignment = 2
[node name="Icon" type="TextureRect" parent="BG/Option/NPC_Info_2/Info_List_0"]
custom_minimum_size = Vector2(95, 95)
layout_mode = 2
stretch_mode = 5
[node name="Info_List_1" type="GridContainer" parent="BG/Option/NPC_Info_2"]
layout_mode = 1
anchors_preset = 6
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -204.0
offset_top = -87.0
offset_right = -8.0
offset_bottom = 46.0
grow_horizontal = 0
grow_vertical = 2
theme_override_constants/v_separation = 7
[node name="Info_1" type="HBoxContainer" parent="BG/Option/NPC_Info_2/Info_List_1"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="icon" type="NinePatchRect" parent="BG/Option/NPC_Info_2/Info_List_1/Info_1"]
custom_minimum_size = Vector2(28, 28)
layout_mode = 2
texture = ExtResource("5_8ts75")
region_rect = Rect2(38, 30, 20, 15)
[node name="title" type="Label" parent="BG/Option/NPC_Info_2/Info_List_1/Info_1"]
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
theme_override_fonts/font = ExtResource("4_mk7go")
theme_override_styles/normal = SubResource("StyleBoxFlat_bktga")
text = "策划"
label_settings = ExtResource("6_3iojh")
horizontal_alignment = 1
[node name="info" type="Label" parent="BG/Option/NPC_Info_2/Info_List_1/Info_1"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "80"
label_settings = ExtResource("6_3iojh")
horizontal_alignment = 1
[node name="Info_2" type="HBoxContainer" parent="BG/Option/NPC_Info_2/Info_List_1"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="icon" type="NinePatchRect" parent="BG/Option/NPC_Info_2/Info_List_1/Info_2"]
custom_minimum_size = Vector2(28, 28)
layout_mode = 2
texture = ExtResource("5_8ts75")
region_rect = Rect2(12, 31, 17, 16)
[node name="title" type="Label" parent="BG/Option/NPC_Info_2/Info_List_1/Info_2"]
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
theme_override_fonts/font = ExtResource("4_mk7go")
theme_override_styles/normal = SubResource("StyleBoxFlat_bktga")
text = "程序"
label_settings = ExtResource("6_3iojh")
horizontal_alignment = 1
[node name="info" type="Label" parent="BG/Option/NPC_Info_2/Info_List_1/Info_2"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "80"
label_settings = ExtResource("6_3iojh")
horizontal_alignment = 1
[node name="Info_3" type="HBoxContainer" parent="BG/Option/NPC_Info_2/Info_List_1"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="icon" type="NinePatchRect" parent="BG/Option/NPC_Info_2/Info_List_1/Info_3"]
custom_minimum_size = Vector2(28, 28)
layout_mode = 2
texture = ExtResource("5_8ts75")
region_rect = Rect2(63, 29, 21, 16)
[node name="title" type="Label" parent="BG/Option/NPC_Info_2/Info_List_1/Info_3"]
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
theme_override_fonts/font = ExtResource("4_mk7go")
theme_override_styles/normal = SubResource("StyleBoxFlat_bktga")
text = "美术"
label_settings = ExtResource("6_3iojh")
horizontal_alignment = 1
[node name="info" type="Label" parent="BG/Option/NPC_Info_2/Info_List_1/Info_3"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "80"
label_settings = ExtResource("6_3iojh")
horizontal_alignment = 1
[node name="Info_4" type="HBoxContainer" parent="BG/Option/NPC_Info_2/Info_List_1"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="icon" type="NinePatchRect" parent="BG/Option/NPC_Info_2/Info_List_1/Info_4"]
custom_minimum_size = Vector2(28, 28)
layout_mode = 2
texture = ExtResource("5_8ts75")
region_rect = Rect2(92, 29, 22, 16)
[node name="title" type="Label" parent="BG/Option/NPC_Info_2/Info_List_1/Info_4"]
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
theme_override_fonts/font = ExtResource("4_mk7go")
theme_override_styles/normal = SubResource("StyleBoxFlat_bktga")
text = "音频"
label_settings = ExtResource("6_3iojh")
horizontal_alignment = 1
[node name="info" type="Label" parent="BG/Option/NPC_Info_2/Info_List_1/Info_4"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "80"
label_settings = ExtResource("6_3iojh")
horizontal_alignment = 1
[node name="Info_List_2" type="HBoxContainer" parent="BG/Option/NPC_Info_2"]
layout_mode = 1
offset_left = 49.0
offset_top = 164.0
offset_right = 169.0
offset_bottom = 164.0
theme_override_constants/separation = 0
alignment = 1
[node name="Segment_1" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 10)
layout_mode = 2
[node name="Segment_2" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_3" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_4" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_5" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_6" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_7" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_8" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_9" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_10" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_11" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_12" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_13" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_14" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_15" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_16" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_17" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_18" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_19" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_20" type="ColorRect" parent="BG/Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Info_List_3" type="HBoxContainer" parent="BG/Option/NPC_Info_2"]
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -191.5
offset_top = -38.0
offset_right = 191.5
offset_bottom = -10.0
grow_horizontal = 2
grow_vertical = 0
[node name="info_4" type="HBoxContainer" parent="BG/Option/NPC_Info_2/Info_List_3"]
layout_mode = 2
[node name="title" type="Label" parent="BG/Option/NPC_Info_2/Info_List_3/info_4"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
theme_override_fonts/font = ExtResource("4_mk7go")
theme_override_styles/normal = SubResource("StyleBoxFlat_x70d5")
text = "费用"
label_settings = ExtResource("6_3iojh")
horizontal_alignment = 1
[node name="info" type="Label" parent="BG/Option/NPC_Info_2/Info_List_3/info_4"]
custom_minimum_size = Vector2(70, 0)
layout_mode = 2
text = "500"
label_settings = ExtResource("6_3iojh")
horizontal_alignment = 1
[node name="info_5" type="HBoxContainer" parent="BG/Option/NPC_Info_2/Info_List_3"]
visible = false
layout_mode = 2
[node name="title" type="Label" parent="BG/Option/NPC_Info_2/Info_List_3/info_5"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_lnvfu")
text = "开发次数"
label_settings = ExtResource("6_3iojh")
horizontal_alignment = 1
[node name="info" type="Label" parent="BG/Option/NPC_Info_2/Info_List_3/info_5"]
custom_minimum_size = Vector2(30, 0)
layout_mode = 2
text = "0"
label_settings = ExtResource("6_3iojh")
horizontal_alignment = 2
[node name="unit" type="Label" parent="BG/Option/NPC_Info_2/Info_List_3/info_5"]
layout_mode = 2
text = "次"
label_settings = ExtResource("6_3iojh")
horizontal_alignment = 2
[node name="Confirm" type="Button" parent="BG"]
custom_minimum_size = Vector2(70, 0)
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -35.0
offset_top = -43.0
offset_right = 35.0
grow_horizontal = 2
grow_vertical = 0
theme = ExtResource("7_mchvh")
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 23
theme_override_styles/focus = SubResource("StyleBoxEmpty_7vjot")
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_dm4ce")
theme_override_styles/hover = SubResource("StyleBoxFlat_hgp08")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_v40qy")
theme_override_styles/normal = SubResource("StyleBoxEmpty_cfg1r")
text = "确定"

View File

@ -1,5 +1,4 @@
# product_focus.gd extends UIPage # 继承自我们之前创建的 UIPage
extends NinePatchRect
#-- 配置变量 (可在 Inspector 中调整) -- #-- 配置变量 (可在 Inspector 中调整) --
@export var min_value_per_category: int = 0 # 每个类别的最小值 @export var min_value_per_category: int = 0 # 每个类别的最小值
@ -8,24 +7,25 @@ extends NinePatchRect
@export var inactive_color: Color = Color("acacac") # 非激活格子的颜色 @export var inactive_color: Color = Color("acacac") # 非激活格子的颜色
#-- 内部变量 -- #-- 内部变量 --
var total_points_from_state: int = 0 # 从 GameState 获取的总点数 var total_points_from_state: int = 0 # 从get_main_data获取的总点数
var remaining_points: int = 0 var remaining_points: int = 0
# 存储每个类别 (info_1 到 info_6) 当前分配的点数 # 存储每个类别 (info_1 到 info_6) 当前分配的点数
var category_points: Array[int] = [] var category_points: Array[int] = []
# 存储每个类别在 GameState 中的键名 ("视觉表现", "游戏深度", ...) # 存储每个类别在 get_main_data 中的键名 ("视觉表现", "游戏深度", ...)
var category_keys: Array[String] = [] var category_keys: Array[String] = []
# 存储每个类别 UI 元素的引用 # 存储每个类别 UI 元素的引用
var category_controls: Array[Dictionary] = [] var category_controls: Array[Dictionary] = []
#-- 节点引用 -- #-- 节点引用 --
@onready var points_label: Label = $Part_2/HBoxContainer/Points @onready var points_label: Label = $BG/Part_2/HBoxContainer/Points
@onready var grid_container: GridContainer = $Part_3/GridContainer @onready var grid_container: GridContainer = $BG/Part_3/GridContainer
@onready var gameplay_info_label: Label = $Part_4/gameplay/info @onready var gameplay_info_label: Label = $BG/Part_4/gameplay/info
@onready var theme_info_label: Label = $Part_4/theme/info @onready var theme_info_label: Label = $BG/Part_4/theme/info
@onready var confirm_button: Button = $Confirm @onready var confirm_button: Button = $BG/Confirm
func _ready(): func _ready():
super._ready() # 调用父类的 _ready() (如果 UIPage 中有 _ready() 逻辑)
add_to_group(str(name)) # 方便父节点查找 add_to_group(str(name)) # 方便父节点查找
# --- 初始化控件引用和类别键名 --- # --- 初始化控件引用和类别键名 ---
@ -103,21 +103,14 @@ func reset_display():
return return
# 1. 从 GameState 获取 task_development 数据 # 1. 从 GameState 获取 task_development 数据
var task_dev_data: Dictionary = GameState.get_value("task_development", {}) #var task_dev_data: Dictionary = GameState.get_value("task_development", {})
var focus_data: Dictionary = {} # 初始化为空字典 var focus_data: Dictionary = get_main_data("产品侧重点",{})
if focus_data.is_empty():
if task_dev_data.is_empty(): push_warning("Product Focus: 'product_focus_points' not found in task_development data. Using defaults.")
push_warning("Product Focus: Failed to get 'task_development' data from GameState. Using defaults for focus points.")
total_points_from_state = 20 # 使用默认总点数 total_points_from_state = 20 # 使用默认总点数
else: else:
# 从 task_dev_data 中获取 product_focus_points # 从 focus_data 获取总数,如果不存在则使用默认值
focus_data = task_dev_data.get("product_focus_points", {}) total_points_from_state = focus_data.get("总数", 20)
if focus_data.is_empty():
push_warning("Product Focus: 'product_focus_points' not found in task_development data. Using defaults.")
total_points_from_state = 20 # 使用默认总点数
else:
# 从 focus_data 获取总数,如果不存在则使用默认值
total_points_from_state = focus_data.get("总数", 20)
# 2. 初始化本地 category_points (从 focus_data 加载) # 2. 初始化本地 category_points (从 focus_data 加载)
category_points.clear() category_points.clear()
@ -231,23 +224,8 @@ func _update_all_button_states():
# 从父节点加载 Part_4 的信息 (玩法和题材) # 从父节点加载 Part_4 的信息 (玩法和题材)
func _load_part4_info(): func _load_part4_info():
var parent = get_parent() gameplay_info_label.text = get_main_data("玩法", "未知")
# 检查父节点和 task_info 是否有效 theme_info_label.text = get_main_data("题材", "未知")
if not is_instance_valid(parent):
push_warning("Product Focus: Parent node or its 'task_info' metadata not available for Part 4.")
gameplay_info_label.text = "错误"
theme_info_label.text = "错误"
return
# 使用 get_meta 获取,避免直接访问可能不存在的属性
var task_info = parent.task_info # 提供默认值 null
if task_info is Dictionary:
gameplay_info_label.text = task_info.get("玩法", "未知")
theme_info_label.text = task_info.get("题材", "未知")
else:
push_warning("Product Focus: Parent's task_info metadata is not a Dictionary or is null.")
gameplay_info_label.text = "错误"
theme_info_label.text = "错误"
# --- 处理确定按钮的逻辑 --- # --- 处理确定按钮的逻辑 ---
func _on_confirm_pressed(): func _on_confirm_pressed():
@ -260,15 +238,6 @@ func _on_confirm_pressed():
for i in range(category_keys.size()): for i in range(category_keys.size()):
confirmed_focus_points[category_keys[i]] = category_points[i] confirmed_focus_points[category_keys[i]] = category_points[i]
confirmed_focus_points['总数'] = total_points_from_state
# 2. 获取父节点 (task_development) 并 传递数据 set_main_data("产品侧重点", confirmed_focus_points)
var parent_node = get_parent() go_next()
if parent_node and parent_node.has_method("update_task_options"):
# 通知父节点更新其内部的 task_info
parent_node.update_task_options({"产品侧重点": confirmed_focus_points})
else:
printerr("错误Product Focus: 父节点未找到或缺少 'update_task_options' 方法。")
# 3. 进入下一个节点
self.hide()
parent_node.show_dialogue("谁来写立项书呢?", "proposal")

View File

@ -1 +1 @@
uid://dfny4mxgw5yb6 uid://cvek2tasik7y1

View File

@ -1,17 +1,17 @@
[gd_scene load_steps=21 format=3 uid="uid://dpshanwm4o3by"] [gd_scene load_steps=21 format=3 uid="uid://chl131ysu15rd"]
[ext_resource type="Texture2D" uid="uid://pucudatqrcrq" path="res://UI/popup/popup_bg_1.png" id="1_kjf47"] [ext_resource type="Script" uid="uid://cvek2tasik7y1" path="res://UI/popup/task_development/product_focus/product_focus.gd" id="1_qmivp"]
[ext_resource type="Script" uid="uid://dfny4mxgw5yb6" path="res://UI/popup/task_development/product_focus/product_focus.gd" id="2_dt4oy"] [ext_resource type="Texture2D" uid="uid://pucudatqrcrq" path="res://UI/popup/popup_bg_1.png" id="2_hxsho"]
[ext_resource type="FontFile" uid="uid://bbr43ojt1sreu" path="res://UI/font/MinecraftStandard.otf" id="2_klru0"] [ext_resource type="FontFile" uid="uid://egugs822n8gr" path="res://UI/font/AlimamaFangYuanTiVF-Thin.ttf" id="3_ct65a"]
[ext_resource type="FontFile" uid="uid://egugs822n8gr" path="res://UI/font/AlimamaFangYuanTiVF-Thin.ttf" id="3_hxsho"] [ext_resource type="FontFile" uid="uid://bbr43ojt1sreu" path="res://UI/font/MinecraftStandard.otf" id="4_lha8x"]
[ext_resource type="LabelSettings" uid="uid://qcn0aduvrgvw" path="res://UI/tres/task_development_platform_label_settings.tres" id="3_kjf47"] [ext_resource type="LabelSettings" uid="uid://qcn0aduvrgvw" path="res://UI/tres/task_development_platform_label_settings.tres" id="5_33nyt"]
[ext_resource type="Theme" uid="uid://bau80ps6kx783" path="res://UI/tres/Bottom_Info_button_theme.tres" id="4_hxsho"] [ext_resource type="Texture2D" uid="uid://dgcugleiv7sfw" path="res://UI/popup/task_development/arrow.png" id="6_u37th"]
[ext_resource type="Texture2D" uid="uid://dgcugleiv7sfw" path="res://UI/popup/task_development/arrow.png" id="4_klru0"] [ext_resource type="Theme" uid="uid://bau80ps6kx783" path="res://UI/tres/Bottom_Info_button_theme.tres" id="7_wmlhw"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ct65a"] [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ct65a"]
[sub_resource type="LabelSettings" id="LabelSettings_lha8x"] [sub_resource type="LabelSettings" id="LabelSettings_lha8x"]
font = ExtResource("2_klru0") font = ExtResource("4_lha8x")
font_size = 14 font_size = 14
outline_size = 6 outline_size = 6
outline_color = Color(0, 0, 0, 1) outline_color = Color(0, 0, 0, 1)
@ -38,28 +38,37 @@ outline_color = Color(0, 0, 0, 1)
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_so1ex"] [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_so1ex"]
[node name="product_focus" type="NinePatchRect"] [node name="product_focus" 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_qmivp")
[node name="BG" type="NinePatchRect" parent="."]
custom_minimum_size = Vector2(500, 350) custom_minimum_size = Vector2(500, 350)
layout_mode = 1
anchors_preset = 8 anchors_preset = 8
anchor_left = 0.5 anchor_left = 0.5
anchor_top = 0.5 anchor_top = 0.5
anchor_right = 0.5 anchor_right = 0.5
anchor_bottom = 0.5 anchor_bottom = 0.5
offset_left = -250.0 offset_left = -250.0
offset_top = -175.0 offset_top = -200.0
offset_right = 250.0 offset_right = 250.0
offset_bottom = 175.0 offset_bottom = 150.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
texture = ExtResource("1_kjf47") texture = ExtResource("2_hxsho")
region_rect = Rect2(1, 35, 86, 53) region_rect = Rect2(1, 35, 86, 53)
patch_margin_left = 8 patch_margin_left = 8
patch_margin_top = 35 patch_margin_top = 35
patch_margin_right = 8 patch_margin_right = 8
patch_margin_bottom = 32 patch_margin_bottom = 32
script = ExtResource("2_dt4oy")
[node name="Part_1" type="NinePatchRect" parent="."] [node name="Part_1" type="NinePatchRect" parent="BG"]
custom_minimum_size = Vector2(500, 0) custom_minimum_size = Vector2(500, 0)
layout_mode = 1 layout_mode = 1
anchors_preset = 5 anchors_preset = 5
@ -69,14 +78,14 @@ offset_left = -250.0
offset_right = 250.0 offset_right = 250.0
offset_bottom = 44.0 offset_bottom = 44.0
grow_horizontal = 2 grow_horizontal = 2
texture = ExtResource("1_kjf47") texture = ExtResource("2_hxsho")
region_rect = Rect2(1, 27, 86, 8) region_rect = Rect2(1, 27, 86, 8)
patch_margin_left = 8 patch_margin_left = 8
patch_margin_top = 7 patch_margin_top = 7
patch_margin_right = 8 patch_margin_right = 8
patch_margin_bottom = 6 patch_margin_bottom = 6
[node name="Title" type="Label" parent="Part_1"] [node name="Title" type="Label" parent="BG/Part_1"]
layout_mode = 1 layout_mode = 1
anchors_preset = 8 anchors_preset = 8
anchor_left = 0.5 anchor_left = 0.5
@ -94,7 +103,7 @@ theme_override_font_sizes/font_size = 27
text = "侧重点" text = "侧重点"
vertical_alignment = 1 vertical_alignment = 1
[node name="Part_2" type="Control" parent="."] [node name="Part_2" type="Control" parent="BG"]
layout_mode = 1 layout_mode = 1
anchors_preset = 5 anchors_preset = 5
anchor_left = 0.5 anchor_left = 0.5
@ -105,7 +114,7 @@ offset_right = 20.0
offset_bottom = 53.0 offset_bottom = 53.0
grow_horizontal = 2 grow_horizontal = 2
[node name="HBoxContainer" type="HBoxContainer" parent="Part_2"] [node name="HBoxContainer" type="HBoxContainer" parent="BG/Part_2"]
layout_mode = 1 layout_mode = 1
anchors_preset = 8 anchors_preset = 8
anchor_left = 0.5 anchor_left = 0.5
@ -120,15 +129,15 @@ grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
alignment = 1 alignment = 1
[node name="Title" type="Label" parent="Part_2/HBoxContainer"] [node name="Title" type="Label" parent="BG/Part_2/HBoxContainer"]
layout_mode = 2 layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1) theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_fonts/font = ExtResource("3_hxsho") theme_override_fonts/font = ExtResource("3_ct65a")
theme_override_font_sizes/font_size = 24 theme_override_font_sizes/font_size = 24
text = "剩余可用点数" text = "剩余可用点数"
horizontal_alignment = 1 horizontal_alignment = 1
[node name="Points" type="Label" parent="Part_2/HBoxContainer"] [node name="Points" type="Label" parent="BG/Part_2/HBoxContainer"]
custom_minimum_size = Vector2(50, 0) custom_minimum_size = Vector2(50, 0)
layout_mode = 2 layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1) theme_override_colors/font_color = Color(0, 0, 0, 1)
@ -139,7 +148,7 @@ text = "2"
label_settings = SubResource("LabelSettings_lha8x") label_settings = SubResource("LabelSettings_lha8x")
horizontal_alignment = 1 horizontal_alignment = 1
[node name="Part_3" type="NinePatchRect" parent="."] [node name="Part_3" type="NinePatchRect" parent="BG"]
custom_minimum_size = Vector2(440, 200) custom_minimum_size = Vector2(440, 200)
layout_mode = 1 layout_mode = 1
anchors_preset = 8 anchors_preset = 8
@ -153,14 +162,14 @@ offset_right = 220.0
offset_bottom = 109.0 offset_bottom = 109.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
texture = ExtResource("1_kjf47") texture = ExtResource("2_hxsho")
region_rect = Rect2(92, 38, 76, 34) region_rect = Rect2(92, 38, 76, 34)
patch_margin_left = 2 patch_margin_left = 2
patch_margin_top = 33 patch_margin_top = 33
patch_margin_right = 2 patch_margin_right = 2
patch_margin_bottom = 33 patch_margin_bottom = 33
[node name="GridContainer" type="GridContainer" parent="Part_3"] [node name="GridContainer" type="GridContainer" parent="BG/Part_3"]
layout_mode = 1 layout_mode = 1
anchors_preset = 8 anchors_preset = 8
anchor_left = 0.5 anchor_left = 0.5
@ -177,25 +186,25 @@ theme_override_constants/h_separation = 40
theme_override_constants/v_separation = 10 theme_override_constants/v_separation = 10
columns = 2 columns = 2
[node name="info_1" type="VBoxContainer" parent="Part_3/GridContainer"] [node name="info_1" type="VBoxContainer" parent="BG/Part_3/GridContainer"]
layout_mode = 2 layout_mode = 2
[node name="Title" type="Label" parent="Part_3/GridContainer/info_1"] [node name="Title" type="Label" parent="BG/Part_3/GridContainer/info_1"]
layout_mode = 2 layout_mode = 2
text = "视觉表现" text = "视觉表现"
label_settings = ExtResource("3_kjf47") label_settings = ExtResource("5_33nyt")
[node name="HBoxContainer" type="HBoxContainer" parent="Part_3/GridContainer/info_1"] [node name="HBoxContainer" type="HBoxContainer" parent="BG/Part_3/GridContainer/info_1"]
layout_mode = 2 layout_mode = 2
[node name="Previous" type="NinePatchRect" parent="Part_3/GridContainer/info_1/HBoxContainer"] [node name="Previous" type="NinePatchRect" parent="BG/Part_3/GridContainer/info_1/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1) modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20) custom_minimum_size = Vector2(20, 20)
layout_mode = 2 layout_mode = 2
texture = ExtResource("4_klru0") texture = ExtResource("6_u37th")
region_rect = Rect2(0, 0, 6, 9) region_rect = Rect2(0, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_1/HBoxContainer/Previous"] [node name="Button" type="Button" parent="BG/Part_3/GridContainer/info_1/HBoxContainer/Previous"]
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@ -207,57 +216,57 @@ theme_override_styles/hover = SubResource("StyleBoxEmpty_hxsho")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_lha8x") theme_override_styles/pressed = SubResource("StyleBoxEmpty_lha8x")
flat = true flat = true
[node name="Segments" type="HBoxContainer" parent="Part_3/GridContainer/info_1/HBoxContainer"] [node name="Segments" type="HBoxContainer" parent="BG/Part_3/GridContainer/info_1/HBoxContainer"]
layout_mode = 2 layout_mode = 2
[node name="Segment_1" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"] [node name="Segment_1" type="ColorRect" parent="BG/Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_2" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"] [node name="Segment_2" type="ColorRect" parent="BG/Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_3" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"] [node name="Segment_3" type="ColorRect" parent="BG/Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_4" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"] [node name="Segment_4" type="ColorRect" parent="BG/Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_5" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"] [node name="Segment_5" type="ColorRect" parent="BG/Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_6" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"] [node name="Segment_6" type="ColorRect" parent="BG/Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_7" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"] [node name="Segment_7" type="ColorRect" parent="BG/Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_8" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"] [node name="Segment_8" type="ColorRect" parent="BG/Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_9" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"] [node name="Segment_9" type="ColorRect" parent="BG/Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_10" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"] [node name="Segment_10" type="ColorRect" parent="BG/Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Next" type="NinePatchRect" parent="Part_3/GridContainer/info_1/HBoxContainer"] [node name="Next" type="NinePatchRect" parent="BG/Part_3/GridContainer/info_1/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1) modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20) custom_minimum_size = Vector2(20, 20)
layout_mode = 2 layout_mode = 2
texture = ExtResource("4_klru0") texture = ExtResource("6_u37th")
region_rect = Rect2(6, 0, 6, 9) region_rect = Rect2(6, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_1/HBoxContainer/Next"] [node name="Button" type="Button" parent="BG/Part_3/GridContainer/info_1/HBoxContainer/Next"]
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@ -269,25 +278,25 @@ theme_override_styles/hover = SubResource("StyleBoxEmpty_u37th")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_wmlhw") theme_override_styles/pressed = SubResource("StyleBoxEmpty_wmlhw")
flat = true flat = true
[node name="info_2" type="VBoxContainer" parent="Part_3/GridContainer"] [node name="info_2" type="VBoxContainer" parent="BG/Part_3/GridContainer"]
layout_mode = 2 layout_mode = 2
[node name="Title" type="Label" parent="Part_3/GridContainer/info_2"] [node name="Title" type="Label" parent="BG/Part_3/GridContainer/info_2"]
layout_mode = 2 layout_mode = 2
text = "游戏深度" text = "游戏深度"
label_settings = ExtResource("3_kjf47") label_settings = ExtResource("5_33nyt")
[node name="HBoxContainer" type="HBoxContainer" parent="Part_3/GridContainer/info_2"] [node name="HBoxContainer" type="HBoxContainer" parent="BG/Part_3/GridContainer/info_2"]
layout_mode = 2 layout_mode = 2
[node name="Previous" type="NinePatchRect" parent="Part_3/GridContainer/info_2/HBoxContainer"] [node name="Previous" type="NinePatchRect" parent="BG/Part_3/GridContainer/info_2/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1) modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20) custom_minimum_size = Vector2(20, 20)
layout_mode = 2 layout_mode = 2
texture = ExtResource("4_klru0") texture = ExtResource("6_u37th")
region_rect = Rect2(0, 0, 6, 9) region_rect = Rect2(0, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_2/HBoxContainer/Previous"] [node name="Button" type="Button" parent="BG/Part_3/GridContainer/info_2/HBoxContainer/Previous"]
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@ -299,57 +308,57 @@ theme_override_styles/hover = SubResource("StyleBoxEmpty_hxsho")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_lha8x") theme_override_styles/pressed = SubResource("StyleBoxEmpty_lha8x")
flat = true flat = true
[node name="Segments" type="HBoxContainer" parent="Part_3/GridContainer/info_2/HBoxContainer"] [node name="Segments" type="HBoxContainer" parent="BG/Part_3/GridContainer/info_2/HBoxContainer"]
layout_mode = 2 layout_mode = 2
[node name="Segment_1" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"] [node name="Segment_1" type="ColorRect" parent="BG/Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_2" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"] [node name="Segment_2" type="ColorRect" parent="BG/Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_3" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"] [node name="Segment_3" type="ColorRect" parent="BG/Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_4" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"] [node name="Segment_4" type="ColorRect" parent="BG/Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_5" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"] [node name="Segment_5" type="ColorRect" parent="BG/Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_6" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"] [node name="Segment_6" type="ColorRect" parent="BG/Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_7" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"] [node name="Segment_7" type="ColorRect" parent="BG/Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_8" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"] [node name="Segment_8" type="ColorRect" parent="BG/Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_9" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"] [node name="Segment_9" type="ColorRect" parent="BG/Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_10" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"] [node name="Segment_10" type="ColorRect" parent="BG/Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Next" type="NinePatchRect" parent="Part_3/GridContainer/info_2/HBoxContainer"] [node name="Next" type="NinePatchRect" parent="BG/Part_3/GridContainer/info_2/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1) modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20) custom_minimum_size = Vector2(20, 20)
layout_mode = 2 layout_mode = 2
texture = ExtResource("4_klru0") texture = ExtResource("6_u37th")
region_rect = Rect2(6, 0, 6, 9) region_rect = Rect2(6, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_2/HBoxContainer/Next"] [node name="Button" type="Button" parent="BG/Part_3/GridContainer/info_2/HBoxContainer/Next"]
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@ -361,25 +370,25 @@ theme_override_styles/hover = SubResource("StyleBoxEmpty_u37th")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_wmlhw") theme_override_styles/pressed = SubResource("StyleBoxEmpty_wmlhw")
flat = true flat = true
[node name="info_3" type="VBoxContainer" parent="Part_3/GridContainer"] [node name="info_3" type="VBoxContainer" parent="BG/Part_3/GridContainer"]
layout_mode = 2 layout_mode = 2
[node name="Title" type="Label" parent="Part_3/GridContainer/info_3"] [node name="Title" type="Label" parent="BG/Part_3/GridContainer/info_3"]
layout_mode = 2 layout_mode = 2
text = "创新性" text = "创新性"
label_settings = ExtResource("3_kjf47") label_settings = ExtResource("5_33nyt")
[node name="HBoxContainer" type="HBoxContainer" parent="Part_3/GridContainer/info_3"] [node name="HBoxContainer" type="HBoxContainer" parent="BG/Part_3/GridContainer/info_3"]
layout_mode = 2 layout_mode = 2
[node name="Previous" type="NinePatchRect" parent="Part_3/GridContainer/info_3/HBoxContainer"] [node name="Previous" type="NinePatchRect" parent="BG/Part_3/GridContainer/info_3/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1) modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20) custom_minimum_size = Vector2(20, 20)
layout_mode = 2 layout_mode = 2
texture = ExtResource("4_klru0") texture = ExtResource("6_u37th")
region_rect = Rect2(0, 0, 6, 9) region_rect = Rect2(0, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_3/HBoxContainer/Previous"] [node name="Button" type="Button" parent="BG/Part_3/GridContainer/info_3/HBoxContainer/Previous"]
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@ -391,57 +400,57 @@ theme_override_styles/hover = SubResource("StyleBoxEmpty_hxsho")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_lha8x") theme_override_styles/pressed = SubResource("StyleBoxEmpty_lha8x")
flat = true flat = true
[node name="Segments" type="HBoxContainer" parent="Part_3/GridContainer/info_3/HBoxContainer"] [node name="Segments" type="HBoxContainer" parent="BG/Part_3/GridContainer/info_3/HBoxContainer"]
layout_mode = 2 layout_mode = 2
[node name="Segment_1" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"] [node name="Segment_1" type="ColorRect" parent="BG/Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_2" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"] [node name="Segment_2" type="ColorRect" parent="BG/Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_3" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"] [node name="Segment_3" type="ColorRect" parent="BG/Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_4" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"] [node name="Segment_4" type="ColorRect" parent="BG/Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_5" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"] [node name="Segment_5" type="ColorRect" parent="BG/Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_6" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"] [node name="Segment_6" type="ColorRect" parent="BG/Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_7" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"] [node name="Segment_7" type="ColorRect" parent="BG/Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_8" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"] [node name="Segment_8" type="ColorRect" parent="BG/Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_9" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"] [node name="Segment_9" type="ColorRect" parent="BG/Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_10" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"] [node name="Segment_10" type="ColorRect" parent="BG/Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Next" type="NinePatchRect" parent="Part_3/GridContainer/info_3/HBoxContainer"] [node name="Next" type="NinePatchRect" parent="BG/Part_3/GridContainer/info_3/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1) modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20) custom_minimum_size = Vector2(20, 20)
layout_mode = 2 layout_mode = 2
texture = ExtResource("4_klru0") texture = ExtResource("6_u37th")
region_rect = Rect2(6, 0, 6, 9) region_rect = Rect2(6, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_3/HBoxContainer/Next"] [node name="Button" type="Button" parent="BG/Part_3/GridContainer/info_3/HBoxContainer/Next"]
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@ -453,25 +462,25 @@ theme_override_styles/hover = SubResource("StyleBoxEmpty_u37th")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_wmlhw") theme_override_styles/pressed = SubResource("StyleBoxEmpty_wmlhw")
flat = true flat = true
[node name="info_4" type="VBoxContainer" parent="Part_3/GridContainer"] [node name="info_4" type="VBoxContainer" parent="BG/Part_3/GridContainer"]
layout_mode = 2 layout_mode = 2
[node name="Title" type="Label" parent="Part_3/GridContainer/info_4"] [node name="Title" type="Label" parent="BG/Part_3/GridContainer/info_4"]
layout_mode = 2 layout_mode = 2
text = "易玩性" text = "易玩性"
label_settings = ExtResource("3_kjf47") label_settings = ExtResource("5_33nyt")
[node name="HBoxContainer" type="HBoxContainer" parent="Part_3/GridContainer/info_4"] [node name="HBoxContainer" type="HBoxContainer" parent="BG/Part_3/GridContainer/info_4"]
layout_mode = 2 layout_mode = 2
[node name="Previous" type="NinePatchRect" parent="Part_3/GridContainer/info_4/HBoxContainer"] [node name="Previous" type="NinePatchRect" parent="BG/Part_3/GridContainer/info_4/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1) modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20) custom_minimum_size = Vector2(20, 20)
layout_mode = 2 layout_mode = 2
texture = ExtResource("4_klru0") texture = ExtResource("6_u37th")
region_rect = Rect2(0, 0, 6, 9) region_rect = Rect2(0, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_4/HBoxContainer/Previous"] [node name="Button" type="Button" parent="BG/Part_3/GridContainer/info_4/HBoxContainer/Previous"]
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@ -483,57 +492,57 @@ theme_override_styles/hover = SubResource("StyleBoxEmpty_hxsho")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_lha8x") theme_override_styles/pressed = SubResource("StyleBoxEmpty_lha8x")
flat = true flat = true
[node name="Segments" type="HBoxContainer" parent="Part_3/GridContainer/info_4/HBoxContainer"] [node name="Segments" type="HBoxContainer" parent="BG/Part_3/GridContainer/info_4/HBoxContainer"]
layout_mode = 2 layout_mode = 2
[node name="Segment_1" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"] [node name="Segment_1" type="ColorRect" parent="BG/Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_2" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"] [node name="Segment_2" type="ColorRect" parent="BG/Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_3" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"] [node name="Segment_3" type="ColorRect" parent="BG/Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_4" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"] [node name="Segment_4" type="ColorRect" parent="BG/Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_5" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"] [node name="Segment_5" type="ColorRect" parent="BG/Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_6" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"] [node name="Segment_6" type="ColorRect" parent="BG/Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_7" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"] [node name="Segment_7" type="ColorRect" parent="BG/Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_8" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"] [node name="Segment_8" type="ColorRect" parent="BG/Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_9" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"] [node name="Segment_9" type="ColorRect" parent="BG/Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_10" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"] [node name="Segment_10" type="ColorRect" parent="BG/Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Next" type="NinePatchRect" parent="Part_3/GridContainer/info_4/HBoxContainer"] [node name="Next" type="NinePatchRect" parent="BG/Part_3/GridContainer/info_4/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1) modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20) custom_minimum_size = Vector2(20, 20)
layout_mode = 2 layout_mode = 2
texture = ExtResource("4_klru0") texture = ExtResource("6_u37th")
region_rect = Rect2(6, 0, 6, 9) region_rect = Rect2(6, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_4/HBoxContainer/Next"] [node name="Button" type="Button" parent="BG/Part_3/GridContainer/info_4/HBoxContainer/Next"]
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@ -545,25 +554,25 @@ theme_override_styles/hover = SubResource("StyleBoxEmpty_u37th")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_wmlhw") theme_override_styles/pressed = SubResource("StyleBoxEmpty_wmlhw")
flat = true flat = true
[node name="info_5" type="VBoxContainer" parent="Part_3/GridContainer"] [node name="info_5" type="VBoxContainer" parent="BG/Part_3/GridContainer"]
layout_mode = 2 layout_mode = 2
[node name="Title" type="Label" parent="Part_3/GridContainer/info_5"] [node name="Title" type="Label" parent="BG/Part_3/GridContainer/info_5"]
layout_mode = 2 layout_mode = 2
text = "操控感" text = "操控感"
label_settings = ExtResource("3_kjf47") label_settings = ExtResource("5_33nyt")
[node name="HBoxContainer" type="HBoxContainer" parent="Part_3/GridContainer/info_5"] [node name="HBoxContainer" type="HBoxContainer" parent="BG/Part_3/GridContainer/info_5"]
layout_mode = 2 layout_mode = 2
[node name="Previous" type="NinePatchRect" parent="Part_3/GridContainer/info_5/HBoxContainer"] [node name="Previous" type="NinePatchRect" parent="BG/Part_3/GridContainer/info_5/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1) modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20) custom_minimum_size = Vector2(20, 20)
layout_mode = 2 layout_mode = 2
texture = ExtResource("4_klru0") texture = ExtResource("6_u37th")
region_rect = Rect2(0, 0, 6, 9) region_rect = Rect2(0, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_5/HBoxContainer/Previous"] [node name="Button" type="Button" parent="BG/Part_3/GridContainer/info_5/HBoxContainer/Previous"]
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@ -575,57 +584,57 @@ theme_override_styles/hover = SubResource("StyleBoxEmpty_hxsho")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_lha8x") theme_override_styles/pressed = SubResource("StyleBoxEmpty_lha8x")
flat = true flat = true
[node name="Segments" type="HBoxContainer" parent="Part_3/GridContainer/info_5/HBoxContainer"] [node name="Segments" type="HBoxContainer" parent="BG/Part_3/GridContainer/info_5/HBoxContainer"]
layout_mode = 2 layout_mode = 2
[node name="Segment_1" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"] [node name="Segment_1" type="ColorRect" parent="BG/Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_2" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"] [node name="Segment_2" type="ColorRect" parent="BG/Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_3" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"] [node name="Segment_3" type="ColorRect" parent="BG/Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_4" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"] [node name="Segment_4" type="ColorRect" parent="BG/Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_5" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"] [node name="Segment_5" type="ColorRect" parent="BG/Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_6" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"] [node name="Segment_6" type="ColorRect" parent="BG/Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_7" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"] [node name="Segment_7" type="ColorRect" parent="BG/Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_8" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"] [node name="Segment_8" type="ColorRect" parent="BG/Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_9" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"] [node name="Segment_9" type="ColorRect" parent="BG/Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_10" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"] [node name="Segment_10" type="ColorRect" parent="BG/Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Next" type="NinePatchRect" parent="Part_3/GridContainer/info_5/HBoxContainer"] [node name="Next" type="NinePatchRect" parent="BG/Part_3/GridContainer/info_5/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1) modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20) custom_minimum_size = Vector2(20, 20)
layout_mode = 2 layout_mode = 2
texture = ExtResource("4_klru0") texture = ExtResource("6_u37th")
region_rect = Rect2(6, 0, 6, 9) region_rect = Rect2(6, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_5/HBoxContainer/Next"] [node name="Button" type="Button" parent="BG/Part_3/GridContainer/info_5/HBoxContainer/Next"]
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@ -637,25 +646,25 @@ theme_override_styles/hover = SubResource("StyleBoxEmpty_u37th")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_wmlhw") theme_override_styles/pressed = SubResource("StyleBoxEmpty_wmlhw")
flat = true flat = true
[node name="info_6" type="VBoxContainer" parent="Part_3/GridContainer"] [node name="info_6" type="VBoxContainer" parent="BG/Part_3/GridContainer"]
layout_mode = 2 layout_mode = 2
[node name="Title" type="Label" parent="Part_3/GridContainer/info_6"] [node name="Title" type="Label" parent="BG/Part_3/GridContainer/info_6"]
layout_mode = 2 layout_mode = 2
text = "沉浸感" text = "沉浸感"
label_settings = ExtResource("3_kjf47") label_settings = ExtResource("5_33nyt")
[node name="HBoxContainer" type="HBoxContainer" parent="Part_3/GridContainer/info_6"] [node name="HBoxContainer" type="HBoxContainer" parent="BG/Part_3/GridContainer/info_6"]
layout_mode = 2 layout_mode = 2
[node name="Previous" type="NinePatchRect" parent="Part_3/GridContainer/info_6/HBoxContainer"] [node name="Previous" type="NinePatchRect" parent="BG/Part_3/GridContainer/info_6/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1) modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20) custom_minimum_size = Vector2(20, 20)
layout_mode = 2 layout_mode = 2
texture = ExtResource("4_klru0") texture = ExtResource("6_u37th")
region_rect = Rect2(0, 0, 6, 9) region_rect = Rect2(0, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_6/HBoxContainer/Previous"] [node name="Button" type="Button" parent="BG/Part_3/GridContainer/info_6/HBoxContainer/Previous"]
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@ -667,57 +676,57 @@ theme_override_styles/hover = SubResource("StyleBoxEmpty_hxsho")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_lha8x") theme_override_styles/pressed = SubResource("StyleBoxEmpty_lha8x")
flat = true flat = true
[node name="Segments" type="HBoxContainer" parent="Part_3/GridContainer/info_6/HBoxContainer"] [node name="Segments" type="HBoxContainer" parent="BG/Part_3/GridContainer/info_6/HBoxContainer"]
layout_mode = 2 layout_mode = 2
[node name="Segment_1" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"] [node name="Segment_1" type="ColorRect" parent="BG/Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_2" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"] [node name="Segment_2" type="ColorRect" parent="BG/Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_3" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"] [node name="Segment_3" type="ColorRect" parent="BG/Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_4" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"] [node name="Segment_4" type="ColorRect" parent="BG/Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_5" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"] [node name="Segment_5" type="ColorRect" parent="BG/Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_6" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"] [node name="Segment_6" type="ColorRect" parent="BG/Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_7" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"] [node name="Segment_7" type="ColorRect" parent="BG/Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_8" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"] [node name="Segment_8" type="ColorRect" parent="BG/Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_9" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"] [node name="Segment_9" type="ColorRect" parent="BG/Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Segment_10" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"] [node name="Segment_10" type="ColorRect" parent="BG/Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0) custom_minimum_size = Vector2(10, 0)
layout_mode = 2 layout_mode = 2
[node name="Next" type="NinePatchRect" parent="Part_3/GridContainer/info_6/HBoxContainer"] [node name="Next" type="NinePatchRect" parent="BG/Part_3/GridContainer/info_6/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1) modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20) custom_minimum_size = Vector2(20, 20)
layout_mode = 2 layout_mode = 2
texture = ExtResource("4_klru0") texture = ExtResource("6_u37th")
region_rect = Rect2(6, 0, 6, 9) region_rect = Rect2(6, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_6/HBoxContainer/Next"] [node name="Button" type="Button" parent="BG/Part_3/GridContainer/info_6/HBoxContainer/Next"]
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@ -729,7 +738,7 @@ theme_override_styles/hover = SubResource("StyleBoxEmpty_u37th")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_wmlhw") theme_override_styles/pressed = SubResource("StyleBoxEmpty_wmlhw")
flat = true flat = true
[node name="Part_4" type="VBoxContainer" parent="."] [node name="Part_4" type="VBoxContainer" parent="BG"]
layout_mode = 1 layout_mode = 1
anchors_preset = 2 anchors_preset = 2
anchor_top = 1.0 anchor_top = 1.0
@ -741,33 +750,33 @@ offset_bottom = -11.0
grow_vertical = 0 grow_vertical = 0
theme_override_constants/separation = 2 theme_override_constants/separation = 2
[node name="gameplay" type="HBoxContainer" parent="Part_4"] [node name="gameplay" type="HBoxContainer" parent="BG/Part_4"]
layout_mode = 2 layout_mode = 2
[node name="title" type="Label" parent="Part_4/gameplay"] [node name="title" type="Label" parent="BG/Part_4/gameplay"]
layout_mode = 2 layout_mode = 2
text = "玩法 - " text = "玩法 - "
label_settings = ExtResource("3_kjf47") label_settings = ExtResource("5_33nyt")
[node name="info" type="Label" parent="Part_4/gameplay"] [node name="info" type="Label" parent="BG/Part_4/gameplay"]
layout_mode = 2 layout_mode = 2
text = "桌面游戏" text = "桌面游戏"
label_settings = ExtResource("3_kjf47") label_settings = ExtResource("5_33nyt")
[node name="theme" type="HBoxContainer" parent="Part_4"] [node name="theme" type="HBoxContainer" parent="BG/Part_4"]
layout_mode = 2 layout_mode = 2
[node name="title" type="Label" parent="Part_4/theme"] [node name="title" type="Label" parent="BG/Part_4/theme"]
layout_mode = 2 layout_mode = 2
text = "题材 - " text = "题材 - "
label_settings = ExtResource("3_kjf47") label_settings = ExtResource("5_33nyt")
[node name="info" type="Label" parent="Part_4/theme"] [node name="info" type="Label" parent="BG/Part_4/theme"]
layout_mode = 2 layout_mode = 2
text = "校园模拟" text = "校园模拟"
label_settings = ExtResource("3_kjf47") label_settings = ExtResource("5_33nyt")
[node name="Confirm" type="Button" parent="."] [node name="Confirm" type="Button" parent="BG"]
custom_minimum_size = Vector2(70, 0) custom_minimum_size = Vector2(70, 0)
layout_mode = 1 layout_mode = 1
anchors_preset = 3 anchors_preset = 3
@ -781,10 +790,10 @@ offset_right = -67.0
offset_bottom = -15.0 offset_bottom = -15.0
grow_horizontal = 0 grow_horizontal = 0
grow_vertical = 0 grow_vertical = 0
theme = ExtResource("4_hxsho") theme = ExtResource("7_wmlhw")
theme_override_colors/font_color = Color(0, 0, 0, 1) theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1) theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_fonts/font = ExtResource("3_hxsho") theme_override_fonts/font = ExtResource("3_ct65a")
theme_override_font_sizes/font_size = 23 theme_override_font_sizes/font_size = 23
theme_override_styles/focus = SubResource("StyleBoxEmpty_rjt3h") theme_override_styles/focus = SubResource("StyleBoxEmpty_rjt3h")
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_ykocv") theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_ykocv")

View File

@ -0,0 +1,16 @@
extends NinePatchRect
@onready var confirm_button: Button = $Confirm
func _ready() -> void:
var node_name = str(self.name)
add_to_group(node_name)
if confirm_button:
if not confirm_button.pressed.is_connected(_on_confirm_pressed):
confirm_button.pressed.connect(_on_confirm_pressed)
func _on_confirm_pressed():
get_parent().start_task()
self.hide()
get_parent().close_all_popups()

View File

@ -0,0 +1,632 @@
[gd_scene load_steps=31 format=3 uid="uid://be000l53jxash"]
[ext_resource type="Texture2D" uid="uid://pucudatqrcrq" path="res://UI/popup/popup_bg_1.png" id="1_0ruly"]
[ext_resource type="Script" uid="uid://ciqayqceaadw4" path="res://UI/popup/task_development/proposal/proposal.gd" id="2_uudne"]
[ext_resource type="Texture2D" uid="uid://dgcugleiv7sfw" path="res://UI/popup/task_development/arrow.png" id="3_uudne"]
[ext_resource type="FontFile" uid="uid://egugs822n8gr" path="res://UI/font/AlimamaFangYuanTiVF-Thin.ttf" id="4_csatq"]
[ext_resource type="LabelSettings" uid="uid://qcn0aduvrgvw" path="res://UI/tres/task_development_platform_label_settings.tres" id="5_l6j0s"]
[ext_resource type="Texture2D" uid="uid://bb3kyiufyyj05" path="res://Entity/NPC/NPC_1_UI.png" id="5_uudne"]
[ext_resource type="Theme" uid="uid://bau80ps6kx783" path="res://UI/tres/Bottom_Info_button_theme.tres" id="6_6v1sj"]
[ext_resource type="Texture2D" uid="uid://hdidwixuokxn" path="res://Entity/NPC/npc_ability_icon.png" id="6_ybd2x"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_6a8uh"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_bu5lv"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_mue51"]
[sub_resource type="Animation" id="Animation_bu5lv"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Title/previous:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(9, 7)]
}
[sub_resource type="Animation" id="Animation_6a8uh"]
resource_name = "base"
length = 0.6
loop_mode = 1
step = 0.2
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Title/previous:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 1,
"values": [Vector2(16, 7), Vector2(12, 7), Vector2(9, 7), Vector2(16, 7)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_mue51"]
_data = {
&"RESET": SubResource("Animation_bu5lv"),
&"base": SubResource("Animation_6a8uh")
}
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_wpgbx"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_r6b7x"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_hsiy4"]
[sub_resource type="Animation" id="Animation_wpgbx"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(461, 7)]
}
[sub_resource type="Animation" id="Animation_mue51"]
resource_name = "base"
length = 0.6
loop_mode = 1
step = 0.2
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 1,
"values": [Vector2(455, 7), Vector2(458, 7), Vector2(461, 7), Vector2(455, 7)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_r6b7x"]
_data = {
&"RESET": SubResource("Animation_wpgbx"),
&"base": SubResource("Animation_mue51")
}
[sub_resource type="LabelSettings" id="LabelSettings_l6j0s"]
font = ExtResource("4_csatq")
font_size = 23
font_color = Color(0, 0, 0, 1)
[sub_resource type="LabelSettings" id="LabelSettings_6v1sj"]
font = ExtResource("4_csatq")
font_size = 24
font_color = Color(0, 0, 0, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_nckjb"]
bg_color = Color(0.913725, 0.913725, 0.913725, 0.913725)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qrj6y"]
bg_color = Color(0.913725, 0.913725, 0.913725, 0.913725)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_v0qi7"]
bg_color = Color(0.913725, 0.913725, 0.913725, 0.913725)
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_rl25l"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tndxe"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xx8ge"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ep6fu"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ci4su"]
[node name="proposal" type="NinePatchRect"]
custom_minimum_size = Vector2(500, 350)
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -250.0
offset_top = -175.0
offset_right = 250.0
offset_bottom = 175.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("1_0ruly")
region_rect = Rect2(1, 35, 86, 53)
patch_margin_left = 8
patch_margin_top = 35
patch_margin_right = 8
patch_margin_bottom = 32
script = ExtResource("2_uudne")
[node name="Title" type="NinePatchRect" parent="."]
custom_minimum_size = Vector2(500, 0)
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -250.0
offset_right = 250.0
offset_bottom = 44.0
grow_horizontal = 2
texture = ExtResource("1_0ruly")
region_rect = Rect2(1, 27, 86, 8)
patch_margin_left = 8
patch_margin_top = 7
patch_margin_right = 8
patch_margin_bottom = 6
[node name="Title" type="Label" parent="Title"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -34.5
offset_top = -18.0
offset_right = 34.5
offset_bottom = 18.0
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 27
text = "策划案负责人"
vertical_alignment = 1
[node name="previous" type="NinePatchRect" parent="Title"]
layout_mode = 1
anchors_preset = 4
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 9.0
offset_top = -15.0
offset_right = 39.0
offset_bottom = 15.0
grow_vertical = 2
texture = ExtResource("3_uudne")
region_rect = Rect2(0, 0, 6, 9)
[node name="Button" type="Button" parent="Title/previous"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_6a8uh")
theme_override_styles/hover = SubResource("StyleBoxEmpty_bu5lv")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_mue51")
flat = true
[node name="AnimationPlayer" type="AnimationPlayer" parent="Title/previous"]
root_node = NodePath("../../..")
libraries = {
&"": SubResource("AnimationLibrary_mue51")
}
autoplay = "base"
[node name="next" type="NinePatchRect" parent="Title"]
layout_mode = 1
anchors_preset = 6
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -39.0
offset_top = -15.0
offset_right = -9.0
offset_bottom = 15.0
grow_horizontal = 0
grow_vertical = 2
texture = ExtResource("3_uudne")
region_rect = Rect2(6, 0, 6, 9)
[node name="Button" type="Button" parent="Title/next"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_wpgbx")
theme_override_styles/hover = SubResource("StyleBoxEmpty_r6b7x")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_hsiy4")
flat = true
[node name="AnimationPlayer" type="AnimationPlayer" parent="Title/next"]
libraries = {
&"": SubResource("AnimationLibrary_r6b7x")
}
autoplay = "base"
[node name="Option" type="Control" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="NPC_Info_1" type="HBoxContainer" parent="Option"]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -170.0
offset_top = 47.0
offset_right = 202.0
offset_bottom = 87.0
grow_horizontal = 2
theme_override_constants/separation = 160
[node name="Name" type="Label" parent="Option/NPC_Info_1"]
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 24
text = "米子一"
label_settings = SubResource("LabelSettings_l6j0s")
horizontal_alignment = 1
[node name="Staff" type="Label" parent="Option/NPC_Info_1"]
custom_minimum_size = Vector2(140, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_fonts/font = ExtResource("4_csatq")
theme_override_font_sizes/font_size = 20
text = "员工"
horizontal_alignment = 2
[node name="NPC_Info_2" type="NinePatchRect" parent="Option"]
custom_minimum_size = Vector2(440, 200)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -220.0
offset_top = -92.0
offset_right = 220.0
offset_bottom = 130.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("1_0ruly")
region_rect = Rect2(92, 38, 76, 34)
patch_margin_left = 2
patch_margin_top = 33
patch_margin_right = 2
patch_margin_bottom = 33
[node name="Info_List_0" type="VBoxContainer" parent="Option/NPC_Info_2"]
custom_minimum_size = Vector2(0, 95)
layout_mode = 0
offset_left = 26.0
offset_top = 23.0
offset_right = 185.0
offset_bottom = 147.0
theme_override_constants/separation = 14
[node name="Job" type="HBoxContainer" parent="Option/NPC_Info_2/Info_List_0"]
layout_mode = 2
theme_override_constants/separation = 25
[node name="Label" type="Label" parent="Option/NPC_Info_2/Info_List_0/Job"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "产品总监"
label_settings = SubResource("LabelSettings_6v1sj")
[node name="Level" type="Label" parent="Option/NPC_Info_2/Info_List_0/Job"]
layout_mode = 2
text = "Lv1"
label_settings = SubResource("LabelSettings_6v1sj")
horizontal_alignment = 2
[node name="Icon" type="TextureRect" parent="Option/NPC_Info_2/Info_List_0"]
custom_minimum_size = Vector2(95, 95)
layout_mode = 2
texture = ExtResource("5_uudne")
stretch_mode = 5
[node name="Info_List_1" type="GridContainer" parent="Option/NPC_Info_2"]
layout_mode = 1
anchors_preset = 6
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -204.0
offset_top = -87.0
offset_right = -8.0
offset_bottom = 46.0
grow_horizontal = 0
grow_vertical = 2
theme_override_constants/v_separation = 7
[node name="Info_1" type="HBoxContainer" parent="Option/NPC_Info_2/Info_List_1"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="icon" type="NinePatchRect" parent="Option/NPC_Info_2/Info_List_1/Info_1"]
custom_minimum_size = Vector2(28, 28)
layout_mode = 2
texture = ExtResource("6_ybd2x")
region_rect = Rect2(38, 30, 20, 15)
[node name="title" type="Label" parent="Option/NPC_Info_2/Info_List_1/Info_1"]
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
theme_override_fonts/font = ExtResource("4_csatq")
theme_override_styles/normal = SubResource("StyleBoxFlat_nckjb")
text = "策划"
label_settings = ExtResource("5_l6j0s")
horizontal_alignment = 1
[node name="info" type="Label" parent="Option/NPC_Info_2/Info_List_1/Info_1"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "80"
label_settings = ExtResource("5_l6j0s")
horizontal_alignment = 1
[node name="Info_2" type="HBoxContainer" parent="Option/NPC_Info_2/Info_List_1"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="icon" type="NinePatchRect" parent="Option/NPC_Info_2/Info_List_1/Info_2"]
custom_minimum_size = Vector2(28, 28)
layout_mode = 2
texture = ExtResource("6_ybd2x")
region_rect = Rect2(12, 31, 17, 16)
[node name="title" type="Label" parent="Option/NPC_Info_2/Info_List_1/Info_2"]
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
theme_override_fonts/font = ExtResource("4_csatq")
theme_override_styles/normal = SubResource("StyleBoxFlat_nckjb")
text = "程序"
label_settings = ExtResource("5_l6j0s")
horizontal_alignment = 1
[node name="info" type="Label" parent="Option/NPC_Info_2/Info_List_1/Info_2"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "80"
label_settings = ExtResource("5_l6j0s")
horizontal_alignment = 1
[node name="Info_3" type="HBoxContainer" parent="Option/NPC_Info_2/Info_List_1"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="icon" type="NinePatchRect" parent="Option/NPC_Info_2/Info_List_1/Info_3"]
custom_minimum_size = Vector2(28, 28)
layout_mode = 2
texture = ExtResource("6_ybd2x")
region_rect = Rect2(63, 29, 21, 16)
[node name="title" type="Label" parent="Option/NPC_Info_2/Info_List_1/Info_3"]
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
theme_override_fonts/font = ExtResource("4_csatq")
theme_override_styles/normal = SubResource("StyleBoxFlat_nckjb")
text = "美术"
label_settings = ExtResource("5_l6j0s")
horizontal_alignment = 1
[node name="info" type="Label" parent="Option/NPC_Info_2/Info_List_1/Info_3"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "80"
label_settings = ExtResource("5_l6j0s")
horizontal_alignment = 1
[node name="Info_4" type="HBoxContainer" parent="Option/NPC_Info_2/Info_List_1"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="icon" type="NinePatchRect" parent="Option/NPC_Info_2/Info_List_1/Info_4"]
custom_minimum_size = Vector2(28, 28)
layout_mode = 2
texture = ExtResource("6_ybd2x")
region_rect = Rect2(92, 29, 22, 16)
[node name="title" type="Label" parent="Option/NPC_Info_2/Info_List_1/Info_4"]
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
theme_override_fonts/font = ExtResource("4_csatq")
theme_override_styles/normal = SubResource("StyleBoxFlat_nckjb")
text = "音频"
label_settings = ExtResource("5_l6j0s")
horizontal_alignment = 1
[node name="info" type="Label" parent="Option/NPC_Info_2/Info_List_1/Info_4"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "80"
label_settings = ExtResource("5_l6j0s")
horizontal_alignment = 1
[node name="Info_List_2" type="HBoxContainer" parent="Option/NPC_Info_2"]
custom_minimum_size = Vector2(160, 10)
layout_mode = 1
offset_left = 27.0
offset_top = 159.0
offset_right = 187.0
offset_bottom = 169.0
theme_override_constants/separation = 0
alignment = 1
[node name="Segment_1" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_2" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_3" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_4" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_5" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_6" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_7" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_8" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_9" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_10" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_11" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_12" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_13" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_14" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_15" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_16" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_17" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_18" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_19" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Segment_20" type="ColorRect" parent="Option/NPC_Info_2/Info_List_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
[node name="Info_List_3" type="HBoxContainer" parent="Option/NPC_Info_2"]
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -191.5
offset_top = -38.0
offset_right = 191.5
offset_bottom = -10.0
grow_horizontal = 2
grow_vertical = 0
[node name="info_4" type="HBoxContainer" parent="Option/NPC_Info_2/Info_List_3"]
layout_mode = 2
[node name="title" type="Label" parent="Option/NPC_Info_2/Info_List_3/info_4"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
theme_override_fonts/font = ExtResource("4_csatq")
theme_override_styles/normal = SubResource("StyleBoxFlat_qrj6y")
text = "费用"
label_settings = ExtResource("5_l6j0s")
horizontal_alignment = 1
[node name="info" type="Label" parent="Option/NPC_Info_2/Info_List_3/info_4"]
custom_minimum_size = Vector2(70, 0)
layout_mode = 2
text = "500"
label_settings = ExtResource("5_l6j0s")
horizontal_alignment = 1
[node name="info_5" type="HBoxContainer" parent="Option/NPC_Info_2/Info_List_3"]
visible = false
layout_mode = 2
[node name="title" type="Label" parent="Option/NPC_Info_2/Info_List_3/info_5"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_v0qi7")
text = "开发次数"
label_settings = ExtResource("5_l6j0s")
horizontal_alignment = 1
[node name="info" type="Label" parent="Option/NPC_Info_2/Info_List_3/info_5"]
custom_minimum_size = Vector2(30, 0)
layout_mode = 2
text = "0"
label_settings = ExtResource("5_l6j0s")
horizontal_alignment = 2
[node name="unit" type="Label" parent="Option/NPC_Info_2/Info_List_3/info_5"]
layout_mode = 2
text = "次"
label_settings = ExtResource("5_l6j0s")
horizontal_alignment = 2
[node name="Confirm" type="Button" parent="."]
custom_minimum_size = Vector2(70, 0)
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -35.0
offset_top = -43.0
offset_right = 35.0
grow_horizontal = 2
grow_vertical = 0
theme = ExtResource("6_6v1sj")
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 23
theme_override_styles/focus = SubResource("StyleBoxEmpty_rl25l")
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_tndxe")
theme_override_styles/hover = SubResource("StyleBoxFlat_xx8ge")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_ep6fu")
theme_override_styles/normal = SubResource("StyleBoxEmpty_ci4su")
text = "确定"

View File

@ -23,13 +23,12 @@ var is_active: bool = false
# 存储用户在当前配置过程中的临时选择 (使用中文 Key) # 存储用户在当前配置过程中的临时选择 (使用中文 Key)
var task_info: Dictionary = { var task_info: Dictionary = {
"平台": "", "平台": "", # 中文 Key for the selected platform (e.g., "台式 & 笔记本")
"玩法": "", "玩法": "", # 中文 Key for the selected gameplay (e.g., "角色扮演(RPG)")
"题材": "", "题材": "", # 中文 Key for the selected theme (e.g., "现代都市")
"开发策略": "", "开发策略": "", # 中文 Key for the selected strategy (e.g., "正常开发")
"预算": 0, "预算": 0, # Calculated budget based on selections
"产品侧重点": {}, "产品侧重点": {} # To be filled by product_focus popup
"关键环节负责人": [] # <--- 新增并初始化为空数组
} }
# --- 初始化 --- # --- 初始化 ---
@ -274,41 +273,23 @@ func _on_category_button_pressed(popup_node: Control) -> void:
# 提供给子弹出窗口调用的接口函数,用于更新选择 # 提供给子弹出窗口调用的接口函数,用于更新选择
# options: 字典,包含更新的类别和对应的 Key # options: 字典,包含更新的类别和对应的 Key,例如 {"平台": "Switch"}
func update_task_options(options: Dictionary) -> void: func update_task_options(options: Dictionary) -> void:
var updated = false var updated = false
for key in options: for key in options: # key 是 "平台", "玩法" 等中文名
var new_value = options[key] if task_info.has(key):
var new_value_key = options[key] # new_value_key 是 "Switch" 等中文 Key
if key == "关键环节负责人": if task_info[key] != new_value_key:
# --- 特殊处理 "关键环节负责人" --- task_info[key] = new_value_key
if not task_info.has(key) or not typeof(task_info[key]) == TYPE_ARRAY:
task_info[key] = [] # 如果不存在或类型不对,则初始化为空数组
if not new_value in task_info[key]: # 避免重复添加 (可选,根据需求)
task_info[key].append(new_value)
updated = true updated = true
print(node_name + ": Appended '%s' to '%s'. Current: %s" % [new_value, key, task_info[key]]) print(node_name + ": Option '%s' updated to '%s'." % [key, new_value_key])
else:
print(node_name + ": '%s' already in '%s'. No change." % [new_value, key])
elif task_info.has(key):
# --- 原有逻辑处理其他普通键值对 ---
if task_info[key] != new_value:
task_info[key] = new_value
updated = true
print(node_name + ": Option '%s' updated to '%s'." % [key, new_value])
else: else:
printerr(node_name + ": Invalid key '%s' provided in update_task_options and not handled as a special case." % key) printerr(node_name + ": Invalid key '%s' provided in update_task_options." % key)
if updated: if updated:
# 对于 "关键环节负责人" 的更新,通常不需要更新按钮文本或重新计算预算 update_button_texts() # 更新主界面按钮显示名称
# 但如果其他选项更新了,则需要执行 calculate_and_update_budget() # 重新计算预算
if not (options.has("关键环节负责人") and options.size() == 1): # 如果不仅仅是更新负责人 print(node_name + ": Task info updated: ", task_info) # Debug
update_button_texts()
calculate_and_update_budget()
print(node_name + ": Task info updated: ", task_info)
# 处理子弹出窗口确认后关闭并返回主页面的逻辑 # 处理子弹出窗口确认后关闭并返回主页面的逻辑
func _close_child_popup_and_return(child_popup_node: Control) -> void: func _close_child_popup_and_return(child_popup_node: Control) -> void:

View File

@ -11,7 +11,7 @@
[ext_resource type="PackedScene" uid="uid://bej6f0cqirn4j" path="res://UI/popup/task_development/theme/theme.tscn" id="8_nckjb"] [ext_resource type="PackedScene" uid="uid://bej6f0cqirn4j" path="res://UI/popup/task_development/theme/theme.tscn" id="8_nckjb"]
[ext_resource type="PackedScene" uid="uid://ctcbxvgljrkvi" path="res://UI/popup/task_development/strategy/strategy.tscn" id="9_6r1e0"] [ext_resource type="PackedScene" uid="uid://ctcbxvgljrkvi" path="res://UI/popup/task_development/strategy/strategy.tscn" id="9_6r1e0"]
[ext_resource type="PackedScene" uid="uid://dpshanwm4o3by" path="res://UI/popup/task_development/product_focus/product_focus.tscn" id="10_at31c"] [ext_resource type="PackedScene" uid="uid://dpshanwm4o3by" path="res://UI/popup/task_development/product_focus/product_focus.tscn" id="10_at31c"]
[ext_resource type="PackedScene" uid="uid://be000l53jxash" path="res://UI/popup/task_development/key_output/proposal.tscn" id="12_v0qi7"] [ext_resource type="PackedScene" uid="uid://be000l53jxash" path="res://UI/popup/task_development/proposal/proposal.tscn" id="12_v0qi7"]
[ext_resource type="PackedScene" uid="uid://dtywh0m5odikx" path="res://UI/popup/dialogue.tscn" id="13_kctqw"] [ext_resource type="PackedScene" uid="uid://dtywh0m5odikx" path="res://UI/popup/dialogue.tscn" id="13_kctqw"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_xtl2p"] [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_xtl2p"]

View File

@ -0,0 +1,87 @@
[gd_scene load_steps=10 format=3 uid="uid://b5npsmec2fo2w"]
[ext_resource type="Script" uid="uid://du4qd5ynhkjs6" path="res://UI/ui_framework.gd" id="1_6fk5m"]
[ext_resource type="PackedScene" uid="uid://bfmnpiqcyi83h" path="res://UI/popup/task_development/main_plan/main_plan.tscn" id="2_qpnja"]
[ext_resource type="PackedScene" uid="uid://hidwgnr45bw7" path="res://UI/popup/task_development/main_plan/platform/platform.tscn" id="3_5pged"]
[ext_resource type="PackedScene" uid="uid://b8bb7bnc08siu" path="res://UI/popup/task_development/main_plan/gameplay/gameplay.tscn" id="4_ub8no"]
[ext_resource type="PackedScene" uid="uid://ydvns1eq64e5" path="res://UI/popup/task_development/main_plan/theme/theme.tscn" id="5_l54p6"]
[ext_resource type="PackedScene" uid="uid://cx5nui56begu8" path="res://UI/popup/task_development/main_plan/strategy/strategy.tscn" id="6_adtuk"]
[ext_resource type="PackedScene" uid="uid://chl131ysu15rd" path="res://UI/popup/task_development/product_focus/product_focus.tscn" id="7_l1sf4"]
[ext_resource type="PackedScene" uid="uid://dtywh0m5odikx" path="res://UI/popup/dialogue.tscn" id="8_6il88"]
[ext_resource type="PackedScene" uid="uid://b5nxlnb6xr2oc" path="res://UI/popup/task_development/npc_select_1/npc_select_1.tscn" id="9_qpnja"]
[node name="task_development_start" type="Control" groups=["task_development"]]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
script = ExtResource("1_6fk5m")
initial_page_path = NodePath("main_plan")
gamestate_data_key = "task_info"
[node name="main_plan" parent="." instance=ExtResource("2_qpnja")]
visible = false
layout_mode = 1
next_ui_node_path = NodePath("../product_focus")
[node name="platform" parent="main_plan" instance=ExtResource("3_5pged")]
visible = false
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -20.0
offset_top = -20.0
offset_right = 20.0
offset_bottom = 20.0
next_ui_node_path = NodePath("..")
parent_page_node_path = NodePath("..")
[node name="gameplay" parent="main_plan" instance=ExtResource("4_ub8no")]
visible = false
layout_mode = 1
next_ui_node_path = NodePath("..")
parent_page_node_path = NodePath("..")
[node name="theme" parent="main_plan" instance=ExtResource("5_l54p6")]
visible = false
layout_mode = 1
next_ui_node_path = NodePath("..")
parent_page_node_path = NodePath("..")
[node name="strategy" parent="main_plan" instance=ExtResource("6_adtuk")]
layout_mode = 1
next_ui_node_path = NodePath("..")
parent_page_node_path = NodePath("..")
[node name="product_focus" parent="." instance=ExtResource("7_l1sf4")]
visible = false
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -20.0
offset_top = -20.0
offset_right = 20.0
offset_bottom = 20.0
next_ui_node_path = NodePath("../dialogue")
back_ui_node_path = NodePath("../main_plan")
[node name="dialogue" parent="." instance=ExtResource("8_6il88")]
visible = false
layout_mode = 1
dialogues = ["谁来写立项书呢?"]
next_ui_node_path = NodePath("../npc_select_1")
back_ui_node_path = NodePath("../npc_select_1")
[node name="npc_select_1" parent="." instance=ExtResource("9_qpnja")]
visible = false
layout_mode = 1
back_ui_node_path = NodePath("../main_plan")

View File

@ -0,0 +1,390 @@
# gameplay.gd (更新版)
extends NinePatchRect
# UI Node References
@onready var list_container = $Option/Info
@onready var previous_button = $Title/previous/Button
@onready var next_button = $Title/next/Button
@onready var title_label = $Title/Title
# Configuration
var items_per_page: int = 7 # 每页显示的选项数量
# Data Storage
var gameplay_list_nodes: Array[VBoxContainer] = [] # 存储 List_1, List_2 等节点
var option_nodes_per_list: Array = [] # 存储每个 List 对应的 option_X 按钮数组
var all_enabled_gameplays: Array = [] # 存储筛选后的玩法数据 { "name": String, "data": Dictionary }
# State
var total_pages: int = 0
var current_page_index: int = 0
var selected_gameplay_key: String = "" # 当前逻辑上选中的项的 Key (用于确认)
var currently_selected_button: Button = null # 当前高亮/聚焦的按钮节点引用
var initial_key_to_highlight: String = "" # 弹窗打开时需要高亮的 Key (由父节点设置)
func _ready():
# 1. 获取 List 节点 (与原版一致)
for i in range(1, 4): # 检查 List_1, List_2, List_3
var list_node = list_container.get_node_or_null("List_" + str(i))
if list_node:
gameplay_list_nodes.append(list_node)
else:
break
if gameplay_list_nodes.is_empty():
printerr("错误:在 gameplay.tscn 中未找到 List 节点!无法显示选项。")
set_process(false)
return
# 2. 获取每个 List 内的 Option 节点 (与原版一致)
for list_node in gameplay_list_nodes:
var current_list_options: Array = []
for i in range(1, items_per_page + 1):
var option_node = list_node.get_node_or_null("option_" + str(i))
if option_node and option_node is Button:
current_list_options.append(option_node)
else:
push_warning("警告:在 %s 中未能找到 option_%d 按钮" % [list_node.name, i])
option_nodes_per_list.append(current_list_options)
# 3. 连接按钮信号 (与原版一致)
if previous_button:
if not previous_button.pressed.is_connected(_on_previous_pressed):
previous_button.pressed.connect(_on_previous_pressed)
else: printerr("错误:在 gameplay.tscn 中未找到 Previous 按钮")
if next_button:
if not next_button.pressed.is_connected(_on_next_pressed):
next_button.pressed.connect(_on_next_pressed)
else: printerr("错误:在 gameplay.tscn 中未找到 Next 按钮")
# 4. 连接 visibility_changed 信号 (与原版一致)
if not is_connected("visibility_changed", _on_visibility_changed):
visibility_changed.connect(_on_visibility_changed)
# 5. 移除 _ready 中的初始数据加载和显示更新
# 这些逻辑现在完全由 _on_visibility_changed -> reset_display 处理
# --- Visibility Change Handler ---
func _on_visibility_changed():
if is_visible_in_tree():
print("Gameplay: 弹窗变为可见,调用 reset_display()")
reset_display() # 当变为可见时调用 reset
# else: # 可选:隐藏时的清理逻辑
# print("Gameplay: 弹窗变为隐藏。")
# --- 新增:用于父节点设置初始选中项 ---
func set_initial_selection(key: String):
"""
(task_development)
task_info Key
"""
initial_key_to_highlight = key
print("Gameplay: 父节点设置初始高亮 Key 为: '%s'" % initial_key_to_highlight)
# --- Data Processing and Display Logic ---
func _process_gameplay_data(all_gameplays_data: Dictionary):
"""【修改】根据从 GameState 获取的玩法数据,筛选出启用的玩法,并计算总页数。"""
all_enabled_gameplays.clear()
if all_gameplays_data.is_empty():
push_warning("警告:接收到的玩法数据为空。")
total_pages = 0
return
# 遍历从 GameState 获取的玩法字典
for gameplay_name in all_gameplays_data:
var gameplay_info = all_gameplays_data[gameplay_name]
# 检查是否为字典且 "enabled" 字段为 true
if typeof(gameplay_info) == TYPE_DICTIONARY and gameplay_info.get("enabled", false) == true:
all_enabled_gameplays.append({"name": gameplay_name, "data": gameplay_info})
# else: # Debugging: 可以取消注释来看哪些被过滤了
# print("Gameplay: 过滤掉未启用或格式错误的玩法: ", gameplay_name)
# 根据筛选结果计算总页数
if items_per_page > 0:
total_pages = ceil(float(all_enabled_gameplays.size()) / items_per_page)
else:
total_pages = 0
printerr("错误items_per_page 为零,无法计算页数。")
print("Gameplay: 处理完成,找到 %d 个启用的玩法,共 %d 页。" % [all_enabled_gameplays.size(), total_pages])
func reset_display():
"""【修改】重置状态,从 GameState 重新加载数据,并准备更新显示。"""
print("Gameplay: 重置显示状态 (reset_display)。")
# 1. 重置本地状态变量
currently_selected_button = null
selected_gameplay_key = ""
# initial_key_to_highlight 由 set_initial_selection 设置,这里不清空
# 2. 【修改】从 GameState 重新加载并处理数据
if not GameState:
printerr("错误GameState 在 reset_display 期间不可用。")
total_pages = 0
all_enabled_gameplays.clear()
_update_display() # 即使没有数据也要更新显示(显示空状态)
return
var task_dev_data = GameState.get_value("task_development", {})
if task_dev_data.is_empty():
printerr("错误:从 GameState 获取 'task_development' 数据失败。")
total_pages = 0
all_enabled_gameplays.clear()
else:
var all_gameplays_data = task_dev_data.get("gameplays", {})
_process_gameplay_data(all_gameplays_data) # 重新处理数据,计算 total_pages
# 3. 【修改】根据 initial_key_to_highlight 确定初始页面
current_page_index = 0 # 默认为第一页
var found_initial_key = false
if total_pages > 0 and not initial_key_to_highlight.is_empty():
# 查找 initial_key_to_highlight 所在的索引和页面
for i in range(all_enabled_gameplays.size()):
if all_enabled_gameplays[i].name == initial_key_to_highlight:
current_page_index = int(floor(float(i) / items_per_page))
found_initial_key = true
print("Gameplay: 找到需要初始高亮的 Key '%s' 在索引 %d, 目标页面 %d" % [initial_key_to_highlight, i, current_page_index])
break # 找到了,停止循环
if not found_initial_key:
print("Gameplay: 需要初始高亮的 Key '%s' 未在启用的玩法中找到,将显示第一页。" % initial_key_to_highlight)
# 保持 current_page_index 为 0
initial_key_to_highlight = "" # 清空,避免后续 _update_display 尝试高亮不存在的项
# 确保页面索引有效
if total_pages == 0:
current_page_index = 0
elif current_page_index >= total_pages:
current_page_index = total_pages - 1
elif current_page_index < 0:
current_page_index = 0
# 4. 触发显示更新
_update_display()
func _update_display():
"""【更新版】更新列表的可见性并填充当前页面的内容。
"""
# --- 处理无数据情况 ---
if gameplay_list_nodes.is_empty():
print("Gameplay: 无可用的 List 节点。")
if title_label: title_label.text = "玩法选择"
return
if total_pages == 0:
for list_node in gameplay_list_nodes: list_node.visible = false
print("Gameplay: 没有启用的玩法选项可供显示。")
if title_label: title_label.text = "玩法选择 (无可用)"
# 确保按钮也禁用或隐藏
if previous_button: previous_button.disabled = true
if next_button: next_button.disabled = true
return
else:
# 确保按钮状态正确 (启用/禁用基于页数)
if previous_button: previous_button.disabled = (total_pages <= 1)
if next_button: next_button.disabled = (total_pages <= 1)
# --- 更新页面索引 (循环) ---
# 注意: reset_display 设置了初始页, 取模主要用于翻页
if total_pages > 0 : # 避免除以零
current_page_index = current_page_index % total_pages
if current_page_index < 0:
current_page_index += total_pages
else:
current_page_index = 0
# --- 更新标题 ---
if title_label:
var display_page_number = current_page_index + 1 # 显示给用户的页码从1开始
title_label.text = "玩法选择 %d/%d" % [display_page_number, total_pages]
# --- 更新 List 可见性 ---
# 确保只有一个 List 节点是可见的,即当前页对应的 List
for i in range(gameplay_list_nodes.size()):
gameplay_list_nodes[i].visible = (i == current_page_index)
# --- 填充可见列表并查找要高亮的按钮 ---
var button_to_highlight: Button = null
var first_enabled_button_on_page: Button = null # 如果 initial_key 不在本页,则使用此按钮
# 检查当前页索引是否在有效范围内
if current_page_index < option_nodes_per_list.size():
var current_options = option_nodes_per_list[current_page_index] # 获取当前页的所有按钮节点
var start_index = current_page_index * items_per_page # 计算当前页数据在 all_enabled_gameplays 中的起始索引
# 遍历当前页的按钮槽位
for i in range(current_options.size()):
var option_button = current_options[i] # 获取具体的按钮节点
var item_index = start_index + i # 计算此按钮对应的数据索引
# --- 断开旧信号 ---
# 确保在连接新信号前断开旧的,防止重复连接
if option_button.pressed.is_connected(_on_option_selected):
option_button.pressed.disconnect(_on_option_selected)
# 检查数据索引是否在有效范围内
if item_index < all_enabled_gameplays.size():
# 获取对应的玩法数据
var gameplay_entry = all_enabled_gameplays[item_index]
var gameplay_name = gameplay_entry.name
var gameplay_data = gameplay_entry.data # 这是包含 Cost, Experience, Popularity 等信息的字典
# 获取按钮内部用于显示信息的 Row 节点
var row = option_button.get_node_or_null("Row")
if row:
# 填充标签文本
var title_label_in_row = row.get_node_or_null("Title")
var exp_label = row.get_node_or_null("Experience")
var pop_label = row.get_node_or_null("Popularity")
# 查找成本标签,兼容 "Cost" 和 "cost" 两种可能的节点名
var cost_label = row.get_node_or_null("Cost") if row.has_node("Cost") else row.get_node_or_null("cost")
# 设置标题
if title_label_in_row: title_label_in_row.text = gameplay_name
# 【修改】设置经验值,显示为整数
if exp_label:
var experience_value = gameplay_data.get("Experience", 0.0) # 获取原始值
exp_label.text = str(int(experience_value)) # 转为整数再转为字符串
# 【保持】设置流行度,保持原始字符串显示
if pop_label:
var popularity_value = gameplay_data.get("Popularity", "未知") # 获取原始值
pop_label.text = str(popularity_value) # 直接转为字符串
# 【修改】设置成本,显示为整数
if cost_label:
var cost_value = gameplay_data.get("Cost", 0.0) # 获取原始值
cost_label.text = str(int(cost_value)) # 转为整数再转为字符串
# 设置按钮可见、可用,并存储玩法名称到元数据
option_button.visible = true
option_button.disabled = false
option_button.set_meta("gameplay_name", gameplay_name)
# --- 连接新信号 (使用 bind) ---
# 传递玩法名称和按钮节点本身给处理函数
option_button.pressed.connect(_on_option_selected.bind(gameplay_name, option_button))
# 记录本页第一个可用的按钮,作为备选高亮目标
if first_enabled_button_on_page == null:
first_enabled_button_on_page = option_button
# 检查此按钮是否是需要初始高亮的按钮
if not initial_key_to_highlight.is_empty() and gameplay_name == initial_key_to_highlight:
button_to_highlight = option_button
# print("Gameplay: 找到需要初始高亮的按钮: ", button_to_highlight.name) # Debug
else: # 未找到 Row 节点
push_warning("警告:在按钮 %s 中未找到 Row 节点" % option_button.get_path())
option_button.visible = false # 隐藏按钮以避免显示不完整
option_button.disabled = true
else: # 此槽位没有对应的启用玩法数据 (例如最后一页不满)
option_button.visible = false # 隐藏按钮
option_button.disabled = true
else:
# 这种情况理论上不应发生,除非页面计算或节点结构有问题
printerr("错误:当前页面索引 %d 超出 option_nodes_per_list 的范围 (大小 %d)" % [current_page_index, option_nodes_per_list.size()])
# --- 设置初始高亮和焦点 ---
# 优先高亮 initial_key_to_highlight 对应的按钮
if button_to_highlight:
# print("Gameplay: 尝试高亮指定按钮: ", button_to_highlight.name) # Debug
_set_initial_highlight(button_to_highlight)
# 如果没有指定按钮或指定按钮不在当前页,则高亮当前页的第一个可用按钮
elif first_enabled_button_on_page:
print("Gameplay: 初始 Key '%s' 不在本页或为空。高亮本页第一个按钮: %s" % [initial_key_to_highlight, first_enabled_button_on_page.name])
_set_initial_highlight(first_enabled_button_on_page)
# 如果当前页没有任何可用按钮
else:
print("Gameplay: 本页没有可供高亮的按钮。")
currently_selected_button = null # 确保没有按钮被标记为选中
selected_gameplay_key = "" # 确保没有 Key 被标记为选中
# (Helper) 设置初始高亮和焦点 (与原版类似,增加日志)
func _set_initial_highlight(button_to_highlight: Button):
if not is_instance_valid(button_to_highlight):
printerr("错误:传递给 _set_initial_highlight 的按钮无效。")
currently_selected_button = null
selected_gameplay_key = ""
return
# print("Gameplay: 设置高亮按钮: ", button_to_highlight.name) # Debug
currently_selected_button = button_to_highlight
selected_gameplay_key = button_to_highlight.get_meta("gameplay_name", "") # 同时设置 Key
# print("Gameplay: currently_selected_button 设置为: ", currently_selected_button.name) # Debug
# print("Gameplay: selected_gameplay_key 设置为: '", selected_gameplay_key, "'") # Debug
# 延迟调用 grab_focus 确保按钮已准备好接收焦点
if currently_selected_button and currently_selected_button.is_inside_tree():
# print("Gameplay: 尝试为按钮 call_deferred('grab_focus'): ", currently_selected_button.name) # Debug
currently_selected_button.call_deferred("grab_focus")
else:
print("Gameplay: 按钮无效或不在场景树中,无法获取焦点。")
# --- Signal Handlers ---
func _on_previous_pressed():
if total_pages > 1: # 仅当有多页时才翻页
current_page_index -= 1
# 循环由 _update_display 处理
_update_display()
func _on_next_pressed():
if total_pages > 1: # 仅当有多页时才翻页
current_page_index += 1
# 循环由 _update_display 处理
_update_display()
# 【修改】处理选项按钮按下事件 (与原版类似,增加日志)
func _on_option_selected(gameplay_name: String, button_node: Button):
"""当选项按钮被按下时调用。"""
if not is_instance_valid(button_node):
printerr("错误:在 _on_option_selected 中收到无效按钮节点Key: " + gameplay_name)
return
if button_node == currently_selected_button:
# --- 双击 (或点击已选中的按钮) ---
print("Gameplay: 检测到双击或确认点击: ", gameplay_name)
_confirm_and_close(gameplay_name) # 确认此选择
else:
# --- 首次点击或点击不同按钮 ---
print("Gameplay: 选中 (高亮) 玩法: ", gameplay_name)
currently_selected_button = button_node
selected_gameplay_key = gameplay_name # 更新 Key以便后续可能的确认
# 更新视觉焦点
button_node.grab_focus()
# 【修改】确认选择并关闭弹窗的逻辑
func _confirm_and_close(key_to_confirm: String):
"""更新父节点并关闭此弹窗。"""
if key_to_confirm.is_empty():
printerr("错误:尝试确认一个空的玩法 Key。")
return
print("Gameplay: 确认选择: ", key_to_confirm)
# --- 更新父节点 (Task Development Popup) ---
var parent_node = get_parent()
if parent_node and parent_node.has_method("update_task_options"):
# 通知父节点更新其内部的 task_info
parent_node.update_task_options({"玩法": key_to_confirm})
print("Gameplay: 已更新父节点的 task_options: 玩法 = ", key_to_confirm)
else:
printerr("错误Gameplay: 父节点未找到或缺少 'update_task_options' 方法。")
# 考虑是否应该在此处停止或仅记录错误后继续
# --- 关闭此弹窗 ---
if parent_node and parent_node.has_method("_close_child_popup_and_return"):
# 调用父节点的标准方法来关闭子弹窗并返回
parent_node._close_child_popup_and_return(self)
else:
printerr("错误Gameplay: 父节点未找到或缺少 '_close_child_popup_and_return' 方法。")
self.hide() # 作为备选方案,直接隐藏自身

View File

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

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ctled08bky4mu"
path="res://.godot/imported/key_output.png-a31ee9fabd17a37980a8303ee0aa6cf0.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://UI/popup/task_development_bak/key_output/key_output.png"
dest_files=["res://.godot/imported/key_output.png-a31ee9fabd17a37980a8303ee0aa6cf0.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

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

View File

@ -1,7 +1,7 @@
[gd_scene load_steps=30 format=3 uid="uid://be000l53jxash"] [gd_scene load_steps=30 format=3 uid="uid://be000l53jxash"]
[ext_resource type="Texture2D" uid="uid://pucudatqrcrq" path="res://UI/popup/popup_bg_1.png" id="1_0ruly"] [ext_resource type="Texture2D" uid="uid://pucudatqrcrq" path="res://UI/popup/popup_bg_1.png" id="1_0ruly"]
[ext_resource type="Script" uid="uid://ciqayqceaadw4" path="res://UI/popup/task_development/key_output/proposal.gd" id="2_uudne"] [ext_resource type="Script" uid="uid://ciqayqceaadw4" path="res://UI/popup/task_development_bak/key_output/proposal.gd" id="2_uudne"]
[ext_resource type="Texture2D" uid="uid://dgcugleiv7sfw" path="res://UI/popup/task_development/arrow.png" id="3_uudne"] [ext_resource type="Texture2D" uid="uid://dgcugleiv7sfw" path="res://UI/popup/task_development/arrow.png" id="3_uudne"]
[ext_resource type="FontFile" uid="uid://egugs822n8gr" path="res://UI/font/AlimamaFangYuanTiVF-Thin.ttf" id="4_csatq"] [ext_resource type="FontFile" uid="uid://egugs822n8gr" path="res://UI/font/AlimamaFangYuanTiVF-Thin.ttf" id="4_csatq"]
[ext_resource type="LabelSettings" uid="uid://qcn0aduvrgvw" path="res://UI/tres/task_development_platform_label_settings.tres" id="5_l6j0s"] [ext_resource type="LabelSettings" uid="uid://qcn0aduvrgvw" path="res://UI/tres/task_development_platform_label_settings.tres" id="5_l6j0s"]

View File

@ -0,0 +1,278 @@
extends NinePatchRect
# References to UI elements
@onready var title_label = $Title/Title
@onready var name_label = $Option/Name
@onready var icon_rect = $Option/Info/Icon
@onready var info1_label = $Option/Info/Info_List_1/Info_1/info # 制造商 (Maker)
@onready var info2_label = $Option/Info/Info_List_1/Info_2/info # 发售 (Sales)
@onready var info3_label = $Option/Info/Info_List_1/Info_3/info # 市场占比 (Market_share)
@onready var info4_label = $Option/Info/Info_List_2/info_4/info # 开发费用 (Cost)
# @onready var info5_label = $Option/Info/Info_List_2/info_5/info # 开发次数 - 暂不处理,数据源未知
@onready var prev_button = $Title/previous/Button
@onready var next_button = $Title/next/Button
@onready var confirm_button = $Confirm
# Internal state
var enabled_platform_keys: Array[String] = [] # Store keys (names) of enabled platforms
var current_platform_index: int = -1 # Index in the enabled_platform_keys array
const PLATFORM_ICON_PATH = "res://UI/popup/task_development/platform/"
# --- 新增:用于存储从 GameState 获取的平台数据 ---
var _platforms_data: Dictionary = {}
# --- 新增:用于存储上次确认的平台 Key 的 GameState 键名 ---
const SELECTED_PLATFORM_KEY_NAME = "selected_platform_key" # 假设用这个顶层键存储
func _ready():
add_to_group(str(name)) # 保持不变
# Connect button signals (保持不变)
if prev_button:
if not prev_button.pressed.is_connected(_on_previous_pressed):
prev_button.pressed.connect(_on_previous_pressed)
if next_button:
if not next_button.pressed.is_connected(_on_next_pressed):
next_button.pressed.connect(_on_next_pressed)
if confirm_button:
if not confirm_button.pressed.is_connected(_on_confirm_pressed):
confirm_button.pressed.connect(_on_confirm_pressed)
# Connect visibility changed signal (保持不变)
if not is_connected("visibility_changed", _on_visibility_changed):
visibility_changed.connect(_on_visibility_changed)
# Initial call if already visible on ready (保持不变)
if is_visible_in_tree():
reset_display()
# Visibility Change Handler (保持不变)
func _on_visibility_changed():
if is_visible_in_tree():
reset_display()
# --- 修改reset_display ---
# 重置显示状态,在窗口可见时调用
func reset_display():
print("Platform: Resetting display state.")
# 1. 加载并过滤平台数据
_load_and_filter_platforms() # 这会更新 _platforms_data 和 enabled_platform_keys
# 2. 检查是否有可用的平台
if enabled_platform_keys.is_empty():
print("Platform: No enabled platforms found during reset.")
current_platform_index = -1
_display_current_platform() # 显示 "无可用平台" 状态
return
# 3. 获取上次确认的平台 Key
var last_confirmed_key = ""
if GameState:
# --- 修改:使用 get_value 获取上次选择 ---
last_confirmed_key = GameState.get_value(SELECTED_PLATFORM_KEY_NAME, "")
else:
push_error("GameState not available during reset_display.")
# 默认使用第一个可用平台
current_platform_index = 0
_display_current_platform()
return
# 4. 查找上次确认的 Key 在当前可用列表中的索引
if not last_confirmed_key.is_empty():
var found_index = enabled_platform_keys.find(last_confirmed_key)
if found_index != -1:
# 5. 如果找到,更新当前索引
current_platform_index = found_index
print("Platform: Found last confirmed key '%s' at index %d." % [last_confirmed_key, found_index])
else:
# 6. 如果找不到(可能平台不再可用),默认使用第一个可用平台
print("Platform: Last confirmed key '%s' not found in enabled list. Defaulting to index 0." % last_confirmed_key)
current_platform_index = 0
else:
# 7. 如果 GameState 中没有记录,默认使用第一个可用平台
print("Platform: No last confirmed key found in GameState. Defaulting to index 0.")
current_platform_index = 0
# 8. 更新显示
_display_current_platform()
# --- 修改_load_and_filter_platforms ---
# 从 GameState 获取平台数据并筛选出启用的平台
func _load_and_filter_platforms():
enabled_platform_keys.clear()
_platforms_data = {} # 清空旧数据
if not GameState:
push_error("GameState not available in _load_and_filter_platforms.")
return
# --- 修改:从 GameState 获取 task_development 数据 ---
var task_dev_data = GameState.get_value("task_development", {})
if task_dev_data.is_empty():
printerr("Platform: Failed to get 'task_development' data from GameState.")
return
# --- 修改:获取 platforms 字典 ---
var all_platforms = task_dev_data.get("platforms", {})
if all_platforms.is_empty():
print("Platform: 'platforms' data in GameState is empty.")
return
# --- 修改:将获取到的平台数据存储到内部变量 ---
_platforms_data = all_platforms
# 遍历平台数据,筛选出启用的平台 Key
for platform_key in _platforms_data:
var platform_info = _platforms_data[platform_key]
# 确保 platform_info 是字典并且 'enabled' 键存在且为 true
if typeof(platform_info) == TYPE_DICTIONARY and platform_info.get("enabled", false) == true:
enabled_platform_keys.append(platform_key)
# 可选:按键名排序,保持一致性
#if not enabled_platform_keys.is_empty():
#enabled_platform_keys.sort() # 简单字符串排序
## current_platform_index 会在 reset_display 中设置
#else:
#print("Platform: No enabled platforms found.")
#current_platform_index = -1 # 明确设置无可用平台
# 根据 current_platform_index 更新 UI 显示
func _display_current_platform():
# 处理无可用平台或索引无效的情况
if current_platform_index < 0 or current_platform_index >= enabled_platform_keys.size():
title_label.text = "游戏平台"
name_label.text = "无可用平台"
if icon_rect: icon_rect.texture = null
if info1_label: info1_label.text = "-" # Maker
if info2_label: info2_label.text = "-" # Sales
if info3_label: info3_label.text = "-" # Market_share
if info4_label: info4_label.text = "-" # Cost
if prev_button: prev_button.disabled = true
if next_button: next_button.disabled = true
if confirm_button: confirm_button.disabled = true
return
# --- 如果有有效平台 ---
if confirm_button: confirm_button.disabled = false # 启用确认按钮
# 更新标题
var display_index = current_platform_index + 1
var total_count = enabled_platform_keys.size()
title_label.text = "游戏平台 %d/%d" % [display_index, total_count]
# 获取当前平台的 Key 和数据
var platform_key = enabled_platform_keys[current_platform_index]
# --- 从内部缓存 _platforms_data 获取平台信息 ---
if not _platforms_data.has(platform_key):
push_error("Current platform key '%s' not found in cached _platforms_data!" % platform_key)
name_label.text = "错误:数据丢失"
# ... 清空其他信息 ...
return
var platform_info = _platforms_data[platform_key]
# 更新平台名称
name_label.text = platform_key
# --- 修改:从 platform_info 读取所有需要的信息,数值转为整数显示 ---
# 使用 .get(key, default) 确保安全访问
# 制造商 (Maker) - 通常是字符串,保持不变
info1_label.text = platform_info.get("Maker", "-")
# 发售 (Sales) - 转换为整数显示
var sales_value = platform_info.get("Sales", null)
if typeof(sales_value) == TYPE_INT or typeof(sales_value) == TYPE_FLOAT:
info2_label.text = str(int(sales_value)) # 转换为整数再转字符串
else:
info2_label.text = "-" # 如果不是数字或不存在,显示 "-"
# 市场占比 (Market_share) - 转换为整数显示
var market_share_value = platform_info.get("Market_share", null)
if typeof(market_share_value) == TYPE_INT or typeof(market_share_value) == TYPE_FLOAT:
info3_label.text = str(int(market_share_value)) # 转换为整数再转字符串
else:
info3_label.text = "-"
# 开发费用 (Cost) - 转换为整数显示
var cost_value = platform_info.get("Cost", null)
if typeof(cost_value) == TYPE_INT or typeof(cost_value) == TYPE_FLOAT:
info4_label.text = str(int(cost_value)) # 转换为整数再转字符串
else:
info4_label.text = "-"
# info5 (开发次数) 暂不处理
# 更新图标 (逻辑不变)
var icon_name = platform_info.get("Icon", "")
if not icon_name.is_empty():
var icon_path = PLATFORM_ICON_PATH + icon_name + ".png"
if ResourceLoader.exists(icon_path):
icon_rect.texture = ResourceLoader.load(icon_path)
else:
push_error("Failed to load platform icon: " + icon_path)
icon_rect.texture = null
else:
print("Platform icon name is empty for key: ", platform_key)
icon_rect.texture = null
# 更新导航按钮状态 (逻辑不变)
var can_navigate = enabled_platform_keys.size() > 1
if prev_button: prev_button.disabled = not can_navigate
if next_button: next_button.disabled = not can_navigate
# --- _on_previous_pressed (逻辑不变) ---
func _on_previous_pressed():
if enabled_platform_keys.size() > 1:
current_platform_index -= 1
if current_platform_index < 0:
current_platform_index = enabled_platform_keys.size() - 1
_display_current_platform()
# --- _on_next_pressed (逻辑不变) ---
func _on_next_pressed():
if enabled_platform_keys.size() > 1:
current_platform_index += 1
if current_platform_index >= enabled_platform_keys.size():
current_platform_index = 0
_display_current_platform()
# --- 修改_on_confirm_pressed ---
# 处理确认选择
func _on_confirm_pressed():
if current_platform_index != -1 and current_platform_index < enabled_platform_keys.size():
# 1. 获取选中的平台 Key
var selected_platform_key = enabled_platform_keys[current_platform_index]
# 2. 获取父节点 (task_development)
var parent_node = get_parent()
# 3. 调用父节点的 update_task_options 更新选项 (保持不变)
if parent_node and parent_node.has_method("update_task_options"):
parent_node.update_task_options({"平台": selected_platform_key})
else:
push_error("Platform: Parent node not found or missing 'update_task_options' method.")
# 即使父节点更新失败,也尝试关闭窗口
# 4. 调用父节点的方法来处理关闭自身 (保持不变)
if parent_node and parent_node.has_method("_close_child_popup_and_return"):
parent_node._close_child_popup_and_return(self)
else:
push_error("Platform: Parent node not found or missing '_close_child_popup_and_return' method.")
self.hide() # Fallback hide
else:
# 处理没有有效平台被选中的情况
print("Platform: No valid platform selected to confirm.")
# 仍然尝试关闭窗口
var parent_node = get_parent()
if parent_node and parent_node.has_method("_close_child_popup_and_return"):
parent_node._close_child_popup_and_return(self)
else:
self.hide() # Fallback hide

View File

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

View File

@ -0,0 +1,488 @@
[gd_scene load_steps=29 format=3 uid="uid://blv6i6w651pcs"]
[ext_resource type="Texture2D" uid="uid://pucudatqrcrq" path="res://UI/popup/popup_bg_1.png" id="1_kr1r5"]
[ext_resource type="Texture2D" uid="uid://dgcugleiv7sfw" path="res://UI/popup/task_development/arrow.png" id="2_6a8uh"]
[ext_resource type="Script" uid="uid://ddadoyxwx3sbh" path="res://UI/popup/task_development_bak/platform/platform.gd" id="2_bu5lv"]
[ext_resource type="Texture2D" uid="uid://b2n1f22bd06tc" path="res://UI/popup/task_development/main_plan/platform/platform_1.png" id="3_bu5lv"]
[ext_resource type="LabelSettings" uid="uid://qcn0aduvrgvw" path="res://UI/tres/task_development_platform_label_settings.tres" id="4_mue51"]
[ext_resource type="Theme" uid="uid://bau80ps6kx783" path="res://UI/tres/Bottom_Info_button_theme.tres" id="5_wpgbx"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_6a8uh"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_bu5lv"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_mue51"]
[sub_resource type="Animation" id="Animation_bu5lv"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Title/previous:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(9, 7)]
}
[sub_resource type="Animation" id="Animation_6a8uh"]
resource_name = "base"
length = 0.6
loop_mode = 1
step = 0.2
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Title/previous:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 1,
"values": [Vector2(16, 7), Vector2(12, 7), Vector2(9, 7), Vector2(16, 7)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_mue51"]
_data = {
&"RESET": SubResource("Animation_bu5lv"),
&"base": SubResource("Animation_6a8uh")
}
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_wpgbx"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_r6b7x"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_hsiy4"]
[sub_resource type="Animation" id="Animation_wpgbx"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(461, 7)]
}
[sub_resource type="Animation" id="Animation_mue51"]
resource_name = "base"
length = 0.6
loop_mode = 1
step = 0.2
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 1,
"values": [Vector2(455, 7), Vector2(458, 7), Vector2(461, 7), Vector2(455, 7)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_r6b7x"]
_data = {
&"RESET": SubResource("Animation_wpgbx"),
&"base": SubResource("Animation_mue51")
}
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_nckjb"]
bg_color = Color(0.913725, 0.913725, 0.913725, 0.913725)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6r1e0"]
bg_color = Color(0.913725, 0.913725, 0.913725, 0.913725)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_at31c"]
bg_color = Color(0.913725, 0.913725, 0.913725, 0.913725)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qrj6y"]
bg_color = Color(0.913725, 0.913725, 0.913725, 0.913725)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_v0qi7"]
bg_color = Color(0.913725, 0.913725, 0.913725, 0.913725)
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_rl25l"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tndxe"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xx8ge"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ep6fu"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ci4su"]
[node name="platform" type="NinePatchRect"]
custom_minimum_size = Vector2(500, 350)
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -250.0
offset_top = -175.0
offset_right = 250.0
offset_bottom = 175.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("1_kr1r5")
region_rect = Rect2(1, 35, 86, 53)
patch_margin_left = 8
patch_margin_top = 35
patch_margin_right = 8
patch_margin_bottom = 32
script = ExtResource("2_bu5lv")
[node name="Title" type="NinePatchRect" parent="."]
custom_minimum_size = Vector2(500, 0)
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -250.0
offset_right = 250.0
offset_bottom = 44.0
grow_horizontal = 2
texture = ExtResource("1_kr1r5")
region_rect = Rect2(1, 27, 86, 8)
patch_margin_left = 8
patch_margin_top = 7
patch_margin_right = 8
patch_margin_bottom = 6
[node name="Title" type="Label" parent="Title"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -34.5
offset_top = -18.0
offset_right = 34.5
offset_bottom = 18.0
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 27
text = "游戏平台"
vertical_alignment = 1
[node name="previous" type="NinePatchRect" parent="Title"]
layout_mode = 1
anchors_preset = 4
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 9.0
offset_top = -15.0
offset_right = 39.0
offset_bottom = 15.0
grow_vertical = 2
texture = ExtResource("2_6a8uh")
region_rect = Rect2(0, 0, 6, 9)
[node name="Button" type="Button" parent="Title/previous"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_6a8uh")
theme_override_styles/hover = SubResource("StyleBoxEmpty_bu5lv")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_mue51")
flat = true
[node name="AnimationPlayer" type="AnimationPlayer" parent="Title/previous"]
root_node = NodePath("../../..")
libraries = {
&"": SubResource("AnimationLibrary_mue51")
}
autoplay = "base"
[node name="next" type="NinePatchRect" parent="Title"]
layout_mode = 1
anchors_preset = 6
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -39.0
offset_top = -15.0
offset_right = -9.0
offset_bottom = 15.0
grow_horizontal = 0
grow_vertical = 2
texture = ExtResource("2_6a8uh")
region_rect = Rect2(6, 0, 6, 9)
[node name="Button" type="Button" parent="Title/next"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_wpgbx")
theme_override_styles/hover = SubResource("StyleBoxEmpty_r6b7x")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_hsiy4")
flat = true
[node name="AnimationPlayer" type="AnimationPlayer" parent="Title/next"]
libraries = {
&"": SubResource("AnimationLibrary_r6b7x")
}
autoplay = "base"
[node name="Option" type="Control" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="Name" type="Label" parent="Option"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -75.0
offset_top = -129.5
offset_right = 75.0
offset_bottom = -95.5
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 24
text = "台式 & 笔记本"
horizontal_alignment = 1
[node name="Info" type="NinePatchRect" parent="Option"]
custom_minimum_size = Vector2(440, 200)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -220.0
offset_top = -92.0
offset_right = 220.0
offset_bottom = 130.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("1_kr1r5")
region_rect = Rect2(92, 38, 76, 34)
patch_margin_left = 2
patch_margin_top = 33
patch_margin_right = 2
patch_margin_bottom = 33
[node name="Icon" type="NinePatchRect" parent="Option/Info"]
custom_minimum_size = Vector2(144, 144)
layout_mode = 1
anchors_preset = 4
anchor_top = 0.5
anchor_bottom = 0.5
offset_top = -108.0
offset_right = 190.0
offset_bottom = 82.0
grow_vertical = 2
texture = ExtResource("3_bu5lv")
[node name="Info_List_1" type="GridContainer" parent="Option/Info"]
layout_mode = 1
anchors_preset = 6
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -225.0
offset_top = -82.0
offset_right = -9.0
offset_bottom = 19.0
grow_horizontal = 0
grow_vertical = 2
theme_override_constants/v_separation = 7
[node name="Info_1" type="HBoxContainer" parent="Option/Info/Info_List_1"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="start" type="Label" parent="Option/Info/Info_List_1/Info_1"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_nckjb")
text = " "
label_settings = ExtResource("4_mue51")
[node name="title" type="Label" parent="Option/Info/Info_List_1/Info_1"]
custom_minimum_size = Vector2(90, 0)
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_nckjb")
text = "制造商"
label_settings = ExtResource("4_mue51")
[node name="info" type="Label" parent="Option/Info/Info_List_1/Info_1"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 2
text = "不定"
label_settings = ExtResource("4_mue51")
horizontal_alignment = 1
[node name="Info_2" type="HBoxContainer" parent="Option/Info/Info_List_1"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="start" type="Label" parent="Option/Info/Info_List_1/Info_2"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_nckjb")
text = " "
label_settings = ExtResource("4_mue51")
[node name="title" type="Label" parent="Option/Info/Info_List_1/Info_2"]
custom_minimum_size = Vector2(90, 0)
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_6r1e0")
text = "发售"
label_settings = ExtResource("4_mue51")
[node name="info" type="Label" parent="Option/Info/Info_List_1/Info_2"]
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
text = "50"
label_settings = ExtResource("4_mue51")
horizontal_alignment = 2
[node name="unit" type="Label" parent="Option/Info/Info_List_1/Info_2"]
layout_mode = 2
text = "万台"
label_settings = ExtResource("4_mue51")
horizontal_alignment = 2
[node name="Info_3" type="HBoxContainer" parent="Option/Info/Info_List_1"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="start" type="Label" parent="Option/Info/Info_List_1/Info_3"]
custom_minimum_size = Vector2(6, 0)
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_nckjb")
text = " "
label_settings = ExtResource("4_mue51")
[node name="title" type="Label" parent="Option/Info/Info_List_1/Info_3"]
custom_minimum_size = Vector2(90, 0)
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_at31c")
text = "市场占比"
label_settings = ExtResource("4_mue51")
[node name="info" type="Label" parent="Option/Info/Info_List_1/Info_3"]
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
text = "38"
label_settings = ExtResource("4_mue51")
horizontal_alignment = 2
[node name="unit" type="Label" parent="Option/Info/Info_List_1/Info_3"]
layout_mode = 2
text = "%"
label_settings = ExtResource("4_mue51")
horizontal_alignment = 2
[node name="Info_List_2" type="HBoxContainer" parent="Option/Info"]
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -191.5
offset_top = -38.0
offset_right = 191.5
offset_bottom = -10.0
grow_horizontal = 2
grow_vertical = 0
alignment = 1
[node name="info_4" type="HBoxContainer" parent="Option/Info/Info_List_2"]
layout_mode = 2
[node name="title" type="Label" parent="Option/Info/Info_List_2/info_4"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_qrj6y")
text = "开发费用"
label_settings = ExtResource("4_mue51")
horizontal_alignment = 1
[node name="info" type="Label" parent="Option/Info/Info_List_2/info_4"]
custom_minimum_size = Vector2(70, 0)
layout_mode = 2
text = "500"
label_settings = ExtResource("4_mue51")
horizontal_alignment = 1
[node name="info_5" type="HBoxContainer" parent="Option/Info/Info_List_2"]
layout_mode = 2
[node name="title" type="Label" parent="Option/Info/Info_List_2/info_5"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_v0qi7")
text = "开发次数"
label_settings = ExtResource("4_mue51")
horizontal_alignment = 1
[node name="info" type="Label" parent="Option/Info/Info_List_2/info_5"]
custom_minimum_size = Vector2(30, 0)
layout_mode = 2
text = "0"
label_settings = ExtResource("4_mue51")
horizontal_alignment = 2
[node name="unit" type="Label" parent="Option/Info/Info_List_2/info_5"]
layout_mode = 2
text = "次"
label_settings = ExtResource("4_mue51")
horizontal_alignment = 2
[node name="Confirm" type="Button" parent="."]
custom_minimum_size = Vector2(70, 0)
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -35.0
offset_top = -43.0
offset_right = 35.0
grow_horizontal = 2
grow_vertical = 0
theme = ExtResource("5_wpgbx")
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 23
theme_override_styles/focus = SubResource("StyleBoxEmpty_rl25l")
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_tndxe")
theme_override_styles/hover = SubResource("StyleBoxFlat_xx8ge")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_ep6fu")
theme_override_styles/normal = SubResource("StyleBoxEmpty_ci4su")
text = "确定"

View File

@ -0,0 +1,274 @@
# product_focus.gd
extends NinePatchRect
#-- 配置变量 (可在 Inspector 中调整) --
@export var min_value_per_category: int = 0 # 每个类别的最小值
@export var max_value_per_category: int = 10 # 每个类别的最大值 (对应10个格子)
@export var active_color: Color = Color("05b700") # 激活格子的颜色
@export var inactive_color: Color = Color("acacac") # 非激活格子的颜色
#-- 内部变量 --
var total_points_from_state: int = 0 # 从 GameState 获取的总点数
var remaining_points: int = 0
# 存储每个类别 (info_1 到 info_6) 当前分配的点数
var category_points: Array[int] = []
# 存储每个类别在 GameState 中的键名 ("视觉表现", "游戏深度", ...)
var category_keys: Array[String] = []
# 存储每个类别 UI 元素的引用
var category_controls: Array[Dictionary] = []
#-- 节点引用 --
@onready var points_label: Label = $Part_2/HBoxContainer/Points
@onready var grid_container: GridContainer = $Part_3/GridContainer
@onready var gameplay_info_label: Label = $Part_4/gameplay/info
@onready var theme_info_label: Label = $Part_4/theme/info
@onready var confirm_button: Button = $Confirm
func _ready():
add_to_group(str(name)) # 方便父节点查找
# --- 初始化控件引用和类别键名 ---
category_keys.clear()
category_controls.clear()
category_points.clear() # 确保清空
var category_index = 0
for info_node in grid_container.get_children():
if not info_node is VBoxContainer:
continue
# 获取类别键名 (从 Title Label)
var title_label = info_node.get_node_or_null("Title")
if title_label:
category_keys.append(title_label.text)
else:
printerr("Product Focus: Category node '%s' is missing a Title Label!" % info_node.name)
category_keys.append("未知类别_%d" % category_index) # 占位符
# 获取并存储控件引用
var hbox = info_node.get_node("HBoxContainer")
var prev_button = hbox.get_node("Previous/Button")
var next_button = hbox.get_node("Next/Button")
var segments_container = hbox.get_node("Segments")
var segments_array: Array[ColorRect] = []
for segment_node in segments_container.get_children():
if segment_node is ColorRect:
segments_array.append(segment_node)
if segments_array.size() != max_value_per_category:
push_warning("Product Focus: Category '%s' has %d segments, expected %d." % [info_node.name, segments_array.size(), max_value_per_category])
var controls = {
"prev_button": prev_button,
"next_button": next_button,
"segments": segments_array
}
category_controls.append(controls)
# 连接按钮信号 (确保只连接一次)
if not prev_button.is_connected("pressed", _on_adjust_points):
prev_button.pressed.connect(_on_adjust_points.bind(-1, category_index))
if not next_button.is_connected("pressed", _on_adjust_points):
next_button.pressed.connect(_on_adjust_points.bind(1, category_index))
category_index += 1
# 连接 visibility_changed 信号 (用于界面显示时重置)
if not is_connected("visibility_changed", _on_visibility_changed):
visibility_changed.connect(_on_visibility_changed)
# 连接确认按钮信号
if confirm_button and not confirm_button.is_connected("pressed", _on_confirm_pressed):
confirm_button.pressed.connect(_on_confirm_pressed)
elif not confirm_button:
printerr("Product Focus: Confirm button node not found!")
# 初始数据加载和 UI 更新将在 _on_visibility_changed 中处理
# --- 可见性改变处理 ---
func _on_visibility_changed():
if is_visible_in_tree():
print("Product Focus: Becoming visible, resetting display.")
reset_display()
# else: # 可选:隐藏时清理
# print("Product Focus: Becoming hidden.")
# --- 重置和加载数据 ---
func reset_display():
if not GameState:
printerr("Product Focus: GameState not available during reset_display.")
# 可以考虑禁用界面或显示错误信息
return
# 1. 从 GameState 获取 task_development 数据
var task_dev_data: Dictionary = GameState.get_value("task_development", {})
var focus_data: Dictionary = {} # 初始化为空字典
if task_dev_data.is_empty():
push_warning("Product Focus: Failed to get 'task_development' data from GameState. Using defaults for focus points.")
total_points_from_state = 20 # 使用默认总点数
else:
# 从 task_dev_data 中获取 product_focus_points
focus_data = task_dev_data.get("product_focus_points", {})
if focus_data.is_empty():
push_warning("Product Focus: 'product_focus_points' not found in task_development data. Using defaults.")
total_points_from_state = 20 # 使用默认总点数
else:
# 从 focus_data 获取总数,如果不存在则使用默认值
total_points_from_state = focus_data.get("总数", 20)
# 2. 初始化本地 category_points (从 focus_data 加载)
category_points.clear()
var current_total_allocated = 0
for key in category_keys:
# 从 focus_data 获取该类别的值,进行类型检查和范围限制
var value_loaded = focus_data.get(key, min_value_per_category)
var value: int = min_value_per_category # 默认值
if typeof(value_loaded) == TYPE_INT:
value = value_loaded
elif typeof(value_loaded) == TYPE_FLOAT:
value = int(value_loaded) # 浮点数转整数
# push_warning("Product Focus: Loaded float value for key '%s', converting to int." % key)
# else: # 非数字类型已在上面用默认值处理
# 确保值在合法范围内
value = clamp(value, min_value_per_category, max_value_per_category)
category_points.append(value)
current_total_allocated += value
# 3. 计算剩余点数 (修正逻辑)
# 如果加载的总分配点数超过了总点数(可能由于存档数据或配置问题)
if current_total_allocated > total_points_from_state:
push_warning("Product Focus: Loaded allocated points (%d) exceed total points (%d). Resetting points to minimums." % [current_total_allocated, total_points_from_state])
# 重置所有点数为最小值,并重新计算
category_points.clear()
current_total_allocated = 0
for _i in range(category_keys.size()):
category_points.append(min_value_per_category)
current_total_allocated += min_value_per_category
remaining_points = total_points_from_state - current_total_allocated
else:
# 正常计算剩余点数
remaining_points = total_points_from_state - current_total_allocated
# 4. 更新 UI
_update_remaining_points_label()
_update_all_category_visuals() # 更新所有格子颜色
_update_all_button_states() # 更新所有按钮状态
_load_part4_info() # 加载玩法和题材信息
# 处理点数调整按钮点击的函数
func _on_adjust_points(delta: int, category_index: int):
# 检查索引是否有效
if category_index < 0 or category_index >= category_points.size():
printerr("Product Focus: Invalid category_index %d in _on_adjust_points" % category_index)
return
var current_cat_value = category_points[category_index]
var new_cat_value = current_cat_value + delta
# 合法性检查: 是否在单项范围内,是否有足够剩余点数来增加
if new_cat_value < min_value_per_category or new_cat_value > max_value_per_category:
return # 超出单项范围
if delta > 0 and remaining_points <= 0:
return # 没点数了不能加
# 更新数据
category_points[category_index] = new_cat_value
remaining_points -= delta
# 更新 UI
_update_remaining_points_label()
_update_category_visuals(category_index) # 只更新当前类别的视觉
_update_all_button_states() # 更新所有按钮状态(因为剩余点数或单项值变了)
# 更新指定类别的格子颜色
func _update_category_visuals(category_index: int):
if category_index < 0 or category_index >= category_controls.size():
printerr("Product Focus: Invalid category_index %d in _update_category_visuals" % category_index)
return
if category_index >= category_points.size():
printerr("Product Focus: category_points array out of bounds for index %d" % category_index)
return
var value = category_points[category_index]
var segments = category_controls[category_index]["segments"]
for i in range(segments.size()):
if i < value:
segments[i].color = active_color
else:
segments[i].color = inactive_color
# 更新所有类别的格子颜色
func _update_all_category_visuals():
for i in range(category_controls.size()):
_update_category_visuals(i)
# 更新剩余点数标签的文本
func _update_remaining_points_label():
points_label.text = str(remaining_points)
# 更新所有类别按钮的启用/禁用状态
func _update_all_button_states():
for i in range(category_controls.size()):
if i >= category_controls.size() or i >= category_points.size():
printerr("Product Focus: Index mismatch in _update_all_button_states at index %d" % i)
continue # 安全检查
var controls = category_controls[i]
var value = category_points[i]
# 更新 "-" 按钮状态: 当前值 > 最小值时启用
controls["prev_button"].disabled = (value <= min_value_per_category)
# 更新 "+" 按钮状态: 当前值 < 最大值 且 剩余点数 > 0 时启用
var can_increment = (value < max_value_per_category) and (remaining_points > 0)
controls["next_button"].disabled = not can_increment
# 从父节点加载 Part_4 的信息 (玩法和题材)
func _load_part4_info():
var parent = get_parent()
# 检查父节点和 task_info 是否有效
if not is_instance_valid(parent):
push_warning("Product Focus: Parent node or its 'task_info' metadata not available for Part 4.")
gameplay_info_label.text = "错误"
theme_info_label.text = "错误"
return
# 使用 get_meta 获取,避免直接访问可能不存在的属性
var task_info = parent.task_info # 提供默认值 null
if task_info is Dictionary:
gameplay_info_label.text = task_info.get("玩法", "未知")
theme_info_label.text = task_info.get("题材", "未知")
else:
push_warning("Product Focus: Parent's task_info metadata is not a Dictionary or is null.")
gameplay_info_label.text = "错误"
theme_info_label.text = "错误"
# --- 处理确定按钮的逻辑 ---
func _on_confirm_pressed():
# 1. 构建包含当前侧重点分配的字典
var confirmed_focus_points: Dictionary = {}
if category_keys.size() != category_points.size():
printerr("Product Focus: Mismatch between category_keys and category_points on confirm!")
# 可以考虑显示错误提示给用户
return # 阻止继续执行
for i in range(category_keys.size()):
confirmed_focus_points[category_keys[i]] = category_points[i]
# 2. 获取父节点 (task_development) 并 传递数据
var parent_node = get_parent()
if parent_node and parent_node.has_method("update_task_options"):
# 通知父节点更新其内部的 task_info
parent_node.update_task_options({"产品侧重点": confirmed_focus_points})
else:
printerr("错误Product Focus: 父节点未找到或缺少 'update_task_options' 方法。")
# 3. 进入下一个节点
self.hide()
parent_node.show_dialogue("谁来写立项书呢?", "proposal")

View File

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

View File

@ -0,0 +1,794 @@
[gd_scene load_steps=21 format=3 uid="uid://dpshanwm4o3by"]
[ext_resource type="Texture2D" uid="uid://pucudatqrcrq" path="res://UI/popup/popup_bg_1.png" id="1_kjf47"]
[ext_resource type="Script" uid="uid://dfny4mxgw5yb6" path="res://UI/popup/task_development_bak/product_focus/product_focus.gd" id="2_dt4oy"]
[ext_resource type="FontFile" uid="uid://bbr43ojt1sreu" path="res://UI/font/MinecraftStandard.otf" id="2_klru0"]
[ext_resource type="FontFile" uid="uid://egugs822n8gr" path="res://UI/font/AlimamaFangYuanTiVF-Thin.ttf" id="3_hxsho"]
[ext_resource type="LabelSettings" uid="uid://qcn0aduvrgvw" path="res://UI/tres/task_development_platform_label_settings.tres" id="3_kjf47"]
[ext_resource type="Theme" uid="uid://bau80ps6kx783" path="res://UI/tres/Bottom_Info_button_theme.tres" id="4_hxsho"]
[ext_resource type="Texture2D" uid="uid://dgcugleiv7sfw" path="res://UI/popup/task_development/arrow.png" id="4_klru0"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ct65a"]
[sub_resource type="LabelSettings" id="LabelSettings_lha8x"]
font = ExtResource("2_klru0")
font_size = 14
outline_size = 6
outline_color = Color(0, 0, 0, 1)
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_dt4oy"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_hxsho"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_lha8x"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_33nyt"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_u37th"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_wmlhw"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_rjt3h"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ykocv"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_oakbh"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_pi7v6"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_so1ex"]
[node name="product_focus" type="NinePatchRect"]
custom_minimum_size = Vector2(500, 350)
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -250.0
offset_top = -175.0
offset_right = 250.0
offset_bottom = 175.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("1_kjf47")
region_rect = Rect2(1, 35, 86, 53)
patch_margin_left = 8
patch_margin_top = 35
patch_margin_right = 8
patch_margin_bottom = 32
script = ExtResource("2_dt4oy")
[node name="Part_1" type="NinePatchRect" parent="."]
custom_minimum_size = Vector2(500, 0)
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -250.0
offset_right = 250.0
offset_bottom = 44.0
grow_horizontal = 2
texture = ExtResource("1_kjf47")
region_rect = Rect2(1, 27, 86, 8)
patch_margin_left = 8
patch_margin_top = 7
patch_margin_right = 8
patch_margin_bottom = 6
[node name="Title" type="Label" parent="Part_1"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -34.5
offset_top = -18.0
offset_right = 34.5
offset_bottom = 18.0
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 27
text = "侧重点"
vertical_alignment = 1
[node name="Part_2" type="Control" parent="."]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -20.0
offset_top = 34.0
offset_right = 20.0
offset_bottom = 53.0
grow_horizontal = 2
[node name="HBoxContainer" type="HBoxContainer" parent="Part_2"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -89.0
offset_top = 4.0
offset_right = 143.0
offset_bottom = 38.0
grow_horizontal = 2
grow_vertical = 2
alignment = 1
[node name="Title" type="Label" parent="Part_2/HBoxContainer"]
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_fonts/font = ExtResource("3_hxsho")
theme_override_font_sizes/font_size = 24
text = "剩余可用点数"
horizontal_alignment = 1
[node name="Points" type="Label" parent="Part_2/HBoxContainer"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
theme_override_styles/normal = SubResource("StyleBoxEmpty_ct65a")
text = "2"
label_settings = SubResource("LabelSettings_lha8x")
horizontal_alignment = 1
[node name="Part_3" type="NinePatchRect" parent="."]
custom_minimum_size = Vector2(440, 200)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -220.0
offset_top = -91.0
offset_right = 220.0
offset_bottom = 109.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("1_kjf47")
region_rect = Rect2(92, 38, 76, 34)
patch_margin_left = 2
patch_margin_top = 33
patch_margin_right = 2
patch_margin_bottom = 33
[node name="GridContainer" type="GridContainer" parent="Part_3"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -204.0
offset_top = -79.0
offset_right = 204.0
offset_bottom = 79.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/h_separation = 40
theme_override_constants/v_separation = 10
columns = 2
[node name="info_1" type="VBoxContainer" parent="Part_3/GridContainer"]
layout_mode = 2
[node name="Title" type="Label" parent="Part_3/GridContainer/info_1"]
layout_mode = 2
text = "视觉表现"
label_settings = ExtResource("3_kjf47")
[node name="HBoxContainer" type="HBoxContainer" parent="Part_3/GridContainer/info_1"]
layout_mode = 2
[node name="Previous" type="NinePatchRect" parent="Part_3/GridContainer/info_1/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20)
layout_mode = 2
texture = ExtResource("4_klru0")
region_rect = Rect2(0, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_1/HBoxContainer/Previous"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_dt4oy")
theme_override_styles/hover = SubResource("StyleBoxEmpty_hxsho")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_lha8x")
flat = true
[node name="Segments" type="HBoxContainer" parent="Part_3/GridContainer/info_1/HBoxContainer"]
layout_mode = 2
[node name="Segment_1" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_2" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_3" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_4" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_5" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_6" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_7" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_8" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_9" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_10" type="ColorRect" parent="Part_3/GridContainer/info_1/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Next" type="NinePatchRect" parent="Part_3/GridContainer/info_1/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20)
layout_mode = 2
texture = ExtResource("4_klru0")
region_rect = Rect2(6, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_1/HBoxContainer/Next"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_33nyt")
theme_override_styles/hover = SubResource("StyleBoxEmpty_u37th")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_wmlhw")
flat = true
[node name="info_2" type="VBoxContainer" parent="Part_3/GridContainer"]
layout_mode = 2
[node name="Title" type="Label" parent="Part_3/GridContainer/info_2"]
layout_mode = 2
text = "游戏深度"
label_settings = ExtResource("3_kjf47")
[node name="HBoxContainer" type="HBoxContainer" parent="Part_3/GridContainer/info_2"]
layout_mode = 2
[node name="Previous" type="NinePatchRect" parent="Part_3/GridContainer/info_2/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20)
layout_mode = 2
texture = ExtResource("4_klru0")
region_rect = Rect2(0, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_2/HBoxContainer/Previous"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_dt4oy")
theme_override_styles/hover = SubResource("StyleBoxEmpty_hxsho")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_lha8x")
flat = true
[node name="Segments" type="HBoxContainer" parent="Part_3/GridContainer/info_2/HBoxContainer"]
layout_mode = 2
[node name="Segment_1" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_2" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_3" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_4" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_5" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_6" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_7" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_8" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_9" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_10" type="ColorRect" parent="Part_3/GridContainer/info_2/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Next" type="NinePatchRect" parent="Part_3/GridContainer/info_2/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20)
layout_mode = 2
texture = ExtResource("4_klru0")
region_rect = Rect2(6, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_2/HBoxContainer/Next"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_33nyt")
theme_override_styles/hover = SubResource("StyleBoxEmpty_u37th")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_wmlhw")
flat = true
[node name="info_3" type="VBoxContainer" parent="Part_3/GridContainer"]
layout_mode = 2
[node name="Title" type="Label" parent="Part_3/GridContainer/info_3"]
layout_mode = 2
text = "创新性"
label_settings = ExtResource("3_kjf47")
[node name="HBoxContainer" type="HBoxContainer" parent="Part_3/GridContainer/info_3"]
layout_mode = 2
[node name="Previous" type="NinePatchRect" parent="Part_3/GridContainer/info_3/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20)
layout_mode = 2
texture = ExtResource("4_klru0")
region_rect = Rect2(0, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_3/HBoxContainer/Previous"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_dt4oy")
theme_override_styles/hover = SubResource("StyleBoxEmpty_hxsho")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_lha8x")
flat = true
[node name="Segments" type="HBoxContainer" parent="Part_3/GridContainer/info_3/HBoxContainer"]
layout_mode = 2
[node name="Segment_1" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_2" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_3" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_4" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_5" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_6" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_7" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_8" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_9" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_10" type="ColorRect" parent="Part_3/GridContainer/info_3/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Next" type="NinePatchRect" parent="Part_3/GridContainer/info_3/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20)
layout_mode = 2
texture = ExtResource("4_klru0")
region_rect = Rect2(6, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_3/HBoxContainer/Next"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_33nyt")
theme_override_styles/hover = SubResource("StyleBoxEmpty_u37th")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_wmlhw")
flat = true
[node name="info_4" type="VBoxContainer" parent="Part_3/GridContainer"]
layout_mode = 2
[node name="Title" type="Label" parent="Part_3/GridContainer/info_4"]
layout_mode = 2
text = "易玩性"
label_settings = ExtResource("3_kjf47")
[node name="HBoxContainer" type="HBoxContainer" parent="Part_3/GridContainer/info_4"]
layout_mode = 2
[node name="Previous" type="NinePatchRect" parent="Part_3/GridContainer/info_4/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20)
layout_mode = 2
texture = ExtResource("4_klru0")
region_rect = Rect2(0, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_4/HBoxContainer/Previous"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_dt4oy")
theme_override_styles/hover = SubResource("StyleBoxEmpty_hxsho")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_lha8x")
flat = true
[node name="Segments" type="HBoxContainer" parent="Part_3/GridContainer/info_4/HBoxContainer"]
layout_mode = 2
[node name="Segment_1" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_2" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_3" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_4" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_5" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_6" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_7" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_8" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_9" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_10" type="ColorRect" parent="Part_3/GridContainer/info_4/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Next" type="NinePatchRect" parent="Part_3/GridContainer/info_4/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20)
layout_mode = 2
texture = ExtResource("4_klru0")
region_rect = Rect2(6, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_4/HBoxContainer/Next"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_33nyt")
theme_override_styles/hover = SubResource("StyleBoxEmpty_u37th")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_wmlhw")
flat = true
[node name="info_5" type="VBoxContainer" parent="Part_3/GridContainer"]
layout_mode = 2
[node name="Title" type="Label" parent="Part_3/GridContainer/info_5"]
layout_mode = 2
text = "操控感"
label_settings = ExtResource("3_kjf47")
[node name="HBoxContainer" type="HBoxContainer" parent="Part_3/GridContainer/info_5"]
layout_mode = 2
[node name="Previous" type="NinePatchRect" parent="Part_3/GridContainer/info_5/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20)
layout_mode = 2
texture = ExtResource("4_klru0")
region_rect = Rect2(0, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_5/HBoxContainer/Previous"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_dt4oy")
theme_override_styles/hover = SubResource("StyleBoxEmpty_hxsho")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_lha8x")
flat = true
[node name="Segments" type="HBoxContainer" parent="Part_3/GridContainer/info_5/HBoxContainer"]
layout_mode = 2
[node name="Segment_1" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_2" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_3" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_4" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_5" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_6" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_7" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_8" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_9" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_10" type="ColorRect" parent="Part_3/GridContainer/info_5/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Next" type="NinePatchRect" parent="Part_3/GridContainer/info_5/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20)
layout_mode = 2
texture = ExtResource("4_klru0")
region_rect = Rect2(6, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_5/HBoxContainer/Next"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_33nyt")
theme_override_styles/hover = SubResource("StyleBoxEmpty_u37th")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_wmlhw")
flat = true
[node name="info_6" type="VBoxContainer" parent="Part_3/GridContainer"]
layout_mode = 2
[node name="Title" type="Label" parent="Part_3/GridContainer/info_6"]
layout_mode = 2
text = "沉浸感"
label_settings = ExtResource("3_kjf47")
[node name="HBoxContainer" type="HBoxContainer" parent="Part_3/GridContainer/info_6"]
layout_mode = 2
[node name="Previous" type="NinePatchRect" parent="Part_3/GridContainer/info_6/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20)
layout_mode = 2
texture = ExtResource("4_klru0")
region_rect = Rect2(0, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_6/HBoxContainer/Previous"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_dt4oy")
theme_override_styles/hover = SubResource("StyleBoxEmpty_hxsho")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_lha8x")
flat = true
[node name="Segments" type="HBoxContainer" parent="Part_3/GridContainer/info_6/HBoxContainer"]
layout_mode = 2
[node name="Segment_1" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_2" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_3" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_4" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_5" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_6" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_7" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_8" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_9" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Segment_10" type="ColorRect" parent="Part_3/GridContainer/info_6/HBoxContainer/Segments"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="Next" type="NinePatchRect" parent="Part_3/GridContainer/info_6/HBoxContainer"]
modulate = Color(0.888333, 0.739503, 0.29321, 1)
custom_minimum_size = Vector2(20, 20)
layout_mode = 2
texture = ExtResource("4_klru0")
region_rect = Rect2(6, 0, 6, 9)
[node name="Button" type="Button" parent="Part_3/GridContainer/info_6/HBoxContainer/Next"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_33nyt")
theme_override_styles/hover = SubResource("StyleBoxEmpty_u37th")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_wmlhw")
flat = true
[node name="Part_4" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 37.0
offset_top = -55.0
offset_right = 191.0
offset_bottom = -11.0
grow_vertical = 0
theme_override_constants/separation = 2
[node name="gameplay" type="HBoxContainer" parent="Part_4"]
layout_mode = 2
[node name="title" type="Label" parent="Part_4/gameplay"]
layout_mode = 2
text = "玩法 - "
label_settings = ExtResource("3_kjf47")
[node name="info" type="Label" parent="Part_4/gameplay"]
layout_mode = 2
text = "桌面游戏"
label_settings = ExtResource("3_kjf47")
[node name="theme" type="HBoxContainer" parent="Part_4"]
layout_mode = 2
[node name="title" type="Label" parent="Part_4/theme"]
layout_mode = 2
text = "题材 - "
label_settings = ExtResource("3_kjf47")
[node name="info" type="Label" parent="Part_4/theme"]
layout_mode = 2
text = "校园模拟"
label_settings = ExtResource("3_kjf47")
[node name="Confirm" type="Button" parent="."]
custom_minimum_size = Vector2(70, 0)
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -137.0
offset_top = -58.0
offset_right = -67.0
offset_bottom = -15.0
grow_horizontal = 0
grow_vertical = 0
theme = ExtResource("4_hxsho")
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_fonts/font = ExtResource("3_hxsho")
theme_override_font_sizes/font_size = 23
theme_override_styles/focus = SubResource("StyleBoxEmpty_rjt3h")
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_ykocv")
theme_override_styles/hover = SubResource("StyleBoxFlat_oakbh")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_pi7v6")
theme_override_styles/normal = SubResource("StyleBoxEmpty_so1ex")
text = "确定"

View File

@ -0,0 +1,291 @@
# 文件名: strategy.gd
# 脚本作用: 控制策略选择界面的显示和交互 (使用主题资源处理焦点视觉)
# 版本:已根据方案 A 修改,使用通用 GameState 接口,从父节点获取初始选择
extends NinePatchRect
# --- 节点引用 ---
@onready var options_container: VBoxContainer = $Option/Options/List_1
@onready var explanation_label: Label = $Option/Explaination
# --- 内部状态 ---
var option_buttons: Array[Button] = [] # 存储选项按钮节点的数组
var highlighted_button: Button = null # 当前逻辑上选中的按钮 (用于区分首次点击和确认点击)
var current_strategy_data: Dictionary = {} # 存储从 GameState 获取的原始策略数据
var initial_selection_key: String = "" # 由父节点传入的初始/当前选择的策略 Key (中文名)
# --- 初始化 ---
func _ready() -> void:
add_to_group(str(name)) # 便于父节点或其他系统查找
# --- 获取按钮引用 ---
option_buttons.clear()
for child in options_container.get_children():
if child is Button:
option_buttons.append(child)
# 断开旧连接以防万一 (虽然理论上 _ready 只执行一次)
if child.is_connected("pressed", _on_option_button_pressed):
child.disconnect("pressed", _on_option_button_pressed)
# --- 连接 GameState 通用值变化信号 ---
# 用于响应策略数据可能的动态变化 (如解锁新策略)
if GameState and not GameState.state_value_changed.is_connected(_on_game_state_value_changed):
GameState.state_value_changed.connect(_on_game_state_value_changed)
# --- 连接 visibility_changed 信号 ---
# 确保每次界面可见时都刷新列表内容
if not is_connected("visibility_changed", _on_visibility_changed):
visibility_changed.connect(_on_visibility_changed)
# _update_strategy_list() 不再在 _ready 中调用,移至 _on_visibility_changed
# --- 父节点调用接口 ---
# 由父节点 (task_development.gd) 在显示此弹窗前调用,传入当前的临时选择
func set_initial_selection(strategy_key: String) -> void:
initial_selection_key = strategy_key
print("Strategy: Initial selection key set by parent: '", initial_selection_key, "'")
# 注意:这里只存储值,实际的高亮逻辑仍在 _update_strategy_list 中处理
# --- 信号处理 ---
# 处理可见性变化的函数
func _on_visibility_changed() -> void:
print("Strategy: _on_visibility_changed called, visible = ", is_visible_in_tree())
# 当节点变为可见时,更新列表并设置初始焦点
if is_visible_in_tree():
print("Strategy popup became visible. Updating list and setting focus...")
# 确保在更新列表前initial_selection_key 已被父节点设置
_update_strategy_list()
# else: # 可选:如果需要在隐藏时做清理,可以在这里添加逻辑
# print("Strategy popup became hidden.")
# 当 GameState 的任何值发生变化时调用
func _on_game_state_value_changed(key: String, new_value) -> void:
# 检查是否是 task_development 数据发生了变化,这可能影响可用策略
# 注意:这是一个比较宽泛的检查。如果 task_development 下其他不相关数据变化也会触发刷新。
if key == "task_development":
# 并且确保当前界面是可见的避免在后台刷新不可见的UI
if is_visible_in_tree():
print("Strategy: Detected change in 'task_development', refreshing list...")
# 重新执行列表更新,它会使用最新的 GameState 数据
# 并尝试根据 initial_selection_key (由父节点设定,理论上不变) 恢复高亮
_update_strategy_list()
# --- 核心 UI 更新逻辑 ---
# 更新策略列表UI的函数
func _update_strategy_list() -> void:
# 0. 重置界面逻辑状态 (清除上次的高亮和说明)
_reset_highlight_state()
# 1. 从 GameState 获取最新的 task_development 数据
if not GameState:
printerr("Strategy: GameState not available during _update_strategy_list.")
for button in option_buttons: button.visible = false
explanation_label.text = "错误:无法访问游戏状态。"
return
var task_dev_data: Dictionary = GameState.get_value("task_development", {})
if task_dev_data.is_empty():
printerr("Strategy: Failed to get 'task_development' data from GameState.")
for button in option_buttons: button.visible = false
explanation_label.text = "错误:无法获取开发数据。"
return
# 提取策略子字典
current_strategy_data = task_dev_data.get("strategies", {})
if current_strategy_data.is_empty():
printerr("Strategy: 'strategies' data not found or empty in task_development.")
for button in option_buttons: button.visible = false
explanation_label.text = "没有可用的开发策略。"
return
# 2. 筛选出已启用的策略
var enabled_strategies = []
for strategy_name in current_strategy_data: # 遍历字典的键 (策略中文名)
var details: Dictionary = current_strategy_data[strategy_name]
# 确保 details 是字典并且 'enabled' 键存在且为 true
if typeof(details) == TYPE_DICTIONARY and details.get("enabled", false) == true:
enabled_strategies.append({"name": strategy_name, "details": details})
# 如果需要固定顺序,可能需要先对 enabled_strategies 排序 (基于某个字段或预设顺序)
# enabled_strategies.sort_custom(func(a, b): return a["name"] < b["name"]) # 示例:按名称排序
# 3. 填充按钮
var strategy_index: int = 0
var first_enabled_button: Button = null # 用于没有初始选择时的默认高亮
# --- 确保每次更新时正确设置按钮状态和信号连接 ---
for button in option_buttons: # 遍历场景中预设的按钮
button.visible = false # 默认隐藏
button.disabled = true # 默认禁用
# 断开旧的 pressed 信号连接,防止重复触发
if button.is_connected("pressed", _on_option_button_pressed):
button.disconnect("pressed", _on_option_button_pressed)
# 清除可能存在的旧元数据
if button.has_meta("strategy_name"):
button.remove_meta("strategy_name")
# 使用筛选出的启用策略数据填充按钮
while strategy_index < enabled_strategies.size() and strategy_index < option_buttons.size():
var button: Button = option_buttons[strategy_index]
var strategy_data: Dictionary = enabled_strategies[strategy_index]
var strategy_name: String = strategy_data["name"]
var strategy_details: Dictionary = strategy_data["details"]
# 获取按钮内部的标签节点
var title_label: Label = button.find_child("Title", true, false) as Label
var cost_label: Label = button.find_child("Cost", true, false) as Label
if title_label and cost_label:
# 设置策略名称
title_label.text = strategy_name
# 设置成本显示 (格式化为百分比)
var cost_value = strategy_details.get("Cost", null) # 尝试获取 Cost 值
if typeof(cost_value) == TYPE_FLOAT or typeof(cost_value) == TYPE_INT:
var percentage: int = int(cost_value * 100)
cost_label.text = "+%d%%" % percentage # GDScript 中 % 需要转义
else:
cost_label.text = "N/A" # 如果 Cost 不存在或不是数字
# 将策略名称存储在按钮元数据中,方便点击时获取
button.set_meta("strategy_name", strategy_name)
button.visible = true # 显示按钮
button.disabled = false # 启用按钮
# 重新连接 pressed 信号,并绑定按钮自身作为参数
button.pressed.connect(_on_option_button_pressed.bind(button))
# 记录第一个可用的按钮,作为没有初始选择时的备选高亮目标
if first_enabled_button == null:
first_enabled_button = button
else:
printerr("Strategy: 在按钮 '%s' 下未找到 Title 或 Cost 标签。" % button.name)
# 保持按钮隐藏和禁用
strategy_index += 1
# 4. 设置初始逻辑选中状态和焦点
print("--- Updating Strategy List ---")
print("Initial selection key from parent: '", initial_selection_key, "'")
var initial_highlight_button: Button = null
# 尝试根据父节点传入的 initial_selection_key 找到对应的按钮
if not initial_selection_key.is_empty():
print("Searching for matching button for key: '", initial_selection_key, "'")
for button in option_buttons:
# 检查按钮是否可见、启用,并且其元数据中的策略名与初始选择键匹配
if button.visible and not button.disabled:
var meta_name = button.get_meta("strategy_name", "")
# print(" Checking button: '", button.name, "' with meta: '", meta_name, "'") # Debug
if meta_name == initial_selection_key:
print(" Match found!: ", button.name)
initial_highlight_button = button
break # 找到后即可停止搜索
# 如果没有找到匹配的按钮 (例如初始选择为空,或对应的策略不再可用)
if initial_highlight_button == null:
print("No match found for initial key or key was empty. Trying first enabled button.")
initial_highlight_button = first_enabled_button # 使用第一个可用的按钮作为默认高亮
# 如果找到了要高亮的按钮 (无论是匹配到的还是第一个可用的)
if initial_highlight_button:
print("Button to initially highlight: ", initial_highlight_button.name)
# 设置逻辑高亮状态并尝试让其获得焦点
_set_initial_highlight(initial_highlight_button)
else:
# 如果连第一个可用的按钮都没有 (例如所有策略都未启用)
print("No button available to highlight.")
explanation_label.text = "没有可用的开发策略。" # 更新说明文本
print("--- Finished Updating Strategy List ---")
# --- 交互处理 ---
# 处理选项按钮点击事件的函数
func _on_option_button_pressed(button_node: Button) -> void:
# 从按钮元数据获取对应的策略名称
var strategy_name: String = button_node.get_meta("strategy_name", "")
if strategy_name.is_empty():
printerr("Strategy: 无法从按钮 %s 获取 strategy_name 元数据。" % button_node.name)
return
# 检查点击的按钮是否就是当前已逻辑高亮的按钮
if button_node == highlighted_button:
# --- 第二次点击 (确认选择) ---
print("Strategy: Confirmed selection - ", strategy_name)
# 1. 通知父节点更新临时选择状态
var parent_node = get_parent() # 获取父节点 (应该是 task_development)
if parent_node and parent_node.has_method("update_task_options"):
# 调用父节点的接口,传递更新信息 (使用中文键名)
parent_node.update_task_options({"开发策略": strategy_name})
print("Strategy: Updated parent task options with 开发策略 = ", strategy_name)
else:
printerr("Strategy: Parent node not found or missing 'update_task_options' method.")
# 2. 请求父节点关闭当前弹窗并返回主界面
if parent_node and parent_node.has_method("_close_child_popup_and_return"):
parent_node._close_child_popup_and_return(self)
else:
# 作为备选方案,如果找不到父节点或方法,直接隐藏自己
printerr("Strategy: Parent node not found or missing '_close_child_popup_and_return' method. Hiding self.")
self.hide()
else:
# --- 第一次点击 或 点击了不同的按钮 (更新逻辑选择和说明) ---
print("Strategy: Logically selected - ", strategy_name)
# 更新当前逻辑高亮的按钮引用
highlighted_button = button_node
# 更新底部的说明文本
# 确保 current_strategy_data 是最新的 (理论上 _update_strategy_list 刚更新过)
if current_strategy_data.has(strategy_name):
var details: Dictionary = current_strategy_data[strategy_name]
explanation_label.text = details.get("Explaination", "暂无说明。") # 使用 .get 提供默认值
else:
# 如果因为某些原因数据不同步,显示错误
explanation_label.text = "错误:找不到策略详情。"
printerr("Strategy: Could not find details for strategy '", strategy_name, "' in current_strategy_data.")
# 注意:此时按钮的视觉焦点可能还未改变,依赖于 Button 主题的 focus 样式
# _set_initial_highlight 内部会处理 grab_focus
# --- 辅助函数 ---
# (辅助函数) 重置界面逻辑高亮状态
func _reset_highlight_state() -> void:
highlighted_button = null # 清除逻辑高亮引用
explanation_label.text = "" # 清空说明文本
# (辅助函数) 设置初始逻辑选中按钮并赋予焦点
func _set_initial_highlight(button_to_highlight: Button) -> void:
print("--- Setting Initial Highlight ---")
if not is_instance_valid(button_to_highlight):
printerr("Strategy: Invalid button passed to _set_initial_highlight.")
return
print("Button received: ", button_to_highlight.name)
# 设置逻辑高亮引用
highlighted_button = button_to_highlight
print("highlighted_button variable set to: ", highlighted_button.name)
# 更新说明文本
var strategy_name = highlighted_button.get_meta("strategy_name", "")
if not strategy_name.is_empty() and current_strategy_data.has(strategy_name):
var details: Dictionary = current_strategy_data[strategy_name]
explanation_label.text = details.get("Explaination", "暂无说明。")
elif not strategy_name.is_empty():
explanation_label.text = "错误:找不到策略详情。"
printerr("Strategy: Could not find details for highlighted strategy '", strategy_name, "'")
else:
explanation_label.text = "错误:无法获取策略名称。" # 如果连元数据都没有
# 尝试让按钮获得实际输入焦点 (使用 call_deferred 确保在安全的时机执行)
# 这会让按钮应用其主题中的 "focus" 样式
if highlighted_button.is_inside_tree() and highlighted_button.visible and not highlighted_button.disabled:
print("Attempting call_deferred('grab_focus') for: ", highlighted_button.name)
highlighted_button.call_deferred("grab_focus")
else:
print("Button '%s' not valid, not in tree, hidden, or disabled. Cannot grab focus." % highlighted_button.name)
print("--- Finished Setting Initial Highlight ---")

View File

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

View File

@ -0,0 +1,351 @@
[gd_scene load_steps=5 format=3 uid="uid://ctcbxvgljrkvi"]
[ext_resource type="Texture2D" uid="uid://pucudatqrcrq" path="res://UI/popup/popup_bg_1.png" id="1_pbd5q"]
[ext_resource type="Script" uid="uid://b5mfw2kc6lqpd" path="res://UI/popup/task_development_bak/strategy/strategy.gd" id="2_rq5uk"]
[ext_resource type="Theme" uid="uid://ddq54ba6vwyn0" path="res://UI/popup/task_development/main_plan/gameplay/gameplay_option_button.tres" id="3_rq5uk"]
[ext_resource type="Texture2D" uid="uid://bb3kyiufyyj05" path="res://Entity/NPC/NPC_1_UI.png" id="4_pbd5q"]
[node name="strategy" type="NinePatchRect"]
custom_minimum_size = Vector2(500, 350)
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -250.0
offset_top = -175.0
offset_right = 250.0
offset_bottom = 175.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("1_pbd5q")
region_rect = Rect2(1, 35, 86, 53)
patch_margin_left = 8
patch_margin_top = 35
patch_margin_right = 8
patch_margin_bottom = 32
script = ExtResource("2_rq5uk")
[node name="Title" type="NinePatchRect" parent="."]
custom_minimum_size = Vector2(500, 0)
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -250.0
offset_right = 250.0
offset_bottom = 44.0
grow_horizontal = 2
texture = ExtResource("1_pbd5q")
region_rect = Rect2(1, 27, 86, 8)
patch_margin_left = 8
patch_margin_top = 7
patch_margin_right = 8
patch_margin_bottom = 6
[node name="Title" type="Label" parent="Title"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -34.5
offset_top = -18.0
offset_right = 34.5
offset_bottom = 18.0
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 27
text = "开发策略"
vertical_alignment = 1
[node name="Option" type="Control" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="Column_Headers" type="HBoxContainer" parent="Option"]
custom_minimum_size = Vector2(420, 0)
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -210.0
offset_top = 51.0
offset_right = 210.0
offset_bottom = 82.0
grow_horizontal = 2
theme_override_constants/separation = 0
[node name="Title" type="Label" parent="Option/Column_Headers"]
custom_minimum_size = Vector2(320, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 22
text = "开发方针"
[node name="Cost" type="Label" parent="Option/Column_Headers"]
custom_minimum_size = Vector2(80, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 22
text = "成本"
horizontal_alignment = 2
[node name="Options" type="NinePatchRect" parent="Option"]
custom_minimum_size = Vector2(440, 170)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -220.0
offset_top = -92.0
offset_right = 220.0
offset_bottom = 78.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("1_pbd5q")
region_rect = Rect2(92, 38, 76, 34)
patch_margin_left = 2
patch_margin_top = 33
patch_margin_right = 2
patch_margin_bottom = 33
[node name="List_1" type="VBoxContainer" parent="Option/Options"]
custom_minimum_size = Vector2(436, 160)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -210.0
offset_top = -80.0
offset_right = 210.0
offset_bottom = 80.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 0
[node name="option_1" type="Button" parent="Option/Options/List_1"]
custom_minimum_size = Vector2(0, 32)
layout_mode = 2
theme = ExtResource("3_rq5uk")
disabled = true
flat = true
[node name="Row" type="HBoxContainer" parent="Option/Options/List_1/option_1"]
custom_minimum_size = Vector2(420, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -210.0
offset_top = -15.5
offset_right = 210.0
offset_bottom = 15.5
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 0
[node name="Title" type="Label" parent="Option/Options/List_1/option_1/Row"]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
text = "正常开发"
[node name="Cost" type="Label" parent="Option/Options/List_1/option_1/Row"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
text = "0"
horizontal_alignment = 2
[node name="option_2" type="Button" parent="Option/Options/List_1"]
custom_minimum_size = Vector2(0, 32)
layout_mode = 2
theme = ExtResource("3_rq5uk")
disabled = true
flat = true
[node name="Row" type="HBoxContainer" parent="Option/Options/List_1/option_2"]
custom_minimum_size = Vector2(420, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -210.0
offset_top = -15.5
offset_right = 210.0
offset_bottom = 15.5
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 0
[node name="Title" type="Label" parent="Option/Options/List_1/option_2/Row"]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
[node name="Cost" type="Label" parent="Option/Options/List_1/option_2/Row"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
horizontal_alignment = 2
[node name="option_3" type="Button" parent="Option/Options/List_1"]
custom_minimum_size = Vector2(0, 32)
layout_mode = 2
theme = ExtResource("3_rq5uk")
disabled = true
flat = true
[node name="Row" type="HBoxContainer" parent="Option/Options/List_1/option_3"]
custom_minimum_size = Vector2(420, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -210.0
offset_top = -15.5
offset_right = 210.0
offset_bottom = 15.5
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 0
[node name="Title" type="Label" parent="Option/Options/List_1/option_3/Row"]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
[node name="Cost" type="Label" parent="Option/Options/List_1/option_3/Row"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
horizontal_alignment = 2
[node name="option_4" type="Button" parent="Option/Options/List_1"]
custom_minimum_size = Vector2(0, 32)
layout_mode = 2
theme = ExtResource("3_rq5uk")
disabled = true
flat = true
[node name="Row" type="HBoxContainer" parent="Option/Options/List_1/option_4"]
custom_minimum_size = Vector2(420, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -210.0
offset_top = -15.5
offset_right = 210.0
offset_bottom = 15.5
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 0
[node name="Title" type="Label" parent="Option/Options/List_1/option_4/Row"]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
[node name="Cost" type="Label" parent="Option/Options/List_1/option_4/Row"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
horizontal_alignment = 2
[node name="option_5" type="Button" parent="Option/Options/List_1"]
custom_minimum_size = Vector2(0, 32)
layout_mode = 2
theme = ExtResource("3_rq5uk")
disabled = true
flat = true
[node name="Row" type="HBoxContainer" parent="Option/Options/List_1/option_5"]
custom_minimum_size = Vector2(420, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -210.0
offset_top = -15.5
offset_right = 210.0
offset_bottom = 15.5
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 0
[node name="Title" type="Label" parent="Option/Options/List_1/option_5/Row"]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
[node name="Cost" type="Label" parent="Option/Options/List_1/option_5/Row"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
horizontal_alignment = 2
[node name="Explaination" type="Label" parent="Option"]
custom_minimum_size = Vector2(200, 60)
layout_mode = 1
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 47.0
offset_top = -83.0
offset_right = 358.0
offset_bottom = -23.0
grow_vertical = 0
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
text = "说明"
horizontal_alignment = 1
vertical_alignment = 1
autowrap_mode = 3
[node name="Human" type="TextureRect" parent="Option"]
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -128.0
offset_top = -90.0
offset_right = -55.0
offset_bottom = -17.0
grow_horizontal = 0
grow_vertical = 0
texture = ExtResource("4_pbd5q")

View File

@ -0,0 +1,381 @@
# task_development.gd
extends Control
# --- 节点引用 ---
@onready var confirm_button = $main_page/Confirm
@onready var platform_button = $"main_page/Part_3/Select_List/1_right/Button"
@onready var gameplay_button = $"main_page/Part_3/Select_List/2_right/Button"
@onready var theme_button = $"main_page/Part_3/Select_List/3_right/Button"
@onready var strategy_button = $"main_page/Part_3/Select_List/4_right/Button"
@onready var budget_button = $"main_page/Part_2/HBoxContainer/Button"
@onready var main_page = $main_page
@onready var platform_popup = $platform
@onready var gameplay_popup = $gameplay
@onready var theme_popup = $theme
@onready var strategy_popup = $strategy
@onready var product_focus = $product_focus
@onready var dialogue: Control = $dialogue
# --- 内部状态 ---
var pop_up_list: Array = [] # 记录打开的UI弹出窗口
var node_name: String
var is_active: bool = false
# 存储用户在当前配置过程中的临时选择 (使用中文 Key)
var task_info: Dictionary = {
"平台": "",
"玩法": "",
"题材": "",
"开发策略": "",
"预算": 0,
"产品侧重点": {},
"关键环节负责人": [] # <--- 新增并初始化为空数组
}
# --- 初始化 ---
func _ready() -> void:
node_name = str(self.name)
add_to_group(node_name) # 用于 UI_MAIN 查找
main_page.hide()
# 连接信号
confirm_button.pressed.connect(_on_confirm_button_pressed)
platform_button.pressed.connect(_on_category_button_pressed.bind(platform_popup))
gameplay_button.pressed.connect(_on_category_button_pressed.bind(gameplay_popup))
theme_button.pressed.connect(_on_category_button_pressed.bind(theme_popup))
strategy_button.pressed.connect(_on_category_button_pressed.bind(strategy_popup))
# 注意:不再需要加载静态数据
# --- 核心显示与关闭逻辑 ---
func show_up():
if GameState.is_on_task_status():
self.visible = true
show_dialogue("正在进行其它项目","")
return
print(node_name + ": Showing up.")
# 1. 设置界面状态
main_page.show()
self.visible = true
is_active = true
pop_up_list.clear()
pop_up_list.append(self)
# 2. 设置默认选项 (从 GameState 读取)
_set_default_selections()
# 3. 更新按钮文本和预算 (基于默认选项)
update_button_texts()
calculate_and_update_budget() # 初始计算预算
# 4. 暂停游戏并处理输入
GameState.pause_game()
set_process_input(true)
func close_all_popups():
print(node_name + ": Closing all popups...")
while not pop_up_list.is_empty():
var popup = pop_up_list.pop_back()
if popup and is_instance_valid(popup):
popup.visible = false
self.visible = false
is_active = false
if GameState: GameState.resume_game() # 假设 GameState 有此方法
set_process_input(false)
# --- 数据初始化 (从 GameState 设置默认值) ---
# 根据 GameState 设置默认选项
func _set_default_selections() -> void:
if not GameState:
printerr(node_name + ": GameState not available during _set_default_selections.")
# 将所有选项设置为空,让用户必须选择
task_info["平台"] = ""
task_info["玩法"] = ""
task_info["题材"] = ""
task_info["开发策略"] = ""
return
var task_dev_data = GameState.get_value("task_development", {})
if task_dev_data.is_empty():
printerr(node_name + ": Failed to get 'task_development' data from GameState during _set_default_selections.")
# 将所有选项设置为空
task_info["平台"] = ""
task_info["玩法"] = ""
task_info["题材"] = ""
task_info["开发策略"] = ""
return
# 定义 GameState 中的 Key 和 task_info 中的 Key 的映射
var categories_map = {
"平台": "platforms",
"玩法": "gameplays",
"题材": "themes",
"开发策略": "strategies"
}
# 为每个类别查找第一个启用的选项
for task_key in categories_map:
var gamestate_key = categories_map[task_key]
var items_data = task_dev_data.get(gamestate_key, {})
var default_item_key = _find_first_enabled_key_in_gamestate(items_data)
task_info[task_key] = default_item_key # 如果没找到,会是 ""
if default_item_key.is_empty():
print(node_name + ": No enabled default found for category '%s'." % task_key)
print(node_name + ": Default selections set from GameState: ", task_info)
# 辅助函数:在 GameState 的某个类别数据中查找第一个 enabled 的 key (中文名称)
func _find_first_enabled_key_in_gamestate(items_data: Dictionary) -> String:
if items_data.is_empty():
return ""
# 尝试按某种顺序查找,如果字典是无序的,结果可能不固定
# 如果需要固定顺序,可能需要在 GameState 中存储时就排序或提供顺序信息
for item_key in items_data:
var item_details = items_data[item_key]
if typeof(item_details) == TYPE_DICTIONARY and item_details.get("enabled", false) == true:
return item_key # 返回第一个找到的 enabled key (中文名称)
# 如果没有找到 enabled 的
return ""
# --- 预算计算与 UI 更新 ---
# 计算并更新预算显示 (完全基于 GameState 数据)
func calculate_and_update_budget() -> void:
if not GameState:
printerr(node_name + ": Cannot calculate budget, GameState not available.")
task_info["预算"] = 0
if budget_button: budget_button.text = "错误"
return
var task_dev_data = GameState.get_value("task_development", {})
if task_dev_data.is_empty():
printerr(node_name + ": Cannot calculate budget, 'task_development' data not found in GameState.")
task_info["预算"] = 0
if budget_button: budget_button.text = "错误"
return
var platform_cost: float = 0.0
var gameplay_cost: float = 0.0
var theme_cost: float = 0.0
var strategy_multiplier: float = 0.0 # 策略的 "Cost" 是乘数因子
# 1. 获取平台成本
var platform_key = task_info["平台"]
if not platform_key.is_empty():
var platform_data = task_dev_data.get("platforms", {}).get(platform_key, {})
platform_cost = platform_data.get("Cost", 0.0)
else:
print(node_name + ": Platform not selected, cost is 0.")
# 2. 获取玩法成本
var gameplay_key = task_info["玩法"]
if not gameplay_key.is_empty():
var gameplay_data = task_dev_data.get("gameplays", {}).get(gameplay_key, {})
gameplay_cost = gameplay_data.get("Cost", 0.0)
else:
print(node_name + ": Gameplay not selected, cost is 0.")
# 3. 获取题材成本
var theme_key = task_info["题材"]
if not theme_key.is_empty():
var theme_data = task_dev_data.get("themes", {}).get(theme_key, {})
theme_cost = theme_data.get("Cost", 0.0)
else:
print(node_name + ": Theme not selected, cost is 0.")
# 4. 获取策略成本乘数
var strategy_key = task_info["开发策略"]
if not strategy_key.is_empty():
var strategy_data = task_dev_data.get("strategies", {}).get(strategy_key, {})
# 假设策略的 "Cost" 字段存储的是成本增加的百分比 (例如 0.2 代表增加 20%)
strategy_multiplier = strategy_data.get("Cost", 0.0)
else:
print(node_name + ": Strategy not selected, multiplier is 0.")
# 计算总预算: (平台 + 玩法 + 题材) * (1 + 策略乘数)
var base_cost = platform_cost + gameplay_cost + theme_cost
var calculated_budget = int(base_cost * (1.0 + strategy_multiplier)) # 结果取整
task_info["预算"] = calculated_budget
# 更新预算显示
if budget_button:
budget_button.text = str(calculated_budget)
# print(node_name + ": Budget updated to " + str(calculated_budget)) # Debug
else:
printerr(node_name + ": Budget button not found when updating budget.")
# 更新所有主界面按钮文本 (直接使用 task_info 中的中文 Key)
func update_button_texts() -> void:
var placeholder_text = "请选择" # 如果未选择,显示的文本
platform_button.text = task_info["平台"] if not task_info["平台"].is_empty() else placeholder_text
gameplay_button.text = task_info["玩法"] if not task_info["玩法"].is_empty() else placeholder_text
theme_button.text = task_info["题材"] if not task_info["题材"].is_empty() else placeholder_text
strategy_button.text = task_info["开发策略"] if not task_info["开发策略"].is_empty() else placeholder_text
# --- 子窗口交互与数据更新 ---
# 提供给子节点调用的接口,用于启动此节点内的对话框
# dialogues: String 或 PackedStringArray - 要显示的对话内容
# next_node: Control (可选) - 对话结束后要显示的下一个UI节点
func show_dialogue(dialogues, next_node_name: String = "") -> void:
if not is_instance_valid(dialogue):
printerr(node_name + ": Dialogue node is not valid or ready.")
return
if not dialogue.has_method("_start"):
printerr(node_name + ": Dialogue node does not have a _start method.")
return
print(node_name + ": Requesting to show dialogue with content: ", dialogues)
# 调用 dialogue 节点的 _start 方法来显示对话框
# 注意:这里假设 dialogue 节点的 _start 方法会处理好显示、暂停游戏等逻辑
var next_node = get_node_or_null(next_node_name)
dialogue._start(dialogues, next_node)
# 可选如果希望对话框显示时task_development 的其他部分(如 main_page 或子弹窗)隐藏,
# 可以在这里添加隐藏逻辑,但这取决于具体交互需求。
# 例如:
# if main_page.visible: main_page.hide()
# if not pop_up_list.is_empty() and pop_up_list[-1] != self and is_instance_valid(pop_up_list[-1]):
# pop_up_list[-1].hide() # 隐藏最上层的子弹窗
# 处理分类按钮点击的通用函数
func _on_category_button_pressed(popup_node: Control) -> void:
if not is_instance_valid(popup_node):
printerr(node_name + ": Invalid popup node provided for category button.")
return
if not GameState:
printerr(node_name + ": Cannot open popup, GameState not available.")
return
# 子窗口现在自行从 GameState 加载数据,无需父窗口传递
# 只需要显示对应的弹窗即可
# --- 确保子窗口在显示时会刷新其内容 ---
# 最好在子窗口的 _on_visibility_changed 或类似方法中处理数据加载
# 如果子窗口没有这个逻辑,可以在这里强制调用一下(如果它们有 reset_display 方法)
if popup_node.has_method("reset_display"):
popup_node.reset_display()
elif popup_node.has_method("_on_visibility_changed"): # 备选方案
# 注意:直接调用 _on_visibility_changed 可能不符合预期,因为它通常由引擎触发
# 更好的方式是确保子窗口的 _ready 或 visibility_changed 信号连接正确并处理加载
pass
main_page.hide()
popup_node.show() # 显示弹窗
pop_up_list.append(popup_node)
print(node_name + ": Showing category popup: ", popup_node.name)
# 提供给子弹出窗口调用的接口函数,用于更新选择
# options: 字典,包含更新的类别和对应的 Key
func update_task_options(options: Dictionary) -> void:
var updated = false
for key in options:
var new_value = options[key]
if key == "关键环节负责人":
# --- 特殊处理 "关键环节负责人" ---
if not task_info.has(key) or not typeof(task_info[key]) == TYPE_ARRAY:
task_info[key] = [] # 如果不存在或类型不对,则初始化为空数组
if not new_value in task_info[key]: # 避免重复添加 (可选,根据需求)
task_info[key].append(new_value)
updated = true
print(node_name + ": Appended '%s' to '%s'. Current: %s" % [new_value, key, task_info[key]])
else:
print(node_name + ": '%s' already in '%s'. No change." % [new_value, key])
elif task_info.has(key):
# --- 原有逻辑处理其他普通键值对 ---
if task_info[key] != new_value:
task_info[key] = new_value
updated = true
print(node_name + ": Option '%s' updated to '%s'." % [key, new_value])
else:
printerr(node_name + ": Invalid key '%s' provided in update_task_options and not handled as a special case." % key)
if updated:
# 对于 "关键环节负责人" 的更新,通常不需要更新按钮文本或重新计算预算
# 但如果其他选项更新了,则需要执行
if not (options.has("关键环节负责人") and options.size() == 1): # 如果不仅仅是更新负责人
update_button_texts()
calculate_and_update_budget()
print(node_name + ": Task info updated: ", task_info)
# 处理子弹出窗口确认后关闭并返回主页面的逻辑
func _close_child_popup_and_return(child_popup_node: Control) -> void:
if not is_instance_valid(child_popup_node):
printerr(node_name + ": Invalid child popup node passed.")
return
print(node_name + ": Request received to close child popup '%s' and return." % child_popup_node.name)
if child_popup_node in pop_up_list:
pop_up_list.erase(child_popup_node)
else:
printerr(node_name + ": Child popup '%s' was not found in pop_up_list during close request." % child_popup_node.name)
child_popup_node.hide()
main_page.show()
print(node_name + ": Hid child popup '%s' and showed main_page." % child_popup_node.name)
# --- 输入处理 ---
# (保持不变)
func _input(event):
if not is_active: return
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed():
if pop_up_list.size() > 0:
var last_popup = pop_up_list.pop_back()
if last_popup and is_instance_valid(last_popup):
if last_popup != self:
last_popup.visible = false
print(node_name + ": Closed popup via right-click: ", last_popup.name)
# 检查是否需要重显主页面
if not pop_up_list.is_empty() and pop_up_list[-1] == self and not main_page.visible:
main_page.show()
print(node_name + ": Re-showing main_page after closing sub-popup.")
get_viewport().set_input_as_handled()
else:
# 在主界面右键点击,关闭整个界面
print(node_name + ": Right-click on main page. Closing task_development.")
pop_up_list.append(last_popup) # 把 self 放回去,让下面的判断处理关闭
# 如果列表为空(或只剩 self 且刚刚弹出的是 self则关闭所有
if pop_up_list.is_empty() or (pop_up_list.size() == 1 and pop_up_list[0] == self and last_popup == self):
print(node_name + ": Closing task_development due to right-click logic.")
close_all_popups()
# 不需要 set_input_as_handled因为界面关闭了
# --- 确认与后续流程 ---
func _on_confirm_button_pressed():
print(node_name + ": Confirmed with task_info: ", task_info)
# 检查是否所有选项都已选择 (Key 不为空)
if task_info["平台"].is_empty() or \
task_info["玩法"].is_empty() or \
task_info["题材"].is_empty() or \
task_info["开发策略"].is_empty():
printerr(node_name + ": Cannot confirm, not all options are selected.")
# 此处可以添加用户提示
var ui_main = get_tree().get_first_node_in_group("UI_MAIN")
if ui_main and ui_main.has_method("show_notification"):
ui_main.show_notification("请确保所有开发选项都已选择!")
return
# --- 触发后续流程 ---
product_focus.show()
main_page.hide()
# 发出开始任务的信号
func start_task():
print(node_name + ": start task with info: ", task_info)
GameState.set_system_status_value("is_on_task", true)

View File

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

View File

@ -0,0 +1,548 @@
[gd_scene load_steps=33 format=3 uid="uid://b4ll7wwg1s0qc"]
[ext_resource type="Texture2D" uid="uid://pucudatqrcrq" path="res://UI/popup/popup_bg_1.png" id="1_57348"]
[ext_resource type="Script" uid="uid://chgujg3vq0jbl" path="res://UI/popup/task_development_bak/task_development.gd" id="1_v0qi7"]
[ext_resource type="Texture2D" uid="uid://bk8155f5le6k4" path="res://UI/main/icon_money.png" id="3_lcrvn"]
[ext_resource type="Theme" uid="uid://bau80ps6kx783" path="res://UI/tres/Bottom_Info_button_theme.tres" id="4_sqys5"]
[ext_resource type="LabelSettings" uid="uid://cvwpqds25xnfs" path="res://UI/tres/ui_main_label_number.tres" id="4_xtl2p"]
[ext_resource type="FontFile" uid="uid://egugs822n8gr" path="res://UI/font/AlimamaFangYuanTiVF-Thin.ttf" id="5_qrj6y"]
[ext_resource type="PackedScene" uid="uid://blv6i6w651pcs" path="res://UI/popup/task_development_bak/platform/platform.tscn" id="6_eatmm"]
[ext_resource type="PackedScene" uid="uid://c7brdhj8ybu1y" path="res://UI/popup/task_development_bak/gameplay/gameplay.tscn" id="7_xfbvu"]
[ext_resource type="PackedScene" uid="uid://bej6f0cqirn4j" path="res://UI/popup/task_development_bak/theme/theme.tscn" id="8_nckjb"]
[ext_resource type="PackedScene" uid="uid://ctcbxvgljrkvi" path="res://UI/popup/task_development_bak/strategy/strategy.tscn" id="9_6r1e0"]
[ext_resource type="PackedScene" uid="uid://dpshanwm4o3by" path="res://UI/popup/task_development_bak/product_focus/product_focus.tscn" id="10_at31c"]
[ext_resource type="PackedScene" uid="uid://be000l53jxash" path="res://UI/popup/task_development_bak/key_output/proposal.tscn" id="12_v0qi7"]
[ext_resource type="PackedScene" uid="uid://dtywh0m5odikx" path="res://UI/popup/dialogue.tscn" id="13_kctqw"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_xtl2p"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_sqys5"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ts1uk"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ka0mh"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_4dqyo"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_k5x0l"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_rl8fy"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ka0mh"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_sqys5"]
bg_color = Color(0.639216, 0.639216, 0.639216, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4dqyo"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ln7qd"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_iashq"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_kfl1n"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_20m4o"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_rl25l"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tndxe"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xx8ge"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ep6fu"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ci4su"]
[node name="task_development" type="Control"]
custom_minimum_size = Vector2(1000, 600)
layout_mode = 3
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -500.0
offset_top = -300.0
offset_right = 500.0
offset_bottom = 300.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_v0qi7")
[node name="main_page" type="NinePatchRect" parent="."]
custom_minimum_size = Vector2(500, 350)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -250.0
offset_top = -175.0
offset_right = 250.0
offset_bottom = 175.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("1_57348")
region_rect = Rect2(1, 35, 86, 53)
patch_margin_left = 8
patch_margin_top = 35
patch_margin_right = 8
patch_margin_bottom = 32
[node name="Part_1" type="NinePatchRect" parent="main_page"]
custom_minimum_size = Vector2(500, 0)
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -250.0
offset_right = 250.0
offset_bottom = 44.0
grow_horizontal = 2
texture = ExtResource("1_57348")
region_rect = Rect2(1, 27, 86, 8)
patch_margin_left = 8
patch_margin_top = 7
patch_margin_right = 8
patch_margin_bottom = 6
[node name="Title" type="Label" parent="main_page/Part_1"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -34.5
offset_top = -18.0
offset_right = 34.5
offset_bottom = 18.0
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 27
text = "新项目"
vertical_alignment = 1
[node name="Part_2" type="Control" parent="main_page"]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -20.0
offset_top = 34.0
offset_right = 20.0
offset_bottom = 53.0
grow_horizontal = 2
[node name="HBoxContainer" type="HBoxContainer" parent="main_page/Part_2"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -24.0
offset_top = 5.0
offset_right = 208.0
offset_bottom = 39.0
grow_horizontal = 2
grow_vertical = 2
[node name="Label" type="Label" parent="main_page/Part_2/HBoxContainer"]
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 24
text = "预算"
horizontal_alignment = 1
[node name="Button" type="Button" parent="main_page/Part_2/HBoxContainer"]
custom_minimum_size = Vector2(180, 0)
layout_mode = 2
theme_override_colors/font_disabled_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_pressed_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_color = Color(0, 0, 0, 1)
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_colors/font_pressed_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
theme_override_styles/focus = SubResource("StyleBoxEmpty_xtl2p")
theme_override_styles/disabled = SubResource("StyleBoxEmpty_sqys5")
theme_override_styles/hover_pressed = SubResource("StyleBoxEmpty_ts1uk")
theme_override_styles/hover = SubResource("StyleBoxEmpty_ka0mh")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_4dqyo")
theme_override_styles/normal = SubResource("StyleBoxEmpty_k5x0l")
text = "500"
icon = ExtResource("3_lcrvn")
flat = true
alignment = 2
icon_alignment = 2
[node name="Part_3" type="NinePatchRect" parent="main_page"]
custom_minimum_size = Vector2(440, 200)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -220.0
offset_top = -91.0
offset_right = 220.0
offset_bottom = 131.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("1_57348")
region_rect = Rect2(92, 38, 76, 34)
patch_margin_left = 2
patch_margin_top = 33
patch_margin_right = 2
patch_margin_bottom = 33
[node name="BG" type="NinePatchRect" parent="main_page/Part_3"]
custom_minimum_size = Vector2(440, 0)
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -220.0
offset_top = -127.0
offset_right = 220.0
offset_bottom = 2.0
grow_horizontal = 2
grow_vertical = 0
texture = ExtResource("1_57348")
region_rect = Rect2(100, 95, 240, 75)
[node name="Select_List" type="GridContainer" parent="main_page/Part_3"]
custom_minimum_size = Vector2(320, 0)
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -180.0
offset_top = 7.0
offset_right = 180.0
offset_bottom = 179.0
grow_horizontal = 2
size_flags_horizontal = 4
theme_override_constants/h_separation = 0
columns = 2
[node name="1_left" type="NinePatchRect" parent="main_page/Part_3/Select_List"]
custom_minimum_size = Vector2(140, 40)
layout_mode = 2
size_flags_horizontal = 4
texture = ExtResource("1_57348")
region_rect = Rect2(1, 95, 36, 24)
patch_margin_left = 7
patch_margin_top = 6
patch_margin_right = 2
patch_margin_bottom = 6
[node name="Label" type="Label" parent="main_page/Part_3/Select_List/1_left"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -60.0
offset_top = -12.0
offset_right = 60.0
offset_bottom = 24.0
grow_horizontal = 2
grow_vertical = 2
text = "平台"
label_settings = ExtResource("4_xtl2p")
horizontal_alignment = 1
[node name="1_right" type="NinePatchRect" parent="main_page/Part_3/Select_List"]
custom_minimum_size = Vector2(220, 40)
layout_mode = 2
size_flags_horizontal = 4
texture = ExtResource("1_57348")
region_rect = Rect2(38, 95, 50, 24)
patch_margin_top = 6
patch_margin_right = 6
patch_margin_bottom = 6
[node name="Button" type="Button" parent="main_page/Part_3/Select_List/1_right"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -70.0
offset_top = -17.5
offset_right = 70.0
offset_bottom = 17.5
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_fonts/font = ExtResource("5_qrj6y")
theme_override_font_sizes/font_size = 18
theme_override_styles/focus = SubResource("StyleBoxEmpty_rl8fy")
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_ka0mh")
theme_override_styles/hover = SubResource("StyleBoxFlat_sqys5")
theme_override_styles/pressed = SubResource("StyleBoxFlat_4dqyo")
theme_override_styles/normal = SubResource("StyleBoxEmpty_ln7qd")
text = "Steam & Epic"
[node name="2_left" type="NinePatchRect" parent="main_page/Part_3/Select_List"]
custom_minimum_size = Vector2(140, 40)
layout_mode = 2
size_flags_horizontal = 4
texture = ExtResource("1_57348")
region_rect = Rect2(1, 95, 36, 24)
patch_margin_left = 7
patch_margin_top = 6
patch_margin_right = 2
patch_margin_bottom = 6
[node name="Label" type="Label" parent="main_page/Part_3/Select_List/2_left"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -60.0
offset_top = -12.0
offset_right = 60.0
offset_bottom = 24.0
grow_horizontal = 2
grow_vertical = 2
text = "玩法"
label_settings = ExtResource("4_xtl2p")
horizontal_alignment = 1
[node name="2_right" type="NinePatchRect" parent="main_page/Part_3/Select_List"]
custom_minimum_size = Vector2(220, 40)
layout_mode = 2
size_flags_horizontal = 4
texture = ExtResource("1_57348")
region_rect = Rect2(38, 95, 50, 24)
patch_margin_top = 6
patch_margin_right = 6
patch_margin_bottom = 6
[node name="Button" type="Button" parent="main_page/Part_3/Select_List/2_right"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -70.0
offset_top = -17.5
offset_right = 70.0
offset_bottom = 17.5
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_fonts/font = ExtResource("5_qrj6y")
theme_override_font_sizes/font_size = 18
theme_override_styles/focus = SubResource("StyleBoxEmpty_iashq")
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_ka0mh")
theme_override_styles/hover = SubResource("StyleBoxFlat_sqys5")
theme_override_styles/pressed = SubResource("StyleBoxFlat_4dqyo")
theme_override_styles/normal = SubResource("StyleBoxEmpty_ln7qd")
text = "休闲放置"
[node name="3_left" type="NinePatchRect" parent="main_page/Part_3/Select_List"]
custom_minimum_size = Vector2(140, 40)
layout_mode = 2
size_flags_horizontal = 4
texture = ExtResource("1_57348")
region_rect = Rect2(1, 95, 36, 24)
patch_margin_left = 7
patch_margin_top = 6
patch_margin_right = 2
patch_margin_bottom = 6
[node name="Label" type="Label" parent="main_page/Part_3/Select_List/3_left"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -60.0
offset_top = -12.0
offset_right = 60.0
offset_bottom = 24.0
grow_horizontal = 2
grow_vertical = 2
text = "题材"
label_settings = ExtResource("4_xtl2p")
horizontal_alignment = 1
[node name="3_right" type="NinePatchRect" parent="main_page/Part_3/Select_List"]
custom_minimum_size = Vector2(220, 40)
layout_mode = 2
size_flags_horizontal = 4
texture = ExtResource("1_57348")
region_rect = Rect2(38, 95, 50, 24)
patch_margin_top = 6
patch_margin_right = 6
patch_margin_bottom = 6
[node name="Button" type="Button" parent="main_page/Part_3/Select_List/3_right"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -70.0
offset_top = -17.5
offset_right = 70.0
offset_bottom = 17.5
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_fonts/font = ExtResource("5_qrj6y")
theme_override_font_sizes/font_size = 18
theme_override_styles/focus = SubResource("StyleBoxEmpty_kfl1n")
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_ka0mh")
theme_override_styles/hover = SubResource("StyleBoxFlat_sqys5")
theme_override_styles/pressed = SubResource("StyleBoxFlat_4dqyo")
theme_override_styles/normal = SubResource("StyleBoxEmpty_ln7qd")
text = "古装仙侠"
[node name="4_left" type="NinePatchRect" parent="main_page/Part_3/Select_List"]
custom_minimum_size = Vector2(140, 40)
layout_mode = 2
size_flags_horizontal = 4
texture = ExtResource("1_57348")
region_rect = Rect2(1, 95, 36, 24)
patch_margin_left = 7
patch_margin_top = 6
patch_margin_right = 2
patch_margin_bottom = 6
[node name="Label" type="Label" parent="main_page/Part_3/Select_List/4_left"]
custom_minimum_size = Vector2(120, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -60.0
offset_top = -12.0
offset_right = 60.0
offset_bottom = 24.0
grow_horizontal = 2
grow_vertical = 2
text = "开发策略"
label_settings = ExtResource("4_xtl2p")
horizontal_alignment = 1
[node name="4_right" type="NinePatchRect" parent="main_page/Part_3/Select_List"]
custom_minimum_size = Vector2(220, 40)
layout_mode = 2
size_flags_horizontal = 4
texture = ExtResource("1_57348")
region_rect = Rect2(38, 95, 50, 24)
patch_margin_top = 6
patch_margin_right = 6
patch_margin_bottom = 6
[node name="Button" type="Button" parent="main_page/Part_3/Select_List/4_right"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -70.0
offset_top = -17.5
offset_right = 70.0
offset_bottom = 17.5
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_fonts/font = ExtResource("5_qrj6y")
theme_override_font_sizes/font_size = 18
theme_override_styles/focus = SubResource("StyleBoxEmpty_20m4o")
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_ka0mh")
theme_override_styles/hover = SubResource("StyleBoxFlat_sqys5")
theme_override_styles/pressed = SubResource("StyleBoxFlat_4dqyo")
theme_override_styles/normal = SubResource("StyleBoxEmpty_ln7qd")
text = "普通开发"
[node name="Confirm" type="Button" parent="main_page"]
custom_minimum_size = Vector2(70, 0)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -25.0
offset_top = 131.0
offset_right = 25.0
offset_bottom = 174.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("4_sqys5")
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 23
theme_override_styles/focus = SubResource("StyleBoxEmpty_rl25l")
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_tndxe")
theme_override_styles/hover = SubResource("StyleBoxFlat_xx8ge")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_ep6fu")
theme_override_styles/normal = SubResource("StyleBoxEmpty_ci4su")
text = "确定"
[node name="platform" parent="." instance=ExtResource("6_eatmm")]
visible = false
layout_mode = 1
[node name="gameplay" parent="." instance=ExtResource("7_xfbvu")]
visible = false
layout_mode = 1
[node name="theme" parent="." instance=ExtResource("8_nckjb")]
visible = false
layout_mode = 1
[node name="strategy" parent="." instance=ExtResource("9_6r1e0")]
visible = false
layout_mode = 1
[node name="product_focus" parent="." instance=ExtResource("10_at31c")]
visible = false
layout_mode = 1
[node name="proposal" parent="." instance=ExtResource("12_v0qi7")]
visible = false
layout_mode = 1
[node name="dialogue" parent="." instance=ExtResource("13_kctqw")]
visible = false
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ihwg8cc3h6ar"
path="res://.godot/imported/task_general.png-f911aa653606ea793cc4d95ffef10b5d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://UI/popup/task_development_bak/task_general.png"
dest_files=["res://.godot/imported/task_general.png-f911aa653606ea793cc4d95ffef10b5d.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

@ -0,0 +1,369 @@
# theme.gd (更新版 - 对齐 gameplay.gd 逻辑)
extends NinePatchRect
# UI Node References
@onready var list_container = $Option/Info
@onready var previous_button = $Title/previous/Button
@onready var next_button = $Title/next/Button
@onready var title_label = $Title/Title
# Configuration
var items_per_page: int = 7 # 每页显示的选项数量 (保持与原 theme.gd 一致)
# Data Storage
var theme_list_nodes: Array[VBoxContainer] = [] # 存储 List_1, List_2 等节点
var option_nodes_per_list: Array = [] # 存储每个 List 对应的 option_X 按钮数组
var all_enabled_themes: Array = [] # 存储筛选后的题材数据 { "name": String, "data": Dictionary }
# State
var total_pages: int = 0
var current_page_index: int = 0
var selected_theme_key: String = "" # 当前逻辑上选中的项的 Key (用于确认)
var currently_selected_button: Button = null # 当前高亮/聚焦的按钮节点引用
var initial_key_to_highlight: String = "" # 弹窗打开时需要高亮的 Key (由父节点设置)
func _ready():
# 1. 获取 List 节点 (与原版 theme.gd 类似,但使用 gameplay.gd 的错误处理)
for i in range(1, 7): # 假设最多有 List_6 (与原 theme.gd 匹配)
var list_node = list_container.get_node_or_null("List_" + str(i))
if list_node:
theme_list_nodes.append(list_node)
else:
break # 假设 List 节点是连续的
if theme_list_nodes.is_empty():
printerr("错误:在 theme.tscn 中未找到 List 节点!无法显示选项。")
set_process(false) # 禁用处理因为没有UI元素
return
# 2. 获取每个 List 内的 Option 节点 (与原版 theme.gd 类似)
for list_node in theme_list_nodes:
var current_list_options: Array = []
for i in range(1, items_per_page + 1):
var option_node = list_node.get_node_or_null("option_" + str(i))
if option_node and option_node is Button:
current_list_options.append(option_node)
else:
# 使用 gameplay.gd 的警告格式
push_warning("警告:在 %s 中未能找到 option_%d 按钮" % [list_node.name, i])
option_nodes_per_list.append(current_list_options)
# 3. 连接按钮信号 (与 gameplay.gd 一致)
if previous_button:
if not previous_button.pressed.is_connected(_on_previous_pressed):
previous_button.pressed.connect(_on_previous_pressed)
else: printerr("错误:在 theme.tscn 中未找到 Previous 按钮")
if next_button:
if not next_button.pressed.is_connected(_on_next_pressed):
next_button.pressed.connect(_on_next_pressed)
else: printerr("错误:在 theme.tscn 中未找到 Next 按钮")
# 4. 连接 visibility_changed 信号 (与 gameplay.gd 一致)
if not is_connected("visibility_changed", _on_visibility_changed):
visibility_changed.connect(_on_visibility_changed)
# 5. 移除 _ready 中的初始数据加载和显示更新
# 这些逻辑现在完全由 _on_visibility_changed -> reset_display 处理
# --- Visibility Change Handler (与 gameplay.gd 一致) ---
func _on_visibility_changed():
if is_visible_in_tree():
print("Theme: 弹窗变为可见,调用 reset_display()")
reset_display() # 当变为可见时调用 reset
# else: # 可选:隐藏时的清理逻辑
# print("Theme: 弹窗变为隐藏。")
# --- 新增:用于父节点设置初始选中项 (与 gameplay.gd 一致) ---
func set_initial_selection(key: String):
"""
(task_development)
task_info Key
"""
initial_key_to_highlight = key
print("Theme: 父节点设置初始高亮 Key 为: '%s'" % initial_key_to_highlight)
# --- Data Processing and Display Logic ---
func _process_theme_data(all_themes_data: Dictionary):
"""【修改】根据从 GameState 获取的题材数据,筛选出启用的题材,并计算总页数。"""
all_enabled_themes.clear()
if all_themes_data.is_empty():
push_warning("警告:接收到的题材数据为空。")
total_pages = 0
return
# 遍历从 GameState 获取的题材字典
for theme_name in all_themes_data:
var theme_info = all_themes_data[theme_name]
# 检查是否为字典且 "enabled" 字段为 true
if typeof(theme_info) == TYPE_DICTIONARY and theme_info.get("enabled", false) == true:
all_enabled_themes.append({"name": theme_name, "data": theme_info})
# else: # Debugging
# print("Theme: 过滤掉未启用或格式错误的题材: ", theme_name)
# 根据筛选结果计算总页数
if items_per_page > 0:
total_pages = ceil(float(all_enabled_themes.size()) / items_per_page)
else:
total_pages = 0
printerr("错误items_per_page 为零,无法计算页数。")
print("Theme: 处理完成,找到 %d 个启用的题材,共 %d 页。" % [all_enabled_themes.size(), total_pages])
func reset_display():
"""【修改】重置状态,从 GameState 重新加载数据,并准备更新显示。"""
print("Theme: 重置显示状态 (reset_display)。")
# 1. 重置本地状态变量
currently_selected_button = null
selected_theme_key = ""
# initial_key_to_highlight 由 set_initial_selection 设置,这里不清空
# 2. 【修改】从 GameState 重新加载并处理数据
if not GameState:
printerr("错误GameState 在 reset_display 期间不可用。")
total_pages = 0
all_enabled_themes.clear()
_update_display() # 即使没有数据也要更新显示(显示空状态)
return
# --- 获取题材数据 ---
var task_dev_data = GameState.get_value("task_development", {})
if task_dev_data.is_empty():
printerr("错误:从 GameState 获取 'task_development' 数据失败。")
total_pages = 0
all_enabled_themes.clear()
else:
# --- 从 task_development 中获取 themes ---
var all_themes_data = task_dev_data.get("themes", {})
_process_theme_data(all_themes_data) # 重新处理数据,计算 total_pages
# 3. 【修改】根据 initial_key_to_highlight 确定初始页面
current_page_index = 0 # 默认为第一页
var found_initial_key = false
if total_pages > 0 and not initial_key_to_highlight.is_empty():
# 查找 initial_key_to_highlight 所在的索引和页面
for i in range(all_enabled_themes.size()):
if all_enabled_themes[i].name == initial_key_to_highlight:
current_page_index = int(floor(float(i) / items_per_page))
found_initial_key = true
print("Theme: 找到需要初始高亮的 Key '%s' 在索引 %d, 目标页面 %d" % [initial_key_to_highlight, i, current_page_index])
break # 找到了,停止循环
if not found_initial_key:
print("Theme: 需要初始高亮的 Key '%s' 未在启用的题材中找到,将显示第一页。" % initial_key_to_highlight)
# 保持 current_page_index 为 0
initial_key_to_highlight = "" # 清空,避免后续 _update_display 尝试高亮不存在的项
# 确保页面索引有效 (与 gameplay.gd 一致)
if total_pages == 0:
current_page_index = 0
elif current_page_index >= total_pages:
current_page_index = total_pages - 1
elif current_page_index < 0:
current_page_index = 0
# 4. 触发显示更新
_update_display()
func _update_display():
"""【更新版】更新列表的可见性并填充当前页面的内容。
"""
# --- 处理无数据情况 (与 gameplay.gd 一致) ---
if theme_list_nodes.is_empty():
print("Theme: 无可用的 List 节点。")
if title_label: title_label.text = "题材选择"
return
if total_pages == 0:
for list_node in theme_list_nodes: list_node.visible = false
print("Theme: 没有启用的题材选项可供显示。")
if title_label: title_label.text = "题材选择 (无可用)"
if previous_button: previous_button.disabled = true
if next_button: next_button.disabled = true
return
else:
if previous_button: previous_button.disabled = (total_pages <= 1)
if next_button: next_button.disabled = (total_pages <= 1)
# --- 更新页面索引 (循环) (与 gameplay.gd 一致) ---
if total_pages > 0 :
current_page_index = current_page_index % total_pages
if current_page_index < 0:
current_page_index += total_pages
else:
current_page_index = 0
# --- 更新标题 (与 gameplay.gd 一致) ---
if title_label:
var display_page_number = current_page_index + 1
title_label.text = "题材选择 %d/%d" % [display_page_number, total_pages]
# --- 更新 List 可见性 (与 gameplay.gd 一致) ---
for i in range(theme_list_nodes.size()):
theme_list_nodes[i].visible = (i == current_page_index)
# --- 填充可见列表并查找要高亮的按钮 (与 gameplay.gd 类似) ---
var button_to_highlight: Button = null
var first_enabled_button_on_page: Button = null
if current_page_index < option_nodes_per_list.size():
var current_options = option_nodes_per_list[current_page_index]
var start_index = current_page_index * items_per_page
for i in range(current_options.size()):
var option_button = current_options[i]
var item_index = start_index + i
# --- 断开旧信号 ---
if option_button.pressed.is_connected(_on_option_selected):
option_button.pressed.disconnect(_on_option_selected)
if item_index < all_enabled_themes.size():
var theme_entry = all_enabled_themes[item_index]
var theme_name = theme_entry.name
var theme_data = theme_entry.data # 包含 Cost, Experience, Popularity 等
var row = option_button.get_node_or_null("Row")
if row:
# 填充标签文本
var title_label_in_row = row.get_node_or_null("Title")
var exp_label = row.get_node_or_null("Experience")
var pop_label = row.get_node_or_null("Popularity")
var cost_label = row.get_node_or_null("Cost") if row.has_node("Cost") else row.get_node_or_null("cost")
if title_label_in_row: title_label_in_row.text = theme_name
# 【修改】设置经验值,显示为整数
if exp_label:
var experience_value = theme_data.get("Experience", 0.0)
exp_label.text = str(int(experience_value))
# 【保持】设置流行度,保持原始字符串显示
if pop_label:
var popularity_value = theme_data.get("Popularity", "未知")
pop_label.text = str(popularity_value)
# 【修改】设置成本,显示为整数
if cost_label:
var cost_value = theme_data.get("Cost", 0.0)
cost_label.text = str(int(cost_value))
option_button.visible = true
option_button.disabled = false
option_button.set_meta("theme_name", theme_name) # 存储题材名称
# --- 连接新信号 (使用 bind) ---
option_button.pressed.connect(_on_option_selected.bind(theme_name, option_button))
if first_enabled_button_on_page == null:
first_enabled_button_on_page = option_button
if not initial_key_to_highlight.is_empty() and theme_name == initial_key_to_highlight:
button_to_highlight = option_button
# print("Theme: 找到需要初始高亮的按钮: ", button_to_highlight.name) # Debug
else: # 未找到 Row 节点
push_warning("警告:在按钮 %s 中未找到 Row 节点" % option_button.get_path())
option_button.visible = false
option_button.disabled = true
else: # 此槽位没有对应的启用题材数据
option_button.visible = false
option_button.disabled = true
else:
printerr("错误:当前页面索引 %d 超出 option_nodes_per_list 的范围 (大小 %d)" % [current_page_index, option_nodes_per_list.size()])
# --- 设置初始高亮和焦点 (与 gameplay.gd 一致) ---
if button_to_highlight:
_set_initial_highlight(button_to_highlight)
elif first_enabled_button_on_page:
print("Theme: 初始 Key '%s' 不在本页或为空。高亮本页第一个按钮: %s" % [initial_key_to_highlight, first_enabled_button_on_page.name])
_set_initial_highlight(first_enabled_button_on_page)
else:
print("Theme: 本页没有可供高亮的按钮。")
currently_selected_button = null
selected_theme_key = ""
# (Helper) 设置初始高亮和焦点 (与 gameplay.gd 一致)
func _set_initial_highlight(button_to_highlight: Button):
if not is_instance_valid(button_to_highlight):
printerr("错误:传递给 _set_initial_highlight 的按钮无效。")
currently_selected_button = null
selected_theme_key = ""
return
# print("Theme: 设置高亮按钮: ", button_to_highlight.name) # Debug
currently_selected_button = button_to_highlight
# --- 修改元数据键名 ---
selected_theme_key = button_to_highlight.get_meta("theme_name", "") # 同时设置 Key
# print("Theme: currently_selected_button 设置为: ", currently_selected_button.name) # Debug
# print("Theme: selected_theme_key 设置为: '", selected_theme_key, "'") # Debug
if currently_selected_button and currently_selected_button.is_inside_tree():
# print("Theme: 尝试为按钮 call_deferred('grab_focus'): ", currently_selected_button.name) # Debug
currently_selected_button.call_deferred("grab_focus")
else:
print("Theme: 按钮无效或不在场景树中,无法获取焦点。")
# --- Signal Handlers ---
# (与 gameplay.gd 一致)
func _on_previous_pressed():
if total_pages > 1:
current_page_index -= 1
_update_display()
# (与 gameplay.gd 一致)
func _on_next_pressed():
if total_pages > 1:
current_page_index += 1
_update_display()
# 【修改】处理选项按钮按下事件 (与 gameplay.gd 一致)
func _on_option_selected(theme_name: String, button_node: Button):
"""当选项按钮被按下时调用。"""
if not is_instance_valid(button_node):
# --- 修改错误信息中的 Key 类型 ---
printerr("错误:在 _on_option_selected 中收到无效按钮节点Key: " + theme_name)
return
if button_node == currently_selected_button:
# --- 双击 (或点击已选中的按钮) ---
print("Theme: 检测到双击或确认点击: ", theme_name)
_confirm_and_close(theme_name) # 确认此选择
else:
# --- 首次点击或点击不同按钮 ---
print("Theme: 选中 (高亮) 题材: ", theme_name)
currently_selected_button = button_node
selected_theme_key = theme_name # 更新 Key
# 更新视觉焦点
button_node.grab_focus()
# 【修改】确认选择并关闭弹窗的逻辑 (与 gameplay.gd 一致)
func _confirm_and_close(key_to_confirm: String):
"""更新父节点并关闭此弹窗。"""
if key_to_confirm.is_empty():
printerr("错误:尝试确认一个空的题材 Key。")
return
print("Theme: 确认选择: ", key_to_confirm)
# --- 更新父节点 (Task Development Popup) ---
var parent_node = get_parent()
if parent_node and parent_node.has_method("update_task_options"):
# --- 修改传递给父节点的字典键名 ---
parent_node.update_task_options({"题材": key_to_confirm})
print("Theme: 已更新父节点的 task_options: 题材 = ", key_to_confirm)
else:
printerr("错误Theme: 父节点未找到或缺少 'update_task_options' 方法。")
# --- 关闭此弹窗 (与 gameplay.gd 一致) ---
if parent_node and parent_node.has_method("_close_child_popup_and_return"):
parent_node._close_child_popup_and_return(self)
else:
printerr("错误Theme: 父节点未找到或缺少 '_close_child_popup_and_return' 方法。")
self.hide() # 备选方案

View File

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

File diff suppressed because it is too large Load Diff

154
UI/ui_base.gd Normal file
View File

@ -0,0 +1,154 @@
# ui_base.gd
class_name UIPage
extends Control
# --- Signals ---
signal navigate_requested(target_page_node: Control)
signal workflow_end_requested(is_confirm_and_trigger_action: bool)
# --- Exported Variables for Inspector ---
## The page to navigate to when a "next" or "confirm" action is triggered.
@export var next_ui_node_path: NodePath
## The page to navigate to when a "back" or "cancel" action is triggered.
@export var back_ui_node_path: NodePath
## For sub-pages, this is their direct parent page (e.g., A is parent of A_1).
## Used if back_ui_node_path is not set for a sub-page, to return to its parent.
@export var parent_page_node_path: NodePath
## If true (default), this page will be hidden when go_next() is called and
## a next_ui_node_path is successfully navigated to.
## If false, this page will remain visible.
@export var hide_on_next: bool = true
# --- Internal Variables ---
var main_controller # Will hold reference to Main.gd
func _ready():
# Attempt to find the Main controller. Assumes Main is the owner or a known path.
if owner and owner.has_method("get_shared_data"): # Simple check if owner is Main
main_controller = owner
# --- Godot Input Handling ---
func _input(event):
# Only process input if the page is currently visible and the feature is enabled
if not visible:
return
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed():
print("UIPage '", name, "' detected right-click. Attempting to go back.")
go_back()
get_viewport().set_input_as_handled() # Consume the event so other nodes don't process it
# --- Public Methods ---
func show_page():
# 仅当页面之前是不可见状态时,才设置 visible = true。
# 或者更简单地,总是设置 visible = true因为重复设置 true 没有副作用。
if not visible:
visible = true
func hide_page():
# Only call _on_page_deactivated if the page was previously visible.
if visible:
_on_page_deactivated() # Call the overridable method before hiding
visible = false
# else:
# print("UIPage " + name + " already hidden. Not re-deactivating.")
func _on_page_activated():
# Base implementation can be empty or log
print("UIPage activated: " + name)
pass
func _on_page_deactivated():
# Base implementation can be empty or log
print("UIPage deactivated: " + name)
pass
# --- Navigation Methods (to be called by buttons on this page) ---
func go_next():
if not next_ui_node_path.is_empty():
var target_node = get_node_or_null(next_ui_node_path)
if target_node and target_node is Control: # Ensure target_node is a Control, ideally a UIPage
emit_signal("navigate_requested", target_node)
else:
printerr("Next UI node not found or not a Control: ", next_ui_node_path, " from page ", name)
emit_signal("workflow_end_requested", true) # Or some error state
else:
# next_ui_node is null, meaning workflow ends with confirmation
emit_signal("workflow_end_requested", true)
func go_back():
var target_path_to_try = back_ui_node_path
if target_path_to_try.is_empty() and not parent_page_node_path.is_empty():
target_path_to_try = parent_page_node_path
if not target_path_to_try.is_empty():
var target_node = get_node_or_null(target_path_to_try)
if target_node and target_node is Control: # Ensure target_node is a Control, ideally a UIPage
emit_signal("navigate_requested", target_node)
else:
printerr("Back/Parent UI node not found or not a Control: ", target_path_to_try, " from page ", name)
emit_signal("workflow_end_requested", false) # Or some error state
else:
emit_signal("workflow_end_requested", false)
# --- Data Access Methods (Examples) ---
func get_main_data(key: String, default = null):
if main_controller:
return main_controller.get_shared_data(key, default)
else: # <--- 修改点: 将警告移入 else 分支
push_warning("Cannot get_main_data, Main controller not found from page '", name, "'")
return default
func set_main_data(key: String, value):
if main_controller:
main_controller.set_shared_data(key, value)
else: # <--- 修改点: 将警告移入 else 分支
push_warning("Cannot set_main_data, Main controller not found from page '", name, "'. Data not set.")
# --- Helper function to check if potential_ancestor is an ancestor of node_to_check ---
func _is_ancestor_of(potential_ancestor: Node, node_to_check: Node) -> bool:
if not potential_ancestor or not node_to_check:
return false
# Ensure both nodes are in the scene tree to safely use get_parent()
if not node_to_check.is_inside_tree() or not potential_ancestor.is_inside_tree():
return false
var current_parent = node_to_check.get_parent()
while current_parent:
if current_parent == potential_ancestor:
return true
current_parent = current_parent.get_parent() # Move up the tree
return false
# --- Helper for navigating to a specific child page (MODIFIED) ---
func go_to_child_page(child_page_node: Control):
# Check if a child_page_node was provided
if not child_page_node:
printerr("Error in '", name, ".go_to_child_page': Target child_page_node is null.")
return
# This check prevents navigating to an ancestor using this function.
# If child_page_node is an ancestor of self, it's considered an invalid target for "go_to_child_page".
# Original problematic line: if child_page_node and child_page_node.is_a_parent_of(self):
if _is_ancestor_of(child_page_node, self):
printerr("Error in '", name, ".go_to_child_page': Navigation target '", child_page_node.name,
"' cannot be an ancestor of the current page '", self.name,
"'. 'go_to_child_page' is intended for pages that are descendants or unrelated in the scene tree hierarchy.")
return
# Optional: If 'go_to_child_page' strictly means a *direct child* in the scene tree,
# you might want to add a check like this:
# if child_page_node.get_parent() != self:
# printerr("Error in '", name, ".go_to_child_page': Target '", child_page_node.name,
# "' is not a direct child of this page '", self.name, "'.")
# return
# Ensure child_page_node is a Control (already type-hinted, but good for safety)
if child_page_node is Control:
emit_signal("navigate_requested", child_page_node)
#self._on_page_deactivated()
else:
# This case might occur if something other than a Control node is passed,
# despite the type hint.
printerr("Error in '", name, ".go_to_child_page': Invalid target '", child_page_node,
"'. It is not a Control node.")

1
UI/ui_base.gd.uid Normal file
View File

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

148
UI/ui_framework.gd Normal file
View File

@ -0,0 +1,148 @@
# main_controller.gd
extends Control
# --- Signals ---
signal workflow_confirmed(final_data: Dictionary)
signal workflow_cancelled()
# --- Exported Variables ---
@export var initial_page_path: NodePath
@export var gamestate_data_key: String
# --- Internal Variables ---
var current_active_page: UIPage = null
var shared_workflow_data: Dictionary = {}
# --- Godot Lifecycle Methods ---
func _ready():
shared_workflow_data = GameState.get_value(gamestate_data_key)
for child in get_children():
if child is UIPage:
child.hide_page()
_connect_page_signals(child)
for sub_child in child.get_children():
if sub_child is UIPage:
sub_child.hide_page()
_connect_page_signals(sub_child)
# --- Public Methods ---
func show_up():
GameState.pause_game()
shared_workflow_data = GameState.get_value(gamestate_data_key)
if not initial_page_path.is_empty():
var initial_page_node = get_node_or_null(initial_page_path)
if initial_page_node and initial_page_node is UIPage:
start_new_workflow(initial_page_node, shared_workflow_data)
else:
printerr("Main: Initial page not found or not a UIPage: ", initial_page_path)
else:
self.visible = false
func start_new_workflow(start_page: UIPage, initial_data: Dictionary = {}):
print("Main: Starting new workflow with page: ", start_page.name, " and data: ", initial_data)
shared_workflow_data = initial_data.duplicate()
for child in get_children():
if child is UIPage and child != start_page:
child.hide_page()
for sub_child in child.get_children():
if sub_child is UIPage:
sub_child.hide_page()
_switch_to_page(start_page)
self.visible = true
func get_shared_data(key: String, default = null):
return shared_workflow_data.get(key, default)
func set_shared_data(key: String, value):
shared_workflow_data[key] = value
print("Main: Shared data updated: ", shared_workflow_data)
# --- Private Helper Methods ---
func _connect_page_signals(page: UIPage):
if not page.is_connected("navigate_requested", Callable(self, "_on_page_navigate_requested")):
page.navigate_requested.connect(self._on_page_navigate_requested)
if not page.is_connected("workflow_end_requested", Callable(self, "_on_page_workflow_end_requested")):
page.workflow_end_requested.connect(self._on_page_workflow_end_requested)
func _switch_to_page(target_page: UIPage):
var previous_page = current_active_page
if previous_page:
# ... (处理前一页面的隐藏逻辑,包括 hide_on_next 和父页面清理) ...
# (这部分逻辑不变)
var should_hide_previous_page = true
if previous_page.has_meta("hide_on_next"):
should_hide_previous_page = previous_page.get_meta("hide_on_next")
elif previous_page.has_signal("about_to_show"):
if "hide_on_next" in previous_page:
should_hide_previous_page = previous_page.hide_on_next
else:
printerr("MainController: 'hide_on_next' property not found on previous_page '", previous_page.name, "'. Defaulting to hide.")
if should_hide_previous_page:
previous_page.hide_page()
var old_page_parent_path = previous_page.parent_page_node_path
if not old_page_parent_path.is_empty():
var old_page_parent_node = previous_page.get_node_or_null(old_page_parent_path) as UIPage
if old_page_parent_node:
var target_is_old_parent_itself = (target_page == old_page_parent_node)
var target_is_sibling_of_previous = false
var target_parent = target_page.get_parent()
if target_parent and target_parent == old_page_parent_node:
target_is_sibling_of_previous = true
if not target_is_old_parent_itself and not target_is_sibling_of_previous:
old_page_parent_node.hide_page()
current_active_page = target_page
current_active_page.show_page() # 调用 UIPage.show_page(),现在它只负责设置 visible = true
# 确保新页面的 _on_page_activated() 被调用,无论它之前是否可见
current_active_page._on_page_activated() # <--- 关键MainController 负责调用
# 确保新当前页面的父页面 (如果它是子页面) 也可见并已激活 (如果需要)
var new_page_parent_path = current_active_page.parent_page_node_path
if not new_page_parent_path.is_empty():
var new_page_parent_node = current_active_page.get_node_or_null(new_page_parent_path) as UIPage
if new_page_parent_node:
# 如果父页面之前不可见,则显示并激活它
if not new_page_parent_node.visible:
new_page_parent_node.show_page() # 使其可见
new_page_parent_node._on_page_activated() # 激活它
else:
# 如果父页面已经可见 (例如,它从未被隐藏)
# 确保其 show_page 至少被调用一次以符合逻辑 (尽管它可能什么都不做)
new_page_parent_node.show_page()
print("Main: Switched to page: ", current_active_page.name)
# --- Signal Handlers ---
func _on_page_navigate_requested(target_page_node: Control): # Changed to Control to match signal
if target_page_node is UIPage:
_switch_to_page(target_page_node)
else:
printerr("Main: Navigation requested to a node that is not a UIPage: ", target_page_node)
func _on_page_workflow_end_requested(is_confirm_and_trigger_action: bool):
print("Main: Workflow end requested. Confirm and trigger: ", is_confirm_and_trigger_action)
if is_confirm_and_trigger_action:
print("Main: Workflow CONFIRMED. Final data: ", shared_workflow_data)
emit_signal("workflow_confirmed", shared_workflow_data)
else:
print("Main: Workflow CANCELLED/BACKED OUT.")
emit_signal("workflow_cancelled")
if current_active_page:
current_active_page.hide_page()
var parent = current_active_page.get_node_or_null(current_active_page.parent_page_node_path) as UIPage
if parent:
parent.hide_page()
current_active_page = null
GameState.set_value(gamestate_data_key,shared_workflow_data)
GameState.resume_game()
self.visible = false
print("Main: UI Workflow finished and Main hidden.")

1
UI/ui_framework.gd.uid Normal file
View File

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

View File

@ -35,6 +35,7 @@ window/stretch/mode="viewport"
UI_MAIN="" UI_MAIN=""
dialogue="" dialogue=""
notice="" notice=""
task_development=""
[rendering] [rendering]