// WL-816r — 섬의 「풀 없는 지형」(길 Road · 밭 Soil · 흙띠/모래) 화면색을 데모 흙 톤에 맞춘다. // 🔴 풀·바닥 초록(816q 확정)은 건드리지 않는다 — 읽기만 하고 대조에만 쓴다. // 에셋 무변경(전부 런타임 복제본). Debug.LogError 금지. public static class WL816r_Play { public static void Start() { var go = UnityEngine.GameObject.Find("~WL816rPlay"); if (go != null) UnityEngine.Object.DestroyImmediate(go); go = new UnityEngine.GameObject("~WL816rPlay"); go.AddComponent(); } } public class WL816r_Runner : UnityEngine.MonoBehaviour { static System.Text.StringBuilder sb; const int W = 1080, H = 1920; const int TS = 768; static string Dir = "Screenshots_WL/WL816r"; // 섬 윗면 텍스처 후보 (모래 사분면, 흙 사분면) — 데모 흙 화면색 #A4A88B 목표 static readonly string[][] TopCand = { new[]{"CCCBB5","807057"}, // 0 = 현행 (전) new[]{"D4D6BE","C6C8B0"}, // 1 = 데모 흙 톤 + 한 단 어두운 흙 new[]{"D9D8C2","CBCAB4"}, // 2 new[]{"CFD1B9","C1C3AB"}, // 3 }; // 밭(Soil) 마름쌍 목표색 후보 (Soil 은 Toon 이 아니라 Simple Lit — 응답이 다르다) static readonly string[] SoilCand = { "-", "D4D6BE", "BFC1A9", "AAAC94" }; void Start() { StartCoroutine(Co()); } System.Collections.IEnumerator Co() { sb = new System.Text.StringBuilder(); System.IO.Directory.CreateDirectory(Dir); if (UnityEngine.SceneManagement.SceneManager.sceneCount < 2) { var op = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("Level01", UnityEngine.SceneManagement.LoadSceneMode.Additive); while (op != null && !op.isDone) yield return null; } for (int i = 0; i < 7; i++) yield return new UnityEngine.WaitForSeconds(1f); var g = UnityEngine.Object.FindFirstObjectByType(UnityEngine.FindObjectsInactive.Include); if (g == null) { L("WLIslandGrass 없음 — 중단"); Flush(); yield break; } var b = g.CalculateInstancesBounds(); // ── 그룹 수집 ──────────────────────────────────────────────────────── var roadR = new System.Collections.Generic.List(); var soilR = new System.Collections.Generic.List(); var topR = new System.Collections.Generic.List(); var cfg = WL.Look.Farm.WLIslandLookSettings.Instance; var topMat = cfg.islandTopMaterial; foreach (var r in UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsSortMode.None)) { if (r == null || !r.enabled) continue; if (r.GetComponent() != null) { soilR.Add(r); continue; } bool isRoad = false; for (var t = r.transform; t != null; t = t.parent) if (t.name == "Road") { isRoad = true; break; } if (isRoad) { roadR.Add(r); continue; } foreach (var m in r.sharedMaterials) if (m != null && m == topMat) { topR.Add(r); break; } } L("그룹 — Road 렌더러=" + roadR.Count + " · Soil=" + soilR.Count + " · 섬윗면(=" + (topMat ? topMat.name : "-") + ")=" + topR.Count); var roadMats = new System.Collections.Generic.HashSet(); foreach (var r in roadR) foreach (var m in r.sharedMaterials) if (m != null) roadMats.Add(m); foreach (var m in roadMats) L(" Road mat=" + m.name + " sh=" + (m.shader ? m.shader.name : "-") + " Dif=" + Cs(m, "_DiffuseColor") + " Shd=" + Cs(m, "_ShadowDiffuseColor")); var soilMats = new System.Collections.Generic.HashSet(); foreach (var r in soilR) foreach (var m in r.sharedMaterials) if (m != null) soilMats.Add(m); foreach (var m in soilMats) L(" Soil mat=" + m.name + " sh=" + (m.shader ? m.shader.name : "-") + " color=" + (m.HasProperty("_BaseColor") ? Hex32(m.GetColor("_BaseColor").gamma) : (m.HasProperty("_Color") ? Hex32(m.GetColor("_Color").gamma) : "-"))); var camTop = MakeTop(b.center.x, b.center.z, 6f); var camPd = MakePd(); // ── ID 마스크 (그룹별 화면 픽셀 확정) ──────────────────────────────── var maskTop = IdMask(camTop, TS, TS, roadR, soilR, topR); var maskPd = IdMask(camPd, W, H, roadR, soilR, topR); L("마스크(탑다운) road=" + Count(maskTop, 1) + " soil=" + Count(maskTop, 2) + " top=" + Count(maskTop, 3)); L("마스크(PD) road=" + Count(maskPd, 1) + " soil=" + Count(maskPd, 2) + " top=" + Count(maskPd, 3)); // ── 전 ────────────────────────────────────────────────────────────── Report("[전]", camTop, TS, TS, maskTop, "before_top"); Report("[전PD]", camPd, W, H, maskPd, "before_pd"); // ── 섬 윗면 텍스처 후보 스윕 (초록 행은 원본 바이트 그대로) ────────── var srcBytes = System.IO.File.ReadAllBytes("Assets/WL/Look/Farm/Textures/WL_IslandTop_Demo.png"); var origTop = topMat; UnityEngine.Material bestTopMat = null; for (int ci = 1; ci < TopCand.Length; ci++) { var tex = MakeTopTex(srcBytes, TopCand[ci][0], TopCand[ci][1]); var m = new UnityEngine.Material(origTop); m.name = origTop.name + "_r" + ci; m.SetTexture("_BaseMap", tex); m.SetTexture("_ShadowBaseMap", tex); SwapMat(topR, origTop, m); yield return null; yield return null; L("[윗면 후보" + ci + " 모래#" + TopCand[ci][0] + " 흙#" + TopCand[ci][1] + "] " + Sample(camTop, TS, TS, maskTop, 3)); SwapMat(topR, m, origTop); if (ci == 1) bestTopMat = m; else { UnityEngine.Object.DestroyImmediate(m); UnityEngine.Object.DestroyImmediate(tex); } } // ── 길 후보 (데모 흙 하이라이트/그림자비 그대로) ───────────────────── // 데모 Terrain.mat 흙 = Highlight(0.8396,0.8304,0.6456) · Shadow(0.3019,0.2276,0.1324) var roadCand = new[] { "D4D6BE", "D9D8C2", "CFD1B9" }; var roadShadowRatio = new UnityEngine.Color(0.3596f, 0.2741f, 0.2051f, 1f); var roadOrig = new System.Collections.Generic.List(roadMats); for (int ci = 0; ci < roadCand.Length; ci++) { var clones = new System.Collections.Generic.List(); foreach (var om in roadOrig) { var m = new UnityEngine.Material(om); var lin = Hex(roadCand[ci]).linear; m.SetColor("_DiffuseColor", lin); m.SetColor("_ShadowDiffuseColor", new UnityEngine.Color(lin.r * roadShadowRatio.r, lin.g * roadShadowRatio.g, lin.b * roadShadowRatio.b, 1f)); clones.Add(m); SwapMat(roadR, om, m); } yield return null; yield return null; L("[길 후보 #" + roadCand[ci] + "] " + Sample(camTop, TS, TS, maskTop, 1)); for (int i = 0; i < roadOrig.Count; i++) { SwapMat(roadR, clones[i], roadOrig[i]); UnityEngine.Object.DestroyImmediate(clones[i]); } } // ── 밭 후보 ───────────────────────────────────────────────────────── for (int ci = 1; ci < SoilCand.Length; ci++) { RetoneSoil(SoilCand[ci]); yield return null; yield return null; L("[밭 후보 #" + SoilCand[ci] + "] " + Sample(camTop, TS, TS, maskTop, 2) + " · 색종류=" + SoilColorKinds(soilR)); } RetoneSoil("-"); // 원복 Flush(); } // ───────────────────────────────── 밭 재도색 (WLIslandLook 과 같은 경로) static UnityEngine.Color[] s_soilOrig; static readonly string[] SF = { "soilDryColor1", "soilDryColor2", "soilWetColor1", "soilWetColor2" }; static void RetoneSoil(string hex) { var farms = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.None); var t = typeof(CryingSnow.FarmingIsland.Farm); foreach (var farm in farms) { var fis = new System.Reflection.FieldInfo[4]; var v = new UnityEngine.Color[4]; bool ok = true; for (int k = 0; k < 4; k++) { fis[k] = t.GetField(SF[k], System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); if (fis[k] == null) { ok = false; break; } v[k] = (UnityEngine.Color)fis[k].GetValue(farm); } if (!ok) continue; if (s_soilOrig == null) s_soilOrig = new[] { v[0], v[1], v[2], v[3] }; if (hex == "-") { for (int k = 0; k < 4; k++) fis[k].SetValue(farm, s_soilOrig[k]); } else { var tgt = Hex(hex).linear; // 마름쌍 중점을 목표로 옮기고, 칸 차이·마름/젖음 차이는 밝기비로 보존 var dm = (s_soilOrig[0] + s_soilOrig[1]) * 0.5f; var wm = (s_soilOrig[2] + s_soilOrig[3]) * 0.5f; float dl = UnityEngine.Mathf.Max(0.0001f, Lum(dm)), wl = Lum(wm); var wt = tgt * (wl / dl); for (int k = 0; k < 4; k++) { var mid = k < 2 ? dm : wm; var nt = k < 2 ? tgt : wt; var d = s_soilOrig[k] - mid; // 칸 차이 보존 fis[k].SetValue(farm, new UnityEngine.Color(nt.r + d.r, nt.g + d.g, nt.b + d.b, 1f)); } } foreach (var s in farm.GetComponentsInChildren(true)) if (s != null) { try { s.Initialize(farm); } catch { } } } } static float Lum(UnityEngine.Color c) { return 0.299f * c.r + 0.587f * c.g + 0.114f * c.b; } static int SoilColorKinds(System.Collections.Generic.List soilR) { var s = new System.Collections.Generic.HashSet(); foreach (var r in soilR) { var m = r.material; if (m == null) continue; var c = m.HasProperty("_BaseColor") ? m.GetColor("_BaseColor") : (m.HasProperty("_Color") ? m.GetColor("_Color") : UnityEngine.Color.black); s.Add(Hex32(c.gamma)); } return s.Count; } // ───────────────────────────────── 텍스처 (초록 행 원본 유지) static UnityEngine.Texture2D MakeTopTex(byte[] srcBytes, string sandHex, string dirtHex) { var tex = new UnityEngine.Texture2D(2, 2, UnityEngine.TextureFormat.RGBA32, false); UnityEngine.ImageConversion.LoadImage(tex, srcBytes); tex.filterMode = UnityEngine.FilterMode.Point; tex.wrapMode = UnityEngine.TextureWrapMode.Clamp; int w = tex.width, h = tex.height; var px = tex.GetPixels32(); var sand = Hex32c(sandHex); var dirt = Hex32c(dirtHex); // 원본에서 모래/흙 색을 「값으로」 찾아 바꾼다 (좌표 하드코딩 금지 · 초록은 손대지 않는다) var sandSrc = new UnityEngine.Color32(0xCC, 0xCB, 0xB5, 255); var dirtSrc = new UnityEngine.Color32(0x80, 0x70, 0x57, 255); int ns = 0, nd = 0; for (int i = 0; i < px.Length; i++) { if (Same(px[i], sandSrc)) { px[i] = sand; ns++; } else if (Same(px[i], dirtSrc)) { px[i] = dirt; nd++; } } tex.SetPixels32(px); tex.Apply(false); L(" 텍스처 " + w + "x" + h + " 모래칸=" + ns + " 흙칸=" + nd + " (초록 무변경)"); return tex; } static bool Same(UnityEngine.Color32 a, UnityEngine.Color32 b) { return a.r == b.r && a.g == b.g && a.b == b.b; } static void SwapMat(System.Collections.Generic.List rs, UnityEngine.Material from, UnityEngine.Material to) { foreach (var r in rs) { var ms = r.sharedMaterials; bool hit = false; for (int i = 0; i < ms.Length; i++) if (ms[i] == from) { ms[i] = to; hit = true; } if (hit) r.sharedMaterials = ms; } } // ───────────────────────────────── ID 마스크 static byte[] IdMask(UnityEngine.Camera cam, int w, int h, System.Collections.Generic.List road, System.Collections.Generic.List soil, System.Collections.Generic.List top) { var unlit = UnityEngine.Shader.Find("Universal Render Pipeline/Unlit"); var saved = new System.Collections.Generic.Dictionary(); var mats = new System.Collections.Generic.List(); System.Action, UnityEngine.Color> paint = (rs, c) => { var m = new UnityEngine.Material(unlit); m.SetColor("_BaseColor", c); mats.Add(m); foreach (var r in rs) { if (!saved.ContainsKey(r)) saved[r] = r.sharedMaterials; var arr = new UnityEngine.Material[r.sharedMaterials.Length]; for (int i = 0; i < arr.Length; i++) arr[i] = m; r.sharedMaterials = arr; } }; // 그 밖의 렌더러는 검정 var others = new System.Collections.Generic.List(); foreach (var r in UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsSortMode.None)) if (r != null && r.enabled && !road.Contains(r) && !soil.Contains(r) && !top.Contains(r)) others.Add(r); paint(others, UnityEngine.Color.black); paint(road, new UnityEngine.Color(1f, 0f, 0f, 1f)); paint(soil, new UnityEngine.Color(0f, 1f, 0f, 1f)); paint(top, new UnityEngine.Color(0f, 0f, 1f, 1f)); var tex = Grab(cam, w, h); var px = tex.GetPixels32(); var mask = new byte[w * h]; for (int i = 0; i < px.Length; i++) { var p = px[i]; if (p.r > 150 && p.g < 90 && p.b < 90) mask[i] = 1; else if (p.g > 150 && p.r < 90 && p.b < 90) mask[i] = 2; else if (p.b > 150 && p.r < 90 && p.g < 90) mask[i] = 3; } UnityEngine.Object.DestroyImmediate(tex); foreach (var kv in saved) kv.Key.sharedMaterials = kv.Value; foreach (var m in mats) UnityEngine.Object.DestroyImmediate(m); return mask; } static int Count(byte[] m, byte id) { int n = 0; for (int i = 0; i < m.Length; i++) if (m[i] == id) n++; return n; } // ───────────────────────────────── 측정 static void Report(string tag, UnityEngine.Camera cam, int w, int h, byte[] mask, string name) { var tex = Grab(cam, w, h); System.IO.File.WriteAllBytes(Dir + "/" + name + ".png", UnityEngine.ImageConversion.EncodeToPNG(tex)); var px = tex.GetPixels32(); L(tag + " 길 " + Stat(px, mask, 1) + " | 밭 " + Stat(px, mask, 2) + " | 섬윗면 " + Stat(px, mask, 3)); UnityEngine.Object.DestroyImmediate(tex); } static string Sample(UnityEngine.Camera cam, int w, int h, byte[] mask, byte id) { var tex = Grab(cam, w, h); var px = tex.GetPixels32(); string s = Stat(px, mask, id); UnityEngine.Object.DestroyImmediate(tex); return s; } static string Stat(UnityEngine.Color32[] px, byte[] mask, byte id) { long r = 0, g = 0, b2 = 0; int n = 0; var uniq = new System.Collections.Generic.Dictionary(); for (int i = 0; i < mask.Length && i < px.Length; i++) { if (mask[i] != id) continue; var p = px[i]; r += p.r; g += p.g; b2 += p.b; n++; int k = (p.r << 16) | (p.g << 8) | p.b; uniq[k] = uniq.ContainsKey(k) ? uniq[k] + 1 : 1; } if (n == 0) return "px0"; var list = new System.Collections.Generic.List>(uniq); list.Sort((A, B) => B.Value.CompareTo(A.Value)); var top = new System.Text.StringBuilder(); for (int i = 0; i < System.Math.Min(3, list.Count); i++) top.Append(string.Format(" #{0:X6}×{1}", list[i].Key, list[i].Value)); return string.Format("n={0} 평균=#{1:X2}{2:X2}{3:X2} 고유={4} 상위:{5}", n, r / n, g / n, b2 / n, uniq.Count, top); } // ───────────────────────────────── 유틸 static UnityEngine.Color Hex(string h) { return new UnityEngine.Color(System.Convert.ToInt32(h.Substring(0, 2), 16) / 255f, System.Convert.ToInt32(h.Substring(2, 2), 16) / 255f, System.Convert.ToInt32(h.Substring(4, 2), 16) / 255f, 1f); } static UnityEngine.Color32 Hex32c(string h) { return new UnityEngine.Color32((byte)System.Convert.ToInt32(h.Substring(0, 2), 16), (byte)System.Convert.ToInt32(h.Substring(2, 2), 16), (byte)System.Convert.ToInt32(h.Substring(4, 2), 16), 255); } static string Hex32(UnityEngine.Color c) { return string.Format("#{0:X2}{1:X2}{2:X2}", (int)(UnityEngine.Mathf.Clamp01(c.r) * 255), (int)(UnityEngine.Mathf.Clamp01(c.g) * 255), (int)(UnityEngine.Mathf.Clamp01(c.b) * 255)); } static string Cs(UnityEngine.Material m, string n) { if (m == null || !m.HasProperty(n)) return "-"; var c = m.GetColor(n); return "(" + c.r.ToString("F4") + "," + c.g.ToString("F4") + "," + c.b.ToString("F4") + ")=" + Hex32(c.gamma); } static UnityEngine.Camera MakeTop(float cx, float cz, float size) { var go = UnityEngine.GameObject.Find("~816rTop"); if (go == null) go = new UnityEngine.GameObject("~816rTop"); var c = go.GetComponent(); if (c == null) c = go.AddComponent(); c.orthographic = true; c.orthographicSize = size; c.nearClipPlane = 0.1f; c.farClipPlane = 200f; c.transform.rotation = UnityEngine.Quaternion.Euler(90f, 0f, 0f); c.transform.position = new UnityEngine.Vector3(cx, 60f, cz); c.enabled = false; return c; } static UnityEngine.Camera MakePd() { UnityEngine.Vector3 tgt = UnityEngine.Vector3.zero; var pc = UnityEngine.GameObject.FindGameObjectWithTag("Player"); if (pc != null) tgt = pc.transform.position; var go = UnityEngine.GameObject.Find("~816rPd"); if (go == null) go = new UnityEngine.GameObject("~816rPd"); var c = go.GetComponent(); if (c == null) c = go.AddComponent(); c.orthographic = false; c.fieldOfView = 60f; c.nearClipPlane = 0.1f; c.farClipPlane = 300f; c.transform.rotation = UnityEngine.Quaternion.Euler(45f, 45f, 0f); c.transform.position = tgt - c.transform.forward * 12f; c.enabled = false; return c; } static UnityEngine.Texture2D Grab(UnityEngine.Camera cam, int w, int h) { var rt = new UnityEngine.RenderTexture(w, h, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); rt.Create(); cam.targetTexture = rt; cam.Render(); var prev = UnityEngine.RenderTexture.active; UnityEngine.RenderTexture.active = rt; var tex = new UnityEngine.Texture2D(w, h, UnityEngine.TextureFormat.RGB24, false); tex.ReadPixels(new UnityEngine.Rect(0, 0, w, h), 0, 0); tex.Apply(); UnityEngine.RenderTexture.active = prev; cam.targetTexture = null; rt.Release(); UnityEngine.Object.DestroyImmediate(rt); return tex; } static void L(string s) { sb.AppendLine(s); Flush(); } static void Flush() { System.IO.File.WriteAllText("AgentScripts/WL816r_PLAY.txt", sb.ToString()); UnityEngine.Debug.Log("[816r play]\n" + sb.ToString()); } }