204 lines
8.1 KiB
GDScript
204 lines
8.1 KiB
GDScript
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||
# 实现 GDD:design/ux/battle.md(胜利/失败终局 overlay——金蜡封占位落于扫雷图区中央 + 判定行 +
|
||
# 明细回看入口 + 继续/返回;失败 = 暮蓝 #5A6C8C 氛围(明度不变)+ 旗化光点收回占位;
|
||
# pattern 6 模态 overlay 层级纪律——遮挡物隐藏/输入锁定断言归场景探针)。
|
||
# Date: 2026-09-12(扩展自 node_overlay 的战斗终局职责分离)
|
||
class_name BattleEndOverlay
|
||
extends Control
|
||
|
||
signal review_pressed
|
||
signal continue_pressed
|
||
|
||
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 _gp_line: Label # 战利品行(OQ14 口径:胜利总结结算展示 GP 总量+增量——T5 接线 2026-09-28)
|
||
var _review_btn: Button
|
||
var _btn: Button
|
||
var _board_rect := Rect2()
|
||
|
||
|
||
func _init() -> void:
|
||
# 二轮修改批 B(2026-09-29 用户目检裁决):半透明浮层文字不可读 → 实底石板皮面板形态
|
||
#(与营地/设置/图例同族)。半透明氛围 dim 保留(胜利暖纱/失败暮蓝),信息与按钮全部上板;
|
||
# 金蜡封 = 面板头部静态徽记(原全屏坠落演出退役——落点被面板遮挡)。
|
||
_dim = ColorRect.new()
|
||
_dim.color = Color(COL_WARM, 0.0)
|
||
_dim.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
add_child(_dim)
|
||
var center := CenterContainer.new()
|
||
center.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
_dim.add_child(center)
|
||
var panel := PanelContainer.new()
|
||
panel.add_theme_stylebox_override("panel", UiKit.panel_skinned(Vector4(16, 14, 16, 14)))
|
||
center.add_child(panel)
|
||
var box := VBoxContainer.new()
|
||
box.custom_minimum_size = Vector2(520, 0)
|
||
box.add_theme_constant_override("separation", 12)
|
||
panel.add_child(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(34, 34)
|
||
_seal.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||
header.add_child(_seal)
|
||
_verdict = UiKit.label("", 24, true)
|
||
_verdict.add_theme_color_override("font_color", UiKit.INK_TITLE)
|
||
_verdict.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
_verdict.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||
header.add_child(_verdict)
|
||
_gp_line = UiKit.label("", 16, true)
|
||
_gp_line.add_theme_color_override("font_color", UiKit.INK_LABEL)
|
||
_gp_line.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
_gp_line.visible = false
|
||
box.add_child(_gp_line)
|
||
box.add_child(HSeparator.new())
|
||
_review_btn = UiKit.button("明细回看")
|
||
_review_btn.pressed.connect(_emit_review_pressed)
|
||
box.add_child(_review_btn)
|
||
_btn = UiKit.button("继续")
|
||
_btn.pressed.connect(_emit_continue_pressed)
|
||
box.add_child(_btn)
|
||
visible = false
|
||
|
||
|
||
# 终局画面数据锚 = 战斗结算 判定行(verdict)——判定行文案由组合根装配传入。
|
||
# gp_summary(OQ14 口径——胜利总结结算展示 GP 总量+增量):组合根装配「战利品 +增量 · 合计总量」
|
||
# 传入(入账次序 = LevelRunner 入账先于组合根 handler,runner.gp 即战后总量);空串 = 不显示(失败侧恒空)。
|
||
func open_victory(verdict_line: String, continue_label: String, board_rect: Rect2, gp_summary: String = "") -> void:
|
||
_dim.color = Color(COL_WARM, 0.28)
|
||
_verdict.text = verdict_line
|
||
_seal.visible = true # 胜利徽记(面板头部静态)
|
||
_gp_line.text = gp_summary
|
||
_gp_line.visible = gp_summary != ""
|
||
_btn.text = continue_label
|
||
_board_rect = board_rect
|
||
visible = true
|
||
_play_crest_assemble() # 碎砖拼纹章(§2.4——样张 v2 冻结批 T3 终稿;绕面板屏心环收)
|
||
_play_flag_recall(0) # 胜利不放旗收回
|
||
|
||
|
||
func open_defeat(verdict_line: String, continue_label: String, board_rect: Rect2) -> void:
|
||
# 明度不变的暮蓝色相氛围(全屏唯一暮蓝档——§2.5 最静一拍)
|
||
_dim.color = Color(COL_DUSK, 0.45)
|
||
_verdict.text = verdict_line
|
||
_gp_line.visible = false # GP 总量+增量归胜利总结结算(OQ14)——失败侧不呈现
|
||
_board_rect = board_rect
|
||
visible = true
|
||
_seal.visible = false # 失败无徽记(暮蓝氛围)
|
||
_play_dust_sadle() # 坐砖掸灰(§2.5——终稿)
|
||
_play_flag_recall(8)
|
||
|
||
|
||
func close() -> void:
|
||
visible = false
|
||
_seal.visible = false
|
||
_clear_crest_bricks()
|
||
|
||
|
||
func is_open() -> bool:
|
||
return visible
|
||
|
||
|
||
func verdict_text() -> String:
|
||
return _verdict.text
|
||
|
||
|
||
func _fx_center() -> Vector2:
|
||
return get_viewport_rect().size * 0.5
|
||
|
||
|
||
# 旗化光点收回(失败——插过的旗逐面化光点收回占位,§2.5)
|
||
func _play_flag_recall(count: int) -> void:
|
||
_fx_flag_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)
|
||
|
||
|
||
# ---- 演出密度终稿(样张 v2 冻结批 T3——程序化 tween,确定性散布,零新帧资产) ----
|
||
|
||
var _crest_bricks: Array[ColorRect] = [] # 纹章碎砖驻留面(close 时回收)
|
||
var _fx_crest_shards := 0
|
||
var _fx_dust_motes := 0
|
||
var _fx_flag_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)
|
||
|
||
|
||
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, "flag_dots": _fx_flag_dots}
|
||
|
||
|
||
func _emit_review_pressed() -> void:
|
||
review_pressed.emit()
|
||
|
||
|
||
func _emit_continue_pressed() -> void:
|
||
continue_pressed.emit()
|