// WL-816e — Play 중 진단 · 성능 실측 · 오프스크린 캡처 (배치모드 헤드리스에서도 동작) public static class WL816e_Play { const string Dir = "Screenshots_WL/WL816e/"; const int W = 1080, H = 1920; public static void Run() { Report("b", true); } public static void RunOff() { Report("a", true); } public static void Report(string tag, bool shoot) { var sb = new System.Text.StringBuilder(); var cfg = WL.Look.Farm.WLIslandLookSettings.Instance; sb.AppendLine("isPlaying=" + UnityEngine.Application.isPlaying + " · scene=" + UnityEngine.SceneManagement.SceneManager.GetActiveScene().name + " · sceneCount=" + UnityEngine.SceneManagement.SceneManager.sceneCount); sb.AppendLine("SO : enabled_=" + (cfg == null ? -1 : cfg.enabled_) + " applyLook=" + (cfg == null ? -1 : cfg.applyLook) + " grass=" + (cfg == null ? -1 : cfg.grassEnabled) + " maxInstances=" + (cfg == null ? -1 : cfg.maxInstances)); sb.AppendLine("IslandLook: 렌더러 " + WL.Look.Farm.WLIslandLook.SwappedRenderers + " · 슬롯 " + WL.Look.Farm.WLIslandLook.SwappedSlots + " · rescan " + WL.Look.Farm.WLIslandLook.Rescans + " · dirLight " + WL.Look.Farm.WLIslandLook.LightingApplied + " · refLook " + WL.Look.Farm.WLIslandLook.ReferenceLookApplied + " · grassSpawned " + WL.Look.Farm.WLIslandLook.GrassSpawned + " · grassRebuild " + WL.Look.Farm.WLIslandLook.GrassRebuilds); sb.AppendLine("Grass : " + WL.Look.Farm.WLIslandGrass.LastLog); sb.AppendLine("RefLook : applied=" + WL.Look.Arena.WLReferenceLook.IsApplied + " outlined=" + WL.Look.Arena.WLReferenceLook.OutlinedRenderers + " contrastMat=" + WL.Look.Arena.WLReferenceLook.ContrastMaterials); sb.AppendLine("ambient : " + UnityEngine.RenderSettings.ambientMode + " " + UnityEngine.RenderSettings.ambientLight + " · skybox=" + (UnityEngine.RenderSettings.skybox != null ? UnityEngine.RenderSettings.skybox.name : "없음") + " · fog=" + UnityEngine.RenderSettings.fog); var ls = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); for (int i = 0; i < ls.Length; i++) sb.AppendLine("light : " + ls[i].name + " " + ls[i].type + " " + ls[i].color + " I=" + ls[i].intensity + " " + ls[i].shadows); // 섬 현황 var mgr = UnityEngine.Object.FindFirstObjectByType(UnityEngine.FindObjectsInactive.Include); int unlocked = 0, total = 0; var isls = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.None); for (int i = 0; i < isls.Length; i++) { total++; if (isls[i].IsUnlocked) unlocked++; } sb.AppendLine("island : 총 " + total + " · 열린 " + unlocked + " · coin=" + (mgr == null ? -1 : mgr.Coin) + " · navmesh=" + (mgr == null ? false : mgr.HasNavMesh)); // 씬 정적 비용 var rends = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); long tris = 0; int slots = 0; var shaders = new System.Collections.Generic.Dictionary(); for (int i = 0; i < rends.Length; i++) { var r = rends[i]; if (r is UnityEngine.ParticleSystemRenderer) continue; UnityEngine.Mesh mesh = null; var mr = r as UnityEngine.MeshRenderer; var sk = r as UnityEngine.SkinnedMeshRenderer; if (mr != null) { var mf = r.GetComponent(); if (mf != null) mesh = mf.sharedMesh; } else if (sk != null) mesh = sk.sharedMesh; if (mesh != null) for (int s = 0; s < mesh.subMeshCount; s++) tris += mesh.GetIndexCount(s) / 3; var ms = r.sharedMaterials; for (int m = 0; m < ms.Length; m++) { if (ms[m] == null) continue; slots++; string sh = ms[m].shader.name; shaders[sh] = shaders.TryGetValue(sh, out int v) ? v + 1 : 1; } } sb.Append("씬 정적 : 렌더러 " + rends.Length + " · 슬롯 " + slots + " · 삼각형 " + tris.ToString("N0") + " · 셰이더 "); foreach (var kv in shaders) sb.Append(kv.Key + " x" + kv.Value + " · "); sb.AppendLine(); // ── 성능 실측(같은 카메라로 N 프레임 렌더 시간) ──────────────── var cam = FindCam("MainCamera"); if (cam != null) { var perf = Measure(cam, 60); sb.AppendLine("성능(" + tag + ") : " + perf); } if (shoot) { System.IO.Directory.CreateDirectory(Dir); Shot(cam, Dir + tag + "_play_main.png"); Shot(TopDownCam(), Dir + tag + "_play_top.png"); Shot(DemoAngleCam(), Dir + tag + "_play_demoangle.png"); sb.AppendLine("캡처 " + tag + "_play_main / _play_top / _play_demoangle"); } UnityEngine.Debug.Log("[WL816e Play " + tag + "]\n" + sb.ToString()); } /// 같은 카메라를 N 번 렌더해 프레임 시간을 잰다 + 마지막 프레임의 드로우콜/삼각형. public static string Measure(UnityEngine.Camera cam, int frames) { var rt = new UnityEngine.RenderTexture(W, H, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); rt.antiAliasing = 1; rt.Create(); var prevT = cam.targetTexture; cam.targetTexture = rt; cam.Render(); // 워밈업 var sw = System.Diagnostics.Stopwatch.StartNew(); for (int i = 0; i < frames; i++) cam.Render(); sw.Stop(); int dc = UnityEditor.UnityStats.drawCalls; int batches = UnityEditor.UnityStats.batches; int setPass = UnityEditor.UnityStats.setPassCalls; int tri = UnityEditor.UnityStats.triangles; int vert = UnityEditor.UnityStats.vertices; cam.targetTexture = prevT; rt.Release(); UnityEngine.Object.DestroyImmediate(rt); double ms = sw.Elapsed.TotalMilliseconds / frames; return "렌더 " + ms.ToString("F2") + " ms/frame (= " + (1000.0 / System.Math.Max(0.001, ms)).ToString("F0") + " FPS 환산)" + " · 드로우콜 " + dc + " · 배치 " + batches + " · SetPass " + setPass + " · 삼각형 " + tri.ToString("N0") + " · 정점 " + vert.ToString("N0"); } // ── 카메라 ───────────────────────────────────────────────────────── public static UnityEngine.Camera TopDownCam() { var go = UnityEngine.GameObject.Find("__WL816eTop"); if (go == null) { go = new UnityEngine.GameObject("__WL816eTop"); go.hideFlags = UnityEngine.HideFlags.DontSave; } var c = go.GetComponent(); if (c == null) c = go.AddComponent(); c.orthographic = true; c.orthographicSize = 9f; c.nearClipPlane = 0.3f; c.farClipPlane = 500f; c.clearFlags = UnityEngine.CameraClearFlags.Skybox; c.depth = -100f; var rot = UnityEngine.Quaternion.Euler(50f, 0f, 0f); go.transform.rotation = rot; go.transform.position = new UnityEngine.Vector3(0f, 0f, 4f) - rot * UnityEngine.Vector3.forward * 40f; if (go.GetComponent() == null) go.AddComponent(); return c; } public static UnityEngine.Camera DemoAngleCam() { var go = UnityEngine.GameObject.Find("__WL816eDemoAngle"); if (go == null) { go = new UnityEngine.GameObject("__WL816eDemoAngle"); go.hideFlags = UnityEngine.HideFlags.DontSave; } var c = go.GetComponent(); if (c == null) c = go.AddComponent(); c.orthographic = true; c.orthographicSize = 10f; c.nearClipPlane = 0.3f; c.farClipPlane = 500f; c.clearFlags = UnityEngine.CameraClearFlags.Skybox; c.backgroundColor = new UnityEngine.Color(0.192f, 0.302f, 0.475f, 0f); c.depth = -100f; var rot = UnityEngine.Quaternion.Euler(30f, 60f, 0f); go.transform.rotation = rot; go.transform.position = new UnityEngine.Vector3(0f, 0.6f, 4f) - rot * UnityEngine.Vector3.forward * 60f; if (go.GetComponent() == null) go.AddComponent(); return c; } public static UnityEngine.Camera FindCam(string name) { var cams = UnityEngine.Object.FindObjectsByType( UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); for (int i = 0; i < cams.Length; i++) if (cams[i].name == name) return cams[i]; for (int i = 0; i < cams.Length; i++) if (cams[i].name.StartsWith("__WL")) continue; else return cams[i]; return cams.Length > 0 ? cams[0] : null; } public static void Shot(UnityEngine.Camera cam, string path) { if (cam == null) return; System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); var rt = new UnityEngine.RenderTexture(W, H, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); rt.antiAliasing = 1; rt.Create(); var prevT = cam.targetTexture; var prevA = UnityEngine.RenderTexture.active; cam.targetTexture = rt; cam.Render(); UnityEngine.RenderTexture.active = rt; var tex = new UnityEngine.Texture2D(W, H, UnityEngine.TextureFormat.RGB24, false); tex.ReadPixels(new UnityEngine.Rect(0, 0, W, H), 0, 0); tex.Apply(); System.IO.File.WriteAllBytes(path, UnityEngine.ImageConversion.EncodeToPNG(tex)); UnityEngine.RenderTexture.active = prevA; cam.targetTexture = prevT; UnityEngine.Object.DestroyImmediate(tex); rt.Release(); UnityEngine.Object.DestroyImmediate(rt); } }