feat(BT12-Dev): A11 정령불 frame 기반 playback 제어 (PD 지시 2026-05-13)

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) <noreply@anthropic.com>
This commit is contained in:
깃 관리자 2026-05-13 23:18:45 +09:00
parent b1b476a061
commit ebd0808034
1 changed files with 47 additions and 0 deletions

View File

@ -48,10 +48,19 @@ namespace EerieVillage.Skills.Effectors
/// <summary>
/// 정령불 인스턴스 — Player 자식 부착·duration 동안 OverlapCircle 영역 적 투사체 SelfDestruct·근접 적 매 초 5 피해.
/// PD 지시 2026-05-13 — FX_Rotating shield Animator frame 제어 (60fps·intro 1~88·loop 89~105·outro 남은 frame).
/// </summary>
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<Animator>();
if (_animator == null) _animator = GetComponentInChildren<Animator>();
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);