Hi everybody.
This question has been asked many times in many forums and Unity answers. Anyway, I have a solution to suggest for this “problem”… and a question about a strange behaviour in the editor. Let me explain.
I’ll talk about ScriptableObject singletons, a super cool pattern to avoid prefabs in the scene just to store resources to spawn or game settings, and so avoid Game Designers forgot to put them in scene or put prefabs that we don’t want in scene (I love game designers, please don’t be mad at me :p).
I learned many tips in this video about right uses and best practices of ScriptableObjects. But I would like to focus on this particular sequence, about the “Reload-Proof Singleton”.
Problem :
To illustrate my captivating story, make a new Unity project, and create the two following scripts. DemoConfig implements the Reload-proof singleton pattern, and GUILogger is just to get feedback of the problem.
using UnityEngine;
[CreateAssetMenu(fileName = "Demo Config", menuName = "Demo Config", order = 750)]
public class DemoConfig : ScriptableObject
{
private static DemoConfig s_Instance = null;
private void OnEnable()
{
Debug.Log("DemoConfig -> OnEnable()");
}
public static DemoConfig GetInstance()
{
if (!s_Instance)
{
DemoConfig[] all = Resources.FindObjectsOfTypeAll<DemoConfig>();
s_Instance = (all.Length > 0) ? all[0] : null;
}
if (!s_Instance)
{
s_Instance = CreateInstance<DemoConfig>();
s_Instance.name = "Default";
}
return s_Instance;
}
}
using UnityEngine;
public class GUILog : MonoBehaviour
{
private void OnGUI()
{
DemoConfig config = DemoConfig.GetInstance();
GUILayout.Box("Config : " + config.name);
}
}
There’s some few steps left :
- Make a “Resources” folder in yout project Assets
- Create a “Demo Config” asset in it (Assets > Create > Demo Config)
- Attach the GUILogger to the default “Main Camera” (or anything else in the scene, of course)
- Play the game
You should see a little GUI box in the top-left corner of the Game View with the message “Config : Demo Config”. Great, GUILog can access to the DemoConfig asset in project folder without referencing it in its properties or using a path to that resource.
But it’s not that easy… Save your scene, close Unity and reopen your project. Don’t click anywhere but on the Play button… So, what’s in the GUI Box now ? Are you still smiling ? In fact, the Reload-proof singleton pattern is not designed for this.
In forums, I saw that many people thought that this pattern is to make ScriptableObject singletons in their project resources. But, it’s not really the objective : this pattern is to make Reload-proof singletons. It means that the object has to be loaded once, to be found in resources… and that’s why you see the “default” DemoConfig asset name.
In the video, Richard Fine explains that the problem is about serialization processes when you reload your project with statics variables initialized at runtime. And if you’re a regular consumer of singletons, you must have experienced null ref errors at hot reload.
The Reload-proof singleton patterns is an answer to that.
Now you could tell me “but why the fuuf did it work the first time ???”. A really good question… It worked because you loaded the asset. Go to your Unity project, just click on the Demo Config asset. You’ll see it in the inspector… I mean, the inspector will load it. Then, run the game…
… Yeah, exactly. Note that if you build the game and start it, you’ll see “Default” in the GUI box.
It means that the method Resources.FindObjectsOfTypeAll() works only for loaded resources, as explained in documentation :
And resources in Resources folder are obviously not loaded “by default”.
Solution :
So, what can we do ? Use Resources.Load() ? Nope, we still need to write a path, and change it when the asset is moved… but if I want to use the same script in another project, you’ll need to change that path another time.
We could use a prefab that store the asset and put it in the scene… STOP, DON’T DO THAT ANYMORE ! You have so many chances to get issues and time wasting to solve them with this kind of patterns… Richard Fine talks about it too in the video.
Someone told me that put DemoConfig script before “Default time” in “Script Execution Order” will solve the problem… It doesn’t at all : this will not load the resource anyway…
The solution is, in fact, really simple :
- Go to Edit > Project Settings > Player
- Open “Other settings” tab
- Add an entry in the “Preloaded Assets” array and place your Demo Config asset in it
You can now see that it works in the Editor AND in the built game… That’s a success, you now have an asset where you can store the objects to spawn, your game settings without pollute your scene or load assets or weird prefabs manually.
Bug or feature ?
But it still remains a problem. And I don’t really know if this is a bug or not in the Unity Editor.
Close the editor and reopen the project with the Demo Config asset in the “Preloaded Assets” array. If you run the game in the editor… There’s still no Demo Config asset loaded (we see “Default” in GUI box).
Is that normal ? You can easily notice that if you build and run the game, the resource will be loaded successfully.
My questions are : is it an expected behaviour that “Preloaded Assets” is not used in the editor ? If yes, how to preload assets in the editor ?
For now, my solution is to rewrite the DemoConfig.GetInstance() method :
public static DemoConfig GetInstance()
{
if (!s_Instance)
{
DemoConfig[] all = Resources.FindObjectsOfTypeAll<DemoConfig>();
s_Instance = (all.Length > 0) ? all[0] : null;
}
#if UNITY_EDITOR
if(!s_Instance)
{
string[] configsGUIDs = UnityEditor.AssetDatabase.FindAssets("t:" + typeof(DemoConfig).Name);
if (configsGUIDs.Length > 0)
{
s_Instance = Resources.Load<DemoConfig>(UnityEditor.AssetDatabase.GUIDToAssetPath(configsGUIDs[0]));
}
}
#endif
if (!s_Instance)
{
s_Instance = CreateInstance<DemoConfig>();
s_Instance.name = "Default";
}
return s_Instance;
}
If we’re in UnityEditor, the path to the resource is get with UnityEditor.AssetDatabase.GUIToAssetPath() and used to load the resource with Resources.Load().
I hope this (too) long topic helped some devs… Please tell about what you think of this pattern (good/bad practice, advantages, issues, …), answer my questions, improve my solutions, suggest new ones, or just tell me if this topic was useful to you ![]()