242 lines
13 KiB
C#
242 lines
13 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// 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;
|
||
}
|
||
|
||
}
|