Files
gplan-2/game/presentation/ui_assembler.gd
T
DKChan 32fdf957fd refactor(代码术语): 卡① flag→seal 全族换代+game/ 禁用词清扫(迭代 31 卡①)
标识符面:flag_*→seal_ 族(含 Godot 信号 flag_judged→seal_judged 与连接/回调同名、
FACTS 两键 flags_count→seals_count/dug_under_flag→dug_under_seal 全链、
.tres 键名与 schema .gd 同批同步);显式豁免零改动=size_flags_*(Godot 布局属性)+_fatal_flag。
中文面:金币→GP(金币袋→GP 袋)/插旗→封印/雷格→危险格/惩罚格→危险格/旗→封印·封印印记族,
game/ 内 *.gd/.tres/.md/.json 四类禁用词与 flag 双双零命中(V1/V4)。
守卫字面量不动(被检查的禁字不是使用):smoke_all V21 零「雷/旗」检查面、
ui_font_asset_test 字形集攻/雷/旗/封 保持原断言逻辑。
代拍板:板面单字槽 glyph「旗」→「封」(禁字纪律+单字位宽,消费面四处同名随动)。
素材换代(2026-10-18 用户裁案甲):assets/art/board/flag_seal.png→seal_mark.png(素材不入 git,
盘上改名+导入重刷),代码两处 res:// 字符串+COL_FLAG/CURSOR_FLAG 常量同步,
闸件四处随批(run_all_gates.sh/render_gate.sh/golden_baseline_refresh.sh/path_check.py)。
anchor.json 键名手改,重锚与改前改后 diff 随本卡收口(同裁案甲)。

基线读数(改前,主区 ed2dfaf6):冒烟 1151 检查 0 失败/单元 34 套全绿/探针 --click
存量红仅 screen_flow_probe(user:// 断点污染在册已知项)。
改后同批读数(本 worktree):冒烟 1151 检查 0 失败/探针 --click 同一存量红、其余全绿。
2026-10-19 02:52:38 +08:00

241 lines
13 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## VERTICAL SLICE - NOT FOR PRODUCTION
## 视图构建装配器:只做 new/add_child/布局,静态无状态,返回句柄供组合根接线。
## 接线拓扑唯一归组合根——本类零信号连接。
## z 序契约:调用方必须按 build 返回序 addChild(登记序 = 模态遮挡序)。
extends RefCounted
# 战斗屏三区(B 战斗画面 / C·D 两列——A 区关卡进度条顶栏随 停点前移批 2026-09-23 退役:节点链
# 进度由关卡画面本体(选路覆盖层整图)承载,28px 纵向空间拨给 C/D 底行 520→548;E 区道具面板
# 已随可选类删除下线,2026-09-15 格面资源批)。
# + 结算演出覆盖层(A–D 之上、模态之下——跨区弹道/光带载体,结算演出链批 2026-09-20)。
# 返回句柄字典:{stage, log_view, info_panel, board_zone, board_view, ap_bar, settlement_fx}
# (gp_label/keystone_label 随 2026-09-26 信息归属拍板退役——GP/钥石迁出战斗屏,句柄不再透出)
static func build_battle_ui(root: Control, db) -> Dictionary:
# set_anchors_and_offsets_preset(非 set_anchors_preset):后者会把控件重置回最小内容尺寸
# (实测 927×613——缩顶、棋盘 45px 格 ×10 行超出 D 区 333px 高被裁,2026-09-13 用户报告回归)
root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
root.add_theme_constant_override("separation", 0)
# B 区:战斗画面(全宽 × 252px——常驻双舞台,布局决策 D1)
var stage := BattleStageView.new(db)
root.add_child(stage)
# C/D 两列(548px——停点前移批 2026-09-23:原 A 区 28px 顶栏纵向空间并入)
var bottom := HBoxContainer.new()
bottom.size_flags_vertical = Control.SIZE_EXPAND_FILL
bottom.add_theme_constant_override("separation", 4)
root.add_child(bottom)
var info_panel := HeroInfoPanelView.new()
bottom.add_child(info_panel)
var board_zone := BoardZoneView.new()
bottom.add_child(board_zone)
board_zone.ap_bar.set_refresh_cost(int(db.action_economy.rap_refresh_cost)) # RAP 读数表值驱动
# 结算演出覆盖层(A–D 之上——z 序 = addChild 序,模态覆盖层组在 build_overlays 中更晚挂入)
var settlement_fx := SettlementFxView.new()
root.add_child(settlement_fx)
# CJK 字体随包(2026-09-27 用户拍板 Fusion Pixel OFL):Control 侧默认主题字体走 project.godot
# gui/theme/custom_font;SubViewport 两世界不继承 Control 主题——显式注入挂点接线(C6 挂点链)。
# 缺位守卫(load_font 返回 null = 引擎回退字体,与旧 Button 渲染同源,不炸)。
var ui_font: Font = UiKit.load_font(UiKit.FONT_UI)
if ui_font != null:
stage.set_world_font(ui_font)
board_zone.board_view.set_world_font(ui_font)
# GP/钥石余额位已随 2026-09-26 信息归属拍板退役(中/大循环资源迁出战斗屏——
# GP 去向 = 关卡画面+战斗结算画面、钥石去向 = 结算画面+营地,呈现归对应 UX spec 批次)
return {
"stage": stage,
"log_view": stage.log_view,
"info_panel": info_panel,
"board_zone": board_zone,
"board_view": board_zone.board_view,
"ap_bar": board_zone.ap_bar,
"settlement_fx": settlement_fx,
}
# 模态覆盖层组(z 序 = 登记序:图例 < 节点 < 终局 < 商店)。
# 返回 {legend_overlay, node_overlay, end_overlay, shop_overlay};调用方按本序 addChild。
# (调整板已随迭代 14.5 调试整合批退役——调试浮窗由组合根直构挂树序最末,见 main._build_ui;
# 明细回看 overlay 已随 M9/P-C7 退役 2026-10-04——冒险中回看归「生涯」06 登记;
# 营地覆盖层已随迭代 27 T1 场景拆分迁 camp_screen 屏壳——本组不再构建)
static func build_overlays(host: Control, db) -> Dictionary:
var legend_overlay := LegendOverlay.new(int(db.action_economy.rap_refresh_cost)) # 打磨包 2026-09-29——RAP 行表值注入
host.add_child(legend_overlay)
var node_overlay := NodeOverlay.new()
host.add_child(node_overlay)
var end_overlay := BattleEndOverlay.new()
host.add_child(end_overlay)
# 商店覆盖层(GP+商店房实施批 2026-09-21——商店房节点兑付呈现;按钮接线归组合根,装配器零信号连接)
var shop_overlay := ShopOverlay.new()
host.add_child(shop_overlay)
return {
"legend_overlay": legend_overlay,
"node_overlay": node_overlay,
"end_overlay": end_overlay,
"shop_overlay": shop_overlay,
}
# 开始覆盖层(返回 {overlay, box, lvbtn, resumebtn, campbtn, careerbtn, settingsbtn, quitbtn}——按钮接线归组合根;
# 迭代 14.5 调试整合批:随机种子/数值调整板/两层调试三散件撤出主按钮组,收进调试浮窗——
# 2026-10-16 用户令:调试入口=UI 最顶层全局浮层角标(build_debug_badge,本层零调试入口)
# UI 落码批 M2(2026-10-04):单列 VBox→双列(左操作 56% + 右舞台);按钮动词化+悬停解释
#(解释全迁 tooltip,G5);lvbtn 升主操作钮(烛光黄 CANDLE);右列=立绘看台。**玩法速览牌已退役
#(2026-10-06 用户令:界面禁玩法文字墙——教学内容归 Beta 帮助手册〔教学引导定稿批 P4 丙〕,历史文案在 git)**;
# tooltip 主题归全局主题单点(迭代 25 2026-10-13——旧 root 挂程序化 tooltip 主题的做法退役)。
# 返回键集不变,组合根接线零改)
static func build_start_overlay(host: Control, db = null) -> Dictionary:
var overlay := overlay_bg(host, UiKit.BG_BRIGHT)
# 石纹弱质感+斜光带+暗角(UI 对齐批 T5 2026-10-06——demo .tilebg 0.14/.beams/.vignette 三层;
# 旧满铺石纹不透退役。缺位不挂即回退平色)
var bd := UiKit.backdrop_tile()
if bd != null:
overlay.add_child(bd)
overlay.add_child(UiKit.beams_rect())
overlay.add_child(UiKit.vignette_rect())
# 双列骨架(M2):左=操作列 56% / 右=舞台+速览
var cols := HBoxContainer.new()
cols.set_anchors_preset(Control.PRESET_FULL_RECT)
cols.add_theme_constant_override("separation", 32)
var pad := MarginContainer.new()
pad.set_anchors_preset(Control.PRESET_FULL_RECT)
for side in ["margin_left", "margin_right", "margin_top", "margin_bottom"]:
pad.add_theme_constant_override(side, 56)
cols.add_child(pad)
overlay.add_child(cols)
var box := VBoxContainer.new()
box.name = "start_box"
box.size_flags_horizontal = Control.SIZE_EXPAND_FILL
box.size_flags_stretch_ratio = 1.4
box.add_theme_constant_override("separation", 10)
pad.add_child(box)
var title := UiKit.label("Dungeon Sweeper",UiKit.FS_H1, true, UiKit.INK_ON_DEEP)
box.add_child(title)
box.add_child(HSeparator.new())
# 组帽·冒险(M2 分组)
box.add_child(UiKit.label("── 冒 险 ──",UiKit.FS_CAPTION, false, UiKit.WARM_FAINT))
# 唯一试玩入口 = 正式「开始冒险」(三关序列);单关测试直入口(L2/L3)退役(2026-09-23 用户拍板)
var resumebtn := UiKit.primary_button("▶ 继续上局") # 迭代 8 卡②——有断点才亮(可见性与关数文案归组合根刷新);主操作钮=审计差距⑤对齐 demo
resumebtn.tooltip_text = "自最近停点恢复"
resumebtn.visible = false
box.add_child(resumebtn)
# 主操作语义对齐 demo(2026-10-06 审计差距⑤——有档时「继续上局」才是首动作=主黄钮,
# 「开始冒险」=常规石钮;tooltip 保留)
var lvbtn := UiKit.button("▶ 开始冒险")
lvbtn.tooltip_text = "三关连探:地精洞窟 → 幽暗地域 → 灰矮人矿城"
box.add_child(lvbtn)
# 组帽·营地与系统
box.add_child(UiKit.label("── 营地与系统 ──",UiKit.FS_CAPTION, false, UiKit.WARM_FAINT))
var campbtn := UiKit.verb_button("营地", UiKit.TEX_ICON_KEYSTONE, "永久强化:钥石换永久属性——跨局成长账本") # 元进度实现批——主菜单营地入口
box.add_child(campbtn)
# 生涯页一级入口(迭代 18 卡④——与营地平级;页=局外只读跨局汇总,零跳转零购买)
var careerbtn := UiKit.button("生涯")
careerbtn.tooltip_text = "跨局生涯汇总:总局数 / 通关局数 / Boss 击杀 / 钥石余额 / 三柜台解锁概览(只读)"
box.add_child(careerbtn)
var settingsbtn := UiKit.button("设置") # 迭代 8 卡④——最小设置面入口
settingsbtn.tooltip_text = "音量 / 全屏 / 减动效"
box.add_child(settingsbtn)
# 二轮修改批 D1(2026-09-29 用户目检裁决):退出游戏与设置同层并行——只在一级菜单出现
var quitbtn := UiKit.button("退出游戏")
box.add_child(quitbtn)
# 迭代 14.5 调试整合批——调试入口收浮层角标;2026-10-16 用户令修正:boot 菜单优先不开
# 浮窗,调试入口=UI 最顶层全局浮层角标(build_debug_badge——LAYER_CONTRACT 末位、
# ModalStack 登记外,任何 UI 不遮挡;对局中两屏角标形态随之退役,全局浮层全场景承载)
# 右列:立绘看台 + 玩法速览牌(M2——帮助全文保留表值驱动,仅容器化;内容含地形变异段不删)
var right := VBoxContainer.new()
right.name = "start_right"
right.size_flags_horizontal = Control.SIZE_EXPAND_FILL
right.add_theme_constant_override("separation", 18)
right.alignment = BoxContainer.ALIGNMENT_CENTER
cols.add_child(right) # ui-struct-probe 批 T2:右列挂 HBox 第二列(原误挂 pad 与左列同矩形叠放——M2 双列失效根因)
var stage := VBoxContainer.new()
stage.alignment = BoxContainer.ALIGNMENT_CENTER
var foe_row := HBoxContainer.new()
foe_row.alignment = BoxContainer.ALIGNMENT_CENTER
foe_row.add_theme_constant_override("separation", 36)
for cfg in [[UiKit.TEX_ENEMY_GOBLIN, 46, 96], [UiKit.TEX_HERO_PORTRAIT, 138, 144], [UiKit.TEX_ENEMY_OGRE_CHIEF, 80, 96]]:
var tr := TextureRect.new()
var tex := UiKit.load_tex(cfg[0])
if tex != null:
tr.texture = tex
tr.custom_minimum_size = Vector2(cfg[1], cfg[2])
tr.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
tr.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
tr.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
if cfg[0] != UiKit.TEX_HERO_PORTRAIT:
tr.modulate = Color(0.7, 0.7, 0.7, 0.4) # 敌影剪影退后(§6.1 立绘>背景)
foe_row.add_child(tr)
stage.add_child(foe_row)
var pedestal := Panel.new()
var ped_sb := UiKit.panel_flat(UiKit.TITLE_PEDESTAL, UiKit.BG_ROOT, 17, 2)
pedestal.add_theme_stylebox_override("panel", ped_sb)
pedestal.custom_minimum_size = Vector2(240, 22)
pedestal.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
stage.add_child(pedestal)
right.add_child(stage)
return {"overlay": overlay, "box": box,
"lvbtn": lvbtn, "resumebtn": resumebtn,
"campbtn": campbtn, "careerbtn": careerbtn,
"settingsbtn": settingsbtn, "quitbtn": quitbtn}
# 调试浮层入口角标(2026-10-16 用户令:UI 最顶层浮层,任何 UI 不遮挡)——main 直接子层、
# LAYER_CONTRACT 末位(float 类:只落树序、不注册 ModalStack——open() 遮挡记账只动登记层,
# 故永不被隐藏);右下角 16px(拇指区惯例,与旧三屏角标同域);可见性归组合根(_debug_entry
# 门控落定后赋值);地图屏底栏 85px 常驻,offset 抬升本函数不处理(组合根 _sync_status_entry)。
static func build_debug_badge(host: Control) -> Button:
var badge := UiKit.button("调试", 0.0) # 角落小件豁免宽度基准(0=不设 min/seals——同状态角标)
badge.add_theme_font_size_override("font_size", UiKit.FS_HINT)
badge.tooltip_text = "打开调试浮窗(当局数据/种子/数值调整)"
badge.grow_horizontal = Control.GROW_DIRECTION_BEGIN # 向左上生长——尺寸变化向屏内
badge.grow_vertical = Control.GROW_DIRECTION_BEGIN
badge.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_RIGHT, Control.PRESET_MODE_MINSIZE, 16)
badge.visible = false # 显式初始化——门控落定后由组合根置位
return badge
# 调试两层模式的层间过渡覆盖层(历史入口专用)。返回 {overlay, label, next_btn}——接线归组合根
static func build_inter_overlay(host: Control) -> Dictionary:
var overlay := overlay_bg(host, Color(0, 0, 0, 0.6))
var box := overlay_box(overlay)
var label := UiKit.label("",UiKit.FS_TITLE, true, UiKit.WARM)
box.add_child(label)
var next_btn := UiKit.button("进入下一层")
box.add_child(next_btn)
overlay.visible = false
return {"overlay": overlay, "label": label, "next_btn": next_btn}
static func overlay_bg(host: Control, color: Color) -> ColorRect:
var dim := ColorRect.new()
dim.color = color
dim.set_anchors_preset(Control.PRESET_FULL_RECT)
host.add_child(dim)
return dim
static func overlay_box(overlay: Control) -> VBoxContainer:
var center := CenterContainer.new()
center.set_anchors_preset(Control.PRESET_FULL_RECT)
overlay.add_child(center)
var box := VBoxContainer.new()
box.add_theme_constant_override("separation", 12)
center.add_child(box)
return box