// ───────────────────────────────────────────────────────────────────────────── // WL815c_Capture.cs — PD 대안 비교 캡처 6장 (에디트 모드 · 임시 씬 저장 0 · 커밋 0) // unity command run_script --file AgentScripts/WL815c_Capture.cs --entry WL815c_Capture.Run // // 3스타일(마법진 링 / 프롭 링 / 불꽃 링) × 2구도 // persp : 811o 구도 각도(cameraHeight 76 · MapZoneData yaw 233 실측) · 원근 · 반경 14 m 전체가 보이게 거리만 확장 // pixel : 814c 방식 = 직교 270×480 렌더 → Point 확대 1080×1920 (도트 룩에서 뭉개지는지 본다) // 앵커 = 기준서 §D-1 존1 (115, 0.4, −22) · 반경 14 // ───────────────────────────────────────────────────────────────────────────── using System; using System.Collections.Generic; using System.IO; using System.Text; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.Rendering.Universal; using UnityEngine.SceneManagement; using WL.Combat.Stage; public static class WL815c_Capture { const string kOutDir = @"E:\NerdNavis\nn_himminji\Screenshots_WL\WL815c"; const string kTxt = "AgentScripts/WL815c_CAPTURE.txt"; const string kMapPrefab = "Assets/Res_Addr/Map/WL_Nature.prefab"; const string kPcPrefab = "Assets/Res_Addr/PC/Ai01.prefab"; const int kLowW = 270, kLowH = 480, kHiW = 1080, kHiH = 1920; const float kMapYaw = 233f; // WL_Nature MapZoneData.CamRot_Portal 실측(811o) const float kCamH = 76f; // WLCameraFramingSettings.cameraHeight 실측(811o) static readonly Vector3 kAnchor = new Vector3(115f, 0.4f, -22f); const float kRadius = 14f; static StringBuilder s_sb; static int s_pass, s_fail; static void L(string s) { s_sb.AppendLine(s); } static void Chk(string n, bool ok, string d) { if (ok) s_pass++; else s_fail++; L((ok ? " PASS " : " FAIL ") + n + " — " + d); } public static string Run() { s_sb = new StringBuilder(); s_pass = 0; s_fail = 0; L("WL-815c 장벽 3스타일 캡처 · " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); L(" 앵커 " + kAnchor.ToString("F1") + " 반경 " + kRadius + " m · 구도 811o(h " + kCamH + " · yaw " + kMapYaw + ")"); var keepSo = WLStageBarrierSettings.Instance; try { Directory.CreateDirectory(kOutDir); EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects, NewSceneMode.Single); foreach (var go in SceneManager.GetActiveScene().GetRootGameObjects()) if (go.GetComponent() != null) UnityEngine.Object.DestroyImmediate(go); var map = Inst(kMapPrefab, Vector3.zero); Chk("E0 맵", map != null, "WL_Nature"); if (map == null) return "FAIL 맵 없음"; // 앵커 지면 실측 Vector3 center = kAnchor; RaycastHit gh; if (Physics.Raycast(kAnchor + Vector3.up * 100f, Vector3.down, out gh, 300f)) center.y = gh.point.y; L(" 존1 지면 실측 y = " + center.y.ToString("F2") + " (앵커 표기 y " + kAnchor.y + ")"); // PC 를 중앙에 세워 크기 비교 기준을 만든다 var pc = Inst(kPcPrefab, center); if (pc != null) { Vector3 f = new Vector3(Mathf.Sin(kMapYaw * Mathf.Deg2Rad), 0f, Mathf.Cos(kMapYaw * Mathf.Deg2Rad)); pc.transform.rotation = Quaternion.LookRotation(-f, Vector3.up); // 🔴 Animator.Update 는 부르지 않는다 — 에디트 모드는 Awake 가 안 돌아 // Actor.Event_Idle(Actor.cs:2297)이 미할당 m_animation 을 만져 콘솔 예외가 난다(실측). // 이 캡처에서 PC 는 크기 비교용이라 포즈는 중요하지 않다. } Chk("E1 PC", pc != null, "Ai01 중앙(크기 비교 기준)"); StageBarrier.HardResetForProbe(); WLStageBarrierSettings.SetInstanceForProbe(keepSo); var styles = new StageBarrierStyle[] { StageBarrierStyle.MagicRing, StageBarrierStyle.Props, StageBarrierStyle.Fire }; var names = new string[] { "ring", "props", "fire" }; for (int i = 0; i < styles.Length; i++) { StageBarrierVisual.StyleOverride = styles[i]; StageBarrier.Set(center, kRadius); StageBarrierVisual.ForcePhase(1f); // 입장 연출 끝난 상태 StageBarrierVisual.SimulateForProbe(2.0f); // 파티클을 2 s 진행(에디트 모드) string info = Info(styles[i]); ShotPersp(center, kOutDir + @"\barrier_" + names[i] + "_persp.png", styles[i] + " · " + info); ShotPixel(center, kOutDir + @"\barrier_" + names[i] + "_pixel.png", styles[i] + " · " + info); } // 클리어 연출 확인용(파일로 남기지 않고 값만) StageBarrier.Clear(); StageBarrierVisual.ForcePhase(0.5f); L(" 클리어 연출 중간(t=0.5) 활성 " + StageBarrierVisual.LiveCount + " · 진행도 " + StageBarrierVisual.Phase.ToString("F2")); } catch (Exception ex) { s_fail++; L(" FAIL 예외 — " + ex.GetType().Name + " " + ex.Message + "\n" + ex.StackTrace); } finally { try { StageBarrier.HardResetForProbe(); } catch { } StageBarrierVisual.StyleOverride = (StageBarrierStyle)(-1); WLStageBarrierSettings.SetInstanceForProbe(keepSo); } L(""); L("RESULT " + (s_fail == 0 ? "PASS" : "FAIL") + " · " + s_pass + " PASS · " + s_fail + " FAIL"); File.WriteAllText(kTxt, s_sb.ToString()); return (s_fail == 0 ? "PASS" : "FAIL") + " " + s_pass + "/" + (s_pass + s_fail); } static string Info(StageBarrierStyle st) { int rend, mats, ps; long tri; StageBarrierVisual.MeasureCost(out rend, out mats, out tri, out ps); string x = "인스턴스 " + StageBarrierVisual.LiveCount + " · 렌더러 " + rend + " · 머티리얼 " + mats + " · tri " + tri + " · PS " + ps; if (st == StageBarrierStyle.MagicRing) x += " · 링 스케일 " + StageBarrierVisual.RingScale.ToString("F2"); else if (st == StageBarrierStyle.Props) x += " · 프롭 " + StageBarrierVisual.PropCount + " · 간격 " + StageBarrierVisual.PropSpacingUsed.ToString("F2") + " m"; else x += " · 불꽃 " + StageBarrierVisual.FireCount + " · 방출률 ×" + StageBarrierVisual.FireRateMul.ToString("F2") + " · 파티클 " + StageBarrierVisual.FireParticleEstimate; return x; } // ── 원근 구도(811o 각도 · 링 전체가 들어오게 거리만 확장) ─────────────── static void ShotPersp(Vector3 center, string file, string label) { var camGo = new GameObject("[WL815c] PerspCam"); var cam = camGo.AddComponent(); var acd = camGo.AddComponent(); acd.renderShadows = true; acd.renderPostProcessing = false; acd.antialiasing = AntialiasingMode.None; cam.orthographic = false; cam.fieldOfView = 45f; cam.nearClipPlane = 0.3f; cam.farClipPlane = 500f; cam.clearFlags = CameraClearFlags.Skybox; // 811o 와 같은 방향 벡터(h = cameraHeight 76 · yaw = 233) Vector3 dir = (Quaternion.Euler(kCamH, kMapYaw, 0f) * Vector3.one).normalized; // 반경 14 m 원이 세로 1080×1920 화면에 들어오려면 = r / tan(가로 FOV/2) float aspect = kHiW / (float)kHiH; float halfV = cam.fieldOfView * 0.5f * Mathf.Deg2Rad; float halfH = Mathf.Atan(Mathf.Tan(halfV) * aspect); float dist = (kRadius * 1.08f) / Mathf.Tan(halfH); cam.transform.position = center + Vector3.up * 2.4f - dir * dist; cam.transform.LookAt(center + Vector3.up * 1.0f); Shot(cam, file, label + " · 원근 FOV 45 · 거리 " + dist.ToString("F1") + " m", false); UnityEngine.Object.DestroyImmediate(camGo); } // ── 도트 룩 근사(814c 방식 · 직교 270×480 → Point 확대) ───────────────── static void ShotPixel(Vector3 center, string file, string label) { var camGo = new GameObject("[WL815c] PixelCam"); var cam = camGo.AddComponent(); var acd = camGo.AddComponent(); acd.renderShadows = true; acd.renderPostProcessing = false; acd.antialiasing = AntialiasingMode.None; Vector3 dir = (Quaternion.Euler(kCamH, kMapYaw, 0f) * Vector3.one).normalized; cam.orthographic = true; // 세로 1080×1920 · 가로가 좁다 → 가로에 반경이 들어가야 한다: orthoSize(세로 반) = r×1.2 / aspect float aspect = kHiW / (float)kHiH; cam.orthographicSize = kRadius * 1.05f / aspect; cam.nearClipPlane = 0.1f; cam.farClipPlane = 300f; cam.clearFlags = CameraClearFlags.Skybox; cam.transform.position = center + Vector3.up * 2.4f - dir * 60f; cam.transform.LookAt(center + Vector3.up * 1.0f); Shot(cam, file, label + " · 직교 size " + cam.orthographicSize.ToString("F1") + " · " + kLowW + "×" + kLowH + " → Point 확대", true); UnityEngine.Object.DestroyImmediate(camGo); } static void Shot(Camera cam, string file, string label, bool pixel) { RenderTexture low = null, hi = null; Texture2D tex = null; try { if (pixel) { low = new RenderTexture(kLowW, kLowH, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); low.filterMode = FilterMode.Point; low.antiAliasing = 1; low.Create(); hi = new RenderTexture(kHiW, kHiH, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); hi.filterMode = FilterMode.Point; hi.Create(); cam.targetTexture = low; cam.Render(); cam.targetTexture = null; Graphics.Blit(low, hi); // Point 확대 = 픽셀 카메라 근사 } else { hi = new RenderTexture(kHiW, kHiH, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); hi.Create(); cam.targetTexture = hi; cam.Render(); cam.targetTexture = null; } int dc = UnityEditor.UnityStats.drawCalls, batches = UnityEditor.UnityStats.batches, tri = UnityEditor.UnityStats.triangles; var active = RenderTexture.active; RenderTexture.active = hi; tex = new Texture2D(kHiW, kHiH, TextureFormat.RGB24, false); tex.ReadPixels(new Rect(0, 0, kHiW, kHiH), 0, 0); tex.Apply(); RenderTexture.active = active; File.WriteAllBytes(file, tex.EncodeToPNG()); var px = tex.GetPixels32(); var seen = new HashSet(); long warm = 0; for (int i = 0; i < px.Length; i += 331) { seen.Add((px[i].r << 16) | (px[i].g << 8) | px[i].b); if (px[i].r > 120 && px[i].r > px[i].b + 40) warm++; // 주황·붉은 픽셀 = 장벽 표식 } var fi = new FileInfo(file); Chk("E " + Path.GetFileName(file), fi.Exists && fi.Length > 4096 && seen.Count > 4, label + " · " + fi.Length + " B · 색 " + seen.Count + "종 · 따뜻한픽셀 " + warm + " · drawCalls " + dc + " · batches " + batches + " · tri " + tri); } finally { RenderTexture.active = null; if (tex != null) UnityEngine.Object.DestroyImmediate(tex); if (low != null) { low.Release(); UnityEngine.Object.DestroyImmediate(low); } if (hi != null) { hi.Release(); UnityEngine.Object.DestroyImmediate(hi); } } } // ── 도구 ──────────────────────────────────────────────────────────────── static GameObject Inst(string path, Vector3 pos) { var p = AssetDatabase.LoadAssetAtPath(path); if (p == null) { L(" [없음] " + path); return null; } var go = UnityEngine.Object.Instantiate(p); go.transform.position = pos; return go; } }