- T0: game/assets/theme/main_theme.tres 挂 project.godot gui/theme/custom—— 15 类型全覆盖(V/HScrollBar·ScrollContainer·H/VSlider·TooltipPanel/Label· LineEdit·SpinBox·H/VSeparator)+预盖 CheckBox/CheckButton/OptionButton/ PopupMenu 弹层;default_font 显式指现役同字体(V6 中文字形防御); 位图生成脚本 tools/gen_theme_icons.py(9 张纯色/几何小位图→assets/art/ui/gen/, 按 .gitignore 政策本地生成不入库);色值溯源表 game/assets/theme/README.md - T1: ui_kit.gd 删 tooltip_theme()/_tip_theme 与 styled_slider()、ui_assembler.gd 删挂载行(旧实现同批删除);settings_overlay 几何内联、debug_overlay 双轨消隐 - T2: 冒烟 _smoke_global_theme() 38 断言(V1 类型覆盖+Button/focus 双红线+ V2 冻结值 5.49:1+V3 预盖证词+V4 退役零残留+V6 字体防御) - 引擎事实(4.7.2 源码+默认主题链双查):CheckButton 与 CheckBox 同套 checked/unchecked 图标名;Slider 无 grabber_pressed 槽;ScrollBar 柄=StyleBox
247 lines
13 KiB
GDScript
247 lines
13 KiB
GDScript
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||
# 实现 GDD:design/ux/battle.md(四区装配器——组合根的 UI 构建拆分载体)。
|
||
# Date: 2026-09-12(code-review 修复批:main.gd 回涨 761 行触规范 1.1 审视线——构建拆出)
|
||
# 高内聚边界(规范 1.1/1.3):视图构建装配器——只做 new / add_child / 布局,
|
||
# 零信号连接、零核心写调用;返回句柄供组合根接线(ADR-0001:接线拓扑唯一归组合根)。
|
||
# 静态无状态。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, camp_overlay};调用方按本序 addChild。
|
||
# (调整板已随迭代 14.5 调试整合批退役——调试浮窗由组合根直构挂树序最末,见 main._build_ui;
|
||
# 明细回看 overlay 已随 M9/P-C7 退役 2026-10-04——冒险中回看归「生涯」06 登记)
|
||
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)
|
||
|
||
# 营地覆盖层(元进度实现批 2026-09-21——主菜单营地入口兑付呈现;开合与开始层互斥仿设置面先例)
|
||
var camp_overlay := CampOverlay.new()
|
||
host.add_child(camp_overlay)
|
||
|
||
return {
|
||
"legend_overlay": legend_overlay,
|
||
"node_overlay": node_overlay,
|
||
"end_overlay": end_overlay,
|
||
"shop_overlay": shop_overlay,
|
||
"camp_overlay": camp_overlay,
|
||
}
|
||
|
||
|
||
# 开始覆盖层(返回 {overlay, box, lvbtn, resumebtn, campbtn, careerbtn, settingsbtn, quitbtn}——按钮接线归组合根;
|
||
# 迭代 14.5 调试整合批:随机种子/数值调整板/两层调试三散件撤出主按钮组,收进调试浮窗——
|
||
# 迭代 18 卡⑦ P2 甲:右下角「调试」角标亦退役(入口迁宿主=战斗屏 HUD+关卡地图屏两枚角标,
|
||
# DS_DEBUG=1 会话浮窗启动即开——本层零调试入口)
|
||
# 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)
|
||
var sub := UiKit.label("扫雷的因 · 逐行兑现的果",UiKit.FS_BODY, false, UiKit.WARM_SOFT)
|
||
box.add_child(sub)
|
||
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)
|
||
|
||
# 迭代 18 卡⑦ P2 甲——旧右下角调试角标退役(原迭代 14.5 装配段删除):入口迁宿主两屏
|
||
#(board_zone_view.debug_badge / level_map_screen.debug_badge),DS_DEBUG 门控与可见性
|
||
# 同步归组合根 _sync_status_entry;开始层零调试入口(DS_DEBUG=1 会话=浮窗启动即开)
|
||
|
||
# 右列:立绘看台 + 玩法速览牌(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}
|
||
|
||
|
||
# 调试两层模式的层间过渡覆盖层(历史入口专用)。返回 {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}
|
||
|
||
|
||
# Playtest 诊断按钮(调试变体——battle.md §States 允许;卡死转储)
|
||
static func build_diag_button(host: Control) -> Button:
|
||
var diag_btn := UiKit.button("诊断转储", 0.0) # 角落小件豁免宽度基准(固定 offsets 112px 宽)
|
||
diag_btn.set_anchors_preset(Control.PRESET_BOTTOM_LEFT)
|
||
diag_btn.offset_left = 6.0
|
||
diag_btn.offset_top = -30.0
|
||
diag_btn.offset_right = 118.0
|
||
diag_btn.offset_bottom = -6.0
|
||
host.add_child(diag_btn)
|
||
return diag_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
|