卡②T2b(呈现层结构收敛批): u8–u17——u9 battle_stage_view(_init拆背景/光柱/世界/文本半区[轮次牌/中段行]/伴随件六族+_enemy_portrait_tex查表化+_spawn_enemy_standee循环提函数)+u10 curio_pick(_init拆顶条/主体两域+_icon_for_id映射表化)+u11 settings(_build拆头部/音量/全屏/演出/返回五行域)+u12 battle_log_view(_format_row按行型拆九私有格式化函数[effect/damage/ineffective/seal_miss/verdict/simple三型/default]+dispatch出参聚合)+u14 ap_bar(_init拆AP计量/RAP/确认/图例刷新四域)+u15 board_zone(_init拆视图/角标+棋盘子件域)+u16 board_fx(光带循环提_spawn_sweep_band)+u17 legend(行构建提_legend_row)+u8 ui_assembler(build_start_overlay拆双列/左列/右列/单立绘四族);gdstyle基线内45→28键

This commit is contained in:
DKChan committed 2026-10-28 15:09:02 +08:00
1 parent 123d7c7832
commit 525529ad88
8 files changed
+318 -176

No files matched your search

+52 -38
View File
@@ -104,28 +104,8 @@ static func build_start_overlay(host: Control, db: ConfigDb = null) -> Dictionar
overlay.add_child(UiKit._beams_rect())
overlay.add_child(UiKit.vignette_rect())
# 双列骨架:左=操作列 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())
# 三入口(主菜单三入口化——营地交互重做卡②,用户裁决 1:进入存档/设置/退出游戏;
# 组帽双撤——冒险组帽组内零钮退化态不留、「营地与系统」帽指称失义,追认单留痕)
var cols := _start_columns(overlay)
var box := _start_left_column(cols)
var entersavebtn := UiKit.primary_button("进入存档") # 主操作钮(唯一进度入口)
entersavebtn.tooltip_text = "选择一个存档位进入(新档 / 进档 / 删档)"
box.add_child(entersavebtn)
@@ -135,12 +115,45 @@ static func build_start_overlay(host: Control, db: ConfigDb = null) -> Dictionar
# 退出游戏与设置同层并行——只在一级菜单出现(行为零变化——契约 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
@@ -153,17 +166,7 @@ static func build_start_overlay(host: Control, db: ConfigDb = null) -> Dictionar
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)
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)
@@ -173,9 +176,20 @@ static func build_start_overlay(host: Control, db: ConfigDb = null) -> Dictionar
stage.add_child(pedestal)
right.add_child(stage)
return {"overlay": overlay, "box": box,
"entersavebtn": entersavebtn,
"settingsbtn": settingsbtn, "quitbtn": quitbtn}
## 单立绘域(循环体提函数——像素过滤居中;敌影剪影退后 §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 直接子层、
+104 -53
View File
@@ -145,71 +145,122 @@ func _format_row(row: Dictionary, agg_count := -1) -> String:
var src := _source_label(String(row.get("source_class", "")))
var round_no: int = int(row.get("round_index", 0))
var prefix := " [color=#%s]R%d%s[/color] " % [UiKit.HEX_LOG_ROUND, round_no, ("·" + src) if src != "" else ""]
var hl := String(row.get("highlight", ""))
var v: int = int(row.get("value", 0))
var name_: String = String(row.get("display_name", ""))
var target := String(row.get("target", ""))
var tail := " → %s" % target if target != "" else ""
var suffix := "(%s)" % src if src != "" else ""
match String(row.get("type", "")):
var kind := String(row.get("type", ""))
var body := ""
match kind:
"effect":
# GP 型效果行:「GP 袋 +3」兑付反馈——value = 本行 ±power,
# escrow = 战斗内缓冲现值(入钱包在战斗末一次上抛——呈现语义与增益池分账)
if String(row.get("effect_id", "")) in ["gp_gain", "gp_loss"]:
var gp_col := UiKit.HEX_LOG_GAIN if v > 0 else UiKit.HEX_LOG_LOSS
var gp_body := "%s %+d(战斗暂存 %+d)%s" % [name_, v, int(row.get("escrow", 0)), suffix]
return prefix + (_gold_frame(gp_body) if hl != "" else "[color=%s]%s[/color]" % [gp_col, gp_body])
# 效果行 value = 单次贡献值,pool_value = 池合成值——禁相加呈现(战斗结算 呈现细则)
var pool: int = int(row.get("pool_value", 0))
var col := UiKit.HEX_LOG_HEAL if v > 0 else UiKit.HEX_LOG_LOSS
var body := "%s 单次%+d(池 %+d)%s" % [name_, v, pool, suffix]
return prefix + (_gold_frame(body) if hl != "" else "[color=%s]%s[/color]" % [col, body])
body = _fmt_effect(row, prefix, suffix)
"damage":
# 伤害行无行上拆行文案——行上归因读数只剩掷点档位随行
# 护甲等级掷点留档(掷点读数与档位随行可读;吃掷点分量恒带,棋盘来源行不带)
var roll_txt := ""
if row.get("roll", null) != null:
roll_txt = "〔掷 %d vs 护甲 %d" % [int(row.roll), int(row.ac)]
if bool(row.get("crit", false)):
roll_txt += " · 暴击×%d" % int(_db.combat.settlement.damage_roll.crit_multiplier)
elif int(row.tier) < 100:
roll_txt += " · ×%d%%" % int(row.tier)
roll_txt += "〕"
var kill := " ⚔击杀!" if row.get("killed", false) else ""
# 伤害数字不明显——伤害值放大加色独立于行文(朱红大一号——数字即信息主角,非同字号色温微差)
var body := "%s [color=#%s][size=17]-%d[/size][/color]%s%s%s%s" % [name_, UiKit.HEX_LOG_DMG_NUM, v, roll_txt, tail, kill, suffix]
var body_col := UiKit.HEX_LOG_DMG_KILL if row.get("killed", false) else (UiKit.HEX_LOG_ROUND if v == 0 else UiKit.HEX_LOG_DMG_BODY)
if bool(row.get("crit", false)):
return prefix + _gold_frame(body) # nat 最大面暴击 = 金框高亮行
return prefix + "[color=%s]%s[/color]" % [body_col, body]
"heal":
return prefix + "[color=#%s]%s +%d%s%s[/color]" % [UiKit.HEX_LOG_HEAL, name_, v, tail, suffix]
body = _fmt_damage(row, prefix, suffix)
"heal", "cast", "decay":
body = _fmt_simple(kind, row, prefix, suffix)
"reward_ineffective":
# 未生效类行:⚠ icon + 划线形状冗余(非色相独载)+ 聚合计数
var n := (" ×%d" % agg_count) if agg_count > 1 else ""
return prefix + "[s][color=#%s]⚠ %s%s[/color][/s]%s" % [UiKit.HEX_LOG_VOIDED, name_, n, suffix]
body = _fmt_ineffective(row, prefix, suffix, agg_count)
"seal_miss":
var miss_note := ""
if hl == "perfect_broken":
miss_note = "(本图完美封印已破——清图奖励不再加倍)"
elif hl == "":
miss_note = "(首次封印落空已豁免——不计入完美封印判定)"
var body := "✗ %s%s" % [name_, miss_note]
return prefix + (_gold_frame(body) if hl == "perfect_broken" else "[color=#%s]%s[/color]" % [UiKit.HEX_LOG_ROUND, body])
body = _fmt_seal_miss(row, prefix)
"verdict":
body = _fmt_verdict(row)
_:
body = _fmt_default(row, prefix, suffix)
return body
## 简单行域(heal/cast/decay 三型——单行单墨直排)
func _fmt_simple(kind: String, row: Dictionary, prefix: String, suffix: String) -> String:
var name_: String = String(row.get("display_name", ""))
match kind:
"heal":
return prefix + "[color=#%s]%s +%d%s%s[/color]" % [UiKit.HEX_LOG_HEAL, name_, int(row.get("value", 0)), _tail_of(row), suffix]
"cast":
# 施放行:增益技 0 伤害施放,具名技能
return prefix + "[color=#%s]✦ %s%s[/color]" % [UiKit.HEX_LOG_CAST, name_, suffix]
"decay":
return prefix + "[color=#%s]状态衰减 ×%d[/color]" % [UiKit.HEX_LOG_DECAY, v]
"verdict":
if int(row.value) == 1:
return "[color=#%s][b]◆ %s[/b][/color]" % [UiKit.HEX_LOG_WIN, name_]
return "[color=#%s][b]◆ %s[/b][/color]" % [UiKit.HEX_LOG_GAIN, name_]
"highlight":
return prefix + _gold_frame("★ %s" % name_)
return prefix + "[color=#%s]状态衰减 ×%d[/color]" % [UiKit.HEX_LOG_DECAY, int(row.get("value", 0))]
return ""
## 默认行域(未知型/高亮星标——未知型=名行直排)
func _fmt_default(row: Dictionary, prefix: String, suffix: String) -> String:
var name_: String = String(row.get("display_name", ""))
if String(row.get("type", "")) == "highlight":
return prefix + _gold_frame("★ %s" % name_)
return prefix + name_ + suffix
## 尾注域:目标格指向(行文附加)
func _tail_of(row: Dictionary) -> String:
var target := String(row.get("target", ""))
return " → %s" % target if target != "" else ""
## GP 型效果行域:「GP 袋 +3」兑付反馈——value = 本行 ±power,
## escrow = 战斗内缓冲现值(入钱包在战斗末一次上抛——呈现语义与增益池分账)
func _fmt_effect(row: Dictionary, prefix: String, suffix: String) -> String:
var v: int = int(row.get("value", 0))
var hl := String(row.get("highlight", ""))
var name_: String = String(row.get("display_name", ""))
if String(row.get("effect_id", "")) in ["gp_gain", "gp_loss"]:
var gp_col := UiKit.HEX_LOG_GAIN if v > 0 else UiKit.HEX_LOG_LOSS
var gp_body := "%s %+d(战斗暂存 %+d)%s" % [name_, v, int(row.get("escrow", 0)), suffix]
return prefix + (_gold_frame(gp_body) if hl != "" else "[color=%s]%s[/color]" % [gp_col, gp_body])
# 效果行 value = 单次贡献值,pool_value = 池合成值——禁相加呈现(战斗结算 呈现细则)
var pool: int = int(row.get("pool_value", 0))
var col := UiKit.HEX_LOG_HEAL if v > 0 else UiKit.HEX_LOG_LOSS
var body := "%s 单次%+d(池 %+d)%s" % [name_, v, pool, suffix]
return prefix + (_gold_frame(body) if hl != "" else "[color=%s]%s[/color]" % [col, body])
## 伤害行域:行上归因读数只剩掷点档位随行(护甲等级掷点留档——掷点读数与档位随行可读;
## 吃掷点分量恒带,棋盘来源行不带)。伤害数字不明显——伤害值放大加色独立于行文(朱红大一号)
func _fmt_damage(row: Dictionary, prefix: String, suffix: String) -> String:
var v: int = int(row.get("value", 0))
var name_: String = String(row.get("display_name", ""))
var tail := _tail_of(row)
var roll_txt := ""
if row.get("roll", null) != null:
roll_txt = "〔掷 %d vs 护甲 %d" % [int(row.roll), int(row.ac)]
if bool(row.get("crit", false)):
roll_txt += " · 暴击×%d" % int(_db.combat.settlement.damage_roll.crit_multiplier)
elif int(row.tier) < 100:
roll_txt += " · ×%d%%" % int(row.tier)
roll_txt += "〕"
var kill := " ⚔击杀!" if row.get("killed", false) else ""
var body := "%s [color=#%s][size=17]-%d[/size][/color]%s%s%s%s" % [name_, UiKit.HEX_LOG_DMG_NUM, v, roll_txt, tail, kill, suffix]
if bool(row.get("crit", false)):
return prefix + _gold_frame(body) # nat 最大面暴击 = 金框高亮行
var body_col := UiKit.HEX_LOG_DMG_KILL if row.get("killed", false) else (UiKit.HEX_LOG_ROUND if v == 0 else UiKit.HEX_LOG_DMG_BODY)
return prefix + "[color=%s]%s[/color]" % [body_col, body]
## 未生效行域:⚠ icon + 划线形状冗余(非色相独载)+ 聚合计数
func _fmt_ineffective(row: Dictionary, prefix: String, suffix: String, agg_count: int) -> String:
var name_: String = String(row.get("display_name", ""))
var n := (" ×%d" % agg_count) if agg_count > 1 else ""
return prefix + "[s][color=#%s]⚠ %s%s[/color][/s]%s" % [UiKit.HEX_LOG_VOIDED, name_, n, suffix]
## 封印落空行域:完美破/首失豁免两注记分档(金框=破完美/普通墨=豁免)
func _fmt_seal_miss(row: Dictionary, prefix: String) -> String:
var hl := String(row.get("highlight", ""))
var name_: String = String(row.get("display_name", ""))
var miss_note := ""
if hl == "perfect_broken":
miss_note = "(本图完美封印已破——清图奖励不再加倍)"
elif hl == "":
miss_note = "(首次封印落空已豁免——不计入完美封印判定)"
var body := "✗ %s%s" % [name_, miss_note]
return prefix + (_gold_frame(body) if hl == "perfect_broken" else "[color=#%s]%s[/color]" % [UiKit.HEX_LOG_ROUND, body])
## 裁定行域:胜金/负绿双墨(粗体菱形头)
func _fmt_verdict(row: Dictionary) -> String:
var name_: String = String(row.get("display_name", ""))
if int(row.value) == 1:
return "[color=#%s][b]◆ %s[/b][/color]" % [UiKit.HEX_LOG_WIN, name_]
return "[color=#%s][b]◆ %s[/b][/color]" % [UiKit.HEX_LOG_GAIN, name_]
func _strip_bb(bb: String) -> String:
var out := ""
var in_tag := false
+52 -43
View File
@@ -26,18 +26,28 @@ var world: Node2D # StageWorld(preload 脚本实例——Node2D 世
func _init(db_ref) -> void:
custom_minimum_size = Vector2(0, 252)
# 面板底(圣经 v3.5 §6.1 舞台正面目标:B 区=地窟看台,世界底层暗背景
# =方向稿 render_battle_screen 取值(值单点收 UiKit.STAGE_BG);描边=PANEL_BORDER 同方向稿)
_build_backdrop()
_build_world_stage()
_build_text_half(db_ref)
_build_companion_layer(db_ref)
## 背景域(圣经 v3.5 §6.1 舞台正面目标:B 区=地窟看台,世界底层暗背景
## =方向稿 render_battle_screen 取值(值单点收 UiKit.STAGE_BG);描边=PANEL_BORDER 同方向稿)
func _build_backdrop() -> void:
var bg := Panel.new()
var sb := UiKit.panel_flat(UiKit.STAGE_BG, UiKit.PANEL_BORDER, 0, 1)
bg.add_theme_stylebox_override("panel", sb)
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(bg)
_build_beams_layer()
# 顶光柱氛围层(§6.1 光源氛围——方向稿四柱:x 基准 110/900/1055/1210@1280,暖光(值单点收
# UiKit.BEAM_LIGHT——T4 裸色收口)自顶向下 60 段渐宽窄渐隐〔顶 α26→0〕;绘制在暗底之上/
# 世界站桩之下——层序=bg < 光柱 < 世界)
## 顶光柱氛围层域(§6.1 光源氛围——方向稿四柱:x 基准 110/900/1055/1210@1280,暖光(值单点收
## UiKit.BEAM_LIGHT——T4 裸色收口)自顶向下 60 段渐宽窄渐隐〔顶 α26→0〕;绘制在暗底之上/
## 世界站桩之下——层序=bg < 光柱 < 世界)
func _build_beams_layer() -> void:
var beams := Control.new()
beams.set_anchors_preset(Control.PRESET_FULL_RECT)
beams.mouse_filter = Control.MOUSE_FILTER_IGNORE
@@ -45,7 +55,9 @@ func _init(db_ref) -> void:
beams.resized.connect(beams.queue_redraw)
add_child(beams)
# ── Node2D 舞台半区(战斗舞台迁移批 Phase B 同构;stretch 1:1 无缩放系数——C2/C5)──
## Node2D 舞台半区域(战斗舞台迁移批 Phase B 同构;stretch 1:1 无缩放系数——C2/C5)
func _build_world_stage() -> void:
_svp_container = SubViewportContainer.new()
_svp_container.stretch = true # 容器尺寸 = 视口逻辑分辨率,恒 1:1
_svp_container.mouse_filter = Control.MOUSE_FILTER_IGNORE # 世界零输入(tooltip 归壳层伴随件)
@@ -61,7 +73,9 @@ func _init(db_ref) -> void:
world = _STAGE_WORLD.new()
_viewport.add_child(world)
# ── Control 文本半区(round_label / stage_center / log_view 原样保留——C1 永久 Control)──
## Control 文本半区域(round_label / stage_center / log_view 原样保留——C1 永久 Control)
func _build_text_half(db_ref) -> void:
var vb := VBoxContainer.new()
vb.add_theme_constant_override("separation", 4)
vb.set_anchors_preset(Control.PRESET_FULL_RECT)
@@ -70,8 +84,14 @@ func _init(db_ref) -> void:
vb.offset_right = -_CONTENT_MARGINS.size.x
vb.offset_bottom = -_CONTENT_MARGINS.size.y
add_child(vb)
_build_narr_row(vb)
_build_center_row(vb)
log_view = BattleLogView.new(db_ref)
vb.add_child(log_view)
# M8(UI 落码批):轮次牌 plate9 素牌化(demo ⑦——墨字上亮牌 12.3:1,暗底暖字退役)
## 轮次牌域(M8 UI 落码批:plate9 素牌化——demo ⑦,墨字上亮牌 12.3:1,暗底暖字退役)
func _build_narr_row(vb: VBoxContainer) -> void:
var narr_plate := PanelContainer.new()
narr_plate.add_theme_stylebox_override("panel", UiKit.panel_skinned(Vector4(12, 4, 12, 4)))
narr_plate.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
@@ -80,12 +100,14 @@ func _init(db_ref) -> void:
round_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
narr_plate.add_child(round_label)
## 中段行域:提示语居中+敌站桩壳层伴随件容器(明细带展开/收起改行高——世界布局随动)
func _build_center_row(vb: VBoxContainer) -> void:
_row = HBoxContainer.new()
_row.add_theme_constant_override("separation", 10)
_row.size_flags_vertical = Control.SIZE_EXPAND_FILL
vb.add_child(_row)
_row.resized.connect(_on_row_resized) # 明细带展开/收起改行高——世界布局随动
_row.resized.connect(_on_row_resized)
var center := CenterContainer.new()
center.size_flags_horizontal = Control.SIZE_EXPAND_FILL
center.mouse_filter = Control.MOUSE_FILTER_IGNORE # 行内只剩文本——鼠标全放行给壳层伴随件
@@ -96,21 +118,17 @@ func _init(db_ref) -> void:
stage_center.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
center_col.add_child(stage_center)
_row.add_child(center)
enemy_box = Control.new() # 站桩壳层伴随件容器(T3 语义改写)
enemy_box.mouse_filter = Control.MOUSE_FILTER_IGNORE
_row.add_child(enemy_box)
log_view = BattleLogView.new(db_ref)
vb.add_child(log_view)
# ── 壳层伴随件层(最顶——世界站桩之上的 Control 触达面)──
## 壳层伴随件层域(最顶——世界站桩之上的 Control 触达面)+主角站桩注册(世界)+ 状态行伴随件(壳层)
func _build_companion_layer(db_ref) -> void:
_companion_layer = Control.new()
_companion_layer.set_anchors_preset(Control.PRESET_FULL_RECT)
_companion_layer.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(_companion_layer)
# 主角站桩本体(世界)+ 状态行伴随件(壳层)
hero_standee = _STANDEE_BODY.new("hero")
var hero_tex := UiKit.load_tex(UiKit.TEX_HERO_PORTRAIT)
hero_standee.set_portrait_texture(hero_tex, true) # B 区立绘 48px 档·normal 态朝右(素材缺位 null 回退字形「人」)
@@ -230,26 +248,24 @@ func enemy_body_at(i: int) -> Node2D:
# 素材装载属组合点职责(主角侧同款先例 _init TEX_HERO_PORTRAIT),StandeeBody 保持纯收 tex 面。
# 素材缺位 load_tex→null→set_portrait_texture 字形回退(表现与素材批前逐位一致);未录键返回 null
# 走默认字形「敌」(与 StandeeBody._portrait_glyph 默认档同口径)——数据表扩键先补此处映射。
## 敌像 id→纹理键映射表(查表+回退——映射语义零变化;未知 id=null 回退字形)
func _enemy_portrait_tex(enemy_id: String) -> Texture2D:
match enemy_id:
"goblin":
return UiKit.load_tex(UiKit.TEX_ENEMY_GOBLIN)
"bugbear":
return UiKit.load_tex(UiKit.TEX_ENEMY_BUGBEAR)
"ogre_chief":
return UiKit.load_tex(UiKit.TEX_ENEMY_OGRE_CHIEF)
"giant_spider":
return UiKit.load_tex(UiKit.TEX_ENEMY_GIANT_SPIDER)
"drider":
return UiKit.load_tex(UiKit.TEX_ENEMY_DRIDER)
"duergar":
return UiKit.load_tex(UiKit.TEX_ENEMY_DUERGAR)
"hook_horror":
return UiKit.load_tex(UiKit.TEX_ENEMY_HOOK_HORROR)
"duergar_king":
return UiKit.load_tex(UiKit.TEX_ENEMY_DUERGAR_KING)
_:
return null
var table := {"goblin": UiKit.TEX_ENEMY_GOBLIN, "bugbear": UiKit.TEX_ENEMY_BUGBEAR,
"ogre_chief": UiKit.TEX_ENEMY_OGRE_CHIEF, "giant_spider": UiKit.TEX_ENEMY_GIANT_SPIDER,
"drider": UiKit.TEX_ENEMY_DRIDER, "duergar": UiKit.TEX_ENEMY_DUERGAR,
"hook_horror": UiKit.TEX_ENEMY_HOOK_HORROR, "duergar_king": UiKit.TEX_ENEMY_DUERGAR_KING}
return UiKit.load_tex(String(table.get(enemy_id, ""))) if table.has(enemy_id) else null
## 单敌站桩构建域(循环体提函数——世界本体+壳层伴随件+敌种立绘接线〔仅重建拍装载一次;
## flip=false——八敌像素源核证朝左/正面,呈现即朝主角无误;反向朝向源换代时翻转义务归素材批〕)
func _spawn_enemy_standee(d: Dictionary) -> Node2D:
var body: Node2D = _STANDEE_BODY.new("enemy")
world.add_child(body)
body.slot_row = _make_slot_companion(enemy_box)
body.set_data(d)
body.set_portrait_texture(_enemy_portrait_tex(String(d.get("enemy_id", ""))), false)
return body
# 敌方站桩投影更新:数量不变 = 就地更新(死亡灰化/受击闪烁等站桩上动画不被逐行刷新重置——禁瞬消纪律);
@@ -263,14 +279,7 @@ func set_enemies(cards_data: Array) -> void:
c.queue_free()
_cards.clear()
for d in cards_data:
var body: Node2D = _STANDEE_BODY.new("enemy")
world.add_child(body)
body.slot_row = _make_slot_companion(enemy_box)
body.set_data(d)
# 敌种立绘接线(仅重建拍装载一次;flip=false——八敌像素源核证朝左/正面,
# 呈现即朝主角无误;反向朝向源换代时翻转义务归素材批)
body.set_portrait_texture(_enemy_portrait_tex(String(d.get("enemy_id", ""))), false)
_cards.append(body)
_cards.append(_spawn_enemy_standee(d))
_sync_layout.call_deferred()
return
for i in cards_data.size():
+15 -10
View File
@@ -89,19 +89,24 @@ func play_boss_shadow() -> void:
# 完美封印:金光带加倍扫射 + 横幅占位(§2.2 最高档)
func play_perfect_sweep() -> void:
for i in 2:
var band := ColorRect.new()
band.color = Color(COL_GOLD, 0.35)
band.size = Vector2(size.x, 14.0 + 10.0 * i)
band.position = Vector2(-size.x, size.y * 0.3 + 40.0 * i)
band.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(band)
var tw := band.create_tween()
tw.tween_property(band, "position:x", size.x + 8.0, UiKit.motion(0.5 + 0.15 * i))
tw.tween_callback(band.queue_free)
for i in 3:
_spawn_sweep_band(i, size)
_show_banner("完美封印 ×2")
## 单光带域(循环体提函数——自左掠过渐出)
func _spawn_sweep_band(i: int, size: Vector2) -> void:
var band := ColorRect.new()
band.color = Color(COL_GOLD, 0.35)
band.size = Vector2(size.x, 14.0 + 10.0 * i)
band.position = Vector2(-size.x, size.y * 0.3 + 40.0 * i)
band.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(band)
var tw := band.create_tween()
tw.tween_property(band, "position:x", size.x + 8.0, UiKit.motion(0.5 + 0.15 * i))
tw.tween_callback(band.queue_free)
# 封印命中:封印化暴击弹道加重占位(自棋盘上缘飞向 B 区敌人卡方向——越界即收)
func play_seal_hit_projectile(heavy: bool) -> void:
var shot := ColorRect.new()
@@ -23,6 +23,12 @@ func _init() -> void:
var vb := VBoxContainer.new()
vb.add_theme_constant_override("separation", 4)
add_child(vb)
_build_views(vb)
_build_status_corner()
## 视图三件域:AP 条+棋盘区(含动效与 toast 叠层)
func _build_views(vb: VBoxContainer) -> void:
ap_bar = ApBarView.new()
vb.add_child(ap_bar)
var board_area := Control.new()
@@ -31,6 +37,11 @@ func _init() -> void:
vb.add_child(board_area)
_board_area = board_area
board_area.resized.connect(_on_board_area_resized)
_build_board_children(board_area)
## 棋盘子件域:视图+动效+toast 三叠层挂接
func _build_board_children(board_area: Control) -> void:
board_view = BoardView.new()
board_view.set_anchors_preset(Control.PRESET_FULL_RECT)
board_area.add_child(board_view)
+42 -14
View File
@@ -30,7 +30,14 @@ func _init() -> void:
# bcard 三竖卡(D1 232×352)+ 底部确认主钮与免费选择注记。差异白名单=免费选择/✓章/无价签。
# 状态机不变:pick→result。M7 同构裁决遗产保留。
var page: Dictionary[Variant, Variant] = UiKit.page_shell(self, false)
var top: HBoxContainer = page.top_row
_build_top_row(page.top_row)
var box: VBoxContainer = page.box
_build_body(box)
visible = false
## 顶条域:标题+刷新图标钮(44×44 豁免宽度基准+角标叠加)+刷新余量行
func _build_top_row(top: HBoxContainer) -> void:
top.add_child(UiKit.label("战利品三选一",UiKit.FS_TITLE, true, UiKit.WARM))
var top_sp := Control.new()
top_sp.size_flags_horizontal = Control.SIZE_EXPAND_FILL
@@ -51,7 +58,32 @@ func _init() -> void:
_refresh_l = UiKit.label("",UiKit.FS_CAPTION, false, UiKit.WARM_SOFT)
_refresh_l.visible = false
top.add_child(_refresh_l)
var box: VBoxContainer = page.box
## 主体域:入账信息行+三竖卡列+底部确认/继续钮与免费选择注记
func _build_body(box: VBoxContainer) -> void:
_label = UiKit.label("",UiKit.FS_BODY, false, UiKit.WARM_SOFT)
_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
box.add_child(_label)
_choice_box = HBoxContainer.new()
_choice_box.add_theme_constant_override("separation", 26) # demo .cards gap 26(T7 对齐)
_choice_box.alignment = BoxContainer.ALIGNMENT_CENTER
_choice_box.size_flags_vertical = Control.SIZE_EXPAND_FILL
box.add_child(_choice_box)
var foot := HBoxContainer.new()
foot.alignment = BoxContainer.ALIGNMENT_CENTER
foot.add_theme_constant_override("separation", 16)
box.add_child(foot)
_confirm_btn = UiKit.primary_button("确认选择")
_confirm_btn.pressed.connect(_on_confirm_pressed)
_confirm_btn.visible = false
foot.add_child(_confirm_btn)
_continue_btn = UiKit.button("继续")
_continue_btn.pressed.connect(_emit_continue_pressed)
_continue_btn.visible = false
foot.add_child(_continue_btn)
_foot_hint = UiKit.label("免费选择 · 刷新不消耗 AP",UiKit.FS_HINT, false, UiKit.WARM_FAINT)
foot.add_child(_foot_hint)
_label = UiKit.label("",UiKit.FS_BODY, false, UiKit.WARM_SOFT)
_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
box.add_child(_label)
@@ -189,19 +221,15 @@ func _emit_continue_pressed() -> void:
# M7:id → 手绘图标键(子串映射容错 id 词表演进;缺位=BCard 空图标槽不炸)
## 图标映射表(id 子串→图标键——判定序 heal/blast/gp/attack/armor/ap 缺省钱袋逐字)
func _icon_for_id(idl: String) -> String:
if idl.contains("heal"):
return UiKit.TEX_ICON_MEDKIT
if idl.contains("blast") or idl.contains("magic"):
return UiKit.TEX_ICON_BLAST
if idl.contains("gp") or idl.contains("coin"):
return UiKit.TEX_ICON_COINBAG
if idl.contains("attack"):
return UiKit.TEX_ICON_SWORD
if idl.contains("armor"):
return UiKit.TEX_ICON_SHIELD
if idl.contains("ap") or idl.contains("action"):
return UiKit.TEX_ICON_POUCH
var rules := [[["heal"], UiKit.TEX_ICON_MEDKIT], [["blast", "magic"], UiKit.TEX_ICON_BLAST],
[["gp", "coin"], UiKit.TEX_ICON_COINBAG], [["attack"], UiKit.TEX_ICON_SWORD],
[["armor"], UiKit.TEX_ICON_SHIELD], [["ap", "action"], UiKit.TEX_ICON_POUCH]]
for rule in rules:
for kw in (rule[0] as Array):
if idl.contains(String(kw)):
return String(rule[1])
return UiKit.TEX_ICON_COINBAG
+19 -11
View File
@@ -32,17 +32,7 @@ func _init(rap_cost: int = -1) -> void:
box.add_child(title)
box.add_child(HSeparator.new())
for e in ENTRIES:
var row := HBoxContainer.new()
row.add_theme_constant_override("separation", 8)
var chip := ColorRect.new()
chip.custom_minimum_size = Vector2(18, 18)
chip.color = Color(e[0])
row.add_child(chip)
var lab := UiKit.label("%s —— %s" % [e[1], e[2]], 13, false, UiKit.INK_LABEL) # 亮底墨字(§7.2 数据素面)
lab.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
lab.custom_minimum_size = Vector2(440, 0)
row.add_child(lab)
box.add_child(row)
box.add_child(_legend_row(e))
box.add_child(HSeparator.new())
# RAP 行(表值驱动,禁字面量:刷新代价读行动经济表)
if _rap_cost >= 0:
@@ -64,6 +54,24 @@ func _init(rap_cost: int = -1) -> void:
visible = false
## 单行域(循环体提函数——色块 chip+词条 18px 网格对齐)
func _legend_row(e: Array) -> HBoxContainer:
var row := HBoxContainer.new()
row.add_theme_constant_override("separation", 8)
var chip := ColorRect.new()
chip.custom_minimum_size = Vector2(18, 18)
chip.color = Color(e[0])
row.add_child(chip)
var lab := UiKit.label("%s —— %s" % [e[1], e[2]], 13, false, UiKit.INK_LABEL) # 亮底墨字(§7.2 数据素面)
lab.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
lab.custom_minimum_size = Vector2(440, 0)
row.add_child(lab)
return row
func is_open() -> bool:
return visible
+23 -7
View File
@@ -37,7 +37,15 @@ func _build() -> void:
var shell: Dictionary[Variant, Variant] = UiKit.modal_shell(self, UiKit.BG_BRIGHT, Vector2(560, 0), 8,
UiKit.panel_skinned(Vector4(20, 16, 20, 16)))
var box: VBoxContainer = shell.box
_build_head(box)
_build_volume_row(box)
_build_fullscreen_row(box)
_build_motion_row(box)
_build_close_row(box)
## 头部域(设置项零增减——只有主音量;减动效与结算演出动画=同一设置,一行承载)
func _build_head(box: VBoxContainer) -> void:
var title := UiKit.label("设置",UiKit.FS_TITLE, true, UiKit.INK_TITLE)
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
box.add_child(title)
@@ -46,7 +54,9 @@ func _build() -> void:
box.add_child(hint)
box.add_child(HSeparator.new())
# 音量(master bus 实时生效——入口归设置、生效口径归 音频.md)
## 音量行(master bus 实时生效——入口归设置、生效口径归 音频.md)
func _build_volume_row(box: VBoxContainer) -> void:
var vol_row := HBoxContainer.new()
vol_row.add_theme_constant_override("separation", 14)
box.add_child(vol_row)
@@ -66,7 +76,9 @@ func _build() -> void:
_volume_val.custom_minimum_size = Vector2(60, 0)
vol_row.add_child(_volume_val)
# 全屏(拨杆形——SwitchToggle;状态字随行)
## 全屏行(拨杆形——SwitchToggle;状态字随行)
func _build_fullscreen_row(box: VBoxContainer) -> void:
var fs_row := HBoxContainer.new()
fs_row.add_theme_constant_override("separation", 14)
box.add_child(fs_row)
@@ -79,9 +91,11 @@ func _build() -> void:
_fs_st = UiKit.label("关",UiKit.FS_HINT, true, UiKit.INK_LABEL)
fs_row.add_child(_fs_st)
# 结算演出动画(=减动效实称;拨杆 on=完整,off=快速时长减半。
# reduced_motion 单设置同驱 UiKit.motion_scale 与结算演播——demo「减动效」行与
# 本行系同一设置,不另立行(拆两行=一设置双行重复)
## 结算演出动画行(=减动效实称;拨杆 on=完整,off=快速时长减半。
## reduced_motion 单设置同驱 UiKit.motion_scale 与结算演播——demo「减动效」行与
## 本行系同一设置,不另立行(拆两行=一设置双行重复))
func _build_motion_row(box: VBoxContainer) -> void:
var motion_row := HBoxContainer.new()
motion_row.add_theme_constant_override("separation", 14)
box.add_child(motion_row)
@@ -95,9 +109,11 @@ func _build() -> void:
motion_row.add_child(_motion_st)
var why := UiKit.label("快速 = 时长减半(运动敏感 / 演示用)",UiKit.FS_CAPTION, false, UiKit.INK_HINT)
motion_row.add_child(why) # 内联随行(demo .why 同排)
box.add_child(HSeparator.new())
# 退出游戏归开始菜单——设置面不再承载(退出只在一级菜单出现)
## 返回钮域(退出游戏归开始菜单——设置面不再承载〔退出只在一级菜单出现〕)
func _build_close_row(box: VBoxContainer) -> void:
var close_row := HBoxContainer.new()
close_row.alignment = BoxContainer.ALIGNMENT_CENTER
box.add_child(close_row)