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

389 lines
16 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
## 战斗/冒险终局 overlay(模态):判定行+三行账+明细回看入口+继续/返回,失败侧暮蓝氛围。
## 摘要文本经 open_victory/open_defeat 由组合根装配喂入——本类零文案装配。
## 内部数据类 _EndOverlayState=宿主画面登记表(只被本文件填读,文件外零消费者——字段为公开数据成员)。
class_name BattleEndOverlay
extends Control
signal continue_pressed # 冒险中无回看入口——回看归「生涯」(06 登记)
const COL_GOLD := UiKit.GOLD
const COL_DUSK := UiKit.DUSK # 暮蓝(全作唯一暮蓝档)
const COL_WARM := UiKit.WARM
## 内部状态件(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:
# 半透明浮层文字不可读 → 实底石板皮面板形态
#(与营地/设置/图例同族)。半透明氛围 dim 保留(胜利暖纱/失败暮蓝——open_* 就地换色),信息与
# 按钮全部上板;金蜡封 = 面板头部静态徽记(坠落演出落点被面板遮挡)。
# 骨架经 UiKit.modal_shell;dim 引用摘出供 open_* 换氛围色。
var shell := UiKit.modal_shell(self, Color(COL_WARM, 0.0), Vector2(520, 0), 12,
UiKit.panel_skinned(Vector4(16, 14, 16, 14)))
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)
st.seal = Panel.new()
st.seal.mouse_filter = Control.MOUSE_FILTER_IGNORE
var sb := UiKit.panel_flat(COL_GOLD, UiKit.GOLD_DEEP, 200, 4)
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:
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
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
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)
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())
# 终局画面数据锚 = 战斗结算 判定行(verdict)——判定行文案由组合根装配传入。
# gp_summary(胜利总结结算展示 GP 总量+增量):组合根装配「战利品 +增量 · 合计总量」
# 传入(入账次序 = LevelRunner 入账先于组合根 handler,runner.gp 即战后总量);空串 = 不显示(失败侧恒空)。
# 可选尾参收拢 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(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
st.board_rect = board_rect
st.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 最静一拍)
st.dim.color = Color(COL_DUSK, 0.6) # 同族压暗
st.verdict.text = verdict_line
st.board_rect = board_rect
visible = true
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
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()
remove_meta(&"celebrate_tw")
_clear_crest_bricks()
func is_open() -> bool:
return visible
func verdict_text() -> String:
return st.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:
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:
_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,确定性散布,零新帧资产;计数与碎砖驻留面在 _EndOverlayState) ----
# 碎砖拼纹章(胜利 §2.4——蜡封落定后十枚碎砖自四周向蜡封外环收拢拼成纹章环,驻留至 close)
func _play_crest_assemble() -> void:
_clear_crest_bricks()
st.fx_counts["crest"] = 10
var c := _fx_center()
for i in 10:
_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:
st.fx_counts["dust"] = 8
var base := _fx_center()
for i in 8:
_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 静帧)
func _play_celebrate() -> void:
if UiKit.motion_scale < 1.0:
return
var prev: Tween = get_meta(&"celebrate_tw", null)
if prev != null and prev.is_valid():
prev.kill()
var tw := st.cele.create_tween()
set_meta(&"celebrate_tw", tw)
tw.set_loops()
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:
st.fx_counts["dust"] += 10
var c := _fx_center()
for i in 10:
_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 三次缓出;减动效直出终值)
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 st.crest_bricks:
if is_instance_valid(b):
b.queue_free()
st.crest_bricks.clear()
# 演出探针出口(fx_probe——胜利纹章/失败掸灰/封印光收回三断言面)
func fx_probe() -> Dictionary:
return {"crest_shards": st.fx_counts["crest"], "dust_motes": st.fx_counts["dust"], "seal_dots": st.fx_counts["seal"]}
func _emit_continue_pressed() -> void:
continue_pressed.emit()