feat(t4): 命中特效终稿+敌方演出链复核——打击火花六枚+弹道到达拍对齐

①standee_body.play_hit_impact:立绘盒中心六枚短火花放射(程序化色块,确定性方向 i*60°,亮金GOLD_LIT/暖白WARM 交替=物理打击语义不占 危险红=雷/奖励金=收益 位;0.12s 一拍即回收);fx_probe 增 hit_sparks 计数+第六断言段
②弹道到达拍对齐:on_step_row_fx 敌方路径 闪白/火花/击杀塌陷 延迟 0.33s≈弹道飞行(0.16+0.19)——呈现链序=弹道→闪白+火花→血条→灰化;突发多定时器互不干扰(flash 重启/死亡幂等),卡片释放 is_instance_valid 守卫,树外即时执行
③flash_hit 强度时长复核=维持现行(1.6,1.4,1.2)/0.08s+回0.2s——白光可读不糊,±30% 白名单内零调整;④伤害飘字子项代拍板=不做(血条已有「当前/最大」即时数字面,飘字=同一伤害第二呈现触 UI 禁重复展示铁律;审计备注项未立项)
This commit is contained in:
DKChan committed 2026-09-29 04:12:42 +08:00
1 parent cabb6b07bf
commit 8723d9d826
3 files changed
+51 -6

No files matched your search

+15 -3
View File
@@ -291,9 +291,21 @@ func on_step_row_fx(row: Dictionary) -> void:
var card: Node2D = _card_by_name(target)
if card == null:
return
card.flash_hit()
if bool(row.get("killed", false)):
card.mark_dead()
# 弹道到达拍对齐(迭代 6 T4 终稿):闪白/打击火花/击杀塌陷延迟 ≈弹道飞行时长
# (play_projectile 0.16+0.19)——呈现链序=弹道→闪白+火花→血条(投影刷新)→灰化塌陷。
# 行突发同拍多定时器互不干扰(flash 重启语义/死亡幂等守卫);卡片提前释放=守卫跳过;
# 树外(防御)=即时执行不调度。
var arrived := func():
if not is_instance_valid(card):
return
card.play_hit_impact()
card.flash_hit()
if bool(row.get("killed", false)):
card.mark_dead()
if get_tree() != null:
get_tree().create_timer(UiKit.motion(0.33)).timeout.connect(arrived)
else:
arrived.call()
func _card_by_name(display_name: String) -> Node2D:
+25 -2
View File
@@ -261,7 +261,9 @@ func mark_dead() -> void:
# ---- 演出密度终稿(样张 v2 冻结批 T3——程序化 tween,零新帧资产) ----
const DEATH_SHARD_N := 5 # 碎块数(§8.4 冻结值)
const HIT_SPARK_N := 6 # 命中火花数(迭代 6 T4 终稿——打击瞬间可读性一拍)
var _death_shards := 0 # 探针出口计数(碎块节点回收后仍保留读数)
var _hit_sparks := 0 # 探针出口计数(命中火花累计)
# 死亡碎块弹飞:立绘盒中心五块碎石抛物线散落(程序化色块——确定性散布,禁全局随机)
@@ -285,9 +287,30 @@ func _spawn_death_shards() -> void:
stw.tween_callback(shard.queue_free)
# 演出探针出口(fx_probe——死亡碎裂断言面)
# 演出探针出口(fx_probe——死亡碎裂/命中火花断言面)
func fx_probe() -> Dictionary:
return {"death_shards": _death_shards}
return {"death_shards": _death_shards, "hit_sparks": _hit_sparks}
# 命中瞬间打击特效(迭代 6 T4 终稿):立绘盒中心六枚短火花放射(程序化色块,确定性方向
# i*60°——禁全局随机;亮金/暖白交替=物理打击语义,不占 危险红=雷 与 奖励金=收益 的语义位)。
# 弹道到达拍由 battle_stage_view 调度(on_step_row_fx 延迟 0.33s≈弹道飞行)。
func play_hit_impact() -> void:
var center := _portrait_rect().get_center()
for i in HIT_SPARK_N:
_hit_sparks += 1
var spark := ColorRect.new()
spark.color = UiKit.GOLD_LIT if i % 2 == 0 else UiKit.WARM
spark.size = Vector2(4, 3)
spark.mouse_filter = Control.MOUSE_FILTER_IGNORE
spark.position = center - spark.size * 0.5
add_child(spark)
var ang := TAU * float(i) / float(HIT_SPARK_N)
var dir := Vector2(cos(ang), sin(ang)) * 16.0
var stw := spark.create_tween()
stw.tween_property(spark, "position", spark.position + dir, UiKit.motion(0.12)).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
stw.parallel().tween_property(spark, "modulate:a", 0.0, UiKit.motion(0.12))
stw.tween_callback(spark.queue_free)
func is_dead() -> bool:
+11 -1
View File
@@ -36,6 +36,16 @@ func _ready() -> void:
if not sb.is_dead():
problems.append("mark_dead 后 is_dead 应真")
# ①b 命中火花(迭代 6 T4 终稿——打击瞬间特效计数)
var sb2 = sb_script.new("enemy")
sb2.box = Vector2(150, 176)
add_child(sb2)
sb2.play_hit_impact()
await get_tree().process_frame
var hp2: Dictionary = sb2.fx_probe()
if int(hp2.get("hit_sparks", 0)) != 6:
problems.append("命中火花应 6 枚(迭代 6 T4),实得 %s" % [hp2.get("hit_sparks")])
# ---- ③ 终局侧:胜利纹章 / 失败掸灰+旗光 ----
var end_ov := BattleEndOverlay.new()
add_child(end_ov)
@@ -98,7 +108,7 @@ func _ready() -> void:
# ---- 汇总 ----
if problems.is_empty():
print("[fx_probe] PASS — 死亡碎裂5块/意图三档+零表示/胜利纹章10/失败掸灰8+旗光8/结算光带+缘线/主角三态切换 全链路通过")
print("[fx_probe] PASS — 死亡碎裂5块/命中火花6/意图三档+零表示/胜利纹章10/失败掸灰8+旗光8/结算光带+缘线/主角三态切换 全链路通过")
get_tree().quit(0)
else:
for p in problems: