Project_WL/Assets/WL/Combat/Telegraph/TelegraphShape.cs

174 lines
9.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ─────────────────────────────────────────────────────────────────────────────
// 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
{
/// <summary>투사체 1종의 판정 상자(로컬 기준) + 이동 정보.</summary>
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<string, TelegraphBox> s_cache = new Dictionary<string, TelegraphBox>(32);
static readonly HashSet<string> s_inFlight = new HashSet<string>();
// ── 진단(프로브가 읽는다)
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; } }
/// <summary>런타임 ProjectileData.Speed 의 기본값(원본 ProjectileBase.cs:19 실측 = 5). SO 로 바꿀 수 있다.</summary>
static float DefaultSpeed { get { var st = St; return st != null && st.defaultProjectileSpeed > 0f ? st.defaultProjectileSpeed : 5f; } }
/// <summary>이 투사체 이름의 판정 상자. 아직 못 읽었으면 SO 기본값(resolved = false)을 돌려주고 비동기 로드를 1회 건다.</summary>
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<GameObject>(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<GameObject>(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);
}
/// <summary>프로브/테스트용 — 실측값을 직접 주입한다(로드 경로 없이 크기 일치를 검증할 때).</summary>
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;
}
/// <summary>풀 인스턴스 이름은 "P_Batty_Meele" 또는 "P_Batty_Meele(Clone)" 이다. 접두 비교(부분 문자열 생성 0).</summary>
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<BoxCollider>();
if (bc == null) bc = root.GetComponentInChildren<BoxCollider>(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<Projectile_Strait>();
if (strait == null) strait = root.GetComponentInChildren<Projectile_Strait>(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;
}
}
}