Files
gplan-2/game/presentation/views/board_fx_view.gd
T
DKChan 32fdf957fd refactor(代码术语): 卡① flag→seal 全族换代+game/ 禁用词清扫(迭代 31 卡①)
标识符面:flag_*→seal_ 族(含 Godot 信号 flag_judged→seal_judged 与连接/回调同名、
FACTS 两键 flags_count→seals_count/dug_under_flag→dug_under_seal 全链、
.tres 键名与 schema .gd 同批同步);显式豁免零改动=size_flags_*(Godot 布局属性)+_fatal_flag。
中文面:金币→GP(金币袋→GP 袋)/插旗→封印/雷格→危险格/惩罚格→危险格/旗→封印·封印印记族,
game/ 内 *.gd/.tres/.md/.json 四类禁用词与 flag 双双零命中(V1/V4)。
守卫字面量不动(被检查的禁字不是使用):smoke_all V21 零「雷/旗」检查面、
ui_font_asset_test 字形集攻/雷/旗/封 保持原断言逻辑。
代拍板:板面单字槽 glyph「旗」→「封」(禁字纪律+单字位宽,消费面四处同名随动)。
素材换代(2026-10-18 用户裁案甲):assets/art/board/flag_seal.png→seal_mark.png(素材不入 git,
盘上改名+导入重刷),代码两处 res:// 字符串+COL_FLAG/CURSOR_FLAG 常量同步,
闸件四处随批(run_all_gates.sh/render_gate.sh/golden_baseline_refresh.sh/path_check.py)。
anchor.json 键名手改,重锚与改前改后 diff 随本卡收口(同裁案甲)。

基线读数(改前,主区 ed2dfaf6):冒烟 1151 检查 0 失败/单元 34 套全绿/探针 --click
存量红仅 screen_flow_probe(user:// 断点污染在册已知项)。
改后同批读数(本 worktree):冒烟 1151 检查 0 失败/探针 --click 同一存量红、其余全绿。
2026-10-19 02:52:38 +08:00

172 lines
6.8 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
## 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 # 封印黑(封印化暴击弹道——封印朱→封印紫→封印黑 2026-09-28)
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 2:
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)
_show_banner("完美排雷 ×2")
# 封印命中:封印化暴击弹道加重占位(自棋盘上缘飞向 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)
# ---- 挖掘挥铲三帧(棋盘小人贴图批 2026-09-25——计划书 §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 与棋盘同层同系)——
# 以手为圆心、铲长为半径旋转(2026-09-25 用户裁决;三帧系素材侧预烤的旋转相位,握把锚 (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)