Files
gplan-2/game/presentation/views/battle_end_overlay.gd
T

203 lines
8.1 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:
_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 box := VBoxContainer.new()
box.add_theme_constant_override("separation", 12)
center.add_child(box)
_verdict = UiKit.label("", 26, true)
_verdict.add_theme_color_override("font_color", COL_WARM)
_verdict.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
box.add_child(_verdict)
_gp_line = UiKit.label("", 16, true)
_gp_line.add_theme_color_override("font_color", UiKit.GOLD)
_gp_line.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_gp_line.visible = false
box.add_child(_gp_line)
_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)
# 金蜡封占位(胜利):圆形色块落于扫雷图区中央——位置由 set_board_rect 注入
_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.size = Vector2(72, 72)
_seal.visible = false
_seal.top_level = true
add_child(_seal)
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
_verdict.add_theme_color_override("font_color", UiKit.INK_ON_GOLD)
_gp_line.text = gp_summary
_gp_line.visible = gp_summary != ""
_btn.text = continue_label
_board_rect = board_rect
visible = true
_play_seal_fall()
_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
_verdict.add_theme_color_override("font_color", COL_WARM)
_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
# 金蜡封自上落下弹跳定格于棋盘中央(§2.4 克制不狂欢——占位级)
func _play_seal_fall() -> void:
var target := _board_rect.get_center() - _seal.size * 0.5
if _board_rect.size == Vector2.ZERO:
target = get_viewport_rect().size * 0.5 - _seal.size * 0.5
_seal.position = Vector2(target.x, -100.0)
_seal.visible = true
var tw := _seal.create_tween()
tw.tween_property(_seal, "position:y", target.y - 26.0, UiKit.motion(0.42)).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN)
tw.tween_property(_seal, "position:y", target.y, UiKit.motion(0.22)).set_trans(Tween.TRANS_BOUNCE).set_ease(Tween.EASE_OUT)
# 旗化光点收回(失败——插过的旗逐面化光点收回占位,§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 := _board_rect.get_center() if _board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5
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 := _board_rect.get_center() if _board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5
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()