fix(BT12-Dev): 잔존 cleanup Scene root 재귀 + Projectile.Awake 자기 destroy (PD 지시 2026-05-14)

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) <noreply@anthropic.com>
This commit is contained in:
깃 관리자 2026-05-14 21:11:38 +09:00
parent ea239fef54
commit 7c894c60da
2 changed files with 65 additions and 29 deletions

View File

@ -36,6 +36,27 @@ namespace EerieVillage.Skills.Effectors
protected float[] _baseAlphas; protected float[] _baseAlphas;
const float FADE_START_RATIO = 0.85f; 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);
}
}
/// <summary> /// <summary>
/// ProjectileSpawner.Trigger 에서 Instantiate 직후 호출. /// ProjectileSpawner.Trigger 에서 Instantiate 직후 호출.
/// </summary> /// </summary>

View File

@ -104,43 +104,58 @@ namespace EerieVillage.Skills
static int DoCleanupStalePooledSpawns(bool useImmediate) static int DoCleanupStalePooledSpawns(bool useImmediate)
{ {
int removed = 0; int removed = 0;
// (1) Projectile 및 파생 component GameObject — root 단위 destroy // PD 지시 2026-05-14 — Scene root 재귀 탐색 (Resources.FindObjectsOfTypeAll 영역 hideFlags hidden GO 누락 우려·정확도 ↑)
var projs = Resources.FindObjectsOfTypeAll<EerieVillage.Skills.Effectors.Projectile>(); var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
foreach (var p in projs) if (!scene.IsValid()) return 0;
var roots = scene.GetRootGameObjects();
// 재귀 cleanup 대상 수집 (직접 destroy 시 enumeration 중 collection 변경 회피)
var targets = new System.Collections.Generic.List<GameObject>();
foreach (var root in roots)
{ {
if (p == null || p.gameObject == null) continue; CollectStaleRecursive(root, targets);
if (!p.gameObject.scene.IsValid()) continue; // prefab asset 제외
if (useImmediate) DestroyImmediate(p.gameObject); else Destroy(p.gameObject);
removed++;
} }
// (2) 박스·Range 시각화 + FX clone — name 기반 foreach (var go in targets)
var allGOs = Resources.FindObjectsOfTypeAll<GameObject>();
foreach (var go in allGOs)
{ {
if (go == null) continue; 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); if (useImmediate) DestroyImmediate(go); else Destroy(go);
removed++; removed++;
} }
}
return removed; return removed;
} }
static void CollectStaleRecursive(GameObject root, System.Collections.Generic.List<GameObject> targets)
{
if (root == null) return;
// Projectile 및 파생 component 부착 → 이 root 단위 destroy
if (root.GetComponent<EerieVillage.Skills.Effectors.Projectile>() != 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 영역 아님) // PD 지시 2026-05-13 — Start 시점에 기본 습득 스킬 자동 장착 (Resources 로드 완료 후·Awake 영역 아님)
void Start() void Start()
{ {