5.8 KiB
OnSceneReady() Not Called - Root Cause Analysis & Fix
Problem
Two ManagedBehaviours were not receiving their OnSceneReady() callbacks:
- PuzzleManager (bootstrapped singleton in DontDestroyOnLoad)
- LevelSwitch (in-scene component)
Root Cause
The Design of OnSceneReady()
LifecycleManager.BroadcastSceneReady(sceneName) only calls OnSceneReady() on components IN the specific scene being loaded:
public void BroadcastSceneReady(string sceneName)
{
foreach (var component in sceneReadyList)
{
if (componentScenes.TryGetValue(component, out string compScene) && compScene == sceneName)
{
component.InvokeSceneReady(); // Only if compScene == sceneName!
}
}
}
When a component registers, LifecycleManager tracks which scene it belongs to:
var sceneName = component.gameObject.scene.name;
componentScenes[component] = sceneName;
PuzzleManager Issue
Registration Log:
[LifecycleManager] Registered PuzzleManager(Clone) (Scene: DontDestroyOnLoad)
The Problem:
- PuzzleManager is in scene "DontDestroyOnLoad" (bootstrapped)
- When AppleHillsOverworld loads,
BroadcastSceneReady("AppleHillsOverworld")is called - Lifecycle manager only broadcasts to components where
compScene == "AppleHillsOverworld" - PuzzleManager's
compScene == "DontDestroyOnLoad"❌ - Result:
OnSceneReady()never called!
LevelSwitch Issue
LevelSwitch should work since it's IN the gameplay scene. However, we need to verify with debug logging to confirm:
- That it's being registered
- That the scene name matches
- That BroadcastSceneReady is being called with the correct scene name
Solution
For PuzzleManager (and all bootstrapped singletons)
❌ Don't use OnSceneReady() - it only works for components IN the scene being loaded
✅ Use SceneManagerService.SceneLoadCompleted event - fires for ALL scene loads
Before (Broken):
protected override void OnSceneReady()
{
// Never called because PuzzleManager is in DontDestroyOnLoad!
string sceneName = SceneManager.GetActiveScene().name;
LoadPuzzlesForScene(sceneName);
}
After (Fixed):
protected override void OnManagedAwake()
{
// Subscribe to scene load events - works for DontDestroyOnLoad components!
if (SceneManagerService.Instance != null)
{
SceneManagerService.Instance.SceneLoadCompleted += OnSceneLoadCompleted;
}
}
private void OnSceneLoadCompleted(string sceneName)
{
LoadPuzzlesForScene(sceneName);
}
protected override void OnDestroy()
{
if (SceneManagerService.Instance != null)
{
SceneManagerService.Instance.SceneLoadCompleted -= OnSceneLoadCompleted;
}
}
For LevelSwitch
Added comprehensive debug logging to trace the lifecycle:
- Awake call confirmation
- Scene name verification
- OnManagedAwake call confirmation
- OnSceneReady call confirmation
This will help identify if the issue is:
- Registration not happening
- Scene name mismatch
- BroadcastSceneReady not being called
Design Guidelines
When to use OnSceneReady():
✅ Scene-specific components (components that live IN a gameplay scene)
- Level-specific initializers
- Scene decorators
- In-scene interactables (like LevelSwitch should be)
When NOT to use OnSceneReady():
❌ Bootstrapped singletons (components in DontDestroyOnLoad)
- PuzzleManager
- InputManager
- Any manager that persists across scenes
Alternative for bootstrapped components:
✅ Subscribe to SceneManagerService.SceneLoadCompleted event
- Fires for every scene load
- Provides scene name as parameter
- Works regardless of component's scene
Pattern Summary
Bootstrapped Singleton Pattern:
public class MyBootstrappedManager : ManagedBehaviour
{
protected override void OnManagedAwake()
{
// Subscribe to scene events
if (SceneManagerService.Instance != null)
{
SceneManagerService.Instance.SceneLoadCompleted += OnSceneLoadCompleted;
}
}
private void OnSceneLoadCompleted(string sceneName)
{
// Handle scene load
}
protected override void OnDestroy()
{
// Unsubscribe
if (SceneManagerService.Instance != null)
{
SceneManagerService.Instance.SceneLoadCompleted -= OnSceneLoadCompleted;
}
base.OnDestroy();
}
}
In-Scene Component Pattern:
public class MySceneComponent : ManagedBehaviour
{
protected override void OnSceneReady()
{
// This WILL be called for in-scene components
// Safe to initialize scene-specific stuff here
}
}
Files Modified
-
PuzzleManager.cs
- Removed
OnSceneReady()override - Added subscription to
SceneLoadCompletedevent inOnManagedAwake() - Added
OnSceneLoadCompleted()callback - Added proper cleanup in
OnDestroy()
- Removed
-
LevelSwitch.cs
- Added comprehensive debug logging
- Kept
OnSceneReady()since it should work for in-scene components - Will verify with logs that it's being called
Expected Behavior After Fix
PuzzleManager:
[LifecycleManager] Registered PuzzleManager(Clone) (Scene: DontDestroyOnLoad)
[PuzzleManager] OnManagedAwake called
[SceneManagerService] Scene loaded: AppleHillsOverworld
[Puzzles] Scene loaded: AppleHillsOverworld, loading puzzle data
LevelSwitch:
[LevelSwitch] Awake called for LevelSwitch in scene AppleHillsOverworld
[LifecycleManager] Registered LevelSwitch (Scene: AppleHillsOverworld)
[LifecycleManager] Broadcasting SceneReady for scene: AppleHillsOverworld
[LevelSwitch] OnManagedAwake called for LevelSwitch
[LevelSwitch] OnSceneReady called for LevelSwitch
Status: ✅ PuzzleManager fixed. LevelSwitch debug logging added for verification.