Files

177 lines
6.8 KiB
GDScript
Raw Permalink 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
## D 区演出层:清图喷泉/Boss 影子/完美封印光带/封印命中弹道/挥铲三帧等一次性占位演出。
## 组合根按核心信号直调 play_*;素材缺位静默回退(演出层不设断言面)。
class_name BoardFxView
extends Control
const COL_GOLD := UiKit.GOLD
const COL_BOSS := UiKit.BOSS_PURPLE # 紫赤 #8F2F6A
const COL_SEAL := UiKit.SEAL_BLACK # 封印黑(封印化暴击弹道)
const CONFETTI_COLS := [UiKit.GOLD, UiKit.GOLD_LIT, UiKit.HERO_BLUE, UiKit.WARM] # 装饰彩带不涉封印语义——原紫位改亮金(紫赤系 Boss 独占不入装饰)
var _banner: Label
func _init() -> void:
mouse_filter = Control.MOUSE_FILTER_IGNORE
set_anchors_preset(Control.PRESET_FULL_RECT)
_banner = UiKit.label("",UiKit.FS_TITLE, true, COL_GOLD)
_banner.set_anchors_preset(Control.PRESET_CENTER_TOP)
_banner.grow_horizontal = Control.GROW_DIRECTION_BOTH
_banner.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_banner.visible = false
_banner.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(_banner)
# 清图瞬间:金粒喷泉 + 彩纸 3–5s 即收(§2.3 占位)
func play_clear_burst() -> void:
var fountain := CPUParticles2D.new()
fountain.position = size * 0.5
fountain.amount = 90
fountain.lifetime = UiKit.motion(1.6)
fountain.one_shot = true
fountain.explosiveness = 0.2
fountain.direction = Vector2(0, -1)
fountain.spread = 55.0
fountain.initial_velocity_min = 320.0
fountain.initial_velocity_max = 560.0
fountain.gravity = Vector2(0, 700)
fountain.scale_amount_min = 2.0
fountain.scale_amount_max = 4.5
fountain.color = COL_GOLD
fountain.emitting = true
add_child(fountain)
var confetti := CPUParticles2D.new()
confetti.position = Vector2(size.x * 0.5, -8.0)
confetti.amount = 60
confetti.lifetime = UiKit.motion(3.2)
confetti.one_shot = true
confetti.explosiveness = 0.9
confetti.direction = Vector2(0, 1)
confetti.spread = 75.0
confetti.initial_velocity_min = 90.0
confetti.initial_velocity_max = 200.0
confetti.gravity = Vector2(0, 140)
confetti.scale_amount_min = 3.0
confetti.scale_amount_max = 5.0
confetti.color_ramp = _confetti_ramp()
confetti.emitting = true
add_child(confetti)
var tw := create_tween()
tw.tween_interval(UiKit.motion(3.5))
tw.tween_callback(func():
for n in [fountain, confetti]:
if is_instance_valid(n):
n.queue_free())
func _confetti_ramp() -> Gradient:
var g := Gradient.new()
g.set_color(0, CONFETTI_COLS[0])
g.set_color(1, CONFETTI_COLS[1])
return g
# Boss 遭遇:紫赤影子爬过棋盘一次性占位演出(§2.6)
func play_boss_shadow() -> void:
var shadow := ColorRect.new()
shadow.color = Color(COL_BOSS, 0.55)
shadow.size = Vector2(size.x, 56.0)
shadow.position = Vector2(0, -60.0)
shadow.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(shadow)
var tw := shadow.create_tween()
tw.tween_property(shadow, "position:y", size.y, UiKit.motion(1.1)).set_trans(Tween.TRANS_SINE)
tw.tween_property(shadow, "modulate:a", 0.0, UiKit.motion(0.5))
tw.tween_callback(shadow.queue_free)
# 完美封印:金光带加倍扫射 + 横幅占位(§2.2 最高档)
func play_perfect_sweep() -> void:
for i in 3:
_spawn_sweep_band(i, size)
_show_banner("完美封印 ×2")
## 单光带域(循环体提函数——自左掠过渐出)
func _spawn_sweep_band(i: int, size: Vector2) -> void:
var band := ColorRect.new()
band.color = Color(COL_GOLD, 0.35)
band.size = Vector2(size.x, 14.0 + 10.0 * i)
band.position = Vector2(-size.x, size.y * 0.3 + 40.0 * i)
band.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(band)
var tw := band.create_tween()
tw.tween_property(band, "position:x", size.x + 8.0, UiKit.motion(0.5 + 0.15 * i))
tw.tween_callback(band.queue_free)
# 封印命中:封印化暴击弹道加重占位(自棋盘上缘飞向 B 区敌人卡方向——越界即收)
func play_seal_hit_projectile(heavy: bool) -> void:
var shot := ColorRect.new()
var w := 8.0 if heavy else 4.0
shot.color = COL_SEAL
shot.size = Vector2(w, w * 2.2)
shot.position = Vector2(size.x * 0.5, size.y * 0.7)
shot.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(shot)
var tw := shot.create_tween()
tw.tween_property(shot, "position", Vector2(size.x * 0.75, -30.0), UiKit.motion(0.35))
tw.parallel().tween_property(shot, "modulate:a", 0.0, UiKit.motion(0.35))
tw.tween_callback(shot.queue_free)
# ---- 挖掘挥铲三帧(计划书 §3.C 解耦方案:铲归 fx 层、角色弹跳归
# BoardView.play_hero_dig_bounce,两演出零共享属性) ----
const SHOVEL_FRAME_PX := 32.0 # 铲帧画布边长(缩放比与英雄帧同源)
const SHOVEL_FRAME_S := 0.08 # ~80ms/帧(f0→f1→f2 共 ~240ms 即收)
const SHOVEL_GRIP := Vector2(18, 12) # 握把锚点(素材侧锁定的画布坐标——英雄持握手位对齐点)
var _shovel_texs: Array[Texture2D] = [] # 惰性装载缓存(素材缺位 = 空表静默回退——素材不入 git)
# 挖掘挥铲:hand = 英雄持握手位(BoardView.hero_hand_point 出口,fx 与棋盘同层同系)——
# 以手为圆心、铲长为半径旋转(三帧系素材侧预烤的旋转相位,握把锚 (18,12)
# 三帧同位,锚点钉死手位即成弧);dir = 挖掘方向单位格向量(素材铲头朝左——dir.x>0 翻转);
# sprite_scale 与英雄精灵同档(整数 1×——原「共享边中点+12% 偏置」定位与 edge/32 缩放作废)。
func play_shovel_swing(hand: Vector2, dir: Vector2i, sprite_scale: float) -> void:
if _shovel_texs.is_empty():
for p in ["res://assets/art/fx/shovel_swing_f0.png",
"res://assets/art/fx/shovel_swing_f1.png",
"res://assets/art/fx/shovel_swing_f2.png"]:
var t := UiKit.load_tex(p)
if t == null:
return # 素材缺位——静默回退(fx 层占位演出不设断言面)
_shovel_texs.append(t)
var flip := dir.x > 0
var grip := SHOVEL_GRIP
if flip:
grip.x = SHOVEL_FRAME_PX - SHOVEL_GRIP.x # flip_h 围绕纹理中心镜像——锚点 x 随镜
var sp := Sprite2D.new()
sp.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
sp.centered = true
sp.texture = _shovel_texs[0]
sp.flip_h = flip
sp.scale = Vector2.ONE * sprite_scale
sp.position = hand + (Vector2(SHOVEL_FRAME_PX, SHOVEL_FRAME_PX) * 0.5 - grip) * sprite_scale
add_child(sp)
var tw := sp.create_tween()
for i in [1, 2]:
tw.tween_interval(UiKit.motion(SHOVEL_FRAME_S))
tw.tween_callback(func() -> void: sp.texture = _shovel_texs[i])
tw.tween_interval(UiKit.motion(SHOVEL_FRAME_S))
tw.tween_callback(sp.queue_free)
func _show_banner(text: String) -> void:
_banner.text = text
_banner.visible = true
_banner.modulate.a = 0.0
var tw := create_tween()
tw.tween_property(_banner, "modulate:a", 1.0, UiKit.motion(0.2))
tw.tween_interval(UiKit.motion(1.6))
tw.tween_property(_banner, "modulate:a", 0.0, UiKit.motion(0.4))
tw.tween_callback(func(): _banner.visible = false)