174 lines
9.7 KiB
C#
174 lines
9.7 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// 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;
|
||
}
|
||
}
|
||
}
|