// ───────────────────────────────────────────────────────────────────────────── // SkillSpectacleRunner.cs — 스킬 스펙터클 전용 틱 러너 + 풀 루트(숨은 오브젝트 1개 · Play 는 DontDestroyOnLoad · 에디트 모드는 DontSave) // // 발주서 WL-811i · 새 Manager/Singleton 을 만들지 않는다(WLReactionRunner · LootBurstRunner 선례 · 발주서 밖 파일 무수정 원칙으로 별도 러너). // Pool(비활성) 아래에서 Instantiate 해 Awake/OnEnable 없이 캐시한 뒤 Live(활성) 로 옮긴다. 씬 전환 · 비활성 · 앱 종료 시 활성 이펙트를 끄고 예산을 돌려준다. // ───────────────────────────────────────────────────────────────────────────── using UnityEngine; using UnityEngine.SceneManagement; namespace WL.Combat.Reaction { public sealed class SkillSpectacleRunner : MonoBehaviour { static SkillSpectacleRunner s_runner; static bool s_sceneHooked; /// 비활성 부모(여기서 Instantiate → Awake 안 돎). public Transform PoolRoot; /// 활성 부모(재생 중 인스턴스). public Transform LiveRoot; public static bool Exists { get { return s_runner != null; } } public static SkillSpectacleRunner Ensure() { if (s_runner != null) return s_runner; bool playing = Application.isPlaying; var go = new GameObject("__WLSkillSpectacle"); if (playing) DontDestroyOnLoad(go); else go.hideFlags = HideFlags.DontSave; s_runner = go.AddComponent(); var pool = new GameObject("Pool"); pool.transform.SetParent(go.transform, false); if (!playing) pool.hideFlags = HideFlags.DontSave; pool.SetActive(false); s_runner.PoolRoot = pool.transform; var live = new GameObject("Live"); live.transform.SetParent(go.transform, false); if (!playing) live.hideFlags = HideFlags.DontSave; s_runner.LiveRoot = live.transform; if (!s_sceneHooked) { SceneManager.activeSceneChanged += OnSceneChanged; s_sceneHooked = true; } return s_runner; } static void OnSceneChanged(Scene from, Scene to) { SkillSpectacle.ClearAll(); } void Update() { SkillSpectacle.Tick(Time.time); } void OnDisable() { SkillSpectacle.ClearAll(); } void OnApplicationQuit() { SkillSpectacle.ClearAll(); } } }