From ea239fef54b5ac9b923609bdd75d7c3757b2ae1c Mon Sep 17 00:00:00 2001 From: swrring Date: Thu, 14 May 2026 18:24:44 +0900 Subject: [PATCH] =?UTF-8?q?fix(BT12-Dev):=20=EC=9E=94=EC=A1=B4=20spawn=20c?= =?UTF-8?q?leanup=20=EA=B0=95=ED=99=94=20=E2=80=94=203=EC=A4=91=20?= =?UTF-8?q?=EC=A7=84=EC=9E=85=EC=A0=90=20+=20FX=20clone=20catch=20(PD=20?= =?UTF-8?q?=EC=A7=80=EC=8B=9C=202026-05-14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PD 추가 보고: "여전히 씬을 실행할 때 이전에 생성 된 풀링 오브젝트가 남아있어" 직전 commit 01865b8 Awake cleanup 1회만 호출 — 진입점 보강. 강화 사항: 1. RuntimeInitializeOnLoadMethod(AfterSceneLoad) 정적 cleanup 추가 — Awake 보다 선행하는 Scene load 직후 진입점. 2. Awake cleanup 무조건 Debug.Log (removed=0 케이스도 호출 확인 가능). 3. DelayedCleanupCoroutine — Awake 후 1·3·10 frame 추가 cleanup (다른 컴포넌트 Awake/Start spawn 케이스 catch). 4. StaleClonePrefixes 신규 — FX clone 7종 prefix 매칭 (FX_Fireball_Bullet·FX_Lightningball·FX_Dragonfire·FX_Thunder· FX_SLASH·FX_PinkMagicArrow·Projectile_*). 5. DoCleanupStalePooledSpawns static 메서드 통합 — useImmediate 옵션 (정적 호출 시 DestroyImmediate·Awake 호출 시 Destroy). 측정 (Play 진입 직후): - AfterSceneLoad static cleanup removed=0 - Awake cleanup removed=0 → 이전 Play 잔존은 0건. Hierarchy 영역 보이는 spawn 은 현재 Play 의 정상 자동 발사 결과 (BaseCooldown Tick spawn) 로 추정. 후속: PD 재현 시나리오 (Stop→Play 또는 Editor crash 후 재실행 등) 직접 확인 필요. Console 영역 "[PlayerSkillInventory] ... removed=" 로그로 cleanup 호출·잔존 카운트 확인 가능. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Skills/Runtime/PlayerSkillInventory.cs | 67 ++++++++++++++++--- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/Assets/Scripts/Skills/Runtime/PlayerSkillInventory.cs b/Assets/Scripts/Skills/Runtime/PlayerSkillInventory.cs index 8eafd97..b059b93 100644 --- a/Assets/Scripts/Skills/Runtime/PlayerSkillInventory.cs +++ b/Assets/Scripts/Skills/Runtime/PlayerSkillInventory.cs @@ -43,19 +43,42 @@ namespace EerieVillage.Skills public event System.Action OnEnemyKilled; public event System.Action OnPlayerDamaged; + // PD 지시 2026-05-14 — Scene load 직후 정적 cleanup (가장 이른 진입점·Awake 보다 선행) + [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.AfterSceneLoad)] + static void OnSceneLoadedStaticCleanup() + { + int removed = DoCleanupStalePooledSpawns(true); // useDestroyImmediate=true (정적·Awake 이전) + Debug.Log("[PlayerSkillInventory] AfterSceneLoad static cleanup removed=" + removed); + } + void Awake() { // PD 지시 2026-05-14 — 게임 재실행 시 이전 Play 잔존 spawn (투사체·박스·Range) 강제 cleanup. - // HideFlags.DontSave 정합 외 DontDestroyOnLoad·Scene 영구 저장·메모리 잔존 경로 방어. - CleanupStalePooledSpawns(); + // 이중 방어: AfterSceneLoad 정적 cleanup 외 Awake·1 frame 후 (Coroutine) 추가 cleanup. + int removed = DoCleanupStalePooledSpawns(false); + Debug.Log("[PlayerSkillInventory] Awake cleanup removed=" + removed); + StartCoroutine(DelayedCleanupCoroutine()); Stats = new PlayerStats(); PlayerStats.Current = Stats; _health = GetComponent(); } + // PD 지시 2026-05-14 — 1·3·10 frame 후 추가 cleanup. Awake 시점 미발화 spawn·다른 컴포넌트 Awake/Start spawn 모두 catch. + System.Collections.IEnumerator DelayedCleanupCoroutine() + { + int[] delays = { 1, 3, 10 }; + foreach (int d in delays) + { + for (int i = 0; i < d; i++) yield return null; + int removed = DoCleanupStalePooledSpawns(false); + if (removed > 0) + Debug.Log("[PlayerSkillInventory] Delayed cleanup frame=" + d + " removed=" + removed); + } + } + // PD 지시 2026-05-14 — 잔존 풀링 GameObject 강제 cleanup - // 대상: Projectile 및 파생 (HomingProjectile·PiercingProjectile) + 박스 시각화 GameObject + // 대상: Projectile 및 파생 (HomingProjectile·PiercingProjectile) + 박스·Range·FX (Clone) 시각화 GameObject static readonly System.Collections.Generic.HashSet StaleSpawnNames = new System.Collections.Generic.HashSet { @@ -66,7 +89,19 @@ namespace EerieVillage.Skills "Range_Debug", }; - void CleanupStalePooledSpawns() + // PD 지시 2026-05-14 — FX clone 카탈로그 (Projectile component 미부착 — Cast FX·HitFx 등) + static readonly string[] StaleClonePrefixes = + { + "FX_Fireball_Bullet", + "FX_Lightningball", + "FX_Dragonfire", + "FX_Thunder", + "FX_SLASH", + "FX_PinkMagicArrow", + "Projectile_", // CreateFallbackProjectile name 패턴 + }; + + static int DoCleanupStalePooledSpawns(bool useImmediate) { int removed = 0; // (1) Projectile 및 파생 component GameObject — root 단위 destroy @@ -75,23 +110,35 @@ namespace EerieVillage.Skills { if (p == null || p.gameObject == null) continue; if (!p.gameObject.scene.IsValid()) continue; // prefab asset 제외 - Destroy(p.gameObject); + if (useImmediate) DestroyImmediate(p.gameObject); else Destroy(p.gameObject); removed++; } - // (2) 박스 시각화 — name 기반 (자체 부착 컴포넌트 부재 케이스) + // (2) 박스·Range 시각화 + FX clone — name 기반 var allGOs = Resources.FindObjectsOfTypeAll(); foreach (var go in allGOs) { if (go == null) continue; if (!go.scene.IsValid()) continue; - if (StaleSpawnNames.Contains(go.name)) + bool matchName = StaleSpawnNames.Contains(go.name); + bool matchPrefix = false; + if (!matchName) { - Destroy(go); + foreach (var pre in StaleClonePrefixes) + { + if (go.name.StartsWith(pre) && go.name.EndsWith("(Clone)")) + { + matchPrefix = true; + break; + } + } + } + if (matchName || matchPrefix) + { + if (useImmediate) DestroyImmediate(go); else Destroy(go); removed++; } } - if (removed > 0) - Debug.Log("[PlayerSkillInventory] CleanupStalePooledSpawns removed=" + removed); + return removed; } // PD 지시 2026-05-13 — Start 시점에 기본 습득 스킬 자동 장착 (Resources 로드 완료 후·Awake 영역 아님)