(Feat) Add Build
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ashwild.EditorTools
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[InitializeOnLoad]
|
||||
public static class FullscreenOnPlay
|
||||
{
|
||||
#region Constants
|
||||
|
||||
/// <summary>
|
||||
/// The checkable menu entry that flips the feature on and off.
|
||||
/// </summary>
|
||||
private const string MenuPath = "Tools/Fullscreen On Play (OBS)";
|
||||
|
||||
/// <summary>
|
||||
/// EditorPrefs key holding the toggle state. Per-user, per-machine — a personal recording setting,
|
||||
/// deliberately not shared through the project.
|
||||
/// </summary>
|
||||
private const string PrefKey = "Ashwild.FullscreenOnPlay.Enabled";
|
||||
|
||||
#endregion
|
||||
|
||||
#region State
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private static EditorWindow fullscreenView;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
static FullscreenOnPlay()
|
||||
{
|
||||
EditorApplication.playModeStateChanged -= HandlePlayModeStateChanged;
|
||||
EditorApplication.playModeStateChanged += HandlePlayModeStateChanged;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Menu
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
[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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Keeps the menu checkmark in sync with the stored preference every time the menu is opened.
|
||||
/// </summary>
|
||||
[MenuItem(MenuPath, true)]
|
||||
private static bool ToggleValidate()
|
||||
{
|
||||
Menu.SetChecked(MenuPath, IsEnabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Play Mode Hook
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private static void HandlePlayModeStateChanged(PlayModeStateChange state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case PlayModeStateChange.EnteredPlayMode:
|
||||
if (IsEnabled) EnterFullscreen();
|
||||
break;
|
||||
case PlayModeStateChange.ExitingPlayMode:
|
||||
ExitFullscreen();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fullscreen Control
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private static void ExitFullscreen()
|
||||
{
|
||||
EditorApplication.delayCall -= ReassertPosition;
|
||||
|
||||
if (fullscreenView == null) return;
|
||||
|
||||
fullscreenView.Close();
|
||||
fullscreenView = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private static void ReassertPosition()
|
||||
{
|
||||
EditorApplication.delayCall -= ReassertPosition;
|
||||
if (fullscreenView == null) return;
|
||||
|
||||
fullscreenView.position = MainDisplayRect();
|
||||
fullscreenView.Focus();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Helpers
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the fullscreen-on-play toggle is currently enabled.
|
||||
/// </summary>
|
||||
private static bool IsEnabled => EditorPrefs.GetBool(PrefKey, false);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user