211 lines
5.8 KiB
Markdown
211 lines
5.8 KiB
Markdown
|
|
# OnSceneReady() Not Called - Root Cause Analysis & Fix
|
||
|
|
|
||
|
|
## Problem
|
||
|
|
|
||
|
|
Two ManagedBehaviours were not receiving their `OnSceneReady()` callbacks:
|
||
|
|
1. **PuzzleManager** (bootstrapped singleton in DontDestroyOnLoad)
|
||
|
|
2. **LevelSwitch** (in-scene component)
|
||
|
|
|
||
|
|
## Root Cause
|
||
|
|
|
||
|
|
### The Design of OnSceneReady()
|
||
|
|
|
||
|
|
`LifecycleManager.BroadcastSceneReady(sceneName)` only calls `OnSceneReady()` on components **IN the specific scene being loaded**:
|
||
|
|
|
||
|
|
```csharp
|
||
|
|
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:
|
||
|
|
```csharp
|
||
|
|
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:
|
||
|
|
1. That it's being registered
|
||
|
|
2. That the scene name matches
|
||
|
|
3. 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):**
|
||
|
|
```csharp
|
||
|
|
protected override void OnSceneReady()
|
||
|
|
{
|
||
|
|
// Never called because PuzzleManager is in DontDestroyOnLoad!
|
||
|
|
string sceneName = SceneManager.GetActiveScene().name;
|
||
|
|
LoadPuzzlesForScene(sceneName);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**After (Fixed):**
|
||
|
|
```csharp
|
||
|
|
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:
|
||
|
|
```csharp
|
||
|
|
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:
|
||
|
|
```csharp
|
||
|
|
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
|
||
|
|
|
||
|
|
1. **PuzzleManager.cs**
|
||
|
|
- Removed `OnSceneReady()` override
|
||
|
|
- Added subscription to `SceneLoadCompleted` event in `OnManagedAwake()`
|
||
|
|
- Added `OnSceneLoadCompleted()` callback
|
||
|
|
- Added proper cleanup in `OnDestroy()`
|
||
|
|
|
||
|
|
2. **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.
|
||
|
|
|