From ebd0808034e325102e8e608d574c2ba38fee1fd2 Mon Sep 17 00:00:00 2001 From: swrring Date: Wed, 13 May 2026 23:18:45 +0900 Subject: [PATCH] =?UTF-8?q?feat(BT12-Dev):=20A11=20=EC=A0=95=EB=A0=B9?= =?UTF-8?q?=EB=B6=88=20frame=20=EA=B8=B0=EB=B0=98=20playback=20=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=20(PD=20=EC=A7=80=EC=8B=9C=202026-05-13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FX_Rotating shield.anim 측정 정합 (m_SampleRate 60·m_StopTime 2.8166666·총 169 frame). PD 명시 영역 제어: - intro: 1~88 frame (0~1.4667s) - loop: 89~105 frame 반복 (17 frame·0.2833s) - outro: 남은 frame (106~169 → 1.7500~2.8167s·1.0667s) — duration 종료 전 자동 재생 SpiritFireInstance.Init: - Animator 캐싱·updateMode = UnscaledTime - speed=1 (normalizedTime 직접 제어) SpiritFireInstance.Update: - elapsed < introEnd → linear sample - elapsed < outroStart → loop range modulo - elapsed >= outroStart → outro linear (duration - outroLength 영역) - Animator.Play(STATE_HASH, 0, normalizedTime) 매 frame 호출 STATE_HASH = Animator.StringToHash("Base Layer.FX_Rotating shield") Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Skills/Effectors/SpiritFireSpawner.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) 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);