Files
AppleHillsProduction/docs/onsceneready_not_called_analysis.md

211 lines
5.8 KiB
Markdown
Raw Normal View History

# 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.