using System; using System.Reflection; using UnityEditor; using UnityEngine; namespace Ashwild.EditorTools { /// /// Editor-only capture helper: an on/off toggle under Tools ▸ Ashwild that, when enabled, forces the /// Game view into a borderless fullscreen window as soon as Play Mode starts — so the running game /// looks exactly like a shipped build (no editor chrome, no toolbar) and is clean to record in OBS. /// The preference is checkable in the menu and persists per-machine in EditorPrefs; entering fullscreen /// spawns a dedicated popup Game view on the main display and it is torn down the moment play stops. /// [InitializeOnLoad] public static class FullscreenOnPlay { #region Constants /// /// The checkable menu entry that flips the feature on and off. /// private const string MenuPath = "Tools/Fullscreen On Play (OBS)"; /// /// EditorPrefs key holding the toggle state. Per-user, per-machine — a personal recording setting, /// deliberately not shared through the project. /// private const string PrefKey = "Ashwild.FullscreenOnPlay.Enabled"; #endregion #region State /// /// The live borderless Game view spawned for fullscreen playback, or null when not in fullscreen. /// Held so it can be closed again the instant Play Mode ends. /// private static EditorWindow fullscreenView; #endregion #region Initialization /// /// Hooks the Play Mode state stream once, on editor load and every domain reload, so the fullscreen /// window follows play/stop regardless of how the user triggered it. /// static FullscreenOnPlay() { EditorApplication.playModeStateChanged -= HandlePlayModeStateChanged; EditorApplication.playModeStateChanged += HandlePlayModeStateChanged; } #endregion #region Menu /// /// Flips the toggle and, as a convenience, applies it immediately when already in Play Mode /// (goes fullscreen right away, or drops back to the windowed Game view). /// [MenuItem(MenuPath, false, 200)] private static void Toggle() { bool enabled = !IsEnabled; EditorPrefs.SetBool(PrefKey, enabled); Menu.SetChecked(MenuPath, enabled); if (!EditorApplication.isPlaying) return; if (enabled) EnterFullscreen(); else ExitFullscreen(); } /// /// Keeps the menu checkmark in sync with the stored preference every time the menu is opened. /// [MenuItem(MenuPath, true)] private static bool ToggleValidate() { Menu.SetChecked(MenuPath, IsEnabled); return true; } #endregion #region Play Mode Hook /// /// Enters fullscreen when play begins (only if the toggle is on) and always tears it down as play /// ends — so a leftover popup can never survive back into edit mode. /// private static void HandlePlayModeStateChanged(PlayModeStateChange state) { switch (state) { case PlayModeStateChange.EnteredPlayMode: if (IsEnabled) EnterFullscreen(); break; case PlayModeStateChange.ExitingPlayMode: ExitFullscreen(); break; } } #endregion #region Fullscreen Control /// /// Spawns a dedicated Game view, strips its toolbar and shows it as a borderless popup sized to the /// main display — the closest the editor gets to a real build's fullscreen. The position is /// re-asserted next editor tick because ShowPopup can clamp the initial rect. Reuses nothing from /// the docked layout, so the user's window arrangement is left untouched. /// private static void EnterFullscreen() { if (fullscreenView != null) return; Type gameViewType = typeof(EditorWindow).Assembly.GetType("UnityEditor.GameView"); if (gameViewType == null) { Debug.LogError("[FullscreenOnPlay] Could not resolve UnityEditor.GameView — fullscreen capture unavailable on this Unity version."); return; } fullscreenView = ScriptableObject.CreateInstance(gameViewType) as EditorWindow; if (fullscreenView == null) { Debug.LogError("[FullscreenOnPlay] Failed to create a Game view instance — fullscreen capture aborted."); return; } fullscreenView.titleContent = new GUIContent("Game (Fullscreen)"); SetShowToolbar(fullscreenView, false); Rect fullscreen = MainDisplayRect(); fullscreenView.ShowPopup(); fullscreenView.position = fullscreen; fullscreenView.minSize = fullscreen.size; fullscreenView.maxSize = fullscreen.size; fullscreenView.Focus(); EditorApplication.delayCall += ReassertPosition; Debug.Log("[FullscreenOnPlay] Game view is fullscreen for capture. To exit: press your Play shortcut (Ctrl/Cmd+P), or Alt+Tab back to the Unity editor and press Stop."); } /// /// Closes the fullscreen popup if one is open. Safe to call when nothing is showing (a no-op), /// so both the play-stop hook and the live toggle can lean on it unconditionally. /// private static void ExitFullscreen() { EditorApplication.delayCall -= ReassertPosition; if (fullscreenView == null) return; fullscreenView.Close(); fullscreenView = null; } /// /// Re-applies the fullscreen rect one tick after showing, defeating any clamp ShowPopup applied /// before the window was fully realized. Guards against the window having been closed meanwhile. /// private static void ReassertPosition() { EditorApplication.delayCall -= ReassertPosition; if (fullscreenView == null) return; fullscreenView.position = MainDisplayRect(); fullscreenView.Focus(); } #endregion #region Internal Helpers /// /// The main display's bounds expressed in editor points (pixels ÷ pixelsPerPoint), so the window /// covers the whole monitor correctly even under OS display scaling on high-DPI screens. /// private static Rect MainDisplayRect() { float pixelsPerPoint = Mathf.Max(1f, EditorGUIUtility.pixelsPerPoint); Resolution res = Screen.currentResolution; return new Rect(0f, 0f, res.width / pixelsPerPoint, res.height / pixelsPerPoint); } /// /// Hides (or shows) the Game view's own toolbar via its internal 'showToolbar' property so the /// capture is pure game with no editor UI. Silently degrades if the property is missing on a /// future Unity version — a visible toolbar is a cosmetic loss, not a failure. /// private static void SetShowToolbar(EditorWindow gameView, bool visible) { PropertyInfo prop = gameView.GetType().GetProperty( "showToolbar", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (prop != null && prop.CanWrite) prop.SetValue(gameView, visible); } /// /// Whether the fullscreen-on-play toggle is currently enabled. /// private static bool IsEnabled => EditorPrefs.GetBool(PrefKey, false); #endregion } }