From b33469ac09da893c19b325cd7ca378c62f5f551a Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 28 Oct 2026 14:53:17 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=A1=E2=91=A1T2a(=E5=91=88=E7=8E=B0?= =?UTF-8?q?=E5=B1=82=E7=BB=93=E6=9E=84=E6=94=B6=E6=95=9B=E6=89=B9):=20u5/u?= =?UTF-8?q?6=E2=80=94=E2=80=94u5=20hero=5Finfo=5Fpanel=5Fview(=5Finit?= =?UTF-8?q?=E6=8B=86=E6=95=B0=E6=8D=AE=E8=A1=8C/=E7=A7=98=E5=AE=9D?= =?UTF-8?q?=E6=A7=BD/=E4=B8=89=E5=8C=BA=E6=AE=B5=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E7=A7=81=E6=9C=89=E6=97=8F+=E5=8D=95=E6=A7=BD/=E6=A7=BD?= =?UTF-8?q?=E5=9B=BE=E6=A0=87/=E5=AD=97=E5=BD=A2=E6=A0=87=E7=AD=BE/?= =?UTF-8?q?=E6=B5=AE=E7=AA=97=E5=9B=9B=E5=9F=9F+set=5Ftreasure=5Finventory?= =?UTF-8?q?=E5=A1=AB=E5=85=85=E5=9F=9F=E6=8B=86=E5=88=86,=E5=BE=AA?= =?UTF-8?q?=E7=8E=AF=E4=BD=93=E6=8F=90=E5=87=BD=E6=95=B0)+u6=20battle=5Fen?= =?UTF-8?q?d=5Foverlay(=5FEndOverlayState=E6=88=98=E6=9C=AB=E5=91=88?= =?UTF-8?q?=E7=8E=B0=E4=B8=8E=E6=BC=94=E5=87=BA=E6=80=81=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E4=BB=B6[fx=E4=B8=89=E8=AE=A1=E6=95=B0=E5=B9=B6=E4=B8=80?= =?UTF-8?q?=E5=AD=97=E5=85=B8]+=E6=8E=A2=E9=92=88=E5=86=BB=E7=BB=934?= =?UTF-8?q?=E4=BD=8F=E5=AE=BF=E4=B8=BB+=5Finit=E6=8B=86=E5=A4=B4=E9=83=A8[?= =?UTF-8?q?=E8=9C=A1=E5=B0=81=E5=9B=BE/=E5=BA=86=E7=A5=9D=E7=AB=8B?= =?UTF-8?q?=E7=BB=98]/=E4=B8=89=E8=A1=8C=E8=B4=A6=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E5=9F=9F+open=5Fvictory=E5=8F=AF=E9=80=89=E5=B0=BE=E5=8F=82?= =?UTF-8?q?=E6=94=B6=E6=8B=A2opts=E6=9D=9F4=E5=8F=82[=E9=94=AEgp=5Fsummary?= =?UTF-8?q?/ks=5Fsummary/bonus=5Fsummary/gp=5Fgain/gp=5Ftotal=E7=BC=BA?= =?UTF-8?q?=E7=9C=81=E9=80=90=E5=AD=97]+=E5=9B=9B=E5=A4=84=E6=BC=94?= =?UTF-8?q?=E5=87=BA=E5=BE=AA=E7=8E=AF=E6=8F=90=E5=87=BD=E6=95=B0[?= =?UTF-8?q?=E5=B0=81=E5=8D=B0=E5=85=89=E7=82=B9/=E7=BA=B9=E7=AB=A0?= =?UTF-8?q?=E7=A2=8E=E7=A0=96/=E5=9D=90=E7=A0=96=E5=B0=98=E7=B5=AE/?= =?UTF-8?q?=E5=B0=98=E9=87=91=E7=B2=92=E5=AD=90])+=E6=B6=88=E8=B4=B9?= =?UTF-8?q?=E7=82=B95=E5=A4=84=E6=94=B9=E5=9D=80(main=C3=972=E6=9D=9F?= =?UTF-8?q?=E5=BD=A2/main=5Fadventure=5Fflow=C3=972/ui=5Fstruct=5Fprobe?= =?UTF-8?q?=C3=971,=E4=B8=89=E5=8F=82=E8=B0=83=E7=94=A8=E7=BC=BA=E7=9C=81?= =?UTF-8?q?=E5=85=BC=E5=AE=B9=E9=9B=B6=E6=94=B9=E5=8A=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/presentation/main.gd | 16 +- game/presentation/main_adventure_flow.gd | 4 +- game/presentation/views/battle_end_overlay.gd | 354 ++++++++++-------- .../views/hero_info_panel_view.gd | 207 +++++----- game/probe/ui_struct_probe.gd | 2 +- 5 files changed, 336 insertions(+), 247 deletions(-) diff --git a/game/presentation/main.gd b/game/presentation/main.gd index fb8b14f5..3b7f3468 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -453,15 +453,19 @@ func _on_battle_finished(payload: Dictionary) -> void: return "level_end": _end_overlay.open_victory(Narrator.battle_end_text("victory", true), - "返回", board_view.get_global_rect(), _gp_summary_text(payload), - "钥石 · 累计 %d" % run_scope.runner.keystone, HudPresenter.bonus_summary_text(run_scope.runner), - int(payload.get("gp_dropped", 0)), int(run_scope.runner.gp)) + "返回", board_view.get_global_rect(), + {"gp_summary": _gp_summary_text(payload), + "ks_summary": "钥石 · 累计 %d" % run_scope.runner.keystone, + "bonus_summary": HudPresenter.bonus_summary_text(run_scope.runner), + "gp_gain": int(payload.get("gp_dropped", 0)), "gp_total": int(run_scope.runner.gp)}) "standalone_end": if result == "victory": _end_overlay.open_victory(Narrator.battle_end_text("victory", false), - "回到遭遇选择", board_view.get_global_rect(), _gp_summary_text(payload), - "钥石 · 累计 %d" % run_scope.runner.keystone, HudPresenter.bonus_summary_text(run_scope.runner), - int(payload.get("gp_dropped", 0)), int(run_scope.runner.gp)) + "回到遭遇选择", board_view.get_global_rect(), + {"gp_summary": _gp_summary_text(payload), + "ks_summary": "钥石 · 累计 %d" % run_scope.runner.keystone, + "bonus_summary": HudPresenter.bonus_summary_text(run_scope.runner), + "gp_gain": int(payload.get("gp_dropped", 0)), "gp_total": int(run_scope.runner.gp)}) else: _end_overlay.open_defeat(Narrator.battle_end_text("defeat", false), "返回", board_view.get_global_rect()) diff --git a/game/presentation/main_adventure_flow.gd b/game/presentation/main_adventure_flow.gd index f05b8c18..66cbe3c2 100644 --- a/game/presentation/main_adventure_flow.gd +++ b/game/presentation/main_adventure_flow.gd @@ -111,7 +111,7 @@ static func _on_adventure_finished(root: Variant, summary: Dictionary) -> void: text += "\n" + root.Narrator.keystone_settle_text(detail, root._bonus.item_name_lookup()) if result == "completed": root._end_overlay.open_victory(text, "返回冒险", root.board_view.get_global_rect(), - "本局 GP 入账 · 合计 %d" % int(summary.get("gp_final", 0))) # GP 行(取数=summary gp_final 清账前快照);钥石明细已在 text(keystone_settle_text)——三行账 + {"gp_summary": "本局 GP 入账 · 合计 %d" % int(summary.get("gp_final", 0))}) # GP 行(取数=summary gp_final 清账前快照);钥石明细已在 text(keystone_settle_text)——三行账 else: root._end_overlay.open_defeat(text, "返回", root.board_view.get_global_rect()) root.logger.log_level_end(summary) # 试玩时间线:冒险终局(摘要随载荷) @@ -178,7 +178,7 @@ static func _on_choices_presented(root: Variant, candidates: Array) -> void: root.run_scope.pending_candidates = candidates.duplicate() root._end_overlay.open_victory(root.Narrator.battle_end_text("victory", false), "下一节点", root.board_view.get_global_rect(), - "战利品 GP +%d · 合计 %d" % [root.run_scope.last_gain_amount, int(root.run_scope.runner.gp)]) + {"gp_summary": "战利品 GP +%d · 合计 %d" % [root.run_scope.last_gain_amount, int(root.run_scope.runner.gp)]}) root._open_modal_after_playback(root._end_overlay) root._refresh_presentations() return diff --git a/game/presentation/views/battle_end_overlay.gd b/game/presentation/views/battle_end_overlay.gd index bfb40e2e..a5a8cec9 100644 --- a/game/presentation/views/battle_end_overlay.gd +++ b/game/presentation/views/battle_end_overlay.gd @@ -10,22 +10,30 @@ const COL_GOLD := UiKit.GOLD const COL_DUSK := UiKit.DUSK # 暮蓝(全作唯一暮蓝档) const COL_WARM := UiKit.WARM -var _dim: ColorRect -var _seal: Panel -var _verdict: Label -var _rows_box: VBoxContainer # 三行账(GP/钥石/本关加成——demo ⑧两列对齐;失败侧整行隐藏) -var _gp_row: HBoxContainer -var _gp_name: Label -var _gp_val: Label -var _ks_row: HBoxContainer -var _ks_name: Label -var _ks_val: Label -var _bonus_row_l: HBoxContainer -var _bonus_name: Label -var _bonus_val: Label -var _cele: TextureRect # 庆祝立绘(idle 占位浮动) -var _btn: Button -var _board_rect := Rect2() +## 内部状态件(u6 子域拆件——契约 9 职责语句「战末呈现与演出态」):单一变化原因=终局画面呈现与 +## 演出计数。探针直读冻结成员(_btn/_gp_name/_gp_row/_gp_val)住宿主不进状态件。 +class _EndOverlayState extends RefCounted: + var _dim: ColorRect + var _seal: Panel + var _verdict: Label + var _rows_box: VBoxContainer # 三行账(GP/钥石/本关加成——demo ⑧两列对齐;失败侧整行隐藏) + var _ks_row: HBoxContainer + var _ks_name: Label + var _ks_val: Label + var _bonus_row_l: HBoxContainer + var _bonus_name: Label + var _bonus_val: Label + var _cele: TextureRect # 庆祝立绘(idle 占位浮动) + var _board_rect := Rect2() + var _crest_bricks: Array[ColorRect] = [] # 纹章碎砖驻留面(close 时回收) + var _fx_counts := {"crest": 0, "dust": 0, "seal": 0} # 演出计数(fx_probe 断言面——crest/dust/seal 三键) + + +var st := _EndOverlayState.new() +var _gp_row: HBoxContainer # 探针直读冻结(S17⑤) +var _gp_name: Label # 探针直读冻结 +var _gp_val: Label # 探针直读冻结 +var _btn: Button # 探针直读冻结 func _init() -> void: @@ -35,19 +43,43 @@ func _init() -> void: # 骨架经 UiKit.modal_shell;_dim 引用摘出供 open_* 换氛围色。 var shell := UiKit.modal_shell(self, Color(COL_WARM, 0.0), Vector2(520, 0), 12, true, UiKit.panel_skinned(Vector4(16, 14, 16, 14))) - _dim = shell.dim + st._dim = shell.dim var box: VBoxContainer = shell.box + _build_header(box) + _build_ledger(box) + # 无明细回看钮——冒险中回看归「生涯」 + _btn = UiKit.primary_button("继续") + _btn.size_flags_horizontal = Control.SIZE_SHRINK_CENTER # 紧凑钮 + _btn.custom_minimum_size.x = 150 + _btn.pressed.connect(_emit_continue_pressed) + box.add_child(_btn) + visible = false + + +## 头部域:金蜡封徽记+判定行+庆祝立绘(demo ⑧——idle 帧浮动占位;减动效=静帧) +func _build_header(box: VBoxContainer) -> void: var header := HBoxContainer.new() header.add_theme_constant_override("separation", 12) header.alignment = BoxContainer.ALIGNMENT_CENTER box.add_child(header) - _seal = Panel.new() - _seal.mouse_filter = Control.MOUSE_FILTER_IGNORE + st._seal = Panel.new() + st._seal.mouse_filter = Control.MOUSE_FILTER_IGNORE var sb := UiKit.panel_flat(COL_GOLD, UiKit.GOLD_DEEP, 200, 4) - _seal.add_theme_stylebox_override("panel", sb) - _seal.custom_minimum_size = Vector2(52, 52) - _seal.size_flags_vertical = Control.SIZE_SHRINK_CENTER - header.add_child(_seal) + st._seal.add_theme_stylebox_override("panel", sb) + st._seal.custom_minimum_size = Vector2(52, 52) + st._seal.size_flags_vertical = Control.SIZE_SHRINK_CENTER + header.add_child(st._seal) + st._seal.add_child(_seal_img()) + st._verdict = UiKit.label("",UiKit.FS_TITLE, true, UiKit.INK_TITLE) + st._verdict.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + st._verdict.size_flags_vertical = Control.SIZE_SHRINK_CENTER + header.add_child(st._verdict) + st._cele = _cele_rect() + header.add_child(st._cele) + + +## 蜡封图域(32px 像素过滤居中) +func _seal_img() -> TextureRect: var seal_img := TextureRect.new() var seal_tex := UiKit.load_tex("res://assets/art/board/seal_mark.png") if seal_tex != null: @@ -58,76 +90,76 @@ func _init() -> void: seal_img.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST seal_img.size_flags_vertical = Control.SIZE_SHRINK_CENTER seal_img.mouse_filter = Control.MOUSE_FILTER_IGNORE - _seal.add_child(seal_img) - _verdict = UiKit.label("",UiKit.FS_TITLE, true, UiKit.INK_TITLE) - _verdict.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - _verdict.size_flags_vertical = Control.SIZE_SHRINK_CENTER - header.add_child(_verdict) - # 庆祝立绘(demo ⑧——idle 帧浮动占位;减动效=静帧) - _cele = TextureRect.new() + return seal_img + + +## 庆祝立绘域(64px idle 帧) +func _cele_rect() -> TextureRect: + var cele := TextureRect.new() var cele_tex := UiKit.load_tex(UiKit.TEX_HERO_IDLE) if cele_tex != null: - _cele.texture = cele_tex - _cele.custom_minimum_size = Vector2(64, 64) - _cele.expand_mode = TextureRect.EXPAND_IGNORE_SIZE - _cele.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED - _cele.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST - _cele.size_flags_vertical = Control.SIZE_SHRINK_CENTER - _cele.visible = false - header.add_child(_cele) - _rows_box = VBoxContainer.new() - _rows_box.add_theme_constant_override("separation", 6) - _rows_box.visible = false - box.add_child(_rows_box) - # 两列对齐账行(审计差距④对齐 demo ⑧——项目名左/数值右,定宽 360) + cele.texture = cele_tex + cele.custom_minimum_size = Vector2(64, 64) + cele.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + cele.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + cele.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + cele.size_flags_vertical = Control.SIZE_SHRINK_CENTER + cele.visible = false + return cele + + +## 三行账域(两列对齐账行——审计差距④对齐 demo ⑧:项目名左/数值右,定宽 360) +func _build_ledger(box: VBoxContainer) -> void: + st._rows_box = VBoxContainer.new() + st._rows_box.add_theme_constant_override("separation", 6) + st._rows_box.visible = false + box.add_child(st._rows_box) _gp_row = _ledger_row() _gp_name = _ledger_name(UiKit.FS_SUB, UiKit.GOLD_INK, true) # GOLD_DEEP 对结算板 2.67:1 越带,换深金墨 5.21:1 _gp_val = _ledger_value(UiKit.FS_SUB, UiKit.GOLD_INK, true) # 同上 _gp_row.add_child(_gp_name) _gp_row.add_child(_gp_val) - _rows_box.add_child(_gp_row) - _ks_row = _ledger_row() - _ks_name = _ledger_name(UiKit.FS_BODY, UiKit.INK_LABEL, false) - _ks_val = _ledger_value(UiKit.FS_BODY, UiKit.INK_LABEL, true) - _ks_row.add_child(_ks_name) - _ks_row.add_child(_ks_val) - _ks_row.visible = false - _rows_box.add_child(_ks_row) - _bonus_row_l = _ledger_row() - _bonus_name = _ledger_name(UiKit.FS_HINT, UiKit.INK_LABEL, false) - _bonus_val = _ledger_value(UiKit.FS_HINT, UiKit.INK_LABEL, true) - _bonus_row_l.add_child(_bonus_name) - _bonus_row_l.add_child(_bonus_val) - _bonus_row_l.visible = false - _rows_box.add_child(_bonus_row_l) + st._rows_box.add_child(_gp_row) + st._ks_row = _ledger_row() + st._ks_name = _ledger_name(UiKit.FS_BODY, UiKit.INK_LABEL, false) + st._ks_val = _ledger_value(UiKit.FS_BODY, UiKit.INK_LABEL, true) + st._ks_row.add_child(st._ks_name) + st._ks_row.add_child(st._ks_val) + st._ks_row.visible = false + st._rows_box.add_child(st._ks_row) + st._bonus_row_l = _ledger_row() + st._bonus_name = _ledger_name(UiKit.FS_HINT, UiKit.INK_LABEL, false) + st._bonus_val = _ledger_value(UiKit.FS_HINT, UiKit.INK_LABEL, true) + st._bonus_row_l.add_child(st._bonus_name) + st._bonus_row_l.add_child(st._bonus_val) + st._bonus_row_l.visible = false + st._rows_box.add_child(st._bonus_row_l) box.add_child(HSeparator.new()) - # 无明细回看钮——冒险中回看归「生涯」 - _btn = UiKit.primary_button("继续") - _btn.size_flags_horizontal = Control.SIZE_SHRINK_CENTER # 紧凑钮 - _btn.custom_minimum_size.x = 150 - _btn.pressed.connect(_emit_continue_pressed) - box.add_child(_btn) - visible = false # 终局画面数据锚 = 战斗结算 判定行(verdict)——判定行文案由组合根装配传入。 # gp_summary(胜利总结结算展示 GP 总量+增量):组合根装配「战利品 +增量 · 合计总量」 # 传入(入账次序 = LevelRunner 入账先于组合根 handler,runner.gp 即战后总量);空串 = 不显示(失败侧恒空)。 -func open_victory(verdict_line: String, continue_label: String, board_rect: Rect2, gp_summary: String = "", - ks_summary: String = "", bonus_summary: String = "", gp_gain := -1, gp_total := -1) -> void: - _dim.color = Color(UiKit.DIM_NIGHT, 0.62) # 全屏压暗对齐 demo - _verdict.text = verdict_line - _seal.visible = true # 胜利徽记(面板头部静态) - _seal.modulate = Color.WHITE +# 可选尾参收拢 opts 束(u6——键 {gp_summary, ks_summary, bonus_summary, gp_gain, gp_total},缺省逐字同值) +func open_victory(verdict_line: String, continue_label: String, board_rect: Rect2, opts := {}) -> void: + var gp_summary: String = opts.get("gp_summary", "") + var ks_summary: String = opts.get("ks_summary", "") + var bonus_summary: String = opts.get("bonus_summary", "") + var gp_gain: int = opts.get("gp_gain", -1) + var gp_total: int = opts.get("gp_total", -1) + st._dim.color = Color(UiKit.DIM_NIGHT, 0.62) # 全屏压暗对齐 demo + st._verdict.text = verdict_line + st._seal.visible = true # 胜利徽记(面板头部静态) + st._seal.modulate = Color.WHITE _set_ledger_row(_gp_name, _gp_val, gp_summary, true) # GP 行=固定名「战利品 GP」(滚动/整串均只入值列) - _set_ledger_row(_ks_name, _ks_val, ks_summary) - _ks_row.visible = ks_summary != "" - _set_ledger_row(_bonus_name, _bonus_val, bonus_summary) - _bonus_row_l.visible = bonus_summary != "" - _rows_box.visible = gp_summary != "" or ks_summary != "" or bonus_summary != "" + _set_ledger_row(st._ks_name, st._ks_val, ks_summary) + st._ks_row.visible = ks_summary != "" + _set_ledger_row(st._bonus_name, st._bonus_val, bonus_summary) + st._bonus_row_l.visible = bonus_summary != "" + st._rows_box.visible = gp_summary != "" or ks_summary != "" or bonus_summary != "" _btn.text = continue_label - _board_rect = board_rect - _cele.visible = true + st._board_rect = board_rect + st._cele.visible = true _play_celebrate() visible = true if UiKit.victory_fx_style == "dust": @@ -141,23 +173,23 @@ func open_victory(verdict_line: String, continue_label: String, board_rect: Rect func open_defeat(verdict_line: String, continue_label: String, board_rect: Rect2) -> void: # 明度不变的暮蓝色相氛围(全屏唯一暮蓝档——§2.5 最静一拍) - _dim.color = Color(COL_DUSK, 0.6) # 同族压暗 - _verdict.text = verdict_line - _board_rect = board_rect + st._dim.color = Color(COL_DUSK, 0.6) # 同族压暗 + st._verdict.text = verdict_line + st._board_rect = board_rect visible = true - _seal.visible = true - _seal.modulate = Color(0.4, 0.4, 0.4) # 失败徽记灰化(demo ⑧) - _cele.visible = false - _rows_box.visible = false # 失败侧不呈现账行 + st._seal.visible = true + st._seal.modulate = Color(0.4, 0.4, 0.4) # 失败徽记灰化(demo ⑧) + st._cele.visible = false + st._rows_box.visible = false # 失败侧不呈现账行 _play_dust_sadle() # 坐砖掸灰(§2.5——终稿) _play_seal_recall(8) func close() -> void: visible = false - _seal.visible = false - _seal.modulate = Color.WHITE - _cele.visible = false + st._seal.visible = false + st._seal.modulate = Color.WHITE + st._cele.visible = false var tw: Tween = get_meta(&"celebrate_tw", null) # 循环句柄随节点存(跨 open/close,close 必杀) if tw != null and tw.is_valid(): tw.kill() @@ -170,7 +202,7 @@ func is_open() -> bool: func verdict_text() -> String: - return _verdict.text + return st._verdict.text # 两列账行工厂(审计差距④——名左/值右,定宽 360) @@ -217,68 +249,83 @@ func _fx_center() -> Vector2: # 封印化光点收回(失败——插过的封印逐面化光点收回占位,§2.5) func _play_seal_recall(count: int) -> void: - _fx_seal_dots = count + st._fx_counts["seal"] = count + var base := st._board_rect.get_center() if st._board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5 for i in count: - var dot := ColorRect.new() - dot.color = COL_GOLD - dot.size = Vector2(6, 6) - var base := _board_rect.get_center() if _board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5 - dot.position = base + Vector2((float(i % 5) - 2.0) * _board_rect.size.x * 0.14 if _board_rect.size.x > 0 else 0.0, - (float(i % 3) - 1.0) * _board_rect.size.y * 0.18) # 确定性散布(S2 禁全局随机——占位演出同守) - dot.mouse_filter = Control.MOUSE_FILTER_IGNORE - add_child(dot) - var tw := dot.create_tween() - tw.tween_interval(UiKit.motion(0.1 * i)) - tw.tween_property(dot, "position:y", dot.position.y - 120.0, UiKit.motion(0.8)) - tw.parallel().tween_property(dot, "modulate:a", 0.0, UiKit.motion(0.8)) - tw.tween_callback(dot.queue_free) + _spawn_seal_dot(i, base) + + +## 单光点域(循环体提函数——延迟升空渐隐回收;确定性散布 S2 禁全局随机) +func _spawn_seal_dot(i: int, base: Vector2) -> void: + var dot := ColorRect.new() + dot.color = COL_GOLD + dot.size = Vector2(6, 6) + dot.position = base + Vector2((float(i % 5) - 2.0) * st._board_rect.size.x * 0.14 if st._board_rect.size.x > 0 else 0.0, + (float(i % 3) - 1.0) * st._board_rect.size.y * 0.18) + dot.mouse_filter = Control.MOUSE_FILTER_IGNORE + add_child(dot) + var tw := dot.create_tween() + tw.tween_interval(UiKit.motion(0.1 * i)) + tw.tween_property(dot, "position:y", dot.position.y - 120.0, UiKit.motion(0.8)) + tw.parallel().tween_property(dot, "modulate:a", 0.0, UiKit.motion(0.8)) + tw.tween_callback(dot.queue_free) # ---- 演出密度(程序化 tween,确定性散布,零新帧资产) ---- -var _crest_bricks: Array[ColorRect] = [] # 纹章碎砖驻留面(close 时回收) -var _fx_crest_shards := 0 -var _fx_dust_motes := 0 -var _fx_seal_dots := 0 +var st._crest_bricks: Array[ColorRect] = [] # 纹章碎砖驻留面(close 时回收) +var st._fx_crest_shards := 0 +var st._fx_dust_motes := 0 +var st._fx_seal_dots := 0 # 碎砖拼纹章(胜利 §2.4——蜡封落定后十枚碎砖自四周向蜡封外环收拢拼成纹章环,驻留至 close) func _play_crest_assemble() -> void: _clear_crest_bricks() - _fx_crest_shards = 10 + st._fx_counts["crest"] = 10 var c := _fx_center() for i in 10: - var brick := ColorRect.new() - brick.color = UiKit.BRICK if i % 2 == 0 else UiKit.FLOOR # 碎砖=砖面族色(掩尘/裸土——图底同源) - brick.size = Vector2(12, 7) - brick.mouse_filter = Control.MOUSE_FILTER_IGNORE - var ang := TAU * float(i) / 10.0 - var ring := c + Vector2(cos(ang), sin(ang)) * 64.0 - brick.position = c + Vector2(cos(ang), sin(ang)) * 200.0 # 自四周场外入场 - add_child(brick) - _crest_bricks.append(brick) - var btw := brick.create_tween() - btw.tween_interval(UiKit.motion(0.52)) # 待蜡封落定(seal 0.42+0.22 弹跳中段) - btw.tween_property(brick, "position", ring - brick.size * 0.5, UiKit.motion(0.4)).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) - btw.parallel().tween_property(brick, "rotation", sin(ang) * 0.35, UiKit.motion(0.4)) + _spawn_crest_brick(i, c) + + +## 单碎砖域(循环体提函数——待蜡封落定后自场外收拢入环;碎砖=砖面族色〔掩尘/裸土——图底同源〕) +func _spawn_crest_brick(i: int, c: Vector2) -> void: + var brick := ColorRect.new() + brick.color = UiKit.BRICK if i % 2 == 0 else UiKit.FLOOR + brick.size = Vector2(12, 7) + brick.mouse_filter = Control.MOUSE_FILTER_IGNORE + var ang := TAU * float(i) / 10.0 + var ring := c + Vector2(cos(ang), sin(ang)) * 64.0 + brick.position = c + Vector2(cos(ang), sin(ang)) * 200.0 # 自四周场外入场 + add_child(brick) + st._crest_bricks.append(brick) + var btw := brick.create_tween() + btw.tween_interval(UiKit.motion(0.52)) # 待蜡封落定(seal 0.42+0.22 弹跳中段) + btw.tween_property(brick, "position", ring - brick.size * 0.5, UiKit.motion(0.4)).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) + btw.parallel().tween_property(brick, "rotation", sin(ang) * 0.35, UiKit.motion(0.4)) # 坐砖掸灰(失败 §2.5——暖灰尘絮自棋盘中带缓缓飘落,最静一拍的运动承载) func _play_dust_sadle() -> void: - _fx_dust_motes = 8 + st._fx_counts["dust"] = 8 var base := _fx_center() for i in 8: - var mote := ColorRect.new() - mote.color = Color(UiKit.WARM_FAINT, 0.85) - mote.size = Vector2(4, 4) - mote.mouse_filter = Control.MOUSE_FILTER_IGNORE - mote.position = base + Vector2((float(i % 4) - 1.5) * 30.0, -24.0 - 8.0 * float(i / 4)) - add_child(mote) - var mtw := mote.create_tween() - mtw.tween_interval(UiKit.motion(0.08 * i)) - mtw.tween_property(mote, "position:y", mote.position.y + 64.0, UiKit.motion(0.9)) - mtw.parallel().tween_property(mote, "modulate:a", 0.0, UiKit.motion(0.9)) - mtw.tween_callback(mote.queue_free) + _spawn_dust_mote(i, base) + + +## 单尘絮域(循环体提函数——中带散布缓降渐隐) +func _spawn_dust_mote(i: int, base: Vector2) -> void: + var mote := ColorRect.new() + mote.color = Color(UiKit.WARM_FAINT, 0.85) + mote.size = Vector2(4, 4) + mote.mouse_filter = Control.MOUSE_FILTER_IGNORE + mote.position = base + Vector2((float(i % 4) - 1.5) * 30.0, -24.0 - 8.0 * float(i / 4)) + add_child(mote) + var mtw := mote.create_tween() + mtw.tween_interval(UiKit.motion(0.08 * i)) + mtw.tween_property(mote, "position:y", mote.position.y + 64.0, UiKit.motion(0.9)) + mtw.parallel().tween_property(mote, "modulate:a", 0.0, UiKit.motion(0.9)) + mtw.tween_callback(mote.queue_free) # 庆祝立绘浮动(idle 占位——UiKit.motion 守减动效;motion_scale<1 静帧) @@ -288,29 +335,34 @@ func _play_celebrate() -> void: var prev: Tween = get_meta(&"celebrate_tw", null) if prev != null and prev.is_valid(): prev.kill() - var tw := _cele.create_tween() + var tw := st._cele.create_tween() set_meta(&"celebrate_tw", tw) tw.set_loops() - tw.tween_property(_cele, "position:y", _cele.position.y - 4.0, UiKit.motion(0.6)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) - tw.tween_property(_cele, "position:y", _cele.position.y, UiKit.motion(0.6)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) + tw.tween_property(st._cele, "position:y", st._cele.position.y - 4.0, UiKit.motion(0.6)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) + tw.tween_property(st._cele, "position:y", st._cele.position.y, UiKit.motion(0.6)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) # 尘金粒子(备选演出——金屑自面板上带飘落闪灭;确定性散布守 S2) func _play_victory_dust() -> void: - _fx_dust_motes += 10 + st._fx_counts["dust"] += 10 var c := _fx_center() for i in 10: - var mote := ColorRect.new() - mote.color = UiKit.CANDLE - mote.size = Vector2(5, 5) - mote.mouse_filter = Control.MOUSE_FILTER_IGNORE - mote.position = c + Vector2((float(i % 5) - 2.0) * 44.0, -40.0 - 10.0 * float(i / 5)) - add_child(mote) - var tw := mote.create_tween() - tw.tween_interval(UiKit.motion(0.12 * i)) - tw.tween_property(mote, "position:y", mote.position.y + 90.0, UiKit.motion(1.1)) - tw.parallel().tween_property(mote, "modulate:a", 0.0, UiKit.motion(1.1)) - tw.tween_callback(mote.queue_free) + _spawn_victory_mote(i, c) + + +## 单金屑域(循环体提函数——上带散布飘落闪灭) +func _spawn_victory_mote(i: int, c: Vector2) -> void: + var mote := ColorRect.new() + mote.color = UiKit.CANDLE + mote.size = Vector2(5, 5) + mote.mouse_filter = Control.MOUSE_FILTER_IGNORE + mote.position = c + Vector2((float(i % 5) - 2.0) * 44.0, -40.0 - 10.0 * float(i / 5)) + add_child(mote) + var tw := mote.create_tween() + tw.tween_interval(UiKit.motion(0.12 * i)) + tw.tween_property(mote, "position:y", mote.position.y + 90.0, UiKit.motion(1.1)) + tw.parallel().tween_property(mote, "modulate:a", 0.0, UiKit.motion(1.1)) + tw.tween_callback(mote.queue_free) # GP 行数字滚动(demo ⑧——0.8s 三次缓出;减动效直出终值) @@ -325,15 +377,15 @@ func _roll_gp(gp_gain: int, gp_total: int) -> void: func _clear_crest_bricks() -> void: - for b in _crest_bricks: + for b in st._crest_bricks: if is_instance_valid(b): b.queue_free() - _crest_bricks.clear() + st._crest_bricks.clear() # 演出探针出口(fx_probe——胜利纹章/失败掸灰/封印光收回三断言面) func fx_probe() -> Dictionary: - return {"crest_shards": _fx_crest_shards, "dust_motes": _fx_dust_motes, "seal_dots": _fx_seal_dots} + return {"crest_shards": st._fx_counts["crest"], "dust_motes": st._fx_counts["dust"], "seal_dots": st._fx_counts["seal"]} func _emit_continue_pressed() -> void: diff --git a/game/presentation/views/hero_info_panel_view.gd b/game/presentation/views/hero_info_panel_view.gd index 7b1c5ca5..7dd765d7 100644 --- a/game/presentation/views/hero_info_panel_view.gd +++ b/game/presentation/views/hero_info_panel_view.gd @@ -30,9 +30,14 @@ func _init() -> void: var col := VBoxContainer.new() col.add_theme_constant_override("separation", 14) # 布局充实(区段间距放大) add_child(col) + _build_data_row(col) + _build_slots(col) + _build_side_sections(col) + +## 数据行域(直角细线数据框——battle.md Component Inventory C 区行规格落形;同时消化纵向空白) +func _build_data_row(col: VBoxContainer) -> void: col.add_child(UiKit.label("探险者",UiKit.FS_SUB, true)) - # 数据行装框(直角细线数据框——battle.md Component Inventory C 区行规格落形;同时消化纵向空白) var data_box := PanelContainer.new() data_box.add_theme_stylebox_override("panel", UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER, 0, 1, Vector4(6, 4, 6, 4))) @@ -45,55 +50,60 @@ func _init() -> void: _armor_label = _data_line() data_col.add_child(_armor_label) - # 秘宝槽(预留):随身战斗用道具的唯一展示面——空槽占位承诺格子在此, - # 不预设交互(挖掘期指向交互归 E 区既有裁决) + +## 秘宝槽域(预留):随身战斗用道具的唯一展示面——空槽占位承诺格子在此, +## 不预设交互(挖掘期指向交互归 E 区既有裁决) +func _build_slots(col: VBoxContainer) -> void: col.add_child(UiKit.label("秘宝",UiKit.FS_BODY, true)) item_slots = HBoxContainer.new() item_slots.add_theme_constant_override("separation", 10) col.add_child(item_slots) for i in 4: - var slot := PanelContainer.new() - slot.custom_minimum_size = Vector2(48, 48) - slot.add_theme_stylebox_override("panel", - UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER, 0, 1, Vector4(4, 4, 4, 4))) - var idx := i - slot.gui_input.connect(func(ev: InputEvent): - if ev is InputEventMouseButton and ev.pressed and ev.button_index == MOUSE_BUTTON_LEFT: - # 空槽/越界槽点击=取消选中(handle 0);有效槽发其 handle(组合根判 AP 即用/进选中态) - treasure_slot_clicked.emit(_slot_handles[idx] if idx < _slot_handles.size() else 0)) - item_slots.add_child(slot) - _slot_panels.append(slot) + _slot_panels.append(_build_slot(i)) - # 特性持有段(三选一特性兑付后常驻可读:逐件行「件名 ×N」;空态整节隐藏。 - # 上移承载——构筑核心信息优先级高于临时强化,且防 C 区下部裁切不可见) - _feat_section = VBoxContainer.new() - _feat_section.add_theme_constant_override("separation", 4) - _feat_section.visible = false - col.add_child(_feat_section) - _feat_section.add_child(UiKit.label("专长",UiKit.FS_BODY, true)) - _feat_list = VBoxContainer.new() - _feat_list.add_theme_constant_override("separation", 2) - _feat_section.add_child(_feat_list) - # 地形变异常驻角标(Boss 图整图型战斗全程可见防中局遗忘; - # 空态整节隐藏=特性段先例;摆位/文案 provisional) - _board_variant_section = VBoxContainer.new() - _board_variant_section.add_theme_constant_override("separation", 4) - _board_variant_section.visible = false - col.add_child(_board_variant_section) - _board_variant_section.add_child(UiKit.label("地形变异",UiKit.FS_BODY, true)) - _board_variant_list = VBoxContainer.new() - _board_variant_list.add_theme_constant_override("separation", 2) - _board_variant_section.add_child(_board_variant_list) - # 特性(预留):锻造/大奖励/关卡奖励节点的属性提升——中循环数据因改变战斗数学准入; - # 与战斗内状态效果(B 区站桩,随战斗消散)分区,防「战斗结束被清」误读;空态整节隐藏 - _curio_section = VBoxContainer.new() - _curio_section.add_theme_constant_override("separation", 4) - _curio_section.visible = false - col.add_child(_curio_section) - _curio_section.add_child(UiKit.label("属性值提升",UiKit.FS_BODY, true)) - _curio_list = VBoxContainer.new() - _curio_list.add_theme_constant_override("separation", 2) - _curio_section.add_child(_curio_list) + +## 单槽构建域:48px 槽牌+点击路由(空槽/越界槽点击=取消选中 handle 0;有效槽发其 handle) +func _build_slot(i: int) -> PanelContainer: + var slot := PanelContainer.new() + slot.custom_minimum_size = Vector2(48, 48) + slot.add_theme_stylebox_override("panel", + UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER, 0, 1, Vector4(4, 4, 4, 4))) + var idx := i + slot.gui_input.connect(func(ev: InputEvent): + if ev is InputEventMouseButton and ev.pressed and ev.button_index == MOUSE_BUTTON_LEFT: + # 空槽/越界槽点击=取消选中(handle 0);有效槽发其 handle(组合根判 AP 即用/进选中态) + treasure_slot_clicked.emit(_slot_handles[idx] if idx < _slot_handles.size() else 0)) + item_slots.add_child(slot) + return slot + + +## 三区段域(特性持有段上移承载——构筑核心信息优先级高于临时强化,且防 C 区下部裁切不可见/ +## 地形变异常驻角标 Boss 图整图型战斗全程可见防中局遗忘/特性预留段防「战斗结束被清」误读;空态整节隐藏) +func _build_side_sections(col: VBoxContainer) -> void: + _feat_section = _labeled_section(col, "专长") + _feat_list = _list_of(_feat_section) + _board_variant_section = _labeled_section(col, "地形变异") + _board_variant_list = _list_of(_board_variant_section) + _curio_section = _labeled_section(col, "属性值提升") + _curio_list = _list_of(_curio_section) + + +## 区段骨架(标题+隐藏容器) +func _labeled_section(col: VBoxContainer, title: String) -> VBoxContainer: + var section := VBoxContainer.new() + section.add_theme_constant_override("separation", 4) + section.visible = false + col.add_child(section) + section.add_child(UiKit.label(title,UiKit.FS_BODY, true)) + return section + + +## 行板容器(separation 2) +func _list_of(section: VBoxContainer) -> VBoxContainer: + var list := VBoxContainer.new() + list.add_theme_constant_override("separation", 2) + section.add_child(list) + return list func set_attack(atk: int, mod_text: String) -> void: @@ -174,46 +184,69 @@ func set_treasure_inventory(items: Array, selected_handle: int) -> void: continue var rec: Dictionary[Variant, Variant] = items[i] _slot_handles.append(int(rec.get("handle", 0))) - var used := bool(rec.get("used", false)) - var usable := bool(rec.get("usable", true)) - var selected := selected_handle != 0 and int(rec.get("handle", 0)) == selected_handle - # 槽内容=手绘像素图标(demo ⑦;glyph 字符→图标键映射,未映射/used/不可用回退字形+条件墨色) - var glyph_ch := String(rec.get("glyph", "?")) - var icon_map := {"爆": UiKit.TEX_ICON_BLAST, "医": UiKit.TEX_ICON_MEDKIT, - "铲": UiKit.TEX_ICON_SHOVEL, "袋": UiKit.TEX_ICON_COINBAG, "钥": UiKit.TEX_ICON_KEYSHARD, - "剑": UiKit.TEX_ICON_SWORD, "盾": UiKit.TEX_ICON_SHIELD, "心": UiKit.TEX_ICON_HEART, - "行": UiKit.TEX_ICON_POUCH} - var icon_path: String = icon_map.get(glyph_ch, "") - var itex := UiKit.load_tex(icon_path) if icon_path != "" else null - if itex != null and not used and usable: - var tr := TextureRect.new() - tr.texture = itex - tr.custom_minimum_size = Vector2(40, 40) - tr.expand_mode = TextureRect.EXPAND_IGNORE_SIZE - tr.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED - tr.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST - tr.set_anchors_preset(Control.PRESET_FULL_RECT) - tr.mouse_filter = Control.MOUSE_FILTER_IGNORE - slot.add_child(tr) - else: - var glyph := UiKit.label(glyph_ch,UiKit.FS_SUB, true) - glyph.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - glyph.vertical_alignment = VERTICAL_ALIGNMENT_CENTER - glyph.set_anchors_preset(Control.PRESET_FULL_RECT) - glyph.mouse_filter = Control.MOUSE_FILTER_IGNORE - if used: - # 「已用」占位墨=灰石深化(STONE_FAINT 对 PANEL_DATA 槽牌 2.7:1; - # STONE_DEEP = 5.6:1;语义分档保持双墨) - glyph.add_theme_color_override("font_color", UiKit.STONE_DEEP) - elif not usable: - # 「不可用」置灰墨=标签深墨(STONE 对槽牌 3.4:1;INK_LABEL = 10.4:1,虚位/失效更强置灰语义) - glyph.add_theme_color_override("font_color", UiKit.INK_LABEL) - slot.add_child(glyph) - var border_col: Color = UiKit.GOLD if selected else UiKit.PANEL_BORDER - slot.add_theme_stylebox_override("panel", - UiKit.panel_flat(UiKit.PANEL_DATA, border_col, 0, 1 if not selected else 2, Vector4(4, 4, 4, 4))) - # note=积点进度行(自动兑付件悬停浮窗补「积点 N/M」,进度随挖格经组合根重查刷新) - var note := String(rec.get("note", "")) - slot.tooltip_text = "%s%s%s" % [String(rec.get("name", "")), - "(%s)" % note if note != "" else "", - "(已用)" if used else ("(不可用——当前无有效目标)" if not usable else "")] + _fill_slot(slot, rec, selected_handle) + + +## 单槽内容填充域:图标优先(像素图标未映射/used/不可用回退字形+条件墨色)+选中金框+悬停浮窗 +func _fill_slot(slot: PanelContainer, rec: Dictionary, selected_handle: int) -> void: + var used := bool(rec.get("used", false)) + var usable := bool(rec.get("usable", true)) + var selected := selected_handle != 0 and int(rec.get("handle", 0)) == selected_handle + # 槽内容=手绘像素图标(demo ⑦;glyph 字符→图标键映射,未映射/used/不可用回退字形+条件墨色) + var itex := _slot_icon_tex(String(rec.get("glyph", "?"))) + if itex != null and not used and usable: + slot.add_child(_slot_icon_rect(itex)) + else: + slot.add_child(_slot_glyph_label(String(rec.get("glyph", "?")), used, usable)) + var border_col: Color = UiKit.GOLD if selected else UiKit.PANEL_BORDER + slot.add_theme_stylebox_override("panel", + UiKit.panel_flat(UiKit.PANEL_DATA, border_col, 0, 1 if not selected else 2, Vector4(4, 4, 4, 4))) + slot.tooltip_text = _slot_tooltip(rec, used, usable) + + +## 字形→图标纹理映射(demo ⑦ 键表单点) +func _slot_icon_tex(glyph_ch: String) -> Texture2D: + var icon_map := {"爆": UiKit.TEX_ICON_BLAST, "医": UiKit.TEX_ICON_MEDKIT, + "铲": UiKit.TEX_ICON_SHOVEL, "袋": UiKit.TEX_ICON_COINBAG, "钥": UiKit.TEX_ICON_KEYSHARD, + "剑": UiKit.TEX_ICON_SWORD, "盾": UiKit.TEX_ICON_SHIELD, "心": UiKit.TEX_ICON_HEART, + "行": UiKit.TEX_ICON_POUCH} + var icon_path: String = icon_map.get(glyph_ch, "") + return UiKit.load_tex(icon_path) if icon_path != "" else null + + +## 图标矩形域(40px 素描框,像素过滤) +func _slot_icon_rect(itex: Texture2D) -> TextureRect: + var tr := TextureRect.new() + tr.texture = itex + tr.custom_minimum_size = Vector2(40, 40) + tr.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + tr.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + tr.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + tr.set_anchors_preset(Control.PRESET_FULL_RECT) + tr.mouse_filter = Control.MOUSE_FILTER_IGNORE + return tr + + +## 字形回退标签域(used=灰石深化墨 5.6:1/不可用=标签深墨 10.4:1 虚位失效更强置灰——语义分档双墨) +func _slot_glyph_label(glyph_ch: String, used: bool, usable: bool) -> Label: + var glyph := UiKit.label(glyph_ch,UiKit.FS_SUB, true) + glyph.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + glyph.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + glyph.set_anchors_preset(Control.PRESET_FULL_RECT) + glyph.mouse_filter = Control.MOUSE_FILTER_IGNORE + if used: + # 「已用」占位墨=灰石深化(STONE_FAINT 对 PANEL_DATA 槽牌 2.7:1; + # STONE_DEEP = 5.6:1;语义分档保持双墨) + glyph.add_theme_color_override("font_color", UiKit.STONE_DEEP) + elif not usable: + # 「不可用」置灰墨=标签深墨(STONE 对槽牌 3.4:1;INK_LABEL = 10.4:1,虚位/失效更强置灰语义) + glyph.add_theme_color_override("font_color", UiKit.INK_LABEL) + return glyph + + +## 悬停浮窗域(note=积点进度行——自动兑付件悬停浮窗补「积点 N/M」,进度随挖格经组合根重查刷新) +func _slot_tooltip(rec: Dictionary, used: bool, usable: bool) -> String: + var note := String(rec.get("note", "")) + return "%s%s%s" % [String(rec.get("name", "")), + "(%s)" % note if note != "" else "", + "(已用)" if used else ("(不可用——当前无有效目标)" if not usable else "")] diff --git a/game/probe/ui_struct_probe.gd b/game/probe/ui_struct_probe.gd index 8ec33796..77c2f02c 100644 --- a/game/probe/ui_struct_probe.gd +++ b/game/probe/ui_struct_probe.gd @@ -157,7 +157,7 @@ func _ready() -> void: problems.append("[battle_hud] 候选中无战斗节点(驱动中断——候选=%s)" % str(main.level_screen._cand_ids)) # ---- 兑付屏(真实 open 面 + 合成最小数据——结构断言只看矩形/宽度/可见性)---- - main._end_overlay.open_victory("结构探针·胜利演算", "返回", Rect2(200, 200, 400, 300), "GP 0") + main._end_overlay.open_victory("结构探针·胜利演算", "返回", Rect2(200, 200, 400, 300), {"gp_summary": "GP 0"}) _settle() await _audit("battle_end", [main._end_overlay]) main._end_overlay.close()