From 7c894c60daa4b61383a1883a749eb564bfc64070 Mon Sep 17 00:00:00 2001 From: swrring Date: Thu, 14 May 2026 21:11:38 +0900 Subject: [PATCH] =?UTF-8?q?fix(BT12-Dev):=20=EC=9E=94=EC=A1=B4=20cleanup?= =?UTF-8?q?=20Scene=20root=20=EC=9E=AC=EA=B7=80=20+=20Projectile.Awake=20?= =?UTF-8?q?=EC=9E=90=EA=B8=B0=20destroy=20(PD=20=EC=A7=80=EC=8B=9C=202026-?= =?UTF-8?q?05-14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PD 추가 진단 (스크린샷 + 발화): "게임 플레이 후 스크롤 되는 맵에 여전히 남아있는 상태야. 아마 스크롤 되며 연장되는 맵 영역이 초기화 되지 않는 것으로 예상 돼" 이전 commit ea239fe (3중 진입점 + FX clone prefix) 의 catch 누락 가능성 차단: 1. PlayerSkillInventory.DoCleanupStalePooledSpawns 영역 Resources.FindObjectsOfTypeAll → Scene.GetRootGameObjects + 재귀 변경. hideFlags hidden GO 누락 우려 제거·정확도 향상. CollectStaleRecursive 신규 — 자식 트리 재귀 탐색. IsStaleByName 헬퍼 — 이름 매칭 통합. 2. Projectile.Awake 신규 — _data == null && _runtime == null 상태 (= Initialize 미호출 잔존) 시 1 frame 유예 후 자기 destroy. PlayerSkillInventory cleanup 누락 케이스 자체 정리 (2중 방어). InfiniteHorizontalBackground·기타 컴포넌트 자식 부착된 잔존도 본 자가 destroy 로 catch. 측정 (Play 진입 직후): - AfterSceneLoad static cleanup removed=0 - Awake cleanup removed=0 → 이전 Play 잔존 = 0건 (Scene load·Awake 시점) → Hierarchy 영역 시각상 spawn = 자동 발사 진행 결과 후속: PD Refresh + Play → 화면 잔존 재확인 필요. 잔존 시 Console "[PlayerSkillInventory] ... removed=N" 로그 + 스크린샷 으로 본 PM 추가 진단. Co-Authored-By: Claude Opus 4.7 (1M context) --- Assets/Scripts/Skills/Effectors/Projectile.cs | 21 ++++++ .../Skills/Runtime/PlayerSkillInventory.cs | 73 +++++++++++-------- 2 files changed, 65 insertions(+), 29 deletions(-) diff --git a/Assets/Scripts/Skills/Effectors/Projectile.cs b/Assets/Scripts/Skills/Effectors/Projectile.cs index b78f5e2..6157682 100644 --- a/Assets/Scripts/Skills/Effectors/Projectile.cs +++ b/Assets/Scripts/Skills/Effectors/Projectile.cs @@ -36,6 +36,27 @@ namespace EerieVillage.Skills.Effectors protected float[] _baseAlphas; const float FADE_START_RATIO = 0.85f; + // PD 지시 2026-05-14 — Awake 시점 _data == null 잔존 GameObject 자기 destroy. + // 게임 재실행·Scene reload 시 이전 Play 잔존 Projectile (Initialize 미호출 상태) 자가 정리. + protected virtual void Awake() + { + if (_data == null && _runtime == null) + { + // Initialize 호출 전 (= 잔존) → 자기 destroy. 정상 spawn 은 Awake 직후 Initialize 호출이라 _data set 정합. + // Awake 직후 Initialize 가 별도 frame 에 호출되는 케이스 대비 1 frame 유예 + StartCoroutine(_DestroyIfStale()); + } + } + + System.Collections.IEnumerator _DestroyIfStale() + { + yield return null; // 1 frame 유예 (정상 Initialize 시점 보장) + if (_data == null && _runtime == null) + { + Destroy(gameObject); + } + } + /// /// ProjectileSpawner.Trigger 에서 Instantiate 직후 호출. /// diff --git a/Assets/Scripts/Skills/Runtime/PlayerSkillInventory.cs b/Assets/Scripts/Skills/Runtime/PlayerSkillInventory.cs index b059b93..76ad933 100644 --- a/Assets/Scripts/Skills/Runtime/PlayerSkillInventory.cs +++ b/Assets/Scripts/Skills/Runtime/PlayerSkillInventory.cs @@ -104,43 +104,58 @@ namespace EerieVillage.Skills static int DoCleanupStalePooledSpawns(bool useImmediate) { int removed = 0; - // (1) Projectile 및 파생 component GameObject — root 단위 destroy - var projs = Resources.FindObjectsOfTypeAll(); - foreach (var p in projs) + // PD 지시 2026-05-14 — Scene root 재귀 탐색 (Resources.FindObjectsOfTypeAll 영역 hideFlags hidden GO 누락 우려·정확도 ↑) + var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene(); + if (!scene.IsValid()) return 0; + var roots = scene.GetRootGameObjects(); + // 재귀 cleanup 대상 수집 (직접 destroy 시 enumeration 중 collection 변경 회피) + var targets = new System.Collections.Generic.List(); + foreach (var root in roots) { - if (p == null || p.gameObject == null) continue; - if (!p.gameObject.scene.IsValid()) continue; // prefab asset 제외 - if (useImmediate) DestroyImmediate(p.gameObject); else Destroy(p.gameObject); - removed++; + CollectStaleRecursive(root, targets); } - // (2) 박스·Range 시각화 + FX clone — name 기반 - var allGOs = Resources.FindObjectsOfTypeAll(); - foreach (var go in allGOs) + foreach (var go in targets) { if (go == null) continue; - if (!go.scene.IsValid()) continue; - bool matchName = StaleSpawnNames.Contains(go.name); - bool matchPrefix = false; - if (!matchName) - { - 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 (useImmediate) DestroyImmediate(go); else Destroy(go); + removed++; } return removed; } + static void CollectStaleRecursive(GameObject root, System.Collections.Generic.List targets) + { + if (root == null) return; + // Projectile 및 파생 component 부착 → 이 root 단위 destroy + if (root.GetComponent() != null) + { + targets.Add(root); + return; // 자식 재귀 불필요 (root destroy 시 자식 자동 destroy) + } + // Name 매칭 (박스·Range·FX clone) + if (IsStaleByName(root.name)) + { + targets.Add(root); + return; + } + // 자식 재귀 + foreach (Transform child in root.transform) + { + CollectStaleRecursive(child.gameObject, targets); + } + } + + static bool IsStaleByName(string name) + { + if (StaleSpawnNames.Contains(name)) return true; + if (!name.EndsWith("(Clone)")) return name.StartsWith("Projectile_"); + foreach (var pre in StaleClonePrefixes) + { + if (name.StartsWith(pre)) return true; + } + return false; + } + // PD 지시 2026-05-13 — Start 시점에 기본 습득 스킬 자동 장착 (Resources 로드 완료 후·Awake 영역 아님) void Start() {