668 lines
40 KiB
C#
668 lines
40 KiB
C#
|
|
// PD 지시 #813 · 발주서 WL-811s — 이펙트 크기 1차 조정 검증 프로브 (에디트 모드 전용 · Play 0 · 로그인 0 · 에셋 무수정)
|
|||
|
|
//
|
|||
|
|
// 실행 (CLAUDE.md §1: using 금지 · 네임스페이스 풀어 쓰기)
|
|||
|
|
// unity command run_script --file AgentScripts/WL811s_Probe.cs --entry WL811s_Probe.Measure --timeout 1800
|
|||
|
|
// unity command run_script --file AgentScripts/WL811s_Probe.cs --entry WL811s_Probe.Verify --timeout 1800
|
|||
|
|
//
|
|||
|
|
// Measure — 값을 정하기 위한 실측(코드 수정 전)
|
|||
|
|
// A. 액터 높이 : PC 프리팹 렌더러 바운드 × ClassConfig.f_Scale · 존 몹 × MonsterList.f_DefaultScale ·
|
|||
|
|
// 엘리트 × WLEliteSettings.scaleMultiplier · 보스 Anubis
|
|||
|
|
// B. 이펙트 원 크기 : 프리팹 인스턴스화 → ParticleSystem.Simulate(t, withChildren, restart) 를 t 스윕하며
|
|||
|
|
// 모든 렌더러 월드 바운드 합집합의 최대 수평 지름/높이 (= 프리팹 자체 localScale 포함한 「원 크기」)
|
|||
|
|
// C. 화면 점유 : Main Camera 프리팹 fov + RealCamera 궤도식(Quaternion.Euler(h,yaw,pan) * Vector3.one)에
|
|||
|
|
// 811o 오버라이드(h 76 · d 3 · up 2.4)를 넣어 카메라를 세우고 1080×1920 뷰포트에서 세로 점유율
|
|||
|
|
//
|
|||
|
|
// Verify — 코드/값을 넣은 뒤의 검증
|
|||
|
|
// D. 16행 전/후 표 · 슬롯 4 화면 점유 ≤ 50 %
|
|||
|
|
// E. EffectScaleGuard : 가짜 풀 루트에 진짜 이펙트 인스턴스를 넣고 활성/비활성 → 배율 적용·원복·멱등 · 규칙 매칭
|
|||
|
|
// F. GC 0 · C8(enabled=0 → 무동작 · 스케일 원본)
|
|||
|
|
//
|
|||
|
|
// 에디트 모드 주의: 프로브가 만드는 오브젝트는 전부 HideAndDontSave + DestroyImmediate · 어떤 에셋도 쓰지 않는다.
|
|||
|
|
public static class WL811s_Probe
|
|||
|
|
{
|
|||
|
|
const string kOutDir = "AgentScripts/staging/WL811s";
|
|||
|
|
const string kMeasureOut = "AgentScripts/staging/WL811s/MEASURE.txt";
|
|||
|
|
const string kVerifyOut = "AgentScripts/staging/WL811s/VERIFY.txt";
|
|||
|
|
const string kPcPrefab = "Assets/Res_Addr/PC/Ai01.prefab";
|
|||
|
|
const string kCamPrefab = "Assets/ResWork/Prefabs/Ingame/Main Camera.prefab";
|
|||
|
|
const string kEffectDir = "Assets/Res_Addr/Effect/";
|
|||
|
|
|
|||
|
|
static System.Text.StringBuilder s_sb = new System.Text.StringBuilder(1 << 16);
|
|||
|
|
static void L(string s) { s_sb.Append(s).Append('\n'); }
|
|||
|
|
|
|||
|
|
static System.Collections.Generic.List<string> s_err = new System.Collections.Generic.List<string>();
|
|||
|
|
static bool s_hooked;
|
|||
|
|
static void OnLog(string cond, string stack, UnityEngine.LogType t)
|
|||
|
|
{
|
|||
|
|
if (t == UnityEngine.LogType.Error || t == UnityEngine.LogType.Exception || t == UnityEngine.LogType.Assert)
|
|||
|
|
s_err.Add(t + " | " + cond);
|
|||
|
|
}
|
|||
|
|
static void Hook() { if (!s_hooked) { UnityEngine.Application.logMessageReceived += OnLog; s_hooked = true; } }
|
|||
|
|
static void Unhook() { if (s_hooked) { UnityEngine.Application.logMessageReceived -= OnLog; s_hooked = false; } }
|
|||
|
|
|
|||
|
|
static void Save(string path)
|
|||
|
|
{
|
|||
|
|
System.IO.Directory.CreateDirectory(kOutDir);
|
|||
|
|
System.IO.File.WriteAllText(path, s_sb.ToString(), new System.Text.UTF8Encoding(false));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────── 공용 측정
|
|||
|
|
|
|||
|
|
/// <summary>프리팹을 숨은 인스턴스로 띄운다(원 localScale 유지 · 원점 · 무회전).</summary>
|
|||
|
|
static UnityEngine.GameObject Spawn(string path)
|
|||
|
|
{
|
|||
|
|
var pf = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(path);
|
|||
|
|
if (pf == null) return null;
|
|||
|
|
var go = UnityEngine.Object.Instantiate(pf);
|
|||
|
|
go.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
|
|||
|
|
go.transform.position = UnityEngine.Vector3.zero;
|
|||
|
|
go.transform.rotation = UnityEngine.Quaternion.identity;
|
|||
|
|
go.transform.localScale = pf.transform.localScale;
|
|||
|
|
return go;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>활성 여부와 무관하게 모든 렌더러의 월드 바운드 합집합. 없으면 false.</summary>
|
|||
|
|
static bool RendererBounds(UnityEngine.GameObject go, out UnityEngine.Bounds b, out int count)
|
|||
|
|
{
|
|||
|
|
b = new UnityEngine.Bounds();
|
|||
|
|
count = 0;
|
|||
|
|
var rs = go.GetComponentsInChildren<UnityEngine.Renderer>(true);
|
|||
|
|
for (int i = 0; i < rs.Length; i++)
|
|||
|
|
{
|
|||
|
|
var r = rs[i];
|
|||
|
|
if (r == null) continue;
|
|||
|
|
var rb = r.bounds;
|
|||
|
|
if (rb.size.sqrMagnitude <= 0.0000001f) continue;
|
|||
|
|
if (count == 0) b = rb; else b.Encapsulate(rb);
|
|||
|
|
count++;
|
|||
|
|
}
|
|||
|
|
return count > 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static UnityEngine.ParticleSystem.Particle[] s_buf = new UnityEngine.ParticleSystem.Particle[8192];
|
|||
|
|
static System.Collections.Generic.List<float> s_radii = new System.Collections.Generic.List<float>(1 << 14);
|
|||
|
|
|
|||
|
|
/// <summary>파티클 실좌표(GetParticles)로 원점 기준 반경 분포를 만든다. 렌더러 바운드(=Unity 보수 컬링 바운드)는 쓰지 않는다.
|
|||
|
|
/// diaMax = 전 파티클 최대 지름 · diaP90 = 90 백분위 지름(스트레이 1개에 흔들리지 않는 「덩치」) · hgt = y 폭.</summary>
|
|||
|
|
static void MeasureEffect(string prefabName, out float diaMax, out float diaP90, out float hgt, out float baseScale, out int psCount, out int pCount, out string note)
|
|||
|
|
{
|
|||
|
|
diaMax = 0f; diaP90 = 0f; hgt = 0f; baseScale = 1f; psCount = 0; pCount = 0; note = "";
|
|||
|
|
var go = Spawn(kEffectDir + prefabName + ".prefab");
|
|||
|
|
if (go == null) { note = "noprefab"; return; }
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
baseScale = go.transform.localScale.x;
|
|||
|
|
var pss = go.GetComponentsInChildren<UnityEngine.ParticleSystem>(true);
|
|||
|
|
psCount = pss.Length;
|
|||
|
|
// 🔴 재현성: 자동 랜덤 시드를 끄고 고정 시드를 준다(안 하면 같은 프리팹이 실행마다 ±16 % 다르게 나온다).
|
|||
|
|
for (int i = 0; i < pss.Length; i++)
|
|||
|
|
{
|
|||
|
|
var ps = pss[i]; if (ps == null) continue;
|
|||
|
|
try { ps.useAutoRandomSeed = false; ps.randomSeed = 8115u; } catch (System.Exception) { }
|
|||
|
|
}
|
|||
|
|
go.SetActive(true);
|
|||
|
|
s_radii.Clear();
|
|||
|
|
float yMin = 1e9f, yMax = -1e9f;
|
|||
|
|
|
|||
|
|
// 파티클이 아닌 메시(예: Effect_WLSwingArc 의 슬래시 메시)는 실 바운드가 정확하다
|
|||
|
|
var mrs = go.GetComponentsInChildren<UnityEngine.MeshRenderer>(true);
|
|||
|
|
for (int i = 0; i < mrs.Length; i++)
|
|||
|
|
{
|
|||
|
|
var mr = mrs[i];
|
|||
|
|
if (mr == null || mr.GetComponent<UnityEngine.ParticleSystem>() != null) continue;
|
|||
|
|
var mb = mr.bounds;
|
|||
|
|
if (mb.size.sqrMagnitude <= 0.0000001f) continue;
|
|||
|
|
float rr = new UnityEngine.Vector2(UnityEngine.Mathf.Abs(mb.center.x) + mb.extents.x, UnityEngine.Mathf.Abs(mb.center.z) + mb.extents.z).magnitude;
|
|||
|
|
s_radii.Add(rr);
|
|||
|
|
if (mb.min.y < yMin) yMin = mb.min.y;
|
|||
|
|
if (mb.max.y > yMax) yMax = mb.max.y;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (pss.Length > 0)
|
|||
|
|
{
|
|||
|
|
float[] ts = new float[] { 0.05f, 0.1f, 0.15f, 0.2f, 0.3f, 0.4f, 0.5f, 0.7f, 1.0f, 1.3f, 1.6f, 2.0f };
|
|||
|
|
for (int k = 0; k < ts.Length; k++)
|
|||
|
|
{
|
|||
|
|
for (int i = 0; i < pss.Length; i++)
|
|||
|
|
{
|
|||
|
|
var ps = pss[i];
|
|||
|
|
if (ps == null) continue;
|
|||
|
|
if (ps.transform.parent != null && ps.transform.parent.GetComponentInParent<UnityEngine.ParticleSystem>() != null) continue;
|
|||
|
|
try { ps.Simulate(ts[k], true, true, false); } catch (System.Exception) { }
|
|||
|
|
}
|
|||
|
|
for (int i = 0; i < pss.Length; i++)
|
|||
|
|
{
|
|||
|
|
var ps = pss[i];
|
|||
|
|
if (ps == null) continue;
|
|||
|
|
int n = 0;
|
|||
|
|
try { n = ps.GetParticles(s_buf); } catch (System.Exception) { continue; }
|
|||
|
|
if (n <= 0) continue;
|
|||
|
|
if (n > pCount) pCount = n;
|
|||
|
|
bool local = ps.main.simulationSpace == UnityEngine.ParticleSystemSimulationSpace.Local;
|
|||
|
|
float ls = UnityEngine.Mathf.Abs(ps.transform.lossyScale.x); if (ls <= 0f) ls = 1f;
|
|||
|
|
for (int q = 0; q < n; q++)
|
|||
|
|
{
|
|||
|
|
var p = s_buf[q];
|
|||
|
|
var pos = local ? ps.transform.TransformPoint(p.position) : p.position;
|
|||
|
|
float sz = p.GetCurrentSize(ps) * (local ? ls : 1f);
|
|||
|
|
float rr = new UnityEngine.Vector2(pos.x, pos.z).magnitude + sz * 0.5f;
|
|||
|
|
s_radii.Add(rr);
|
|||
|
|
if (pos.y - sz * 0.5f < yMin) yMin = pos.y - sz * 0.5f;
|
|||
|
|
if (pos.y + sz * 0.5f > yMax) yMax = pos.y + sz * 0.5f;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
for (int i = 0; i < pss.Length; i++) { var ps = pss[i]; if (ps != null) { try { ps.Stop(true, UnityEngine.ParticleSystemStopBehavior.StopEmittingAndClear); } catch (System.Exception) { } } }
|
|||
|
|
}
|
|||
|
|
else note = "nops";
|
|||
|
|
|
|||
|
|
if (s_radii.Count == 0) { note = (note.Length > 0 ? note + "," : "") + "noparticle"; return; }
|
|||
|
|
s_radii.Sort();
|
|||
|
|
diaMax = s_radii[s_radii.Count - 1] * 2f;
|
|||
|
|
int idx = UnityEngine.Mathf.Clamp(UnityEngine.Mathf.RoundToInt((s_radii.Count - 1) * 0.90f), 0, s_radii.Count - 1);
|
|||
|
|
diaP90 = s_radii[idx] * 2f;
|
|||
|
|
hgt = (yMax > yMin) ? (yMax - yMin) : 0f;
|
|||
|
|
}
|
|||
|
|
finally { UnityEngine.Object.DestroyImmediate(go); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────── A. 액터 높이
|
|||
|
|
|
|||
|
|
class ActorRow { public string kind, name, path; public float tableScale; public float rawH, rawD; public bool ok; }
|
|||
|
|
|
|||
|
|
static System.Collections.Generic.List<ActorRow> MeasureActors()
|
|||
|
|
{
|
|||
|
|
var list = new System.Collections.Generic.List<ActorRow>();
|
|||
|
|
// PC (ClassConfig.f_Scale = 0.7 · 4 근접 클래스 공통 · 프리팹은 코스튬 테이블이 고른다 → 기본 Ai01)
|
|||
|
|
list.Add(new ActorRow { kind = "PC", name = "Ai01 (10101/10102/10501/10502)", path = kPcPrefab, tableScale = 0.7f });
|
|||
|
|
list.Add(new ActorRow { kind = "PC", name = "Ai02", path = "Assets/Res_Addr/PC/Ai02.prefab", tableScale = 0.7f });
|
|||
|
|
list.Add(new ActorRow { kind = "PC", name = "Eri01", path = "Assets/Res_Addr/PC/Eri01.prefab", tableScale = 0.7f });
|
|||
|
|
// 존 몹 (813e 813001~813004)
|
|||
|
|
AddMob(list, "존1", "Mob/Batty_A", 1.5f); AddMob(list, "존1", "Mob/Batty_D", 1.5f);
|
|||
|
|
AddMob(list, "존2", "Mob/Porin_A", 1.3f); AddMob(list, "존2", "Mob/Porin_E", 1.3f);
|
|||
|
|
AddMob(list, "존3", "Mob/Racco_A", 1.1f); AddMob(list, "존3", "Mob/Rabby_Brown", 1.5f);
|
|||
|
|
AddMob(list, "존4", "Mob/Devilu_A", 1.2f); AddMob(list, "존4", "Mob/Devilu_C", 1.2f);
|
|||
|
|
// 엘리트 (813s · × scaleMultiplier)
|
|||
|
|
AddMob(list, "엘리트1", "Elite/Aspethis_D", 2.3f); AddMob(list, "엘리트2", "Elite/Cute_Crab_C_Blue", 2.5f);
|
|||
|
|
AddMob(list, "엘리트3", "Elite/Beeto_A", 3.0f); AddMob(list, "엘리트4", "Elite/Mummy_King", 2.3f);
|
|||
|
|
// 보스
|
|||
|
|
AddMob(list, "보스", "FieldBoss/Anubis", 1.5f);
|
|||
|
|
|
|||
|
|
for (int i = 0; i < list.Count; i++)
|
|||
|
|
{
|
|||
|
|
var r = list[i];
|
|||
|
|
var go = Spawn(r.path);
|
|||
|
|
if (go == null) { r.ok = false; continue; }
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
go.transform.localScale = UnityEngine.Vector3.one; // 원 크기(테이블 배율 전) 를 잰다
|
|||
|
|
UnityEngine.Bounds b; int c;
|
|||
|
|
if (RendererBounds(go, out b, out c)) { r.rawH = b.size.y; r.rawD = UnityEngine.Mathf.Max(b.size.x, b.size.z); r.ok = true; }
|
|||
|
|
}
|
|||
|
|
finally { UnityEngine.Object.DestroyImmediate(go); }
|
|||
|
|
}
|
|||
|
|
return list;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void AddMob(System.Collections.Generic.List<ActorRow> l, string kind, string rel, float sc)
|
|||
|
|
{
|
|||
|
|
l.Add(new ActorRow { kind = kind, name = rel, path = "Assets/Res_Addr/Mobs/" + rel + ".prefab", tableScale = sc });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────── C. 카메라 · 화면 점유
|
|||
|
|
|
|||
|
|
class CamInfo { public float fov, h, dist, up, yaw, pan; public UnityEngine.Vector3 camPos; public UnityEngine.Vector3 lookAt; }
|
|||
|
|
|
|||
|
|
static CamInfo BuildCam()
|
|||
|
|
{
|
|||
|
|
var ci = new CamInfo { fov = 60f, h = 76f, dist = 3f, up = 2.4f, yaw = 347.21f, pan = 0f };
|
|||
|
|
var cam = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(kCamPrefab);
|
|||
|
|
if (cam != null)
|
|||
|
|
{
|
|||
|
|
var c = cam.GetComponent<UnityEngine.Camera>();
|
|||
|
|
if (c != null) ci.fov = c.fieldOfView;
|
|||
|
|
var rc = cam.GetComponent<RealCamera>();
|
|||
|
|
if (rc != null)
|
|||
|
|
{
|
|||
|
|
var t = typeof(RealCamera);
|
|||
|
|
var fy = t.GetField("rotateAround", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
|||
|
|
if (fy != null) ci.yaw = (float)fy.GetValue(rc);
|
|||
|
|
var fp = t.GetField("cameraPan", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
|||
|
|
if (fp != null) ci.pan = (float)fp.GetValue(rc);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
var st = WL.CameraFraming.WLCameraFramingSettings.Instance;
|
|||
|
|
if (st != null && st.framingEnabled) { ci.h = st.cameraHeight; ci.dist = st.distance; ci.up = st.distanceUp; }
|
|||
|
|
|
|||
|
|
// RealCamera 궤도식 그대로: rotateVector = Quaternion.Euler(h, yaw, pan) * Vector3.one
|
|||
|
|
var rot = UnityEngine.Quaternion.Euler(ci.h, ci.yaw, ci.pan);
|
|||
|
|
var rv = rot * UnityEngine.Vector3.one;
|
|||
|
|
ci.camPos = UnityEngine.Vector3.up * ci.up - rv * ci.dist; // 타겟 = 원점
|
|||
|
|
ci.lookAt = UnityEngine.Vector3.zero;
|
|||
|
|
return ci;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>원점(PC 발밑)에 지름 dia · 높이 hgt 의 구/원기둥이 있을 때 세로 화면 점유율(0~1).</summary>
|
|||
|
|
static float ScreenFraction(CamInfo ci, float dia, float hgt, float anchorY)
|
|||
|
|
{
|
|||
|
|
var go = new UnityEngine.GameObject("__wl811s_cam");
|
|||
|
|
go.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var cam = go.AddComponent<UnityEngine.Camera>();
|
|||
|
|
cam.enabled = false;
|
|||
|
|
cam.fieldOfView = ci.fov;
|
|||
|
|
cam.aspect = 1080f / 1920f;
|
|||
|
|
cam.nearClipPlane = 0.1f; cam.farClipPlane = 1000f;
|
|||
|
|
go.transform.position = ci.camPos;
|
|||
|
|
go.transform.LookAt(ci.lookAt);
|
|||
|
|
|
|||
|
|
float r = dia * 0.5f;
|
|||
|
|
float yc = anchorY;
|
|||
|
|
float minY = 1e9f, maxY = -1e9f;
|
|||
|
|
for (int i = 0; i < 8; i++)
|
|||
|
|
{
|
|||
|
|
float a = i * (UnityEngine.Mathf.PI * 2f / 8f);
|
|||
|
|
float x = UnityEngine.Mathf.Cos(a) * r, z = UnityEngine.Mathf.Sin(a) * r;
|
|||
|
|
for (int k = 0; k < 2; k++)
|
|||
|
|
{
|
|||
|
|
float y = yc + (k == 0 ? -hgt * 0.5f : hgt * 0.5f);
|
|||
|
|
var vp = cam.WorldToViewportPoint(new UnityEngine.Vector3(x, y, z));
|
|||
|
|
if (vp.z <= 0f) continue;
|
|||
|
|
if (vp.y < minY) minY = vp.y;
|
|||
|
|
if (vp.y > maxY) maxY = vp.y;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (maxY < minY) return 0f;
|
|||
|
|
return maxY - minY;
|
|||
|
|
}
|
|||
|
|
finally { UnityEngine.Object.DestroyImmediate(go); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────── Measure 엔트리
|
|||
|
|
|
|||
|
|
public static string Measure()
|
|||
|
|
{
|
|||
|
|
s_sb.Length = 0; s_err.Clear(); Hook();
|
|||
|
|
L("=== WL-811s MEASURE · " + System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " (에디트 모드 · Play 0) ===");
|
|||
|
|
|
|||
|
|
// A
|
|||
|
|
L("");
|
|||
|
|
L("[A] 액터 높이 (프리팹 렌더러 바운드 · 원 크기 → × 테이블 배율)");
|
|||
|
|
L("kind\tname\trawH(m)\trawD(m)\ttableScale\t실높이(m)\t실지름(m)");
|
|||
|
|
var actors = MeasureActors();
|
|||
|
|
float pcH = 0f;
|
|||
|
|
for (int i = 0; i < actors.Count; i++)
|
|||
|
|
{
|
|||
|
|
var r = actors[i];
|
|||
|
|
if (!r.ok) { L(r.kind + "\t" + r.name + "\tMISSING"); continue; }
|
|||
|
|
float h = r.rawH * r.tableScale, d = r.rawD * r.tableScale;
|
|||
|
|
L(r.kind + "\t" + r.name + "\t" + r.rawH.ToString("F3") + "\t" + r.rawD.ToString("F3") + "\t" + r.tableScale.ToString("F2")
|
|||
|
|
+ "\t" + h.ToString("F3") + "\t" + d.ToString("F3"));
|
|||
|
|
if (r.kind == "PC" && pcH <= 0f) pcH = h;
|
|||
|
|
}
|
|||
|
|
float eliteMul = 1.3f;
|
|||
|
|
var est = WL.Combat.Reaction.WLEliteSettings.Instance;
|
|||
|
|
if (est != null) eliteMul = est.scaleMultiplier;
|
|||
|
|
L("엘리트 배수(WLEliteSettings.scaleMultiplier) = " + eliteMul.ToString("F2") + " → 위 「엘리트*」 실높이 × " + eliteMul.ToString("F2"));
|
|||
|
|
L("PC 기준 높이 = " + pcH.ToString("F3") + " m");
|
|||
|
|
|
|||
|
|
// B
|
|||
|
|
L("");
|
|||
|
|
L("[B] 이펙트 원 크기 (프리팹 localScale 포함 · Simulate t 스윕 · 파티클 실좌표 GetParticles)");
|
|||
|
|
L("prefab\tbaseScale\tps\tpMax\tdiaP90(m)\tdiaMax(m)\thgt(m)\tnote");
|
|||
|
|
string[] names = EffectNames();
|
|||
|
|
var dict = new System.Collections.Generic.Dictionary<string, float>();
|
|||
|
|
for (int i = 0; i < names.Length; i++)
|
|||
|
|
{
|
|||
|
|
float diaMax, diaP90, hgt, bs; int ps, pc; string note;
|
|||
|
|
MeasureEffect(names[i], out diaMax, out diaP90, out hgt, out bs, out ps, out pc, out note);
|
|||
|
|
dict[names[i]] = diaP90;
|
|||
|
|
L(names[i] + "\t" + bs.ToString("F3") + "\t" + ps + "\t" + pc + "\t" + diaP90.ToString("F3") + "\t" + diaMax.ToString("F3") + "\t" + hgt.ToString("F3") + "\t" + note);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// C
|
|||
|
|
L("");
|
|||
|
|
var ci = BuildCam();
|
|||
|
|
L("[C] 카메라 (Main Camera.prefab fov " + ci.fov.ToString("F1") + " · RealCamera yaw " + ci.yaw.ToString("F2") + " pan " + ci.pan.ToString("F2")
|
|||
|
|
+ " · 811o 오버라이드 h " + ci.h.ToString("F1") + " d " + ci.dist.ToString("F2") + " up " + ci.up.ToString("F2") + ")");
|
|||
|
|
L("camPos(타겟 원점 기준) = " + ci.camPos.ToString("F3") + " · 거리 " + ci.camPos.magnitude.ToString("F3")
|
|||
|
|
+ " m · 내림각 " + (UnityEngine.Mathf.Asin(UnityEngine.Mathf.Clamp(ci.camPos.y / UnityEngine.Mathf.Max(0.0001f, ci.camPos.magnitude), -1f, 1f)) * UnityEngine.Mathf.Rad2Deg).ToString("F1") + "°");
|
|||
|
|
L("세로 1080×1920 (aspect " + (1080f / 1920f).ToString("F4") + ") · 1 m 구가 PC 몸 중심(y=" + (pcH * 0.5f).ToString("F2") + ")에 있을 때 세로 점유 = "
|
|||
|
|
+ (ScreenFraction(ci, 1f, 1f, pcH * 0.5f) * 100f).ToString("F1") + " %");
|
|||
|
|
|
|||
|
|
L("");
|
|||
|
|
L("[C2] 현재 16행 슬롯 4 본체의 화면 세로 점유 (현재 scale · 앵커 Mid = PC 몸 중심)");
|
|||
|
|
L("skill\tprefab\tscale\tdia0(m)\tdia*(m)\t세로점유%");
|
|||
|
|
var so = WL.Combat.Reaction.WLSkillSpectacleSettings.Instance;
|
|||
|
|
if (so != null && so.rows != null)
|
|||
|
|
{
|
|||
|
|
for (int i = 0; i < so.rows.Length; i++)
|
|||
|
|
{
|
|||
|
|
var row = so.rows[i];
|
|||
|
|
if (row.slot != 4) continue;
|
|||
|
|
string p = row.body.prefab;
|
|||
|
|
float d0 = 0f; dict.TryGetValue(p, out d0);
|
|||
|
|
float sc = row.body.scale > 0f ? row.body.scale : 1f;
|
|||
|
|
float d = d0 * sc;
|
|||
|
|
L(row.skillId + "\t" + p + "\t" + sc.ToString("F2") + "\t" + d0.ToString("F3") + "\t" + d.ToString("F3") + "\t"
|
|||
|
|
+ (ScreenFraction(ci, d, d, pcH * 0.5f) * 100f).ToString("F1"));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else L("WLSkillSpectacleSettings 로드 실패");
|
|||
|
|
|
|||
|
|
L("");
|
|||
|
|
L("콘솔 error/exception = " + s_err.Count);
|
|||
|
|
for (int i = 0; i < s_err.Count && i < 10; i++) L(" " + s_err[i]);
|
|||
|
|
Unhook();
|
|||
|
|
Save(kMeasureOut);
|
|||
|
|
return "MEASURE done · rows=" + actors.Count + " effects=" + names.Length + " err=" + s_err.Count + " → " + kMeasureOut;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────── Verify 엔트리
|
|||
|
|
|
|||
|
|
// 발주 전 16행 값(main @ 22832ff5d 실측) — (skillId, charge, body, impact) · −1 = 그 단 없음
|
|||
|
|
static float[][] Before()
|
|||
|
|
{
|
|||
|
|
return new float[][] {
|
|||
|
|
new float[]{703101,-1f,1.2f,1.0f}, new float[]{703102,1.0f,1.3f,1.2f},
|
|||
|
|
new float[]{703103,1.0f,1.4f,1.0f}, new float[]{702006,1.2f,1.5f,1.2f},
|
|||
|
|
new float[]{703106,-1f,1.2f,1.0f}, new float[]{703105,1.0f,1.3f,1.0f},
|
|||
|
|
new float[]{703104,1.0f,1.35f,-1f}, new float[]{702009,1.2f,1.6f,1.2f},
|
|||
|
|
new float[]{703502,-1f,1.2f,1.0f}, new float[]{703503,1.0f,1.4f,1.0f},
|
|||
|
|
new float[]{703501,-1f,1.3f,-1f}, new float[]{702005,1.2f,1.8f,1.2f},
|
|||
|
|
new float[]{703505,-1f,1.2f,1.0f}, new float[]{703506,-1f,1.5f,1.0f},
|
|||
|
|
new float[]{703504,1.0f,1.3f,-1f}, new float[]{702012,1.2f,1.5f,1.2f},
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static UnityEngine.GameObject s_fakeInfoGo, s_poolRoot;
|
|||
|
|
static InGameInfo s_prevInfo;
|
|||
|
|
|
|||
|
|
static void PoolBegin()
|
|||
|
|
{
|
|||
|
|
s_prevInfo = InGameInfo.Ins;
|
|||
|
|
s_fakeInfoGo = new UnityEngine.GameObject("__wl811s_info");
|
|||
|
|
s_fakeInfoGo.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
|
|||
|
|
s_poolRoot = new UnityEngine.GameObject("__wl811s_pool");
|
|||
|
|
s_poolRoot.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
|
|||
|
|
var info = s_fakeInfoGo.AddComponent<InGameInfo>(); // 에디트 모드라 Awake 미실행 = 부작용 0
|
|||
|
|
info.tf_Effects = s_poolRoot.transform;
|
|||
|
|
InGameInfo.Ins = info;
|
|||
|
|
}
|
|||
|
|
static void PoolEnd()
|
|||
|
|
{
|
|||
|
|
InGameInfo.Ins = s_prevInfo;
|
|||
|
|
if (s_poolRoot != null) UnityEngine.Object.DestroyImmediate(s_poolRoot);
|
|||
|
|
if (s_fakeInfoGo != null) UnityEngine.Object.DestroyImmediate(s_fakeInfoGo);
|
|||
|
|
s_poolRoot = null; s_fakeInfoGo = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static UnityEngine.GameObject PoolChild(string prefabName, bool active)
|
|||
|
|
{
|
|||
|
|
var pf = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(kEffectDir + prefabName + ".prefab");
|
|||
|
|
if (pf == null) return null;
|
|||
|
|
var go = UnityEngine.Object.Instantiate(pf, s_poolRoot.transform);
|
|||
|
|
go.name = prefabName + "(Clone)"; // 원본 풀과 같은 이름 규약
|
|||
|
|
go.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
|
|||
|
|
go.transform.localScale = pf.transform.localScale;
|
|||
|
|
go.SetActive(active);
|
|||
|
|
return go;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static float s_tick;
|
|||
|
|
static void Tick() { s_tick += 1f; WL.Combat.Reaction.EffectScaleGuard.Tick(s_tick); }
|
|||
|
|
|
|||
|
|
static int s_pass, s_fail;
|
|||
|
|
static void Chk(string name, bool ok, string detail)
|
|||
|
|
{
|
|||
|
|
if (ok) s_pass++; else s_fail++;
|
|||
|
|
L((ok ? "PASS " : "🔴FAIL ") + name + " · " + detail);
|
|||
|
|
}
|
|||
|
|
static bool Near(float a, float b, float eps) { return UnityEngine.Mathf.Abs(a - b) <= eps; }
|
|||
|
|
|
|||
|
|
public static string Verify()
|
|||
|
|
{
|
|||
|
|
s_sb.Length = 0; s_err.Clear(); s_pass = 0; s_fail = 0; Hook();
|
|||
|
|
L("=== WL-811s VERIFY · " + System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " (에디트 모드 · Play 0) ===");
|
|||
|
|
|
|||
|
|
UnityEditor.AssetDatabase.Refresh(); // 디스크에서 손으로 고친 .asset 을 다시 읽는다
|
|||
|
|
UnityEngine.Resources.UnloadUnusedAssets();
|
|||
|
|
WL.Combat.Reaction.WLSkillSpectacleSettings.ClearCache();
|
|||
|
|
WL.Combat.Reaction.WLEffectScaleSettings.ClearCache();
|
|||
|
|
var sk = WL.Combat.Reaction.WLSkillSpectacleSettings.Instance;
|
|||
|
|
var es = WL.Combat.Reaction.WLEffectScaleSettings.Instance;
|
|||
|
|
Chk("SO 로드", sk != null && es != null, "spectacle=" + (sk != null) + " effectScale=" + (es != null) + " rules=" + (es != null && es.rules != null ? es.rules.Length : -1));
|
|||
|
|
|
|||
|
|
// G. guid
|
|||
|
|
L("");
|
|||
|
|
L("[G] 새 에셋 guid → 경로(충돌 0 확인)");
|
|||
|
|
string[][] gp = new string[][] {
|
|||
|
|
new string[]{"8115a0c3e7d24b6f9a1c8e5d3b7f2049","Assets/WL/Combat/Reaction/WLEffectScaleSettings.cs"},
|
|||
|
|
new string[]{"8115b1d4f8e35c709b2d9f6e4c803150","Assets/WL/Combat/Reaction/EffectScaleGuard.cs"},
|
|||
|
|
new string[]{"8115c2e509f46d81ac3ea07f5d914261","Assets/WL/Combat/Reaction/EffectScaleGuardRunner.cs"},
|
|||
|
|
new string[]{"8115d3f61a057e92bd4fb1806ea25372","Assets/WL/Combat/Settings/Resources/WL/WLEffectScaleSettings.asset"},
|
|||
|
|
};
|
|||
|
|
for (int i = 0; i < gp.Length; i++)
|
|||
|
|
{
|
|||
|
|
string got = UnityEditor.AssetDatabase.GUIDToAssetPath(gp[i][0]);
|
|||
|
|
Chk("guid " + gp[i][0].Substring(0, 8), got == gp[i][1], got);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// D. 16행 전/후 · 화면 점유
|
|||
|
|
var ci = BuildCam();
|
|||
|
|
float pcH = 1.191f;
|
|||
|
|
// 세로 50 % 가 되는 지름(이분 탐색 · 원 산식 그대로)
|
|||
|
|
float lo = 0.1f, hi = 20f;
|
|||
|
|
for (int i = 0; i < 40; i++) { float mid = (lo + hi) * 0.5f; if (ScreenFraction(ci, mid, mid, pcH * 0.5f) < 0.5f) lo = mid; else hi = mid; }
|
|||
|
|
float capDia = (lo + hi) * 0.5f;
|
|||
|
|
L("");
|
|||
|
|
L("[D] 화면 점유 (fov " + ci.fov.ToString("F0") + " · 카메라 거리 " + ci.camPos.magnitude.ToString("F2") + " m · 1080×1920)");
|
|||
|
|
L("세로 50 % 가 되는 지름 = " + capDia.ToString("F3") + " m (= 슬롯 4 본체 캡)");
|
|||
|
|
|
|||
|
|
var before = Before();
|
|||
|
|
L("");
|
|||
|
|
L("[D2] 16행 전/후 (scale · dia0 = 프리팹 원 지름 실측 · after = dia0 × new)");
|
|||
|
|
L("skill\tslot\tlabel\tphase\tprefab\tdia0\tbefore\tafter\t지름before\t지름after\t세로%before\t세로%after");
|
|||
|
|
var dia = new System.Collections.Generic.Dictionary<string, float>();
|
|||
|
|
for (int i = 0; sk != null && sk.rows != null && i < sk.rows.Length; i++)
|
|||
|
|
{
|
|||
|
|
var row = sk.rows[i];
|
|||
|
|
float[] b = null;
|
|||
|
|
for (int k = 0; k < before.Length; k++) if ((int)before[k][0] == row.skillId) { b = before[k]; break; }
|
|||
|
|
for (int ph = 0; ph < 3; ph++)
|
|||
|
|
{
|
|||
|
|
var v = ph == 0 ? row.charge : (ph == 1 ? row.body : row.impact);
|
|||
|
|
if (string.IsNullOrEmpty(v.prefab)) continue;
|
|||
|
|
float d0;
|
|||
|
|
if (!dia.TryGetValue(v.prefab, out d0))
|
|||
|
|
{
|
|||
|
|
float dm, dp, hg, bs; int ps, pc; string nt;
|
|||
|
|
MeasureEffect(v.prefab, out dm, out dp, out hg, out bs, out ps, out pc, out nt);
|
|||
|
|
d0 = dp; dia[v.prefab] = d0;
|
|||
|
|
}
|
|||
|
|
float ob = b != null ? b[ph + 1] : -1f;
|
|||
|
|
float ns = v.scale > 0f ? v.scale : 1f;
|
|||
|
|
float anchor = v.anchor == eEffectLocation.Bottom ? 0f : (v.anchor == eEffectLocation.Top ? pcH : pcH * 0.5f);
|
|||
|
|
L(row.skillId + "\t" + row.slot + "\t" + row.label + "\t" + (ph == 0 ? "charge" : (ph == 1 ? "body" : "impact")) + "\t" + v.prefab
|
|||
|
|
+ "\t" + d0.ToString("F3") + "\t" + (ob < 0f ? "?" : ob.ToString("F3")) + "\t" + ns.ToString("F3")
|
|||
|
|
+ "\t" + (ob < 0f ? "?" : (d0 * ob).ToString("F3")) + "\t" + (d0 * ns).ToString("F3")
|
|||
|
|
+ "\t" + (ob < 0f ? "?" : (ScreenFraction(ci, d0 * ob, d0 * ob, anchor) * 100f).ToString("F1"))
|
|||
|
|
+ "\t" + (ScreenFraction(ci, d0 * ns, d0 * ns, anchor) * 100f).ToString("F1"));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
L("");
|
|||
|
|
L("[D3] 슬롯 4 본체 캡 판정 (≤ 50 %)");
|
|||
|
|
for (int i = 0; sk != null && sk.rows != null && i < sk.rows.Length; i++)
|
|||
|
|
{
|
|||
|
|
var row = sk.rows[i];
|
|||
|
|
if (row.slot != 4 || string.IsNullOrEmpty(row.body.prefab)) continue;
|
|||
|
|
float d0 = dia.ContainsKey(row.body.prefab) ? dia[row.body.prefab] : 0f;
|
|||
|
|
float d = d0 * (row.body.scale > 0f ? row.body.scale : 1f);
|
|||
|
|
float f = ScreenFraction(ci, d, d, pcH * 0.5f);
|
|||
|
|
Chk("슬롯4 " + row.skillId + " " + row.body.prefab, f <= 0.5f + 0.005f,
|
|||
|
|
"지름 " + d.ToString("F3") + " m · 세로 " + (f * 100f).ToString("F1") + " %");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// E. 가드
|
|||
|
|
L("");
|
|||
|
|
L("[E] EffectScaleGuard (가짜 풀 루트 = InGameInfo.tf_Effects · 진짜 이펙트 프리팹 인스턴스)");
|
|||
|
|
WL.Combat.Reaction.EffectScaleGuard.ResetDiagnostics();
|
|||
|
|
WL.Combat.Reaction.WLEffectScaleSettings.RuntimeOverride = 1;
|
|||
|
|
bool prevByHeight = es != null && es.scaleByTargetHeight;
|
|||
|
|
if (es != null) es.scaleByTargetHeight = false;
|
|||
|
|
PoolBegin();
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var stun = PoolChild("Effect_Stun", true);
|
|||
|
|
var hit = PoolChild("Effect_Hit_10101", true);
|
|||
|
|
var none = PoolChild("Effect_AnubisCharging", true); // 보스 패턴 = 규칙 없음 = 원본 유지
|
|||
|
|
var poison = PoolChild("Effect_Poison", false); // 비활성으로 시작
|
|||
|
|
var origStun = stun.transform.localScale;
|
|||
|
|
var origHit = hit.transform.localScale;
|
|||
|
|
var origNone = none.transform.localScale;
|
|||
|
|
var origPoison = poison.transform.localScale;
|
|||
|
|
|
|||
|
|
int rStun = es.FindRule("Effect_Stun(Clone)", WL.Combat.Reaction.WLEffectScaleSettings.CoreLength("Effect_Stun(Clone)"));
|
|||
|
|
int rNone = es.FindRule("Effect_AnubisCharging(Clone)", WL.Combat.Reaction.WLEffectScaleSettings.CoreLength("Effect_AnubisCharging(Clone)"));
|
|||
|
|
Chk("규칙 매칭", rStun >= 0 && rNone < 0, "Effect_Stun → rule " + rStun + " (scale " + (rStun >= 0 ? es.rules[rStun].scale.ToString("F3") : "-") + ") · Effect_AnubisCharging → " + rNone + "(없음 = 원본)");
|
|||
|
|
|
|||
|
|
Tick();
|
|||
|
|
float fStun = es.rules[rStun].scale;
|
|||
|
|
Chk("① 활성 인스턴스 배율 적용", Near(stun.transform.localScale.x, origStun.x * fStun, 0.0005f),
|
|||
|
|
"Effect_Stun " + origStun.x.ToString("F3") + " → " + stun.transform.localScale.x.ToString("F4") + " (기대 " + (origStun.x * fStun).ToString("F4") + ")");
|
|||
|
|
Chk("② 규칙 없는 것은 무변경", Near(none.transform.localScale.x, origNone.x, 0.0001f),
|
|||
|
|
"Effect_AnubisCharging " + none.transform.localScale.x.ToString("F4") + " (원본 " + origNone.x.ToString("F4") + ")");
|
|||
|
|
Chk("③ 비활성은 아직 미적용", Near(poison.transform.localScale.x, origPoison.x, 0.0001f), "Effect_Poison " + poison.transform.localScale.x.ToString("F4"));
|
|||
|
|
|
|||
|
|
int ap1 = WL.Combat.Reaction.EffectScaleGuard.AppliedCount;
|
|||
|
|
var afterFirst = stun.transform.localScale;
|
|||
|
|
Tick(); Tick(); Tick();
|
|||
|
|
Chk("④ 멱등(3회 더 틱)", Near(stun.transform.localScale.x, afterFirst.x, 0.000001f) && WL.Combat.Reaction.EffectScaleGuard.AppliedCount == ap1,
|
|||
|
|
"scale " + stun.transform.localScale.x.ToString("F6") + " · Applied " + ap1 + " → " + WL.Combat.Reaction.EffectScaleGuard.AppliedCount);
|
|||
|
|
|
|||
|
|
stun.SetActive(false); Tick();
|
|||
|
|
Chk("⑤ 반납(SetActive false) → 원 스케일 복귀", Near(stun.transform.localScale.x, origStun.x, 0.0001f),
|
|||
|
|
stun.transform.localScale.x.ToString("F4") + " (원본 " + origStun.x.ToString("F4") + ")");
|
|||
|
|
|
|||
|
|
stun.SetActive(true); Tick();
|
|||
|
|
Chk("⑥ 재활성 → 다시 적용", Near(stun.transform.localScale.x, origStun.x * fStun, 0.0005f), stun.transform.localScale.x.ToString("F4"));
|
|||
|
|
|
|||
|
|
poison.SetActive(true); Tick();
|
|||
|
|
int rP = es.FindRule("Effect_Poison(Clone)", WL.Combat.Reaction.WLEffectScaleSettings.CoreLength("Effect_Poison(Clone)"));
|
|||
|
|
Chk("⑦ 나중에 활성된 인스턴스도 적용", rP >= 0 && Near(poison.transform.localScale.x, origPoison.x * es.rules[rP].scale, 0.0005f),
|
|||
|
|
"Effect_Poison → " + poison.transform.localScale.x.ToString("F4"));
|
|||
|
|
|
|||
|
|
// 대상 높이 옵션
|
|||
|
|
L("");
|
|||
|
|
L("[E2] scaleByTargetHeight (대상 액터 실높이로 재계산)");
|
|||
|
|
var mobPf = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>("Assets/Res_Addr/Mobs/Mob/Batty_A.prefab");
|
|||
|
|
var mobGo = mobPf != null ? UnityEngine.Object.Instantiate(mobPf) : null;
|
|||
|
|
if (mobGo != null)
|
|||
|
|
{
|
|||
|
|
mobGo.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
|
|||
|
|
mobGo.transform.position = UnityEngine.Vector3.zero;
|
|||
|
|
var actor = mobGo.GetComponent<Actor>();
|
|||
|
|
var fiTop = typeof(Actor).GetField("tf_Top", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
|
|||
|
|
var fiT2 = typeof(TurnOff_GO).GetField("m_target2", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
|
|||
|
|
Chk("리플렉션 필드 존재", actor != null && fiTop != null && fiT2 != null,
|
|||
|
|
"Actor=" + (actor != null) + " Actor.tf_Top=" + (fiTop != null) + " TurnOff_GO.m_target2=" + (fiT2 != null));
|
|||
|
|
if (actor != null && fiTop != null && fiT2 != null)
|
|||
|
|
{
|
|||
|
|
var topGo = new UnityEngine.GameObject("HUD_Top"); topGo.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
|
|||
|
|
topGo.transform.SetParent(mobGo.transform, false);
|
|||
|
|
topGo.transform.position = new UnityEngine.Vector3(0f, 0.537f, 0f); // 존1 Batty 실높이(MEASURE [A])
|
|||
|
|
fiTop.SetValue(actor, topGo.transform);
|
|||
|
|
var off = stun.GetComponent<TurnOff_GO>();
|
|||
|
|
Chk("Effect_Stun 에 TurnOff_GO 존재", off != null, off != null ? "ok" : "없음");
|
|||
|
|
if (off != null)
|
|||
|
|
{
|
|||
|
|
fiT2.SetValue(off, actor);
|
|||
|
|
es.scaleByTargetHeight = true;
|
|||
|
|
stun.SetActive(false); Tick(); stun.SetActive(true); Tick();
|
|||
|
|
float expect = UnityEngine.Mathf.Clamp((0.537f * es.rules[rStun].targetRatio) / es.rules[rStun].baseDiameter, es.minScale, es.maxScale);
|
|||
|
|
Chk("⑧ 대상 높이 기반 재계산", Near(stun.transform.localScale.x, origStun.x * expect, 0.0005f),
|
|||
|
|
"몹 높이 0.537 m × " + es.rules[rStun].targetRatio + " ÷ " + es.rules[rStun].baseDiameter + " = " + expect.ToString("F4")
|
|||
|
|
+ " → scale " + stun.transform.localScale.x.ToString("F4") + " · 지름 " + (es.rules[rStun].baseDiameter * expect).ToString("F3") + " m"
|
|||
|
|
+ " · HeightResolved " + WL.Combat.Reaction.EffectScaleGuard.HeightResolved);
|
|||
|
|
fiT2.SetValue(off, null);
|
|||
|
|
int miss0 = WL.Combat.Reaction.EffectScaleGuard.HeightMissed;
|
|||
|
|
stun.SetActive(false); Tick(); stun.SetActive(true); Tick();
|
|||
|
|
Chk("⑨ 대상 없으면 고정 배율로 폴백", Near(stun.transform.localScale.x, origStun.x * fStun, 0.0005f) && WL.Combat.Reaction.EffectScaleGuard.HeightMissed > miss0,
|
|||
|
|
"scale " + stun.transform.localScale.x.ToString("F4") + " · HeightMissed " + miss0 + " → " + WL.Combat.Reaction.EffectScaleGuard.HeightMissed);
|
|||
|
|
es.scaleByTargetHeight = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
UnityEngine.Object.DestroyImmediate(mobGo);
|
|||
|
|
}
|
|||
|
|
else Chk("몹 프리팹 로드", false, "Batty_A 없음");
|
|||
|
|
|
|||
|
|
// F. GC
|
|||
|
|
L("");
|
|||
|
|
L("[F] GC · C8");
|
|||
|
|
stun.SetActive(true); hit.SetActive(true); Tick();
|
|||
|
|
long m0 = System.GC.GetAllocatedBytesForCurrentThread();
|
|||
|
|
long mono0 = UnityEngine.Profiling.Profiler.GetMonoUsedSizeLong();
|
|||
|
|
for (int i = 0; i < 200000; i++) Tick();
|
|||
|
|
long dAlloc = System.GC.GetAllocatedBytesForCurrentThread() - m0;
|
|||
|
|
long dMono = UnityEngine.Profiling.Profiler.GetMonoUsedSizeLong() - mono0;
|
|||
|
|
Chk("⑩ GC 0 (Tick ×200,000 · 워밍 후)", dAlloc == 0, "GetAllocatedBytesForCurrentThread Δ " + dAlloc + " B · MonoUsed Δ " + dMono + " B");
|
|||
|
|
|
|||
|
|
// C8
|
|||
|
|
WL.Combat.Reaction.WLEffectScaleSettings.RuntimeOverride = -1;
|
|||
|
|
Tick();
|
|||
|
|
Chk("⑪ C8 (enabled = 0) → 전량 원복", Near(stun.transform.localScale.x, origStun.x, 0.0001f) && Near(hit.transform.localScale.x, origHit.x, 0.0001f)
|
|||
|
|
&& WL.Combat.Reaction.EffectScaleGuard.ActiveScaled == 0,
|
|||
|
|
"stun " + stun.transform.localScale.x.ToString("F4") + " · hit " + hit.transform.localScale.x.ToString("F4") + " · ActiveScaled " + WL.Combat.Reaction.EffectScaleGuard.ActiveScaled);
|
|||
|
|
int ap2 = WL.Combat.Reaction.EffectScaleGuard.AppliedCount;
|
|||
|
|
Tick(); Tick();
|
|||
|
|
Chk("⑫ C8 상태에서 무동작", WL.Combat.Reaction.EffectScaleGuard.AppliedCount == ap2, "Applied " + ap2 + " → " + WL.Combat.Reaction.EffectScaleGuard.AppliedCount);
|
|||
|
|
|
|||
|
|
L("카운터: Sweep " + WL.Combat.Reaction.EffectScaleGuard.SweepCount + " · Tracked " + WL.Combat.Reaction.EffectScaleGuard.TrackedCount
|
|||
|
|
+ " · Ruled " + WL.Combat.Reaction.EffectScaleGuard.RuledCount + " · Applied " + WL.Combat.Reaction.EffectScaleGuard.AppliedCount
|
|||
|
|
+ " · Restored " + WL.Combat.Reaction.EffectScaleGuard.RestoredCount + " · Skipped " + WL.Combat.Reaction.EffectScaleGuard.SkippedCount);
|
|||
|
|
}
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
WL.Combat.Reaction.WLEffectScaleSettings.RuntimeOverride = 0;
|
|||
|
|
if (es != null) es.scaleByTargetHeight = prevByHeight;
|
|||
|
|
WL.Combat.Reaction.EffectScaleGuard.ResetDiagnostics();
|
|||
|
|
PoolEnd();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
L("");
|
|||
|
|
L("RESULT " + (s_fail == 0 ? "PASS" : "FAIL") + " " + s_pass + "/" + (s_pass + s_fail) + " · 콘솔 error/exception = " + s_err.Count);
|
|||
|
|
for (int i = 0; i < s_err.Count && i < 10; i++) L(" " + s_err[i]);
|
|||
|
|
Unhook();
|
|||
|
|
Save(kVerifyOut);
|
|||
|
|
return "VERIFY " + (s_fail == 0 ? "PASS" : "FAIL") + " " + s_pass + "/" + (s_pass + s_fail) + " err=" + s_err.Count + " → " + kVerifyOut;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static string[] EffectNames()
|
|||
|
|
{
|
|||
|
|
return new string[]
|
|||
|
|
{
|
|||
|
|
// 16행(811i 매핑) — 차지/본체/착탄
|
|||
|
|
"Effect_Berserk","Effect_PCDash","Effect_Howling","Effect_Casting_Energyball","Effect_Casting_ManaShield","Effect_Casting_Meteor",
|
|||
|
|
"Effect_Slash_10101_1","Effect_Slash_10101_2","Effect_Slash_10101_3","Effect_WLSwingArc",
|
|||
|
|
"Effect_Slash_10102_1","Effect_Slash_10102_2","Effect_SwordShield","Effect_STR_EXTRA_SPLASH",
|
|||
|
|
"Effect_Slash_10501_1","Effect_Slash_10501_2","Effect_Slash_10501_3","Effect_Heal",
|
|||
|
|
"Effect_Slash_10502_1","Effect_Slash_10502_2","Effect_Aggro","ShineBulletFlash",
|
|||
|
|
"Effect_Hit_10101","Effect_Hit_10102","Effect_Hit_10501","Effect_Hit_SweepAttack","Effect_Hit_Skill_EnergySlashFlash",
|
|||
|
|
"Effect_Hit_Energyball","WindBullet_Hit","WaveShotHit","ShineBulletHit","Effect_Hit_Skill_CrossSlash","Effect_Hit_Skill_SpinSlash",
|
|||
|
|
// 원본 풀 상태이상
|
|||
|
|
"Effect_Stun","Effect_Poison","Effect_Burn","Effect_Blind","Effect_Curse","Effect_Slow","Effect_Freezing","Effect_Frost",
|
|||
|
|
"Effect_HPBurn","Effect_ManaBurn","Effect_SilenceCurse","Effect_Blood","Effect_DefenceDecrease","Effect_Barrier","Effect_ManaShield",
|
|||
|
|
"Effect_Cure","Effect_SpeedUp","Effect_StatUp","Effect_Reincarnation","Effect_VampirHeal",
|
|||
|
|
// 원본 풀 히트(클래스 · 몹 · 보스)
|
|||
|
|
"Effect_Hit_10201","Effect_Hit_10202","Effect_Hit_10301","Effect_Hit_10302","Effect_Hit_10303","Effect_Hit_10401","Effect_Hit_10402",
|
|||
|
|
"Effect_Hit_10502","Effect_Hit_10601","Effect_Hit_IceBolt","Effect_Hit_Fireball","Effect_Hit_Firebolt","Effect_Hit_Electronic",
|
|||
|
|
"Effect_Hit_Bead","Effect_Hit_BlackHole","Effect_Hit_AnubisSkill1","Effect_Hit_AnubisSkill2",
|
|||
|
|
"Effect_Hit_BrotherPenguin_Attack","Effect_Hit_FlowerDryad_Attack","Effect_Hit_IceGolem_Attack","Effect_Hit_Shaga_Attack",
|
|||
|
|
"Effect_Hit_Mob_HellfireFlames","Effect_Hit_RookieArcherSkill1","Effect_Hit_SummonGolem","Effect_Hit_SummonWorm",
|
|||
|
|
"Effect_Hit_Skill_DaggerThrow","Effect_Hit_Skill_Frozen"
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|