diff --git a/Assets/Scripts/Skills/Effectors/SpiritFireSpawner.cs b/Assets/Scripts/Skills/Effectors/SpiritFireSpawner.cs
index e93c1fd..392faef 100644
--- a/Assets/Scripts/Skills/Effectors/SpiritFireSpawner.cs
+++ b/Assets/Scripts/Skills/Effectors/SpiritFireSpawner.cs
@@ -48,10 +48,19 @@ namespace EerieVillage.Skills.Effectors
///
/// 정령불 인스턴스 — Player 자식 부착·duration 동안 OverlapCircle 영역 적 투사체 SelfDestruct·근접 적 매 초 5 피해.
+ /// PD 지시 2026-05-13 — FX_Rotating shield Animator frame 제어 (60fps·intro 1~88·loop 89~105·outro 남은 frame).
///
public class SpiritFireInstance : MonoBehaviour
{
+ // PD 지시 2026-05-13 — FX_Rotating shield.anim 정합 (m_SampleRate 60·m_StopTime 2.8166666 → 169 frame·2.8167s)
+ const float FPS = 60f;
+ const int INTRO_END_FRAME = 88;
+ const int LOOP_END_FRAME = 105;
+ const float CLIP_LENGTH = 2.8166666f;
+ static readonly int STATE_HASH = Animator.StringToHash("Base Layer.FX_Rotating shield");
+
Transform _player;
+ Animator _animator;
float _spawnTime;
float _duration;
float _radius;
@@ -65,10 +74,48 @@ namespace EerieVillage.Skills.Effectors
_radius = radius;
_damage = damage;
_spawnTime = Time.unscaledTime;
+
+ _animator = GetComponent();
+ if (_animator == null) _animator = GetComponentInChildren();
+ if (_animator != null)
+ {
+ _animator.updateMode = AnimatorUpdateMode.UnscaledTime;
+ _animator.speed = 1f;
+ }
}
void Update()
{
+ // PD 지시 2026-05-13 — Animator frame 제어 (intro 0~88f·loop 88~105f·outro 105~clip_end)
+ if (_animator != null)
+ {
+ float introEnd = INTRO_END_FRAME / FPS; // 1.4667s
+ float loopEnd = LOOP_END_FRAME / FPS; // 1.75s
+ float outroLength = Mathf.Max(0f, CLIP_LENGTH - loopEnd); // 1.0666s
+ float outroStart = Mathf.Max(introEnd, _duration - outroLength);
+
+ float elapsed = Time.unscaledTime - _spawnTime;
+ float targetTime;
+
+ if (elapsed < introEnd)
+ {
+ targetTime = elapsed;
+ }
+ else if (elapsed < outroStart)
+ {
+ float loopRange = Mathf.Max(0.01f, loopEnd - introEnd);
+ float loopT = (elapsed - introEnd) % loopRange;
+ targetTime = introEnd + loopT;
+ }
+ else
+ {
+ targetTime = loopEnd + (elapsed - outroStart);
+ }
+
+ float normalized = Mathf.Clamp01(targetTime / CLIP_LENGTH);
+ _animator.Play(STATE_HASH, 0, normalized);
+ }
+
if (Time.unscaledTime - _spawnTime >= _duration)
{
Destroy(gameObject);