4.1 KiB
OnSceneReady Not Called for LevelSwitch - Root Cause & Fix
Problem Identified from Logs
[LevelSwitch] Awake called for CementFactory in scene AppleHillsOverworld
[LevelSwitch] OnManagedAwake called for CementFactory
✅ Awake() called
✅ OnManagedAwake() called
❌ OnSceneReady() NEVER called
Root Cause Analysis
The Timing Issue
Looking at the stack trace, OnManagedAwake() is being called during registration at LifecycleManager line 125, which is the "late registration" code path (boot already complete).
The sequence that breaks OnSceneReady:
-
Phase 8 in
SceneManagerService.SwitchSceneAsync():await LoadSceneAsync(newSceneName, progress);- Unity loads the scene
- LevelSwitch.Awake() runs
- LevelSwitch calls base.Awake()
- ManagedBehaviour.Awake() calls LifecycleManager.Register()
- LifecycleManager checks:
if (currentSceneReady == sceneName)→ FALSE (not set yet!) - OnSceneReady() NOT called
-
Phase 9 in
SceneManagerService.SwitchSceneAsync():LifecycleManager.Instance?.BroadcastSceneReady(newSceneName);- Sets
currentSceneReady = sceneName - Broadcasts to all components in sceneReadyList
- But LevelSwitch was already checked in step 1, so it's skipped!
- Sets
The Gap
Between when LoadSceneAsync() completes (scene loaded, Awake() called) and when BroadcastSceneReady() is called, there's a timing gap where:
- Components register with LifecycleManager
currentSceneReadyis NOT yet set to the new scene name- Late registration check fails:
currentSceneReady == sceneName→ false - Components miss their OnSceneReady() call
The Fix
Modified LifecycleManager.Register() to check if a scene is actually loaded via Unity's SceneManager, not just relying on currentSceneReady:
// If this scene is already ready, call OnSceneReady immediately
// Check both currentSceneReady AND if the Unity scene is actually loaded
// (during scene loading, components Awake before BroadcastSceneReady is called)
bool sceneIsReady = currentSceneReady == sceneName;
// Also check if this is happening during boot and the scene is the active scene
// This handles components that register during initial scene load
if (!sceneIsReady && isBootComplete && sceneName != "DontDestroyOnLoad")
{
var scene = UnityEngine.SceneManagement.SceneManager.GetSceneByName(sceneName);
sceneIsReady = scene.isLoaded;
}
if (sceneIsReady)
{
LogDebug($"Late registration: Calling OnSceneReady immediately for {component.gameObject.name}");
component.InvokeSceneReady();
}
Why This Works
- Components register during scene load (after Awake())
currentSceneReadymight not be set yet- BUT
scene.isLoadedreturns true because Unity has already loaded the scene - OnSceneReady() gets called immediately during registration
- Components get their lifecycle hook even though they register between load and broadcast
Expected Behavior After Fix
When playing the game, you should now see:
[LevelSwitch] Awake called for CementFactory in scene AppleHillsOverworld
[LifecycleManager] Late registration: Calling OnManagedAwake immediately for CementFactory
[LevelSwitch] OnManagedAwake called for CementFactory
[LifecycleManager] Late registration: Calling OnSceneReady immediately for CementFactory
[LevelSwitch] OnSceneReady called for CementFactory ← THIS IS NEW!
Files Modified
- LifecycleManager.cs - Enhanced late registration check to verify Unity scene load status
Design Insight
This reveals an important timing consideration:
During scene loading:
- Unity loads the scene
- All Awake() methods run (including base.Awake() for ManagedBehaviours)
- Components register with LifecycleManager
SceneManager.LoadSceneAsynccompletes- THEN SceneManagerService calls BroadcastSceneReady()
There's an inherent gap between Unity's scene load completion and our lifecycle broadcast. The fix handles this by checking Unity's actual scene state, not just our tracking variable.
Status: ✅ FIXED - OnSceneReady will now be called for all in-scene ManagedBehaviours, even during late registration!