diff --git a/AgentScripts/WL816h_Build.cs b/AgentScripts/WL816h_Build.cs new file mode 100644 index 000000000..69009c801 --- /dev/null +++ b/AgentScripts/WL816h_Build.cs @@ -0,0 +1,133 @@ +// WL-816h — ③ WL 워터 복사본 3단(A/B/C) 만들기 + SO 배선 (에디트 모드) +// 🔴 `Assets/WL/Materials/WL_Water_Ocean.mat` 원본은 **복사만** 한다(0줄 수정). +// 🔴 `Assets/FarmingIsland/**` 0줄. +using UnityEngine; +using UnityEditor; + +public static class WL816h_Build +{ + const string SrcOcean = "Assets/WL/Materials/WL_Water_Ocean.mat"; + const string Dir = "Assets/WL/Look/Farm/Materials/"; + const string SO = "Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset"; + const string FiWater = "Assets/FarmingIsland/Materials/SimpleWater.mat"; // 확인 후 대체 + + // ToonWaterU 프로퍼티 참조 이름 (shadergraph 실측) + const string P_Size = "Vector1_07238257334b4a149e675e2451801030"; + const string P_FoamDistance = "Vector1_101dc4546b684d40bc2ff2fc59ab43d5"; + const string P_FoamScale = "Vector1_11671d7a4cdb4d059a6c69e39f3f7b3f"; + const string P_FoamSpeed = "Vector1_3089b6a325a44c1686907af4ee3b0776"; + const string P_FoamEdge = "Vector1_34f757bec6b8422b9aef3b9d97117a6e"; + const string P_DeepDistance = "Vector1_566e288f42864b8e9432d81fbdb83f38"; + const string P_Height = "Vector1_d813b569a4d541d1b2ac4ea9bc66a3b9"; + const string P_WaweSpeed = "Vector1_ff7a2780aaa94ddd86e156c4707e39a2"; + const string P_Strength = "Vector1_99cc56b5c6fd474082f07f00a6df94f9"; + const string P_Speed = "Vector1_8e22a98f75c94b218c49e6c4805e799d"; + const string P_Alpha = "Vector1_7090c8ca8ea84e2d85858ae4beb5be38"; + const string P_WaterColor = "Color_9af4ad59934f40d1ae6565e6ab22c45e"; + const string P_DarkColor = "Color_ca031ae309ff42bdb95f777248fb961d"; + const string P_FoamColor = "Color_e1f155248d144786a62fc1585ca21e9a"; + const string P_FoamShadow = "Color_2323d69962e04c499c5d5f0925432b55"; + + public static void Run() + { + var sb = new System.Text.StringBuilder(); + var src = AssetDatabase.LoadAssetAtPath(SrcOcean); + if (src == null) { sb.AppendLine("🔴 원본 없음 " + SrcOcean); Dump(sb); return; } + sb.AppendLine("원본 " + SrcOcean + " shader=" + src.shader.name); + + // ── A = WL_Water_Ocean 그대로 (기준) ────────────────────────── + var a = Copy(SrcOcean, Dir + "Farm_Water_WL_A.mat", sb); + + // ── B = 데모 톤 (816a 실측 = 데모 Water.mat 의 깊은물/얕은물) ── + var b = Copy(SrcOcean, Dir + "Farm_Water_WL_B.mat", sb); + if (b != null) + { + b.SetColor(P_WaterColor, new Color(0.298f, 0.800f, 1.000f, 1f)); + b.SetColor(P_DarkColor, new Color(0.133f, 0.518f, 0.745f, 1f)); + b.SetColor(P_FoamShadow, new Color(0.80f, 0.90f, 0.96f, 1f)); + b.SetFloat(P_FoamDistance, 3f); + b.SetFloat(P_FoamScale, 36f); + b.SetFloat(P_DeepDistance, 5f); + b.SetFloat(P_Height, 0.12f); + b.SetFloat(P_WaweSpeed, 0.5f); + EditorUtility.SetDirty(b); + } + + // ── C = 차분(채도↓ · 파도·거품 작게) ─────────────────────────── + var c = Copy(SrcOcean, Dir + "Farm_Water_WL_C.mat", sb); + if (c != null) + { + c.SetColor(P_WaterColor, new Color(0.360f, 0.690f, 0.820f, 1f)); + c.SetColor(P_DarkColor, new Color(0.170f, 0.420f, 0.570f, 1f)); + c.SetColor(P_FoamShadow, new Color(0.78f, 0.86f, 0.92f, 1f)); + c.SetFloat(P_FoamDistance, 2.2f); + c.SetFloat(P_FoamScale, 28f); + c.SetFloat(P_DeepDistance, 7f); + c.SetFloat(P_Height, 0.09f); + c.SetFloat(P_WaweSpeed, 0.4f); + c.SetFloat(P_FoamSpeed, 0.4f); + EditorUtility.SetDirty(c); + } + + AssetDatabase.SaveAssets(); + + // ── FI 원본 물 머티리얼 찾기 (remap 의 from 을 그대로 재사용) ── + Material fiWater = null; + var so = AssetDatabase.LoadAssetAtPath(SO); + if (so != null && so.materialRemap != null) + foreach (var e in so.materialRemap) + if (e != null && e.from != null && e.from.shader != null + && e.from.shader.name == "Shader Graphs/Simple Water") fiWater = e.from; + sb.AppendLine("FI 원본 물 = " + (fiWater == null ? "🔴 못 찾음" : AssetDatabase.GetAssetPath(fiWater))); + + // ── SO 배선 ─────────────────────────────────────────────────── + if (so != null) + { + so.waterFrom = fiWater; + so.waterTo = b; // 1차 기본 = B (3단 비교 뒤 확정) + so.waterMode = 1; + so.skyScopeEnabled = 1; + if (so.skyScenes == null || so.skyScenes.Length < 4) + so.skyScenes = new string[] { "Level01", "WL_FarmLook", "WL_Dungeon01", "WL_Dungeon02" }; + EditorUtility.SetDirty(so); + AssetDatabase.SaveAssets(); + sb.AppendLine("SO 배선 완료 — waterMode=" + so.waterMode + " waterTo=" + (so.waterTo ? so.waterTo.name : "없음") + + " skyScopeEnabled=" + so.skyScopeEnabled + " skyScenes=" + string.Join(",", so.skyScenes)); + } + else sb.AppendLine("🔴 SO 못 읽음 " + SO); + + Dump(sb); + } + + /// 3단 중 하나를 SO 에 물린다 (비교 캡처용). + public static void PickA() { Pick("Farm_Water_WL_A"); } + public static void PickB() { Pick("Farm_Water_WL_B"); } + public static void PickC() { Pick("Farm_Water_WL_C"); } + public static void PickOff() { Pick(null); } + + static void Pick(string name) + { + var so = AssetDatabase.LoadAssetAtPath(SO); + if (so == null) { Debug.Log("[816h] SO 없음"); return; } + if (name == null) { so.waterMode = 0; } + else { so.waterMode = 1; so.waterTo = AssetDatabase.LoadAssetAtPath(Dir + name + ".mat"); } + EditorUtility.SetDirty(so); AssetDatabase.SaveAssets(); + Debug.Log("[816h] 물 = " + (name ?? "지금(FarmingIsland)") + " mode=" + so.waterMode); + } + + static Material Copy(string src, string dst, System.Text.StringBuilder sb) + { + if (System.IO.File.Exists(dst)) AssetDatabase.DeleteAsset(dst); + if (!AssetDatabase.CopyAsset(src, dst)) { sb.AppendLine("🔴 복사 실패 " + dst); return null; } + AssetDatabase.ImportAsset(dst); + var m = AssetDatabase.LoadAssetAtPath(dst); + sb.AppendLine("복사 " + dst + " = " + (m == null ? "null" : m.shader.name)); + return m; + } + + static void Dump(System.Text.StringBuilder sb) + { + System.IO.File.WriteAllText("AgentScripts/WL816h_BUILD.txt", sb.ToString()); + Debug.Log("[816h build]\n" + sb); + } +} diff --git a/AgentScripts/WL816h_Probe.cs b/AgentScripts/WL816h_Probe.cs new file mode 100644 index 000000000..435967303 --- /dev/null +++ b/AgentScripts/WL816h_Probe.cs @@ -0,0 +1,209 @@ +// WL-816h — ① 지금 상태 실측 (PD 실행 경로 · 하늘 · 물) +// PD 경로 재현 = InGame 을 활성 씬으로 Play → Level01 을 Additive (816f 와 같은 호출) +// 로그인은 워커 금지(§9)라 못 탄다 → 구조만 같게. +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Universal; +using UnityEngine.SceneManagement; + +public static class WL816h_Probe +{ + public const string Dir = "Screenshots_WL/WL816h/"; + public const int W = 1080, H = 1920; + + public static void Open() + { + UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/Scenes/InGame.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); + Debug.Log("[816h] InGame 열림"); + } + + public static void Start() + { + var go = GameObject.Find("~WL816hProbe"); + if (go != null) Object.DestroyImmediate(go); + go = new GameObject("~WL816hProbe"); + go.AddComponent(); + Debug.Log("[816h] 러너 시작"); + } + + // ── 에디트 모드: 레퍼런스(아레나·데모) 씬의 하늘을 그대로 읽고 찍는다 ── + public static void RefScenes() + { + var sb = new System.Text.StringBuilder(); + Ref(sb, "Assets/WL/Look/Arena/Scenes/WL_ArenaProto.unity", "arena"); + Ref(sb, "Assets/LowPolyFantasyArena/Scenes/LowPolyArena_Demo.unity", "demo"); + System.IO.File.WriteAllText("AgentScripts/WL816h_REF.txt", sb.ToString()); + Debug.Log("[816h ref]\n" + sb); + } + + static void Ref(System.Text.StringBuilder sb, string path, string tag) + { + UnityEditor.SceneManagement.EditorSceneManager.OpenScene(path, UnityEditor.SceneManagement.OpenSceneMode.Single); + sb.AppendLine("### " + tag + " " + path); + sb.AppendLine(WL816h_Util.SkyLine()); + var cams = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); + foreach (var c in cams) + { + var ac = c.GetComponent(); + sb.AppendLine(" CAM " + c.name + " active=" + c.gameObject.activeInHierarchy + " en=" + c.enabled + + " ortho=" + c.orthographic + (c.orthographic ? (" size=" + c.orthographicSize) : (" fov=" + c.fieldOfView)) + + " clear=" + c.clearFlags + " post=" + (ac == null ? "noACD" : ac.renderPostProcessing.ToString())); + } + var vols = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); + sb.AppendLine(" Volume 수 = " + vols.Length); + // 하늘만 보이는 카메라로 찍는다(수평선 위) + var cam = WL816h_Util.SkyCam(); + sb.AppendLine(" 하늘 화면색 = " + WL816h_Util.Shot(cam, Dir + "ref_sky_" + tag + ".png", true)); + Object.DestroyImmediate(cam.gameObject); + } +} + +public static class WL816h_Util +{ + public static string SkyLine() + { + return "RenderSettings: amb=" + RenderSettings.ambientMode + " light=" + F(RenderSettings.ambientLight) + + " sky=" + F(RenderSettings.ambientSkyColor) + " I=" + RenderSettings.ambientIntensity + + " skybox=" + (RenderSettings.skybox ? RenderSettings.skybox.name + "/" + RenderSettings.skybox.shader.name : "없음") + + " fog=" + RenderSettings.fog + " refl=" + RenderSettings.defaultReflectionMode + + "/" + RenderSettings.reflectionIntensity + + " sun=" + (RenderSettings.sun ? RenderSettings.sun.name : "없음"); + } + + public static string F(Color c) { return "(" + c.r.ToString("F3") + "," + c.g.ToString("F3") + "," + c.b.ToString("F3") + ")"; } + + /// 수평선 위만 잡는 카메라 — 화면의 대부분이 하늘이다. + public static Camera SkyCam() + { + var go = new GameObject("~WL816hSkyCam"); go.hideFlags = HideFlags.DontSave; + var c = go.AddComponent(); + c.orthographic = false; c.fieldOfView = 50f; + c.nearClipPlane = 0.3f; c.farClipPlane = 2000f; + c.clearFlags = CameraClearFlags.Skybox; c.cullingMask = ~0; c.depth = -100f; + go.transform.position = new Vector3(0f, 40f, 0f); + go.transform.rotation = Quaternion.Euler(-16f, 30f, 0f); // 위를 본다 = 하늘만 + if (go.GetComponent() == null) go.AddComponent(); + return c; + } + + /// 오프스크린 렌더 → PNG. 반환 = 화면 평균색(#RRGGBB). + public static string Shot(Camera cam, string path, bool wantAvg) + { + if (cam == null) return "카메라 없음"; + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + var rt = new RenderTexture(WL816h_Probe.W, WL816h_Probe.H, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); + rt.antiAliasing = 1; rt.Create(); + var prevT = cam.targetTexture; var prevA = RenderTexture.active; + cam.targetTexture = rt; cam.Render(); + RenderTexture.active = rt; + var tex = new Texture2D(WL816h_Probe.W, WL816h_Probe.H, TextureFormat.RGB24, false); + tex.ReadPixels(new Rect(0, 0, WL816h_Probe.W, WL816h_Probe.H), 0, 0); tex.Apply(); + RenderTexture.active = prevA; cam.targetTexture = prevT; + System.IO.File.WriteAllBytes(path, tex.EncodeToPNG()); + string avg = ""; + if (wantAvg) + { + var px = tex.GetPixels32(); + long r = 0, g = 0, b = 0; + for (int i = 0; i < px.Length; i++) { r += px[i].r; g += px[i].g; b += px[i].b; } + avg = "#" + ((int)(r / px.Length)).ToString("X2") + ((int)(g / px.Length)).ToString("X2") + ((int)(b / px.Length)).ToString("X2"); + } + Object.DestroyImmediate(tex); rt.Release(); Object.DestroyImmediate(rt); + return avg + " → " + path; + } +} + +public class WL816h_ProbeRunner : MonoBehaviour +{ + static System.Text.StringBuilder sb; + void Start() { StartCoroutine(Co()); } + static void L(string s) { sb.AppendLine(s); } + + IEnumerator Co() + { + sb = new System.Text.StringBuilder(); + L("=== WL-816h 지금 상태 (PD 경로 = InGame 활성 + Level01 Additive) ==="); + L("t0 활성씬=" + SceneManager.GetActiveScene().name + " / " + WL816h_Util.SkyLine()); + + bool viaGame = false; + try { if (InGameInfo.Ins != null) { InGameInfo.Ins.Load_Map(900); viaGame = true; } } + catch (System.Exception e) { L("Load_Map 예외(=로그인 없이는 못 탄다): " + e.GetType().Name); } + if (!viaGame) + { + L("→ SceneInfo.Load_AddScene 과 같은 호출로 대체: LoadSceneAsync(\"Level01\", Additive)"); + var op = SceneManager.LoadSceneAsync("Level01", LoadSceneMode.Additive); + while (op != null && !op.isDone) yield return null; + } + for (int i = 0; i < 6; i++) yield return new WaitForSeconds(1f); + + L(""); + L("t+6s 활성씬=" + SceneManager.GetActiveScene().name + " sceneCount=" + SceneManager.sceneCount); + L("🔴 " + WL816h_Util.SkyLine()); + L("IslandLook: 렌더러 " + WL.Look.Farm.WLIslandLook.SwappedRenderers + " light=" + WL.Look.Farm.WLIslandLook.LightingApplied + + " refLook=" + WL.Look.Farm.WLIslandLook.ReferenceLookApplied); + L("RefLook : applied=" + WL.Look.Arena.WLReferenceLook.IsApplied + " log=" + WL.Look.Arena.WLReferenceLook.LastLog); + + // ── 🔴 카메라 · 포스트 — 아레나 무드가 「보이는 카메라」에 실제로 걸렸나 ── + L(""); + Camera live = null; + var cams = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); + foreach (var c in cams) + { + bool isLive = c.gameObject.activeInHierarchy && c.enabled && c.targetTexture == null; + var ac = c.GetComponent(); + L("CAM " + c.name + " scene=" + c.gameObject.scene.name + " live=" + isLive + " depth=" + c.depth + + " tag=" + c.tag + " clear=" + c.clearFlags + + " 🔴post=" + (ac == null ? "noACD" : ac.renderPostProcessing.ToString())); + if (isLive && (live == null || c.depth > live.depth)) live = c; + } + L("→ 보이는 카메라 = " + (live == null ? "없음" : live.name + "(" + live.gameObject.scene.name + ")")); + L("Camera.main = " + (Camera.main == null ? "없음" : Camera.main.name + "(" + Camera.main.gameObject.scene.name + ")")); + var vols = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); + foreach (var v in vols) L("Volume " + v.name + " global=" + v.isGlobal + " w=" + v.weight + " prio=" + v.priority + + " profile=" + (v.sharedProfile ? v.sharedProfile.name : "없음")); + + // ── 물 ── + L(""); + foreach (var r in Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None)) + { + if (r == null || r.gameObject.scene.name != "Level01") continue; + var m = r.sharedMaterial; + if (m == null || m.shader == null) continue; + if (!m.shader.name.ToLower().Contains("water") && !r.name.ToLower().Contains("water")) continue; + L("WATER obj=" + r.name + " layer=" + r.gameObject.layer + " mat=" + m.name + " shader=" + m.shader.name + + " queue=" + m.renderQueue + " bounds=" + r.bounds.size.ToString("F0") + " pos=" + r.transform.position.ToString("F1")); + } + + // ── URP 렌더러가 Opaque/Depth 텍스처를 주나(ToonWaterU 가 요구) ── + var urp = GraphicsSettings.defaultRenderPipeline as UniversalRenderPipelineAsset; + L("URP asset = " + (urp == null ? "없음" : urp.name + " opaqueTex=" + urp.supportsCameraOpaqueTexture + + " depthTex=" + urp.supportsCameraDepthTexture + " msaa=" + urp.msaaSampleCount)); + + // ── 캡처 ── + L(""); + L("하늘(섬) = " + WL816h_Util.Shot(WL816h_Util.SkyCam(), WL816h_Probe.Dir + "a_now_sky.png", true)); + L("섬 화면 = " + WL816h_Util.Shot(live, WL816h_Probe.Dir + "a_now_island.png", true)); + L("물 근접 = " + WL816h_Util.Shot(WaterCam(), WL816h_Probe.Dir + "a_now_water.png", true)); + + System.IO.File.WriteAllText("AgentScripts/WL816h_PROBE.txt", sb.ToString()); + Debug.Log("[816h probe 완료]\n" + sb); + } + + /// 물이 화면의 절반을 차지하는 카메라(섬 가장자리 바깥). + public static Camera WaterCam() + { + var go = GameObject.Find("~WL816hWaterCam"); + if (go == null) { go = new GameObject("~WL816hWaterCam"); go.hideFlags = HideFlags.DontSave; } + var c = go.GetComponent(); if (c == null) c = go.AddComponent(); + c.orthographic = false; c.fieldOfView = 45f; + c.nearClipPlane = 0.3f; c.farClipPlane = 2000f; + c.clearFlags = CameraClearFlags.Skybox; c.cullingMask = ~0; c.depth = -100f; + go.transform.position = new Vector3(-18f, 6f, -18f); + go.transform.rotation = Quaternion.Euler(12f, 45f, 0f); + if (go.GetComponent() == null) go.AddComponent(); + return c; + } +} diff --git a/AgentScripts/WL816h_Probe2.cs b/AgentScripts/WL816h_Probe2.cs new file mode 100644 index 000000000..d9f3b7010 --- /dev/null +++ b/AgentScripts/WL816h_Probe2.cs @@ -0,0 +1,143 @@ +// WL-816h — ② 「쨍한 파란 하늘」의 정체 + 섬에서 나간 뒤 하늘 오염 실측 (지금 상태 = 고치기 전) +using System.Collections; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Universal; +using UnityEngine.SceneManagement; + +public static class WL816h_Probe2 +{ + public const string Dir = "Screenshots_WL/WL816h/"; + public const int W = 1080, H = 1920; + + public static void Start() + { + var go = GameObject.Find("~WL816hProbe2"); + if (go != null) Object.DestroyImmediate(go); + go = new GameObject("~WL816hProbe2"); + go.AddComponent(); + Debug.Log("[816h p2] 러너 시작"); + } + + public static string SkyLine() + { + return "amb=" + RenderSettings.ambientMode + " light=" + F(RenderSettings.ambientLight) + + " skySrc=" + F(RenderSettings.ambientSkyColor) + " I=" + RenderSettings.ambientIntensity + + " skybox=" + (RenderSettings.skybox ? RenderSettings.skybox.name : "없음") + + " fog=" + RenderSettings.fog + " refl=" + RenderSettings.defaultReflectionMode + "/" + RenderSettings.reflectionIntensity + + " sun=" + (RenderSettings.sun ? RenderSettings.sun.name : "없음"); + } + public static string F(Color c) { return "(" + c.r.ToString("F3") + "," + c.g.ToString("F3") + "," + c.b.ToString("F3") + ")"; } + + public static Camera SkyCam() + { + var go = GameObject.Find("~WL816hSkyCam2"); + if (go == null) { go = new GameObject("~WL816hSkyCam2"); go.hideFlags = HideFlags.DontSave; } + var c = go.GetComponent(); if (c == null) c = go.AddComponent(); + c.orthographic = false; c.fieldOfView = 50f; c.nearClipPlane = 0.3f; c.farClipPlane = 2000f; + c.clearFlags = CameraClearFlags.Skybox; c.cullingMask = ~0; c.depth = -100f; + go.transform.position = new Vector3(0f, 40f, 0f); + go.transform.rotation = Quaternion.Euler(-16f, 30f, 0f); + if (go.GetComponent() == null) go.AddComponent(); + return c; + } + + public static string Shot(Camera cam, string path) + { + if (cam == null) return "카메라 없음"; + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + var rt = new RenderTexture(W, H, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); + rt.antiAliasing = 1; rt.Create(); + var prevT = cam.targetTexture; var prevA = RenderTexture.active; + cam.targetTexture = rt; cam.Render(); RenderTexture.active = rt; + var tex = new Texture2D(W, H, TextureFormat.RGB24, false); + tex.ReadPixels(new Rect(0, 0, W, H), 0, 0); tex.Apply(); + RenderTexture.active = prevA; cam.targetTexture = prevT; + System.IO.File.WriteAllBytes(path, tex.EncodeToPNG()); + // 위 10 % · 아래 10 % 평균 + string s = ""; + s += "상단10%=" + Band(tex, 0.90f, 1.00f) + " 하단10%=" + Band(tex, 0.00f, 0.10f); + Object.DestroyImmediate(tex); rt.Release(); Object.DestroyImmediate(rt); + return s + " → " + path; + } + + static string Band(Texture2D t, float y0, float y1) + { + int a = Mathf.RoundToInt(y0 * (t.height - 1)), b = Mathf.RoundToInt(y1 * (t.height - 1)); + long r = 0, g = 0, bl = 0; int n = 0; + var px = t.GetPixels32(); + for (int y = a; y < b; y += 4) + for (int x = 0; x < t.width; x += 4) { var c = px[y * t.width + x]; r += c.r; g += c.g; bl += c.b; n++; } + if (n == 0) return "-"; + return "#" + ((int)(r / n)).ToString("X2") + ((int)(g / n)).ToString("X2") + ((int)(bl / n)).ToString("X2"); + } +} + +public class WL816h_Probe2Runner : MonoBehaviour +{ + static System.Text.StringBuilder sb; + static void L(string s) { sb.AppendLine(s); } + void Start() { StartCoroutine(Co()); } + + IEnumerator Co() + { + sb = new System.Text.StringBuilder(); + L("=== ② 「파란 하늘」의 정체 + 섬 나간 뒤 오염 (고치기 전) ==="); + + Camera live = null; + foreach (var c in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) + { + if (!c.gameObject.activeInHierarchy || !c.enabled || c.targetTexture != null) continue; + if (live == null || c.depth > live.depth) live = c; + } + L("보이는 카메라 = " + (live == null ? "없음" : live.name + " fov=" + live.fieldOfView + + " euler=" + live.transform.eulerAngles.ToString("F0") + " pos=" + live.transform.position.ToString("F1"))); + + Renderer water = null; + foreach (var r in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) + if (r != null && r.gameObject.scene.name == "Level01" && r.name == "Water") water = r; + L("물 렌더러 = " + (water == null ? "없음" : water.name + " mat=" + water.sharedMaterial.name + + " shader=" + water.sharedMaterial.shader.name)); + + L("물 ON = " + WL816h_Probe2.Shot(live, WL816h_Probe2.Dir + "a1_water_on.png")); + if (water != null) water.enabled = false; + yield return null; + L("물 OFF = " + WL816h_Probe2.Shot(live, WL816h_Probe2.Dir + "a2_water_off.png")); + if (water != null) water.enabled = true; + yield return null; + + L(""); + L("섬 내리기 전 : " + WL816h_Probe2.SkyLine()); + var op = SceneManager.UnloadSceneAsync("Level01"); + while (op != null && !op.isDone) yield return null; + yield return new WaitForSeconds(1.5f); + L("섬 내린 뒤 : " + WL816h_Probe2.SkyLine()); + L("InGame 원본 : amb=Skybox light=(0.212,0.227,0.259) skybox=Default-Skybox fog=False refl=Skybox/1 sun=없음 (씬 파일 실측)"); + L("🔴 오염 = " + Diff()); + + var op2 = SceneManager.LoadSceneAsync("Map_C01", LoadSceneMode.Additive); + while (op2 != null && !op2.isDone) yield return null; + yield return new WaitForSeconds(1.5f); + L(""); + L("전투 맵(Map_C01) 올린 뒤 : " + WL816h_Probe2.SkyLine()); + L("🔴 오염 = " + Diff()); + L("전투맵 하늘 = " + WL816h_Probe2.Shot(WL816h_Probe2.SkyCam(), WL816h_Probe2.Dir + "f0_before_battle_sky.png")); + + System.IO.File.WriteAllText("AgentScripts/WL816h_PROBE2.txt", sb.ToString()); + Debug.Log("[816h p2 완료]\n" + sb); + } + + static string Diff() + { + string d = ""; + if (RenderSettings.ambientMode != AmbientMode.Skybox) d += "ambientMode=" + RenderSettings.ambientMode + "(원본 Skybox) · "; + var a = RenderSettings.ambientLight; + if (Mathf.Abs(a.r - 0.212f) > 0.004f || Mathf.Abs(a.g - 0.227f) > 0.004f || Mathf.Abs(a.b - 0.259f) > 0.004f) + d += "ambientLight=" + WL816h_Probe2.F(a) + "(원본 (0.212,0.227,0.259)) · "; + if (RenderSettings.fog) d += "fog on · "; + if (RenderSettings.sun != null) d += "sun=" + RenderSettings.sun.name + "(원본 없음) · "; + if (RenderSettings.skybox == null || RenderSettings.skybox.name != "Default-Skybox") + d += "skybox=" + (RenderSettings.skybox ? RenderSettings.skybox.name : "없음") + " · "; + return d.Length == 0 ? "없음(오염 0)" : d; + } +} diff --git a/AgentScripts/WL816h_Verify.cs b/AgentScripts/WL816h_Verify.cs new file mode 100644 index 000000000..f4a4444c0 --- /dev/null +++ b/AgentScripts/WL816h_Verify.cs @@ -0,0 +1,306 @@ +// WL-816h — ④ PD 경로에서 적용 확인 · 물 3단 비교 · 성능 전/후 · 🔴 맵별 하늘 격리 +// 전부 Play 중 실측. 🔴 Play 중에 에셋(SO·머티리얼)을 고치지 않는다(§9) — 렌더러만 바꾼다. +using System.Collections; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Universal; +using UnityEngine.SceneManagement; + +public static class WL816h_Verify +{ + public const string Dir = "Screenshots_WL/WL816h/"; + public const int W = 1080, H = 1920; + + public static void Open() + { + UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/Scenes/InGame.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); + Debug.Log("[816h] InGame 열림"); + } + + public static void Start() + { + var go = GameObject.Find("~WL816hVerify"); + if (go != null) Object.DestroyImmediate(go); + go = new GameObject("~WL816hVerify"); + go.AddComponent(); + Debug.Log("[816h] verify 러너 시작"); + } + + // ── 공용 ──────────────────────────────────────────────────────────── + public static string Sky() + { + return "amb=" + RenderSettings.ambientMode + " light=" + F(RenderSettings.ambientLight) + + " skySrc=" + F(RenderSettings.ambientSkyColor) + " I=" + RenderSettings.ambientIntensity + + " skybox=" + (RenderSettings.skybox ? RenderSettings.skybox.name : "없음") + + " fog=" + RenderSettings.fog + " refl=" + RenderSettings.defaultReflectionMode + "/" + RenderSettings.reflectionIntensity + + " sun=" + (RenderSettings.sun ? RenderSettings.sun.name : "없음"); + } + public static string F(Color c) { return "(" + c.r.ToString("F3") + "," + c.g.ToString("F3") + "," + c.b.ToString("F3") + ")"; } + + public static Camera Cam(string name, Vector3 pos, Vector3 euler, float fov, CameraClearFlags clear) + { + var go = GameObject.Find(name); + if (go == null) { go = new GameObject(name); go.hideFlags = HideFlags.DontSave; } + var c = go.GetComponent(); if (c == null) c = go.AddComponent(); + c.orthographic = false; c.fieldOfView = fov; c.nearClipPlane = 0.3f; c.farClipPlane = 2000f; + c.clearFlags = clear; c.cullingMask = ~0; c.depth = -100f; + go.transform.position = pos; go.transform.rotation = Quaternion.Euler(euler); + var ac = go.GetComponent(); + if (ac == null) ac = go.AddComponent(); + ac.renderPostProcessing = true; // 아레나 무드가 걸린 화면 그대로 + return c; + } + + /// 섬 가장자리 바깥 — 바다가 화면의 절반을 차지한다. + public static Camera WaterCam() + { return Cam("~WL816hWaterCam", new Vector3(-16f, 5.5f, -16f), new Vector3(10f, 45f, 0f), 45f, CameraClearFlags.Skybox); } + + /// 수평선이 화면에 들어오는 각도 — 하늘과 바다가 함께 보인다. + public static Camera HorizonCam() + { return Cam("~WL816hHorizonCam", new Vector3(-14f, 8f, -14f), new Vector3(4f, 45f, 0f), 55f, CameraClearFlags.Skybox); } + + public static Camera SkyCam() + { return Cam("~WL816hSkyCam3", new Vector3(0f, 40f, 0f), new Vector3(-16f, 30f, 0f), 50f, CameraClearFlags.Skybox); } + + public static Texture2D Grab(Camera cam) + { + var rt = new RenderTexture(W, H, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); + rt.antiAliasing = 1; rt.Create(); + var pT = cam.targetTexture; var pA = RenderTexture.active; + cam.targetTexture = rt; cam.Render(); RenderTexture.active = rt; + var tex = new Texture2D(W, H, TextureFormat.RGB24, false); + tex.ReadPixels(new Rect(0, 0, W, H), 0, 0); tex.Apply(); + RenderTexture.active = pA; cam.targetTexture = pT; + rt.Release(); Object.DestroyImmediate(rt); + return tex; + } + + public static string Shot(Camera cam, string path) + { + if (cam == null) return "카메라 없음"; + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + var tex = Grab(cam); + System.IO.File.WriteAllBytes(path, tex.EncodeToPNG()); + string s = "상10%=" + Band(tex, 0.90f, 1.00f) + " 중=" + Band(tex, 0.45f, 0.55f) + " 하10%=" + Band(tex, 0f, 0.10f); + Object.DestroyImmediate(tex); + return s + " → " + path; + } + + public static string Band(Texture2D t, float y0, float y1) + { + int a = Mathf.RoundToInt(y0 * (t.height - 1)), b = Mathf.RoundToInt(y1 * (t.height - 1)); + var px = t.GetPixels32(); + long r = 0, g = 0, bl = 0; int n = 0; + for (int y = a; y < b; y += 3) for (int x = 0; x < t.width; x += 3) + { var c = px[y * t.width + x]; r += c.r; g += c.g; bl += c.b; n++; } + if (n == 0) return "-"; + return "#" + ((int)(r / n)).ToString("X2") + ((int)(g / n)).ToString("X2") + ((int)(bl / n)).ToString("X2"); + } + + /// 같은 카메라로 60프레임 오프스크린 렌더 — ms/frame. + public static float Ms(Camera cam, int frames) + { + var rt = new RenderTexture(W, H, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); + rt.antiAliasing = 1; rt.Create(); + var pT = 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 = pT; rt.Release(); Object.DestroyImmediate(rt); + return (float)sw.Elapsed.TotalMilliseconds / frames; + } + + /// 좌/우 두 장을 한 장으로 (PD 판단용). + public static void Pair(string left, string right, string outPath) + { + var a = Load(left); var b = Load(right); + if (a == null || b == null) return; + int w = a.width + b.width, h = Mathf.Max(a.height, b.height); + var o = new Texture2D(w, h, TextureFormat.RGB24, false); + var fill = new Color32[w * h]; + for (int i = 0; i < fill.Length; i++) fill[i] = new Color32(24, 24, 28, 255); + o.SetPixels32(fill); + o.SetPixels32(0, 0, a.width, a.height, a.GetPixels32()); + o.SetPixels32(a.width, 0, b.width, b.height, b.GetPixels32()); + o.Apply(); + System.IO.File.WriteAllBytes(outPath, o.EncodeToPNG()); + Object.DestroyImmediate(o); Object.DestroyImmediate(a); Object.DestroyImmediate(b); + } + + /// 여러 장을 가로로 이어 붙인다(값 3단 비교용). + public static void Strip(string[] paths, string outPath) + { + var ts = new Texture2D[paths.Length]; + int w = 0, h = 0; + for (int i = 0; i < paths.Length; i++) + { ts[i] = Load(paths[i]); if (ts[i] == null) return; w += ts[i].width; h = Mathf.Max(h, ts[i].height); } + var o = new Texture2D(w, h, TextureFormat.RGB24, false); + var fill = new Color32[w * h]; + for (int i = 0; i < fill.Length; i++) fill[i] = new Color32(24, 24, 28, 255); + o.SetPixels32(fill); + int x = 0; + for (int i = 0; i < ts.Length; i++) { o.SetPixels32(x, 0, ts[i].width, ts[i].height, ts[i].GetPixels32()); x += ts[i].width; } + o.Apply(); + System.IO.File.WriteAllBytes(outPath, o.EncodeToPNG()); + Object.DestroyImmediate(o); + for (int i = 0; i < ts.Length; i++) Object.DestroyImmediate(ts[i]); + } + + static Texture2D Load(string p) + { + if (!System.IO.File.Exists(p)) return null; + var t = new Texture2D(2, 2, TextureFormat.RGB24, false); + t.LoadImage(System.IO.File.ReadAllBytes(p)); + return t; + } +} + +public class WL816h_VerifyRunner : MonoBehaviour +{ + static System.Text.StringBuilder sb; + static void L(string s) { sb.AppendLine(s); } + const string MatDir = "Assets/WL/Look/Farm/Materials/"; + + void Start() { StartCoroutine(Co()); } + + IEnumerator Co() + { + sb = new System.Text.StringBuilder(); + L("=== WL-816h 적용 확인 (PD 경로 = InGame 활성 + Level01 Additive) ==="); + + // 활성 씬(InGame)의 **손 안 댄** 하늘 = 나중에 「원래대로」 판정 기준 + string pristine = WL816h_Verify.Sky(); + L("0) InGame 원래 하늘 : " + pristine); + + bool viaGame = false; + try { if (InGameInfo.Ins != null) { InGameInfo.Ins.Load_Map(900); viaGame = true; } } + catch (System.Exception) { } + L(" Load_Map(900) = " + (viaGame ? "성공" : "🔴 로그인 없이는 못 탄다 → LoadSceneAsync(Level01, Additive) 로 대체(816f 와 같음)")); + if (!viaGame) + { + var op = SceneManager.LoadSceneAsync("Level01", LoadSceneMode.Additive); + while (op != null && !op.isDone) yield return null; + } + for (int i = 0; i < 7; i++) yield return new WaitForSeconds(1f); + + // ── 1) 훅이 돌았나 ──────────────────────────────────────────── + L(""); + L("1) 섬 진입 — 활성씬=" + SceneManager.GetActiveScene().name + " sceneCount=" + SceneManager.sceneCount); + L(" IslandLook 렌더러 " + WL.Look.Farm.WLIslandLook.SwappedRenderers + + " · 물 교체 " + WL.Look.Farm.WLIslandLook.WaterSwapped + + " · refLook=" + WL.Look.Farm.WLIslandLook.ReferenceLookApplied); + L(" 🔴 SkyScope held=" + WL.Look.Farm.WLSkyScope.Held + " by=" + WL.Look.Farm.WLSkyScope.HeldBy + + " acquires=" + WL.Look.Farm.WLSkyScope.Acquires + " restores=" + WL.Look.Farm.WLSkyScope.Restores); + L(" 섬 하늘 = " + WL816h_Verify.Sky()); + + Renderer water = null; + foreach (var r in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) + if (r != null && r.gameObject.scene.name == "Level01" && r.name == "Water") water = r; + L(" 물 렌더러 = " + (water == null ? "🔴 없음" : water.name + " mat=" + water.sharedMaterial.name + + " shader=" + water.sharedMaterial.shader.name + " queue=" + water.sharedMaterial.renderQueue)); + + Camera live = null; + foreach (var c in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) + { + if (!c.gameObject.activeInHierarchy || !c.enabled || c.targetTexture != null) continue; + if (c.name.StartsWith("~WL816h")) continue; + if (live == null || c.depth > live.depth) live = c; + } + L(" 보이는 카메라 = " + (live == null ? "없음" : live.name + "(" + live.gameObject.scene.name + ")")); + + // ── 2) 물 3단 비교 + 성능 (같은 카메라 · 같은 프레임 구간) ──── + L(""); + L("2) 물 값 3단 비교 + 성능 (같은 카메라 1080×1920 · 60프레임 오프스크린)"); + string[] names = { null, "Farm_Water_WL_A", "Farm_Water_WL_B", "Farm_Water_WL_C" }; + string[] labels = { "지금(FI SimpleWater)", "A=WL_Water_Ocean 원본", "B=데모 톤", "C=차분" }; + string[] liveShots = new string[4]; + string[] horizonShots = new string[4]; + var fiMat = water != null ? water.sharedMaterial : null; // 지금 걸려 있는 것(= WL 워터 B) + + // 「지금」 = FarmingIsland 물(우리 복사본 Farm_SimpleWater_Demo) + Material now = UnityEditor.AssetDatabase.LoadAssetAtPath(MatDir + "Farm_SimpleWater_Demo.mat"); + + for (int i = 0; i < names.Length; i++) + { + Material m = names[i] == null ? now : UnityEditor.AssetDatabase.LoadAssetAtPath(MatDir + names[i] + ".mat"); + if (m == null) { L(" 🔴 머티리얼 없음 " + (names[i] ?? "Farm_SimpleWater_Demo")); continue; } + if (water != null) water.sharedMaterial = m; + yield return null; yield return null; + yield return new WaitForSeconds(0.4f); + + string tag = names[i] == null ? "now" : names[i].Substring(names[i].Length - 1).ToLower(); + liveShots[i] = WL816h_Verify.Dir + "w_" + i + "_" + tag + "_live.png"; + horizonShots[i] = WL816h_Verify.Dir + "w_" + i + "_" + tag + "_horizon.png"; + string s1 = WL816h_Verify.Shot(live, liveShots[i]); + string s2 = WL816h_Verify.Shot(WL816h_Verify.HorizonCam(), horizonShots[i]); + float ms = WL816h_Verify.Ms(live, 60); + L(" [" + labels[i] + "] " + ms.ToString("F3") + " ms/frame"); + L(" 게임화면 " + s1); + L(" 수평선 " + s2); + } + + // 채택값으로 되돌린다(SO 가 가리키는 것) + var cfg = WL.Look.Farm.WLIslandLookSettings.Instance; + if (water != null && cfg != null && cfg.waterTo != null) water.sharedMaterial = cfg.waterTo; + yield return null; + L(" → 채택 = " + (cfg != null && cfg.waterTo != null ? cfg.waterTo.name : "?")); + + WL816h_Verify.Strip(liveShots, WL816h_Verify.Dir + "e_water_steps.png"); + WL816h_Verify.Strip(horizonShots, WL816h_Verify.Dir + "e_water_steps_horizon.png"); + L(" 3단 비교 한 장 = " + WL816h_Verify.Dir + "e_water_steps.png · e_water_steps_horizon.png"); + + // ── 3) 적용 후 대표 캡처 ────────────────────────────────────── + L(""); + L("3) 적용 후"); + L(" 섬 게임화면 = " + WL816h_Verify.Shot(live, WL816h_Verify.Dir + "b_after_island.png")); + L(" 수평선 = " + WL816h_Verify.Shot(WL816h_Verify.HorizonCam(), WL816h_Verify.Dir + "b_after_horizon.png")); + L(" 물 근접 = " + WL816h_Verify.Shot(WL816h_Verify.WaterCam(), WL816h_Verify.Dir + "b_after_water.png")); + L(" 하늘만 = " + WL816h_Verify.Shot(WL816h_Verify.SkyCam(), WL816h_Verify.Dir + "b_after_sky.png")); + WL816h_Verify.Pair(WL816h_Verify.Dir + "a_now_island.png", WL816h_Verify.Dir + "b_after_island.png", + WL816h_Verify.Dir + "c_side_by_side.png"); + L(" 좌우 비교 = " + WL816h_Verify.Dir + "c_side_by_side.png (좌 지금 / 우 적용 후)"); + + // ── 4) 🔴 맵별 하늘 격리 ────────────────────────────────────── + L(""); + L("4) 🔴 맵별 하늘 격리 — 섬 → 던전 → 전투 맵"); + L(" [섬] " + WL816h_Verify.Sky()); + string islandSky = WL816h_Verify.Shot(WL816h_Verify.SkyCam(), WL816h_Verify.Dir + "f1_island_sky.png"); + L(" [섬] 하늘 = " + islandSky); + + // 섬 → 던전 (같은 하늘이어야 한다) + var u1 = SceneManager.UnloadSceneAsync("Level01"); + while (u1 != null && !u1.isDone) yield return null; + var d1 = SceneManager.LoadSceneAsync("WL_Dungeon01", LoadSceneMode.Additive); + while (d1 != null && !d1.isDone) yield return null; + yield return new WaitForSeconds(2f); + L(" [던전] SkyScope held=" + WL.Look.Farm.WLSkyScope.Held + " by=" + WL.Look.Farm.WLSkyScope.HeldBy + + " acquires=" + WL.Look.Farm.WLSkyScope.Acquires + " restores=" + WL.Look.Farm.WLSkyScope.Restores); + L(" [던전] " + WL816h_Verify.Sky()); + L(" [던전] 하늘 = " + WL816h_Verify.Shot(WL816h_Verify.SkyCam(), WL816h_Verify.Dir + "f2_dungeon_sky.png")); + + // 던전 → 전투 맵 (원래대로여야 한다) + var u2 = SceneManager.UnloadSceneAsync("WL_Dungeon01"); + while (u2 != null && !u2.isDone) yield return null; + var b1 = SceneManager.LoadSceneAsync("Map_C01", LoadSceneMode.Additive); + while (b1 != null && !b1.isDone) yield return null; + yield return new WaitForSeconds(2f); + L(" [전투] SkyScope held=" + WL.Look.Farm.WLSkyScope.Held + + " acquires=" + WL.Look.Farm.WLSkyScope.Acquires + " restores=" + WL.Look.Farm.WLSkyScope.Restores); + string after = WL816h_Verify.Sky(); + L(" [전투] " + after); + L(" [전투] 하늘 = " + WL816h_Verify.Shot(WL816h_Verify.SkyCam(), WL816h_Verify.Dir + "f3_battle_sky.png")); + L(" 🔴 원래대로인가 = " + (after == pristine ? "예 — 글자 하나까지 같다(오염 0)" : "🔴 아니오\n 전 : " + pristine + "\n 후 : " + after)); + + WL816h_Verify.Strip(new string[] { + WL816h_Verify.Dir + "f1_island_sky.png", + WL816h_Verify.Dir + "f2_dungeon_sky.png", + WL816h_Verify.Dir + "f3_battle_sky.png" }, + WL816h_Verify.Dir + "f_sky_isolation.png"); + L(" 한 장 = " + WL816h_Verify.Dir + "f_sky_isolation.png (좌 섬 / 중 던전 / 우 전투 맵)"); + + System.IO.File.WriteAllText("AgentScripts/WL816h_VERIFY.txt", sb.ToString()); + Debug.Log("[816h verify 완료]\n" + sb); + } +} diff --git a/Assets/WL/Look/Farm/Materials/Farm_Water_WL_A.mat b/Assets/WL/Look/Farm/Materials/Farm_Water_WL_A.mat new file mode 100644 index 000000000..0f4cc4af5 --- /dev/null +++ b/Assets/WL/Look/Farm/Materials/Farm_Water_WL_A.mat @@ -0,0 +1,101 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Farm_Water_WL_A + m_Shader: {fileID: -6465566751694194690, guid: 85b142f88e4bec1488c6b275a0e5dc57, + type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: + - MOTIONVECTORS + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - Texture2D_086cedcb1d864765840f9996e4b0002d: + m_Texture: {fileID: 2800000, guid: 945a5174b1a6da34ea1028ed2feeb1e9, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - Texture2D_1d2b8f4484d74423a27218f18b962ade: + m_Texture: {fileID: 2800000, guid: d48aae5302069a94f9410b8d6b76fed1, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - Texture2D_4094dc7f19e94579ac3bbc9c811f51a4: + m_Texture: {fileID: 2800000, guid: 62100fbe73f198245b71c0a6eeeb94f6, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - Texture2D_685ca00e8e7c4cb2b45a9e11981bb64a: + m_Texture: {fileID: 2800000, guid: 5088aa2234cf5b24cad13def77f7da5f, type: 3} + 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: + - Vector1_07238257334b4a149e675e2451801030: 7.83 + - Vector1_101dc4546b684d40bc2ff2fc59ab43d5: 5 + - Vector1_11671d7a4cdb4d059a6c69e39f3f7b3f: 50 + - Vector1_21ae4fce4585460386edec89c9895501: 0 + - Vector1_2b4963cef1de4859983ffcbae94c6f60: 0 + - Vector1_3089b6a325a44c1686907af4ee3b0776: 0.6 + - Vector1_34f757bec6b8422b9aef3b9d97117a6e: 4 + - Vector1_566e288f42864b8e9432d81fbdb83f38: 3.36 + - Vector1_68f0c21b684f4b7c9ebef8789113b107: 2 + - Vector1_7090c8ca8ea84e2d85858ae4beb5be38: 1 + - Vector1_85ea9246e72a44ed9e0482861767c826: 0.03 + - Vector1_8e22a98f75c94b218c49e6c4805e799d: 0.01 + - Vector1_99cc56b5c6fd474082f07f00a6df94f9: 0.0394 + - Vector1_ad7e0fcd37f44d68b68de6f06bb43b96: 0.01 + - Vector1_c6f2603b30534ec8b0039379573789e3: 1 + - Vector1_d813b569a4d541d1b2ac4ea9bc66a3b9: 0.2 + - Vector1_dd1e17693dc14b10b91828bdfd5adaa8: 0 + - Vector1_ff7a2780aaa94ddd86e156c4707e39a2: 0.64 + - _QueueControl: 0 + - _QueueOffset: 0 + - _XRMotionVectorsPass: 1 + m_Colors: + - Color_1ec6cccf8ead489ebb917674f7e8b3b1: {r: 1, g: 1, b: 1, a: 1} + - Color_2323d69962e04c499c5d5f0925432b55: {r: 1, g: 0.93773586, b: 0.93773586, + a: 1} + - Color_9af4ad59934f40d1ae6565e6ab22c45e: {r: 0, g: 0.8823529, b: 1, a: 1} + - Color_ca031ae309ff42bdb95f777248fb961d: {r: 0, g: 0.11372443, b: 0.41509432, + a: 1} + - Color_e1f155248d144786a62fc1585ca21e9a: {r: 1, g: 1, b: 1, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 +--- !u!114 &7840779067325084833 +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 diff --git a/Assets/WL/Look/Farm/Materials/Farm_Water_WL_A.mat.meta b/Assets/WL/Look/Farm/Materials/Farm_Water_WL_A.mat.meta new file mode 100644 index 000000000..982f73b63 --- /dev/null +++ b/Assets/WL/Look/Farm/Materials/Farm_Water_WL_A.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 675e37d255df5a44386db0f4cf281a32 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/WL/Look/Farm/Materials/Farm_Water_WL_B.mat b/Assets/WL/Look/Farm/Materials/Farm_Water_WL_B.mat new file mode 100644 index 000000000..cde44ca4a --- /dev/null +++ b/Assets/WL/Look/Farm/Materials/Farm_Water_WL_B.mat @@ -0,0 +1,99 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Farm_Water_WL_B + m_Shader: {fileID: -6465566751694194690, guid: 85b142f88e4bec1488c6b275a0e5dc57, + type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: + - MOTIONVECTORS + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - Texture2D_086cedcb1d864765840f9996e4b0002d: + m_Texture: {fileID: 2800000, guid: 945a5174b1a6da34ea1028ed2feeb1e9, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - Texture2D_1d2b8f4484d74423a27218f18b962ade: + m_Texture: {fileID: 2800000, guid: d48aae5302069a94f9410b8d6b76fed1, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - Texture2D_4094dc7f19e94579ac3bbc9c811f51a4: + m_Texture: {fileID: 2800000, guid: 62100fbe73f198245b71c0a6eeeb94f6, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - Texture2D_685ca00e8e7c4cb2b45a9e11981bb64a: + m_Texture: {fileID: 2800000, guid: 5088aa2234cf5b24cad13def77f7da5f, type: 3} + 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: + - Vector1_07238257334b4a149e675e2451801030: 7.83 + - Vector1_101dc4546b684d40bc2ff2fc59ab43d5: 3 + - Vector1_11671d7a4cdb4d059a6c69e39f3f7b3f: 36 + - Vector1_21ae4fce4585460386edec89c9895501: 0 + - Vector1_2b4963cef1de4859983ffcbae94c6f60: 0 + - Vector1_3089b6a325a44c1686907af4ee3b0776: 0.6 + - Vector1_34f757bec6b8422b9aef3b9d97117a6e: 4 + - Vector1_566e288f42864b8e9432d81fbdb83f38: 5 + - Vector1_68f0c21b684f4b7c9ebef8789113b107: 2 + - Vector1_7090c8ca8ea84e2d85858ae4beb5be38: 1 + - Vector1_85ea9246e72a44ed9e0482861767c826: 0.03 + - Vector1_8e22a98f75c94b218c49e6c4805e799d: 0.01 + - Vector1_99cc56b5c6fd474082f07f00a6df94f9: 0.0394 + - Vector1_ad7e0fcd37f44d68b68de6f06bb43b96: 0.01 + - Vector1_c6f2603b30534ec8b0039379573789e3: 1 + - Vector1_d813b569a4d541d1b2ac4ea9bc66a3b9: 0.12 + - Vector1_dd1e17693dc14b10b91828bdfd5adaa8: 0 + - Vector1_ff7a2780aaa94ddd86e156c4707e39a2: 0.5 + - _QueueControl: 0 + - _QueueOffset: 0 + - _XRMotionVectorsPass: 1 + m_Colors: + - Color_1ec6cccf8ead489ebb917674f7e8b3b1: {r: 1, g: 1, b: 1, a: 1} + - Color_2323d69962e04c499c5d5f0925432b55: {r: 0.8, g: 0.9, b: 0.96, a: 1} + - Color_9af4ad59934f40d1ae6565e6ab22c45e: {r: 0.298, g: 0.8, b: 1, a: 1} + - Color_ca031ae309ff42bdb95f777248fb961d: {r: 0.133, g: 0.518, b: 0.745, a: 1} + - Color_e1f155248d144786a62fc1585ca21e9a: {r: 1, g: 1, b: 1, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 +--- !u!114 &7840779067325084833 +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 diff --git a/Assets/WL/Look/Farm/Materials/Farm_Water_WL_B.mat.meta b/Assets/WL/Look/Farm/Materials/Farm_Water_WL_B.mat.meta new file mode 100644 index 000000000..5b0f4996f --- /dev/null +++ b/Assets/WL/Look/Farm/Materials/Farm_Water_WL_B.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4ccbf1a15347b64459d3a1fa63cc303d +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/WL/Look/Farm/Materials/Farm_Water_WL_C.mat b/Assets/WL/Look/Farm/Materials/Farm_Water_WL_C.mat new file mode 100644 index 000000000..08442ab02 --- /dev/null +++ b/Assets/WL/Look/Farm/Materials/Farm_Water_WL_C.mat @@ -0,0 +1,99 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Farm_Water_WL_C + m_Shader: {fileID: -6465566751694194690, guid: 85b142f88e4bec1488c6b275a0e5dc57, + type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: + - MOTIONVECTORS + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - Texture2D_086cedcb1d864765840f9996e4b0002d: + m_Texture: {fileID: 2800000, guid: 945a5174b1a6da34ea1028ed2feeb1e9, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - Texture2D_1d2b8f4484d74423a27218f18b962ade: + m_Texture: {fileID: 2800000, guid: d48aae5302069a94f9410b8d6b76fed1, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - Texture2D_4094dc7f19e94579ac3bbc9c811f51a4: + m_Texture: {fileID: 2800000, guid: 62100fbe73f198245b71c0a6eeeb94f6, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - Texture2D_685ca00e8e7c4cb2b45a9e11981bb64a: + m_Texture: {fileID: 2800000, guid: 5088aa2234cf5b24cad13def77f7da5f, type: 3} + 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: + - Vector1_07238257334b4a149e675e2451801030: 7.83 + - Vector1_101dc4546b684d40bc2ff2fc59ab43d5: 2.2 + - Vector1_11671d7a4cdb4d059a6c69e39f3f7b3f: 28 + - Vector1_21ae4fce4585460386edec89c9895501: 0 + - Vector1_2b4963cef1de4859983ffcbae94c6f60: 0 + - Vector1_3089b6a325a44c1686907af4ee3b0776: 0.4 + - Vector1_34f757bec6b8422b9aef3b9d97117a6e: 4 + - Vector1_566e288f42864b8e9432d81fbdb83f38: 7 + - Vector1_68f0c21b684f4b7c9ebef8789113b107: 2 + - Vector1_7090c8ca8ea84e2d85858ae4beb5be38: 1 + - Vector1_85ea9246e72a44ed9e0482861767c826: 0.03 + - Vector1_8e22a98f75c94b218c49e6c4805e799d: 0.01 + - Vector1_99cc56b5c6fd474082f07f00a6df94f9: 0.0394 + - Vector1_ad7e0fcd37f44d68b68de6f06bb43b96: 0.01 + - Vector1_c6f2603b30534ec8b0039379573789e3: 1 + - Vector1_d813b569a4d541d1b2ac4ea9bc66a3b9: 0.09 + - Vector1_dd1e17693dc14b10b91828bdfd5adaa8: 0 + - Vector1_ff7a2780aaa94ddd86e156c4707e39a2: 0.4 + - _QueueControl: 0 + - _QueueOffset: 0 + - _XRMotionVectorsPass: 1 + m_Colors: + - Color_1ec6cccf8ead489ebb917674f7e8b3b1: {r: 1, g: 1, b: 1, a: 1} + - Color_2323d69962e04c499c5d5f0925432b55: {r: 0.78, g: 0.86, b: 0.92, a: 1} + - Color_9af4ad59934f40d1ae6565e6ab22c45e: {r: 0.36, g: 0.69, b: 0.82, a: 1} + - Color_ca031ae309ff42bdb95f777248fb961d: {r: 0.17, g: 0.42, b: 0.57, a: 1} + - Color_e1f155248d144786a62fc1585ca21e9a: {r: 1, g: 1, b: 1, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 +--- !u!114 &7840779067325084833 +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 diff --git a/Assets/WL/Look/Farm/Materials/Farm_Water_WL_C.mat.meta b/Assets/WL/Look/Farm/Materials/Farm_Water_WL_C.mat.meta new file mode 100644 index 000000000..7f51d6048 --- /dev/null +++ b/Assets/WL/Look/Farm/Materials/Farm_Water_WL_C.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c4a590309ecaa044da75b96fa1e150a3 +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 d7a01036d..55f4786a1 100644 --- a/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset +++ b/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset @@ -55,6 +55,15 @@ MonoBehaviour: - 0.5 - 2 - 5 + skyScopeEnabled: 1 + skyScenes: + - Level01 + - WL_FarmLook + - WL_Dungeon01 + - WL_Dungeon02 + waterMode: 1 + waterFrom: {fileID: 2100000, guid: ce0f8fd131f36964dbc918b8c63f838b, type: 2} + waterTo: {fileID: 2100000, guid: 4ccbf1a15347b64459d3a1fa63cc303d, type: 2} grassEnabled: 1 scatter: - enabled_: 1 diff --git a/Assets/WL/Look/Farm/WLIslandLook.cs b/Assets/WL/Look/Farm/WLIslandLook.cs index 53f69da62..04c8a228b 100644 --- a/Assets/WL/Look/Farm/WLIslandLook.cs +++ b/Assets/WL/Look/Farm/WLIslandLook.cs @@ -268,8 +268,30 @@ namespace WL.Look.Farm } } + /// + /// 🔴 816h — 섬 둘레의 바다(`Water` · 1000×1000 평면)를 **WL 워터 셰이더**로. + /// `Assets/FarmingIsland/**` 는 0줄 — 렌더러의 `sharedMaterials` 만 런타임에 바꾼다. + /// `waterMode = 0` 이면 이 판정 자체를 건너뛰어 지금(FarmingIsland 물)으로 100 % 돌아간다. + /// + static bool IsWater(WLIslandLookSettings cfg, Material src) + { + if (src == null) return false; + if (cfg.waterFrom != null && src == cfg.waterFrom) return true; + if (src.shader != null && src.shader.name == "Shader Graphs/Simple Water") return true; + // 이미 우리 복사본(Farm_SimpleWater_Demo)으로 바뀐 뒤 다시 훑는 경우 + return src.name.StartsWith("Farm_SimpleWater"); + } + + public static int WaterSwapped; + static Material Lookup(WLIslandLookSettings cfg, Material src) { + if (cfg.waterMode != 0 && cfg.waterTo != null && IsWater(cfg, src)) + { + if (src != cfg.waterTo) WaterSwapped++; + return cfg.waterTo; + } + for (int i = 0; i < cfg.materialRemap.Length; i++) { var e = cfg.materialRemap[i]; @@ -286,15 +308,23 @@ namespace WL.Look.Farm // ───────────────────────────────────────────────────────────────── public static void ApplyLighting(WLIslandLookSettings cfg, Scene scene) { - RenderSettings.ambientMode = AmbientMode.Skybox; - RenderSettings.ambientSkyColor = cfg.ambientSky; - RenderSettings.ambientEquatorColor = cfg.ambientEquator; - RenderSettings.ambientGroundColor = cfg.ambientGround; - RenderSettings.ambientLight = cfg.ambientSky; - RenderSettings.ambientIntensity = cfg.ambientIntensity; - if (cfg.fogOff != 0) RenderSettings.fog = false; - if (cfg.skybox != null) RenderSettings.skybox = cfg.skybox; - RenderSettings.defaultReflectionMode = DefaultReflectionMode.Skybox; + // 🔴 816h — 하늘(앰비언트·스카이박스·안개·반사)은 **활성 씬(InGame)** 소유라 + // 여기서 그냥 쓰면 섬에서 나간 뒤 전투 맵·로비까지 그대로 남는다. + // 그래서 `WLSkyScope` 가 **스냅샷 → 적용 → 나갈 때 원복** 을 맡는다. + // (값은 그대로 이 SO 것 = 아레나/데모 값. 새 값 0) + if (cfg.skyScopeEnabled != 0) WLSkyScope.Acquire(cfg, scene.name); + else + { + RenderSettings.ambientMode = AmbientMode.Skybox; + RenderSettings.ambientSkyColor = cfg.ambientSky; + RenderSettings.ambientEquatorColor = cfg.ambientEquator; + RenderSettings.ambientGroundColor = cfg.ambientGround; + RenderSettings.ambientLight = cfg.ambientSky; + RenderSettings.ambientIntensity = cfg.ambientIntensity; + if (cfg.fogOff != 0) RenderSettings.fog = false; + if (cfg.skybox != null) RenderSettings.skybox = cfg.skybox; + RenderSettings.defaultReflectionMode = DefaultReflectionMode.Skybox; + } var lights = Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); for (int i = 0; i < lights.Length; i++) diff --git a/Assets/WL/Look/Farm/WLIslandLookSettings.cs b/Assets/WL/Look/Farm/WLIslandLookSettings.cs index b4bf79ab4..9171060c8 100644 --- a/Assets/WL/Look/Farm/WLIslandLookSettings.cs +++ b/Assets/WL/Look/Farm/WLIslandLookSettings.cs @@ -171,6 +171,32 @@ namespace WL.Look.Farm [Tooltip("런타임에 뒤늦게 생기는 오브젝트(상인·작물·아이템)까지 잡기 위해 다시 훑는 시각(초).")] public float[] rescanAtSeconds = new float[] { 0.5f, 2f, 5f }; + // ───────────────────────────────── §816h-1 하늘 범위 + [Header("§816h — 하늘(RenderSettings)은 「활성 씬」 소유다")] + [Tooltip("🔴 816f/816h 실측 — 섬·던전·전투 맵은 전부 `InGame` 위에 **Additive** 로 붙는다. " + + "그래서 앰비언트·스카이박스·안개는 붙는 씬이 아니라 **활성 씬(InGame)** 것이 쓰인다. " + + "1 이면 아래 `skyScenes` 에 들어간 맵에 들어갈 때만 활성 씬의 하늘을 **스냅샷 뜨고** 우리 값으로 바꾸고, " + + "그 맵에서 나가면 **스냅샷 그대로 되돌린다**(= 다른 맵 오염 0).")] + public int skyScopeEnabled = 1; + + [Tooltip("하늘을 통일할 맵. 섬과 던전이 같은 하늘을 쓰게 한다. 전투 맵은 여기 넣지 않는다(원래대로).")] + public string[] skyScenes = new string[] { "Level01", "WL_FarmLook", "WL_Dungeon01", "WL_Dungeon02" }; + + [Tooltip("맵을 바꿀 때 `SceneInfo` 는 내리기와 올리기를 같은 프레임에 시작한다(실측). " + + "이만큼 기다린 뒤에도 범위 맵이 하나도 없을 때만 하늘을 반납한다 — 섬↔던전 이동에서 한 번 튀는 것을 막는다.")] + public float skyReleaseDelaySeconds = 1.5f; + + // ───────────────────────────────── §816h-2 물 + [Header("§816h — 섬의 물을 WL 워터 셰이더로 (PD 지시)")] + [Tooltip("0 = 지금(FarmingIsland 물 · 100 % 복귀) · 1 = WL 워터(`ToonWaterU`).")] + public int waterMode = 1; + + [Tooltip("교체 대상 판별 — FarmingIsland 원본 물 머티리얼(참조만 · 0줄 수정).")] + public Material waterFrom; + + [Tooltip("WL 워터 복사본(`Assets/WL/Materials/WL_Water_Ocean.mat` 기준). 비우면 교체하지 않는다.")] + public Material waterTo; + // ───────────────────────────────── §1 풀밭 [Header("§1 — 섬 타일 위 풀밭 (데모와 같은 인스턴싱)")] [Tooltip("0 이면 풀을 깔지 않는다.")] diff --git a/Assets/WL/Look/Farm/WLSkyScope.cs b/Assets/WL/Look/Farm/WLSkyScope.cs new file mode 100644 index 000000000..eabb16837 --- /dev/null +++ b/Assets/WL/Look/Farm/WLSkyScope.cs @@ -0,0 +1,271 @@ +// ───────────────────────────────────────────────────────────────────────────── +// WLSkyScope.cs — 하늘(RenderSettings)을 「들어간 맵 동안만」 빌려 쓰고 나갈 때 돌려준다 +// (WL-816h · #816) +// +// ■ 왜 필요한가 — 🔴 실측(816f · 816h) +// `RenderSettings`(앰비언트·스카이박스·안개·반사·sun)는 **씬마다 하나가 아니라 +// 「활성 씬」 하나가 소유**한다. 이 게임은 `InGame` 을 활성으로 두고 섬·던전·전투 맵을 +// 전부 **Additive** 로 붙인다(`SceneInfo.Load_AddScene` 실측). 그래서 +// · 섬 씬(`Level01`)이 제 씬에 적어 둔 하늘(FarmingIsland Skybox)은 **절대 안 쓰이고** +// · 누가 `RenderSettings` 에 쓰면 그것은 **모든 맵에 남는다**(전투 맵·로비까지 오염). +// 816e/816f 의 `WLIslandLook.ApplyLighting` 은 값을 **쓰기만** 하고 되돌리지 않았다. +// 지금은 쓰는 값이 마침 `InGame` 원본과 같아서 오염이 안 보였을 뿐이다(816h 실측 = 차이 0). +// +// ■ 그래서 이렇게 고친다 +// `skyScenes`(섬·던전)에 **처음 들어갈 때 활성 씬의 하늘을 통째로 스냅샷**해 두고 +// 우리 값(= 아레나/데모와 같은 값 · SO 재사용)으로 바꾼다. +// 그 맵들이 **하나도 안 남았을 때** 스냅샷을 **글자 그대로** 되돌린다. +// → 섬·던전은 같은 하늘 · 전투 맵·로비는 원래대로. +// +// ■ C8 되돌리기 — `WLIslandLookSettings.skyScopeEnabled = 0` 이면 아무것도 하지 않는다. +// 씬·에셋 파일을 한 글자도 쓰지 않는다(전부 런타임 메모리). +// +// 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다(ErrorLogHookManager 팝업). +// ───────────────────────────────────────────────────────────────────────────── + +using System.Collections; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.SceneManagement; + +namespace WL.Look.Farm +{ + public static class WLSkyScope + { + public const string RunnerName = "~WLSkyScopeRunner"; + + // ── 진단(프로브가 읽는다) ──────────────────────────────────────── + public static bool Held; + public static int Acquires, Applies, Restores; + public static string LastLog = ""; + public static string HeldBy = ""; + + struct Snap + { + public bool valid; + public string ownerScene; + public AmbientMode mode; + public Color light, sky, equator, ground; + public float intensity; + public Material skybox; + public bool fog; public Color fogColor; public FogMode fogMode; + public float fogDensity, fogStart, fogEnd; + public DefaultReflectionMode reflMode; public float reflIntensity; + public Light sun; + } + + static Snap s_snap; + static int s_snapHandle; // 스냅샷을 뜬 활성 씬 + static bool s_installed; + static WLSkyScopeRunner s_runner; + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] + public static void Install() + { + if (s_installed) return; + var cfg = WLIslandLookSettings.Instance; + if (cfg == null || cfg.enabled_ == 0 || cfg.skyScopeEnabled == 0) return; + s_installed = true; + + SceneManager.sceneLoaded += OnSceneLoaded; + SceneManager.sceneUnloaded += OnSceneUnloaded; + SceneManager.activeSceneChanged += OnActiveSceneChanged; + + // 🔴 아직 아무 맵도 안 붙은 지금이 「활성 씬의 원래 하늘」이다 — 여기서 한 번만 뜬다. + CaptureIfNeeded(); + + for (int i = 0; i < SceneManager.sceneCount; i++) + { + var sc = SceneManager.GetSceneAt(i); + if (sc.isLoaded && IsScoped(cfg, sc.name)) Acquire(cfg, sc.name); + } + } + + static void OnActiveSceneChanged(Scene from, Scene to) + { + // 활성 씬이 바뀌면 하늘의 주인이 바뀐 것 — 새 주인의 원래 값을 다시 뜬다. + s_snap.valid = false; + Held = false; HeldBy = ""; + CaptureIfNeeded(); + } + + public static bool IsScoped(WLIslandLookSettings cfg, string sceneName) + { + if (cfg == null || cfg.skyScenes == null) return false; + for (int i = 0; i < cfg.skyScenes.Length; i++) + if (cfg.skyScenes[i] == sceneName) return true; + return false; + } + + static void OnSceneLoaded(Scene scene, LoadSceneMode mode) + { + var cfg = WLIslandLookSettings.Instance; + if (cfg == null || cfg.enabled_ == 0 || cfg.skyScopeEnabled == 0) return; + if (!IsScoped(cfg, scene.name)) return; + Acquire(cfg, scene.name); + } + + static void OnSceneUnloaded(Scene scene) + { + var cfg = WLIslandLookSettings.Instance; + if (cfg == null || cfg.skyScopeEnabled == 0) return; + if (!Held) return; + // 🔴 맵을 바꿀 때 `SceneInfo` 는 **같은 프레임에** 내리기와 올리기를 함께 시작한다. + // 그래서 바로 되돌리면 「섬 → 던전」 에서 잘못 되돌아간다. 두 프레임 기다렸다 + // **그때도 범위 맵이 하나도 없을 때만** 되돌린다. + EnsureRunner(); + if (s_runner != null) s_runner.StartCoroutine(Co_MaybeRelease(cfg)); + else ReleaseIfEmpty(cfg); + } + + static IEnumerator Co_MaybeRelease(WLIslandLookSettings cfg) + { + yield return null; + yield return null; + // 🔴 섬 → 던전처럼 범위 안에서 맵만 바뀔 때는 새 맵이 다 올라올 때까지 기다린다. + // (안 기다리면 반납했다가 다시 빌리면서 화면이 한 번 튄다 — 816h 실측) + float wait = cfg != null ? cfg.skyReleaseDelaySeconds : 1.5f; + if (wait > 0f) yield return new WaitForSeconds(wait); + ReleaseIfEmpty(cfg); + } + + static void ReleaseIfEmpty(WLIslandLookSettings cfg) + { + for (int i = 0; i < SceneManager.sceneCount; i++) + { + var sc = SceneManager.GetSceneAt(i); + if (sc.isLoaded && IsScoped(cfg, sc.name)) return; // 아직 섬/던전에 있다 + } + Release(cfg); + } + + // ───────────────────────────────────────────────────────────────── + /// 섬·던전에 들어갈 때 — 활성 씬의 하늘을 스냅샷하고 우리 값으로 바꾼다. + public static void Acquire(WLIslandLookSettings cfg, string sceneName) + { + if (cfg == null || cfg.skyScopeEnabled == 0) return; + // 🔴 스냅샷은 **활성 씬마다 딱 한 번**만 뜬다. + // 여기서 다시 뜨면, 이미 다른 시스템(던전 씬에 들어 있는 `WL_ReferenceLook`)이 + // 앰비언트를 바꿔 놓은 뒤의 값을 「원래 값」으로 잘못 기억한다(816h 1차 실측에서 실제로 났다). + CaptureIfNeeded(); + if (!Held) + { + Held = true; + HeldBy = sceneName; + Acquires++; + } + ApplySky(cfg); + } + + /// 섬·던전에서 나갈 때 — 스냅샷을 글자 그대로 되돌린다. + public static void Release(WLIslandLookSettings cfg) + { + if (!Held) return; + Restore(); + Held = false; HeldBy = ""; + Restores++; + if (cfg != null) cfg.Log("하늘 반납 — 활성 씬(" + s_snap.ownerScene + ") 원래대로"); + LastLog = "released → " + Describe(); + } + + static void CaptureIfNeeded() + { + var act = SceneManager.GetActiveScene(); + if (s_snap.valid && s_snapHandle == act.handle) return; + s_snapHandle = act.handle; + Capture(); + } + + static void Capture() + { + s_snap.valid = true; + s_snap.ownerScene = SceneManager.GetActiveScene().name; + s_snap.mode = RenderSettings.ambientMode; + s_snap.light = RenderSettings.ambientLight; + s_snap.sky = RenderSettings.ambientSkyColor; + s_snap.equator = RenderSettings.ambientEquatorColor; + s_snap.ground = RenderSettings.ambientGroundColor; + s_snap.intensity = RenderSettings.ambientIntensity; + s_snap.skybox = RenderSettings.skybox; + s_snap.fog = RenderSettings.fog; + s_snap.fogColor = RenderSettings.fogColor; + s_snap.fogMode = RenderSettings.fogMode; + s_snap.fogDensity = RenderSettings.fogDensity; + s_snap.fogStart = RenderSettings.fogStartDistance; + s_snap.fogEnd = RenderSettings.fogEndDistance; + s_snap.reflMode = RenderSettings.defaultReflectionMode; + s_snap.reflIntensity = RenderSettings.reflectionIntensity; + s_snap.sun = RenderSettings.sun; + } + + static void Restore() + { + if (!s_snap.valid) return; + RenderSettings.ambientMode = s_snap.mode; + RenderSettings.ambientLight = s_snap.light; + RenderSettings.ambientSkyColor = s_snap.sky; + RenderSettings.ambientEquatorColor = s_snap.equator; + RenderSettings.ambientGroundColor = s_snap.ground; + RenderSettings.ambientIntensity = s_snap.intensity; + RenderSettings.skybox = s_snap.skybox; + RenderSettings.fog = s_snap.fog; + RenderSettings.fogColor = s_snap.fogColor; + RenderSettings.fogMode = s_snap.fogMode; + RenderSettings.fogDensity = s_snap.fogDensity; + RenderSettings.fogStartDistance = s_snap.fogStart; + RenderSettings.fogEndDistance = s_snap.fogEnd; + RenderSettings.defaultReflectionMode = s_snap.reflMode; + RenderSettings.reflectionIntensity = s_snap.reflIntensity; + RenderSettings.sun = s_snap.sun; // 내려간 씬의 라이트면 자동으로 null 이 된다 + } + + /// 아레나/데모와 같은 하늘 — 값은 전부 SO 에서 온다(새 값 0). + static void ApplySky(WLIslandLookSettings cfg) + { + // 하늘 그 자체(스카이박스·안개·반사)는 언제나 우리 값으로. + if (cfg.fogOff != 0) RenderSettings.fog = false; + if (cfg.skybox != null) RenderSettings.skybox = cfg.skybox; + RenderSettings.defaultReflectionMode = DefaultReflectionMode.Skybox; + + // 🔴 앰비언트(무드)는 `WLReferenceLook`(814t 아레나 룩)의 것이다. + // 던전 씬에는 `WL_ReferenceLook` 이 **씬에 들어 있어** 우리보다 먼저 켜진다. + // 그때 여기서 앰비언트를 덮어쓰면 던전의 아레나 무드가 죽는다 → 이미 걸렸으면 손대지 않는다. + // (섬에서는 우리가 먼저 → 이 값을 깔고, 그 위에 아레나 무드가 얹힌다 = 던전과 같은 결과) + if (!WL.Look.Arena.WLReferenceLook.IsApplied) + { + RenderSettings.ambientMode = AmbientMode.Skybox; + RenderSettings.ambientSkyColor = cfg.ambientSky; + RenderSettings.ambientEquatorColor = cfg.ambientEquator; + RenderSettings.ambientGroundColor = cfg.ambientGround; + RenderSettings.ambientLight = cfg.ambientSky; + RenderSettings.ambientIntensity = cfg.ambientIntensity; + } + Applies++; + LastLog = "held(" + HeldBy + ") → " + Describe(); + } + + public static string Describe() + { + return "amb=" + RenderSettings.ambientMode + + " light=(" + RenderSettings.ambientLight.r.ToString("F3") + "," + + RenderSettings.ambientLight.g.ToString("F3") + "," + + RenderSettings.ambientLight.b.ToString("F3") + ")" + + " skybox=" + (RenderSettings.skybox ? RenderSettings.skybox.name : "없음") + + " fog=" + RenderSettings.fog + + " refl=" + RenderSettings.defaultReflectionMode + + " sun=" + (RenderSettings.sun ? RenderSettings.sun.name : "없음"); + } + + static void EnsureRunner() + { + if (s_runner != null) return; + var go = new GameObject(RunnerName); + go.hideFlags = HideFlags.DontSave; + Object.DontDestroyOnLoad(go); + s_runner = go.AddComponent(); + } + } + + /// 코루틴 한 개를 태우기 위한 빈 그릇. + public sealed class WLSkyScopeRunner : MonoBehaviour { } +} diff --git a/Assets/WL/Look/Farm/WLSkyScope.cs.meta b/Assets/WL/Look/Farm/WLSkyScope.cs.meta new file mode 100644 index 000000000..fc13b830b --- /dev/null +++ b/Assets/WL/Look/Farm/WLSkyScope.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 574d1f0a1b453ca4fbf7b1b25cf059a6 \ No newline at end of file