Rework of base interactables and managed behaviors
This commit is contained in:
210
docs/onsceneready_not_called_analysis.md
Normal file
210
docs/onsceneready_not_called_analysis.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# 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.
|
||||
|
||||
Reference in New Issue
Block a user