638 lines
37 KiB
C#
638 lines
37 KiB
C#
|
|
// PD 지시 #814 · 발주서 WL-814b — Critter 픽셀 카메라 리그 검증 프로브 + 룩 캡처
|
|||
|
|
// (에디트 모드 전용 · Play 0 · 로그인 0 · 디스크 에셋 무수정)
|
|||
|
|
//
|
|||
|
|
// 실행 (CLAUDE.md §1: using 금지 · 네임스페이스 풀어 쓰기)
|
|||
|
|
// unity command run_script --file AgentScripts/WL814b_Probe.cs --entry WL814b_Probe.MakeAsset --timeout 300
|
|||
|
|
// unity command run_script --file AgentScripts/WL814b_Probe.cs --entry WL814b_Probe.Edit --timeout 1800
|
|||
|
|
//
|
|||
|
|
// 무엇을 재는가
|
|||
|
|
// A. 표 ① 보조 — 원본 Main Camera.prefab 값 · NewGameUI 캔버스 렌더 모드/카메라 · UICamera 스택
|
|||
|
|
// B. 리그 — 임시 씬(추가 로드 · 저장 0)에 맵 + PC + 잡몹 + 원본 카메라 → 조립 → 구조 표
|
|||
|
|
// C. 복원 — 원본 카메라 값 스냅샷 == 복원값(문자열 동일) · 리그 오브젝트 0 · RT 해제
|
|||
|
|
// D. 매핑오차 — Camera.main 투영(WorldToViewportPoint) vs 실제 RT→쿼드→뷰 카메라 화면 좌표(5점 · px)
|
|||
|
|
// E. GC — 프록시 미러 · 회전 스냅 판정 200k 회 할당 증가분(0 B 목표)
|
|||
|
|
// F. C8 — RuntimeDisabled = true → Tick 이 즉시 복원 · 조립 0
|
|||
|
|
// G. 캡처 — 1080×1920 4장(ortho 3.8×480 · 3.8×360 · 4.6×480 · 대조 원근 원본)
|
|||
|
|
public static class WL814b_Probe
|
|||
|
|
{
|
|||
|
|
const string kOutDir = @"E:\NerdNavis\nn_himminji\Screenshots_WL\WL814b";
|
|||
|
|
const string kMap = "Assets/Res_Addr/Map/WL_Nature.prefab";
|
|||
|
|
const string kPc = "Assets/Res_Addr/PC/Ai01.prefab";
|
|||
|
|
const string kCam = "Assets/ResWork/Prefabs/Ingame/Main Camera.prefab";
|
|||
|
|
const string kRig = "Assets/3DPixelCamera/URP/PixelCameraSystem.prefab";
|
|||
|
|
const string kSo = "Assets/WL/Combat/Settings/Resources/WL/WLPixelCameraSettings.asset";
|
|||
|
|
const string kSnap = "AgentScripts/staging/WL814b/EDIT_PROBE.txt";
|
|||
|
|
|
|||
|
|
const int W = 1080, H = 1920;
|
|||
|
|
|
|||
|
|
static System.Text.StringBuilder sb;
|
|||
|
|
static int pass, fail;
|
|||
|
|
static UnityEngine.Vector3 s_lookAtWorld;
|
|||
|
|
|
|||
|
|
static void L(string s) { sb.Append(s).Append('\n'); }
|
|||
|
|
static void Chk(bool ok, string label, string detail)
|
|||
|
|
{
|
|||
|
|
if (ok) pass++; else fail++;
|
|||
|
|
sb.Append(ok ? " PASS " : " FAIL ").Append(label);
|
|||
|
|
if (!string.IsNullOrEmpty(detail)) sb.Append(" ").Append(detail);
|
|||
|
|
sb.Append('\n');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|||
|
|
// SO 에셋 생성 (1회) — 손으로 YAML 쓰지 않고 에디터가 만들게 한다
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|||
|
|
public static string MakeAsset()
|
|||
|
|
{
|
|||
|
|
sb = new System.Text.StringBuilder();
|
|||
|
|
var dir = System.IO.Path.GetDirectoryName(kSo).Replace('\\', '/');
|
|||
|
|
if (!System.IO.Directory.Exists(dir)) { L("FAIL 폴더 없음 " + dir); return sb.ToString(); }
|
|||
|
|
|
|||
|
|
var so = UnityEditor.AssetDatabase.LoadAssetAtPath<WL.PixelArt.WLPixelCameraSettings>(kSo);
|
|||
|
|
bool created = false;
|
|||
|
|
if (so == null)
|
|||
|
|
{
|
|||
|
|
so = UnityEngine.ScriptableObject.CreateInstance<WL.PixelArt.WLPixelCameraSettings>();
|
|||
|
|
UnityEditor.AssetDatabase.CreateAsset(so, kSo);
|
|||
|
|
created = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var rig = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(kRig);
|
|||
|
|
so.pixelCameraSystemPrefab = rig;
|
|||
|
|
UnityEditor.EditorUtility.SetDirty(so);
|
|||
|
|
UnityEditor.AssetDatabase.SaveAssets();
|
|||
|
|
UnityEditor.AssetDatabase.Refresh();
|
|||
|
|
|
|||
|
|
L((created ? "생성" : "갱신") + " " + kSo);
|
|||
|
|
L(" prefab=" + (rig == null ? "null(패키지 해제 확인)" : UnityEditor.AssetDatabase.GetAssetPath(rig)));
|
|||
|
|
L(" guid=" + UnityEditor.AssetDatabase.AssetPathToGUID(kSo));
|
|||
|
|
L(" enabled=" + so.enabled + " orthoSize=" + so.orthoSize + " pixelHeight=" + so.pixelHeight
|
|||
|
|
+ " rigLayer=" + so.rigLayer + " proxyMode=" + so.proxyMode
|
|||
|
|
+ " compensateViewZoom=" + so.compensateViewZoom);
|
|||
|
|
return sb.ToString();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|||
|
|
// 본 프로브
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|||
|
|
public static string Edit()
|
|||
|
|
{
|
|||
|
|
sb = new System.Text.StringBuilder();
|
|||
|
|
pass = fail = 0;
|
|||
|
|
System.IO.Directory.CreateDirectory(kOutDir);
|
|||
|
|
|
|||
|
|
L("# WL-814b 프로브 — " + System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
|
|||
|
|
L("unity=" + UnityEngine.Application.unityVersion + " · isPlaying=" + UnityEngine.Application.isPlaying);
|
|||
|
|
|
|||
|
|
SectionA();
|
|||
|
|
|
|||
|
|
// 배치모드 에디터의 현재(무제목·빈) 씬에 직접 짓는다 — 추가 씬 생성은 무제목 씬이 열려 있으면 금지된다.
|
|||
|
|
// 모든 인스턴스는 HideAndDontSave + DestroyImmediate 이므로 씬에 남지도, 저장되지도 않는다(811o 방식).
|
|||
|
|
var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
|
|||
|
|
L("현재 씬 = '" + scene.name + "' path='" + scene.path + "' dirty(before)=" + scene.isDirty
|
|||
|
|
+ " rootCount=" + scene.rootCount);
|
|||
|
|
|
|||
|
|
UnityEngine.GameObject mapGo = null, pcGo = null, camGo = null, lightGo = null;
|
|||
|
|
var mobs = new System.Collections.Generic.List<UnityEngine.GameObject>();
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
BuildScene(ref mapGo, ref pcGo, ref camGo, ref lightGo, mobs);
|
|||
|
|
if (camGo == null) { L("FAIL 카메라 인스턴스 실패 — 중단"); }
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
var cam = camGo.GetComponent<UnityEngine.Camera>();
|
|||
|
|
SectionBCD(cam, pcGo);
|
|||
|
|
SectionE(cam);
|
|||
|
|
SectionF(cam);
|
|||
|
|
SectionG(cam);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (System.Exception ex) { L("EXCEPTION " + ex.GetType().Name + " " + ex.Message); L(ex.StackTrace); fail++; }
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
WL.PixelArt.PixelCameraRig.Teardown("probe end");
|
|||
|
|
WL.PixelArt.WLPixelCameraSettings.RuntimeDisabled = false;
|
|||
|
|
for (int i = 0; i < mobs.Count; i++) if (mobs[i] != null) UnityEngine.Object.DestroyImmediate(mobs[i]);
|
|||
|
|
if (pcGo != null) UnityEngine.Object.DestroyImmediate(pcGo);
|
|||
|
|
if (camGo != null) UnityEngine.Object.DestroyImmediate(camGo);
|
|||
|
|
if (mapGo != null) UnityEngine.Object.DestroyImmediate(mapGo);
|
|||
|
|
if (lightGo != null) UnityEngine.Object.DestroyImmediate(lightGo);
|
|||
|
|
var after = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
|
|||
|
|
L("씬 정리 후: dirty=" + after.isDirty + " rootCount=" + after.rootCount + " (저장 0)");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
L("");
|
|||
|
|
L("RESULT " + (fail == 0 ? "PASS" : "FAIL") + " " + pass + "/" + (pass + fail));
|
|||
|
|
var outPath = kSnap;
|
|||
|
|
System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(outPath));
|
|||
|
|
System.IO.File.WriteAllText(outPath, sb.ToString(), new System.Text.UTF8Encoding(false));
|
|||
|
|
return sb.ToString();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── A. 표 ① 보조 실측 ────────────────────────────────────────────────────
|
|||
|
|
static void SectionA()
|
|||
|
|
{
|
|||
|
|
L("");
|
|||
|
|
L("## A. 원본 카메라 · UI 캔버스 실측");
|
|||
|
|
var camPf = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(kCam);
|
|||
|
|
if (camPf == null) { L("FAIL Main Camera.prefab 로드 실패"); fail++; return; }
|
|||
|
|
var c = camPf.GetComponent<UnityEngine.Camera>();
|
|||
|
|
var rc = camPf.GetComponent("RealCamera");
|
|||
|
|
var al = camPf.GetComponent<UnityEngine.AudioListener>();
|
|||
|
|
var mrv = camPf.GetComponent("MeshRendererVisibility");
|
|||
|
|
var ucd = camPf.GetComponent<UnityEngine.Rendering.Universal.UniversalAdditionalCameraData>();
|
|||
|
|
L("Main Camera.prefab: tag=" + camPf.tag + " layer=" + camPf.layer + " parent=" + (camPf.transform.parent == null ? "none" : camPf.transform.parent.name)
|
|||
|
|
+ " children=" + camPf.transform.childCount);
|
|||
|
|
L(" Camera: ortho=" + c.orthographic + " fov=" + c.fieldOfView + " size=" + c.orthographicSize
|
|||
|
|
+ " near=" + c.nearClipPlane + " far=" + c.farClipPlane + " depth=" + c.depth
|
|||
|
|
+ " mask=" + c.cullingMask + " clear=" + c.clearFlags + " hdr=" + c.allowHDR + " msaa=" + c.allowMSAA
|
|||
|
|
+ " occlusionCulling=" + c.useOcclusionCulling);
|
|||
|
|
if (ucd != null)
|
|||
|
|
L(" URP: type=" + ucd.renderType + " post=" + ucd.renderPostProcessing + " shadows=" + ucd.renderShadows
|
|||
|
|
+ " volumeMask=" + ucd.volumeLayerMask.value + " aa=" + ucd.antialiasing
|
|||
|
|
+ " stack=" + (ucd.cameraStack == null ? -1 : ucd.cameraStack.Count));
|
|||
|
|
L(" 컴포넌트: RealCamera=" + (rc != null) + " AudioListener=" + (al != null) + " MeshRendererVisibility=" + (mrv != null));
|
|||
|
|
Chk(rc != null && al != null, "원본 카메라 = RealCamera + AudioListener 보유(재부모화·비활성 금지 근거)", "");
|
|||
|
|
|
|||
|
|
var uiPf = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>("Assets/Res_Addr/MainUI/NewGameUI.prefab");
|
|||
|
|
if (uiPf != null)
|
|||
|
|
{
|
|||
|
|
var canvases = uiPf.GetComponentsInChildren<UnityEngine.Canvas>(true);
|
|||
|
|
L("NewGameUI.prefab 캔버스 " + canvases.Length + "개");
|
|||
|
|
for (int i = 0; i < canvases.Length; i++)
|
|||
|
|
{
|
|||
|
|
var cv = canvases[i];
|
|||
|
|
L(" [" + i + "] " + cv.name + " renderMode=" + cv.renderMode
|
|||
|
|
+ " worldCamera=" + (cv.worldCamera == null ? "null" : cv.worldCamera.name)
|
|||
|
|
+ " planeDistance=" + cv.planeDistance + " sortingOrder=" + cv.sortingOrder + " layer=" + cv.gameObject.layer);
|
|||
|
|
}
|
|||
|
|
var cams = uiPf.GetComponentsInChildren<UnityEngine.Camera>(true);
|
|||
|
|
for (int i = 0; i < cams.Length; i++)
|
|||
|
|
L(" UI camera: " + cams[i].name + " ortho=" + cams[i].orthographic + " depth=" + cams[i].depth
|
|||
|
|
+ " mask=" + cams[i].cullingMask + " clear=" + cams[i].clearFlags);
|
|||
|
|
bool ssc = false;
|
|||
|
|
for (int i = 0; i < canvases.Length; i++) if (canvases[i].renderMode == UnityEngine.RenderMode.ScreenSpaceCamera) ssc = true;
|
|||
|
|
Chk(ssc, "인게임 UI 캔버스 = Screen Space - Camera(UICamera) → 원본 카메라를 끄면 UI 가 사라진다", "");
|
|||
|
|
}
|
|||
|
|
else { L("FAIL NewGameUI.prefab 로드 실패"); fail++; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── 씬 조립 ──────────────────────────────────────────────────────────────
|
|||
|
|
static void BuildScene(ref UnityEngine.GameObject mapGo, ref UnityEngine.GameObject pcGo,
|
|||
|
|
ref UnityEngine.GameObject camGo, ref UnityEngine.GameObject lightGo,
|
|||
|
|
System.Collections.Generic.List<UnityEngine.GameObject> mobs)
|
|||
|
|
{
|
|||
|
|
L("");
|
|||
|
|
L("## B. 임시 씬(추가 로드 · 저장 0)");
|
|||
|
|
|
|||
|
|
var mapPf = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(kMap);
|
|||
|
|
if (mapPf != null) { mapGo = UnityEngine.Object.Instantiate(mapPf); mapGo.name = "__WL814b_Map"; mapGo.hideFlags = UnityEngine.HideFlags.HideAndDontSave; }
|
|||
|
|
L("맵 " + (mapGo == null ? "실패" : mapGo.name + " renderers=" + mapGo.GetComponentsInChildren<UnityEngine.Renderer>(true).Length));
|
|||
|
|
|
|||
|
|
// 지면 y 찾기 + 소품이 가장 많은 앵커 고르기(룩 캡처라 배경이 비어 있으면 안 된다)
|
|||
|
|
UnityEngine.Physics.SyncTransforms();
|
|||
|
|
var rends = mapGo == null ? new UnityEngine.Renderer[0] : mapGo.GetComponentsInChildren<UnityEngine.Renderer>(true);
|
|||
|
|
// 소품이 실제로 모여 있는 곳을 찾는다 — 소품 중심들 중 반경 10 m 이웃이 가장 많은 지점
|
|||
|
|
var cands = new System.Collections.Generic.List<UnityEngine.Vector3>();
|
|||
|
|
for (int r = 0; r < rends.Length; r++)
|
|||
|
|
{
|
|||
|
|
if (rends[r] == null) continue;
|
|||
|
|
var b = rends[r].bounds;
|
|||
|
|
if (b.size.y < 1.0f || b.size.y > 20f) continue; // 지형/바닥/거대 오브젝트 제외
|
|||
|
|
if (b.center.y > 6f) continue; // 언덕 위·공중 제외
|
|||
|
|
cands.Add(b.center);
|
|||
|
|
}
|
|||
|
|
UnityEngine.Vector3 pcPos = new UnityEngine.Vector3(139f, 0.8f, -20f);
|
|||
|
|
int bestCount = -1;
|
|||
|
|
for (int i = 0; i < cands.Count; i++)
|
|||
|
|
{
|
|||
|
|
int n = 0;
|
|||
|
|
for (int j = 0; j < cands.Count; j++)
|
|||
|
|
if ((cands[j] - cands[i]).sqrMagnitude < 100f) n++; // 반경 10 m
|
|||
|
|
if (n > bestCount) { bestCount = n; pcPos = cands[i]; }
|
|||
|
|
}
|
|||
|
|
string bestHit = "미검출";
|
|||
|
|
UnityEngine.RaycastHit gh;
|
|||
|
|
if (UnityEngine.Physics.Raycast(new UnityEngine.Vector3(pcPos.x, pcPos.y + 100f, pcPos.z), UnityEngine.Vector3.down, out gh, 400f))
|
|||
|
|
{ pcPos.y = gh.point.y; bestHit = gh.collider.name; }
|
|||
|
|
L(" 소품 후보 " + cands.Count + "개 · 가장 밀집한 지점 반경 10 m 이웃 " + bestCount + "개");
|
|||
|
|
L("PC 위치 " + pcPos.ToString("F2") + " · 반경 10 m 소품 " + bestCount + "개 (지면 " + bestHit + ")");
|
|||
|
|
|
|||
|
|
var pcPf = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(kPc);
|
|||
|
|
if (pcPf != null)
|
|||
|
|
{
|
|||
|
|
pcGo = UnityEngine.Object.Instantiate(pcPf);
|
|||
|
|
pcGo.name = "__WL814b_PC";
|
|||
|
|
pcGo.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
|
|||
|
|
pcGo.transform.position = pcPos;
|
|||
|
|
pcGo.transform.rotation = UnityEngine.Quaternion.Euler(0f, 200f, 0f);
|
|||
|
|
StripBehaviours(pcGo);
|
|||
|
|
L("PC 포즈: " + PoseIdle(pcGo));
|
|||
|
|
}
|
|||
|
|
L("PC " + (pcGo == null ? "실패" : "ok"));
|
|||
|
|
|
|||
|
|
// 잡몹 3
|
|||
|
|
string[] mobPaths = {
|
|||
|
|
"Assets/Res_Addr/Mobs/Mob/Anto_A.prefab",
|
|||
|
|
"Assets/Res_Addr/Mobs/Mob/Anto_B.prefab",
|
|||
|
|
"Assets/Res_Addr/Mobs/Mob/Angez_A.prefab",
|
|||
|
|
};
|
|||
|
|
UnityEngine.Vector3[] mobOff = {
|
|||
|
|
new UnityEngine.Vector3(2.2f, 0f, 1.6f),
|
|||
|
|
new UnityEngine.Vector3(-2.4f, 0f, 0.8f),
|
|||
|
|
new UnityEngine.Vector3(0.2f, 0f, 3.0f),
|
|||
|
|
};
|
|||
|
|
for (int i = 0; i < mobPaths.Length; i++)
|
|||
|
|
{
|
|||
|
|
var pf = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(mobPaths[i]);
|
|||
|
|
if (pf == null) continue;
|
|||
|
|
var g = UnityEngine.Object.Instantiate(pf);
|
|||
|
|
g.name = "__WL814b_Mob" + i;
|
|||
|
|
g.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
|
|||
|
|
var p = pcPos + mobOff[i];
|
|||
|
|
UnityEngine.RaycastHit mh;
|
|||
|
|
if (UnityEngine.Physics.Raycast(new UnityEngine.Vector3(p.x, 200f, p.z), UnityEngine.Vector3.down, out mh, 500f)) p.y = mh.point.y;
|
|||
|
|
g.transform.position = p;
|
|||
|
|
g.transform.rotation = UnityEngine.Quaternion.Euler(0f, 20f + i * 90f, 0f);
|
|||
|
|
StripBehaviours(g);
|
|||
|
|
PoseIdle(g);
|
|||
|
|
mobs.Add(g);
|
|||
|
|
}
|
|||
|
|
L("잡몹 " + mobs.Count + "체");
|
|||
|
|
|
|||
|
|
lightGo = new UnityEngine.GameObject("__WL814b_Sun");
|
|||
|
|
lightGo.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
|
|||
|
|
var lt = lightGo.AddComponent<UnityEngine.Light>();
|
|||
|
|
lt.type = UnityEngine.LightType.Directional;
|
|||
|
|
lt.intensity = 1.1f;
|
|||
|
|
lt.color = new UnityEngine.Color(1f, 0.97f, 0.92f);
|
|||
|
|
lightGo.transform.rotation = UnityEngine.Quaternion.Euler(48f, 35f, 0f);
|
|||
|
|
|
|||
|
|
// 원본 카메라 인스턴스 — 811o 구도(h 76 · d 3.0 · up 2.4)를 RealCamera 수식으로 직접 계산
|
|||
|
|
var camPf = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(kCam);
|
|||
|
|
if (camPf == null) return;
|
|||
|
|
camGo = UnityEngine.Object.Instantiate(camPf);
|
|||
|
|
camGo.name = "__WL814b_MainCamera";
|
|||
|
|
camGo.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
|
|||
|
|
StripBehaviours(camGo); // RealCamera·MeshRendererVisibility 정지(트랜스폼은 우리가 잡는다)
|
|||
|
|
var cam = camGo.GetComponent<UnityEngine.Camera>();
|
|||
|
|
cam.aspect = (float)W / H;
|
|||
|
|
|
|||
|
|
const float camHeight = 76f, rotateAround = 347.21f, distanceAway = 3.0f, distanceUp = 2.4f;
|
|||
|
|
var lookAt = new UnityEngine.Vector3(0f, 1.06f, 0f);
|
|||
|
|
var rotv = UnityEngine.Quaternion.Euler(camHeight, rotateAround, 0f) * UnityEngine.Vector3.one;
|
|||
|
|
var camPosition = pcPos + UnityEngine.Vector3.up * distanceUp - rotv * distanceAway;
|
|||
|
|
camGo.transform.position = camPosition;
|
|||
|
|
s_lookAtWorld = pcPos + lookAt;
|
|||
|
|
camGo.transform.LookAt(s_lookAtWorld);
|
|||
|
|
float dist = UnityEngine.Vector3.Distance(camPosition, pcPos + lookAt);
|
|||
|
|
L("카메라(811o 구도) pos=" + camPosition.ToString("F3") + " euler=" + camGo.transform.eulerAngles.ToString("F2"));
|
|||
|
|
L(" PC 주시점까지 거리 = " + dist.ToString("F3") + " m → 원근 fov 60 의 반높이 = " + (dist * UnityEngine.Mathf.Tan(30f * UnityEngine.Mathf.Deg2Rad)).ToString("F3") + " m");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>에디트 모드에서는 Animator 가 돌지 않아 T 포즈가 된다 → idle 클립을 1프레임 샘플링한다.</summary>
|
|||
|
|
static string PoseIdle(UnityEngine.GameObject go)
|
|||
|
|
{
|
|||
|
|
var anim = go.GetComponentInChildren<UnityEngine.Animator>(true);
|
|||
|
|
if (anim == null || anim.runtimeAnimatorController == null) return "Animator 없음(T 포즈)";
|
|||
|
|
var clips = anim.runtimeAnimatorController.animationClips;
|
|||
|
|
if (clips == null || clips.Length == 0) return "클립 0(T 포즈)";
|
|||
|
|
UnityEngine.AnimationClip pick = null;
|
|||
|
|
for (int i = 0; i < clips.Length; i++)
|
|||
|
|
{
|
|||
|
|
if (clips[i] == null) continue;
|
|||
|
|
var n = clips[i].name.ToLower();
|
|||
|
|
if (n.Contains("idle") || n.Contains("wait") || n.Contains("stand")) { pick = clips[i]; break; }
|
|||
|
|
}
|
|||
|
|
if (pick == null) pick = clips[0];
|
|||
|
|
pick.SampleAnimation(anim.gameObject, 0.4f);
|
|||
|
|
return pick.name + " @0.4s (" + clips.Length + "클립)";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>임시 씬에서 원본 로직이 돌지 않게 MonoBehaviour 를 전부 끈다(에셋 무변경 · 인스턴스만).</summary>
|
|||
|
|
static void StripBehaviours(UnityEngine.GameObject go)
|
|||
|
|
{
|
|||
|
|
var all = go.GetComponentsInChildren<UnityEngine.MonoBehaviour>(true);
|
|||
|
|
for (int i = 0; i < all.Length; i++)
|
|||
|
|
{
|
|||
|
|
if (all[i] == null) continue;
|
|||
|
|
if (all[i] is UnityEngine.Rendering.Universal.UniversalAdditionalCameraData) continue;
|
|||
|
|
try { all[i].enabled = false; } catch { }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── B/C/D. 조립 · 복원 · 매핑 오차 ──────────────────────────────────────
|
|||
|
|
static void SectionBCD(UnityEngine.Camera cam, UnityEngine.GameObject pcGo)
|
|||
|
|
{
|
|||
|
|
L("");
|
|||
|
|
L("## B. 리그 조립");
|
|||
|
|
var cfg = MakeCfg(3.8f, 480, false);
|
|||
|
|
if (cfg == null) { L("FAIL SO/프리팹 없음 — 중단"); fail++; return; }
|
|||
|
|
|
|||
|
|
string before = WL.PixelArt.PixelCameraRig.CurrentSummary(cam);
|
|||
|
|
int origMask = cam.cullingMask;
|
|||
|
|
L("조립 전 원본 카메라: " + before);
|
|||
|
|
|
|||
|
|
bool ok = WL.PixelArt.PixelCameraRig.Assemble(cfg, cam);
|
|||
|
|
Chk(ok && WL.PixelArt.PixelCameraRig.IsAssembled, "조립 성공", WL.PixelArt.PixelCameraRig.LastLog);
|
|||
|
|
if (!ok) return;
|
|||
|
|
|
|||
|
|
var rig = WL.PixelArt.PixelCameraRig.RigRoot;
|
|||
|
|
var gameCam = WL.PixelArt.PixelCameraRig.GameCamera;
|
|||
|
|
var viewCam = WL.PixelArt.PixelCameraRig.ViewCamera;
|
|||
|
|
var mgr = WL.PixelArt.PixelCameraRig.Manager;
|
|||
|
|
var proxy = WL.PixelArt.PixelCameraRig.Proxy;
|
|||
|
|
|
|||
|
|
L("리그 구조:");
|
|||
|
|
Dump(rig.transform, 1);
|
|||
|
|
L("프록시: " + (proxy == null ? "null" : proxy.name + " parent=" + (proxy.parent == null ? "none" : proxy.parent.name)
|
|||
|
|
+ " worldPos=" + proxy.position.ToString("F4") + " camPos=" + cam.transform.position.ToString("F4")));
|
|||
|
|
Chk(proxy != null && (proxy.position - cam.transform.position).sqrMagnitude < 1e-8f,
|
|||
|
|
"프록시 월드 위치 == 원본 카메라 위치(지연 0)", "");
|
|||
|
|
Chk(proxy != null && UnityEngine.Quaternion.Angle(proxy.rotation, cam.transform.rotation) < 1e-3f,
|
|||
|
|
"프록시 회전 == 원본 카메라 회전", "");
|
|||
|
|
|
|||
|
|
string after = WL.PixelArt.PixelCameraRig.CurrentSummary(cam);
|
|||
|
|
L("조립 후 원본 카메라: " + after);
|
|||
|
|
Chk(cam.orthographic, "원본 카메라 직교 전환", "size=" + cam.orthographicSize);
|
|||
|
|
Chk(cam.cullingMask == 0, "원본 카메라 월드 미렌더(cullingMask 0)", "mask=" + cam.cullingMask);
|
|||
|
|
Chk(cam.tag == "MainCamera" && cam.enabled, "MainCamera 태그·enabled 유지(Camera.main 보존)", "tag=" + cam.tag);
|
|||
|
|
|
|||
|
|
// Critter 를 1프레임 강제로 돌린다(에디트 모드에는 LateUpdate 가 안 올 수 있다)
|
|||
|
|
viewCam.aspect = (float)W / H;
|
|||
|
|
mgr.SendMessage("LateUpdate", UnityEngine.SendMessageOptions.DontRequireReceiver);
|
|||
|
|
|
|||
|
|
var rt = gameCam.targetTexture;
|
|||
|
|
L("게임 카메라: ortho=" + gameCam.orthographic + " size=" + gameCam.orthographicSize.ToString("F4")
|
|||
|
|
+ " mask=" + gameCam.cullingMask + " depth=" + gameCam.depth
|
|||
|
|
+ " RT=" + (rt == null ? "null" : rt.width + "×" + rt.height + " filter=" + rt.filterMode + " fmt=" + rt.format));
|
|||
|
|
L("뷰 카메라: ortho=" + viewCam.orthographic + " size=" + viewCam.orthographicSize.ToString("F4")
|
|||
|
|
+ " mask=" + viewCam.cullingMask + " near=" + viewCam.nearClipPlane + " far=" + viewCam.farClipPlane
|
|||
|
|
+ " localPos=" + viewCam.transform.localPosition.ToString("F5"));
|
|||
|
|
L("Critter: GameResolution=" + mgr.GameResolution + " zoom=" + mgr.GameCameraZoom.ToString("F5")
|
|||
|
|
+ " viewZoom=" + mgr.ViewCameraZoom + " grid=" + mgr.VoxelGridMovement + " sub=" + mgr.SubpixelAdjustments
|
|||
|
|
+ " followRot=" + mgr.FollowRotation);
|
|||
|
|
Chk(rt != null && rt.height == 480 && rt.filterMode == UnityEngine.FilterMode.Point,
|
|||
|
|
"저해상도 RT 480줄 · 포인트 필터", rt == null ? "null" : rt.width + "×" + rt.height);
|
|||
|
|
Chk(gameCam.cullingMask == (origMask & ~(1 << cfg.rigLayer)),
|
|||
|
|
"게임 카메라 = 원본 cullingMask 승계", "orig=" + origMask + " game=" + gameCam.cullingMask);
|
|||
|
|
Chk((gameCam.cullingMask & (1 << cfg.rigLayer)) == 0, "게임 카메라가 픽셀 쿼드를 다시 그리지 않는다(리그 레이어 제외)", "");
|
|||
|
|
Chk(viewCam.cullingMask == (1 << cfg.rigLayer), "뷰 카메라는 픽셀 쿼드만 그린다", "mask=" + viewCam.cullingMask);
|
|||
|
|
|
|||
|
|
// ── D. 매핑 오차
|
|||
|
|
L("");
|
|||
|
|
L("## D. 매핑 오차 — Camera.main 투영 vs 실제 화면(1080×1920 · px)");
|
|||
|
|
MappingTable(cam, gameCam, viewCam, mgr, pcGo);
|
|||
|
|
|
|||
|
|
// ── C. 복원
|
|||
|
|
L("");
|
|||
|
|
L("## C. 복원");
|
|||
|
|
var rtRef = gameCam.targetTexture;
|
|||
|
|
WL.PixelArt.PixelCameraRig.Teardown("probe C");
|
|||
|
|
string restored = WL.PixelArt.PixelCameraRig.CurrentSummary(cam);
|
|||
|
|
L("복원 후 원본 카메라: " + restored);
|
|||
|
|
Chk(restored == before, "원본 카메라 값 스냅샷 == 복원값(문자열 완전 일치)", restored == before ? "" : "\n before=" + before + "\n after =" + restored);
|
|||
|
|
Chk(!WL.PixelArt.PixelCameraRig.IsAssembled, "리그 오브젝트 0", "");
|
|||
|
|
Chk(rtRef == null, "RT 참조 해제(Release + Destroy)", "rt=" + (rtRef == null ? "null" : "alive"));
|
|||
|
|
int strays = 0;
|
|||
|
|
var left = UnityEngine.Object.FindObjectsByType<PixelCamera.PixelCameraManager>(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.None);
|
|||
|
|
strays = left == null ? 0 : left.Length;
|
|||
|
|
Chk(strays == 0, "씬에 PixelCameraManager 잔재 0", "found=" + strays);
|
|||
|
|
int proxyLeft = 0;
|
|||
|
|
for (int i = 0; i < cam.transform.childCount; i++) if (cam.transform.GetChild(i).name == WL.PixelArt.PixelCameraRig.ProxyName) proxyLeft++;
|
|||
|
|
Chk(proxyLeft == 0, "원본 카메라 자식 프록시 0", "found=" + proxyLeft);
|
|||
|
|
|
|||
|
|
var mat = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Material>("Assets/3DPixelCamera/URP/UpscaleMaterialURP.mat");
|
|||
|
|
bool matClean = mat == null || !mat.HasProperty("_LowResTexture") || mat.GetTexture("_LowResTexture") == null;
|
|||
|
|
Chk(matClean, "디스크 UpscaleMaterialURP.mat 의 _LowResTexture 오염 0(런타임 인스턴스 사용)",
|
|||
|
|
mat == null ? "mat=null" : ("tex=" + (mat.GetTexture("_LowResTexture") == null ? "null" : mat.GetTexture("_LowResTexture").name)));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void MappingTable(UnityEngine.Camera src, UnityEngine.Camera gameCam, UnityEngine.Camera viewCam,
|
|||
|
|
PixelCamera.PixelCameraManager mgr, UnityEngine.GameObject pcGo)
|
|||
|
|
{
|
|||
|
|
var quad = FindQuad();
|
|||
|
|
if (quad == null) { L("FAIL 픽셀 쿼드를 못 찾음"); fail++; return; }
|
|||
|
|
var rt = gameCam.targetTexture;
|
|||
|
|
if (rt == null) { L("FAIL RT 없음"); fail++; return; }
|
|||
|
|
|
|||
|
|
var basePos = pcGo != null ? pcGo.transform.position : src.transform.position + src.transform.forward * 6f;
|
|||
|
|
var pts = new UnityEngine.Vector3[]
|
|||
|
|
{
|
|||
|
|
basePos,
|
|||
|
|
basePos + new UnityEngine.Vector3(0f, 1.7f, 0f),
|
|||
|
|
basePos + new UnityEngine.Vector3(1.8f, 0.2f, 1.4f),
|
|||
|
|
basePos + new UnityEngine.Vector3(-2.1f, 1.0f, 1.0f),
|
|||
|
|
basePos + new UnityEngine.Vector3(0.4f, 0.6f, 2.6f),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
L(" # | world | Camera.main px | 실제 화면 px | 오차 px | RT양자화 포함 오차 px");
|
|||
|
|
float maxErr = 0f, maxErrQ = 0f, sumErr = 0f, sumErrQ = 0f;
|
|||
|
|
for (int i = 0; i < pts.Length; i++)
|
|||
|
|
{
|
|||
|
|
var p = pts[i];
|
|||
|
|
var vSrc = src.WorldToViewportPoint(p);
|
|||
|
|
var expect = new UnityEngine.Vector2(vSrc.x * W, vSrc.y * H);
|
|||
|
|
|
|||
|
|
var vGame = gameCam.WorldToViewportPoint(p);
|
|||
|
|
var actual = ViewportToScreen(vGame.x, vGame.y, quad, viewCam);
|
|||
|
|
|
|||
|
|
float qx = (UnityEngine.Mathf.Floor(vGame.x * rt.width) + 0.5f) / rt.width;
|
|||
|
|
float qy = (UnityEngine.Mathf.Floor(vGame.y * rt.height) + 0.5f) / rt.height;
|
|||
|
|
var actualQ = ViewportToScreen(qx, qy, quad, viewCam);
|
|||
|
|
|
|||
|
|
float e = UnityEngine.Vector2.Distance(expect, actual);
|
|||
|
|
float eq = UnityEngine.Vector2.Distance(expect, actualQ);
|
|||
|
|
maxErr = UnityEngine.Mathf.Max(maxErr, e); maxErrQ = UnityEngine.Mathf.Max(maxErrQ, eq);
|
|||
|
|
sumErr += e; sumErrQ += eq;
|
|||
|
|
L(" " + (i + 1) + " | " + p.ToString("F2")
|
|||
|
|
+ " | (" + expect.x.ToString("F1") + "," + expect.y.ToString("F1") + ")"
|
|||
|
|
+ " | (" + actual.x.ToString("F1") + "," + actual.y.ToString("F1") + ")"
|
|||
|
|
+ " | " + e.ToString("F2")
|
|||
|
|
+ " | " + eq.ToString("F2"));
|
|||
|
|
}
|
|||
|
|
L(" 최대 " + maxErr.ToString("F2") + " px · 평균 " + (sumErr / pts.Length).ToString("F2")
|
|||
|
|
+ " px (연속) / 최대 " + maxErrQ.ToString("F2") + " px · 평균 " + (sumErrQ / pts.Length).ToString("F2") + " px (RT 양자화 포함)");
|
|||
|
|
L(" 참고: 1 RT 픽셀 = 화면 " + ((float)H / rt.height).ToString("F2") + " px(세로) · " + ((float)W / rt.width).ToString("F2") + " px(가로)");
|
|||
|
|
Chk(maxErr <= 2.0f, "연속 매핑 오차 ≤ 2 px (compensateViewZoom 보정 확인)", "max=" + maxErr.ToString("F2") + " px");
|
|||
|
|
Chk(maxErrQ <= (float)H / rt.height, "RT 양자화 포함 오차 ≤ 업스케일 1픽셀", "max=" + maxErrQ.ToString("F2") + " px");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static UnityEngine.Vector2 ViewportToScreen(float vx, float vy, UnityEngine.Transform quad, UnityEngine.Camera viewCam)
|
|||
|
|
{
|
|||
|
|
var quadLocal = new UnityEngine.Vector3(vx - 0.5f, vy - 0.5f, 0f);
|
|||
|
|
var quadWorld = quad.TransformPoint(quadLocal);
|
|||
|
|
var vView = viewCam.WorldToViewportPoint(quadWorld);
|
|||
|
|
return new UnityEngine.Vector2(vView.x * W, vView.y * H);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static UnityEngine.Transform FindQuad()
|
|||
|
|
{
|
|||
|
|
var rig = WL.PixelArt.PixelCameraRig.RigRoot;
|
|||
|
|
if (rig == null) return null;
|
|||
|
|
var uc = rig.GetComponentInChildren<PixelCamera.UpscaledCanvas>(true);
|
|||
|
|
return uc == null ? null : uc.transform;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void Dump(UnityEngine.Transform t, int depth)
|
|||
|
|
{
|
|||
|
|
var line = new System.Text.StringBuilder();
|
|||
|
|
line.Append(' ', depth * 2).Append("- ").Append(t.name).Append(" layer=").Append(t.gameObject.layer);
|
|||
|
|
var comps = t.GetComponents<UnityEngine.Component>();
|
|||
|
|
line.Append(" [");
|
|||
|
|
for (int i = 0; i < comps.Length; i++) if (comps[i] != null) line.Append(comps[i].GetType().Name).Append(' ');
|
|||
|
|
line.Append("]");
|
|||
|
|
line.Append(" lp=").Append(t.localPosition.ToString("F4")).Append(" ls=").Append(t.localScale.ToString("F4"));
|
|||
|
|
L(line.ToString());
|
|||
|
|
for (int i = 0; i < t.childCount; i++) Dump(t.GetChild(i), depth + 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── E. GC ────────────────────────────────────────────────────────────────
|
|||
|
|
static void SectionE(UnityEngine.Camera cam)
|
|||
|
|
{
|
|||
|
|
L("");
|
|||
|
|
L("## E. GC (200k 회)");
|
|||
|
|
var probeGo = new UnityEngine.GameObject("__WL814b_MirrorProbe");
|
|||
|
|
probeGo.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
|
|||
|
|
var mirror = probeGo.AddComponent<WL.PixelArt.PixelCameraProxyMirror>();
|
|||
|
|
mirror.Source = cam.transform;
|
|||
|
|
|
|||
|
|
System.GC.Collect();
|
|||
|
|
long m0 = System.GC.GetAllocatedBytesForCurrentThread();
|
|||
|
|
long t0 = System.GC.GetTotalMemory(false);
|
|||
|
|
int g0 = System.GC.CollectionCount(0);
|
|||
|
|
for (int i = 0; i < 200000; i++) mirror.Mirror();
|
|||
|
|
long m1 = System.GC.GetAllocatedBytesForCurrentThread();
|
|||
|
|
long t1 = System.GC.GetTotalMemory(false);
|
|||
|
|
int g1 = System.GC.CollectionCount(0);
|
|||
|
|
L("프록시 미러 ×200k: thread Δ" + (m1 - m0) + " B · total Δ" + (t1 - t0) + " B · gen0 " + (g1 - g0));
|
|||
|
|
Chk(m1 - m0 == 0, "프록시 미러 GC 0 B", "Δ=" + (m1 - m0));
|
|||
|
|
UnityEngine.Object.DestroyImmediate(probeGo);
|
|||
|
|
|
|||
|
|
var cfg = MakeCfg(3.8f, 480, false);
|
|||
|
|
WL.PixelArt.PixelCameraRig.Assemble(cfg, cam);
|
|||
|
|
System.GC.Collect();
|
|||
|
|
long s0 = System.GC.GetAllocatedBytesForCurrentThread();
|
|||
|
|
for (int i = 0; i < 200000; i++) WL.PixelArt.PixelCameraRig.ProbeSnapTick();
|
|||
|
|
long s1 = System.GC.GetAllocatedBytesForCurrentThread();
|
|||
|
|
L("회전 스냅 판정 ×200k: thread Δ" + (s1 - s0) + " B");
|
|||
|
|
Chk(s1 - s0 == 0, "회전 스냅 판정 GC 0 B", "Δ=" + (s1 - s0));
|
|||
|
|
WL.PixelArt.PixelCameraRig.Teardown("probe E");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── F. C8 ────────────────────────────────────────────────────────────────
|
|||
|
|
static void SectionF(UnityEngine.Camera cam)
|
|||
|
|
{
|
|||
|
|
L("");
|
|||
|
|
L("## F. C8 롤백");
|
|||
|
|
var so = WL.PixelArt.WLPixelCameraSettings.Instance;
|
|||
|
|
Chk(so != null, "SO 로드(Resources/WL/WLPixelCameraSettings)", so == null ? "null" : "enabled=" + so.enabled);
|
|||
|
|
|
|||
|
|
string before = WL.PixelArt.PixelCameraRig.CurrentSummary(cam);
|
|||
|
|
var cfg = MakeCfg(3.8f, 480, false);
|
|||
|
|
WL.PixelArt.PixelCameraRig.Assemble(cfg, cam);
|
|||
|
|
Chk(WL.PixelArt.PixelCameraRig.IsAssembled, "조립됨(C8 전)", "");
|
|||
|
|
|
|||
|
|
WL.PixelArt.WLPixelCameraSettings.RuntimeDisabled = true;
|
|||
|
|
Chk(!WL.PixelArt.WLPixelCameraSettings.Enabled, "RuntimeDisabled → Enabled=false", "");
|
|||
|
|
WL.PixelArt.PixelCameraRig.Tick(); // Active=false → 즉시 Teardown
|
|||
|
|
Chk(!WL.PixelArt.PixelCameraRig.IsAssembled, "Tick 1회에 리그 해체", "");
|
|||
|
|
string after = WL.PixelArt.PixelCameraRig.CurrentSummary(cam);
|
|||
|
|
Chk(after == before, "C8 복원값 == 원값", after == before ? "" : "\n before=" + before + "\n after =" + after);
|
|||
|
|
|
|||
|
|
WL.PixelArt.PixelCameraRig.Tick(); // 재호출해도 조립 0
|
|||
|
|
Chk(!WL.PixelArt.PixelCameraRig.IsAssembled, "C8 상태에서 재조립 0", "");
|
|||
|
|
WL.PixelArt.WLPixelCameraSettings.RuntimeDisabled = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── G. 캡처 ──────────────────────────────────────────────────────────────
|
|||
|
|
static void SectionG(UnityEngine.Camera cam)
|
|||
|
|
{
|
|||
|
|
L("");
|
|||
|
|
L("## G. 룩 캡처 1080×1920 → " + kOutDir);
|
|||
|
|
L(CaptureControl(cam, "look_00_persp_original"));
|
|||
|
|
L(CaptureRig(cam, 3.8f, 480, "look_01_ortho38_px480"));
|
|||
|
|
L(CaptureRig(cam, 3.8f, 360, "look_02_ortho38_px360"));
|
|||
|
|
L(CaptureRig(cam, 4.6f, 480, "look_03_ortho46_px480"));
|
|||
|
|
// 보너스 — 811o 구도의 실측 거리(카메라→주시점)에서 원근 fov 60 과 PC 평면 크기가 정확히 같아지는 ortho
|
|||
|
|
float matched = UnityEngine.Vector3.Distance(cam.transform.position, s_lookAtWorld) * UnityEngine.Mathf.Tan(30f * UnityEngine.Mathf.Deg2Rad);
|
|||
|
|
L(CaptureRig(cam, matched, 480, "look_04_orthoMatched_px480") + " ← 실측 정합값 ortho " + matched.ToString("F3"));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static string CaptureControl(UnityEngine.Camera cam, string tag)
|
|||
|
|
{
|
|||
|
|
var rt = new UnityEngine.RenderTexture(W, H, 24, UnityEngine.RenderTextureFormat.ARGB32);
|
|||
|
|
var prevTarget = cam.targetTexture;
|
|||
|
|
float prevAspect = cam.aspect;
|
|||
|
|
cam.aspect = (float)W / H;
|
|||
|
|
cam.targetTexture = rt;
|
|||
|
|
cam.Render();
|
|||
|
|
cam.targetTexture = prevTarget;
|
|||
|
|
cam.aspect = prevAspect;
|
|||
|
|
string path = WritePng(rt, tag);
|
|||
|
|
rt.Release(); UnityEngine.Object.DestroyImmediate(rt);
|
|||
|
|
return " 대조(원근 fov " + cam.fieldOfView + ") → " + path;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static string CaptureRig(UnityEngine.Camera cam, float orthoSize, int pixelHeight, string tag)
|
|||
|
|
{
|
|||
|
|
var cfg = MakeCfg(orthoSize, pixelHeight, false); // viewCameraInMainStack=false → 뷰 카메라를 Base 로 직접 렌더
|
|||
|
|
if (cfg == null) return " FAIL cfg";
|
|||
|
|
if (!WL.PixelArt.PixelCameraRig.Assemble(cfg, cam)) return " FAIL assemble " + tag;
|
|||
|
|
|
|||
|
|
var gameCam = WL.PixelArt.PixelCameraRig.GameCamera;
|
|||
|
|
var viewCam = WL.PixelArt.PixelCameraRig.ViewCamera;
|
|||
|
|
var mgr = WL.PixelArt.PixelCameraRig.Manager;
|
|||
|
|
|
|||
|
|
var cap = new UnityEngine.RenderTexture(W, H, 24, UnityEngine.RenderTextureFormat.ARGB32);
|
|||
|
|
viewCam.aspect = (float)W / H;
|
|||
|
|
viewCam.targetTexture = cap;
|
|||
|
|
mgr.SendMessage("LateUpdate", UnityEngine.SendMessageOptions.DontRequireReceiver);
|
|||
|
|
mgr.SendMessage("LateUpdate", UnityEngine.SendMessageOptions.DontRequireReceiver); // RT 생성 후 위치·줌 확정
|
|||
|
|
|
|||
|
|
var lowRt = gameCam.targetTexture;
|
|||
|
|
gameCam.Render(); // 저해상도 RT 를 채운다
|
|||
|
|
viewCam.Render(); // 쿼드를 1080×1920 으로 업스케일
|
|||
|
|
|
|||
|
|
string path = WritePng(cap, tag);
|
|||
|
|
string info = " ortho " + orthoSize.ToString("0.##") + " × px " + pixelHeight
|
|||
|
|
+ " → RT " + (lowRt == null ? "null" : lowRt.width + "×" + lowRt.height)
|
|||
|
|
+ " · gameZoom " + mgr.GameCameraZoom.ToString("F4")
|
|||
|
|
+ " · viewSize " + viewCam.orthographicSize.ToString("F4")
|
|||
|
|
+ " → " + path;
|
|||
|
|
|
|||
|
|
viewCam.targetTexture = null;
|
|||
|
|
cap.Release(); UnityEngine.Object.DestroyImmediate(cap);
|
|||
|
|
WL.PixelArt.PixelCameraRig.Teardown("capture " + tag);
|
|||
|
|
return info;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static string WritePng(UnityEngine.RenderTexture rt, string tag)
|
|||
|
|
{
|
|||
|
|
var prev = UnityEngine.RenderTexture.active;
|
|||
|
|
UnityEngine.RenderTexture.active = rt;
|
|||
|
|
var tex = new UnityEngine.Texture2D(rt.width, rt.height, UnityEngine.TextureFormat.RGB24, false);
|
|||
|
|
tex.ReadPixels(new UnityEngine.Rect(0, 0, rt.width, rt.height), 0, 0);
|
|||
|
|
tex.Apply();
|
|||
|
|
UnityEngine.RenderTexture.active = prev;
|
|||
|
|
var path = System.IO.Path.Combine(kOutDir, tag + ".png");
|
|||
|
|
System.IO.File.WriteAllBytes(path, UnityEngine.ImageConversion.EncodeToPNG(tex));
|
|||
|
|
UnityEngine.Object.DestroyImmediate(tex);
|
|||
|
|
return path;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── 임시 설정(디스크 SO 를 절대 건드리지 않는다) ─────────────────────────
|
|||
|
|
static WL.PixelArt.WLPixelCameraSettings MakeCfg(float orthoSize, int pixelHeight, bool inStack)
|
|||
|
|
{
|
|||
|
|
var rig = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(kRig);
|
|||
|
|
if (rig == null) return null;
|
|||
|
|
var cfg = UnityEngine.ScriptableObject.CreateInstance<WL.PixelArt.WLPixelCameraSettings>();
|
|||
|
|
cfg.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
|
|||
|
|
cfg.pixelCameraSystemPrefab = rig;
|
|||
|
|
cfg.orthoSize = orthoSize;
|
|||
|
|
cfg.pixelHeight = pixelHeight;
|
|||
|
|
cfg.viewCameraInMainStack = inStack;
|
|||
|
|
cfg.verboseLog = false;
|
|||
|
|
return cfg;
|
|||
|
|
}
|
|||
|
|
}
|