# VERTICAL SLICE - NOT FOR PRODUCTION # Date: 2026-09-21(元进度实现批——营地覆盖层呈现;2026-09-30 迭代 9 卡⑭ 视觉终稿——对照营地屏 # 方向稿 artwork/mockups/v3-draft-2026-09-29/4-camp.png 重排:石纹全屏底 + 顶栏铭牌/钥石徽 + # 三柜台列(属性锻造/道具工坊/奖励格图鉴,头带深棕+米色板身+内嵌行板,暗底→亮板双色墨制)+ # 底栏关闭钮/操作注记;最近冒险收底栏条带。信号面/投影契约零变化:buy_requested(attr)/closed、 # open_camp(data)/refresh_camp(data)、_forge_rows 行结构 {attr,level_l,price_l,btn}(探针依赖)。 # 挂接方式:开合与开始层互斥仿 TunePanel 先例(开始覆盖层非模态且树序在后——打开本层前由组合根 # 隐藏开始层,规范 2.3 遮挡纪律)。 # 高内聚边界:纯视图 setter 面——账本快照经组合根喂入(MetaProgression 只读投影),购买/关闭 # 意图经信号外发——零核心写调用、零 MetaProgression 持有。 class_name CampOverlay extends Control signal buy_requested(attr: String) signal closed const ATTR_LABELS := {"atk": "攻击", "max_hp": "生命上限", "armor_class": "护甲"} # 档位文案轴(数据契约 attr 词表,与 ShopOverlay 同款) # 方向稿取色(4-camp.png——柜台头带深棕/行板暖米;双色墨=亮板配深墨) const HEADER_BAND_COL := Color8(122, 82, 50) const ROW_PLATE_COL := Color8(240, 230, 206) const BADGE_PANEL_COL := Color8(40, 32, 24) var _balance_label: Label var _forge_holder: VBoxContainer var _workshop_holder: VBoxContainer var _forge_rows: Array[Dictionary] = [] # [{attr, level_l, price_l, btn}]——refresh_camp 就地重刷(零重建回流) var _workshop_rows: Array[Dictionary] = [] # [{item_id, progress_l, status_l}] var _recent_list: Label # 最近冒险(逐行拼接 label——只读账面,随 open_camp 重建) func _init() -> void: var dim := ColorRect.new() dim.color = Color(0, 0, 0, 0.45) # 方向稿=石纹底透出(浅 dim——石纹全屏底担当氛围) dim.set_anchors_preset(Control.PRESET_FULL_RECT) add_child(dim) # 石纹底噪(样张 v2 冻结件——start overlay 同款;缺位不挂即回退平 dim) var bd_tex := UiKit.load_tex(UiKit.TEX_BACKDROP_STONE) if bd_tex != null: var bd := TextureRect.new() bd.texture = bd_tex bd.stretch_mode = TextureRect.STRETCH_TILE bd.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST bd.set_anchors_preset(Control.PRESET_FULL_RECT) bd.mouse_filter = Control.MOUSE_FILTER_IGNORE dim.add_child(bd) var root := MarginContainer.new() root.set_anchors_preset(Control.PRESET_FULL_RECT) root.add_theme_constant_override("margin_left", 16) root.add_theme_constant_override("margin_right", 16) root.add_theme_constant_override("margin_top", 12) root.add_theme_constant_override("margin_bottom", 12) dim.add_child(root) var col := VBoxContainer.new() col.add_theme_constant_override("separation", 8) root.add_child(col) # ── 顶栏:左铭牌(标题+副题)/ 右钥石徽 ── var top := HBoxContainer.new() top.add_theme_constant_override("separation", 10) col.add_child(top) var plaque := PanelContainer.new() plaque.add_theme_stylebox_override("panel", UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER, 2, 1, Vector4(14, 8, 14, 8))) top.add_child(plaque) var plaque_col := VBoxContainer.new() plaque.add_child(plaque_col) var title := UiKit.label("营地 · 永久强化", 22, true) title.add_theme_color_override("font_color", UiKit.INK_TITLE) plaque_col.add_child(title) var sub := UiKit.label("跨局成长账本——钥石换永久属性", 13, false) sub.add_theme_color_override("font_color", UiKit.STONE) plaque_col.add_child(sub) var spacer := Control.new() spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL top.add_child(spacer) var badge := PanelContainer.new() badge.add_theme_stylebox_override("panel", UiKit.panel_flat(BADGE_PANEL_COL, UiKit.PANEL_BORDER, 2, 1, Vector4(14, 8, 14, 8))) top.add_child(badge) var bal_row := HBoxContainer.new() bal_row.add_theme_constant_override("separation", 6) badge.add_child(bal_row) var keystone_icon := UiKit.icon_rect(UiKit.TEX_ICON_KEYSTONE, 18) # 缺位 null → 纯文本回退 if keystone_icon != null: bal_row.add_child(keystone_icon) _balance_label = UiKit.label("钥石 0", 18, true) _balance_label.add_theme_color_override("font_color", UiKit.GOLD) bal_row.add_child(_balance_label) # ── 三柜台列(方向稿同构:头带深棕+米色板身)── var mid := HBoxContainer.new() mid.add_theme_constant_override("separation", 18) mid.alignment = BoxContainer.ALIGNMENT_CENTER mid.size_flags_vertical = Control.SIZE_EXPAND_FILL col.add_child(mid) _forge_holder = VBoxContainer.new() _forge_holder.add_theme_constant_override("separation", 6) mid.add_child(_counter_panel("属性锻造", UiKit.TEX_ICON_FORGE, _forge_holder, 356)) _workshop_holder = VBoxContainer.new() _workshop_holder.add_theme_constant_override("separation", 6) mid.add_child(_counter_panel("道具工坊", UiKit.TEX_ICON_WORKSHOP, _workshop_holder, 356)) var atlas_holder := VBoxContainer.new() mid.add_child(_counter_panel("奖励格图鉴", UiKit.TEX_ICON_ATLAS, atlas_holder, 356)) var lock_l := UiKit.label(" —— 柜台结构位预留:内容排 Alpha(地图战斗资源差异化批)", 13, false) lock_l.add_theme_color_override("font_color", UiKit.STONE) lock_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART lock_l.custom_minimum_size = Vector2(300, 0) atlas_holder.add_child(lock_l) # ── 底栏:关闭钮(左)/ 操作注记(右)+ 最近冒险条带 ── var close_btn := UiKit.button("关闭营地(回开始界面)") close_btn.pressed.connect(_emit_closed) var bottom := HBoxContainer.new() bottom.add_theme_constant_override("separation", 10) bottom.add_child(close_btn) var spacer2 := Control.new() spacer2.size_flags_horizontal = Control.SIZE_EXPAND_FILL bottom.add_child(spacer2) var caption := UiKit.label("每购得一件永久强化,对应柜台亮起一格暖光 · 价格为钥石", 13, false) caption.add_theme_color_override("font_color", UiKit.WARM_SOFT) bottom.add_child(caption) col.add_child(bottom) _recent_list = UiKit.label("", 13, false) _recent_list.add_theme_color_override("font_color", UiKit.WARM_SOFT) _recent_list.clip_text = true _recent_list.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS col.add_child(_recent_list) visible = false # 柜台列面板(方向稿同构):PanelContainer 米板身 + 头带深棕(图标+标题暖白) func _counter_panel(title_text: String, icon_path: String, holder: VBoxContainer, min_w: int) -> PanelContainer: var panel := PanelContainer.new() panel.custom_minimum_size = Vector2(min_w, 0) panel.add_theme_stylebox_override("panel", UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER, 3, 1, Vector4(10, 10, 10, 10))) var vbox := VBoxContainer.new() vbox.add_theme_constant_override("separation", 8) panel.add_child(vbox) var band := PanelContainer.new() band.add_theme_stylebox_override("panel", UiKit.panel_flat(HEADER_BAND_COL, UiKit.PANEL_BORDER, 2, 1, Vector4(10, 5, 10, 5))) vbox.add_child(band) var head := HBoxContainer.new() head.add_theme_constant_override("separation", 6) band.add_child(head) if icon_path != "": var ic := UiKit.icon_rect(icon_path, 16) # 缺位 null → 纯文本回退 if ic != null: head.add_child(ic) var l := UiKit.label(title_text, 16, true) l.add_theme_color_override("font_color", UiKit.WARM) head.add_child(l) vbox.add_child(holder) return panel # ---- 柜台行构建(open_camp 重建;行结构仿 ShopOverlay 商品卡——亮板双色墨制)---- # 打开营地(全量重建三柜台行 + 最近冒险——数据 = 组合根 _camp_data() 投影) func open_camp(data: Dictionary) -> void: _rebuild_forge(data) _rebuild_workshop(data) _rebuild_recent(data) refresh_camp(data) visible = true # 状态重刷(购买后调用——余额/价格/禁用态/工坊状态就地改文案,零重建) func refresh_camp(data: Dictionary) -> void: var balance := int(data.get("balance", 0)) _balance_label.text = "钥石 %d" % balance var forge_by_attr := {} for row in data.get("forge", []): forge_by_attr[String(row.get("attr", ""))] = row for card in _forge_rows: var row: Dictionary = forge_by_attr.get(String(card.attr), {}) if row.is_empty(): continue card.level_l.text = "Lv %d" % int(row.get("level", 0)) card.price_l.text = "%d 钥石" % int(row.get("price", 0)) card.btn.disabled = balance < int(row.get("price", 0)) # 余额不足禁用置灰 var ws_by_id := {} for row in data.get("workshop", []): ws_by_id[String(row.get("item_id", ""))] = row for card in _workshop_rows: var row: Dictionary = ws_by_id.get(String(card.item_id), {}) if row.is_empty(): continue var th := int(row.get("threshold", 0)) var prog := int(row.get("progress", 0)) var unlocked := bool(row.get("unlocked", false)) card.progress_l.text = "累计通关 %d / %d 次" % [prog, th] # 迭代 7 T5——失义件(显影尘/地穴全图卷 Alpha 重定)与退役件(探宝罗盘 2026-09-29 拍板①乙)呈现口径; # 状态色随卡⑭ 亮板双色墨制(深墨族替代暖白族——亮板上暖白不可读) var st_text := "未解锁" var st_col: Color = UiKit.STONE if bool(row.get("retired", false)): st_text = "已退役" st_col = UiKit.STONE elif bool(row.get("deferred", false)) and unlocked: st_text = "已解锁 · 能力封存中" st_col = UiKit.GOLD_DEEP elif unlocked: st_text = "已解锁" st_col = UiKit.GOLD_DEEP card.status_l.text = st_text card.status_l.add_theme_color_override("font_color", st_col) func _rebuild_forge(data: Dictionary) -> void: _forge_rows.clear() for child in _forge_holder.get_children(): child.queue_free() for row in data.get("forge", []): _forge_rows.append(_build_forge_row(_forge_holder, row)) func _build_forge_row(holder: VBoxContainer, row: Dictionary) -> Dictionary: var card := PanelContainer.new() card.add_theme_stylebox_override("panel", UiKit.panel_flat(ROW_PLATE_COL, UiKit.PANEL_BORDER, 2, 1, Vector4(10, 4, 10, 4))) var line := HBoxContainer.new() line.add_theme_constant_override("separation", 12) card.add_child(line) var attr := String(row.get("attr", "")) var name_l := UiKit.label("%s %+d/级" % [String(row.get("display_name", "")), int(row.get("flat", 0))], 14, true) name_l.add_theme_color_override("font_color", UiKit.INK) name_l.custom_minimum_size = Vector2(132, 0) line.add_child(name_l) var level_l := UiKit.label("", 14, false) level_l.add_theme_color_override("font_color", UiKit.INK) level_l.custom_minimum_size = Vector2(44, 0) line.add_child(level_l) var price_l := UiKit.label("", 14, false) price_l.add_theme_color_override("font_color", UiKit.STONE) price_l.custom_minimum_size = Vector2(64, 0) line.add_child(price_l) var btn := UiKit.button("锻造 +1 级") btn.pressed.connect(_emit_buy_requested.bind(attr)) line.add_child(btn) holder.add_child(card) return {"attr": attr, "level_l": level_l, "price_l": price_l, "btn": btn} func _rebuild_workshop(data: Dictionary) -> void: _workshop_rows.clear() for child in _workshop_holder.get_children(): child.queue_free() for row in data.get("workshop", []): var card := PanelContainer.new() card.add_theme_stylebox_override("panel", UiKit.panel_flat(ROW_PLATE_COL, UiKit.PANEL_BORDER, 2, 1, Vector4(10, 4, 10, 4))) var line := HBoxContainer.new() line.add_theme_constant_override("separation", 12) card.add_child(line) var name_l := UiKit.label(String(row.get("display_name", "")), 14, true) name_l.add_theme_color_override("font_color", UiKit.INK) name_l.custom_minimum_size = Vector2(132, 0) line.add_child(name_l) var progress_l := UiKit.label("", 13, false) progress_l.add_theme_color_override("font_color", UiKit.STONE) progress_l.custom_minimum_size = Vector2(110, 0) line.add_child(progress_l) var status_l := UiKit.label("", 13, true) status_l.add_theme_color_override("font_color", UiKit.STONE) line.add_child(status_l) _workshop_holder.add_child(card) _workshop_rows.append({"item_id": String(row.get("item_id", "")), "progress_l": progress_l, "status_l": status_l}) func _rebuild_recent(data: Dictionary) -> void: var lines: Array[String] = [] var recent: Array = data.get("recent", []) if recent.is_empty(): lines.append(" 最近冒险:(暂无记录——完成或结束一次冒险后入账)") for s in recent: var verdict := "失败" if String(s.get("result", "")) == "completed": verdict = "完成" elif String(s.get("result", "")) == "abandoned": verdict = "放弃" lines.append("最近冒险:%s · 通关 %d 关 · 钥石 +%d · 种子 %d" % [verdict, int(s.get("levels_cleared", 0)), int(s.get("keystone_total", 0)), int(s.get("master_seed", 0))]) _recent_list.text = " | ".join(lines) func _emit_closed() -> void: closed.emit() func _emit_buy_requested(attr: String) -> void: buy_requested.emit(attr)