// WL919_Probe.cs — PD 지시 #919 시험(Play 중 · 섬) // Bat() : 시험 더미(박쥐) · 섬의 몹 — 콜라이더(종류 · 중심 · 크기 · 트리거 · 레이어 · 월드 높이) · 렌더러 높이 · 위치 함수 높이 // Bow() : 활 노드(FindBow 규칙) · 무기 소켓 · 활 그림 중심 · 크기 · PC 키 using System.Text; using UnityEngine; public static class WL919_Probe { static string V(Vector3 v) { return v.ToString("F2"); } public static object Bat() { var sb = new StringBuilder(); foreach (var m in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) { if (m == null) continue; float gy = m.Get_position().y; sb.Append(m.name).Append(" · 위치 y ").Append(gy.ToString("F2")).Append(" · 중심 +").Append((m.Get_Center_position().y - gy).ToString("F2")).Append(" · 머리 +").Append((m.Get_Top_postion().y - gy).ToString("F2")) .Append(" · 배율 ").Append(m.transform.localScale.x.ToString("F3")).Append(" · 레이어 ").Append(LayerMask.LayerToName(m.gameObject.layer)).AppendLine(); var ag = m.GetComponent(); if (ag != null) sb.Append(" agent baseOffset ").Append(ag.baseOffset.ToString("F2")).Append(" 높이 ").Append(ag.height.ToString("F2")).Append(" 반지름 ").Append(ag.radius.ToString("F2")).AppendLine(); foreach (var c in m.GetComponentsInChildren(true)) { sb.Append(" C ").Append(c.GetType().Name).Append(" ").Append(c.transform == m.transform ? "(루트)" : c.transform.name).Append(" 켜짐 ").Append(c.enabled && c.gameObject.activeInHierarchy).Append(" 트리거 ").Append(c.isTrigger) .Append(" 레이어 ").Append(LayerMask.LayerToName(c.gameObject.layer)).Append(" 월드 y ").Append((c.bounds.min.y - gy).ToString("F2")).Append("~").Append((c.bounds.max.y - gy).ToString("F2")).Append(" 폭 ").Append(c.bounds.size.x.ToString("F2")); var cc = c as CapsuleCollider; if (cc != null) sb.Append(" 캡슐 중심 ").Append(V(cc.center)).Append(" r ").Append(cc.radius.ToString("F2")).Append(" h ").Append(cc.height.ToString("F2")).Append(" 축 ").Append(cc.direction); var bc = c as BoxCollider; if (bc != null) sb.Append(" 상자 중심 ").Append(V(bc.center)).Append(" 크기 ").Append(V(bc.size)); var sc = c as SphereCollider; if (sc != null) sb.Append(" 구 중심 ").Append(V(sc.center)).Append(" r ").Append(sc.radius.ToString("F2")); sb.AppendLine(); } Bounds rb = new Bounds(); bool has = false; foreach (var r in m.GetComponentsInChildren(false)) { if (!r.enabled || r is ParticleSystemRenderer) continue; if (!has) { rb = r.bounds; has = true; } else rb.Encapsulate(r.bounds); } if (has) sb.Append(" 그림 y ").Append((rb.min.y - gy).ToString("F2")).Append("~").Append((rb.max.y - gy).ToString("F2")).AppendLine(); } return sb.Length > 0 ? sb.ToString() : "몹 없음"; } // ── 판정 상자 높이 · 스킬별 명중 ── static readonly System.Collections.Generic.Dictionary s_hit = new System.Collections.Generic.Dictionary(); static void OnHit(in WL.Combat.Core.HitConfirmedEvent e) { if (!(e.victim is MobActor)) return; var pd = e.projectile; string k = pd != null && pd.m_SkillListTableData != null ? pd.m_SkillListTableData.e_Skill.ToString() : "기본/기타"; int n; s_hit.TryGetValue(k, out n); s_hit[k] = n + 1; } const string Key = "wl919log"; static StringBuilder Buf() { var sb = System.AppDomain.CurrentDomain.GetData(Key) as StringBuilder; if (sb == null) { sb = new StringBuilder(); System.AppDomain.CurrentDomain.SetData(Key, sb); } return sb; } public static object Log() { return Buf().ToString(); } /// seconds 동안 PC 가 낸 판정 투사체(처음 켜진 순간의 콜라이더 높이 · PC 발 기준) · 스킬별 몹 명중 수 · 핫키 slot(0 = 안 누름). public static object Hits(float seconds, int slot) { var pc = MyValue.MyPC as PCActor; if (pc == null) return "PC 없음"; var sb = Buf(); sb.Length = 0; s_hit.Clear(); WL.Combat.Core.CombatEvents.HitConfirmed.Remove(OnHit); WL.Combat.Core.CombatEvents.HitConfirmed.Add(OnHit); var seen = new System.Collections.Generic.HashSet(); float t0 = Time.time; UnityEngine.Events.UnityAction act = null; act = () => { float t = Time.time - t0; float gy = pc.Get_position().y; foreach (var pb in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) { if (pb == null) continue; var pd = pb.Get_ProjectTileData(); if (pd == null || !ReferenceEquals(pd.shooter, pc)) continue; var cols = pb.GetComponentsInChildren(false); if (cols.Length == 0) continue; bool on = false; Bounds b = new Bounds(); foreach (var c in cols) { if (!c.enabled) continue; if (!on) { b = c.bounds; on = true; } else b.Encapsulate(c.bounds); } if (!on) continue; if (!seen.Add(pb.GetInstanceID())) continue; sb.AppendFormat("{0:F2} 판정 {1} · y {2:F2}~{3:F2} · 폭 {4:F2}×{5:F2}", t, pb.name.Replace("(Clone)", ""), b.min.y - gy, b.max.y - gy, b.size.x, b.size.z).Append('\n'); } if (t > seconds) { Application.onBeforeRender -= act; WL.Combat.Core.CombatEvents.HitConfirmed.Remove(OnHit); sb.Append("끝 · 명중"); foreach (var kv in s_hit) sb.Append(' ').Append(kv.Key).Append(' ').Append(kv.Value); sb.Append('\n'); } }; Application.onBeforeRender += act; return slot > 0 ? "slot" + slot + ": " + WL.Combat.Weapon.WLSkillHotkey.Fire(slot) : "관찰 " + seconds + " 초"; } /// 3타 모으기(~WL_BowCharge) — 위치 · 부모 · 파티클별 공간 · 모양 위치 · 살아 있는 입자 중심(월드) vs 활 그림(렌더러 bounds) 중심 · 화면 좌표. public static object Charge() { var pc = MyValue.MyPC as PCActor; if (pc == null) return "PC 없음"; var sb = new StringBuilder(); var cam = Camera.main; Bounds bow = new Bounds(); bool hasBow = false; if (pc.tfs_weapon != null) foreach (var s in pc.tfs_weapon) if (s != null && s.name == "w_archer_l" && s.gameObject.activeInHierarchy) foreach (var r in s.GetComponentsInChildren(false)) { if (!r.enabled || r.name == "[WL] Glow") continue; if (!hasBow) { bow = r.bounds; hasBow = true; } else bow.Encapsulate(r.bounds); } sb.Append("활 그림 중심 ").Append(hasBow ? V(bow.center - pc.Get_position()) : "-").Append(" · 화면 ").Append(hasBow && cam != null ? cam.WorldToViewportPoint(bow.center).ToString("F3") : "-").AppendLine(); foreach (var t in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) { if (t.name != "~WL_BowCharge") continue; sb.Append("모으기 원점 ").Append(V(t.position - pc.Get_position())).Append(" · 부모 ").Append(t.parent != null ? t.parent.name : "-").Append(" · lossy ").Append(t.lossyScale.x.ToString("F3")).Append(" · 활 중심까지 ").Append(hasBow ? Vector3.Distance(t.position, bow.center).ToString("F2") : "-").AppendLine(); foreach (var ps in t.GetComponentsInChildren(true)) { var m = ps.main; int n = ps.particleCount; Vector3 sum = Vector3.zero; var arr = new ParticleSystem.Particle[Mathf.Max(1, n)]; n = ps.GetParticles(arr); for (int i = 0; i < n; i++) sum += m.simulationSpace == ParticleSystemSimulationSpace.World ? arr[i].position : ps.transform.TransformPoint(arr[i].position); var c = n > 0 ? sum / n : ps.transform.position; sb.Append(" ").Append(ps.name).Append(" ").Append(ps.gameObject.activeInHierarchy ? "" : "(꺼짐) ").Append(m.simulationSpace).Append(" · 크기모드 ").Append(m.scalingMode).Append(" · 입자 ").Append(n) .Append(" · 입자 중심 → 활 중심 ").Append(hasBow && n > 0 ? Vector3.Distance(c, bow.center).ToString("F2") : "-").Append(" m · 원점 → 활 ").Append(hasBow ? Vector3.Distance(ps.transform.position, bow.center).ToString("F2") : "-") .Append(" · 모양 ").Append(ps.shape.enabled ? ps.shape.shapeType + " 위치 " + ps.shape.position.ToString("F2") + " r " + ps.shape.radius.ToString("F2") : "off") .Append(" · 화면 ").Append(cam != null && n > 0 ? cam.WorldToViewportPoint(c).ToString("F3") : "-").AppendLine(); } } return sb.ToString(); } // ── 날아가는 화살 붙잡기 · 가까이 찍기 ── static ProjectileBase s_arrow; /// mode 0 = 기본 겉모습(~WL_Visual 켜짐) · 1 = 3타(~WL_Charged 켜짐) · 2 = 속사(Projectile_ArcherSkill2Arrow) 화살이 PC 에서 minDist m 넘게 날아간 순간 일시 정지 — 진행 방향과 앞(+Z) 사이 각도. public static object Arrow(int mode, float minDist, float maxSeconds) { var pc = MyValue.MyPC as PCActor; if (pc == null) return "PC 없음"; var sb = Buf(); sb.Length = 0; s_arrow = null; var prev = new System.Collections.Generic.Dictionary(); float t0 = Time.time; int ch0 = WL.Combat.Weapon.WLBowChargeReceiver.Charges; float chargeT = -1f; UnityEngine.Events.UnityAction act = null; act = () => { float t = Time.time - t0; if (chargeT < 0f && WL.Combat.Weapon.WLBowChargeReceiver.Charges > ch0) { chargeT = t; sb.AppendFormat("{0:F2} 3타 모으기 시작 · {1}", t, WL.Combat.Weapon.WLBowChargeReceiver.LastLog).Append('\n'); } foreach (var pb in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) { if (pb == null) continue; bool basic = pb.name.StartsWith("Projectile_1040"), rapid = pb.name.StartsWith("Projectile_ArcherSkill2Arrow"); if (!basic && !rapid) continue; var vis = pb.transform.Find("~WL_Visual"); var chg = pb.transform.Find("~WL_Charged"); bool isCharged = chg != null && chg.gameObject.activeSelf; Vector3 p0; bool had = prev.TryGetValue(pb, out p0); prev[pb] = pb.transform.position; if (chargeT >= 0f && isCharged && !sb.ToString().Contains("3타 화살 생성")) sb.AppendFormat("{0:F2} 3타 화살 생성 · 모으기부터 {1:F2} 초", t, t - chargeT).Append('\n'); bool want = mode == 0 ? basic && vis != null && vis.gameObject.activeSelf && !isCharged : mode == 1 ? basic && isCharged : rapid; float dpc = Vector3.Distance(pb.transform.position, pc.Get_position()); if (!want || !had || dpc < minDist || dpc > minDist + 3f) continue; // 멀리 날아가는 옛 관통 화살 제외 var mv = pb.transform.position - p0; if (mv.sqrMagnitude < 1e-6f) continue; Application.onBeforeRender -= act; EditorPause(); s_arrow = pb; System.AppDomain.CurrentDomain.SetData("wl919arrow", pb); // run_script 마다 어셈블리가 새로 — 정적 필드 대신 sb.AppendFormat("■ 일시 정지 {0:F2} · {1} · 진행-앞 각 {2:F0}° · 진행 높이 변화 {3:F2} · lossy {4:F3} · 기본 {5} · 3타 {6}", t, pb.name, Vector3.Angle(mv, pb.transform.forward), mv.normalized.y, pb.transform.lossyScale.x, vis != null && vis.gameObject.activeSelf, isCharged).Append('\n'); return; } if (t > maxSeconds) { Application.onBeforeRender -= act; sb.Append("못 찾음\n"); } }; Application.onBeforeRender += act; return "화살 관찰 " + mode; } static void EditorPause() { #if UNITY_EDITOR UnityEditor.EditorApplication.isPaused = true; #endif } /// 붙잡은 화살을 게임 카메라와 같은 각도로 dist m 앞에서 찍는다(PNG · w×h). public static object Shot(string outFile, float dist, float fov) { if (s_arrow == null) s_arrow = System.AppDomain.CurrentDomain.GetData("wl919arrow") as ProjectileBase; var main = Camera.main; if (main == null || s_arrow == null) return "카메라/화살 없음"; var go = new GameObject("~WL919_Shot"); var cam = go.AddComponent(); cam.enabled = false; cam.CopyFrom(main); cam.fieldOfView = fov; int ui = LayerMask.NameToLayer("UI"); if (ui >= 0) cam.cullingMask &= ~(1 << ui); // 월드 HUD(HP 바 등) 빼기 go.transform.rotation = main.transform.rotation; go.transform.position = s_arrow.transform.position - main.transform.forward * dist; var rt = new RenderTexture(1200, 800, 24, RenderTextureFormat.ARGB32); cam.targetTexture = rt; cam.aspect = 1.5f; cam.Render(); var prev = RenderTexture.active; RenderTexture.active = rt; var tex = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false); tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0); tex.Apply(); RenderTexture.active = prev; System.IO.File.WriteAllBytes(outFile, tex.EncodeToPNG()); Object.Destroy(tex); cam.targetTexture = null; Object.Destroy(rt); Object.Destroy(go); var sp = main.WorldToViewportPoint(s_arrow.transform.position + s_arrow.transform.forward * 0.3f) - main.WorldToViewportPoint(s_arrow.transform.position); return "저장 " + outFile + " · 화면에서 화살 앞 방향 (" + sp.x.ToString("F3") + "," + sp.y.ToString("F3") + ")"; } /// PC → 더미 사이 띠(반경 r m) 안의 켜진 렌더러 — 이름(경로) · 종류 · 재질 · 셰이더 · 파티클 수. public static object Path(float r) { var pc = MyValue.MyPC as PCActor; var d = WL.IslandMob.WLTrailTest.Dummy; if (pc == null || d == null) return "PC/더미 없음"; Vector3 a = pc.Get_position(), b = d.Get_position(); a.y = b.y = 0f; var sb = new StringBuilder(); int n = 0; foreach (var rd in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) { if (rd == null || !rd.enabled || !rd.isVisible) continue; if (rd.transform.IsChildOf(pc.transform) || rd.transform.IsChildOf(d.transform)) continue; var c = rd.bounds.center; c.y = 0f; var ab = b - a; float t = Mathf.Clamp01(Vector3.Dot(c - a, ab) / Mathf.Max(1e-4f, ab.sqrMagnitude)); if (Vector3.Distance(c, a + ab * t) > r || rd.bounds.size.magnitude > 30f) continue; var ps = rd.GetComponent(); var m = rd.sharedMaterial; string path = rd.name; var tr = rd.transform.parent; for (int k = 0; k < 3 && tr != null; k++, tr = tr.parent) path = tr.name + "/" + path; sb.Append(rd.GetType().Name).Append(' ').Append(path).Append(" · 재질 ").Append(m != null ? m.name + " / " + m.shader.name : "-").Append(ps != null ? " · 입자 " + ps.particleCount : "").Append(" · 크기 ").Append(rd.bounds.size.ToString("F2")).AppendLine(); if (++n > 40) break; } return n + " 개\n" + sb; } public static object Bow() { var pc = MyValue.MyPC as PCActor; if (pc == null) return "PC 없음"; var sb = new StringBuilder(); float gy = pc.Get_position().y; sb.Append("PC 키 ").Append((pc.Get_Top_postion().y - gy).ToString("F2")).Append(" · 중심 +").Append((pc.Get_Center_position().y - gy).ToString("F2")).Append(" · 배율 ").Append(pc.transform.localScale.x.ToString("F3")).AppendLine(); Transform bow = null; foreach (var t in pc.GetComponentsInChildren(false)) if (t.gameObject.activeInHierarchy && t.name.IndexOf("Bow", System.StringComparison.OrdinalIgnoreCase) >= 0 && t.name.IndexOf("Elbow", System.StringComparison.OrdinalIgnoreCase) < 0) { bow = t; break; } sb.Append("FindBow → ").Append(bow != null ? bow.name + " (부모 " + (bow.parent != null ? bow.parent.name : "-") + ")" : "없음").AppendLine(); if (pc.tfs_weapon != null) foreach (var s in pc.tfs_weapon) { if (s == null || !s.gameObject.activeInHierarchy) continue; var cl = WL.Combat.Weapon.WLWeaponGlowFx.CenterLocal(s); var cw = s.TransformPoint(cl); Bounds b = new Bounds(); bool has = false; foreach (var r in s.GetComponentsInChildren(false)) { if (!r.enabled || r.name == "[WL] Glow") continue; if (!has) { b = r.bounds; has = true; } else b.Encapsulate(r.bounds); } sb.Append("소켓 ").Append(s.name).Append(" · 그림 중심 ").Append(V(cw - pc.Get_position())).Append(" · 소켓 원점 ").Append(V(s.position - pc.Get_position())) .Append(" · 원점-중심 ").Append(Vector3.Distance(s.position, cw).ToString("F2")).Append(" m · 그림 크기 ").Append(has ? V(b.size) : "-").Append(" · lossy ").Append(s.lossyScale.x.ToString("F3")); if (bow != null) sb.Append(" · FindBow-중심 ").Append(Vector3.Distance(bow.position, cw).ToString("F2")).Append(" m"); sb.AppendLine(); } return sb.ToString(); } }