Files
nightclub-arcadia/NightclubArcadia/Assets/Editor/GameSceneWorkflow.cs
T
lennartandClaude Opus 5 2a20bd68fe add an Open Game Scenes menu, and fix the Editor-lock check
The scenes were never destroyed. Pressing Play with only Bootstrap open means
GameBootstrap loads Systems and the level at runtime, and Unity discards
runtime-loaded scenes when Play ends, restoring whatever the Editor had open —
Bootstrap alone. The Hierarchy comes back nearly empty. Every scene file was
byte-identical to HEAD throughout, still text.

Tools > Nightclub Arcadia > Open Game Scenes opens all three with the level
active, which is the authoring setup. With them already open, GameBootstrap's
LoadIfNeeded skips them and they survive Play. GameSceneWorkflow also captures
the scene setup before Play and restores it afterwards as a safety net, skipping
the restore when Unity already got it right and ignoring scenes deleted meanwhile
— an exception in that callback would leave the Editor in a worse state than the
problem it fixes. Toggleable under the same menu.

Also fixes `make lock`, which had a self-matching bug since Phase 1: `pgrep -f`
on the full binary path matched the very shell running the check, because the
pattern appears in its own command line. It reported the Editor as running when
nothing was, and would have blocked every make test/build once anything else
matched. Now `pgrep -x Unity`, which matches the process name and cannot match
the shell.

EditMode 42/42, PlayMode 6/6, YarnCheck 9 files / 35 nodes.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-25 21:35:36 +02:00

130 lines
4.8 KiB
C#

using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
namespace NightclubArcadia.EditorTools
{
/// <summary>
/// Makes the split-scene layout usable in the Editor.
///
/// The game runs from Bootstrap, which loads Systems and a level additively AT
/// RUNTIME. Unity discards runtime-loaded scenes when Play ends and restores
/// whatever the Editor had open, which for someone who pressed Play in Bootstrap is
/// Bootstrap alone. The Hierarchy comes back nearly empty and it looks as though the
/// scenes were destroyed. They are not — nothing is written to disk — but it is an
/// unpleasant way to work.
///
/// Two fixes, both opt-outable:
/// - Open Game Scenes puts all three in the Hierarchy for authoring.
/// - The Editor's scene setup is captured before Play and restored after, so what
/// you had open is what you get back.
///
/// GameBootstrap skips any scene that is already loaded, so having all three open
/// and pressing Play does the right thing rather than loading anything twice.
/// </summary>
[InitializeOnLoad]
public static class GameSceneWorkflow
{
const string RestorePrefKey = "NightclubArcadia.RestoreSceneSetup";
const string RestoreMenu = "Tools/Nightclub Arcadia/Restore Scene Setup After Play";
static SceneSetup[] setupBeforePlay;
static GameSceneWorkflow()
{
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}
[MenuItem("Tools/Nightclub Arcadia/Open Game Scenes")]
public static void OpenGameScenes()
{
// The prompt cannot be shown in batch mode, where it returns false and would
// make this menu a silent no-op.
if (!Application.isBatchMode && !EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
{
return;
}
foreach (var path in new[] { ScenePaths.Bootstrap, ScenePaths.Systems, ScenePaths.WorkingLevel })
{
if (!File.Exists(path))
{
Debug.LogError($"[Scenes] Missing: {path}");
return;
}
}
EditorSceneManager.OpenScene(ScenePaths.Bootstrap, OpenSceneMode.Single);
EditorSceneManager.OpenScene(ScenePaths.Systems, OpenSceneMode.Additive);
var level = EditorSceneManager.OpenScene(ScenePaths.WorkingLevel, OpenSceneMode.Additive);
// The level owns the lighting and is where new objects should land.
EditorSceneManager.SetActiveScene(level);
Debug.Log("[Scenes] Bootstrap + Systems + SC101_ConferenceHall open. Level is active.");
}
[MenuItem(RestoreMenu)]
static void ToggleRestore()
{
var on = !EditorPrefs.GetBool(RestorePrefKey, true);
EditorPrefs.SetBool(RestorePrefKey, on);
Debug.Log($"[Scenes] Restore scene setup after Play: {(on ? "on" : "off")}");
}
[MenuItem(RestoreMenu, true)]
static bool ToggleRestoreValidate()
{
Menu.SetChecked(RestoreMenu, EditorPrefs.GetBool(RestorePrefKey, true));
return true;
}
static void OnPlayModeStateChanged(PlayModeStateChange state)
{
if (!EditorPrefs.GetBool(RestorePrefKey, true))
{
return;
}
if (state == PlayModeStateChange.ExitingEditMode)
{
setupBeforePlay = EditorSceneManager.GetSceneManagerSetup();
return;
}
if (state != PlayModeStateChange.EnteredEditMode || setupBeforePlay == null)
{
return;
}
var setup = setupBeforePlay;
setupBeforePlay = null;
// A scene deleted or renamed while Play was running would throw here, and
// an exception in this callback leaves the Editor in a confusing state.
var usable = setup.Where(s => File.Exists(s.path)).ToArray();
if (usable.Length == 0)
{
return;
}
var current = EditorSceneManager.GetSceneManagerSetup();
if (current.Length == usable.Length &&
current.Select(s => s.path).SequenceEqual(usable.Select(s => s.path)))
{
return; // already what we wanted; do not disturb it
}
try
{
EditorSceneManager.RestoreSceneManagerSetup(usable);
}
catch (System.Exception e)
{
Debug.LogWarning($"[Scenes] Could not restore the pre-Play scene setup: {e.Message}");
}
}
}
}