Project_WL/AgentScripts/WL815c_Capture.cs

242 lines
13 KiB
C#
Raw Permalink Normal View History

// ─────────────────────────────────────────────────────────────────────────────
// 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;
}
}