Project_WL/AgentScripts/WL813tj_Probe.cs

477 lines
28 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.

// WL-813tj (#813) — 실측 프로브(Play 0 · 에디트 모드).
// unity command run_script --file AgentScripts/WL813tj_Probe.cs --entry WL813tj_Probe.DumpSafeArea
// unity command run_script --file AgentScripts/WL813tj_Probe.cs --entry WL813tj_Probe.DumpBoss
// unity command run_script --file AgentScripts/WL813tj_Probe.cs --entry WL813tj_Probe.DumpSurvival
// unity command run_script --file AgentScripts/WL813tj_Probe.cs --entry WL813tj_Probe.DumpPrefab
// 🔴 프리팹은 LoadPrefabContents 로 열고 **저장하지 않는다**(검증만). 구독은 전부 되돌린다.
using System.Text;
using UnityEditor;
using UnityEngine;
public static class WL813tj_Probe
{
public const string NewGameUIPath = "Assets/Res_Addr/MainUI/NewGameUI.prefab";
public const string HudPath = "IngameUIs/WL_HUD";
public const string PadPath = "IngameUIs/BattleUI";
static Transform FindByPath(Transform root, string path)
{
var cur = root;
foreach (var part in path.Split('/'))
{
Transform next = null;
for (int i = 0; i < cur.childCount; i++)
if (cur.GetChild(i).name == part) { next = cur.GetChild(i); break; }
if (next == null) return null;
cur = next;
}
return cur;
}
static void Save(string file, string body)
{
try { System.IO.File.WriteAllText(System.IO.Path.Combine("Logs", file), body); } catch { }
}
// ═══ ⓒ Safe Area ════════════════════════════════════════════════════════
struct Res { public string name; public int w, h; public Rect safe; public string src; }
static Res[] QaResolutions()
{
// QA 5종(802b·813c 와 같은 목록). safeArea 는 「추정」 — 실기 인셋은 빌드 전까지 확인 불가(C23).
return new[]
{
new Res { name="1080x1920", w=1080, h=1920, safe=new Rect(0, 0, 1080, 1920), src="노치 없음(전체)" },
new Res { name="720x1600", w= 720, h=1600, safe=new Rect(0, 0, 720, 1600), src="노치 없음(전체)" },
new Res { name="1440x3200", w=1440, h=3200, safe=new Rect(0, 0, 1440, 3200), src="노치 없음(전체)" },
new Res { name="1179x2556", w=1179, h=2556, safe=new Rect(0, 102, 1179, 2277), src="다이내믹 아일랜드 「추정」 top 177 / bottom 102 px" },
new Res { name="1080x2400", w=1080, h=2400, safe=new Rect(0, 0, 1080, 2310), src="펀치홀 「추정」 top 90 px" },
};
}
/// <summary>SafeAreaFitter.Apply 와 **같은 4줄**을 재현한다(Screen.safeArea 를 주입할 수 없어서).</summary>
static void FitterMath(Rect safe, int w, int h, bool applyH, bool applyV, out Vector2 min, out Vector2 max)
{
min = new Vector2(safe.xMin / w, safe.yMin / h);
max = new Vector2(safe.xMax / w, safe.yMax / h);
if (!applyH) { min.x = 0f; max.x = 1f; }
if (!applyV) { min.y = 0f; max.y = 1f; }
}
public static object DumpSafeArea()
{
var sb = new StringBuilder();
var root = PrefabUtility.LoadPrefabContents(NewGameUIPath);
try
{
var rt = root.transform;
sb.AppendLine("[1] 캔버스 루트 순서 (children=" + rt.childCount + ")");
for (int i = 0; i < rt.childCount; i++)
{
var c = rt.GetChild(i);
var crt = c as RectTransform;
bool panel = c.name.StartsWith("WL_SafeArea");
sb.AppendLine(" [" + i + "] " + c.name + (panel ? " ← Safe Area 패널" : "") +
" active=" + c.gameObject.activeSelf +
(crt != null ? " aMin" + crt.anchorMin + " aMax" + crt.anchorMax +
" off" + crt.offsetMin + crt.offsetMax + " scale" + crt.localScale : ""));
if (!panel) continue;
var f = c.GetComponent<WL.UI.SafeAreaFitter>();
sb.AppendLine(" SafeAreaFitter=" + (f != null) + " Graphic=" + (c.GetComponent<UnityEngine.UI.Graphic>() != null) +
" comps=" + c.GetComponents<Component>().Length);
for (int j = 0; j < c.childCount; j++)
{
var g = c.GetChild(j) as RectTransform;
sb.AppendLine(" └ " + c.GetChild(j).name + " aMin" + g.anchorMin + " aMax" + g.anchorMax +
" off" + g.offsetMin + g.offsetMax + " scale" + g.localScale + " z=" + g.localPosition.z +
" active=" + g.gameObject.activeSelf);
}
}
sb.AppendLine();
sb.AppendLine("[2] 평탄화 순서(그리는 순서) = 원본과 같아야 회귀 0");
var flat = new StringBuilder();
for (int i = 0; i < rt.childCount; i++)
{
var c = rt.GetChild(i);
if (c.name.StartsWith("WL_SafeArea")) for (int j = 0; j < c.childCount; j++) flat.Append(c.GetChild(j).name).Append(" · ");
else flat.Append(c.name).Append(" · ");
}
sb.AppendLine(" 지금 : " + flat.ToString().TrimEnd(' ', '·'));
sb.AppendLine(" 원본 : New Image · Camera · Floating Joystick · CamRot · Common · LobbyUIs · IngameUIs · FarmUIs · MessageInfo · Center · PopupMgr · TestMapUIMgr");
sb.AppendLine();
sb.AppendLine("[3] SafeAreaFitter 산식 표 (QA 5해상도 · applyH=applyV=true)");
sb.AppendLine(" ※ Screen.safeArea 를 주입할 수 없어 SafeAreaFitter.Apply 의 4줄을 그대로 재현했다(802b 와 같은 방법).");
sb.AppendLine(" 해상도 | safeArea(x,y,w,h) | anchorMin | anchorMax | 화면 인셋(px) | 근거");
foreach (var r in QaResolutions())
{
Vector2 mn, mx;
FitterMath(r.safe, r.w, r.h, true, true, out mn, out mx);
float insetTop = r.h - r.safe.yMax, insetBottom = r.safe.yMin;
sb.AppendLine(" " + r.name.PadRight(12) + "| " +
("(" + r.safe.x + "," + r.safe.y + "," + r.safe.width + "," + r.safe.height + ")").PadRight(25) + "| " +
("(" + mn.x.ToString("F4") + ", " + mn.y.ToString("F4") + ")").PadRight(17) + "| " +
("(" + mx.x.ToString("F4") + ", " + mx.y.ToString("F4") + ")").PadRight(17) + "| " +
("top " + insetTop + " / bottom " + insetBottom).PadRight(22) + "| " + r.src);
}
sb.AppendLine(" offsetMin/offsetMax 는 언제나 (0,0) — 패널은 앵커만 움직인다(802b 와 동일).");
// 실측 1건: 에디터가 보고하는 실제 Screen 으로 진짜 컴포넌트를 돌려 재현식과 대조한다.
sb.AppendLine();
sb.AppendLine("[4] 실측 대조 1건 — 에디터 Screen(" + Screen.width + "x" + Screen.height + ") safeArea=" + Screen.safeArea);
foreach (var pn in new[] { "WL_SafeArea_Back", "WL_SafeArea_Front" })
{
var p = rt.Find(pn) as RectTransform;
if (p == null) { sb.AppendLine(" " + pn + " 없음"); continue; }
var f = p.GetComponent<WL.UI.SafeAreaFitter>();
if (f == null) { sb.AppendLine(" " + pn + " SafeAreaFitter 없음"); continue; }
f.Apply();
Vector2 mn, mx;
FitterMath(Screen.safeArea, Screen.width, Screen.height, true, true, out mn, out mx);
sb.AppendLine(" " + pn + " 실제 Apply → aMin" + p.anchorMin + " aMax" + p.anchorMax +
" off" + p.offsetMin + p.offsetMax + " | 재현식 aMin(" + mn.x.ToString("F4") + ", " + mn.y.ToString("F4") +
") aMax(" + mx.x.ToString("F4") + ", " + mx.y.ToString("F4") + ") | 일치=" +
(Vector2.Distance(p.anchorMin, mn) < 1e-5f && Vector2.Distance(p.anchorMax, mx) < 1e-5f));
// 검증만 — 프리팹은 저장하지 않는다
}
sb.AppendLine();
sb.AppendLine("[5] 회귀 — WLIngameUiOverride 경로(설정 에셋의 옛 문자열이 그대로 먹는가)");
var gs = WL.Settings.WLGameplaySettings.Instance;
if (gs == null) sb.AppendLine(" WLGameplaySettings 에셋 없음");
else
{
foreach (var p in gs.ingameHideCanvasGroupPaths ?? new string[0])
{
var t = WL.UI.WLVignetteUtil.FindUiPath(rt, p);
sb.AppendLine(" [canvasGroupHide] \"" + p + "\" → " + (t == null ? "🔴 없음" : "OK " + FullPath(rt, t)));
}
foreach (var p in gs.battlePadPaths ?? new string[0])
{
var t = WL.UI.WLVignetteUtil.FindUiPath(rt, p);
sb.AppendLine(" [battlePad] \"" + p + "\" → " + (t == null ? "🔴 없음" : "OK " + FullPath(rt, t) + " children=" + t.childCount));
}
var ui = root.GetComponent<NewGameUI>();
var common = (ui != null && ui.gos != null && ui.gos.Length > 0) ? ui.gos[0] : null;
sb.AppendLine(" [gos[0]] NewGameUI.gos[0]=" + (common == null ? "🔴 null" : common.name + " (참조 · 재부모화 무관) 부모=" +
(common.transform.parent != null ? common.transform.parent.name : "-")));
if (common != null)
foreach (var n in gs.ingameHideCommonObjectNames ?? new string[0])
sb.AppendLine(" [commonHide] \"" + n + "\" → " + (common.transform.Find(n) == null ? "🔴 없음" : "OK"));
sb.AppendLine(" hideBattlePad=" + gs.hideBattlePad + " (읽기만 · S 소유 · 값 전환은 Lead)");
}
// 813j 피격 테두리 대상도 같은 탐색기로 확인
var sur = WL.UI.WLSurvivalUiSettings.Instance;
if (sur != null)
{
var t = WL.UI.WLVignetteUtil.FindUiPath(rt, sur.hitBorderTargetPath);
sb.AppendLine(" [hitBorderTarget] \"" + sur.hitBorderTargetPath + "\" → " +
(t == null ? "🔴 없음" : "OK " + FullPath(rt, t) +
" size" + ((RectTransform)t).sizeDelta));
}
}
finally { PrefabUtility.UnloadPrefabContents(root); }
var body = sb.ToString();
Save("WL813tj_probe_safearea.txt", body);
return body;
}
static string FullPath(Transform root, Transform t)
{
var sb = new StringBuilder(t.name);
var cur = t.parent;
while (cur != null && cur != root) { sb.Insert(0, cur.name + "/"); cur = cur.parent; }
return sb.ToString();
}
// ═══ ⓑ 813t 보스 배너 · 경고 비네트 ═════════════════════════════════════
public static object DumpBoss()
{
var sb = new StringBuilder();
var root = PrefabUtility.LoadPrefabContents(NewGameUIPath);
WL.UI.BossBanner bc = null;
try
{
var hud = FindByPath(root.transform, HudPath);
var node = hud != null ? hud.Find(WL813tj_ApplyNames.Banner) : null;
if (node == null) return "🔴 " + HudPath + "/" + WL813tj_ApplyNames.Banner + " 없음 — Apply 먼저";
bc = node.GetComponent<WL.UI.BossBanner>();
if (bc == null) return "🔴 BossBanner 컴포넌트 없음";
sb.AppendLine("[0] 초기화 — 런타임과 같은 경로(OnEnable→Initialize)");
sb.AppendLine(" 구독 전 Spawned=" + WL.Combat.Core.CombatEvents.Spawned.Count +
" BossPhase=" + WL.Combat.Boss.BossEvents.BossPhase.Count);
sb.AppendLine(" " + bc.Initialize());
sb.AppendLine(" 구독 후 Spawned=" + WL.Combat.Core.CombatEvents.Spawned.Count +
" BossPhase=" + WL.Combat.Boss.BossEvents.BossPhase.Count);
float t0 = Time.unscaledTime;
sb.AppendLine();
sb.AppendLine("[1] 비보스 스폰 → 반응 없음(미표시 규칙)");
sb.AppendLine(" " + WL.UI.BossBanner.RaiseFakeNonBossSpawned());
bc.Tick(t0);
sb.AppendLine(" visible=" + bc.Visible + " shows=" + bc.ShowCount + " (기대 False / 0)");
sb.AppendLine();
sb.AppendLine("[2] 보스 스폰(CombatEvents.Spawned isBoss=true) → 배너 + 비네트");
sb.AppendLine(" " + WL.UI.BossBanner.RaiseFakeBossSpawned());
var s = WL.UI.WLBossUiSettings.Instance;
float[] marks = { 0f, 0.05f, 0.15f, 0.20f, 0.40f, 0.79f, 0.81f, 1.0f, 1.19f, 1.21f };
sb.AppendLine(" t(s) | 배너α | 배너scale | 경고α | 상태");
foreach (var m in marks)
{
bc.Tick(t0 + m);
string state = (m < s.bannerSeconds ? "배너 " : "배너끝 ") + (m < s.warnVignetteSeconds ? "· 비네트" : "· 비네트끝");
sb.AppendLine(" " + m.ToString("F2").PadLeft(5) + " | " + bc.BannerAlpha.ToString("F3") +
" | " + bc.BannerScale.ToString("F3").PadLeft(8) +
" | " + bc.WarnAlpha.ToString("F3") + " | " + state);
}
sb.AppendLine(" shows=" + bc.ShowCount + " seen(Spawned/보스/Phase)=" + bc.SpawnedSeen + "/" + bc.BossSpawnSeen + "/" + bc.PhaseSeen);
sb.AppendLine();
sb.AppendLine("[3] 이름 승격 — BossEvents.BossPhase(isSpawn, monsterId) 가 MonsterList 이름을 준다");
sb.AppendLine(" 표 로드 여부: table_monsterlist.Ins=" + (table_monsterlist.Ins != null ? "있음" : "없음(에디트 모드)") +
" · table_localtext.Ins=" + (table_localtext.Ins != null ? "있음" : "없음"));
sb.AppendLine(" ResolveMonsterName(10006 Anubis)=" + (WL.UI.BossBanner.ResolveMonsterName(10006) ?? "(null · 표 미로드)"));
sb.AppendLine(" " + WL.UI.BossBanner.RaiseFakeBossPhaseSpawn(10006));
sb.AppendLine(" → " + bc.Dump().Split('\n')[0].Trim());
sb.AppendLine();
sb.AppendLine("[4] 직접 Trigger(이름 주입) — 표 없이도 이름 줄이 붙는지");
sb.AppendLine(" " + bc.ResetDiagnostics() + " (에디트 모드는 Time 이 멈춰 가드가 안 풀린다)");
sb.AppendLine(" " + bc.Trigger("아누비스", true));
bc.Tick(Time.unscaledTime + 0.2f);
sb.AppendLine();
sb.AppendLine("[5] 최종 덤프");
sb.Append(bc.Dump());
sb.AppendLine();
sb.AppendLine("[6] 813c 보스 HP 바와 같은 프레임 — 같은 Spawned Dispatch 를 함께 탄다");
var barNode = hud.Find("WL_BossHpBar");
var bar = barNode != null ? barNode.GetComponent<WL.UI.BossHpBar>() : null;
if (bar == null) sb.AppendLine(" 🔴 WL_BossHpBar 없음");
else
{
bar.Initialize();
bc.ResetDiagnostics();
sb.AppendLine(" 구독자 Spawned=" + WL.Combat.Core.CombatEvents.Spawned.Count + " (배너 + 보스바)");
int f0 = Time.frameCount;
sb.AppendLine(" " + WL.UI.BossHpBar.RaiseFakeBossSpawned(1000f));
bc.Tick(Time.unscaledTime + 0.2f); // 페이드인 0.15s 뒤(t=0 은 α 0 이 정상)
sb.AppendLine(" 같은 프레임(frame=" + f0 + "): 배너 shows=" + bc.ShowCount + " visible=" + bc.Visible +
" · 보스바 visible=" + bar.Visible + " ratio=" + bar.LastRatio.ToString("F3"));
bar.Subscribe(false);
}
}
finally
{
if (bc != null) bc.Subscribe(false);
PrefabUtility.UnloadPrefabContents(root);
}
sb.AppendLine();
sb.AppendLine("해제 후 구독자 Spawned=" + WL.Combat.Core.CombatEvents.Spawned.Count +
" BossPhase=" + WL.Combat.Boss.BossEvents.BossPhase.Count + " (0 이어야 한다)");
var body = sb.ToString();
Save("WL813tj_probe_boss.txt", body);
return body;
}
// ═══ ⓑ 813j 물약 · 부활 · 피격 ══════════════════════════════════════════
public static object DumpSurvival()
{
var sb = new StringBuilder();
var root = PrefabUtility.LoadPrefabContents(NewGameUIPath);
WL.UI.HitVignette hv = null;
try
{
var hud = FindByPath(root.transform, HudPath);
var pad = FindByPath(root.transform, PadPath);
var s = WL.UI.WLSurvivalUiSettings.Instance;
if (s == null) return "🔴 WLSurvivalUiSettings 에셋 없음";
// ── A. 피격 테두리 + 가장자리 비네트
var hvNode = hud != null ? hud.Find(WL813tj_ApplyNames.Hit) : null;
hv = hvNode != null ? hvNode.GetComponent<WL.UI.HitVignette>() : null;
sb.AppendLine("[A] 피격 연출 (CombatEvents.Damaged · 실측 발생지 MyActor.cs:309 = PC 전용)");
if (hv == null) sb.AppendLine(" 🔴 WL_HitVignette 없음");
else
{
sb.AppendLine(" 구독 전 Damaged=" + WL.Combat.Core.CombatEvents.Damaged.Count);
sb.AppendLine(" " + hv.Initialize());
sb.AppendLine(" 구독 후 Damaged=" + WL.Combat.Core.CombatEvents.Damaged.Count);
float t0 = Time.unscaledTime;
sb.AppendLine(" ① 무적 피격(invincible=true · dmg 0) ×3 → 무시되어야 한다");
for (int i = 0; i < 3; i++) WL.UI.HitVignette.RaiseFakeDamaged(0, true);
sb.AppendLine(" seen=" + hv.DamagedSeen + " 무적스킵=" + hv.InvincibleSkipped + " flashes=" + hv.FlashCount +
" borderα=" + hv.BorderAlpha.ToString("F3") + " (기대 flashes=0)");
sb.AppendLine(" ② 실피격(invincible=false · dmg 120) → 번쩍");
sb.AppendLine(" " + WL.UI.HitVignette.RaiseFakeDamaged(120, false));
sb.AppendLine(" t(s) | 테두리α | 가장자리α");
foreach (var m in new[] { 0f, 0.10f, 0.20f, 0.29f, 0.31f })
{
hv.Tick(t0 + m);
sb.AppendLine(" " + m.ToString("F2").PadLeft(5) + " | " + hv.BorderAlpha.ToString("F3").PadLeft(7) +
" | " + hv.EdgeAlpha.ToString("F3"));
}
sb.AppendLine(" ③ 같은 프레임 연타 → 스로틀(" + s.hitMinIntervalSeconds + "s)");
int before = hv.FlashCount;
for (int i = 0; i < 4; i++) WL.UI.HitVignette.RaiseFakeDamaged(50, false);
sb.AppendLine(" 추가 요청 4 → flashes " + before + "→" + hv.FlashCount + " 스로틀스킵=" + hv.ThrottleSkipped);
sb.AppendLine();
sb.Append(hv.Dump());
hv.RemoveBorder(); // 프리팹에 굽지 않는다 — 검증 후 즉시 제거
}
// ── B. 부활 팝업
sb.AppendLine();
sb.AppendLine("[B] 부활 팝업 (공용 Popup/SortOrder_5 재사용 · 813i 미연결)");
var rvNode = hud != null ? hud.Find(WL813tj_ApplyNames.Revive) : null;
var rv = rvNode != null ? rvNode.GetComponent<WL.UI.ReviveDialog>() : null;
if (rv == null) sb.AppendLine(" 🔴 WL_ReviveDialog 없음");
else
{
sb.AppendLine(" " + rv.Initialize());
float t0 = Time.unscaledTime;
sb.AppendLine(" 813i 진입점 NotifyDeath() → " + WL.UI.ReviveDialog.NotifyDeath());
rv.Tick(t0 + s.reviveDelaySeconds - 0.01f);
sb.AppendLine(" t=" + (s.reviveDelaySeconds - 0.01f).ToString("F2") + "s : 표시=" + rv.FallbackVisible + " 대기중=" + rv.Pending + " (기대 False/True)");
rv.Tick(t0 + s.reviveDelaySeconds);
sb.AppendLine(" t=" + s.reviveDelaySeconds.ToString("F2") + "s : 표시=" + rv.FallbackVisible +
" 공용팝업사용=" + rv.UsedCommonPopup + " shows=" + rv.ShownCount);
sb.AppendLine(" 본문=\"" + rv.LastMessage.Replace("\n", " / ") + "\"");
bool got = false;
WL.UI.ReviveDialog.ReviveRequested = () => got = true;
sb.AppendLine(" 확인 → " + rv.OnConfirm() + " · 813i 콜백 수신=" + got + " · 표시=" + rv.FallbackVisible);
WL.UI.ReviveDialog.ReviveRequested = null;
sb.AppendLine();
sb.Append(rv.Dump());
}
// ── C. 물약 버튼
sb.AppendLine();
sb.AppendLine("[C] 물약 버튼 (813c 예약 슬롯 · 813i Provider 미연결 → 대역으로 왕복 검증)");
var ptNode = pad != null ? pad.Find(WL813tj_ApplyNames.Potion) : null;
var pt = ptNode != null ? ptNode.GetComponent<WL.UI.PotionButton>() : null;
if (pt == null) sb.AppendLine(" 🔴 WL_PotionButton 없음");
else
{
WL.UI.PotionButton.ClearProvider();
sb.AppendLine(" " + pt.Initialize());
sb.AppendLine(" ① 813i 미연결: HasProvider=" + WL.UI.PotionButton.HasProvider + " · " + pt.Refresh());
sb.AppendLine(" 클릭 → " + pt.OnClickPotion() + " (used=" + pt.UsedCount + " blocked=" + pt.BlockedCount + ")");
sb.AppendLine(" ② 813i 대역 설치: " + WL.UI.PotionButton.InstallFakeProvider(3, 5f));
sb.AppendLine(" " + pt.Refresh());
sb.AppendLine(" 클릭1 → " + pt.OnClickPotion() + " · " + pt.Refresh());
sb.AppendLine(" 쿨 중 클릭2 → " + pt.OnClickPotion() + " · " + pt.Refresh());
if (WL.UI.PotionButton.FakeCooldownSetter != null) WL.UI.PotionButton.FakeCooldownSetter(2.5f);
sb.AppendLine(" 쿨 절반(2.5/5) → " + pt.Refresh());
if (WL.UI.PotionButton.FakeCooldownSetter != null) WL.UI.PotionButton.FakeCooldownSetter(0f);
sb.AppendLine(" 쿨 종료 → " + pt.Refresh());
sb.AppendLine(" 클릭3 → " + pt.OnClickPotion());
if (WL.UI.PotionButton.FakeCooldownSetter != null) WL.UI.PotionButton.FakeCooldownSetter(0f);
sb.AppendLine(" 클릭4 → " + pt.OnClickPotion() + " · " + pt.Refresh());
if (WL.UI.PotionButton.FakeCooldownSetter != null) WL.UI.PotionButton.FakeCooldownSetter(0f);
sb.AppendLine(" 잔량 0 클릭5 → " + pt.OnClickPotion() + " · " + pt.Refresh());
sb.AppendLine();
sb.Append(pt.Dump());
sb.AppendLine(" " + WL.UI.PotionButton.ClearProvider());
}
}
finally
{
if (hv != null) hv.Subscribe(false);
PrefabUtility.UnloadPrefabContents(root);
}
sb.AppendLine();
sb.AppendLine("해제 후 구독자 Damaged=" + WL.Combat.Core.CombatEvents.Damaged.Count + " (0 이어야 한다)");
var body = sb.ToString();
Save("WL813tj_probe_survival.txt", body);
return body;
}
// ═══ 노드 배치 · 겹침 ═══════════════════════════════════════════════════
public static object DumpPrefab()
{
var sb = new StringBuilder();
var root = PrefabUtility.LoadPrefabContents(NewGameUIPath);
try
{
var hud = FindByPath(root.transform, HudPath);
var pad = FindByPath(root.transform, PadPath);
sb.AppendLine("[WL_HUD] children=" + (hud != null ? hud.childCount : -1) +
" SafeAreaFitter=" + (hud != null && hud.GetComponent<WL.UI.SafeAreaFitter>() != null));
if (hud != null)
for (int i = 0; i < hud.childCount; i++)
{
var c = hud.GetChild(i) as RectTransform;
sb.AppendLine(" [" + i + "] " + c.name + " pos" + c.anchoredPosition + " size" + c.sizeDelta +
" aMin" + c.anchorMin + " children=" + c.childCount + " comps=" + c.GetComponents<Component>().Length);
}
sb.AppendLine("[BattleUI] children=" + (pad != null ? pad.childCount : -1));
if (pad != null)
for (int i = 0; i < pad.childCount; i++)
{
var c = pad.GetChild(i) as RectTransform;
sb.AppendLine(" [" + i + "] " + c.name + " pos" + c.anchoredPosition + " size" + c.sizeDelta +
" aMin" + c.anchorMin + " active=" + c.gameObject.activeSelf);
}
// 배너 · 보스바 세로 겹침 검사(px)
var hudS = WL.UI.WLHudLayoutSettings.Instance;
var bs = WL.UI.WLBossUiSettings.Instance;
if (hudS != null && bs != null)
{
float barTop = hudS.bossBarTopMarginPx;
float barBottom = barTop + hudS.bossBarSizePx.y + hudS.bossBarNameFontPx + hudS.bossBarNameGapPx;
float banTop = bs.bannerTopMarginPx, banBottom = banTop + bs.bannerSizePx.y;
sb.AppendLine();
sb.AppendLine("[겹침 · 상단에서 px] 보스바 " + barTop.ToString("F0") + "~" + barBottom.ToString("F0") +
" · 배너 " + banTop.ToString("F0") + "~" + banBottom.ToString("F0") +
" → 겹침=" + (banTop < barBottom));
sb.AppendLine(" 813g 연쇄문구 = 화면 중앙 위 " + (WL.UI.WLCombatTextSettings.Instance != null ?
WL.UI.WLCombatTextSettings.Instance.killChainTopOffsetPx.ToString("F0") : "?") + " px (중앙 기준 · 상단 밴드와 별개)");
}
// 물약 버튼과 813c 슬롯 간 최소 여백
if (hudS != null)
{
var sur = WL.UI.WLSurvivalUiSettings.Instance;
int idx = sur != null ? sur.potionReserveSlotIndex : 0;
Vector2 pp = hudS.ReserveSlotPx(idx);
float pr = hudS.reserveDiameterPx * 0.5f;
float min = float.MaxValue; string who = "";
for (int i = 0; i < hudS.activeSkillSlots; i++)
{
var q = hudS.FanSlotPx(i);
float g = Vector2.Distance(pp, q) - pr - hudS.slotDiameterPx * 0.5f;
if (g < min) { min = g; who = "slot" + i; }
}
float ga = Vector2.Distance(pp, hudS.attackCenterPx) - pr - hudS.attackDiameterPx * 0.5f;
if (ga < min) { min = ga; who = "Attack"; }
sb.AppendLine();
sb.AppendLine("[물약 버튼] 예약 슬롯" + idx + " dx/dy px=" + pp + " 지름=" + hudS.reserveDiameterPx +
"px · 최소 여백=" + min.ToString("F1") + "px (" + who + ") · 기준 ≥16 → " + (min >= 16f ? "OK" : "🔴"));
sb.AppendLine(" 모서리 도달 반경 = " + (pp.magnitude + pr).ToString("F1") + "px (기준서 목표 " + hudS.thumbReachRadiusPx + "px)");
}
}
finally { PrefabUtility.UnloadPrefabContents(root); }
var body = sb.ToString();
Save("WL813tj_probe_prefab.txt", body);
return body;
}
}
/// <summary>Apply 스크립트와 이름을 맞추기 위한 상수(run_script 는 파일마다 독립 어셈블리라 참조가 안 된다 · 813c 교훈).</summary>
public static class WL813tj_ApplyNames
{
public const string Banner = "WL_BossBanner";
public const string Hit = "WL_HitVignette";
public const string Revive = "WL_ReviveDialog";
public const string Potion = "WL_PotionButton";
}