// WL917_Probe.cs — #917 Casual RPG VFX 후보 미리보기(Play 중) — PC 둘레 격자에 후보 프리팹을 띄우고 화면 좌표 표를 남긴다 // Grid(pathsCsv, spacing, scale) · Log() using System.Text; using UnityEditor; using UnityEngine; public static class WL917_Probe { const string Key = "wl917log"; 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(); } static string Vp(Vector3 w) { var cam = Camera.main; if (cam == null) return "-"; var v = cam.WorldToViewportPoint(w); return v.x.ToString("F3") + "," + (1f - v.y).ToString("F3"); } static Actor Mob(float dist) { var pc = MyValue.MyPC as PCActor; Actor d = WL.IslandMob.WLTrailTest.Dummy; if (d == null || !d.isActiveAndEnabled) { float bd = float.MaxValue; foreach (var m in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) { if (m == null || m.IsDead()) continue; float dd = Vector3.Distance(pc.Get_position(), m.Get_position()); if (dd < bd) { bd = dd; d = m; } } } if (d != null && dist > 0f) { var f = pc.transform.forward; f.y = 0f; f.Normalize(); var want = pc.Get_position() + f * dist; UnityEngine.AI.NavMeshHit hit; if (UnityEngine.AI.NavMesh.SamplePosition(want, out hit, 1.5f, UnityEngine.AI.NavMesh.AllAreas)) want = hit.position; d.Stop(true); d.Set_Warp(want); pc.Change_Target(true, d); } return d; } /// 기본 공격 관찰 — mode 0: 겉모습 붙은 화살(~WL_Visual)이 날아가는 순간 · 1: 3타 기 모으기(WLBowCharge) 순간 · 2: 화살 명중 이펙트(WL_Fx_HitLight) 순간에 일시 정지. public static object PauseOn(int mode, float dist, float maxSeconds) { var pc = MyValue.MyPC as PCActor; if (pc == null) return "PC 없음"; var d = Mob(dist); var sb = Buf(); sb.Length = 0; float t0 = Time.time; int ch0 = WL.Combat.Weapon.WLBowChargeReceiver.Charges; float chargeAt = -1f; UnityEngine.Events.UnityAction act = null; act = () => { float t = Time.time - t0; Vector3? at = null; if (mode == 0) foreach (var pb in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) if (pb != null && pb.name.StartsWith("Projectile_1040") && pb.transform.Find("~WL_Visual") != null && Vector3.Distance(pb.transform.position, pc.Get_position()) > 2.6f) { at = pb.transform.position; break; } if (mode == 1 && WL.Combat.Weapon.WLBowChargeReceiver.Charges > ch0) { if (chargeAt < 0f) chargeAt = t; if (t - chargeAt >= 0.2f) at = pc.Get_Center_position(); } if (mode == 2) { var h = GameObject.Find("WL_Fx_HitLight(Clone)"); if (h != null) at = h.transform.position; } if (at.HasValue || t > maxSeconds) { Application.onBeforeRender -= act; if (at.HasValue) EditorApplication.isPaused = true; sb.AppendFormat("{0} · t {1:F2} · 대상 화면 {2} · PC 화면 {3} · 몹 화면 {4} · 겉모습 붙임 {5} · 명중 {6} · {7}", at.HasValue ? "■ 일시 정지" : "못 찾음", t, at.HasValue ? Vp(at.Value) : "-", Vp(pc.Get_Center_position()), d != null ? Vp(d.Get_Center_position()) : "-", WL.Combat.Weapon.WLProjectileVisual.Attached, WL.Combat.Weapon.WLProjectileHitFx.Shown, WL.Combat.Weapon.WLProjectileVisual.LastLog).Append('\n'); } }; Application.onBeforeRender += act; return "관찰 " + mode; } /// 3타 기 모으기 연출을 직접 띄우고(WLBowChargeReceiver.WLBowCharge · dur 초) after 초 뒤 일시 정지. public static object Charge(float dur, float after) { var pc = MyValue.MyPC; if (pc == null) return "PC 없음"; var rc = pc.GetComponent(); if (rc == null) return "수신기 없음"; rc.WLBowCharge(new AnimationEvent { floatParameter = dur }); float t0 = Time.time; UnityEngine.Events.UnityAction act = null; act = () => { if (Time.time - t0 >= after) { Application.onBeforeRender -= act; EditorApplication.isPaused = true; } }; Application.onBeforeRender += act; return "모으기 · PC 화면 " + Vp(pc.Get_Center_position()) + " · " + WL.Combat.Weapon.WLBowChargeReceiver.LastLog; } static int s_rainHits; static string s_rainLast = ""; static void OnRainHit(in WL.Combat.Core.HitConfirmedEvent e) { var pd = e.projectile; if (pd == null || pd.m_SkillListTableData == null) return; if (pd.m_SkillListTableData.e_Skill.ToString() != "Skill_ArrowRain") return; s_rainHits++; s_rainLast = e.victim != null ? e.victim.name : "-"; } /// 활 각성(E · 화살 비) — 대상 몹 dist m · 프레임마다 명중 수 · pauseAt 에 일시 정지(화면 좌표). public static object Rain(float dist, float pauseAt, float seconds) { var pc = MyValue.MyPC as PCActor; if (pc == null) return "PC 없음"; var d = Mob(dist); var sb = Buf(); sb.Length = 0; s_rainHits = 0; WL.Combat.Core.CombatEvents.HitConfirmed.Remove(OnRainHit); WL.Combat.Core.CombatEvents.HitConfirmed.Add(OnRainHit); float t0 = Time.time; bool paused = false; int last = -1; int c0 = WL.Combat.Weapon.WLSkillVfx.Casters, ce0 = WL.Combat.Weapon.WLSkillVfx.Centers; UnityEngine.Events.UnityAction act = null; act = () => { float t = Time.time - t0; if (s_rainHits != last) { last = s_rainHits; string box = "-"; foreach (var s in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) { var bc = s.GetComponent(); box = (bc != null ? bc.size.x.ToString("F1") : "?") + "m/" + s.Loop + "회/" + s.DelayTime.ToString("F3"); } sb.AppendFormat("{0:F2} 명중 {1} ({2}) · 판정 {3}", t, s_rainHits, s_rainLast, box).Append('\n'); } if (!paused && pauseAt > 0f && t >= pauseAt) { paused = true; EditorApplication.isPaused = true; sb.AppendFormat("■ 일시 정지 · PC 화면 {0} · 몹 화면 {1}", Vp(pc.Get_position()), d != null ? Vp(d.Get_position()) : "-").Append('\n'); } if (Mathf.Repeat(t, 0.25f) < Time.deltaTime && d != null) foreach (var s in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) { var v = d.Get_position() - s.transform.position; v.y = 0f; sb.AppendFormat("{0:F2} 몹-판정 중심 {1:F2} m · 몹 상태 {2}", t, v.magnitude, d.Get_CurAnim()).Append('\n'); } if (t > seconds) { Application.onBeforeRender -= act; WL.Combat.Core.CombatEvents.HitConfirmed.Remove(OnRainHit); sb.AppendFormat("끝 · 명중 {0} · 시전자 원 +{1} · 빔 +{2} · {3}", s_rainHits, WL.Combat.Weapon.WLSkillVfx.Casters - c0, WL.Combat.Weapon.WLSkillVfx.Centers - ce0, WL.Combat.Weapon.WLSkillVfx.LastLog).Append('\n'); } }; Application.onBeforeRender += act; return "E: " + WL.Combat.Weapon.WLSkillHotkey.Fire(2); } /// 검은 배경 미리보기 — 장면 위 높은 곳(y 1000)에 후보를 한 줄로 띄우고 시각별로 파티클을 Simulate 한 뒤 전용 카메라로 PNG 저장(이름 · 시각 순). public static object Preview(string pathsCsv, float scale, float spacing, string timesCsv, string outPrefix, float pitch) { var paths = pathsCsv.Split('|'); var times = System.Array.ConvertAll(timesCsv.Split(','), s => float.Parse(s, System.Globalization.CultureInfo.InvariantCulture)); var root = new GameObject("~WL917_Preview"); root.transform.position = new Vector3(0f, 1000f, 0f); var inst = new GameObject[paths.Length]; var sb = new StringBuilder(); for (int i = 0; i < paths.Length; i++) { var prefab = AssetDatabase.LoadAssetAtPath(paths[i]); if (prefab == null) { sb.AppendLine("없음 " + paths[i]); continue; } var go = Object.Instantiate(prefab, root.transform); go.transform.localPosition = new Vector3((i - (paths.Length - 1) * 0.5f) * spacing, 0f, 0f); go.transform.localScale = prefab.transform.localScale * scale; foreach (var ps in go.GetComponentsInChildren(true)) { var m = ps.main; m.scalingMode = ParticleSystemScalingMode.Hierarchy; } inst[i] = go; } var camGo = new GameObject("~WL917_Cam"); var cam = camGo.AddComponent(); float width = spacing * paths.Length; cam.clearFlags = CameraClearFlags.SolidColor; cam.backgroundColor = new Color(0.08f, 0.08f, 0.12f, 1f); cam.orthographic = false; cam.fieldOfView = 30f; cam.nearClipPlane = 0.1f; cam.farClipPlane = 200f; float dist = width * 0.5f / Mathf.Tan(15f * Mathf.Deg2Rad) / 1.9f; camGo.transform.rotation = Quaternion.Euler(pitch, 0f, 0f); camGo.transform.position = root.transform.position + new Vector3(0f, 0.5f, 0f) - camGo.transform.forward * dist; var rt = new RenderTexture(1600, 520, 24, RenderTextureFormat.ARGB32); cam.targetTexture = rt; cam.aspect = 1600f / 520f; foreach (var t in times) { foreach (var go in inst) if (go != null) foreach (var ps in go.GetComponentsInChildren(true)) if (ps.transform.parent == null || ps.transform.parent.GetComponentInParent() == null) ps.Simulate(t, true, true, false); 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; var file = outPrefix + "_" + t.ToString("F2", System.Globalization.CultureInfo.InvariantCulture) + ".png"; System.IO.File.WriteAllBytes(file, tex.EncodeToPNG()); Object.Destroy(tex); sb.AppendLine("저장 " + file); } cam.targetTexture = null; Object.Destroy(rt); Object.Destroy(camGo); Object.Destroy(root); for (int i = 0; i < paths.Length; i++) sb.Append(i).Append(' ').Append(System.IO.Path.GetFileNameWithoutExtension(paths[i])).Append(" · "); return sb.ToString(); } /// 후보 1 개를 PC 옆(카메라 오른쪽 side m)에 띄우고 pauseAt 에 일시 정지 — 화면 좌표. public static object One(string path, float side, float scale, float pauseAt) { var pc = MyValue.MyPC; if (pc == null) return "PC 없음"; var cam = Camera.main; var prefab = AssetDatabase.LoadAssetAtPath(path); if (prefab == null) return "없음 " + path; var right = cam.transform.right; right.y = 0f; right.Normalize(); var pos = pc.Get_position() + right * side; var go = Object.Instantiate(prefab, pos, prefab.transform.rotation); go.transform.localScale = prefab.transform.localScale * scale; foreach (var ps in go.GetComponentsInChildren(true)) { var m = ps.main; m.scalingMode = ParticleSystemScalingMode.Hierarchy; } Object.Destroy(go, 6f); float t0 = Time.time; UnityEngine.Events.UnityAction act = null; act = () => { if (Time.time - t0 >= pauseAt) { Application.onBeforeRender -= act; EditorApplication.isPaused = true; } }; Application.onBeforeRender += act; var vp = cam.WorldToViewportPoint(pos); return string.Format("{0} · 화면 {1:F3},{2:F3}", System.IO.Path.GetFileNameWithoutExtension(path), vp.x, 1f - vp.y); } public static object Grid(string pathsCsv, float spacing, float scale, float pauseAt) { var pc = MyValue.MyPC; if (pc == null) return "PC 없음"; var cam = Camera.main; var sb = Buf(); sb.Length = 0; var paths = pathsCsv.Split('|'); int cols = 4; var right = cam != null ? cam.transform.right : Vector3.right; right.y = 0f; right.Normalize(); var fwd = Vector3.Cross(right, Vector3.up) * -1f; var origin = pc.Get_position() - right * spacing * 1.5f + fwd * spacing * 0.5f; for (int i = 0; i < paths.Length; i++) { var prefab = AssetDatabase.LoadAssetAtPath(paths[i]); if (prefab == null) { sb.AppendLine("없음 " + paths[i]); continue; } var pos = origin + right * spacing * (i % cols) - fwd * spacing * (i / cols); var go = Object.Instantiate(prefab, pos, prefab.transform.rotation); go.transform.localScale = prefab.transform.localScale * scale; foreach (var ps in go.GetComponentsInChildren(true)) { var m = ps.main; m.scalingMode = ParticleSystemScalingMode.Hierarchy; } Object.Destroy(go, 6f); var vp = cam != null ? cam.WorldToViewportPoint(pos) : Vector3.zero; sb.AppendFormat("{0} {1} · 화면 {2:F3},{3:F3}", i, System.IO.Path.GetFileNameWithoutExtension(paths[i]), vp.x, 1f - vp.y).AppendLine(); } if (pauseAt > 0f) { float t0 = Time.time; UnityEngine.Events.UnityAction act = null; act = () => { if (Time.time - t0 >= pauseAt) { Application.onBeforeRender -= act; EditorApplication.isPaused = true; } }; Application.onBeforeRender += act; } return sb.ToString(); } }