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]>
This commit is contained in:
@@ -233,6 +233,16 @@ Assets/Scenes/
|
||||
`Bootstrap` must stay index 0 in the build settings. The level is made the active scene after
|
||||
load, so new objects and lighting land there rather than in Bootstrap.
|
||||
|
||||
**Working in the Editor.** `Tools ▸ Nightclub Arcadia ▸ Open Game Scenes` opens all three at
|
||||
once, with the level active. Do that before pressing Play.
|
||||
|
||||
If you press Play with only `Bootstrap` open, `GameBootstrap` loads the other two *at runtime*,
|
||||
and Unity discards runtime-loaded scenes when Play ends — the Hierarchy comes back with Bootstrap
|
||||
alone and it looks as though the scenes were destroyed. **Nothing is written to disk**; it is the
|
||||
Hierarchy, not the files. With all three already open, `GameBootstrap` skips loading them
|
||||
(`LoadIfNeeded`) and they are still there afterwards. `GameSceneWorkflow` also captures and
|
||||
restores the scene setup around Play as a safety net; toggle it under the same menu.
|
||||
|
||||
```
|
||||
writing/
|
||||
├── README.md session entry point
|
||||
|
||||
@@ -52,7 +52,12 @@ build: lock ## Build a macOS player into build/
|
||||
-o $(CURDIR)/build/NightclubArcadia.app
|
||||
|
||||
lock: ## Fail if the Unity Editor has the project open
|
||||
@if pgrep -fl "Unity.app/Contents/MacOS/Unity" | grep -v AssetImportWorker | grep -q .; then \
|
||||
# pgrep -x matches the process NAME only. The previous `pgrep -f` on the full
|
||||
# binary path matched this very shell, because the pattern appears in its own
|
||||
# command line — the check reported the Editor running when nothing was.
|
||||
# AssetImportWorkers share the name but only exist alongside an Editor, so
|
||||
# matching them is correct rather than a false positive.
|
||||
@if pgrep -x Unity > /dev/null 2>&1; then \
|
||||
echo "The Unity Editor is running — close it first (only one instance per project)."; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e05a1f05ef74ff7a53f6e24ab37e51e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user