From 9e1bd055d5a1475971aa735ce420cabbf87237d9 Mon Sep 17 00:00:00 2001 From: chen Date: Sun, 9 Aug 2026 16:13:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BB=BA=E7=AD=91=E9=9D=A2=E6=9D=BF?= =?UTF-8?q?=E2=80=94=E2=80=94=E8=B5=84=E6=BA=90=E6=8C=89=E9=92=AE=E5=85=A5?= =?UTF-8?q?=E5=8F=A3+=E5=BB=BA=E9=80=A0=E5=8D=87=E7=BA=A7=E4=BA=A7?= =?UTF-8?q?=E5=87=BA=EF=BC=88D6=20=E6=A0=B8=E5=BF=83=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/design/建筑系统设计.md | 2 +- project.godot | 1 + resources/game_config.cfg | 13 +++ src/Core/GameConfig.gd | 17 ++++ src/Core/MainGame.gd | 6 ++ src/Core/MainGame.tscn | 4 + src/Sect/BuildingManager.gd | 63 +++++++++++++ src/Sect/BuildingManager.gd.uid | 1 + src/Sect/buildings/Building.gd | 54 +++++++++++ src/Sect/buildings/Building.gd.uid | 1 + src/UI/HUD/TopBarHUD.tscn | 1 + src/UI/HUD/top_bar_hud.gd | 6 ++ .../Panels/BuildingsPanel/BuildingsPanel.tscn | 77 ++++++++++++++++ .../Panels/BuildingsPanel/buildings_panel.gd | 91 +++++++++++++++++++ .../BuildingsPanel/buildings_panel.gd.uid | 1 + 15 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 src/Sect/BuildingManager.gd create mode 100644 src/Sect/BuildingManager.gd.uid create mode 100644 src/Sect/buildings/Building.gd create mode 100644 src/Sect/buildings/Building.gd.uid create mode 100644 src/UI/Panels/BuildingsPanel/BuildingsPanel.tscn create mode 100644 src/UI/Panels/BuildingsPanel/buildings_panel.gd create mode 100644 src/UI/Panels/BuildingsPanel/buildings_panel.gd.uid diff --git a/doc/design/建筑系统设计.md b/doc/design/建筑系统设计.md index 278114a..7cba194 100644 --- a/doc/design/建筑系统设计.md +++ b/doc/design/建筑系统设计.md @@ -2,7 +2,7 @@ > 对应开发计划表 D6(8/8):① 灵石矿场景 ② 建造→产出 ③ 升级。交付标准:建筑产资源,升级加速。 > 本文档只描述设计,不包含代码。 -> 状态:步骤 1–2 已实现(食物资源/HUD 灵米显示/开局 500 灵石+100 灵米配置驱动),步骤 3+ 待实现 +> 状态:步骤 1–2、4、6 已实现(食物资源/HUD 灵米/开局赠送配置化;Building+BuildingManager 建造升级产出;BuildingsPanel 面板+资源按钮+暂停恢复),步骤 3 部分(建筑参数配置已加)、5 待实现 --- diff --git a/project.godot b/project.godot index e863e97..42ceff1 100644 --- a/project.godot +++ b/project.godot @@ -23,6 +23,7 @@ SectManager="*res://src/Core/SectManager.gd" GameConfig="*res://src/Core/GameConfig.gd" DiscipleManager="*res://src/Character/DiscipleManager.gd" CultivationManager="*res://src/Character/CultivationManager.gd" +BuildingManager="*res://src/Sect/BuildingManager.gd" [display] diff --git a/resources/game_config.cfg b/resources/game_config.cfg index 9ce1c33..76235ae 100644 --- a/resources/game_config.cfg +++ b/resources/game_config.cfg @@ -34,3 +34,16 @@ hud_font_size=24 ; HUD 字体大小 [building] ; 建筑系统通用 starting_spirit_stones=500 ; 开局赠送灵石 starting_food=100 ; 开局赠送灵米 +max_level=10 ; 建筑等级上限 + +[building_mine] ; 灵石矿 +cost=200 ; 建造价(灵石) +output=10 ; 日产出(灵石) +upgrade_cost_x=2.0 ; 升级价格倍率 +upgrade_output_x=1.5 ; 升级产出倍率 + +[building_field] ; 灵田 +cost=100 ; 建造价(灵石) +output=5 ; 日产出(灵米) +upgrade_cost_x=2.0 ; 升级价格倍率 +upgrade_output_x=1.5 ; 升级产出倍率 diff --git a/src/Core/GameConfig.gd b/src/Core/GameConfig.gd index d453927..86094d8 100644 --- a/src/Core/GameConfig.gd +++ b/src/Core/GameConfig.gd @@ -47,6 +47,14 @@ var hud_font_size := 32 var starting_spirit_stones := 500 ## 开局赠送灵米(新游戏发放)。 var starting_food := 100 +## 建筑等级上限。 +var building_max_level := 10 + +## 各建筑参数表(键 = 建筑类型名,值 = {cost, output, upgrade_cost_x, upgrade_output_x})。 +var building_params: Dictionary = { + "mine": {"cost": 200, "output": 10, "upgrade_cost_x": 2.0, "upgrade_output_x": 1.5}, + "field": {"cost": 100, "output": 5, "upgrade_cost_x": 2.0, "upgrade_output_x": 1.5}, +} ## 各难度资质权重表(键 = 难度档位,值 = 下品/中品/上品/天骄 权重)。 var aptitude_weights_by_difficulty: Dictionary = { @@ -104,6 +112,15 @@ func _load_config() -> void: # 建筑系统 starting_spirit_stones = cfg.get_value("building", "starting_spirit_stones", starting_spirit_stones) starting_food = cfg.get_value("building", "starting_food", starting_food) + building_max_level = cfg.get_value("building", "max_level", building_max_level) + for key in building_params.keys(): + var p: Dictionary = building_params[key] + building_params[key] = { + "cost": cfg.get_value("building_" + key, "cost", p["cost"]), + "output": cfg.get_value("building_" + key, "output", p["output"]), + "upgrade_cost_x": cfg.get_value("building_" + key, "upgrade_cost_x", p["upgrade_cost_x"]), + "upgrade_output_x": cfg.get_value("building_" + key, "upgrade_output_x", p["upgrade_output_x"]), + } var apt_raw: Variant = cfg.get_value("cultivation", "aptitude_speed", []) if apt_raw is Array and apt_raw.size() == aptitude_speed.size(): var arr: Array[float] = [] diff --git a/src/Core/MainGame.gd b/src/Core/MainGame.gd index b5ba535..256ac8f 100644 --- a/src/Core/MainGame.gd +++ b/src/Core/MainGame.gd @@ -9,6 +9,8 @@ func _ready() -> void: TimeSystem.apply_default_speed() # 开局招募:清空旧数据 → 生成 5 名候选 → 暂停时间等待选择(读档流程接入后由存档还原) DiscipleManager.start_new_game() + # 开局建筑:清空旧建筑(读档流程接入后由存档还原) + BuildingManager.reset() # 开局赠送资源(数量来自配置;读档流程接入后由存档还原) SectManager.add_spirit_stones(GameConfig.starting_spirit_stones) SectManager.add_food(GameConfig.starting_food) @@ -20,3 +22,7 @@ func _on_top_bar_hud_sign_return_mainmenu() -> void: ## 顶部 HUD 的"管理"按钮请求:打开弟子名录面板。 func _on_top_bar_hud_sign_open_disciples() -> void: $DisciplesPanel.show_panel() + +## 顶部 HUD 的"资源"按钮请求:打开建筑面板。 +func _on_top_bar_hud_sign_open_buildings() -> void: + $BuildingsPanel.show_panel() diff --git a/src/Core/MainGame.tscn b/src/Core/MainGame.tscn index 90051d8..b95ffc9 100644 --- a/src/Core/MainGame.tscn +++ b/src/Core/MainGame.tscn @@ -4,6 +4,7 @@ [ext_resource type="PackedScene" uid="uid://cahux61qqblje" path="res://src/UI/Panels/DisciplesPanel/DisciplesPanel.tscn" id="1_d5cpm"] [ext_resource type="PackedScene" uid="uid://dl0tb68vsw3c6" path="res://src/UI/HUD/TopBarHUD.tscn" id="1_g6xmk"] [ext_resource type="PackedScene" uid="uid://cahbpjpnmbcfv" path="res://src/UI/Panels/RecruitPanel/RecruitPanel.tscn" id="1_k5nrm"] +[ext_resource type="PackedScene" path="res://src/UI/Panels/BuildingsPanel/BuildingsPanel.tscn" id="1_b9dpm"] [ext_resource type="Texture2D" uid="uid://cweu17mwblxmg" path="res://assets/sprites/ui/宗门后山.png" id="2_2atgf"] [ext_resource type="Texture2D" uid="uid://dnjuv1btbxm4o" path="res://assets/sprites/ui/宗门全景.png" id="5_58g0x"] @@ -35,5 +36,8 @@ layout_mode = 3 [node name="DisciplesPanel" parent="." unique_id=780000015 instance=ExtResource("1_d5cpm")] +[node name="BuildingsPanel" parent="." unique_id=795000012 instance=ExtResource("1_b9dpm")] + +[connection signal="sign_open_buildings" from="TopBarHud" to="." method="_on_top_bar_hud_sign_open_buildings"] [connection signal="sign_open_disciples" from="TopBarHud" to="." method="_on_top_bar_hud_sign_open_disciples"] [connection signal="sign_return_mainmenu" from="TopBarHud" to="." method="_on_top_bar_hud_sign_return_mainmenu"] diff --git a/src/Sect/BuildingManager.gd b/src/Sect/BuildingManager.gd new file mode 100644 index 0000000..82c4555 --- /dev/null +++ b/src/Sect/BuildingManager.gd @@ -0,0 +1,63 @@ +## 建筑管理器(Autoload 单例)。 +## 负责建筑建造/升级与每日产出结算,产出直接进入 SectManager 资源。 +## 本期每种建筑至多一座(面板按类型一行展示);多实例留待后续扩展。 +extends Node + +## 建筑列表变化时发出(建造/升级/重置)。 +signal buildings_changed + +## 宗门建筑列表。 +var buildings: Array[Building] = [] + + +func _ready() -> void: + # 每日推进时结算各建筑产出 + TimeSystem.day_passed.connect(_on_day_passed) + +## 建造建筑:检查灵石足够 → 扣费入列。 +## 同类型已存在时拒绝(本期一座一型)。 +## @param type: 建筑种类 +## @return 是否建造成功 +func build(type: Building.Type) -> bool: + if _find(type) != null: + return false + var b := Building.new() + b.type = type + if SectManager.spirit_stones < b.get_build_cost(): + return false + SectManager.remove_spirit_stones(b.get_build_cost()) + buildings.append(b) + buildings_changed.emit() + return true + +## 升级建筑:检查灵石足够 → 扣费提升等级。 +## @param b: 要升级的建筑 +## @return 是否升级成功 +func upgrade(b: Building) -> bool: + if b.is_max_level() or SectManager.spirit_stones < b.get_upgrade_cost(): + return false + SectManager.remove_spirit_stones(b.get_upgrade_cost()) + b.level += 1 + buildings_changed.emit() + return true + +## 清空建筑列表(新游戏开局调用)。 +func reset() -> void: + buildings.clear() + buildings_changed.emit() + +## 每日结算:各建筑产出进入 SectManager 对应资源。 +func _on_day_passed(_year: int, _month: int, _day: int) -> void: + for b in buildings: + match b.type: + Building.Type.MINE: + SectManager.add_spirit_stones(b.get_output()) + Building.Type.FIELD: + SectManager.add_food(b.get_output()) + +## 查找指定类型的建筑,不存在返回 null。 +func _find(type: Building.Type) -> Building: + for b in buildings: + if b.type == type: + return b + return null diff --git a/src/Sect/BuildingManager.gd.uid b/src/Sect/BuildingManager.gd.uid new file mode 100644 index 0000000..5cccc55 --- /dev/null +++ b/src/Sect/BuildingManager.gd.uid @@ -0,0 +1 @@ +uid://6hi0wog110up diff --git a/src/Sect/buildings/Building.gd b/src/Sect/buildings/Building.gd new file mode 100644 index 0000000..56e00d0 --- /dev/null +++ b/src/Sect/buildings/Building.gd @@ -0,0 +1,54 @@ +## 建筑数据模型(纯数据,不挂在场景树上)。 +## 价格与产出不在建筑上存副本,运行时按 type+level 从 GameConfig 计算。 +class_name Building +extends Resource + +## 建筑种类。 +enum Type { MINE, FIELD } # 灵石矿 / 灵田 + +## 建筑类型。 +var type: Type = Type.MINE +## 等级(1 起)。 +var level: int = 1 + + +## 建筑类型键(对应 GameConfig.building_params 字典键)。 +static func get_type_key(t: Type) -> String: + match t: + Type.MINE: + return "mine" + Type.FIELD: + return "field" + return "" + +## 建筑显示名称。 +static func get_type_name(t: Type) -> String: + match t: + Type.MINE: + return "灵石矿" + Type.FIELD: + return "灵田" + return "" + +## 建造价格(灵石)。 +func get_build_cost() -> int: + return get_build_cost_for(type) + +## 建造价格(静态查询,无需实例)。 +static func get_build_cost_for(t: Type) -> int: + var p: Dictionary = GameConfig.building_params[get_type_key(t)] + return int(p["cost"]) + +## 日产出量 = 基础产出 × 升级产出倍率^(等级-1)。 +func get_output() -> int: + var p: Dictionary = GameConfig.building_params[get_type_key(type)] + return int(p["output"] * pow(p["upgrade_output_x"], level - 1)) + +## 升级价格 = 建造价 × 升级价格倍率^等级。 +func get_upgrade_cost() -> int: + var p: Dictionary = GameConfig.building_params[get_type_key(type)] + return int(p["cost"] * pow(p["upgrade_cost_x"], level)) + +## 是否已达等级上限。 +func is_max_level() -> bool: + return level >= GameConfig.building_max_level diff --git a/src/Sect/buildings/Building.gd.uid b/src/Sect/buildings/Building.gd.uid new file mode 100644 index 0000000..a1836ad --- /dev/null +++ b/src/Sect/buildings/Building.gd.uid @@ -0,0 +1 @@ +uid://dmfhbyjwngqh3 diff --git a/src/UI/HUD/TopBarHUD.tscn b/src/UI/HUD/TopBarHUD.tscn index 6f7100d..32f2d57 100644 --- a/src/UI/HUD/TopBarHUD.tscn +++ b/src/UI/HUD/TopBarHUD.tscn @@ -224,4 +224,5 @@ size_flags_stretch_ratio = 75.0 [connection signal="button_up" from="VBoxContainer/HBoxContainer2/SpeedGroup/Button_Pause" to="." method="_on_button_pause_button_up"] [connection signal="button_up" from="VBoxContainer/HBoxContainer2/Button_StaffManagement" to="." method="_on_button_staff_management_button_up"] +[connection signal="button_up" from="VBoxContainer/HBoxContainer2/Button_ResourcesManagement" to="." method="_on_button_resources_management_button_up"] [connection signal="button_up" from="VBoxContainer/HBoxContainer2/Button_Return" to="." method="_on_button_return_button_up"] diff --git a/src/UI/HUD/top_bar_hud.gd b/src/UI/HUD/top_bar_hud.gd index 674b2f0..488a9ea 100644 --- a/src/UI/HUD/top_bar_hud.gd +++ b/src/UI/HUD/top_bar_hud.gd @@ -6,6 +6,8 @@ extends Control signal sign_return_mainmenu ## 请求打开弟子名录面板。 signal sign_open_disciples +## 请求打开建筑面板。 +signal sign_open_buildings @onready var time_label: Label = %Value_Time @onready var currency_label: Label = %Value_Currency @@ -62,6 +64,10 @@ func _on_button_return_button_up() -> void: func _on_button_staff_management_button_up() -> void: sign_open_disciples.emit() +## 点击"资源"按钮:请求打开建筑面板(由 MainGame 处理)。 +func _on_button_resources_management_button_up() -> void: + sign_open_buildings.emit() + ## 点击暂停/继续按钮,切换后刷新按钮状态。 func _on_button_pause_button_up() -> void: TimeSystem.toggle_pause() diff --git a/src/UI/Panels/BuildingsPanel/BuildingsPanel.tscn b/src/UI/Panels/BuildingsPanel/BuildingsPanel.tscn new file mode 100644 index 0000000..d36f095 --- /dev/null +++ b/src/UI/Panels/BuildingsPanel/BuildingsPanel.tscn @@ -0,0 +1,77 @@ +[gd_scene format=3 uid="uid://b9k4v8q2x5n3m7p6"] + +[ext_resource type="Script" path="res://src/UI/Panels/BuildingsPanel/buildings_panel.gd" id="1_script"] +[ext_resource type="FontFile" uid="uid://4su41gib4l5u" path="res://assets/fonts/lxgw-wenkai-v1.522/LXGWWenKaiMono-Medium.ttf" id="2_font"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dialog"] +bg_color = Color(0.07, 0.13, 0.12, 0.97) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.4, 0.72, 0.6, 1) +corner_radius_top_left = 8 +corner_radius_top_right = 8 +corner_radius_bottom_right = 8 +corner_radius_bottom_left = 8 + +[node name="BuildingsPanel" type="Control" unique_id=795000001] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_script") + +[node name="Backdrop" type="ColorRect" parent="." unique_id=795000002] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0.02, 0.05, 0.05, 0.65) + +[node name="CenterContainer" type="CenterContainer" parent="." unique_id=795000003] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Panel" type="PanelContainer" parent="CenterContainer" unique_id=795000004] +theme_override_styles/panel = SubResource("StyleBoxFlat_dialog") + +[node name="MarginContainer" type="MarginContainer" parent="CenterContainer/Panel" unique_id=795000005] +theme_override_constants/margin_left = 28 +theme_override_constants/margin_top = 20 +theme_override_constants/margin_right = 28 +theme_override_constants/margin_bottom = 20 + +[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/Panel/MarginContainer" unique_id=795000006] +theme_override_constants/separation = 12 + +[node name="TitleLabel" type="Label" parent="CenterContainer/Panel/MarginContainer/VBoxContainer" unique_id=795000007] +unique_name_in_owner = true +theme_override_fonts/font = ExtResource("2_font") +theme_override_font_sizes/font_size = 30 +theme_override_colors/font_color = Color(0.82, 0.95, 0.88, 1) +text = "宗门建筑" + +[node name="ListContainer" type="VBoxContainer" parent="CenterContainer/Panel/MarginContainer/VBoxContainer" unique_id=795000008] +unique_name_in_owner = true +theme_override_constants/separation = 10 + +[node name="ButtonRow" type="HBoxContainer" parent="CenterContainer/Panel/MarginContainer/VBoxContainer" unique_id=795000009] + +[node name="Spacer" type="Control" parent="CenterContainer/Panel/MarginContainer/VBoxContainer/ButtonRow" unique_id=795000010] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Button_Close" type="Button" parent="CenterContainer/Panel/MarginContainer/VBoxContainer/ButtonRow" unique_id=795000011] +unique_name_in_owner = true +theme_override_fonts/font = ExtResource("2_font") +theme_override_font_sizes/font_size = 22 +text = "关闭" + +[connection signal="gui_input" from="Backdrop" to="." method="_on_backdrop_gui_input"] +[connection signal="button_up" from="CenterContainer/Panel/MarginContainer/VBoxContainer/ButtonRow/Button_Close" to="." method="hide_panel"] diff --git a/src/UI/Panels/BuildingsPanel/buildings_panel.gd b/src/UI/Panels/BuildingsPanel/buildings_panel.gd new file mode 100644 index 0000000..68f4cad --- /dev/null +++ b/src/UI/Panels/BuildingsPanel/buildings_panel.gd @@ -0,0 +1,91 @@ +## 建筑面板(挂载于 BuildingsPanel.tscn)。 +## 展示可建/已建建筑(灵石矿/灵田),提供建造与升级操作。 +## 打开面板暂停时间,关闭恢复;监听建筑变化信号实时刷新。 +extends Control + +@onready var title_label: Label = %TitleLabel +@onready var list_container: VBoxContainer = %ListContainer + + +func _ready() -> void: + BuildingManager.buildings_changed.connect(_on_buildings_changed) + visible = false + +## 打开面板并刷新列表(暂停时间推进)。 +func show_panel() -> void: + _refresh() + TimeSystem.set_paused(true) + visible = true + +## 关闭面板(恢复时间推进)。 +func hide_panel() -> void: + visible = false + TimeSystem.set_paused(false) + +## 点击遮罩关闭面板。 +func _on_backdrop_gui_input(event: InputEvent) -> void: + if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT: + hide_panel() + +## 建筑列表变化时刷新。 +func _on_buildings_changed() -> void: + if visible: + _refresh() + +## 重建列表:按建筑类型逐行生成(未建显示建造按钮,已建显示等级/产出/升级按钮)。 +func _refresh() -> void: + for child in list_container.get_children(): + child.queue_free() + title_label.text = "宗门建筑" + for type in [Building.Type.MINE, Building.Type.FIELD]: + _add_row(type) + +## 添加一行建筑条目。 +## @param type: 建筑种类 +func _add_row(type: Building.Type) -> void: + var b := BuildingManager._find(type) + var row := HBoxContainer.new() + row.add_theme_constant_override("separation", 12) + + var name_label := Label.new() + name_label.text = Building.get_type_name(type) + name_label.custom_minimum_size = Vector2(140, 0) + row.add_child(name_label) + + var info_label := Label.new() + info_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + if b == null: + info_label.text = "未建造" + else: + info_label.text = "等级 %d 日产出 %d" % [b.level, b.get_output()] + row.add_child(info_label) + + var btn := Button.new() + if b == null: + var cost := Building.get_build_cost_for(type) + btn.text = "建造(%d 灵石)" % cost + btn.disabled = SectManager.spirit_stones < cost + btn.button_up.connect(_on_build_button_up.bind(type)) + else: + if b.is_max_level(): + btn.text = "已满级" + btn.disabled = true + else: + btn.text = "升级(%d 灵石)" % b.get_upgrade_cost() + btn.disabled = SectManager.spirit_stones < b.get_upgrade_cost() + btn.button_up.connect(_on_upgrade_button_up.bind(b)) + row.add_child(btn) + + list_container.add_child(row) + +## 点击建造按钮。 +## @param type: 建筑种类 +func _on_build_button_up(type: Building.Type) -> void: + if BuildingManager.build(type): + _refresh() + +## 点击升级按钮。 +## @param b: 要升级的建筑 +func _on_upgrade_button_up(b: Building) -> void: + if BuildingManager.upgrade(b): + _refresh() diff --git a/src/UI/Panels/BuildingsPanel/buildings_panel.gd.uid b/src/UI/Panels/BuildingsPanel/buildings_panel.gd.uid new file mode 100644 index 0000000..fbf4cf5 --- /dev/null +++ b/src/UI/Panels/BuildingsPanel/buildings_panel.gd.uid @@ -0,0 +1 @@ +uid://bhvcvw4ufjhxv