In this lesson, you will learn how to replace fragile MonoBehaviour singletons with ScriptableObject global managers to create persistent, scene-independent data and logic containers.
Moving Beyond the Singleton
In your previous work with singletons, you likely encountered the "Don't Destroy On Load" dance—manually managing scene transitions, dealing with initialization order, and fighting the DontDestroyOnLoad hierarchy clutter. By shifting your global managers to ScriptableObject, you decouple your architectural logic from the active scene's lifecycle.
A ScriptableObject exists in your project’s memory independently of any specific scene. When you reference it in a component, you are pointing to a shared data asset rather than a scene-bound object.
Implementing the Global Manager
To create a global manager, define it as a ScriptableObject instead of a MonoBehaviour. Consider a GameSettingsManager:
[CreateAssetMenu(menuName = "Managers/GameSettings")]
public class GameSettingsManager : ScriptableObject
{
public float masterVolume = 1.0f;
public bool isMusicEnabled = true;
public void ToggleMusic() => isMusicEnabled = !isMusicEnabled;
}
Because this is a ScriptableObject, you don't need a "Manager GameObject" in your scene. Instead, you create an instance of this asset in your Project folder. Any MonoBehaviour—whether it’s a UI slider or an Audio Player—can hold a reference to this specific asset:
public class MusicController : MonoBehaviour
{
public GameSettingsManager settings; // Drag the asset here in Inspector
void Start()
{
if (settings.isMusicEnabled) PlayMusic();
}
}
Architectural Benefits
This approach provides three distinct advantages:
- Persistence: The state of your
ScriptableObjectpersists across scene loads, making it an ideal place for cross-scene data storage. - Editor Debugging: You can inspect the
ScriptableObjectasset in the Project window while the game is running to see your global state change in real-time. - Reduced Boilerplate: You eliminate the need for
Instancechecks,Awakeinitialization, and potential race conditions caused byDontDestroyOnLoadobjects attempting to find each other.
Key Takeaway: Moving your global logic to ScriptableObjects decouples your system state from the scene hierarchy, allowing for a cleaner, more modular architecture that survives scene transitions naturally.