diff --git a/AgentScripts/WL816h_BUILD.txt b/AgentScripts/WL816h_BUILD.txt new file mode 100644 index 000000000..9178eaa49 --- /dev/null +++ b/AgentScripts/WL816h_BUILD.txt @@ -0,0 +1,6 @@ +원본 Assets/WL/Materials/WL_Water_Ocean.mat shader=Shader Graphs/ToonWaterU +복사 Assets/WL/Look/Farm/Materials/Farm_Water_WL_A.mat = Shader Graphs/ToonWaterU +복사 Assets/WL/Look/Farm/Materials/Farm_Water_WL_B.mat = Shader Graphs/ToonWaterU +복사 Assets/WL/Look/Farm/Materials/Farm_Water_WL_C.mat = Shader Graphs/ToonWaterU +FI 원본 물 = Assets/FarmingIsland/Materials/Environments/SimpleWater.mat +SO 배선 완료 — waterMode=1 waterTo=Farm_Water_WL_B skyScopeEnabled=1 skyScenes=Level01,WL_FarmLook,WL_Dungeon01,WL_Dungeon02 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_CONSOLE.txt b/AgentScripts/WL816h_CONSOLE.txt new file mode 100644 index 000000000..4e7aab0d6 --- /dev/null +++ b/AgentScripts/WL816h_CONSOLE.txt @@ -0,0 +1,22 @@ +error=6 warning=408 log=1398 +--- error 메시지(중복 묶음) --- + ×1 PlayFabException: Must be logged in to call this method + PlayFab.PlayFabClientAPI.GetTitleNews (PlayFab.ClientModels.GetTitleNewsRequest request, System.Action`1[T] resultCallba + ServerInfo.Get_News (System.Action`1[T] _act) (at Assets/Script/Server/ServerInfo.cs:428) + DataCheckMgr+d__11.MoveNext () (at Assets/Script/Util/DataCheckMgr.cs:38) + ×1 NullReferenceException: Object reference not set to an instance of an object + DataCheckMgr+d__13.MoveNext () (at Assets/Script/Util/DataCheckMgr.cs:59) + UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) + UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator) + ×1 NullReferenceException: Object reference not set to an instance of an object + InGameInfo.Set_Init () (at Assets/Script/Info/InGameInfo.cs:46) + InGameInfo+d__16.MoveNext () (at Assets/Script/Info/InGameInfo.cs:78) + UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) + ×1 NullReferenceException: Object reference not set to an instance of an object + ProjectileInfo+d__7.MoveNext () (at Assets/Script/Info/ProjectileInfo.cs:33) + UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) + ×1 NullReferenceException: Object reference not set to an instance of an object + DropItemInfo.Make_DropItem (System.Int32 key, System.Int32 count) (at Assets/Script/Info/DropItemInfo.cs:60) + DropItemInfo.Start () (at Assets/Script/Info/DropItemInfo.cs:24) + ×1 NullReferenceException: Object reference not set to an instance of an object + LoadMapMgr.Awake () (at Assets/Script/Ingame/LoadMapMgr.cs:16) diff --git a/AgentScripts/WL816h_CONSOLE_off.txt b/AgentScripts/WL816h_CONSOLE_off.txt new file mode 100644 index 000000000..02d30a845 --- /dev/null +++ b/AgentScripts/WL816h_CONSOLE_off.txt @@ -0,0 +1,22 @@ +error=6 warning=410 log=4139 +--- error 메시지(중복 묶음) --- + ×1 NullReferenceException: Object reference not set to an instance of an object + ProjectileInfo+d__7.MoveNext () (at Assets/Script/Info/ProjectileInfo.cs:33) + UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) + ×1 PlayFabException: Must be logged in to call this method + PlayFab.PlayFabClientAPI.GetTitleNews (PlayFab.ClientModels.GetTitleNewsRequest request, System.Action`1[T] resultCallba + ServerInfo.Get_News (System.Action`1[T] _act) (at Assets/Script/Server/ServerInfo.cs:428) + DataCheckMgr+d__11.MoveNext () (at Assets/Script/Util/DataCheckMgr.cs:38) + ×1 NullReferenceException: Object reference not set to an instance of an object + DataCheckMgr+d__13.MoveNext () (at Assets/Script/Util/DataCheckMgr.cs:59) + UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) + UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator) + ×1 NullReferenceException: Object reference not set to an instance of an object + InGameInfo.Set_Init () (at Assets/Script/Info/InGameInfo.cs:46) + InGameInfo+d__16.MoveNext () (at Assets/Script/Info/InGameInfo.cs:78) + UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) + ×1 NullReferenceException: Object reference not set to an instance of an object + DropItemInfo.Make_DropItem (System.Int32 key, System.Int32 count) (at Assets/Script/Info/DropItemInfo.cs:60) + DropItemInfo.Start () (at Assets/Script/Info/DropItemInfo.cs:24) + ×1 NullReferenceException: Object reference not set to an instance of an object + LoadMapMgr.Awake () (at Assets/Script/Ingame/LoadMapMgr.cs:16) diff --git a/AgentScripts/WL816h_Console.cs b/AgentScripts/WL816h_Console.cs new file mode 100644 index 000000000..7f1129422 --- /dev/null +++ b/AgentScripts/WL816h_Console.cs @@ -0,0 +1,58 @@ +// WL-816h — 콘솔 error/warning 수와 error 메시지만 뽑아 파일로 (토큰 절약) +using System.Reflection; +using UnityEngine; + +public static class WL816h_Console +{ + public static void Dump() + { + var t = System.Type.GetType("UnityEditor.LogEntries,UnityEditor"); + var sb = new System.Text.StringBuilder(); + object[] args = { 0, 0, 0 }; + t.GetMethod("GetCountsByType", BindingFlags.Public | BindingFlags.Static).Invoke(null, args); + sb.AppendLine("error=" + args[0] + " warning=" + args[1] + " log=" + args[2]); + + int n = (int)t.GetMethod("StartGettingEntries", BindingFlags.Public | BindingFlags.Static).Invoke(null, null); + var entryType = System.Type.GetType("UnityEditor.LogEntry,UnityEditor"); + var entry = System.Activator.CreateInstance(entryType); + var getEntry = t.GetMethod("GetEntryInternal", BindingFlags.Public | BindingFlags.Static); + var msgF = entryType.GetField("message", BindingFlags.Public | BindingFlags.Instance); + var modeF = entryType.GetField("mode", BindingFlags.Public | BindingFlags.Instance); + var seen = new System.Collections.Generic.Dictionary(); + for (int i = 0; i < n; i++) + { + object[] a2 = { i, entry }; + getEntry.Invoke(null, a2); + int mode = (int)modeF.GetValue(a2[1]); + // Error(1) | Assert(2) | Fatal(0x10) | ScriptingError(0x100) | ScriptingException(0x400) | ScriptCompileError(0x800) + bool isErr = (mode & (1 | 2 | 0x10 | 0x100 | 0x400 | 0x800 | 0x2000)) != 0; + if (!isErr) continue; + string m = (string)msgF.GetValue(a2[1]); + if (m == null) continue; + // 메시지 1줄 + 스택 앞 3줄 (누가 냈는지 보려고) + var lines = m.Split('\n'); + var key = new System.Text.StringBuilder(lines[0].Length > 140 ? lines[0].Substring(0, 140) : lines[0]); + for (int k = 1; k < lines.Length && k <= 3; k++) + { + var s = lines[k].Trim(); + if (s.Length == 0) continue; + if (s.Length > 120) s = s.Substring(0, 120); + key.Append("\n ").Append(s); + } + string kk = key.ToString(); + seen[kk] = seen.ContainsKey(kk) ? seen[kk] + 1 : 1; + } + t.GetMethod("EndGettingEntries", BindingFlags.Public | BindingFlags.Static).Invoke(null, null); + sb.AppendLine("--- error 메시지(중복 묶음) ---"); + foreach (var kv in seen) sb.AppendLine(" ×" + kv.Value + " " + kv.Key); + if (seen.Count == 0) sb.AppendLine(" (없음)"); + System.IO.File.WriteAllText("AgentScripts/WL816h_CONSOLE.txt", sb.ToString()); + Debug.Log("[816h console]\n" + sb); + } + + public static void Clear() + { + var t = System.Type.GetType("UnityEditor.LogEntries,UnityEditor"); + t.GetMethod("Clear", BindingFlags.Public | BindingFlags.Static).Invoke(null, null); + } +} diff --git a/AgentScripts/WL816h_DEMO.txt b/AgentScripts/WL816h_DEMO.txt new file mode 100644 index 000000000..5fd79be77 --- /dev/null +++ b/AgentScripts/WL816h_DEMO.txt @@ -0,0 +1,2 @@ +데모 = #647676 → Screenshots_WL/WL816h/x_demo_horizon.png +나란히 = Screenshots_WL/WL816h/d_demo_vs_island.png (좌 데모 / 우 우리 섬 · 같은 화각) diff --git a/AgentScripts/WL816h_Demo.cs b/AgentScripts/WL816h_Demo.cs new file mode 100644 index 000000000..e6840dcf0 --- /dev/null +++ b/AgentScripts/WL816h_Demo.cs @@ -0,0 +1,81 @@ +// WL-816h — ⓓ 데모 씬과 나란히 (에디트 모드 렌더 · §9 데모 Play 안 함) +using UnityEngine; +using UnityEngine.Rendering.Universal; +using UnityEditor; + +public static class WL816h_Demo +{ + const string Dir = "Screenshots_WL/WL816h/"; + const int W = 1080, H = 1920; + + public static void Run() + { + var sb = new System.Text.StringBuilder(); + UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/LowPolyFantasyArena/Scenes/LowPolyArena_Demo.unity", + UnityEditor.SceneManagement.OpenSceneMode.Single); + + // 수평선이 보이는 화각 — 우리 섬 캡처(b_after_horizon)와 같은 fov·피치 + var go = new GameObject("~WL816hDemoCam"); go.hideFlags = HideFlags.DontSave; + var c = go.AddComponent(); + c.orthographic = false; c.fieldOfView = 55f; c.nearClipPlane = 0.3f; c.farClipPlane = 2000f; + c.clearFlags = CameraClearFlags.Skybox; c.cullingMask = ~0; c.depth = -100f; + go.transform.position = new Vector3(-14f, 8f, -14f); + go.transform.rotation = Quaternion.Euler(4f, 45f, 0f); + go.AddComponent(); + + sb.AppendLine("데모 = " + Shot(c, Dir + "x_demo_horizon.png")); + Object.DestroyImmediate(go); + + Pair(Dir + "x_demo_horizon.png", Dir + "b_after_horizon.png", Dir + "d_demo_vs_island.png"); + sb.AppendLine("나란히 = " + Dir + "d_demo_vs_island.png (좌 데모 / 우 우리 섬 · 같은 화각)"); + System.IO.File.WriteAllText("AgentScripts/WL816h_DEMO.txt", sb.ToString()); + Debug.Log("[816h demo]\n" + sb); + + // 다음 작업을 위해 InGame 으로 돌려 둔다(데모 씬 저장 0) + UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/Scenes/InGame.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); + } + + static string Shot(Camera cam, string path) + { + 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 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 = null; + System.IO.File.WriteAllBytes(path, tex.EncodeToPNG()); + var px = tex.GetPixels32(); long r = 0, g = 0, b = 0; + for (int i = 0; i < px.Length; i += 7) { r += px[i].r; g += px[i].g; b += px[i].b; } + int n = (px.Length + 6) / 7; + Object.DestroyImmediate(tex); rt.Release(); Object.DestroyImmediate(rt); + return "#" + ((int)(r / n)).ToString("X2") + ((int)(g / n)).ToString("X2") + ((int)(b / n)).ToString("X2") + " → " + path; + } + + static void Pair(string left, string right, string outPath) + { + var a = Load(left); var b = Load(right); + if (a == null || b == null) { Debug.Log("[816h] 나란히 실패 " + left + " / " + right); 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); + } + + 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; + } +} diff --git a/AgentScripts/WL816h_FUNC.txt b/AgentScripts/WL816h_FUNC.txt new file mode 100644 index 000000000..1b88c23cd --- /dev/null +++ b/AgentScripts/WL816h_FUNC.txt @@ -0,0 +1,8 @@ +=== ⑥ 기능 무영향 (PD 경로 · 채택 상태) === +물 : mat=Farm_Water_WL_C · MeshCollider=있음 enabled=True mesh=Plane · MeshFilter=Plane · layer=0 · pos=(0.0, -1.0, 0.0) · scale=(100, 1, 100) +걷기: (0.00, 0.00, 0.00) → (4.24, 0.01, 4.24) · 이동 6.00 m · grounded=True +풀(확장 전): 타일 5 · 인스턴스 680 · 드로우콜 2 · 삼각형 6111 · 제외점 358 · 밀도 1.80(=3.24개/㎡) · 섬 타일 5 +풀(1칸 잠금): 타일 4 · 인스턴스 602 · 드로우콜 2 · 삼각형 5409 · 제외점 240 · 밀도 1.80(=3.24개/㎡) +풀(다시 열기): 타일 5 · 인스턴스 699 · 드로우콜 2 · 삼각형 6282 · 제외점 339 · 밀도 1.80(=3.24개/㎡) · 재생성 2회 +최종 = Screenshots_WL/WL816h/b_final_pd_path.png +SkyScope held=True · held(Level01) → amb=Skybox light=(0.212,0.227,0.259) skybox=Default-Skybox fog=False refl=Skybox sun=DirectionalLight diff --git a/AgentScripts/WL816h_Func.cs b/AgentScripts/WL816h_Func.cs new file mode 100644 index 000000000..c1bf0ce6f --- /dev/null +++ b/AgentScripts/WL816h_Func.cs @@ -0,0 +1,115 @@ +// WL-816h — ⑥ 기능 무영향 실측 (걷기 · 섬 확장 · 물 콜라이더) + 최종 PD 경로 캡처 +using System.Collections; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.Rendering.Universal; +using FIIsland = CryingSnow.FarmingIsland.Island; + +public static class WL816h_Func +{ + public static void Open() + { + UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/Scenes/InGame.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); + } + public static void Start() + { + var go = GameObject.Find("~WL816hFunc"); + if (go != null) Object.DestroyImmediate(go); + go = new GameObject("~WL816hFunc"); + go.AddComponent(); + } +} + +public class WL816h_FuncRunner : 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("=== ⑥ 기능 무영향 (PD 경로 · 채택 상태) ==="); + 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); + + // 물 — 콜라이더·메시가 그대로인가 (머티리얼만 바꿨다) + 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; + if (water != null) + { + var mc = water.GetComponent(); + var mf = water.GetComponent(); + L("물 : mat=" + water.sharedMaterial.name + " · MeshCollider=" + (mc != null ? ("있음 enabled=" + mc.enabled + " mesh=" + (mc.sharedMesh ? mc.sharedMesh.name : "없음")) : "없음") + + " · MeshFilter=" + (mf != null && mf.sharedMesh ? mf.sharedMesh.name : "없음") + + " · layer=" + water.gameObject.layer + " · pos=" + water.transform.position.ToString("F1") + + " · scale=" + water.transform.localScale.ToString("F0")); + } + + // 걷기 — FI 플레이어의 CharacterController 로 대각선 6 m + var cc = Object.FindFirstObjectByType(FindObjectsInactive.Exclude); + if (cc != null) + { + var p0 = cc.transform.position; + var dir = new Vector3(1f, 0f, 1f).normalized; + float moved = 0f; + for (int i = 0; i < 120 && moved < 6f; i++) + { cc.Move(dir * 0.05f + Vector3.down * 0.05f); moved += 0.05f; yield return null; } + var p1 = cc.transform.position; + L("걷기: " + p0.ToString("F2") + " → " + p1.ToString("F2") + + " · 이동 " + Vector3.Distance(new Vector3(p0.x, 0, p0.z), new Vector3(p1.x, 0, p1.z)).ToString("F2") + " m" + + " · grounded=" + cc.isGrounded); + } + else L("걷기: CharacterController 없음(세이브 상태에 따라 없을 수 있다)"); + + // 섬 확장 — 타일 하나 잠갔다 다시 열기 (풀이 따라오나) + int before = WL.Look.Farm.WLIslandGrass.LastLog != null ? 1 : 0; + var islands = Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); + L("풀(확장 전): " + WL.Look.Farm.WLIslandGrass.LastLog + " · 섬 타일 " + islands.Length); + FIIsland target = null; + foreach (var isl in islands) if (isl != null && isl.IsUnlocked && isl.gameObject.activeInHierarchy) target = isl; + if (target != null) + { + target.gameObject.SetActive(false); + yield return new WaitForSeconds(2f); + L("풀(1칸 잠금): " + WL.Look.Farm.WLIslandGrass.LastLog); + target.gameObject.SetActive(true); + yield return new WaitForSeconds(3f); + L("풀(다시 열기): " + WL.Look.Farm.WLIslandGrass.LastLog + " · 재생성 " + WL.Look.Farm.WLIslandLook.GrassRebuilds + "회"); + } + + // 최종 PD 경로 캡처 + 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("최종 = " + Shot(live, "Screenshots_WL/WL816h/b_final_pd_path.png")); + L("SkyScope held=" + WL.Look.Farm.WLSkyScope.Held + " · " + WL.Look.Farm.WLSkyScope.LastLog); + + System.IO.File.WriteAllText("AgentScripts/WL816h_FUNC.txt", sb.ToString()); + Debug.Log("[816h func]\n" + sb); + } + + static string Shot(Camera cam, string path) + { + if (cam == null) return "카메라 없음"; + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + var rt = new RenderTexture(1080, 1920, 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(1080, 1920, TextureFormat.RGB24, false); + tex.ReadPixels(new Rect(0, 0, 1080, 1920), 0, 0); tex.Apply(); + RenderTexture.active = pA; cam.targetTexture = pT; + System.IO.File.WriteAllBytes(path, tex.EncodeToPNG()); + Object.DestroyImmediate(tex); rt.Release(); Object.DestroyImmediate(rt); + return path; + } +} diff --git a/AgentScripts/WL816h_PROBE.txt b/AgentScripts/WL816h_PROBE.txt new file mode 100644 index 000000000..d79df0513 --- /dev/null +++ b/AgentScripts/WL816h_PROBE.txt @@ -0,0 +1,24 @@ +=== WL-816h 지금 상태 (PD 경로 = InGame 활성 + Level01 Additive) === +t0 활성씬=InGame / RenderSettings: amb=Skybox light=(0.212,0.227,0.259) sky=(0.212,0.227,0.259) I=1 skybox=Default-Skybox/Skybox/Procedural fog=False refl=Skybox/1 sun=없음 +Load_Map 예외(=로그인 없이는 못 탄다): NullReferenceException +→ SceneInfo.Load_AddScene 과 같은 호출로 대체: LoadSceneAsync("Level01", Additive) + +t+6s 활성씬=InGame sceneCount=2 +🔴 RenderSettings: amb=Flat light=(0.217,0.228,0.250) sky=(0.217,0.228,0.250) I=1 skybox=Default-Skybox/Skybox/Procedural fog=False refl=Skybox/1 sun=DirectionalLight +IslandLook: 렌더러 1575 light=1 refLook=True +RefLook : applied=True log=Ⓐ17 Ⓑ6mat/md0.12 Ⓒ0.30 Ⓓoff + +CAM Main Camera scene=InGame live=False depth=5 tag=MainCamera clear=Skybox 🔴post=True +CAM MainCamera scene=Level01 live=True depth=-1 tag=MainCamera clear=Skybox 🔴post=True +→ 보이는 카메라 = MainCamera(Level01) +Camera.main = MainCamera(Level01) +Volume Global Volume global=True w=1 prio=0 profile=GlobalVolumeProfile_Blind + +WATER obj=Water layer=0 mat=Farm_SimpleWater_Demo shader=Shader Graphs/Simple Water queue=2999 bounds=(1000, 0, 1000) pos=(0.0, -1.0, 0.0) +WATER obj=WateringCan layer=0 mat=Farm_Palette_ToonTex shader=Shader Graphs/Toon queue=2000 bounds=(1, 1, 1) pos=(0.8, 1.1, 0.0) +WATER obj=WaterParticle layer=0 mat=WaterCan shader=Universal Render Pipeline/Particles/Unlit queue=2450 bounds=(0, 0, 0) pos=(0.0, 0.0, 0.0) +URP asset = URP-Performant opaqueTex=True depthTex=True msaa=1 + +하늘(섬) = #7998B7 → Screenshots_WL/WL816h/a_now_sky.png +섬 화면 = #6FAFD1 → Screenshots_WL/WL816h/a_now_island.png +물 근접 = #80B7C6 → Screenshots_WL/WL816h/a_now_water.png diff --git a/AgentScripts/WL816h_PROBE2.txt b/AgentScripts/WL816h_PROBE2.txt new file mode 100644 index 000000000..4bb35220b --- /dev/null +++ b/AgentScripts/WL816h_PROBE2.txt @@ -0,0 +1,14 @@ +=== ② 「파란 하늘」의 정체 + 섬 나간 뒤 오염 (고치기 전) === +보이는 카메라 = MainCamera fov=60 euler=(45, 0, 0) pos=(0.0, 15.6, -15.6) +물 렌더러 = Water mat=Farm_SimpleWater_Demo shader=Shader Graphs/Simple Water +물 ON = 상단10%=#5BB5FE 하단10%=#5BB5FE → Screenshots_WL/WL816h/a1_water_on.png +물 OFF = 상단10%=#5E4D68 하단10%=#5C4C66 → Screenshots_WL/WL816h/a2_water_off.png + +섬 내리기 전 : amb=Flat light=(0.217,0.228,0.250) skySrc=(0.217,0.228,0.250) I=1 skybox=Default-Skybox fog=False refl=Skybox/1 sun=DirectionalLight +섬 내린 뒤 : amb=Skybox light=(0.212,0.227,0.259) skySrc=(0.212,0.227,0.259) I=1 skybox=Default-Skybox fog=False refl=Skybox/1 sun=없음 +InGame 원본 : amb=Skybox light=(0.212,0.227,0.259) skybox=Default-Skybox fog=False refl=Skybox/1 sun=없음 (씬 파일 실측) +🔴 오염 = 없음(오염 0) + +전투 맵(Map_C01) 올린 뒤 : amb=Skybox light=(0.212,0.227,0.259) skySrc=(0.212,0.227,0.259) I=1 skybox=Default-Skybox fog=False refl=Skybox/1 sun=없음 +🔴 오염 = 없음(오염 0) +전투맵 하늘 = 상단10%=#586773 하단10%=#4D3B1E → Screenshots_WL/WL816h/f0_before_battle_sky.png 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_REF.txt b/AgentScripts/WL816h_REF.txt new file mode 100644 index 000000000..88d6b3b72 --- /dev/null +++ b/AgentScripts/WL816h_REF.txt @@ -0,0 +1,14 @@ +### arena Assets/WL/Look/Arena/Scenes/WL_ArenaProto.unity +RenderSettings: amb=Skybox light=(0.212,0.227,0.259) sky=(0.212,0.227,0.259) I=1 skybox=Default-Skybox/Skybox/Procedural fog=False refl=Skybox/1 sun=Directional Light + CAM PlanarReflectionProbe active=True en=True ortho=True size=5 clear=SolidColor post=False + CAM Main Camera active=True en=True ortho=True size=10 clear=Skybox post=False + Volume 수 = 0 + 하늘 화면색 = #798EA9 → Screenshots_WL/WL816h/ref_sky_arena.png +### demo Assets/LowPolyFantasyArena/Scenes/LowPolyArena_Demo.unity +RenderSettings: amb=Skybox light=(0.212,0.227,0.259) sky=(0.212,0.227,0.259) I=1 skybox=Default-Skybox/Skybox/Procedural fog=False refl=Skybox/1 sun=Directional Light + CAM Camera1 active=True en=True ortho=False fov=60 clear=Skybox post=noACD + CAM Camera2 active=True en=True ortho=False fov=60 clear=Skybox post=noACD + CAM Camera4 active=True en=True ortho=False fov=60 clear=Skybox post=noACD + CAM Camera3 active=True en=True ortho=False fov=60 clear=Skybox post=noACD + Volume 수 = 0 + 하늘 화면색 = #88A4C4 → Screenshots_WL/WL816h/ref_sky_demo.png diff --git a/AgentScripts/WL816h_ROLLBACK_R1.txt b/AgentScripts/WL816h_ROLLBACK_R1.txt new file mode 100644 index 000000000..7a3b7de8c --- /dev/null +++ b/AgentScripts/WL816h_ROLLBACK_R1.txt @@ -0,0 +1,11 @@ +=== 되돌리기 실측 [enabled_=1 skyScope=1 waterMode=1] === +InGame 원래 하늘 : amb=Skybox light=(0.212,0.227,0.259) skySrc=(0.212,0.227,0.259) I=1 skybox=Default-Skybox fog=False refl=Skybox/1 sun=없음 +물 : Farm_Water_WL_C / Shader Graphs/ToonWaterU +머티리얼 교체 : 렌더러 1575 · 물 1 +풀 : 타일 5 · 인스턴스 680 · 드로우콜 2 · 삼각형 6111 · 제외점 358 · 밀도 1.80(=3.24개/㎡) +RefLook : True +SkyScope : held=True acquires=1 restores=0 +섬 하늘 : amb=Flat light=(0.217,0.228,0.250) skySrc=(0.217,0.228,0.250) I=1 skybox=Default-Skybox fog=False refl=Skybox/1 sun=DirectionalLight +화면 : #729AA9 → Screenshots_WL/WL816h/z_rollback_water_now.png +전투 맵 : amb=Skybox light=(0.212,0.227,0.259) skySrc=(0.212,0.227,0.259) I=1 skybox=Default-Skybox fog=False refl=Skybox/1 sun=없음 +🔴 원래대로 = 예 diff --git a/AgentScripts/WL816h_ROLLBACK_R2.txt b/AgentScripts/WL816h_ROLLBACK_R2.txt new file mode 100644 index 000000000..72324f523 --- /dev/null +++ b/AgentScripts/WL816h_ROLLBACK_R2.txt @@ -0,0 +1,11 @@ +=== 되돌리기 실측 [enabled_=0 skyScope=0 waterMode=0] === +InGame 원래 하늘 : amb=Skybox light=(0.212,0.227,0.259) skySrc=(0.212,0.227,0.259) I=1 skybox=Default-Skybox fog=False refl=Skybox/1 sun=없음 +물 : SimpleWater / Shader Graphs/Simple Water +머티리얼 교체 : 렌더러 0 · 물 0 +풀 : +RefLook : False +SkyScope : held=False acquires=0 restores=0 +섬 하늘 : amb=Skybox light=(0.212,0.227,0.259) skySrc=(0.212,0.227,0.259) I=1 skybox=Default-Skybox fog=False refl=Skybox/1 sun=DirectionalLight +화면 : #2891BA → Screenshots_WL/WL816h/z_rollback_off.png +전투 맵 : amb=Skybox light=(0.212,0.227,0.259) skySrc=(0.212,0.227,0.259) I=1 skybox=Default-Skybox fog=False refl=Skybox/1 sun=없음 +🔴 원래대로 = 예 diff --git a/AgentScripts/WL816h_Rollback.cs b/AgentScripts/WL816h_Rollback.cs new file mode 100644 index 000000000..b3211669e --- /dev/null +++ b/AgentScripts/WL816h_Rollback.cs @@ -0,0 +1,131 @@ +// WL-816h — ⑤ 되돌리기 실측 (SO 스위치 하나로 100 % 복귀) +// R1 = waterMode 0 + skyScopeEnabled 0 → 816f 상태(FarmingIsland 물 · 스코프 없음) +// R2 = enabled_ 0 → 섬 룩 전체 off (816e/816f 가 쓰던 스위치) +using System.Collections; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEditor; + +public static class WL816h_Rollback +{ + const string SO = "Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset"; + const string Dir = "Screenshots_WL/WL816h/"; + + public static void SetR1() { Set(1, 0, 0); } // 섬 룩은 그대로 · 물/스코프만 끈다 + public static void SetR2() { Set(0, 0, 0); } // 전부 off + public static void SetOn() { Set(1, 1, 1); } // 채택 상태로 복귀 + + static void Set(int enabled_, int sky, int water) + { + var so = AssetDatabase.LoadAssetAtPath(SO); + if (so == null) { Debug.Log("[816h] SO 없음"); return; } + so.enabled_ = enabled_; so.skyScopeEnabled = sky; so.waterMode = water; + EditorUtility.SetDirty(so); AssetDatabase.SaveAssets(); + WL.Look.Farm.WLIslandLookSettings.Invalidate(); + Debug.Log("[816h] SO enabled_=" + enabled_ + " skyScope=" + sky + " waterMode=" + water); + UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/Scenes/InGame.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); + } + + public static void Start() + { + var go = GameObject.Find("~WL816hRollback"); + if (go != null) Object.DestroyImmediate(go); + go = new GameObject("~WL816hRollback"); + go.AddComponent(); + } +} + +/// run_script 는 파일 하나만 컴파일한다 → 도우미를 여기에 그대로 둔다. +public static class RB +{ + public const int W = 1080, H = 1920; + + 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 : "없음"); + } + static string F(Color c) { return "(" + c.r.ToString("F3") + "," + c.g.ToString("F3") + "," + c.b.ToString("F3") + ")"; } + + 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 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; + System.IO.File.WriteAllBytes(path, tex.EncodeToPNG()); + var px = tex.GetPixels32(); long r = 0, g = 0, b = 0; + for (int i = 0; i < px.Length; i += 7) { r += px[i].r; g += px[i].g; b += px[i].b; } + int n = (px.Length + 6) / 7; + string s = "#" + ((int)(r / n)).ToString("X2") + ((int)(g / n)).ToString("X2") + ((int)(b / n)).ToString("X2"); + Object.DestroyImmediate(tex); rt.Release(); Object.DestroyImmediate(rt); + return s + " → " + path; + } +} + +public class WL816h_RollbackRunner : MonoBehaviour +{ + void Start() { StartCoroutine(Co()); } + + IEnumerator Co() + { + var sb = new System.Text.StringBuilder(); + var cfg = WL.Look.Farm.WLIslandLookSettings.Instance; + string tag = "enabled_=" + (cfg == null ? -1 : cfg.enabled_) + + " skyScope=" + (cfg == null ? -1 : cfg.skyScopeEnabled) + + " waterMode=" + (cfg == null ? -1 : cfg.waterMode); + sb.AppendLine("=== 되돌리기 실측 [" + tag + "] ==="); + string pristine = RB.Sky(); + sb.AppendLine("InGame 원래 하늘 : " + pristine); + + 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); + + 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; + sb.AppendLine("물 : " + (water == null ? "없음" : water.sharedMaterial.name + " / " + water.sharedMaterial.shader.name)); + sb.AppendLine("머티리얼 교체 : 렌더러 " + WL.Look.Farm.WLIslandLook.SwappedRenderers + + " · 물 " + WL.Look.Farm.WLIslandLook.WaterSwapped); + sb.AppendLine("풀 : " + WL.Look.Farm.WLIslandGrass.LastLog); + sb.AppendLine("RefLook : " + WL.Look.Arena.WLReferenceLook.IsApplied); + sb.AppendLine("SkyScope : held=" + WL.Look.Farm.WLSkyScope.Held + + " acquires=" + WL.Look.Farm.WLSkyScope.Acquires + " restores=" + WL.Look.Farm.WLSkyScope.Restores); + sb.AppendLine("섬 하늘 : " + RB.Sky()); + + 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; + } + string shot = "Screenshots_WL/WL816h/z_rollback_" + (cfg != null && cfg.enabled_ == 0 ? "off" : "water_now") + ".png"; + sb.AppendLine("화면 : " + RB.Shot(live, shot)); + + // 섬을 내리고 전투 맵 → 하늘이 원래대로인가 + var u = SceneManager.UnloadSceneAsync("Level01"); + while (u != null && !u.isDone) yield return null; + yield return new WaitForSeconds(2.5f); + var b = SceneManager.LoadSceneAsync("Map_C01", LoadSceneMode.Additive); + while (b != null && !b.isDone) yield return null; + yield return new WaitForSeconds(1.5f); + string after = RB.Sky(); + sb.AppendLine("전투 맵 : " + after); + sb.AppendLine("🔴 원래대로 = " + (after == pristine ? "예" : "🔴 아니오 (전 " + pristine + ")")); + + string path = "AgentScripts/WL816h_ROLLBACK_" + (cfg != null && cfg.enabled_ == 0 ? "R2" : "R1") + ".txt"; + System.IO.File.WriteAllText(path, sb.ToString()); + Debug.Log("[816h rollback]\n" + sb); + } +} diff --git a/AgentScripts/WL816h_VERIFY.txt b/AgentScripts/WL816h_VERIFY.txt new file mode 100644 index 000000000..408896a2e --- /dev/null +++ b/AgentScripts/WL816h_VERIFY.txt @@ -0,0 +1,51 @@ +=== WL-816h 적용 확인 (PD 경로 = InGame 활성 + Level01 Additive) === +0) InGame 원래 하늘 : amb=Skybox light=(0.212,0.227,0.259) skySrc=(0.212,0.227,0.259) I=1 skybox=Default-Skybox fog=False refl=Skybox/1 sun=없음 + Load_Map(900) = 🔴 로그인 없이는 못 탄다 → LoadSceneAsync(Level01, Additive) 로 대체(816f 와 같음) + +1) 섬 진입 — 활성씬=InGame sceneCount=2 + IslandLook 렌더러 1575 · 물 교체 1 · refLook=True + 🔴 SkyScope held=True by=Level01 acquires=1 restores=0 + 섬 하늘 = amb=Flat light=(0.217,0.228,0.250) skySrc=(0.217,0.228,0.250) I=1 skybox=Default-Skybox fog=False refl=Skybox/1 sun=DirectionalLight + 물 렌더러 = Water mat=Farm_Water_WL_C shader=Shader Graphs/ToonWaterU queue=3000 + 보이는 카메라 = MainCamera(Level01) + +2) 물 값 3단 비교 + 성능 (같은 카메라 1080×1920 · 60프레임 오프스크린) + [지금(FI SimpleWater)] 0.828 ms/frame + 게임화면 상10%=#5BB5FE 중=#8BC1AA 하10%=#5BB5FE → Screenshots_WL/WL816h/w_0_now_live.png + 수평선 상10%=#6174C2 중=#5CB7FE 하10%=#8AB1CC → Screenshots_WL/WL816h/w_0_now_horizon.png + [A=WL_Water_Ocean 원본] 0.864 ms/frame + 게임화면 상10%=#0FBFEB 중=#77C2A6 하10%=#1DC0E9 → Screenshots_WL/WL816h/w_1_a_live.png + 수평선 상10%=#6174C2 중=#2BC2EF 하10%=#4497B6 → Screenshots_WL/WL816h/w_1_a_horizon.png + [B=데모 톤] 0.814 ms/frame + 게임화면 상10%=#50B0EB 중=#83BEA6 하10%=#53B0E9 → Screenshots_WL/WL816h/w_2_b_live.png + 수평선 상10%=#6174C2 중=#55B1EF 하10%=#76A0B9 → Screenshots_WL/WL816h/w_2_b_horizon.png + [C=차분] 0.853 ms/frame + 게임화면 상10%=#6097C2 중=#88B89B 하10%=#6397C1 → Screenshots_WL/WL816h/w_3_c_live.png + 수평선 상10%=#6174C2 중=#659AC5 하10%=#8093A3 → Screenshots_WL/WL816h/w_3_c_horizon.png + → 채택 = Farm_Water_WL_C + +2-b) 성능 전/후 (같은 카메라 · 60프레임 × 2회 · 순서 교대) + 0회차 전(FI SimpleWater) = 0.855 ms/frame + 0회차 후(Farm_Water_WL_C) = 0.901 ms/frame + 1회차 전(FI SimpleWater) = 0.889 ms/frame + 1회차 후(Farm_Water_WL_C) = 0.886 ms/frame + 3단 비교 한 장 = Screenshots_WL/WL816h/e_water_steps.png · e_water_steps_horizon.png + +3) 적용 후 + 섬 게임화면 = 상10%=#6097C2 중=#87B79B 하10%=#6397C1 → Screenshots_WL/WL816h/b_after_island.png + 수평선 = 상10%=#6174C2 중=#659AC5 하10%=#8093A4 → Screenshots_WL/WL816h/b_after_horizon.png + 물 근접 = 상10%=#8399E6 중=#78807A 하10%=#4C7EA7 → Screenshots_WL/WL816h/b_after_water.png + 하늘만 = 상10%=#4958A3 중=#7186D3 하10%=#6598C2 → Screenshots_WL/WL816h/b_after_sky.png + 좌우 비교 = Screenshots_WL/WL816h/c_side_by_side.png (좌 지금 / 우 적용 후) + +4) 🔴 맵별 하늘 격리 — 섬 → 던전 → 전투 맵 + [섬] amb=Flat light=(0.217,0.228,0.250) skySrc=(0.217,0.228,0.250) I=1 skybox=Default-Skybox fog=False refl=Skybox/1 sun=DirectionalLight + [섬] 하늘 = 상10%=#4958A3 중=#7186D3 하10%=#6598C2 → Screenshots_WL/WL816h/f1_island_sky.png + [던전] SkyScope held=True by=Level01 acquires=1 restores=0 + [던전] amb=Flat light=(0.217,0.228,0.250) skySrc=(0.217,0.228,0.250) I=1 skybox=Default-Skybox fog=False refl=Skybox/1 sun=Directional Light + [던전] 하늘 = 상10%=#4958A3 중=#7186D3 하10%=#60506F → Screenshots_WL/WL816h/f2_dungeon_sky.png + [전투] SkyScope held=False acquires=1 restores=1 + [전투] amb=Skybox light=(0.212,0.227,0.259) skySrc=(0.212,0.227,0.259) I=1 skybox=Default-Skybox fog=False refl=Skybox/1 sun=없음 + [전투] 하늘 = 상10%=#586773 중=#839397 하10%=#4D3A1D → Screenshots_WL/WL816h/f3_battle_sky.png + 🔴 원래대로인가 = 예 — 글자 하나까지 같다(오염 0) + 한 장 = Screenshots_WL/WL816h/f_sky_isolation.png (좌 섬 / 중 던전 / 우 전투 맵) diff --git a/AgentScripts/WL816h_Verify.cs b/AgentScripts/WL816h_Verify.cs new file mode 100644 index 000000000..64bb47e70 --- /dev/null +++ b/AgentScripts/WL816h_Verify.cs @@ -0,0 +1,323 @@ +// 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 : "?")); + + // ── 2-b) 성능 전/후 — 순서 바꿔 두 번씩 (측정 순서 편향 제거) ── + L(""); + L("2-b) 성능 전/후 (같은 카메라 · 60프레임 × 2회 · 순서 교대)"); + var adopted = cfg != null ? cfg.waterTo : null; + for (int round = 0; round < 2; round++) + { + foreach (var pair in new[] { new object[] { "전(FI SimpleWater)", now }, new object[] { "후(" + (adopted ? adopted.name : "?") + ")", adopted } }) + { + var m = pair[1] as Material; if (m == null) continue; + if (water != null) water.sharedMaterial = m; + yield return null; yield return null; + L(" " + round + "회차 " + (string)pair[0] + " = " + WL816h_Verify.Ms(live, 60).ToString("F3") + " ms/frame"); + } + } + if (water != null && adopted != null) water.sharedMaterial = adopted; + yield return null; + + 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..ac793f475 100644 --- a/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset +++ b/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset @@ -55,6 +55,16 @@ MonoBehaviour: - 0.5 - 2 - 5 + skyScopeEnabled: 1 + skyScenes: + - Level01 + - WL_FarmLook + - WL_Dungeon01 + - WL_Dungeon02 + skyReleaseDelaySeconds: 1.5 + waterMode: 1 + waterFrom: {fileID: 2100000, guid: ce0f8fd131f36964dbc918b8c63f838b, type: 2} + waterTo: {fileID: 2100000, guid: c4a590309ecaa044da75b96fa1e150a3, 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