89 lines
4.3 KiB
C#
89 lines
4.3 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
|
// LevelUpBurstRunner.cs — LevelUpBurst 의 코루틴 호스트(WLHitFeelRunner 선례)
|
|
//
|
|
// PD 지시 #813 · 발주서 WL-813k §1-2 (2026-09-09)
|
|
//
|
|
// LevelUpBurst 가 런타임에 1개만 만들고 DontDestroyOnLoad 로 들고 있는다. 씬·프리팹을 건드리지 않는다.
|
|
// 두 코루틴 모두 **되돌리기 안전**을 최우선으로 짰다.
|
|
// · 이펙트 파괴: 수명이 끝나면 파괴하고 EffectBudget 슬롯을 정확히 1회만 반납한다(러너가 사라져도 OnDisable 이 반납).
|
|
// · 스케일 펀치: 우리가 쓴 스케일이 그대로 남아 있을 때만 원래 스케일로 복원한다(사망·리스폰·버프가 바꿨으면 포기).
|
|
// 카메라는 건드리지 않는다(발주서 §1-2 「카메라 미접촉」).
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
using System.Collections;
|
|
using UnityEngine;
|
|
using WL.Combat.Core;
|
|
|
|
namespace WL.Combat.Growth
|
|
{
|
|
public class LevelUpBurstRunner : MonoBehaviour
|
|
{
|
|
/// <summary>반납하지 않은 EffectBudget.Heavy 슬롯 수(러너가 죽을 때 정산한다).</summary>
|
|
int _heldBudget;
|
|
|
|
/// <summary>수명이 끝나면 이펙트를 파괴한다. releaseBudget 이면 EffectBudget.Heavy 를 정확히 1회 반납.</summary>
|
|
public void RunDespawn(GameObject go, float seconds, bool releaseBudget)
|
|
{
|
|
if (go == null)
|
|
{
|
|
if (releaseBudget) EffectBudget.Release(EffectBudgetKind.Heavy);
|
|
return;
|
|
}
|
|
if (releaseBudget) _heldBudget++;
|
|
StartCoroutine(CoDespawn(go, seconds, releaseBudget));
|
|
}
|
|
|
|
IEnumerator CoDespawn(GameObject go, float seconds, bool releaseBudget)
|
|
{
|
|
if (seconds > 0f) yield return new WaitForSecondsRealtime(seconds);
|
|
if (go != null) Object.Destroy(go);
|
|
if (releaseBudget) { _heldBudget--; EffectBudget.Release(EffectBudgetKind.Heavy); }
|
|
}
|
|
|
|
/// <summary>PC 스케일 펀치(0 → 최대 → 0 · unscaled). 되돌리기 안전.</summary>
|
|
public void RunScalePunch(Transform tf, float punch, float duration)
|
|
{
|
|
if (tf == null) return;
|
|
StartCoroutine(CoScalePunch(tf, punch, duration));
|
|
}
|
|
|
|
IEnumerator CoScalePunch(Transform tf, float punch, float duration)
|
|
{
|
|
if (tf == null) yield break;
|
|
|
|
Vector3 baseScale = tf.localScale;
|
|
if (punch <= 1.0001f || baseScale.x <= 0.0001f) yield break;
|
|
|
|
// ProjectileBase.cs 는 transform.localScale.x > 2f 로 **보스를 판정**한다.
|
|
// 펀치가 그 경계를 넘나들면 잠깐 보스 판정이 흔들리므로 아예 건너뛴다(WLHitFeelRunner 와 같은 규칙).
|
|
if (baseScale.x <= 2f && baseScale.x * punch > 2f) yield break;
|
|
|
|
float dur = Mathf.Max(0.01f, duration);
|
|
float t = 0f;
|
|
Vector3 written = baseScale;
|
|
|
|
while (t < dur)
|
|
{
|
|
t += Time.unscaledDeltaTime;
|
|
if (tf == null) yield break;
|
|
// 외부가 스케일을 바꿨다면(사망·리스폰·버프) 우리 것을 덮어쓰지 않는다.
|
|
if ((tf.localScale - written).sqrMagnitude > 1e-6f) yield break;
|
|
|
|
float k = Mathf.Clamp01(t / dur);
|
|
float s = Mathf.Sin(k * Mathf.PI); // 0 → 1 → 0
|
|
written = baseScale * (1f + (punch - 1f) * s);
|
|
tf.localScale = written;
|
|
yield return null;
|
|
}
|
|
|
|
if (tf != null && (tf.localScale - written).sqrMagnitude <= 1e-6f) tf.localScale = baseScale;
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
// 러너가 사라지면 잡고 있던 슬롯이 영구히 남는다 — 반드시 반납한다.
|
|
while (_heldBudget > 0) { _heldBudget--; EffectBudget.Release(EffectBudgetKind.Heavy); }
|
|
}
|
|
}
|
|
}
|