Project_WL/AgentScripts/WL815c_Capture.cs

242 lines
13 KiB
C#
Raw Permalink 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.

// ─────────────────────────────────────────────────────────────────────────────
// 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<Camera>() != 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<Camera>();
var acd = camGo.AddComponent<UniversalAdditionalCameraData>();
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<Camera>();
var acd = camGo.AddComponent<UniversalAdditionalCameraData>();
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<int>();
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<GameObject>(path);
if (p == null) { L(" [없음] " + path); return null; }
var go = UnityEngine.Object.Instantiate(p);
go.transform.position = pos;
return go;
}
}