// ───────────────────────────────────────────────────────────────────────────── // TelegraphShape.cs — 예고 판의 크기를 「투사체 프리팹의 BoxCollider」에서 읽어 온다(신규 데이터 0) // // PD 지시 #815-5 · 발주서 WL-815f §1-2 · 기준서 v1 §G-0 「공격 판정의 출처」 // // ■ 왜 콜라이더를 읽나 // 원본은 근접도 투사체를 쏜다(MobActor.Projectile → Shoot_Projectile(s_Porjectile1, …)). // 따라서 **맞는 범위 = 그 투사체 프리팹의 BoxCollider** 다. 데칼 크기를 코드 상수로 넣으면 판정과 어긋난다. // 실측(기준서 §G-0): Mob/P_*_Meele 3×3×3 center 0 · Elite/P_Golem_A 3×3×3 center z 1.5 · FieldBoss/Anubis_Attack 2×2×4 center z 2. // // ■ 어떻게 읽나 (이름 1개당 1회 · 결과는 캐시 · 정상 흐름 GC 0) // ① 에디터(에디트 모드·플레이 모드 공통) = AssetDatabase 로 **동기** 로드 → 프로브가 Play 없이 실측할 수 있다. // ② 빌드/플레이 = ProjectileInfo 풀(원본이 미리 로드해 둔 인스턴스)을 이름으로 훑어 콜라이더를 읽는다. // ③ 그래도 없으면 Addressables 비동기 로드를 1회 걸고(중복 방지), 그 사이에는 SO 기본값을 쓴다. // // ■ 원거리 판(기준서 §G-6 「직선 데칼」) // 길이 = 투사체 속도 × 수명. 속도는 프리팹의 ProjectileBase.Speed(= 기본 5) 이고 // Projectile_Strait.m_Speed 가 0 보다 크면 그 값이 이긴다(원본 Projectile_Strait.Set 실측). // 수명은 몹 테이블의 f_ProjectileLifeTime1(근접 0.1 · 원거리 1.5 실측). // // 🔴 원본 무수정 — 읽기만 한다. // ───────────────────────────────────────────────────────────────────────────── using System.Collections.Generic; using UnityEngine; namespace WL.Combat.Telegraph { /// 투사체 1종의 판정 상자(로컬 기준) + 이동 정보. public struct TelegraphBox { public Vector3 size; // BoxCollider.size × 프리팹 lossyScale public Vector3 center; // BoxCollider.center × 프리팹 lossyScale public float speed; // 실효 이동 속도(m/s · 0 이면 제자리) public bool resolved; // 진짜 프리팹에서 읽었는가(false = SO 기본값) public string source; // 진단 문자열(어디서 읽었나) } public static class TelegraphShape { const string kPrefix = "Assets/Res_Addr/Projectile/"; const string kSuffix = ".prefab"; static readonly Dictionary s_cache = new Dictionary(32); static readonly HashSet s_inFlight = new HashSet(); // ── 진단(프로브가 읽는다) public static int ResolvedCount, FallbackCount, PoolHitCount, AssetDbHitCount, AddressablesRequestCount; public static string LastSource = ""; public static int CacheCount { get { return s_cache.Count; } } static WLTelegraphSettings St { get { return WLTelegraphSettings.Instance; } } /// 런타임 ProjectileData.Speed 의 기본값(원본 ProjectileBase.cs:19 실측 = 5). SO 로 바꿀 수 있다. static float DefaultSpeed { get { var st = St; return st != null && st.defaultProjectileSpeed > 0f ? st.defaultProjectileSpeed : 5f; } } /// 이 투사체 이름의 판정 상자. 아직 못 읽었으면 SO 기본값(resolved = false)을 돌려주고 비동기 로드를 1회 건다. public static TelegraphBox Get(string projectileName) { if (string.IsNullOrEmpty(projectileName) || projectileName == "None") return Fallback("(투사체 없음)"); TelegraphBox box; if (s_cache.TryGetValue(projectileName, out box)) return box; // ① 에디터 — 동기 로드(프로브가 Play 없이 실측한다) #if UNITY_EDITOR var asset = UnityEditor.AssetDatabase.LoadAssetAtPath(kPrefix + projectileName + kSuffix); if (asset != null && ReadFrom(asset.transform, "AssetDatabase", out box)) { s_cache[projectileName] = box; ResolvedCount++; AssetDbHitCount++; LastSource = box.source; return box; } #endif // ② 원본 풀(ProjectileInfo 가 table_effectlist 로 미리 로드해 둔 인스턴스) if (ProjectileInfo.isIns && ProjectileInfo.Ins != null) { var root = ProjectileInfo.Ins.transform; string baseName = BaseName(projectileName); for (int i = 0; i < root.childCount; i++) { var c = root.GetChild(i); if (!NameMatches(c.name, baseName)) continue; if (ReadFrom(c, "ProjectileInfo 풀", out box)) { s_cache[projectileName] = box; ResolvedCount++; PoolHitCount++; LastSource = box.source; return box; } } } // ③ Addressables 비동기 로드 1회 — 이번 예고는 기본값으로 그리고 다음 예고부터 정확해진다 if (!s_inFlight.Contains(projectileName) && AddrResourceMgr.isIns && AddrResourceMgr.Ins != null) { s_inFlight.Add(projectileName); AddressablesRequestCount++; string key = projectileName; AddrResourceMgr.Ins.LoadObject(kPrefix + key + kSuffix, handle => { s_inFlight.Remove(key); var go = handle.Result; TelegraphBox b; if (go != null && ReadFrom(go.transform, "Addressables", out b)) { s_cache[key] = b; ResolvedCount++; } }); } return Fallback(projectileName); } /// 프로브/테스트용 — 실측값을 직접 주입한다(로드 경로 없이 크기 일치를 검증할 때). public static void Prime(string projectileName, Vector3 size, Vector3 center, float speed, string source) { if (string.IsNullOrEmpty(projectileName)) return; s_cache[projectileName] = new TelegraphBox { size = size, center = center, speed = speed, resolved = true, source = source }; } public static void ClearCache() { s_cache.Clear(); s_inFlight.Clear(); } public static void ResetDiagnostics() { ResolvedCount = FallbackCount = PoolHitCount = AssetDbHitCount = AddressablesRequestCount = 0; LastSource = ""; } // ───────────────────────────────────────── 내부 static TelegraphBox Fallback(string why) { FallbackCount++; var st = St; var size = st != null ? st.defaultBoxSize : new Vector3(3f, 3f, 3f); return new TelegraphBox { size = size, center = Vector3.zero, speed = 0f, resolved = false, source = "기본값 " + why }; } static string BaseName(string projectileName) { int slash = projectileName.LastIndexOf('/'); return slash >= 0 ? projectileName.Substring(slash + 1) : projectileName; } /// 풀 인스턴스 이름은 "P_Batty_Meele" 또는 "P_Batty_Meele(Clone)" 이다. 접두 비교(부분 문자열 생성 0). static bool NameMatches(string instanceName, string baseName) { if (instanceName == null || instanceName.Length < baseName.Length) return false; return string.CompareOrdinal(instanceName, 0, baseName, 0, baseName.Length) == 0; } static bool ReadFrom(Transform root, string source, out TelegraphBox box) { box = default(TelegraphBox); if (root == null) return false; var bc = root.GetComponent(); if (bc == null) bc = root.GetComponentInChildren(true); if (bc == null) return false; // 프리팹 자체 스케일까지 반영한다(실측 = 전부 1 이지만 값이 바뀌어도 따라간다) var ls = bc.transform.lossyScale; var size = new Vector3(Mathf.Abs(bc.size.x * ls.x), Mathf.Abs(bc.size.y * ls.y), Mathf.Abs(bc.size.z * ls.z)); var center = new Vector3(bc.center.x * ls.x, bc.center.y * ls.y, bc.center.z * ls.z); // 🔴 속도 실측 — 프리팹에는 Speed 필드가 없다(ProjectileBase 는 ColliderLayer/m_Pierce/str_HitEffect 만 직렬화). // 실제 속도는 런타임 ProjectileData.Speed(기본 5)이고, Projectile_Strait.m_Speed 가 0 보다 크면 그 값이 이긴다 // (Projectile_Strait.Set: `if (m_Speed > 0f) m_ProjecTileData.Speed = m_Speed;`). // ProjectileInfo.Shoot_Projectile 은 Speed 를 건드리지 않으므로 m_Speed = 0 인 프리팹은 기본값 5 로 난다. float speed = 0f; var strait = root.GetComponent(); if (strait == null) strait = root.GetComponentInChildren(true); if (strait != null) speed = strait.m_Speed > 0f ? strait.m_Speed : DefaultSpeed; box = new TelegraphBox { size = size, center = center, speed = speed, resolved = true, source = source }; return true; } } }