diff --git a/CLAUDE.md b/CLAUDE.md index c7ca0b4..d62ddf7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/Makefile b/Makefile index 365b0f5..6b184f5 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/NightclubArcadia/Assets/Editor/GameSceneWorkflow.cs b/NightclubArcadia/Assets/Editor/GameSceneWorkflow.cs new file mode 100644 index 0000000..f34469f --- /dev/null +++ b/NightclubArcadia/Assets/Editor/GameSceneWorkflow.cs @@ -0,0 +1,129 @@ +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; + +namespace NightclubArcadia.EditorTools +{ + /// + /// 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. + /// + [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}"); + } + } + } +} diff --git a/NightclubArcadia/Assets/Editor/GameSceneWorkflow.cs.meta b/NightclubArcadia/Assets/Editor/GameSceneWorkflow.cs.meta new file mode 100644 index 0000000..7578b91 --- /dev/null +++ b/NightclubArcadia/Assets/Editor/GameSceneWorkflow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0e05a1f05ef74ff7a53f6e24ab37e51e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: