## VERTICAL SLICE - NOT FOR PRODUCTION ## 战斗/冒险终局 overlay(模态):判定行+三行账+明细回看入口+继续/返回,失败侧暮蓝氛围。 ## 摘要文本经 open_victory/open_defeat 由组合根装配喂入——本类零文案装配。 class_name BattleEndOverlay extends Control signal continue_pressed # 冒险中无回看入口——回看归「生涯」(06 登记) 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() func _init() -> void: # 半透明浮层文字不可读 → 实底石板皮面板形态 #(与营地/设置/图例同族)。半透明氛围 dim 保留(胜利暖纱/失败暮蓝——open_* 就地换色),信息与 # 按钮全部上板;金蜡封 = 面板头部静态徽记(坠落演出落点被面板遮挡)。 # 骨架经 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 var box: VBoxContainer = shell.box 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 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) var seal_img := TextureRect.new() var seal_tex := UiKit.load_tex("res://assets/art/board/seal_mark.png") if seal_tex != null: seal_img.texture = seal_tex seal_img.custom_minimum_size = Vector2(32, 32) seal_img.expand_mode = TextureRect.EXPAND_IGNORE_SIZE seal_img.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED 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() 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) _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) 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 _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 != "" _btn.text = continue_label _board_rect = board_rect _cele.visible = true _play_celebrate() visible = true if UiKit.victory_fx_style == "dust": _play_victory_dust() # 尘金粒子(P-C5 两套并存——开关=调试浮窗,会话级) else: _play_crest_assemble() # 碎砖拼纹章(§2.4;默认) _play_seal_recall(0) # 胜利不放封印收回 if gp_gain >= 0: _roll_gp(gp_gain, gp_total) 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 visible = true _seal.visible = true _seal.modulate = Color(0.4, 0.4, 0.4) # 失败徽记灰化(demo ⑧) _cele.visible = false _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 _clear_crest_bricks() func is_open() -> bool: return visible func verdict_text() -> String: return _verdict.text # 两列账行工厂(审计差距④——名左/值右,定宽 360) func _ledger_row() -> HBoxContainer: var r := HBoxContainer.new() r.custom_minimum_size = Vector2(360, 0) return r func _ledger_name(size: int, col: Color, _bold: bool) -> Label: var l := UiKit.label("", size, true, col) l.size_flags_horizontal = Control.SIZE_EXPAND_FILL return l func _ledger_value(size: int, col: Color, bold: bool) -> Label: return UiKit.label("", size, bold, col) # 拆名/值(组合根传入整串):gp_shape=固定名「战利品 GP」值取余串;其余行按「:」或「 · 」切 #(全角冒号不入值列——实机「:攻击 +1」缩进 Corrections);无分隔符=整串入值列、名空 func _set_ledger_row(name_l: Label, val_l: Label, s: String, gp_shape := false) -> void: if gp_shape and s.begins_with("战利品 GP"): name_l.text = "战利品 GP" val_l.text = s.substr(6).strip_edges() return var sep := s.find(":") if sep >= 0: name_l.text = s.substr(0, sep) val_l.text = s.substr(sep + 1).strip_edges() return sep = s.find(" · ") if sep >= 0: name_l.text = s.substr(0, sep) val_l.text = s.substr(sep).strip_edges() else: name_l.text = "" val_l.text = s func _fx_center() -> Vector2: return get_viewport_rect().size * 0.5 # 封印化光点收回(失败——插过的封印逐面化光点收回占位,§2.5) func _play_seal_recall(count: int) -> void: _fx_seal_dots = count 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) # ---- 演出密度(程序化 tween,确定性散布,零新帧资产) ---- var _crest_bricks: Array[ColorRect] = [] # 纹章碎砖驻留面(close 时回收) var _fx_crest_shards := 0 var _fx_dust_motes := 0 var _fx_seal_dots := 0 # 碎砖拼纹章(胜利 §2.4——蜡封落定后十枚碎砖自四周向蜡封外环收拢拼成纹章环,驻留至 close) func _play_crest_assemble() -> void: _clear_crest_bricks() _fx_crest_shards = 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)) # 坐砖掸灰(失败 §2.5——暖灰尘絮自棋盘中带缓缓飘落,最静一拍的运动承载) func _play_dust_sadle() -> void: _fx_dust_motes = 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) # 庆祝立绘浮动(idle 占位——UiKit.motion 守减动效;motion_scale<1 静帧) func _play_celebrate() -> void: if UiKit.motion_scale < 1.0: return var tw := _cele.create_tween() 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) # 尘金粒子(备选演出——金屑自面板上带飘落闪灭;确定性散布守 S2) func _play_victory_dust() -> void: _fx_dust_motes += 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) # GP 行数字滚动(demo ⑧——0.8s 三次缓出;减动效直出终值) func _roll_gp(gp_gain: int, gp_total: int) -> void: if UiKit.motion_scale < 1.0: _gp_val.text = "+%d · 合计 %d" % [gp_gain, gp_total] return var tw := create_tween() tw.tween_method(func(v: float) -> void: _gp_val.text = "+%d · 合计 %d" % [roundi(gp_gain * v), gp_total], 0.0, 1.0, UiKit.motion(0.8)).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT) func _clear_crest_bricks() -> void: for b in _crest_bricks: if is_instance_valid(b): b.queue_free() _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} func _emit_continue_pressed() -> void: continue_pressed.emit()