From 0b649230d8f876be9c8fbf96c20e2441769c7f0a Mon Sep 17 00:00:00 2001 From: swrring Date: Sun, 13 Sep 2026 14:19:39 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[WL-816f]=20=EB=B0=94=EB=8B=A5=EC=9D=84=20?= =?UTF-8?q?=EB=8D=B0=EB=AA=A8=EC=99=80=20=EC=9D=BC=EC=B9=98=20=E2=80=94=20?= =?UTF-8?q?=EC=9E=94=EB=94=94=20=EB=8B=A8=EC=83=89=20+=20=ED=92=80=20?= =?UTF-8?q?=EB=8C=80=EB=B9=84=E2=86=93=20+=20=EB=B0=80=EB=8F=84=202.5?= =?UTF-8?q?=E2=86=921.8=20+=20=EB=B0=AD=20=EC=B9=B8=20=ED=91=9C=EC=8B=9C?= =?UTF-8?q?=20=ED=86=A4=E2=86=93=20(#816)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PD 「내가 실행할 때는 전혀 풀밭이 아니야 · 바닥이 완전히 같은 느낌이 나도록」 ■ ① PD 실행 경로 실측 = 훅은 실제로 돈다(버그 아님) InGame 활성 + Level01 Additive 재현: 머티리얼 1,575 렌더러 교체 · 풀 생성 · ReferenceLook on · 콘솔 error 0. 「안 나온다」가 아니라 「데모와 전혀 다르게 보인다」였다. 다만 이 경로에서만 생기는 실측 차이 2가지를 찾아 보고에 적었다 (세이브 파일이 활성 씬 이름으로 갈린다 · RenderSettings 가 InGame 소유). ■ ② 바닥 3요소 (같은 화각·같은 거리 캡처로 판정) 체크무늬 : 잔디 타일 텍스처는 단색(#A0E4A2) — 격자 0. 밭(Soil)의 칸 표시는 게임 기능이라 남기고 **색만** 데모 톤으로(soilTint·soilCheckerContrast). 바닥 색 : #AFEDAE → #A0E4A2 (화면색 평균 데모 #94BE81 ↔ 우리 #9AC37E) 풀 색 : 새 복사본 Farm_Grass_Demo.mat (_DiffuseColor #A6E39F · _ShadowDiffuseColor #95CC8F) → 풀이 바닥과 같은 색으로 깔려 「카펫」이 사라진다(에지% 8.09 → 1.96 · 데모 1.79) 밀도 : 2.5 → 1.8 (6.25 → 3.24 개/㎡) 🔴 Assets/FarmingIsland/** 0줄 · Assets/Script/** 0줄 · 3DPixelArtEnvironment 0줄 · ProjectSettings 0줄 · 전역 URP/품질 0. 되돌리기 = enabled_ = 0. Co-Authored-By: Claude Opus 5 --- AgentScripts/WL816f_Build.cs | 58 +++++ AgentScripts/WL816f_Cmp.cs | 128 ++++++++++ AgentScripts/WL816f_Ctl.cs | 87 +++++++ AgentScripts/WL816f_Ground.cs | 116 +++++++++ AgentScripts/WL816f_PLAN.txt | 13 + AgentScripts/WL816f_Pd.cs | 233 ++++++++++++++++++ AgentScripts/WL816f_Probe.cs | 113 +++++++++ AgentScripts/WL816f_Sweep.cs | 227 +++++++++++++++++ AgentScripts/WL816f_Verify.cs | 155 ++++++++++++ .../Look/Farm/Materials/Farm_Grass_Demo.mat | 159 ++++++++++++ .../Farm/Materials/Farm_Grass_Demo.mat.meta | 8 + .../Resources/WL/WLIslandLookSettings.asset | 9 +- .../Look/Farm/Textures/WL_IslandTop_Demo.png | Bin 101 -> 93 bytes Assets/WL/Look/Farm/WLIslandLook.cs | 69 ++++++ Assets/WL/Look/Farm/WLIslandLookSettings.cs | 13 + 15 files changed, 1385 insertions(+), 3 deletions(-) create mode 100644 AgentScripts/WL816f_Build.cs create mode 100644 AgentScripts/WL816f_Cmp.cs create mode 100644 AgentScripts/WL816f_Ctl.cs create mode 100644 AgentScripts/WL816f_Ground.cs create mode 100644 AgentScripts/WL816f_PLAN.txt create mode 100644 AgentScripts/WL816f_Pd.cs create mode 100644 AgentScripts/WL816f_Probe.cs create mode 100644 AgentScripts/WL816f_Sweep.cs create mode 100644 AgentScripts/WL816f_Verify.cs create mode 100644 Assets/WL/Look/Farm/Materials/Farm_Grass_Demo.mat create mode 100644 Assets/WL/Look/Farm/Materials/Farm_Grass_Demo.mat.meta diff --git a/AgentScripts/WL816f_Build.cs b/AgentScripts/WL816f_Build.cs new file mode 100644 index 000000000..ed46a892b --- /dev/null +++ b/AgentScripts/WL816f_Build.cs @@ -0,0 +1,58 @@ +// WL-816f — 채택값을 에셋에 굽는다(에디트 모드). +// ① 풀 머티리얼 복사본 `Farm_Grass_Demo.mat`(데모 `Instanced_Grass` 0줄 · 복사본만) +// ② SO: 풀 머티리얼 교체 · 밀도 2.5 → 1.8 +public static class WL816f_Build +{ + const string SoPath = "Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset"; + const string MatPath = "Assets/WL/Look/Farm/Materials/Farm_Grass_Demo.mat"; + + public static void Run() + { + var sb = new System.Text.StringBuilder(); + var cfg = UnityEditor.AssetDatabase.LoadAssetAtPath(SoPath); + if (cfg == null) { UnityEngine.Debug.Log("SO 없음"); return; } + + // ① 풀 머티리얼 복사본 + UnityEngine.Material grassSrc = null; + for (int i = 0; i < cfg.scatter.Length; i++) + if (cfg.scatter[i] != null && cfg.scatter[i].label.Contains("풀")) grassSrc = cfg.scatter[i].material; + if (grassSrc == null) { UnityEngine.Debug.Log("풀 머티리얼 없음"); return; } + + var mat = UnityEditor.AssetDatabase.LoadAssetAtPath(MatPath); + if (mat == null) + { + mat = new UnityEngine.Material(grassSrc); + mat.name = "Farm_Grass_Demo"; + UnityEditor.AssetDatabase.CreateAsset(mat, MatPath); + sb.AppendLine("새 머티리얼 " + MatPath + " (원본 " + grassSrc.name + " 복사)"); + } + else sb.AppendLine("기존 머티리얼 갱신 " + MatPath); + + // 816f 실측 채택값 — 풀 화면색 ≈ 바닥 화면색(데모와 같은 「안 튀는 풀」) + mat.SetColor("_DiffuseColor", Hex("#A6E39F")); + mat.SetColor("_ShadowDiffuseColor", Hex("#95CC8F")); + UnityEditor.EditorUtility.SetDirty(mat); + + // ② SO + for (int i = 0; i < cfg.scatter.Length; i++) + { + var s = cfg.scatter[i]; + if (s == null) continue; + if (s.label.Contains("풀")) { s.material = mat; s.density = 1.8f; sb.AppendLine("풀: mat→Farm_Grass_Demo · density 2.5→1.8"); } + else if (s.label.Contains("꽃")) { s.density = 1.8f; sb.AppendLine("꽃: density 2.5→1.8 (풀과 같은 층)"); } + } + UnityEditor.EditorUtility.SetDirty(cfg); + UnityEditor.AssetDatabase.SaveAssets(); + UnityEditor.AssetDatabase.Refresh(); + + sb.AppendLine("soilToneEnabled=" + cfg.soilToneEnabled + " soilTint=" + cfg.soilTint + " soilCheckerContrast=" + cfg.soilCheckerContrast); + sb.AppendLine("islandTopMaterial=" + (cfg.islandTopMaterial ? cfg.islandTopMaterial.name : "없음") + + " _BaseMap=" + (cfg.islandTopMaterial && cfg.islandTopMaterial.GetTexture("_BaseMap") + ? cfg.islandTopMaterial.GetTexture("_BaseMap").name : "없음")); + System.IO.File.WriteAllText("AgentScripts/WL816f_BUILD.txt", sb.ToString()); + UnityEngine.Debug.Log(sb.ToString()); + } + + static UnityEngine.Color Hex(string h) + { UnityEngine.Color c; UnityEngine.ColorUtility.TryParseHtmlString(h, out c); return c; } +} diff --git a/AgentScripts/WL816f_Cmp.cs b/AgentScripts/WL816f_Cmp.cs new file mode 100644 index 000000000..f8bb26c37 --- /dev/null +++ b/AgentScripts/WL816f_Cmp.cs @@ -0,0 +1,128 @@ +// WL-816f §2 — 「같은 화각·같은 거리」 바닥 비교용 렌더. +// 표준 바닥 카메라 = 직교 size 5 · euler(30,60,0) · 1080×1920 (814r/816a/816e 화각과 같은 방향) +// 데모는 에디트 모드에서 인스턴싱을 직접 발행해 그린다(데모 씬 Play 금지 · §9). +public static class WL816f_Cmp +{ + public const string Dir = "Screenshots_WL/WL816f/"; + public const int W = 1080, H = 1920; + + // ── 데모 바닥 ────────────────────────────────────────────────────── + public static void Demo() + { + UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/3DPixelArtEnvironment/Demo/Demo.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); + + var sb = new System.Text.StringBuilder(); + // 평평한 풀밭 지점을 찾는다(경사가 작고 1층 스플랫이 지배적인 곳) + var terr = UnityEngine.Object.FindFirstObjectByType(); + UnityEngine.Vector3 spot = new UnityEngine.Vector3(130f, 0f, 90f); + if (terr != null) + { + var td = terr.terrainData; + float best = 999f; + for (float x = 40f; x < 180f; x += 10f) + for (float z = 40f; z < 180f; z += 10f) + { + float u = x / td.size.x, v = z / td.size.z; + var nrm = td.GetInterpolatedNormal(u, v); + float slope = 1f - nrm.y; + var c = td.alphamapTextures[0].GetPixelBilinear(u, v); + if (c.r < 0.9f) continue; // 1층(풀)이 지배적인 곳만 + if (slope < best) { best = slope; spot = new UnityEngine.Vector3(x, td.GetInterpolatedHeight(u, v) + terr.transform.position.y, z); } + } + sb.AppendLine("데모 바닥 지점 " + spot.ToString("F2") + " slope=" + best.ToString("F4")); + } + + var cam = GroundCam(spot, 6f); + int n = WL816f_Inst.Publish(sb); + Shoot(cam, Dir + "x_demo_ground.png", sb); + // 넓은 화각(ortho 10)도 하나 + cam = GroundCam(spot, 10f); + WL816f_Inst.Publish(sb); + Shoot(cam, Dir + "x_demo_wide.png", sb); + sb.AppendLine("데모 인스턴싱 구성 " + n); + System.IO.File.WriteAllText("AgentScripts/WL816f_DEMO.txt", sb.ToString()); + UnityEngine.Debug.Log(sb.ToString()); + } + + /// 표준 바닥 카메라 — 지점 spot 을 데모 화각으로 본다. + 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, System.Text.StringBuilder sb) + { + if (cam == null) { if (sb != null) sb.AppendLine("카메라 없음 " + path); return; } + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + var rt = new UnityEngine.RenderTexture(W, H, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); + rt.antiAliasing = 1; rt.Create(); + var prevT = cam.targetTexture; var prevA = UnityEngine.RenderTexture.active; + cam.targetTexture = rt; cam.Render(); + 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(); + System.IO.File.WriteAllBytes(path, UnityEngine.ImageConversion.EncodeToPNG(tex)); + UnityEngine.RenderTexture.active = prevA; cam.targetTexture = prevT; + UnityEngine.Object.DestroyImmediate(tex); + rt.Release(); UnityEngine.Object.DestroyImmediate(rt); + WL816f_Inst.Free(); + if (sb != null) sb.AppendLine("캡처 " + path); + } +} + +/// 에디트 모드에서 InstancesBehaviour 가 그릴 것을 이번 프레임에 직접 발행한다(Play 없이). +public static class WL816f_Inst +{ + static readonly System.Collections.Generic.List s_pending + = new System.Collections.Generic.List(); + + public static int Publish(System.Text.StringBuilder sb) + { + int n = 0; long inst = 0; + var bs = UnityEngine.Object.FindObjectsByType( + UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); + for (int i = 0; i < bs.Length; i++) + { + var b = bs[i]; + System.Collections.Generic.Dictionary> data = null; + try { data = b.GetInstanceData(); } catch { continue; } + if (data == null) continue; + var bounds = b.CalculateInstancesBounds(); + var l2w = b.transform.localToWorldMatrix; l2w.m03 = 0; l2w.m13 = 0; l2w.m23 = 0; + foreach (var kv in data) + { + if (kv.Value == null || kv.Value.Count == 0) continue; + var cfg = new Environment.Instancing.InstancingConfiguration(kv.Key, kv.Value, "_InstanceData"); + cfg.MaterialPropertyBlock.SetMatrix("_LocalToWorld", l2w); + UnityEngine.Graphics.DrawMeshInstancedIndirect(cfg.Mesh, 0, cfg.Material, bounds, cfg.CommandBuffer, 0, + cfg.MaterialPropertyBlock, UnityEngine.Rendering.ShadowCastingMode.Off, false, 0); + n++; inst += kv.Value.Count; + s_pending.Add(cfg); + } + } + if (sb != null) sb.AppendLine(" 인스턴스 " + inst + " (구성 " + n + ")"); + return n; + } + + public static void Free() + { + for (int i = 0; i < s_pending.Count; i++) s_pending[i].FreeMemory(); + s_pending.Clear(); + } +} diff --git a/AgentScripts/WL816f_Ctl.cs b/AgentScripts/WL816f_Ctl.cs new file mode 100644 index 000000000..66f8232b3 --- /dev/null +++ b/AgentScripts/WL816f_Ctl.cs @@ -0,0 +1,87 @@ +// WL-816f — SO 토글(되돌리기 검증용) +public static class WL816f_Ctl +{ + const string kSo = "Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset"; + public static void Off() { Set(0); } + public static void On() { Set(1); } + static void Set(int en) + { + var so = UnityEditor.AssetDatabase.LoadAssetAtPath(kSo); + so.enabled_ = en; + UnityEditor.EditorUtility.SetDirty(so); + UnityEditor.AssetDatabase.SaveAssets(); + WL.Look.Farm.WLIslandLookSettings.Invalidate(); + UnityEngine.Debug.Log("[816f Ctl] enabled_=" + en); + } + + /// Play 중: 되돌리기 상태 실측 + 캡처 + public static void Rollback() + { + var go = UnityEngine.GameObject.Find("~WL816fRb"); + if (go != null) UnityEngine.Object.DestroyImmediate(go); + go = new UnityEngine.GameObject("~WL816fRb"); + go.AddComponent(); + } +} + +public class WL816f_RbRunner : UnityEngine.MonoBehaviour +{ + void Start() { StartCoroutine(Co()); } + System.Collections.IEnumerator Co() + { + 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 < 6; i++) yield return new UnityEngine.WaitForSeconds(1f); + + var sb = new System.Text.StringBuilder(); + var cfg = WL.Look.Farm.WLIslandLookSettings.Instance; + sb.AppendLine("enabled_=" + (cfg == null ? -1 : cfg.enabled_)); + sb.AppendLine("머티리얼 교체 " + WL.Look.Farm.WLIslandLook.SwappedRenderers + " · 풀 " + WL.Look.Farm.WLIslandGrass.Instances + + " · refLook " + WL.Look.Arena.WLReferenceLook.IsApplied + + " · Soil톤 farm " + WL.Look.Farm.WLIslandLook.SoilFarmsToned); + sb.AppendLine("ambient " + UnityEngine.RenderSettings.ambientMode + " " + UnityEngine.RenderSettings.ambientLight + + " skybox=" + (UnityEngine.RenderSettings.skybox ? UnityEngine.RenderSettings.skybox.name : "없음")); + var soils = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); + string c0 = "-", c1 = "-"; + for (int i = 0; i < soils.Length && i < 2; i++) + { + var r = soils[i].GetComponentInChildren(true); + if (r == null) continue; + if (i == 0) c0 = UnityEngine.ColorUtility.ToHtmlStringRGB(r.material.color); + else c1 = UnityEngine.ColorUtility.ToHtmlStringRGB(r.material.color); + } + sb.AppendLine("Soil 색 [0]=" + c0 + " [1]=" + c1); + var rends = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); + var shaders = new System.Collections.Generic.Dictionary(); + for (int i = 0; i < rends.Length; i++) + { + if (rends[i].gameObject.scene.name != "Level01") continue; + var m = rends[i].sharedMaterial; if (m == null) continue; + var k = m.shader.name; + shaders[k] = shaders.ContainsKey(k) ? shaders[k] + 1 : 1; + } + foreach (var kv in shaders) sb.AppendLine("shader " + kv.Key + " ×" + kv.Value); + + var cams = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); + UnityEngine.Camera top = null; + for (int i = 0; i < cams.Length; i++) { var c = cams[i]; if (!c.enabled || c.targetTexture != null) continue; if (top == null || c.depth > top.depth) top = c; } + if (top != null) + { + System.IO.Directory.CreateDirectory("Screenshots_WL/WL816f"); + var rt = new UnityEngine.RenderTexture(1080, 1920, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); + rt.Create(); var pt = top.targetTexture; top.targetTexture = rt; top.Render(); + UnityEngine.RenderTexture.active = rt; + var tex = new UnityEngine.Texture2D(1080, 1920, UnityEngine.TextureFormat.RGB24, false); + tex.ReadPixels(new UnityEngine.Rect(0, 0, 1080, 1920), 0, 0); tex.Apply(); + System.IO.File.WriteAllBytes("Screenshots_WL/WL816f/z_rollback.png", UnityEngine.ImageConversion.EncodeToPNG(tex)); + UnityEngine.RenderTexture.active = null; top.targetTexture = pt; + UnityEngine.Object.DestroyImmediate(tex); rt.Release(); UnityEngine.Object.DestroyImmediate(rt); + sb.AppendLine("캡처 Screenshots_WL/WL816f/z_rollback.png"); + } + System.IO.File.WriteAllText("AgentScripts/WL816f_ROLLBACK.txt", sb.ToString()); + UnityEngine.Debug.Log("[816f rollback]\n" + sb.ToString()); + } +} diff --git a/AgentScripts/WL816f_Ground.cs b/AgentScripts/WL816f_Ground.cs new file mode 100644 index 000000000..03a35de13 --- /dev/null +++ b/AgentScripts/WL816f_Ground.cs @@ -0,0 +1,116 @@ +// WL-816f §2 — 데모 바닥 vs 우리 바닥 실측(에디트 모드 · 데모 씬 Play 안 함) +// ① 풀 메시의 월드 크기 ② 격자 간격 ③ 풀 머티리얼 색 ④ 바닥 머티리얼 색 +public static class WL816f_Ground +{ + public static void Run() + { + var sb = new System.Text.StringBuilder(); + + // ── 데모 ──────────────────────────────────────────────────── + UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/3DPixelArtEnvironment/Demo/Demo.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); + sb.AppendLine("=== 데모 (Demo.unity) ==="); + var tibs = UnityEngine.Object.FindObjectsByType( + UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.None); + foreach (var t in tibs) + { + sb.AppendLine("TIB " + t.name + " lossyScale=" + t.transform.lossyScale.ToString("F3") + + " posVar=" + t.PositionVariance + " scaleVar=" + t.ScaleVariance); + var terr = t.GetComponent(); + if (terr != null) sb.AppendLine(" terrain size=" + terr.terrainData.size.ToString("F1") + + " alphamapTex=" + terr.terrainData.alphamapTextureCount + + " mat=" + (terr.materialTemplate ? terr.materialTemplate.name + " (" + terr.materialTemplate.shader.name + ")" : "없음")); + Layer(sb, "1층", t.FirstLayer, t.transform.lossyScale); + Layer(sb, "2층", t.SecondLayer, t.transform.lossyScale); + Layer(sb, "3층", t.ThirdLayer, t.transform.lossyScale); + Layer(sb, "4층", t.FourthLayer, t.transform.lossyScale); + if (terr != null && terr.materialTemplate != null) MatDump(sb, " 지형mat ", terr.materialTemplate); + } + + // 데모 지형 스플랫 — 1층(풀)이 차지하는 면적 비율 + foreach (var t in tibs) + { + var terr = t.GetComponent(); + if (terr == null || terr.terrainData.alphamapTextureCount == 0) continue; + var tex = terr.terrainData.alphamapTextures[0]; + var px = tex.GetPixels(); + int[] cnt = new int[4]; + for (int i = 0; i < px.Length; i++) + { + float[] v = { px[i].r, px[i].g, px[i].b, px[i].a }; + int bi = 0; for (int k = 1; k < 4; k++) if (v[k] > v[bi]) bi = k; + cnt[bi]++; + } + sb.AppendLine(" 스플랫 지배비율(" + tex.width + "x" + tex.height + "): 1층 " + (100f * cnt[0] / px.Length).ToString("F1") + + "% · 2층 " + (100f * cnt[1] / px.Length).ToString("F1") + "% · 3층 " + (100f * cnt[2] / px.Length).ToString("F1") + + "% · 4층 " + (100f * cnt[3] / px.Length).ToString("F1") + "%"); + } + + // ── 우리 ──────────────────────────────────────────────────── + sb.AppendLine(); + sb.AppendLine("=== 우리 (WLIslandLookSettings) ==="); + var cfg = UnityEditor.AssetDatabase.LoadAssetAtPath( + "Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset"); + sb.AppendLine("posVar=" + cfg.positionVariance + " scaleVar=" + cfg.scaleVariance + " edgeInset=" + cfg.edgeInset); + for (int i = 0; i < cfg.scatter.Length; i++) + { + var s = cfg.scatter[i]; + var b = s.mesh != null ? s.mesh.bounds.size : UnityEngine.Vector3.zero; + sb.AppendLine(" [" + s.label + "] en=" + s.enabled_ + " mesh=" + (s.mesh ? s.mesh.name : "없음") + + " bounds=" + b.ToString("F3") + " scale=" + s.scale + + " → 월드 " + (b * s.scale).ToString("F3") + + " · prob=" + s.probability + " density=" + s.density + "(=" + (s.density * s.density).ToString("F2") + "개/㎡ · 격자 " + + (1f / s.density).ToString("F3") + "m)" + + " mat=" + (s.material ? s.material.name : "없음")); + if (s.material != null) MatDump(sb, " ", s.material); + } + sb.AppendLine("islandTopMaterial = " + (cfg.islandTopMaterial ? cfg.islandTopMaterial.name : "없음")); + if (cfg.islandTopMaterial != null) MatDump(sb, " ", cfg.islandTopMaterial); + for (int i = 0; i < cfg.materialRemap.Length; i++) + { + var e = cfg.materialRemap[i]; + sb.AppendLine(" remap en=" + e.enabled_ + " " + (e.from ? e.from.name : "-") + " → " + (e.to ? e.to.name : "-") + + (e.to != null && e.to.HasProperty("_BaseMap") && e.to.GetTexture("_BaseMap") != null + ? " 🔴텍스처 " + e.to.GetTexture("_BaseMap").name : " (텍스처 없음)")); + } + + System.IO.File.WriteAllText("AgentScripts/WL816f_GROUND.txt", sb.ToString()); + UnityEngine.Debug.Log(sb.ToString()); + } + + static void Layer(System.Text.StringBuilder sb, string name, + Environment.Instancing.TerrainInstancesBehaviour.TerrainInstancingInput inp, UnityEngine.Vector3 lossy) + { + if (inp == null || inp.Settings == null || inp.Settings.Length == 0) { sb.AppendLine(" " + name + " (없음)"); return; } + sb.AppendLine(" " + name + " Density=" + inp.Density + " (=" + (inp.Density * inp.Density).ToString("F2") + + "개/㎡ · 격자 " + (1f / inp.Density).ToString("F3") + "m)"); + foreach (var s in inp.Settings) + { + var b = s.Mesh != null ? s.Mesh.bounds.size : UnityEngine.Vector3.zero; + var w = new UnityEngine.Vector3(b.x * s.Scale * lossy.x, b.y * s.Scale * lossy.y, b.z * s.Scale * lossy.z); + sb.AppendLine(" mesh=" + (s.Mesh ? s.Mesh.name + "(tri " + (s.Mesh.triangles.Length / 3) + ")" : "없음") + + " bounds=" + b.ToString("F3") + " Scale=" + s.Scale + " → 월드 " + w.ToString("F3") + + " prob=" + s.Probability + " nOff=" + s.NormalOffset + + " mat=" + (s.Material ? s.Material.name : "없음")); + if (s.Material != null) MatDump(sb, " ", s.Material); + } + } + + static void MatDump(System.Text.StringBuilder sb, string pad, UnityEngine.Material m) + { + string[] cols = { "_DiffuseColor", "_ShadowDiffuseColor", "_BaseColor", "_Color", "_TopColor", "_BottomColor", "_TipColor" }; + string[] flts = { "_Shades", "_Brightness", "_MinimumDarkness", "_AmbientStrength", "_Cull", "_WindStrength", "_WindDensity" }; + var s = pad + "shader=" + m.shader.name; + foreach (var c in cols) if (m.HasProperty(c)) s += " " + c + "=" + Hex(m.GetColor(c)); + foreach (var f in flts) if (m.HasProperty(f)) s += " " + f + "=" + m.GetFloat(f).ToString("F3"); + if (m.HasProperty("_BaseMap")) s += " _BaseMap=" + (m.GetTexture("_BaseMap") ? m.GetTexture("_BaseMap").name : "없음"); + if (m.HasProperty("_ShadowBaseMap")) s += " _ShadowBaseMap=" + (m.GetTexture("_ShadowBaseMap") ? m.GetTexture("_ShadowBaseMap").name : "없음"); + sb.AppendLine(s); + } + + static string Hex(UnityEngine.Color c) + { + return "(" + c.r.ToString("F3") + "," + c.g.ToString("F3") + "," + c.b.ToString("F3") + ")#" + + UnityEngine.ColorUtility.ToHtmlStringRGB(c); + } +} diff --git a/AgentScripts/WL816f_PLAN.txt b/AgentScripts/WL816f_PLAN.txt new file mode 100644 index 000000000..f9deb8dcb --- /dev/null +++ b/AgentScripts/WL816f_PLAN.txt @@ -0,0 +1,13 @@ +# WL-816f 바닥 단계 비교 — 이름 | 바닥초록HEX | 풀Diffuse | 풀Shadow | 밀도 | 꽃확률 | 풀Scale ('-' = 그대로) +# 바닥색 채택 = A0E4A2 (데모 평균 #94BE81 에 화면색을 맞춘 역산값) +# ── 2단계: 풀 색 대비 3단 ── +c1 | A0E4A2 | C6F398 | 40533C | 2.5 | - | - +c2 | A0E4A2 | ABDE93 | 9AC884 | 2.5 | - | - +c3 | A0E4A2 | A6E39F | 95CC8F | 2.5 | - | - +# ── 3단계: 밀도 3단 (대비 = c2) ── +e1 | A0E4A2 | ABDE93 | 9AC884 | 2.50 | - | - +e2 | A0E4A2 | ABDE93 | 9AC884 | 1.80 | - | - +e3 | A0E4A2 | ABDE93 | 9AC884 | 1.25 | - | - +# ── 후보 조합 ── +f1 | A0E4A2 | A6E39F | 95CC8F | 1.80 | - | - +f2 | A0E4A2 | ABDE93 | 9AC884 | 2.20 | - | - diff --git a/AgentScripts/WL816f_Pd.cs b/AgentScripts/WL816f_Pd.cs new file mode 100644 index 000000000..c4db94255 --- /dev/null +++ b/AgentScripts/WL816f_Pd.cs @@ -0,0 +1,233 @@ +// WL-816f — ① PD 실행 경로 재현 (로그인 없이): InGame 씬 Play → Level01 을 Additive 로 붙인다. +// = InGameInfo.Load_Map(900) → SceneInfo.Load_AddScene 이 하는 것과 같은 구조(활성 씬 = InGame). +// 배치모드 헤드리스에서 동작. 캡처는 오프스크린 렌더. +public static class WL816f_Pd +{ + public const string Dir = "Screenshots_WL/WL816f/"; + public const int W = 1080, H = 1920; + public static string Report = ""; + + // ── 에디트 모드: 씬 준비 ─────────────────────────────────────────── + public static void Open() + { + UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/Scenes/InGame.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); + UnityEngine.Debug.Log("[816f] InGame 열림 dirty=" + UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene().isDirty); + } + + // ── Play 중: 러너를 띄운다 ───────────────────────────────────────── + public static void Start() + { + var go = UnityEngine.GameObject.Find("~WL816fPd"); + if (go != null) UnityEngine.Object.DestroyImmediate(go); + go = new UnityEngine.GameObject("~WL816fPd"); + go.AddComponent(); + UnityEngine.Debug.Log("[816f] 러너 시작"); + } + + public static void Fetch() + { + var txt = WL816f_PdRunner.Log; + System.IO.File.WriteAllText("AgentScripts/WL816f_PD.txt", txt); + UnityEngine.Debug.Log("[816f]\n" + txt); + } +} + +public class WL816f_PdRunner : UnityEngine.MonoBehaviour +{ + public static string Log = "(아직)"; + static System.Text.StringBuilder sb; + + void Start() { StartCoroutine(Co()); } + + System.Collections.IEnumerator Co() + { + sb = new System.Text.StringBuilder(); + L("=== PD 경로 재현 (InGame 활성 + Level01 Additive) ==="); + L("t0 활성씬=" + UnityEngine.SceneManagement.SceneManager.GetActiveScene().name + + " sceneCount=" + UnityEngine.SceneManagement.SceneManager.sceneCount); + + // 훅 설치 상태 + var cfg = WL.Look.Farm.WLIslandLookSettings.Instance; + L("SO enabled_=" + (cfg == null ? -1 : cfg.enabled_) + " applyLook=" + (cfg == null ? -1 : cfg.applyLook) + + " grass=" + (cfg == null ? -1 : cfg.grassEnabled)); + + // InGameInfo 로 진짜 경로를 태워 본다 + bool viaGame = false; + try + { + if (InGameInfo.Ins != null) + { + L("InGameInfo.Ins 있음 → Load_Map(900) 시도"); + InGameInfo.Ins.Load_Map(900); + viaGame = true; + } + } + catch (System.Exception e) { L("Load_Map 예외: " + e.GetType().Name + " " + e.Message); } + + if (!viaGame) + { + L("→ 로그인 없이는 Load_Map 이 못 돈다. SceneInfo.Load_AddScene 과 **같은 호출**로 대체:"); + L(" SceneManager.LoadSceneAsync(\"Level01\", Additive)"); + var op = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("Level01", UnityEngine.SceneManagement.LoadSceneMode.Additive); + while (op != null && !op.isDone) yield return null; + } + + for (int i = 0; i < 6; i++) yield return new UnityEngine.WaitForSeconds(1f); + + L(""); + L("t+6s 활성씬=" + UnityEngine.SceneManagement.SceneManager.GetActiveScene().name + + " sceneCount=" + UnityEngine.SceneManagement.SceneManager.sceneCount); + for (int i = 0; i < UnityEngine.SceneManagement.SceneManager.sceneCount; i++) + { + var s = UnityEngine.SceneManagement.SceneManager.GetSceneAt(i); + L(" scene[" + i + "] " + s.name + " loaded=" + s.isLoaded + " roots=" + (s.isLoaded ? s.rootCount : -1)); + } + + // ── 훅이 돌았나 ─────────────────────────────────────────────── + L(""); + L("IslandLook : 렌더러 " + WL.Look.Farm.WLIslandLook.SwappedRenderers + " 슬롯 " + WL.Look.Farm.WLIslandLook.SwappedSlots + + " look=" + WL.Look.Farm.WLIslandLook.LookApplied + " light=" + WL.Look.Farm.WLIslandLook.LightingApplied + + " refLook=" + WL.Look.Farm.WLIslandLook.ReferenceLookApplied + " grassSpawned=" + WL.Look.Farm.WLIslandLook.GrassSpawned); + L("Grass : " + WL.Look.Farm.WLIslandGrass.LastLog); + var g = UnityEngine.Object.FindFirstObjectByType(UnityEngine.FindObjectsInactive.Include); + L("GrassObj : " + (g == null ? "없음" : (g.name + " active=" + g.gameObject.activeInHierarchy + "/" + g.enabled + + " scene=" + g.gameObject.scene.name + " layer=" + g.gameObject.layer))); + L("RefLook : applied=" + WL.Look.Arena.WLReferenceLook.IsApplied + " outlined=" + WL.Look.Arena.WLReferenceLook.OutlinedRenderers); + L("ambient : " + UnityEngine.RenderSettings.ambientMode + " " + UnityEngine.RenderSettings.ambientLight + + " skybox=" + (UnityEngine.RenderSettings.skybox ? UnityEngine.RenderSettings.skybox.name : "없음") + + " fog=" + UnityEngine.RenderSettings.fog); + + // ── 카메라 — 누가 실제로 화면을 그리나 ──────────────────────── + L(""); + var cams = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.None); + UnityEngine.Camera top = null; + for (int i = 0; i < cams.Length; i++) + { + var c = cams[i]; + bool live = c.gameObject.activeInHierarchy && c.enabled && c.targetTexture == null; + L("CAM " + c.name + " scene=" + c.gameObject.scene.name + " live=" + live + + " depth=" + c.depth + " mask=0x" + c.cullingMask.ToString("X") + " clear=" + c.clearFlags + + " pos=" + c.transform.position.ToString("F1")); + if (live && (top == null || c.depth > top.depth)) top = c; + } + L("→ 화면에 보이는 카메라 = " + (top == null ? "없음" : top.name + " (scene " + top.gameObject.scene.name + ")")); + if (top != null) + { + L(" 그 카메라의 컬링마스크에 들어가는 레이어:"); + string inc = "", exc = ""; + for (int i = 0; i < 32; i++) + { + string n = UnityEngine.LayerMask.LayerToName(i); + if (string.IsNullOrEmpty(n)) continue; + if ((top.cullingMask & (1 << i)) != 0) inc += i + ":" + n + " "; else exc += i + ":" + n + " "; + } + L(" 포함 = " + inc); + L(" 🔴제외 = " + exc); + } + + // ── 섬 렌더러가 어느 레이어에 있나 ──────────────────────────── + L(""); + var counts = new System.Collections.Generic.Dictionary(); + var rends = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); + int nIsland = 0; + for (int i = 0; i < rends.Length; i++) + { + if (rends[i].gameObject.scene.name != "Level01") continue; + nIsland++; + int l = rends[i].gameObject.layer; + counts[l] = counts.ContainsKey(l) ? counts[l] + 1 : 1; + } + L("Level01 렌더러 " + nIsland + " 개의 레이어 분포:"); + foreach (var kv in counts) + L(" layer " + kv.Key + " (" + UnityEngine.LayerMask.LayerToName(kv.Key) + ") = " + kv.Value + + (top != null ? ((top.cullingMask & (1 << kv.Key)) != 0 ? " [보임]" : " 🔴[안 보임]") : "")); + L("풀이 그려지는 레이어 = " + UnityEngine.LayerMask.NameToLayer("Default") + " (InstancesBehaviour.Update 고정)"); + + L("Soil 톤 : farm " + WL.Look.Farm.WLIslandLook.SoilFarmsToned + " · 타일 " + WL.Look.Farm.WLIslandLook.SoilTilesRepainted); + + // ── 캡처 (보이는 카메라 그대로 + 위에서 + 표준 바닥 카메라) ── + Shot(top, WL816f_Pd.Dir + "a_pd_path.png"); + Shot(TopCam(), WL816f_Pd.Dir + "a_pd_path_top.png"); + Shot(GroundCam(CleanSpot(), 6f), WL816f_Pd.Dir + "x_ours_ground.png"); + Shot(GroundCam(CleanSpot(), 10f), WL816f_Pd.Dir + "x_ours_wide.png"); + + Log = sb.ToString(); + System.IO.File.WriteAllText("AgentScripts/WL816f_PD.txt", Log); + UnityEngine.Debug.Log("[816f 완료]\n" + Log); + } + + static void L(string s) { sb.AppendLine(s); } + + /// 소품이 가장 적은(=풀이 가장 넓은) 섬 타일의 중심. + UnityEngine.Vector3 CleanSpot() + { + var islands = UnityEngine.Object.FindObjectsByType( + UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); + UnityEngine.Vector3 best = UnityEngine.Vector3.zero; int bs = int.MaxValue; + for (int i = 0; i < islands.Length; i++) + { + var isl = islands[i]; + if (isl == null || !isl.IsUnlocked || !isl.gameObject.activeInHierarchy) continue; + int sc = isl.GetComponentsInChildren(false).Length; + if (sc < bs) { bs = sc; best = isl.transform.position; } + } + return best; + } + + /// 표준 바닥 카메라 — 데모와 같은 화각(직교 · 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; + } + + UnityEngine.Camera TopCam() + { + var go = new UnityEngine.GameObject("~WL816fTop"); + var c = go.AddComponent(); + c.orthographic = true; c.orthographicSize = 14f; + c.nearClipPlane = 0.3f; c.farClipPlane = 500f; + c.clearFlags = UnityEngine.CameraClearFlags.Skybox; + c.cullingMask = ~0; + c.depth = -100f; + var rot = UnityEngine.Quaternion.Euler(30f, 60f, 0f); + go.transform.rotation = rot; + go.transform.position = new UnityEngine.Vector3(0f, 1f, 0f) - rot * UnityEngine.Vector3.forward * 60f; + if (go.GetComponent() == null) + go.AddComponent(); + return c; + } + + public static void Shot(UnityEngine.Camera cam, string path) + { + if (cam == null) { L("캡처 실패(카메라 없음) " + path); return; } + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + var rt = new UnityEngine.RenderTexture(WL816f_Pd.W, WL816f_Pd.H, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); + rt.antiAliasing = 1; rt.Create(); + var prevT = cam.targetTexture; var prevA = UnityEngine.RenderTexture.active; + cam.targetTexture = rt; + cam.Render(); + UnityEngine.RenderTexture.active = rt; + var tex = new UnityEngine.Texture2D(WL816f_Pd.W, WL816f_Pd.H, UnityEngine.TextureFormat.RGB24, false); + tex.ReadPixels(new UnityEngine.Rect(0, 0, WL816f_Pd.W, WL816f_Pd.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); + rt.Release(); UnityEngine.Object.DestroyImmediate(rt); + if (sb != null) L("캡처 " + path); + } +} diff --git a/AgentScripts/WL816f_Probe.cs b/AgentScripts/WL816f_Probe.cs new file mode 100644 index 000000000..c54a6aaa5 --- /dev/null +++ b/AgentScripts/WL816f_Probe.cs @@ -0,0 +1,113 @@ +// WL-816f — ① PD 실행 경로(로그인 → Load_Map(900) → Level01 Additive) 구조 실측 +// 에디트 모드 전용. InGame 씬이 섬 위에 무엇을 얹는지(조명·Volume·카메라)를 본다. +public static class WL816f_Probe +{ + public static void Run() + { + var sb = new System.Text.StringBuilder(); + + // 1) InGame 씬(= PD 경로의 활성 씬)을 연다 + var sc = UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/Scenes/InGame.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); + sb.AppendLine("=== InGame.unity (PD 경로의 ACTIVE 씬) ==="); + Dump(sb, sc); + + // 2) Level01 을 Additive 로 얹는다 (= Load_Map(900) 과 같은 구조) + var sc2 = UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/FarmingIsland/Scenes/Level01.unity", UnityEditor.SceneManagement.OpenSceneMode.Additive); + sb.AppendLine(); + sb.AppendLine("=== Level01.unity (Additive) ==="); + Dump(sb, sc2); + + sb.AppendLine(); + sb.AppendLine("active scene = " + UnityEngine.SceneManagement.SceneManager.GetActiveScene().name); + sb.AppendLine("RenderSettings(활성 씬 기준) ambient=" + UnityEngine.RenderSettings.ambientMode + + " light=" + UnityEngine.RenderSettings.ambientLight + + " sky=" + UnityEngine.RenderSettings.ambientSkyColor + + " skyboxMat=" + (UnityEngine.RenderSettings.skybox ? UnityEngine.RenderSettings.skybox.name : "없음") + + " fog=" + UnityEngine.RenderSettings.fog); + + // 3) 레이어 이름 확인 (풀은 Default 레이어에 그려진다) + sb.AppendLine("layer Default=" + UnityEngine.LayerMask.NameToLayer("Default")); + for (int i = 0; i < 32; i++) + { + string n = UnityEngine.LayerMask.LayerToName(i); + if (!string.IsNullOrEmpty(n)) sb.Append(i + ":" + n + " "); + } + sb.AppendLine(); + + System.IO.File.WriteAllText("AgentScripts/WL816f_PROBE.txt", sb.ToString()); + UnityEngine.Debug.Log(sb.ToString()); + } + + static void Dump(System.Text.StringBuilder sb, UnityEngine.SceneManagement.Scene sc) + { + var roots = sc.GetRootGameObjects(); + sb.AppendLine("roots=" + roots.Length); + for (int i = 0; i < roots.Length; i++) sb.Append(roots[i].name + (roots[i].activeSelf ? "" : "(off)") + " · "); + sb.AppendLine(); + + // 카메라 + foreach (var r in roots) + foreach (var c in r.GetComponentsInChildren(true)) + sb.AppendLine(" CAM " + Path(c.transform) + " active=" + c.gameObject.activeInHierarchy + "/" + c.enabled + + " depth=" + c.depth + " mask=0x" + c.cullingMask.ToString("X") + + " clear=" + c.clearFlags + " ortho=" + c.orthographic + " fov=" + c.fieldOfView + + " far=" + c.farClipPlane + + " urp=" + (c.GetComponent() != null + ? ("post=" + c.GetComponent().renderPostProcessing + + " volMask=0x" + c.GetComponent().volumeLayerMask.value.ToString("X") + + " type=" + c.GetComponent().renderType) + : "없음")); + + // 라이트 + foreach (var r in roots) + foreach (var l in r.GetComponentsInChildren(true)) + sb.AppendLine(" LIGHT " + Path(l.transform) + " active=" + l.gameObject.activeInHierarchy + "/" + l.enabled + + " " + l.type + " col=" + l.color + " I=" + l.intensity + " sh=" + l.shadows + + " rot=" + l.transform.eulerAngles); + + // Volume (포스트) + foreach (var r in roots) + foreach (var v in r.GetComponentsInChildren(true)) + sb.AppendLine(" VOL " + Path(v.transform) + " active=" + v.gameObject.activeInHierarchy + "/" + v.enabled + + " global=" + v.isGlobal + " prio=" + v.priority + " weight=" + v.weight + + " profile=" + (v.sharedProfile ? v.sharedProfile.name + " [" + Comps(v.sharedProfile) + "]" : "없음") + + " layer=" + v.gameObject.layer); + + // RenderSettings 는 씬별이다 — 씬을 활성으로 만들고 읽는다 + var prev = UnityEngine.SceneManagement.SceneManager.GetActiveScene(); + UnityEngine.SceneManagement.SceneManager.SetActiveScene(sc); + sb.AppendLine(" RS ambient=" + UnityEngine.RenderSettings.ambientMode + + " light=" + UnityEngine.RenderSettings.ambientLight + " sky=" + UnityEngine.RenderSettings.ambientSkyColor + + " I=" + UnityEngine.RenderSettings.ambientIntensity + + " skyboxMat=" + (UnityEngine.RenderSettings.skybox ? UnityEngine.RenderSettings.skybox.name : "없음") + + " fog=" + UnityEngine.RenderSettings.fog + " sun=" + (UnityEngine.RenderSettings.sun ? UnityEngine.RenderSettings.sun.name : "없음")); + if (prev.IsValid() && prev != sc) UnityEngine.SceneManagement.SceneManager.SetActiveScene(prev); + + // MapData / GlobalVolumeMgr / CheckShadow 같은 룩 관여 컴포넌트 + foreach (var r in roots) + foreach (var m in r.GetComponentsInChildren(true)) + { + if (m == null) continue; + string t = m.GetType().Name; + if (t == "MapData" || t == "GlobalVolumeMgr" || t == "CheckShadow" || t == "SetSkybox" + || t == "GraphicsManager" || t == "UniversalRenderPipelineAsset") + sb.AppendLine(" COMP " + t + " @ " + Path(m.transform) + " active=" + m.gameObject.activeInHierarchy + "/" + m.enabled); + } + } + + static string Comps(UnityEngine.Rendering.VolumeProfile p) + { + var s = ""; + for (int i = 0; i < p.components.Count; i++) s += p.components[i].GetType().Name + (p.components[i].active ? "" : "(off)") + ","; + return s; + } + + static string Path(UnityEngine.Transform t) + { + string s = t.name; + while (t.parent != null) { t = t.parent; s = t.name + "/" + s; } + return s; + } +} diff --git a/AgentScripts/WL816f_Sweep.cs b/AgentScripts/WL816f_Sweep.cs new file mode 100644 index 000000000..1e47fb617 --- /dev/null +++ b/AgentScripts/WL816f_Sweep.cs @@ -0,0 +1,227 @@ +// 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; + } +} diff --git a/AgentScripts/WL816f_Verify.cs b/AgentScripts/WL816f_Verify.cs new file mode 100644 index 000000000..fd9cf33ca --- /dev/null +++ b/AgentScripts/WL816f_Verify.cs @@ -0,0 +1,155 @@ +// WL-816f — PD 경로 Play 에서 ⓓ성능 ⓔ기능(걷기·농사·건설/확장) ⓕ되돌리기를 실측한다. +public static class WL816f_Verify +{ + public static void Start() + { + var go = UnityEngine.GameObject.Find("~WL816fVerify"); + if (go != null) UnityEngine.Object.DestroyImmediate(go); + go = new UnityEngine.GameObject("~WL816fVerify"); + go.AddComponent(); + } +} + +public class WL816f_VerifyRunner : UnityEngine.MonoBehaviour +{ + const string Dir = "Screenshots_WL/WL816f/"; + static System.Text.StringBuilder sb; + void Start() { StartCoroutine(Co()); } + + System.Collections.IEnumerator Co() + { + sb = new System.Text.StringBuilder(); + 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 < 6; i++) yield return new UnityEngine.WaitForSeconds(1f); + + L("=== WL-816f 검증 (PD 경로 · InGame 활성 + Level01 Additive) ==="); + L("풀 : " + WL.Look.Farm.WLIslandGrass.LastLog); + L("Soil 톤 : farm " + WL.Look.Farm.WLIslandLook.SoilFarmsToned + " · 타일 " + WL.Look.Farm.WLIslandLook.SoilTilesRepainted); + + // ── ⓓ 성능 : 같은 카메라로 60프레임 오프스크린 렌더 ───────── + var cam = FindLiveCam(); + L(""); + L("[성능] 카메라 " + (cam ? cam.name : "없음")); + float onMs = RenderBench(cam, 60); + var g = UnityEngine.Object.FindFirstObjectByType(UnityEngine.FindObjectsInactive.Include); + int instOn = WL.Look.Farm.WLIslandGrass.Instances; + long triOn = WL.Look.Farm.WLIslandGrass.Triangles; + if (g != null) g.enabled = false; // 풀만 끈 상태 + yield return null; yield return null; + float offMs = RenderBench(cam, 60); + if (g != null) g.enabled = true; + yield return null; yield return null; + L("풀 켬 " + onMs.ToString("F3") + " ms/frame · 풀 끔 " + offMs.ToString("F3") + " ms/frame · 델타 " + (onMs - offMs).ToString("F3") + " ms"); + L("인스턴스 " + instOn + " · 삼각형(풀) " + triOn + " · 드로우콜 +" + WL.Look.Farm.WLIslandGrass.DrawnConfigs); + + // ── ⓔ 기능 ───────────────────────────────────────────────── + L(""); + L("[기능]"); + // 풀 콜라이더 0 · 레이캐스트 · NavMesh + int colliders = 0; + var gobj = g != null ? g.gameObject : null; + if (gobj != null) colliders = gobj.GetComponentsInChildren(true).Length; + L("풀 오브젝트 콜라이더 = " + colliders + " (0 이어야 한다)"); + + int hit = 0, nav = 0; + for (int i = 0; i < 100; i++) + { + var p = new UnityEngine.Vector3(UnityEngine.Random.Range(-7f, 7f), 3f, UnityEngine.Random.Range(-7f, 7f)); + UnityEngine.RaycastHit h; + if (UnityEngine.Physics.Raycast(p, UnityEngine.Vector3.down, out h, 10f)) hit++; + UnityEngine.AI.NavMeshHit nh; + if (UnityEngine.AI.NavMesh.SamplePosition(new UnityEngine.Vector3(p.x, 0f, p.z), out nh, 1.5f, ~0)) nav++; + } + L("풀밭 100점 아래 레이캐스트 적중 " + hit + "/100 · NavMesh 샘플 " + nav + "/100"); + + // 걷기 — FI PlayerController 의 CharacterController + var cc = UnityEngine.Object.FindFirstObjectByType(UnityEngine.FindObjectsInactive.Exclude); + if (cc != null) + { + var p0 = cc.transform.position; + var dir = new UnityEngine.Vector3(1f, 0f, 1f).normalized; + float moved = 0f; + for (int i = 0; i < 120; i++) { cc.Move(dir * 0.05f + UnityEngine.Vector3.down * 0.02f); yield return null; } + moved = UnityEngine.Vector3.Distance(new UnityEngine.Vector3(p0.x, 0f, p0.z), new UnityEngine.Vector3(cc.transform.position.x, 0f, cc.transform.position.z)); + L("걷기 : " + p0.ToString("F2") + " → " + cc.transform.position.ToString("F2") + " = " + moved.ToString("F2") + " m · grounded=" + cc.isGrounded); + } + else L("걷기 : CharacterController 없음(미확인)"); + + // 농사 — Farm/Soil 개수 + 씨뿌리기 상태 전환 + var farms = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); + var soils = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); + L("농사 : Farm " + farms.Length + " · Soil " + soils.Length); + int painted = 0; + for (int i = 0; i < soils.Length; i++) + { + var r = soils[i].GetComponentInChildren(true); + if (r != null && r.sharedMaterial != null) painted++; + } + L("Soil 렌더러 살아있음 " + painted + "/" + soils.Length + " · 첫 Soil 색 " + + (soils.Length > 0 && soils[0].GetComponentInChildren(true) != null + ? UnityEngine.ColorUtility.ToHtmlStringRGB(soils[0].GetComponentInChildren(true).material.color) : "-")); + + // 확장 — 섬 타일 수 + var islands = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.None); + int unlocked = 0; for (int i = 0; i < islands.Length; i++) if (islands[i].IsUnlocked && islands[i].gameObject.activeInHierarchy) unlocked++; + L("확장 : 섬 타일 전체 " + islands.Length + " · 열린 타일 " + unlocked + " · 풀 깔린 타일 " + WL.Look.Farm.WLIslandGrass.Tiles); + + // ── 최종 캡처 ─────────────────────────────────────────────── + Shot(FindLiveCam(), Dir + "d_final_wide.png"); + + System.IO.File.WriteAllText("AgentScripts/WL816f_VERIFY.txt", sb.ToString()); + UnityEngine.Debug.Log("[816f verify]\n" + sb.ToString()); + } + + static void L(string s) { sb.AppendLine(s); } + + static UnityEngine.Camera FindLiveCam() + { + var cams = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); + UnityEngine.Camera top = null; + for (int i = 0; i < cams.Length; i++) + { + var c = cams[i]; + if (!c.enabled || c.targetTexture != null) continue; + if (top == null || c.depth > top.depth) top = c; + } + return top; + } + + static float RenderBench(UnityEngine.Camera cam, int frames) + { + if (cam == null) return -1f; + var rt = new UnityEngine.RenderTexture(1080, 1920, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); + rt.antiAliasing = 1; rt.Create(); + var prev = cam.targetTexture; cam.targetTexture = rt; + cam.Render(); // 워밍업 + var sw = System.Diagnostics.Stopwatch.StartNew(); + for (int i = 0; i < frames; i++) cam.Render(); + sw.Stop(); + cam.targetTexture = prev; + rt.Release(); UnityEngine.Object.DestroyImmediate(rt); + return (float)sw.Elapsed.TotalMilliseconds / frames; + } + + static void Shot(UnityEngine.Camera cam, string path) + { + if (cam == null) return; + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + var rt = new UnityEngine.RenderTexture(1080, 1920, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); + rt.antiAliasing = 1; rt.Create(); + var prevT = cam.targetTexture; var prevA = UnityEngine.RenderTexture.active; + cam.targetTexture = rt; cam.Render(); + UnityEngine.RenderTexture.active = rt; + var tex = new UnityEngine.Texture2D(1080, 1920, UnityEngine.TextureFormat.RGB24, false); + tex.ReadPixels(new UnityEngine.Rect(0, 0, 1080, 1920), 0, 0); tex.Apply(); + System.IO.File.WriteAllBytes(path, UnityEngine.ImageConversion.EncodeToPNG(tex)); + UnityEngine.RenderTexture.active = prevA; cam.targetTexture = prevT; + UnityEngine.Object.DestroyImmediate(tex); + rt.Release(); UnityEngine.Object.DestroyImmediate(rt); + L("캡처 " + path); + } +} diff --git a/Assets/WL/Look/Farm/Materials/Farm_Grass_Demo.mat b/Assets/WL/Look/Farm/Materials/Farm_Grass_Demo.mat new file mode 100644 index 000000000..c35aae89f --- /dev/null +++ b/Assets/WL/Look/Farm/Materials/Farm_Grass_Demo.mat @@ -0,0 +1,159 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-3938375536428912352 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion + version: 10 +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Farm_Grass_Demo + m_Shader: {fileID: -6465566751694194690, guid: 116bae4840169554085c5aa591d459f5, + type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _MAIN_LIGHT_SHADOWS + - _SHADOWS_SOFT + m_InvalidKeywords: + - _CLOUDSENABLED + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: + - MOTIONVECTORS + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _AmbientStrength: 0.1 + - _Blend: 0 + - _BlendModePreserveSpecular: 1 + - _Brightness: 0.25 + - _BumpScale: 1 + - _CLOUDSENABLED: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cloud_Change: 0.005 + - _Cloud_Cover: 0.5 + - _Cloud_Density: 0.01 + - _Cloud_Strength: 1 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _EnvironmentReflections: 1 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 0 + - _MAIN_LIGHT: 0 + - _Metallic: 0 + - _MinimumDarkness: 0.2 + - _OcclusionStrength: 1 + - _Parallax: 0.005 + - _QueueControl: 0 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _SHADOWS_SOFT: 1 + - _Shades: 7 + - _Smoothness: 0.5 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 + - _VerticalDifference: 1 + - _WindDensity: 0.2 + - _WindStrength: 0.3 + - _WorkflowMode: 1 + - _XRMotionVectorsPass: 1 + - _ZWrite: 1 + m_Colors: + - _BaseColor: {r: 1, g: 1, b: 1, a: 1} + - _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0} + - _Cloud_Step: {r: 13, g: 17, b: 0, a: 0} + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _DiffuseColor: {r: 0.6509804, g: 0.8901961, b: 0.62352943, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + - _ShadowDiffuseColor: {r: 0.58431375, g: 0.8, b: 0.56078434, a: 1} + - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} + - _WindMovement: {r: 6, g: 0, b: 0, a: 0} + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Assets/WL/Look/Farm/Materials/Farm_Grass_Demo.mat.meta b/Assets/WL/Look/Farm/Materials/Farm_Grass_Demo.mat.meta new file mode 100644 index 000000000..ae4a21e3e --- /dev/null +++ b/Assets/WL/Look/Farm/Materials/Farm_Grass_Demo.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bfa67f7091cb7ff418051fec03e5c28c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset b/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset index 63bc62710..d7a01036d 100644 --- a/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset +++ b/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset @@ -38,6 +38,9 @@ MonoBehaviour: to: {fileID: 2100000, guid: 1c9ce9b7865f6d643ad9601f6fa81ba3, type: 2} islandTopMaterial: {fileID: 2100000, guid: 500f1cf3127afb748b316fe28b128edc, type: 2} skipSoilRenderers: 1 + soilToneEnabled: 1 + soilTint: {r: 0.84, g: 0.86, b: 0.9, a: 1} + soilCheckerContrast: 0.6 ambientSky: {r: 0.212, g: 0.227, b: 0.259, a: 1} ambientEquator: {r: 0.114, g: 0.125, b: 0.133, a: 1} ambientGround: {r: 0.047, g: 0.043, b: 0.035, a: 1} @@ -57,11 +60,11 @@ MonoBehaviour: - enabled_: 1 label: "\uD480" mesh: {fileID: -4413095505993930501, guid: 1f298817fdd2a184480e8af5b21278bc, type: 3} - material: {fileID: 2100000, guid: a194cb4ce6027c246af528b83b728a15, type: 2} + material: {fileID: 2100000, guid: bfa67f7091cb7ff418051fec03e5c28c, type: 2} probability: 100 scale: 0.35 normalOffset: 0.04 - density: 2.5 + density: 1.8 - enabled_: 1 label: "\uAF43" mesh: {fileID: -6327915695457451627, guid: dab2c84bf0b5ced4aaf0cffc4c491d55, type: 3} @@ -69,7 +72,7 @@ MonoBehaviour: probability: 1 scale: 0.35 normalOffset: 0.04 - density: 2.5 + density: 1.8 - enabled_: 1 label: "\uC790\uAC08" mesh: {fileID: 2928966540353291585, guid: 7f1f11ac2c783374b876f8dc2bb7c7db, type: 3} diff --git a/Assets/WL/Look/Farm/Textures/WL_IslandTop_Demo.png b/Assets/WL/Look/Farm/Textures/WL_IslandTop_Demo.png index 083457176e0413ab0fe2810dbb42510da288050f..51e92600720497dc220b13bb1d87594fae541707 100644 GIT binary patch delta 73 zcmYd|ognGL#K6GdovV2VNU32sY&MZBn bF-M3&U?*Emi+ijEP(6dEtDnm{r-UW|J82hB delta 81 zcmaz|ognGM#=yXEgnwT*kkavVaSV}=OtvYmdR@<^vLW@Ql8PKNvoIGwcY6_Q&+Gf^ joC}$E=}w7uyuiZnEr?^D%%syoKz$6Ju6{1-oD!M<)nFMX diff --git a/Assets/WL/Look/Farm/WLIslandLook.cs b/Assets/WL/Look/Farm/WLIslandLook.cs index 9aa8b991f..53f69da62 100644 --- a/Assets/WL/Look/Farm/WLIslandLook.cs +++ b/Assets/WL/Look/Farm/WLIslandLook.cs @@ -25,6 +25,7 @@ using UnityEngine.SceneManagement; using CryingSnow.FarmingIsland; using FIIsland = CryingSnow.FarmingIsland.Island; using FISoil = CryingSnow.FarmingIsland.Soil; +using FIFarm = CryingSnow.FarmingIsland.Farm; namespace WL.Look.Farm { @@ -85,6 +86,7 @@ namespace WL.Look.Farm { s_done.Clear(); s_hooked.Clear(); + s_tonedFarms.Clear(); LookApplied = false; GrassSpawned = false; ReferenceLookApplied = false; } @@ -127,6 +129,7 @@ namespace WL.Look.Farm } if (cfg.applyLighting != 0) ApplyLighting(cfg, scene); if (cfg.applyLook != 0 && cfg.applyReferenceLook != 0) ApplyReferenceLook(cfg, scene); + if (cfg.soilToneEnabled != 0) ToneSoil(cfg, scene); if (cfg.grassEnabled != 0) SpawnGrass(cfg, scene); @@ -148,6 +151,11 @@ namespace WL.Look.Farm if (w > 0f) yield return new WaitForSeconds(w); if (!scene.isLoaded) yield break; if (cfg.applyLook != 0) { SwapMaterials(cfg, scene); Rescans++; } + // 🔴 816f 실측 — PD 실행 경로(Additive)에서 `RenderSettings` 는 **활성 씬(InGame)** + // 소유이고, 그 씬에는 조명을 덮어쓰는 코드(`MapData`)가 없다(실측). + // 그래서 조명은 재훑기에서 **다시 걸지 않는다** — 다시 걸면 `WLReferenceLook` 의 + // 무드(Flat)를 Skybox 로 되돌려 오히려 룩이 바뀐다(실측으로 확인). + if (cfg.soilToneEnabled != 0) ToneSoil(cfg, scene); HookIslands(cfg, scene); } } @@ -302,6 +310,67 @@ namespace WL.Look.Farm } } + // ───────────────────────────────────────────────────────────────── + // ②-b 밭(Soil) 칸 표시 — **기능은 그대로 두고 색만** 데모 톤으로 (816f · PD 지시) + // FI `Farm` 의 4색(마름1·마름2·젖음1·젖음2)을 런타임에 낮추고 + // `Soil.Initialize(farm)`(public) 를 다시 불러 칠만 갱신한다. + // → 물주기 피드백(마름↔젖음)·칸 구분은 전부 살아 있다. `Assets/FarmingIsland/**` 0줄. + // ───────────────────────────────────────────────────────────────── + static readonly string[] s_soilFields = { "soilDryColor1", "soilDryColor2", "soilWetColor1", "soilWetColor2" }; + static readonly HashSet s_tonedFarms = new HashSet(); + + public static int SoilFarmsToned, SoilTilesRepainted; + + public static void ToneSoil(WLIslandLookSettings cfg, Scene scene) + { + var farms = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); + var t = typeof(FIFarm); + for (int i = 0; i < farms.Length; i++) + { + var farm = farms[i]; + if (farm == null || farm.gameObject.scene != scene) continue; + if (s_tonedFarms.Contains(farm)) continue; + + // 1) 마름1/마름2 · 젖음1/젖음2 각각의 평균으로 모으고(대비 축소) 틴트를 곱한다 + var vals = new Color[4]; + var fis = new System.Reflection.FieldInfo[4]; + bool ok = true; + for (int k = 0; k < 4; k++) + { + fis[k] = t.GetField(s_soilFields[k], + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + if (fis[k] == null) { ok = false; break; } + vals[k] = (Color)fis[k].GetValue(farm); + } + if (!ok) continue; + + Apply(cfg, fis, farm, vals, 0, 1); // 마름 한 쌍 + Apply(cfg, fis, farm, vals, 2, 3); // 젖음 한 쌍 + s_tonedFarms.Add(farm); + SoilFarmsToned++; + + // 2) 이미 칠해진 타일을 다시 칠한다 — FI 의 public 진입점 그대로 + var soils = farm.GetComponentsInChildren(true); + for (int s = 0; s < soils.Length; s++) + { + if (soils[s] == null) continue; + try { soils[s].Initialize(farm); SoilTilesRepainted++; } catch { } + } + } + } + + static void Apply(WLIslandLookSettings cfg, System.Reflection.FieldInfo[] fis, FIFarm farm, Color[] v, int a, int b) + { + var mid = (v[a] + v[b]) * 0.5f; + float c = cfg.soilCheckerContrast; + var ca = Color.Lerp(mid, v[a], c); + var cb = Color.Lerp(mid, v[b], c); + fis[a].SetValue(farm, Mul(ca, cfg.soilTint)); + fis[b].SetValue(farm, Mul(cb, cfg.soilTint)); + } + + static Color Mul(Color a, Color b) { return new Color(a.r * b.r, a.g * b.g, a.b * b.b, a.a); } + // ───────────────────────────────────────────────────────────────── // ③ 아레나와 같은 외곽선·대비·무드 (씬 비의존 — 816a 실측) // ───────────────────────────────────────────────────────────────── diff --git a/Assets/WL/Look/Farm/WLIslandLookSettings.cs b/Assets/WL/Look/Farm/WLIslandLookSettings.cs index de79d8488..b4bf79ab4 100644 --- a/Assets/WL/Look/Farm/WLIslandLookSettings.cs +++ b/Assets/WL/Look/Farm/WLIslandLookSettings.cs @@ -136,6 +136,19 @@ namespace WL.Look.Farm [Tooltip("1 이면 농사 타일(Soil)은 원본 머티리얼 그대로 둔다(색을 코드가 직접 칠하는 곳이라 건드리면 안 된다).")] public int skipSoilRenderers = 1; + // ── 816f: 밭(Soil) 체크무늬 **기능은 유지**하고 색만 데모 톤으로 (PD 2026-09-13) + [Header("§0-816f — 밭(Soil) 칸 표시 톤 (기능 유지 · 색만)")] + [Tooltip("1 이면 FI `Farm` 의 마른/젖은 흙 색 4개를 런타임에 데모 톤으로 낮춘다. " + + "칸 구분(마름/젖음 표시)은 그대로 남는다 — 색만 바뀐다. 0 이면 FI 원본 색.")] + public int soilToneEnabled = 1; + + [Tooltip("흙 색에 곱하는 값(0~1). 낮출수록 주황이 죽는다.")] + public Color soilTint = new Color(0.84f, 0.86f, 0.90f, 1f); + + [Tooltip("체크무늬 두 색의 차이를 이 비율로 줄인다(1 = FI 원본 차이 · 0 = 차이 없음=칸 안 보임). " + + "🔴 0 으로 두지 말 것 — 어디를 갈았는지 안 보인다.")] + [Range(0f, 1f)] public float soilCheckerContrast = 0.6f; + [Header("§0 — 조명 값 (816a 실측 = 데모/아레나와 동일)")] public Color ambientSky = new Color(0.212f, 0.227f, 0.259f, 1f); public Color ambientEquator = new Color(0.114f, 0.125f, 0.133f, 1f); From b0f8febb127d2959a726892dc768ea7f234473de Mon Sep 17 00:00:00 2001 From: swrring Date: Sun, 13 Sep 2026 14:22:19 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[WL-816f]=20=EB=A7=88=EB=AC=B4=EB=A6=AC=20?= =?UTF-8?q?=EC=8B=A4=EC=B8=A1=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8(?= =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=EC=A0=84/=ED=9B=84=20=C2=B7=20=EB=B0=AD?= =?UTF-8?q?=20=EC=B9=B8=20=ED=91=9C=EC=8B=9C=20=C2=B7=20=ED=99=95=EC=9E=A5?= =?UTF-8?q?)=20(#816)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 --- AgentScripts/WL816f_Final.cs | 114 +++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 AgentScripts/WL816f_Final.cs diff --git a/AgentScripts/WL816f_Final.cs b/AgentScripts/WL816f_Final.cs new file mode 100644 index 000000000..10f86f899 --- /dev/null +++ b/AgentScripts/WL816f_Final.cs @@ -0,0 +1,114 @@ +// WL-816f — 마무리 실측: ⓐ성능 전(밀도 2.5)/후(1.8) ⓑ밭 칸 표시 유지 ⓒ확장 시 새 타일에 풀 +public static class WL816f_Final +{ + public static void Start() + { + var go = UnityEngine.GameObject.Find("~WL816fFinal"); + if (go != null) UnityEngine.Object.DestroyImmediate(go); + go = new UnityEngine.GameObject("~WL816fFinal"); + go.AddComponent(); + } +} + +public class WL816f_FinalRunner : UnityEngine.MonoBehaviour +{ + static System.Text.StringBuilder sb; + void Start() { StartCoroutine(Co()); } + + System.Collections.IEnumerator Co() + { + sb = new System.Text.StringBuilder(); + 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 < 6; i++) yield return new UnityEngine.WaitForSeconds(1f); + + var g = UnityEngine.Object.FindFirstObjectByType(UnityEngine.FindObjectsInactive.Include); + var src = WL.Look.Farm.WLIslandLookSettings.Instance; + var rt = UnityEngine.Object.Instantiate(src); // 에셋 무변경 + g.cfg = rt; + var cam = LiveCam(); + + // ── ⓐ 성능: 816e(2.5) vs 816f(1.8) vs 풀 없음 ─────────────── + L("[성능] 같은 카메라(" + (cam ? cam.name : "?") + ") · 1080×1920 오프스크린 60프레임 평균"); + float[] dens = { 2.5f, 1.8f }; + for (int k = 0; k < dens.Length; k++) + { + SetD(rt, dens[k]); g.Rebuild(); yield return null; yield return null; + float ms = Bench(cam, 60); + L(" 밀도 " + dens[k].ToString("F2") + " : 인스턴스 " + WL.Look.Farm.WLIslandGrass.Instances + + " · 삼각형 " + WL.Look.Farm.WLIslandGrass.Triangles + + " · 드로우콜 +" + WL.Look.Farm.WLIslandGrass.DrawnConfigs + + " · " + ms.ToString("F3") + " ms/frame (" + (1000f / ms).ToString("F0") + " fps 환산)"); + } + g.enabled = false; yield return null; yield return null; + float off = Bench(cam, 60); + L(" 풀 없음 : " + off.ToString("F3") + " ms/frame"); + g.enabled = true; SetD(rt, 1.8f); g.Rebuild(); yield return null; + + // ── ⓑ 밭 칸 표시가 남아 있나(두 색이 서로 달라야 한다) ────── + L(""); + var soils = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); + var seen = new System.Collections.Generic.Dictionary(); + for (int i = 0; i < soils.Length; i++) + { + var r = soils[i].GetComponentInChildren(true); + if (r == null) continue; + var h = UnityEngine.ColorUtility.ToHtmlStringRGB(r.material.color); + seen[h] = seen.ContainsKey(h) ? seen[h] + 1 : 1; + } + L("[밭 칸 표시] Soil " + soils.Length + "개의 색 종류 " + seen.Count + " (2 여야 칸 구분이 살아 있다)"); + foreach (var kv in seen) L(" " + kv.Key + " ×" + kv.Value); + + // ── ⓒ 확장: 타일 하나를 잠갔다 다시 열어 풀이 따라오는지 ──── + L(""); + var islands = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); + CryingSnow.FarmingIsland.Island target = null; + for (int i = 0; i < islands.Length; i++) + if (islands[i].IsUnlocked && islands[i].gameObject.activeInHierarchy && islands[i].transform.position != UnityEngine.Vector3.zero) { target = islands[i]; break; } + int before = WL.Look.Farm.WLIslandGrass.Tiles; + if (target != null) + { + target.gameObject.SetActive(false); + yield return new UnityEngine.WaitForSeconds(2.5f); + int mid = WL.Look.Farm.WLIslandGrass.Tiles; + target.gameObject.SetActive(true); + yield return new UnityEngine.WaitForSeconds(3f); + int after = WL.Look.Farm.WLIslandGrass.Tiles; + L("[확장] 풀 깔린 타일 " + before + " → (타일 1개 잠금) " + mid + " → (다시 열기) " + after + + (after == before ? " ✔ 새로 열린 타일에 풀이 자동으로 깔린다" : " 🔴 복귀 실패")); + } + else L("[확장] 대상 타일 없음(미확인)"); + + System.IO.File.WriteAllText("AgentScripts/WL816f_FINAL.txt", sb.ToString()); + UnityEngine.Debug.Log("[816f final]\n" + sb.ToString()); + } + + static void L(string s) { sb.AppendLine(s); } + static void SetD(WL.Look.Farm.WLIslandLookSettings c, float d) + { + for (int i = 0; i < c.scatter.Length; i++) + if (c.scatter[i] != null && (c.scatter[i].label.Contains("풀") || c.scatter[i].label.Contains("꽃"))) c.scatter[i].density = d; + } + static UnityEngine.Camera LiveCam() + { + var cams = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); + UnityEngine.Camera t = null; + for (int i = 0; i < cams.Length; i++) { var c = cams[i]; if (!c.enabled || c.targetTexture != null) continue; if (t == null || c.depth > t.depth) t = c; } + return t; + } + static float Bench(UnityEngine.Camera cam, int frames) + { + if (cam == null) return -1f; + var rt = new UnityEngine.RenderTexture(1080, 1920, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); + rt.Create(); var prev = cam.targetTexture; cam.targetTexture = rt; + for (int i = 0; i < 10; i++) cam.Render(); + var sw = System.Diagnostics.Stopwatch.StartNew(); + for (int i = 0; i < frames; i++) cam.Render(); + sw.Stop(); cam.targetTexture = prev; + rt.Release(); UnityEngine.Object.DestroyImmediate(rt); + return (float)sw.Elapsed.TotalMilliseconds / frames; + } +}