#if UNITY_EDITOR using UnityEngine; using UnityEditor; namespace ProjectM.Client { /// /// EDITOR-ONLY live tuner for the "3D pixel art" full-screen render style — the /// Hidden/ProjectM/PixelOutline shader driven by PixelOutline.mat via the /// PC_Renderer FullScreenPassRendererFeature. Self-spawns on Play (no scene wiring), /// so it works in Game.unity and DevSandbox.unity alike. Toggle the panel with F3. /// /// It writes straight to the shared material ASSET, so every drag applies to the running /// game instantly AND sticks after you exit Play mode (material assets aren't reverted the /// way scene objects are). "Save Asset" force-writes to disk now; "Reset" restores the /// shader's shipped defaults. Stripped from player builds (#if UNITY_EDITOR). /// public class PixelArtDevControls : MonoBehaviour { const string MaterialPath = "Assets/_Project/Shaders/PixelOutline.mat"; static PixelArtDevControls _instance; /// /// OPT-IN EditorPref, mirroring `ProjectM/Boot Into Menu (Editor)`. This used to self-spawn into EVERY /// scene — Game.unity included, not just DevSandbox — and draws its toggle button on /// every IMGUI pass BEFORE the `!_open` early-out. IMGUI dispatches at least Layout+Repaint per frame, /// so it allocated continuously during normal gameplay and dominated allocation profiling until it was /// found on 2026-08-13. Off by default; enable from the menu when you actually want to tune the look. /// const string EnabledPref = "ProjectM.PixelArtDevControls.Enabled"; const string MenuPath = "ProjectM/Pixel Art Dev Controls (Editor)"; [MenuItem(MenuPath)] static void ToggleEnabled() => EditorPrefs.SetBool(EnabledPref, !EditorPrefs.GetBool(EnabledPref, false)); [MenuItem(MenuPath, true)] static bool ToggleEnabledValidate() { Menu.SetChecked(MenuPath, EditorPrefs.GetBool(EnabledPref, false)); return true; } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] static void Bootstrap() { if (_instance != null) return; if (!EditorPrefs.GetBool(EnabledPref, false)) return; // opt-in only — see EnabledPref var go = new GameObject("~PixelArtDevControls") { hideFlags = HideFlags.HideAndDontSave }; DontDestroyOnLoad(go); _instance = go.AddComponent(); } Material _mat; bool _open; Vector2 _scroll; GUIStyle _wrap; Material Mat { get { if (_mat == null) _mat = AssetDatabase.LoadAssetAtPath(MaterialPath); return _mat; } } GUIStyle Wrap => _wrap ??= new GUIStyle(GUI.skin.label) { wordWrap = true, fontSize = 10 }; void OnDisable() => AimPresentation.ForceCursorVisible = false; void Update() { var kb = UnityEngine.InputSystem.Keyboard.current; // legacy Input throws under active Input System handling if (kb != null && kb.f3Key.wasPressedThisFrame) _open = !_open; } void OnGUI() { if (GUI.Button(new Rect(10, 10, 128, 24), _open ? "PIXEL ▲ (F3)" : "PIXEL ▼ (F3)")) _open = !_open; if (!_open) return; AimPresentation.ForceCursorVisible = true; var mat = Mat; if (mat == null) { GUILayout.BeginArea(new Rect(10, 40, 270, 64), GUI.skin.box); GUILayout.Label("PixelOutline.mat not found at\n" + MaterialPath, Wrap); GUILayout.EndArea(); return; } float panelH = Mathf.Min(660f, Screen.height - 60f); GUILayout.BeginArea(new Rect(10, 40, 272, panelH), GUI.skin.box); GUILayout.Label("PIXEL-ART RENDER STYLE (live)"); _scroll = GUILayout.BeginScrollView(_scroll); Toggle(mat, "_MasterEnabled", "MASTER (whole effect on/off)"); GUILayout.Space(6); GUILayout.Label("- Toggles -"); Toggle(mat, "_PixelateEnabled", "Pixelate"); Toggle(mat, "_PosterizeEnabled", "Posterize (banded color)"); Toggle(mat, "_DepthEdgesEnabled", "Depth edges (silhouette)"); Toggle(mat, "_NormalEdgesEnabled", "Normal edges (creases)"); GUILayout.Space(6); GUILayout.Label("- Pixelation -"); Slider(mat, "_PixelHeight", "Grid height (px)", 40f, 720f, "0"); GUILayout.Space(6); GUILayout.Label("- Color / tone -"); Slider(mat, "_ColorLevels", "Posterize levels", 2f, 64f, "0"); Slider(mat, "_Brightness", "Brightness", 0.5f, 2f, "0.00"); Slider(mat, "_Contrast", "Contrast", 0.5f, 2f, "0.00"); Slider(mat, "_Saturation", "Saturation", 0f, 2f, "0.00"); GUILayout.Space(6); GUILayout.Label("- Outlines -"); Slider(mat, "_DepthThreshold", "Depth threshold", 0f, 1f, "0.000"); Slider(mat, "_NormalThreshold", "Normal threshold", 0f, 4f, "0.00"); Slider(mat, "_OutlineThickness", "Thickness (texels)", 0.25f, 4f, "0.00"); Slider(mat, "_EdgeStrength", "Edge strength", 0f, 1f, "0.00"); ColorRow(mat, "_OutlineColorOuter", "Silhouette color"); ColorRow(mat, "_OutlineColorInner", "Crease color"); GUILayout.Space(8); GUILayout.BeginHorizontal(); if (GUILayout.Button("Reset defaults")) ResetDefaults(mat); if (GUILayout.Button("Save Asset")) SaveAsset(mat); GUILayout.EndHorizontal(); GUILayout.Label("Drags apply live and persist after Play. 'Save Asset' writes to disk now.", Wrap); GUILayout.EndScrollView(); GUILayout.EndArea(); } static void Toggle(Material mat, string prop, string label) { bool cur = mat.GetFloat(prop) > 0.5f; bool now = GUILayout.Toggle(cur, " " + label); if (now != cur) { mat.SetFloat(prop, now ? 1f : 0f); EditorUtility.SetDirty(mat); } } static void Slider(Material mat, string prop, string label, float min, float max, string fmt) { float cur = mat.GetFloat(prop); GUILayout.BeginHorizontal(); GUILayout.Label(label, GUILayout.Width(126)); GUILayout.Label(cur.ToString(fmt), GUILayout.Width(48)); GUILayout.EndHorizontal(); float now = GUILayout.HorizontalSlider(cur, min, max); if (!Mathf.Approximately(now, cur)) { mat.SetFloat(prop, now); EditorUtility.SetDirty(mat); } } static void ColorRow(Material mat, string prop, string label) { Color c = mat.GetColor(prop); GUILayout.Label(label); Color n = c; n.r = ChannelSlider("R", c.r); n.g = ChannelSlider("G", c.g); n.b = ChannelSlider("B", c.b); if (n != c) { n.a = 1f; mat.SetColor(prop, n); EditorUtility.SetDirty(mat); } } static float ChannelSlider(string label, float v) { GUILayout.BeginHorizontal(); GUILayout.Label(label, GUILayout.Width(14)); float r = GUILayout.HorizontalSlider(v, 0f, 1f); GUILayout.EndHorizontal(); return r; } // Shipped defaults from PixelOutline.shader / .mat. static void ResetDefaults(Material mat) { mat.SetFloat("_MasterEnabled", 1f); mat.SetFloat("_PixelHeight", 200f); mat.SetFloat("_PosterizeEnabled", 1f); mat.SetFloat("_ColorLevels", 12f); mat.SetFloat("_Brightness", 1.1f); mat.SetFloat("_Contrast", 1.05f); mat.SetFloat("_Saturation", 1.35f); mat.SetFloat("_DepthThreshold", 0.08f); mat.SetFloat("_NormalThreshold", 1f); mat.SetFloat("_OutlineThickness", 1f); mat.SetFloat("_EdgeStrength", 1f); mat.SetFloat("_PixelateEnabled", 1f); mat.SetFloat("_DepthEdgesEnabled", 1f); mat.SetFloat("_NormalEdgesEnabled", 1f); mat.SetColor("_OutlineColorOuter", Color.black); mat.SetColor("_OutlineColorInner", Color.black); EditorUtility.SetDirty(mat); } static void SaveAsset(Material mat) { EditorUtility.SetDirty(mat); AssetDatabase.SaveAssetIfDirty(mat); } } } #endif