240 lines
12 KiB
GDScript
240 lines
12 KiB
GDScript
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||
## 视图构建装配器:只做 new/add_child/布局,静态无状态,返回句柄供组合根接线。
|
||
## 接线拓扑唯一归组合根——本类零信号连接。
|
||
## z 序契约:调用方必须按 build 返回序 addChild(登记序 = 模态遮挡序)。
|
||
extends RefCounted
|
||
|
||
|
||
# 战斗屏三区(B 战斗画面 / C·D 两列——节点链
|
||
# 进度由关卡画面本体(选路覆盖层整图)承载,纵向空间归 C/D 底行;
|
||
# 无顶栏进度条、无道具面板区)。分区几何读 ConfigDb.presentation.battle_layout。
|
||
# + 结算演出覆盖层(A–D 之上、模态之下——跨区弹道/光带载体)。
|
||
# 返回句柄字典:{stage, log_view, info_panel, board_zone, board_view, ap_bar, settlement_fx}
|
||
# (返回句柄不含 gp_label/keystone_label——GP/钥石不在战斗屏呈现)
|
||
static func build_battle_ui(root: Control, db: ConfigDb) -> Dictionary:
|
||
# set_anchors_and_offsets_preset(非 set_anchors_preset):后者会把控件重置回最小内容尺寸
|
||
# (实测 927×613——缩顶、棋盘 45px 格 ×10 行超出 D 区 333px 高被裁)
|
||
root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
root.add_theme_constant_override("separation", 0)
|
||
|
||
var layout: BattleLayoutTable = db.presentation.battle_layout
|
||
|
||
# B 区:战斗画面(全宽 × battle_layout.stage_h——常驻双舞台,布局决策 D1)
|
||
var stage := BattleStageView.new(db, layout)
|
||
root.add_child(stage)
|
||
|
||
# C/D 两列(高度 = 基座高 − B 区高——底行承载)
|
||
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(layout)
|
||
bottom.add_child(info_panel)
|
||
|
||
var board_zone := BoardZoneView.new(layout)
|
||
bottom.add_child(board_zone)
|
||
board_zone.ap_bar.set_refresh_cost(int(db.run.action_economy.rap_refresh_cost)) # RAP 读数表值驱动
|
||
|
||
# 结算演出覆盖层(A–D 之上——z 序 = addChild 序,模态覆盖层组在 build_overlays 中更晚挂入)
|
||
var settlement_fx := SettlementFxView.new()
|
||
root.add_child(settlement_fx)
|
||
|
||
# CJK 字体随包(Fusion Pixel OFL):Control 侧默认主题字体走 project.godot
|
||
# gui/theme/custom_font;SubViewport 两世界不继承 Control 主题——显式注入挂点接线(C6 挂点链)。
|
||
# 缺位守卫(load_font 返回 null = 引擎回退字体,不炸)。
|
||
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/钥石余额位不在战斗屏(中/大循环资源迁出战斗屏——
|
||
# 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。
|
||
# (本组不含调试浮窗——由组合根直构挂树序最末,见 main._build_ui;
|
||
# 本组不含明细回看 overlay——冒险中回看归「生涯」页;
|
||
# 本组不含营地覆盖层——在 camp_screen 屏壳构建)
|
||
static func build_overlays(host: Control, db: ConfigDb) -> Dictionary:
|
||
var legend_overlay := LegendOverlay.new(int(db.run.action_economy.rap_refresh_cost)) # 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)
|
||
|
||
# 商店覆盖层(商店房节点兑付呈现;按钮接线归组合根,装配器零信号连接)
|
||
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, entersavebtn, settingsbtn, quitbtn}——按钮接线归组合根;
|
||
# 随机种子/数值调整板/两层调试三散件不在主按钮组,收进调试浮窗——
|
||
# 调试入口=UI 最顶层全局浮层角标(build_debug_badge,本层零调试入口)
|
||
# 布局=双列 VBox(左操作 56% + 右舞台);按钮动词化+悬停解释
|
||
#(解释全迁 tooltip,G5);entersavebtn 为主操作钮(烛光黄 CANDLE——主菜单三入口化唯一进度入口);右列=立绘看台。**界面禁玩法文字墙
|
||
#(教学内容归 Beta 帮助手册〔教学引导定稿 P4 丙〕)**;本层无玩法速览牌;
|
||
# tooltip 主题归全局主题单点(禁在 root 挂程序化 tooltip 主题)。
|
||
# 返回键集如首行所列,接线归组合根)
|
||
static func build_start_overlay(host: Control, db: ConfigDb = null) -> Dictionary:
|
||
var overlay := overlay_bg(host, UiKit.BG_BRIGHT)
|
||
# 石纹弱质感+斜光带+暗角(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())
|
||
# 双列骨架:左=操作列 56% / 右=舞台(立绘看台)
|
||
var cols := _start_columns(overlay)
|
||
var box := _start_left_column(cols)
|
||
var entersavebtn := UiKit.primary_button("进入存档") # 主操作钮(唯一进度入口)
|
||
entersavebtn.tooltip_text = "选择一个存档位进入(新档 / 进档 / 删档)"
|
||
box.add_child(entersavebtn)
|
||
var settingsbtn := UiKit.button("设置") # 最小设置面入口(行为零变化——契约 2)
|
||
settingsbtn.tooltip_text = "音量 / 全屏 / 减动效"
|
||
box.add_child(settingsbtn)
|
||
# 退出游戏与设置同层并行——只在一级菜单出现(行为零变化——契约 2)
|
||
var quitbtn := UiKit.button("退出游戏")
|
||
box.add_child(quitbtn)
|
||
# 调试入口收浮层角标;boot 菜单优先不开
|
||
# 浮窗,调试入口=UI 最顶层全局浮层角标(build_debug_badge——LAYER_CONTRACT 末位、
|
||
# ModalStack 登记外,任何 UI 不遮挡;本层不设屏内调试角标,全局浮层全场景承载)
|
||
_start_right_column(cols)
|
||
return {"overlay": overlay, "box": box,
|
||
"entersavebtn": entersavebtn,
|
||
"settingsbtn": settingsbtn, "quitbtn": quitbtn}
|
||
|
||
|
||
## 双列骨架域:左=操作列 56% / 右=舞台(立绘看台)——全屏锚+56px 四边衬距
|
||
static func _start_columns(overlay: Control) -> HBoxContainer:
|
||
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)
|
||
return cols
|
||
|
||
|
||
## 左操作列域:标题+三入口(主菜单三入口化——营地交互重做卡②,用户裁决 1:
|
||
## 进入存档/设置/退出游戏;组帽双撤——冒险组帽组内零钮退化态不留、「营地与系统」帽指称失义,追认单留痕)
|
||
static func _start_left_column(cols: HBoxContainer) -> VBoxContainer:
|
||
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)
|
||
(cols.get_child(0) as MarginContainer).add_child(box)
|
||
box.add_child(UiKit.label("Dungeon Sweeper",UiKit.FS_H1, true, UiKit.INK_ON_DEEP))
|
||
box.add_child(HSeparator.new())
|
||
return box
|
||
|
||
|
||
## 右列域:立绘看台(三张立绘 + 标题底座;界面禁玩法文字墙,故无速览牌)
|
||
static func _start_right_column(cols: HBoxContainer) -> void:
|
||
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) # 右列挂 HBox 第二列(禁挂 pad——与左列同矩形叠放会破双列)
|
||
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]]:
|
||
foe_row.add_child(_start_portrait(cfg))
|
||
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)
|
||
|
||
|
||
## 单立绘域(循环体提函数——像素过滤居中;敌影剪影退后 §6.1 立绘>背景)
|
||
static func _start_portrait(cfg: Array) -> TextureRect:
|
||
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)
|
||
return tr
|
||
|
||
|
||
# 调试浮层入口角标(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
|