// WL-816f §2 — PD 경로(InGame 활성 + Level01 Additive) Play 에서 바닥 3요소를 단계별로 바꿔 캡처한다. // 변형 목록은 AgentScripts/WL816f_PLAN.txt (한 줄 = 한 단계) // 이름 | 바닥초록HEX | 풀Diffuse | 풀Shadow | 밀도 | 꽃확률 | 풀Scale // '-' = 그대로. // 🔴 에셋을 고치지 않는다 — 전부 런타임 인스턴스(Material/Texture2D/SO 복사본). public static class WL816f_Sweep { public static void Start() { var go = UnityEngine.GameObject.Find("~WL816fSweep"); if (go != null) UnityEngine.Object.DestroyImmediate(go); go = new UnityEngine.GameObject("~WL816fSweep"); go.AddComponent(); UnityEngine.Debug.Log("[816f sweep] 시작"); } } public class WL816f_SweepRunner : UnityEngine.MonoBehaviour { const string Dir = "Screenshots_WL/WL816f/"; const int W = 1080, H = 1920; static System.Text.StringBuilder sb; UnityEngine.Material groundMat, grassMat, flowerMat; UnityEngine.Texture2D groundTex; UnityEngine.Color32 sand = new UnityEngine.Color32(0xCC, 0xCB, 0xB5, 255); UnityEngine.Color32 dirt = new UnityEngine.Color32(0x80, 0x70, 0x57, 255); WL.Look.Farm.WLIslandLookSettings rt; UnityEngine.Vector3 aim; void Start() { StartCoroutine(Co()); } System.Collections.IEnumerator Co() { sb = new System.Text.StringBuilder(); // ── PD 경로 부팅 ──────────────────────────────────────────── 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 < 5; i++) yield return new UnityEngine.WaitForSeconds(1f); var g = UnityEngine.Object.FindFirstObjectByType(UnityEngine.FindObjectsInactive.Include); if (g == null) { L("🔴 WLIslandGrass 없음 — 중단"); Done(); yield break; } var src = WL.Look.Farm.WLIslandLookSettings.Instance; rt = UnityEngine.Object.Instantiate(src); // 에셋을 고치지 않기 위한 런타임 복사본 g.cfg = rt; // 런타임 머티리얼·텍스처 groundTex = new UnityEngine.Texture2D(8, 8, UnityEngine.TextureFormat.RGBA32, false); groundTex.filterMode = UnityEngine.FilterMode.Point; groundTex.wrapMode = UnityEngine.TextureWrapMode.Clamp; SetGround(new UnityEngine.Color32(0xAF, 0xED, 0xAE, 255)); groundMat = new UnityEngine.Material(rt.islandTopMaterial); groundMat.SetTexture("_BaseMap", groundTex); if (groundMat.HasProperty("_ShadowBaseMap")) groundMat.SetTexture("_ShadowBaseMap", groundTex); rt.islandTopMaterial = groundMat; for (int i = 0; i < rt.scatter.Length; i++) { var s = rt.scatter[i]; if (s == null || s.material == null) continue; if (s.label.Contains("풀")) { grassMat = new UnityEngine.Material(s.material); s.material = grassMat; } else if (s.label.Contains("꽃")) { flowerMat = new UnityEngine.Material(s.material); s.material = flowerMat; } } // 섬 본체 렌더러에 런타임 바닥 머티리얼을 얹는다 int nb = 0; var rends = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.None); for (int i = 0; i < rends.Length; i++) { var r = rends[i]; if (r == null || r.gameObject.scene.name != "Level01") continue; if (r.GetComponent() == null) continue; var ms = r.sharedMaterials; bool ch = false; for (int m = 0; m < ms.Length; m++) if (ms[m] != null && ms[m].name.StartsWith("Farm_IslandTop_Demo")) { ms[m] = groundMat; ch = true; } if (ch) { r.sharedMaterials = ms; nb++; } } L("바닥 머티리얼 런타임 교체 렌더러 " + nb); // 조준점 = 깨끗한 잔디 타일 중심(막힌 것이 가장 적은 타일) aim = PickCleanSpot(); L("조준점 " + aim.ToString("F2")); // ── 변형 실행 ─────────────────────────────────────────────── string planPath = "AgentScripts/WL816f_PLAN.txt"; if (!System.IO.File.Exists(planPath)) { L("PLAN 없음"); Done(); yield break; } var lines = System.IO.File.ReadAllLines(planPath); foreach (var raw in lines) { var line = raw.Trim(); if (line.Length == 0 || line.StartsWith("#")) continue; var p = line.Split('|'); for (int i = 0; i < p.Length; i++) p[i] = p[i].Trim(); string name = p[0]; if (p.Length > 1 && p[1] != "-") SetGround(Hex(p[1])); if (p.Length > 2 && p[2] != "-" && grassMat != null) grassMat.SetColor("_DiffuseColor", HexC(p[2])); if (p.Length > 3 && p[3] != "-" && grassMat != null) grassMat.SetColor("_ShadowDiffuseColor", HexC(p[3])); if (p.Length > 4 && p[4] != "-") SetDensity(float.Parse(p[4])); if (p.Length > 5 && p[5] != "-") SetFlowerProb(float.Parse(p[5])); if (p.Length > 6 && p[6] != "-") SetGrassScale(float.Parse(p[6])); g.Rebuild(); yield return null; yield return null; var cam = GroundCam(aim, 6f); Shoot(cam, Dir + name + ".png"); L(name + " : 타일 " + WL.Look.Farm.WLIslandGrass.Tiles + " · 인스턴스 " + WL.Look.Farm.WLIslandGrass.Instances + " · 밀도 " + WL.Look.Farm.WLIslandGrass.UsedDensity.ToString("F2") + " · 삼각형 " + WL.Look.Farm.WLIslandGrass.Triangles + " · 그라운드 " + UnityEngine.ColorUtility.ToHtmlStringRGB(groundTex.GetPixel(0, 7)) + " · 풀D " + (grassMat ? UnityEngine.ColorUtility.ToHtmlStringRGB(grassMat.GetColor("_DiffuseColor")) : "-") + " · 풀S " + (grassMat ? UnityEngine.ColorUtility.ToHtmlStringRGB(grassMat.GetColor("_ShadowDiffuseColor")) : "-")); yield return null; } Done(); } void Done() { System.IO.File.WriteAllText("AgentScripts/WL816f_SWEEP.txt", sb.ToString()); UnityEngine.Debug.Log("[816f sweep 완료]\n" + sb.ToString()); } static void L(string s) { sb.AppendLine(s); } void SetGround(UnityEngine.Color32 green) { // Island01 UV 배치: 위 절반 = 모래/흙, 아래 절반(v<0.5) = 잔디 var px = new UnityEngine.Color32[64]; for (int y = 0; y < 8; y++) for (int x = 0; x < 8; x++) px[y * 8 + x] = y < 4 ? green : (x < 4 ? sand : dirt); groundTex.SetPixels32(px); groundTex.Apply(); } void SetDensity(float d) { for (int i = 0; i < rt.scatter.Length; i++) { var s = rt.scatter[i]; if (s == null) continue; if (s.label.Contains("풀") || s.label.Contains("꽃")) s.density = d; } } void SetFlowerProb(float p) { for (int i = 0; i < rt.scatter.Length; i++) if (rt.scatter[i] != null && rt.scatter[i].label.Contains("꽃")) rt.scatter[i].probability = p; } void SetGrassScale(float v) { for (int i = 0; i < rt.scatter.Length; i++) if (rt.scatter[i] != null && (rt.scatter[i].label.Contains("풀") || rt.scatter[i].label.Contains("꽃"))) rt.scatter[i].scale = v; } /// 표준 바닥 카메라 — 데모와 같은 화각(직교 · euler 30,60,0). public static UnityEngine.Camera GroundCam(UnityEngine.Vector3 spot, float size) { var go = UnityEngine.GameObject.Find("~WL816fGroundCam"); if (go == null) { go = new UnityEngine.GameObject("~WL816fGroundCam"); go.hideFlags = UnityEngine.HideFlags.DontSave; } var c = go.GetComponent(); if (c == null) c = go.AddComponent(); c.orthographic = true; c.orthographicSize = size; c.nearClipPlane = 0.3f; c.farClipPlane = 500f; c.clearFlags = UnityEngine.CameraClearFlags.SolidColor; c.backgroundColor = new UnityEngine.Color(0.192f, 0.302f, 0.475f, 1f); c.cullingMask = ~0; c.depth = -100f; var rot = UnityEngine.Quaternion.Euler(30f, 60f, 0f); go.transform.rotation = rot; go.transform.position = spot - rot * UnityEngine.Vector3.forward * 60f; if (go.GetComponent() == null) go.AddComponent(); return c; } public static void Shoot(UnityEngine.Camera cam, string path) { if (cam == null) return; System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); var rt2 = new UnityEngine.RenderTexture(W, H, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); rt2.antiAliasing = 1; rt2.Create(); var prevT = cam.targetTexture; var prevA = UnityEngine.RenderTexture.active; cam.targetTexture = rt2; cam.Render(); UnityEngine.RenderTexture.active = rt2; var tex = new UnityEngine.Texture2D(W, H, UnityEngine.TextureFormat.RGB24, false); tex.ReadPixels(new UnityEngine.Rect(0, 0, W, H), 0, 0); tex.Apply(); System.IO.File.WriteAllBytes(path, UnityEngine.ImageConversion.EncodeToPNG(tex)); UnityEngine.RenderTexture.active = prevA; cam.targetTexture = prevT; UnityEngine.Object.DestroyImmediate(tex); rt2.Release(); UnityEngine.Object.DestroyImmediate(rt2); } static UnityEngine.Color32 Hex(string h) { UnityEngine.Color c; UnityEngine.ColorUtility.TryParseHtmlString(h.StartsWith("#") ? h : "#" + h, out c); return (UnityEngine.Color32)c; } static UnityEngine.Color HexC(string h) { UnityEngine.Color c; UnityEngine.ColorUtility.TryParseHtmlString(h.StartsWith("#") ? h : "#" + h, out c); return c; } /// 풀이 가장 넓게 깔린 타일의 중심을 고른다(소품·밭이 적은 곳). UnityEngine.Vector3 PickCleanSpot() { var islands = UnityEngine.Object.FindObjectsByType( UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); UnityEngine.Vector3 best = UnityEngine.Vector3.zero; int bestScore = int.MaxValue; for (int i = 0; i < islands.Length; i++) { var isl = islands[i]; if (isl == null || !isl.IsUnlocked || !isl.gameObject.activeInHierarchy) continue; int score = isl.GetComponentsInChildren(false).Length; if (score < bestScore) { bestScore = score; best = isl.transform.position; } } return best; } }