115 lines
4.1 KiB
Markdown
115 lines
4.1 KiB
Markdown
# 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:**
|
|
|
|
1. **Phase 8** in `SceneManagerService.SwitchSceneAsync()`:
|
|
```csharp
|
|
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
|
|
|
|
2. **Phase 9** in `SceneManagerService.SwitchSceneAsync()`:
|
|
```csharp
|
|
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!
|
|
|
|
### 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
|
|
- `currentSceneReady` is 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`:
|
|
|
|
```csharp
|
|
// 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
|
|
|
|
1. Components register during scene load (after Awake())
|
|
2. `currentSceneReady` might not be set yet
|
|
3. BUT `scene.isLoaded` returns true because Unity has already loaded the scene
|
|
4. OnSceneReady() gets called immediately during registration
|
|
5. 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
|
|
|
|
1. **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.LoadSceneAsync` completes
|
|
- **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!
|
|
|