Scene Management Patterns
Scene Group Setup
Create Scene Group Asset
- •Right-click in Project:
Create > Scriptable Objects > Scene Management > Scene Group - •Add scenes to the
Sceneslist - •Assign correct
SceneTypefor each scene - •Configure
Persistentflag appropriately
Scene Type Rules
- •Boot: Not persistent, loaded once
- •PersistentSystems: Persistent, always loaded
- •AppShell: Persistent, main app root
- •GameplayBase: Not persistent, loaded with gameplay
- •World: Not persistent, level geometry
- •Overlay: Not persistent, temporary UI
Scene Group Configuration
Core Group (SG_Core)
csharp
// Scene Group contains: // - SC_Boot (Boot, Persistent: false) // - SC_PersistentSystems (PersistentSystems, Persistent: true) // - SC_AppShell (AppShell, Persistent: true)
Gameplay Group (SG_Gameplay)
csharp
// Scene Group contains: // - SC_GameplayBase (GameplayBase, Persistent: false) // - SC_World_Gameplay (World, Persistent: false)
Overlay Group (SG_Overlays)
csharp
// Scene Group contains: // - SC_Overlay_HUD (Overlay, Persistent: false) // - SC_Overlay_Popups (Overlay, Persistent: false)
Loading Scenes
Load Scene Group
csharp
public async UniTask LoadSceneGroupAsync(SceneGroup sceneGroup)
{
try
{
await SceneGroupManager.Instance.LoadSceneGroupAsync(sceneGroup);
Debug.Log($"[SceneLoader] Loaded scene group: {sceneGroup.name}");
}
catch (Exception e)
{
Debug.LogError($"[SceneLoader] Failed to load scene group: {e.Message}");
}
}
Startup Flow
csharp
// 1. Load Core Group await LoadSceneGroupAsync(SG_Core); // 2. Wait for systems to initialize await WaitForSystemsReady(); // 3. Unload Boot scene await UnloadSceneAsync(SC_Boot);
Scene Lifecycle
Scene Loaded
csharp
public class GameplaySceneController : MonoBehaviour
{
void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (scene.name == "SC_GameplayBase")
{
InitializeGameplay();
}
}
}
Scene Unloaded
csharp
void OnDisable()
{
SceneManager.sceneUnloaded += OnSceneUnloaded;
}
void OnSceneUnloaded(Scene scene)
{
if (scene.name == "SC_World_Gameplay")
{
CleanupGameplay();
}
}
Persistent Scenes
Managers in PersistentSystems
csharp
public class DataManager : MonoBehaviour
{
public static DataManager Instance { get; private set; }
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject); // Persists across scenes
}
}
UI in AppShell
csharp
public class UIManager : MonoBehaviour
{
// Persists across gameplay
// Main menu always accessible
// Returns here after match ends
}
Scene Transitions
To Gameplay
csharp
public async UniTask StartMatchAsync()
{
// Load gameplay scenes
await SceneGroupManager.Instance.LoadSceneGroupAsync(SG_Gameplay);
await SceneGroupManager.Instance.LoadSceneGroupAsync(SG_Overlays);
// Initialize match
await MatchManager.Instance.StartMatchAsync();
}
From Gameplay
csharp
public async UniTask EndMatchAsync()
{
// Show end screen
UIManager.Instance.ShowMatchEndScreen();
// Unload gameplay scenes
await SceneGroupManager.Instance.UnloadSceneGroupAsync(SG_Gameplay);
await SceneGroupManager.Instance.UnloadSceneGroupAsync(SG_Overlays);
// Return to AppShell (already loaded)
}
Validation Rules
Scene Group Validation
- •Core group must have exactly one Boot, PersistentSystems, AppShell
- •Gameplay group must have exactly one GameplayBase, World
- •Overlay scenes must not be persistent
- •Boot scene must not be persistent
Runtime Validation
csharp
public void ValidateSceneGroup(SceneGroup group)
{
foreach (var scene in group.Scenes)
{
// Check persistence rules
if (scene.Type == SceneType.Boot && scene.Persistent)
{
Debug.LogError("Boot scene cannot be persistent!");
}
if (scene.Type == SceneType.AppShell && !scene.Persistent)
{
Debug.LogError("AppShell must be persistent!");
}
}
}
Common Patterns
Wait for Scene Loaded
csharp
public async UniTask WaitForSceneLoadedAsync(string sceneName)
{
while (!IsSceneLoaded(sceneName))
{
await UniTask.Yield();
}
}
bool IsSceneLoaded(string sceneName)
{
for (int i = 0; i < SceneManager.sceneCount; i++)
{
if (SceneManager.GetSceneAt(i).name == sceneName)
{
return true;
}
}
return false;
}
Find Objects in Scene
csharp
public T FindObjectInScene<T>(string sceneName) where T : Component
{
var scene = SceneManager.GetSceneByName(sceneName);
if (!scene.IsValid()) return null;
var rootObjects = scene.GetRootGameObjects();
foreach (var obj in rootObjects)
{
var component = obj.GetComponentInChildren<T>();
if (component != null)
return component;
}
return null;
}
Best Practices
Scene Organization
- •Keep related systems in same scene
- •Use additive loading for overlays
- •Unload scenes when not needed
- •Never load Boot scene after startup
Performance
- •Load scenes asynchronously
- •Show loading progress
- •Preload frequently used scenes
- •Unload unused scenes to free memory
Error Handling
- •Validate scene exists before loading
- •Handle load failures gracefully
- •Provide user feedback during loading
- •Fallback to safe state on error