From 8723d9d826fcb7e96daa669778bd67320db027a8 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Tue, 29 Sep 2026 04:12:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(t4):=20=E5=91=BD=E4=B8=AD=E7=89=B9?= =?UTF-8?q?=E6=95=88=E7=BB=88=E7=A8=BF+=E6=95=8C=E6=96=B9=E6=BC=94?= =?UTF-8?q?=E5=87=BA=E9=93=BE=E5=A4=8D=E6=A0=B8=E2=80=94=E2=80=94=E6=89=93?= =?UTF-8?q?=E5=87=BB=E7=81=AB=E8=8A=B1=E5=85=AD=E6=9E=9A+=E5=BC=B9?= =?UTF-8?q?=E9=81=93=E5=88=B0=E8=BE=BE=E6=8B=8D=E5=AF=B9=E9=BD=90=20?= =?UTF-8?q?=E2=91=A0standee=5Fbody.play=5Fhit=5Fimpact:=E7=AB=8B=E7=BB=98?= =?UTF-8?q?=E7=9B=92=E4=B8=AD=E5=BF=83=E5=85=AD=E6=9E=9A=E7=9F=AD=E7=81=AB?= =?UTF-8?q?=E8=8A=B1=E6=94=BE=E5=B0=84(=E7=A8=8B=E5=BA=8F=E5=8C=96?= =?UTF-8?q?=E8=89=B2=E5=9D=97,=E7=A1=AE=E5=AE=9A=E6=80=A7=E6=96=B9?= =?UTF-8?q?=E5=90=91=20i*60=C2=B0,=E4=BA=AE=E9=87=91GOLD=5FLIT/=E6=9A=96?= =?UTF-8?q?=E7=99=BDWARM=20=E4=BA=A4=E6=9B=BF=3D=E7=89=A9=E7=90=86?= =?UTF-8?q?=E6=89=93=E5=87=BB=E8=AF=AD=E4=B9=89=E4=B8=8D=E5=8D=A0=20?= =?UTF-8?q?=E5=8D=B1=E9=99=A9=E7=BA=A2=3D=E9=9B=B7/=E5=A5=96=E5=8A=B1?= =?UTF-8?q?=E9=87=91=3D=E6=94=B6=E7=9B=8A=20=E4=BD=8D;0.12s=20=E4=B8=80?= =?UTF-8?q?=E6=8B=8D=E5=8D=B3=E5=9B=9E=E6=94=B6);fx=5Fprobe=20=E5=A2=9E=20?= =?UTF-8?q?hit=5Fsparks=20=E8=AE=A1=E6=95=B0+=E7=AC=AC=E5=85=AD=E6=96=AD?= =?UTF-8?q?=E8=A8=80=E6=AE=B5=20=E2=91=A1=E5=BC=B9=E9=81=93=E5=88=B0?= =?UTF-8?q?=E8=BE=BE=E6=8B=8D=E5=AF=B9=E9=BD=90:on=5Fstep=5Frow=5Ffx=20?= =?UTF-8?q?=E6=95=8C=E6=96=B9=E8=B7=AF=E5=BE=84=20=E9=97=AA=E7=99=BD/?= =?UTF-8?q?=E7=81=AB=E8=8A=B1/=E5=87=BB=E6=9D=80=E5=A1=8C=E9=99=B7=20?= =?UTF-8?q?=E5=BB=B6=E8=BF=9F=200.33s=E2=89=88=E5=BC=B9=E9=81=93=E9=A3=9E?= =?UTF-8?q?=E8=A1=8C(0.16+0.19)=E2=80=94=E2=80=94=E5=91=88=E7=8E=B0?= =?UTF-8?q?=E9=93=BE=E5=BA=8F=3D=E5=BC=B9=E9=81=93=E2=86=92=E9=97=AA?= =?UTF-8?q?=E7=99=BD+=E7=81=AB=E8=8A=B1=E2=86=92=E8=A1=80=E6=9D=A1?= =?UTF-8?q?=E2=86=92=E7=81=B0=E5=8C=96;=E7=AA=81=E5=8F=91=E5=A4=9A?= =?UTF-8?q?=E5=AE=9A=E6=97=B6=E5=99=A8=E4=BA=92=E4=B8=8D=E5=B9=B2=E6=89=B0?= =?UTF-8?q?(flash=20=E9=87=8D=E5=90=AF/=E6=AD=BB=E4=BA=A1=E5=B9=82?= =?UTF-8?q?=E7=AD=89),=E5=8D=A1=E7=89=87=E9=87=8A=E6=94=BE=20is=5Finstance?= =?UTF-8?q?=5Fvalid=20=E5=AE=88=E5=8D=AB,=E6=A0=91=E5=A4=96=E5=8D=B3?= =?UTF-8?q?=E6=97=B6=E6=89=A7=E8=A1=8C=20=E2=91=A2flash=5Fhit=20=E5=BC=BA?= =?UTF-8?q?=E5=BA=A6=E6=97=B6=E9=95=BF=E5=A4=8D=E6=A0=B8=3D=E7=BB=B4?= =?UTF-8?q?=E6=8C=81=E7=8E=B0=E8=A1=8C(1.6,1.4,1.2)/0.08s+=E5=9B=9E0.2s?= =?UTF-8?q?=E2=80=94=E2=80=94=E7=99=BD=E5=85=89=E5=8F=AF=E8=AF=BB=E4=B8=8D?= =?UTF-8?q?=E7=B3=8A,=C2=B130%=20=E7=99=BD=E5=90=8D=E5=8D=95=E5=86=85?= =?UTF-8?q?=E9=9B=B6=E8=B0=83=E6=95=B4;=E2=91=A3=E4=BC=A4=E5=AE=B3?= =?UTF-8?q?=E9=A3=98=E5=AD=97=E5=AD=90=E9=A1=B9=E4=BB=A3=E6=8B=8D=E6=9D=BF?= =?UTF-8?q?=3D=E4=B8=8D=E5=81=9A(=E8=A1=80=E6=9D=A1=E5=B7=B2=E6=9C=89?= =?UTF-8?q?=E3=80=8C=E5=BD=93=E5=89=8D/=E6=9C=80=E5=A4=A7=E3=80=8D?= =?UTF-8?q?=E5=8D=B3=E6=97=B6=E6=95=B0=E5=AD=97=E9=9D=A2,=E9=A3=98?= =?UTF-8?q?=E5=AD=97=3D=E5=90=8C=E4=B8=80=E4=BC=A4=E5=AE=B3=E7=AC=AC?= =?UTF-8?q?=E4=BA=8C=E5=91=88=E7=8E=B0=E8=A7=A6=20UI=20=E7=A6=81=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E5=B1=95=E7=A4=BA=E9=93=81=E5=BE=8B;=E5=AE=A1?= =?UTF-8?q?=E8=AE=A1=E5=A4=87=E6=B3=A8=E9=A1=B9=E6=9C=AA=E7=AB=8B=E9=A1=B9?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/presentation/views/battle_stage_view.gd | 18 ++++++++++--- game/presentation/views/standee_body.gd | 27 ++++++++++++++++++-- game/smoke/fx_probe.gd | 12 ++++++++- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/game/presentation/views/battle_stage_view.gd b/game/presentation/views/battle_stage_view.gd index e792d188..aa89d201 100644 --- a/game/presentation/views/battle_stage_view.gd +++ b/game/presentation/views/battle_stage_view.gd @@ -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: diff --git a/game/presentation/views/standee_body.gd b/game/presentation/views/standee_body.gd index 2ea2d6c0..748433c5 100644 --- a/game/presentation/views/standee_body.gd +++ b/game/presentation/views/standee_body.gd @@ -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: diff --git a/game/smoke/fx_probe.gd b/game/smoke/fx_probe.gd index 65e3428c..47ceb5fd 100644 --- a/game/smoke/fx_probe.gd +++ b/game/smoke/fx_probe.gd @@ -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: