430 lines
25 KiB
C#
430 lines
25 KiB
C#
// WL-816b — 🔴 **Play 모드에서만** 재는 무기 측정/촬영 하니스 (814x2~x4 방식 그대로)
|
||
// 편집 모드 렌더는 스킨 메시가 갱신되지 않아 무의미하다(814x 실측).
|
||
// run_script --file AgentScripts/WL816b_Play.cs --entry WL816b.Probe (Play 중에 부른다)
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using UnityEngine;
|
||
using WL.Look.Arena;
|
||
using WL.Look.Character;
|
||
|
||
public static class WL816b
|
||
{
|
||
public const string RAW = "Screenshots_WL/WL816b/raw/";
|
||
const int W = 1080, H = 1920;
|
||
public const float ORTHO = 10f; // 🔴 게임 크기(814x~x4 와 동일 축)
|
||
const string WDIR = "Assets/Res_Addr/Weapon/";
|
||
|
||
static readonly StringBuilder sb = new StringBuilder();
|
||
static void L(string s) { sb.AppendLine(s); }
|
||
|
||
static GameObject s_root;
|
||
static Camera s_cam;
|
||
static Vector3 s_camPos, s_center, s_pos;
|
||
static Quaternion s_rot, s_camRot;
|
||
static Animator s_an;
|
||
static float s_camSize, s_ts;
|
||
static Transform[] s_sockets;
|
||
static readonly List<GameObject> s_spawned = new List<GameObject>();
|
||
static float s_charHeight;
|
||
|
||
// ─────────────────────────────────────────────────────────── 표: 무기 → 소켓 index (ClassConfig 실측)
|
||
public static readonly string[][] WEAPONS = new string[][] {
|
||
// 이름 소켓idx 클래스
|
||
new[]{ "PP_Theme_10_Mace_003", "1", "10501 기본" },
|
||
new[]{ "PP_Theme_10_Shield_003", "0", "10501 기본" },
|
||
new[]{ "Elven_Sword_01", "2", "10101/10102" },
|
||
new[]{ "PP_Theme_08_Mace_003", "1", "10502" },
|
||
new[]{ "PP_Theme_08_Shield_003", "0", "10502" },
|
||
new[]{ "PP_Theme_05_Bow_003", "6", "10402" },
|
||
new[]{ "PP_Theme_05_Bow_004", "6", "10401" },
|
||
new[]{ "PP_Theme_03_Scepter_001","1", "10303" },
|
||
new[]{ "PP_Theme_03_Wand_001", "1", "10302" },
|
||
new[]{ "Dagger1", "3", "10201/10202" },
|
||
new[]{ "Dagger2", "4", "10201/10202" },
|
||
new[]{ "Orb", "5", "10301" },
|
||
};
|
||
|
||
static void Setup()
|
||
{
|
||
s_root = GameObject.Find("PC_LH_M05");
|
||
if (s_root == null)
|
||
foreach (var g in UnityEngine.Object.FindObjectsByType<Animator>(FindObjectsSortMode.None))
|
||
if (g.name.Contains("LH_M05")) { s_root = g.gameObject; break; }
|
||
s_ts = Time.timeScale; Time.timeScale = 0f;
|
||
s_an = s_root.GetComponentInChildren<Animator>();
|
||
if (s_an != null) { s_an.speed = 0f; try { s_an.Play("idle", 0, 0.25f); s_an.Update(0f); } catch { } }
|
||
|
||
s_cam = Camera.main ?? UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsSortMode.None).First(c => c.orthographic);
|
||
s_camPos = s_cam.transform.position; s_camSize = s_cam.orthographicSize; s_camRot = s_cam.transform.rotation;
|
||
s_root.transform.rotation = Quaternion.Euler(0, s_cam.transform.eulerAngles.y + 180f, 0);
|
||
s_pos = s_root.transform.position; s_rot = s_root.transform.rotation;
|
||
|
||
// 🔴 키 = 소켓 아래(무기)를 뺀 몸 렌더러만. 814p `MeasureBodyLocalHeight` 와 같은 정의.
|
||
var body = s_root.GetComponentsInChildren<Renderer>(true).Where(r => r.enabled && r.gameObject.activeInHierarchy).ToArray();
|
||
var b = new Bounds(s_root.transform.position, Vector3.zero);
|
||
foreach (var r in body) b.Encapsulate(r.bounds);
|
||
s_center = b.center; s_charHeight = b.size.y;
|
||
|
||
// 소켓 — 이름으로 찾는다(LH_M05 본 계층)
|
||
string[] names = { "w_shield", "w_righthand", "w_onehand", "w_bladedancer_l", "w_bladedancer_r", "w_orb", "w_archer_l" };
|
||
s_sockets = new Transform[names.Length];
|
||
foreach (var t in s_root.GetComponentsInChildren<Transform>(true))
|
||
for (int i = 0; i < names.Length; i++)
|
||
if (s_sockets[i] == null && t.name == names[i]) s_sockets[i] = t;
|
||
L("root=" + s_root.name + " 키 " + s_charHeight.ToString("F4") + " m · center " + s_center.ToString("F2")
|
||
+ " · rootLossy " + s_root.transform.lossyScale.ToString("F4"));
|
||
for (int i = 0; i < names.Length; i++)
|
||
L(" socket[" + i + "] " + names[i] + " = " + (s_sockets[i] == null ? "없음" : s_sockets[i].name + " lossy " + s_sockets[i].lossyScale.ToString("F4")));
|
||
L(" renderers " + string.Join(",", body.Select(r => r.name)));
|
||
var pc = WLCharacterPaletteSettings.Instance;
|
||
L(" palette SO enabled=" + (pc == null ? "SO없음" : pc.enabled_.ToString()) + " rows=" + (pc == null || pc.rendererNames == null ? 0 : pc.rendererNames.Length));
|
||
}
|
||
|
||
static void Teardown()
|
||
{
|
||
Clear();
|
||
s_cam.transform.SetPositionAndRotation(s_camPos, s_camRot); s_cam.orthographicSize = s_camSize;
|
||
Time.timeScale = s_ts;
|
||
}
|
||
|
||
// ────────────────────────────────────────────────────────── 무기 부착 (814p 규칙: 키의 60 % · clamp 0.3~1.0)
|
||
public static GameObject Spawn(string weapon, int socketIdx, out float ratioBefore, out float mult)
|
||
{
|
||
ratioBefore = 0f; mult = 1f;
|
||
var pf = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(WDIR + weapon + ".prefab");
|
||
if (pf == null || s_sockets[socketIdx] == null) return null;
|
||
var go = UnityEngine.Object.Instantiate(pf, s_sockets[socketIdx]);
|
||
go.name = weapon + "(Clone)"; // 🔴 실제 게임과 같은 이름(DSUtil.Get_Clone) — 팔레트 표 매칭이 이름 기반이다
|
||
go.transform.localPosition = Vector3.zero; go.transform.localRotation = Quaternion.identity; go.transform.localScale = Vector3.one;
|
||
s_spawned.Add(go);
|
||
|
||
// 길이 = 무기 루트 로컬 공간의 렌더러 localBounds 합 × lossyScale (814p 정의 · 포즈 무관)
|
||
var rs = go.GetComponentsInChildren<Renderer>(true).Where(r => !(r is ParticleSystemRenderer) && !(r is TrailRenderer) && !(r is LineRenderer)).ToArray();
|
||
if (rs.Length == 0) return go; // Orb = 파티클만 → 미보정(814p 미확인 ②)
|
||
var wb = new Bounds(); bool first = true;
|
||
foreach (var r in rs)
|
||
{
|
||
var lb = r.localBounds; var m = go.transform.worldToLocalMatrix * r.transform.localToWorldMatrix;
|
||
for (int c = 0; c < 8; c++)
|
||
{
|
||
var p = m.MultiplyPoint3x4(lb.center + Vector3.Scale(lb.extents, new Vector3((c & 1) == 0 ? -1 : 1, (c & 2) == 0 ? -1 : 1, (c & 4) == 0 ? -1 : 1)));
|
||
if (first) { wb = new Bounds(p, Vector3.zero); first = false; } else wb.Encapsulate(p);
|
||
}
|
||
}
|
||
var ls = go.transform.lossyScale;
|
||
float len = Mathf.Max(wb.size.x * Mathf.Abs(ls.x), Mathf.Max(wb.size.y * Mathf.Abs(ls.y), wb.size.z * Mathf.Abs(ls.z)));
|
||
ratioBefore = len / Mathf.Max(s_charHeight, 1e-6f);
|
||
mult = Mathf.Clamp(0.60f / Mathf.Max(ratioBefore, 1e-6f), 0.3f, 1.0f);
|
||
go.transform.localScale = Vector3.one * mult;
|
||
return go;
|
||
}
|
||
|
||
public static void Clear()
|
||
{
|
||
foreach (var g in s_spawned) if (g != null) UnityEngine.Object.DestroyImmediate(g);
|
||
s_spawned.Clear();
|
||
}
|
||
|
||
// ────────────────────────────────────────────────────────── 렌더
|
||
class Shot { public Color32[] px; }
|
||
|
||
static Shot Render(float ortho)
|
||
{
|
||
s_root.transform.SetPositionAndRotation(s_pos, s_rot);
|
||
if (s_an != null) { s_an.speed = 0f; try { s_an.Play("idle", 0, 0.25f); s_an.Update(0f); } catch { } }
|
||
s_cam.orthographicSize = ortho;
|
||
s_cam.transform.SetPositionAndRotation(s_center - (s_camRot * Vector3.forward) * 30f, s_camRot);
|
||
var rt = new RenderTexture(W, H, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
|
||
rt.antiAliasing = 1; rt.filterMode = FilterMode.Point; rt.Create();
|
||
var prev = s_cam.targetTexture; s_cam.targetTexture = rt; s_cam.Render(); s_cam.targetTexture = prev;
|
||
var pa = RenderTexture.active; RenderTexture.active = rt;
|
||
var t = new Texture2D(W, H, TextureFormat.RGBA32, false); t.ReadPixels(new Rect(0, 0, W, H), 0, 0); t.Apply();
|
||
RenderTexture.active = pa;
|
||
var s = new Shot { px = t.GetPixels32() };
|
||
UnityEngine.Object.Destroy(t); rt.Release(); UnityEngine.Object.Destroy(rt);
|
||
return s;
|
||
}
|
||
|
||
static void Save(Shot s, string name)
|
||
{
|
||
Directory.CreateDirectory(Path.GetDirectoryName(RAW + name));
|
||
var t = new Texture2D(W, H, TextureFormat.RGBA32, false); t.SetPixels32(s.px); t.Apply();
|
||
File.WriteAllBytes(RAW + name, t.EncodeToPNG()); UnityEngine.Object.Destroy(t);
|
||
}
|
||
|
||
static void Shoot(string name, float ortho) { Render(ortho); Save(Render(ortho), name); }
|
||
|
||
/// <summary>지정한 렌더러만 남긴 흑백 마스크(배경·포스트 off · 레이어 31 단독).</summary>
|
||
static bool[] Mask(Renderer[] on, float ortho, string savePng)
|
||
{
|
||
var all = UnityEngine.Object.FindObjectsByType<Renderer>(FindObjectsSortMode.None);
|
||
var was = all.Select(r => r.enabled).ToArray();
|
||
var ucd = s_cam.GetComponent<UnityEngine.Rendering.Universal.UniversalAdditionalCameraData>();
|
||
bool post = ucd != null && ucd.renderPostProcessing; if (ucd != null) ucd.renderPostProcessing = false;
|
||
int cull = s_cam.cullingMask; var clr = s_cam.clearFlags; var bg = s_cam.backgroundColor;
|
||
var lay = on.Select(r => r.gameObject.layer).ToArray();
|
||
for (int i = 0; i < all.Length; i++) all[i].enabled = on.Contains(all[i]);
|
||
foreach (var r in on) r.gameObject.layer = 31;
|
||
s_cam.cullingMask = 1 << 31; s_cam.clearFlags = CameraClearFlags.SolidColor; s_cam.backgroundColor = Color.black;
|
||
Render(ortho); // 🔴 갓 Instantiate 한 렌더러는 첫 렌더에 안 잡힌다(814x §6-7 과 같은 증상) — 1장 버린다
|
||
var sh = Render(ortho);
|
||
for (int i = 0; i < on.Length; i++) on[i].gameObject.layer = lay[i];
|
||
s_cam.cullingMask = cull; s_cam.clearFlags = clr; s_cam.backgroundColor = bg;
|
||
for (int i = 0; i < all.Length; i++) if (all[i] != null) all[i].enabled = was[i];
|
||
if (ucd != null) ucd.renderPostProcessing = post;
|
||
var m = new bool[sh.px.Length];
|
||
for (int i = 0; i < m.Length; i++) m[i] = sh.px[i].r + sh.px[i].g + sh.px[i].b > 24;
|
||
if (!string.IsNullOrEmpty(savePng))
|
||
{
|
||
var q = new Shot { px = new Color32[m.Length] };
|
||
for (int i = 0; i < m.Length; i++) q.px[i] = m[i] ? new Color32(255, 255, 255, 255) : new Color32(0, 0, 0, 255);
|
||
Save(q, savePng);
|
||
}
|
||
return m;
|
||
}
|
||
|
||
public struct Met { public int px, uniq, steps; public float ratio; public Vector3 mean; }
|
||
|
||
/// <summary>포스트 포함 게임 렌더에서 마스크 영역의 고유색/비율/단계를 잰다.</summary>
|
||
static Met Measure(Shot game, bool[] mask)
|
||
{
|
||
var set = new HashSet<int>(); int n = 0; long r = 0, g = 0, b = 0;
|
||
var cols = new List<Color32>();
|
||
for (int i = 0; i < mask.Length; i++)
|
||
{
|
||
if (!mask[i]) continue;
|
||
var p = game.px[i]; n++; r += p.r; g += p.g; b += p.b;
|
||
set.Add((p.r << 16) | (p.g << 8) | p.b);
|
||
cols.Add(p);
|
||
}
|
||
var m = new Met { px = n, uniq = set.Count, ratio = n > 0 ? set.Count * 100f / n : 0f };
|
||
if (n > 0) m.mean = new Vector3(r / (float)n, g / (float)n, b / (float)n);
|
||
m.steps = Steps(cols);
|
||
return m;
|
||
}
|
||
|
||
/// <summary>단계 = RGB 거리 16 이내로 묶었을 때 면적 90 % 를 덮는 군집 수(814x 정의).</summary>
|
||
static int Steps(List<Color32> cols)
|
||
{
|
||
if (cols.Count == 0) return 0;
|
||
var cent = new List<Vector3>(); var cnt = new List<int>();
|
||
foreach (var c in cols)
|
||
{
|
||
var v = new Vector3(c.r, c.g, c.b);
|
||
int bj = -1; float bd = 16f;
|
||
for (int j = 0; j < cent.Count; j++) { float d = (cent[j] / cnt[j] - v).magnitude; if (d < bd) { bd = d; bj = j; } }
|
||
if (bj < 0) { cent.Add(v); cnt.Add(1); }
|
||
else { cent[bj] += v; cnt[bj]++; }
|
||
}
|
||
var order = Enumerable.Range(0, cnt.Count).OrderByDescending(i => cnt[i]).ToArray();
|
||
int tot = cols.Count, acc = 0, k = 0;
|
||
foreach (var i in order) { acc += cnt[i]; k++; if (acc >= tot * 0.90f) break; }
|
||
return k;
|
||
}
|
||
|
||
// ────────────────────────────────────────────────────────── 팔레트 적용 (런타임 · 에셋 저장 0)
|
||
public static Material s_baseMat;
|
||
static readonly List<UnityEngine.Object> s_tmp = new List<UnityEngine.Object>();
|
||
|
||
/// <summary>구운 결과를 스폰된 무기에 끼운다. 에셋을 만들지 않는다(스윕용).</summary>
|
||
public static void ApplyBaked(GameObject go, Vector3[] pal, Mesh mesh)
|
||
{
|
||
if (s_baseMat == null) s_baseMat = UnityEditor.AssetDatabase.LoadAssetAtPath<Material>("Assets/WL/Look/Character/Materials/M05_ToonP.mat");
|
||
int N = pal.Length, G = Mathf.NextPowerOfTwo(Mathf.CeilToInt(Mathf.Sqrt(N))), CELL = 8, S = G * CELL;
|
||
var tex = new Texture2D(S, S, TextureFormat.RGBA32, false); // mip 없음
|
||
tex.filterMode = FilterMode.Point; tex.wrapMode = TextureWrapMode.Clamp;
|
||
var px = new Color32[S * S];
|
||
for (int i = 0; i < px.Length; i++) px[i] = new Color32(255, 0, 255, 255);
|
||
for (int j = 0; j < N; j++)
|
||
{
|
||
int gx = j % G, gy = j / G;
|
||
var c = new Color32((byte)Mathf.RoundToInt(Mathf.Clamp01(pal[j].x) * 255f), (byte)Mathf.RoundToInt(Mathf.Clamp01(pal[j].y) * 255f), (byte)Mathf.RoundToInt(Mathf.Clamp01(pal[j].z) * 255f), 255);
|
||
for (int y = 0; y < CELL; y++) for (int x = 0; x < CELL; x++) px[(gy * CELL + y) * S + gx * CELL + x] = c;
|
||
}
|
||
tex.SetPixels32(px); tex.Apply(false, false);
|
||
var mat = new Material(s_baseMat) { name = "W_ToonP_tmp" };
|
||
if (mat.HasProperty("_BaseMap")) mat.SetTexture("_BaseMap", tex);
|
||
if (mat.HasProperty("_MainTex")) mat.SetTexture("_MainTex", tex);
|
||
s_tmp.Add(tex); s_tmp.Add(mat);
|
||
foreach (var mf in go.GetComponentsInChildren<MeshFilter>(true)) mf.sharedMesh = mesh;
|
||
foreach (var r in go.GetComponentsInChildren<Renderer>(true))
|
||
{
|
||
if (r is ParticleSystemRenderer) continue;
|
||
var ms = r.sharedMaterials; for (int i = 0; i < ms.Length; i++) ms[i] = mat; r.sharedMaterials = ms;
|
||
}
|
||
}
|
||
|
||
public static void FreeTmp() { foreach (var o in s_tmp) if (o != null) UnityEngine.Object.DestroyImmediate(o); s_tmp.Clear(); }
|
||
|
||
/// <summary>씬의 참조 룩(외곽선·대비)을 새 렌더러까지 포함해 다시 얹는다(멱등).</summary>
|
||
/// <summary>참조 룩 장식을 벗긴다. 🔴 메시·머티리얼을 갈아끼우기 **전에** 반드시 부른다 —
|
||
/// 안 그러면 다음 Apply 의 Restore 가 예전 머티리얼을 도로 덮어써서 교체가 사라진다.</summary>
|
||
public static void UnLook()
|
||
{
|
||
WLReferenceLook.Restore();
|
||
foreach (var g in UnityEngine.Object.FindObjectsByType<GameObject>(FindObjectsInactive.Include, FindObjectsSortMode.None))
|
||
if (g != null && g.name == "WL814t_ReferenceLook_Volume") UnityEngine.Object.DestroyImmediate(g);
|
||
}
|
||
|
||
public static void ReLook()
|
||
{
|
||
var cfg = WLReferenceLookSettings.Instance;
|
||
if (cfg == null || cfg.enabled_ == 0) return;
|
||
// 🔴 Play 에서 `Destroy` 는 프레임 끝에 실행된다. run_script 는 한 콜백 안에서 끝나므로
|
||
// Apply 를 반복하면 임시 Volume 이 겹겹이 살아남아 포스트가 누적된다(캐릭터 수치가 회차마다 나빠진다).
|
||
// → Restore 로 참조를 끊은 뒤 남은 Volume 을 즉시 지우고 새로 얹는다.
|
||
WLReferenceLook.Restore();
|
||
foreach (var g in UnityEngine.Object.FindObjectsByType<GameObject>(FindObjectsInactive.Include, FindObjectsSortMode.None))
|
||
if (g != null && g.name == "WL814t_ReferenceLook_Volume") UnityEngine.Object.DestroyImmediate(g);
|
||
WLReferenceLook.Apply(WLReferenceLook.Opt.FromSettings(cfg), cfg);
|
||
}
|
||
|
||
/// <summary>🔴 「부위(팔레트 칸)당 색 단계」 — 814x 정의. 칸마다 고유색을 찍은 지도(Unlit·빛 0)로 칸을 가른다.</summary>
|
||
public static string CellSteps(GameObject go, Vector3[] pal, out int maxStep, out float avgStep)
|
||
{
|
||
maxStep = 0; avgStep = 0f;
|
||
int N = pal.Length, G = Mathf.NextPowerOfTwo(Mathf.CeilToInt(Mathf.Sqrt(N))), CELL = 8, S = G * CELL;
|
||
var idTex = new Texture2D(S, S, TextureFormat.RGBA32, false) { filterMode = FilterMode.Point, wrapMode = TextureWrapMode.Clamp };
|
||
var px = new Color32[S * S]; var idCol = new Color32[N];
|
||
for (int i = 0; i < px.Length; i++) px[i] = new Color32(0, 0, 0, 255);
|
||
for (int j = 0; j < N; j++)
|
||
{
|
||
idCol[j] = new Color32((byte)(20 + j * 13 % 236), (byte)(250 - j * 29 % 200), (byte)(40 + j * 47 % 200), 255);
|
||
int gx = j % G, gy = j / G;
|
||
for (int y = 0; y < CELL; y++) for (int x = 0; x < CELL; x++) px[(gy * CELL + y) * S + gx * CELL + x] = idCol[j];
|
||
}
|
||
idTex.SetPixels32(px); idTex.Apply(false, false);
|
||
var rends = go.GetComponentsInChildren<Renderer>(true).Where(r => !(r is ParticleSystemRenderer)).ToArray();
|
||
var keep = rends.Select(r => r.sharedMaterials).ToArray();
|
||
var idMat = new Material(Shader.Find("Unlit/Texture")); idMat.mainTexture = idTex;
|
||
foreach (var r in rends) { var ms = new Material[r.sharedMaterials.Length]; for (int i = 0; i < ms.Length; i++) ms[i] = idMat; r.sharedMaterials = ms; }
|
||
var idShot = RenderIsolated(rends);
|
||
for (int i = 0; i < rends.Length; i++) rends[i].sharedMaterials = keep[i];
|
||
UnityEngine.Object.DestroyImmediate(idMat); UnityEngine.Object.DestroyImmediate(idTex);
|
||
|
||
var game = Render(ORTHO);
|
||
var sbb = new StringBuilder(); int sum = 0, cnt = 0;
|
||
for (int j = 0; j < N; j++)
|
||
{
|
||
var cols = new List<Color32>();
|
||
for (int i = 0; i < idShot.px.Length; i++)
|
||
{
|
||
var p = idShot.px[i];
|
||
if (Mathf.Abs(p.r - idCol[j].r) <= 10 && Mathf.Abs(p.g - idCol[j].g) <= 10 && Mathf.Abs(p.b - idCol[j].b) <= 10) cols.Add(game.px[i]);
|
||
}
|
||
if (cols.Count < 3) continue; // 3 px 미만 칸은 통계 의미 없음
|
||
int st = Steps(cols);
|
||
sbb.Append(j + ":" + cols.Count + "px/" + st + "단 ");
|
||
if (st > maxStep) maxStep = st;
|
||
sum += st; cnt++;
|
||
}
|
||
avgStep = cnt > 0 ? sum / (float)cnt : 0f;
|
||
return "칸 " + cnt + "개 측정 · 평균 " + avgStep.ToString("F2") + " · 최대 " + maxStep + " · " + sbb;
|
||
}
|
||
|
||
static Shot RenderIsolated(Renderer[] on)
|
||
{
|
||
var all = UnityEngine.Object.FindObjectsByType<Renderer>(FindObjectsSortMode.None);
|
||
var was = all.Select(r => r.enabled).ToArray();
|
||
var ucd = s_cam.GetComponent<UnityEngine.Rendering.Universal.UniversalAdditionalCameraData>();
|
||
bool post = ucd != null && ucd.renderPostProcessing; if (ucd != null) ucd.renderPostProcessing = false;
|
||
int cull = s_cam.cullingMask; var clr = s_cam.clearFlags; var bg = s_cam.backgroundColor;
|
||
var lay = on.Select(r => r.gameObject.layer).ToArray();
|
||
for (int i = 0; i < all.Length; i++) all[i].enabled = on.Contains(all[i]);
|
||
foreach (var r in on) r.gameObject.layer = 31;
|
||
s_cam.cullingMask = 1 << 31; s_cam.clearFlags = CameraClearFlags.SolidColor; s_cam.backgroundColor = Color.black;
|
||
Render(ortho: ORTHO);
|
||
var sh = Render(ORTHO);
|
||
for (int i = 0; i < on.Length; i++) on[i].gameObject.layer = lay[i];
|
||
s_cam.cullingMask = cull; s_cam.clearFlags = clr; s_cam.backgroundColor = bg;
|
||
for (int i = 0; i < all.Length; i++) if (all[i] != null) all[i].enabled = was[i];
|
||
if (ucd != null) ucd.renderPostProcessing = post;
|
||
return sh;
|
||
}
|
||
|
||
public static Met MeasureNow(GameObject go, string maskName, string shotName)
|
||
{
|
||
var wr = go.GetComponentsInChildren<Renderer>(true).Where(r => r.enabled && r.gameObject.activeInHierarchy && !(r is ParticleSystemRenderer)).ToArray();
|
||
var mk = Mask(wr, ORTHO, maskName);
|
||
var sh = Render(ORTHO); Save(sh, shotName);
|
||
return Measure(sh, mk);
|
||
}
|
||
|
||
public static Met MeasureChar(string maskName, string shotName)
|
||
{
|
||
var br = s_root.GetComponentsInChildren<Renderer>(true).Where(r => r.enabled && r.gameObject.activeInHierarchy
|
||
&& !r.transform.IsChildOf(s_sockets[0]) && !r.transform.IsChildOf(s_sockets[1]) && !r.transform.IsChildOf(s_sockets[2])
|
||
&& !r.transform.IsChildOf(s_sockets[3]) && !r.transform.IsChildOf(s_sockets[4]) && !r.transform.IsChildOf(s_sockets[5]) && !r.transform.IsChildOf(s_sockets[6])).ToArray();
|
||
var mk = Mask(br, ORTHO, maskName);
|
||
var sh = Render(ORTHO); if (!string.IsNullOrEmpty(shotName)) Save(sh, shotName);
|
||
return Measure(sh, mk);
|
||
}
|
||
|
||
public static void PubSetup() { Setup(); }
|
||
public static void PubTeardown() { Teardown(); }
|
||
public static void Log(string s) { L(s); }
|
||
public static void Flush(string f) { Directory.CreateDirectory("AgentScripts"); File.WriteAllText("AgentScripts/" + f, sb.ToString(), new UTF8Encoding(true)); }
|
||
public static void Reset() { sb.Length = 0; }
|
||
public static void ShootGame(string name) { Shoot(name, ORTHO); }
|
||
|
||
// ── 디버그: 마스크가 0 px 로 나온 무기를 캔다
|
||
public static void Dbg()
|
||
{
|
||
sb.Length = 0; Setup();
|
||
foreach (var wn in new[] { "PP_Theme_10_Mace_003", "PP_Theme_08_Mace_003" })
|
||
{
|
||
Clear();
|
||
float rb, mult; var go = Spawn(wn, 1, out rb, out mult);
|
||
var r = go.GetComponentsInChildren<Renderer>(true)[0];
|
||
L(wn + " scale=" + go.transform.localScale.ToString("F4") + " lossy=" + go.transform.lossyScale.ToString("F4")
|
||
+ " worldBounds=" + r.bounds.ToString("F3") + " vis=" + r.isVisible
|
||
+ " mat=" + r.sharedMaterial.name + " shader=" + r.sharedMaterial.shader.name
|
||
+ " layer=" + r.gameObject.layer + " pos=" + go.transform.position.ToString("F3"));
|
||
var mk = Mask(new[] { r }, ORTHO, "dbg_" + wn + ".png");
|
||
int n = 0; for (int i = 0; i < mk.Length; i++) if (mk[i]) n++;
|
||
L(" mask px=" + n);
|
||
}
|
||
Clear(); Teardown();
|
||
File.WriteAllText("AgentScripts/WL816b_DBG.txt", sb.ToString(), new UTF8Encoding(true));
|
||
}
|
||
|
||
// ────────────────────────────────────────────────────────── ① 우선순위 프로브
|
||
public static void Probe()
|
||
{
|
||
sb.Length = 0; Setup();
|
||
L("");
|
||
L("=== 무기별 화면 실측 (인게임 Play · 포스트 포함 · 1080x1920 · ortho " + ORTHO.ToString("F0") + ") ===");
|
||
|
||
var bodyR = s_root.GetComponentsInChildren<Renderer>(true).Where(r => r.enabled && r.gameObject.activeInHierarchy).ToArray();
|
||
var mBody = Mask(bodyR, ORTHO, "mask_char.png");
|
||
var gm = Render(ORTHO); Save(gm, "a_before_none.png");
|
||
var mc = Measure(gm, mBody);
|
||
L(string.Format("캐릭터(무기 없음) : {0} px · 고유색 {1} · 비율 {2:F2} % · 단계 {3}", mc.px, mc.uniq, mc.ratio, mc.steps));
|
||
L("");
|
||
L("| 무기 | 소켓 | 클래스 | 배수 | 화면 px | 고유색 | 서로다른색 % | 단계 |");
|
||
L("|---|---|---|---|---|---|---|---|");
|
||
|
||
foreach (var w in WEAPONS)
|
||
{
|
||
Clear();
|
||
float rb, mult;
|
||
var go = Spawn(w[0], int.Parse(w[1]), out rb, out mult);
|
||
if (go == null) { L("| " + w[0] + " | " + w[1] + " | " + w[2] + " | — | 부착실패 | | | |"); continue; }
|
||
var wr = go.GetComponentsInChildren<Renderer>(true).Where(r => r.enabled && r.gameObject.activeInHierarchy).ToArray();
|
||
if (wr.Length == 0) { L("| " + w[0] + " | " + w[1] + " | " + w[2] + " | — | 렌더러 0 | | | |"); continue; }
|
||
var mk = Mask(wr, ORTHO, "mask_" + w[0] + ".png");
|
||
var sh = Render(ORTHO); Save(sh, "a_before_" + w[0] + ".png");
|
||
var mm = Measure(sh, mk);
|
||
L(string.Format("| {0} | {1} | {2} | ×{3:F4} | **{4}** | {5} | {6:F2} | {7} |",
|
||
w[0], w[1], w[2], mult, mm.px, mm.uniq, mm.ratio, mm.steps));
|
||
}
|
||
Clear(); Teardown();
|
||
Directory.CreateDirectory("AgentScripts");
|
||
File.WriteAllText("AgentScripts/WL816b_PLAY_PROBE.txt", sb.ToString(), new UTF8Encoding(true));
|
||
Debug.Log("[WL816b] play probe done");
|
||
}
|
||
}
|