Unity : Singleton ScriptableObjects resets after play

2.8k views Asked by At

I have a singleton class that contains general information about my game.

public class GeneralGameData : ScriptableObject
{
    private static GeneralGameData _currentGeneralGameData;

    public static GeneralGameData CurrentGeneralGameData
    {
        get
        {
            if (_currentGeneralGameData == null)
            {
                _currentGeneralGameData = CreateInstance<GeneralGameData>();
            }

            DontDestroyOnLoad(_currentGeneralGameData);

            return _currentGeneralGameData;
        }
    }

    public string GameName;

    public string GameVersion;

}

This class has no presents in the scene.

I also have a window to show and change that

 public class GeneralGameDataMenuItem : EditorWindow
{
    [MenuItem("CoreMaker/GeneralGameData")]
    private static void _generalGameData()
    {
        GetWindow<GeneralGameDataMenuItem>("GeneralGameData");
    }

    void OnGUI()
    {
        GeneralGameData.CurrentGeneralGameData.GameName = EditorGUILayout.TextField("GameName", GeneralGameData.CurrentGeneralGameData.GameName);
        GeneralGameData.CurrentGeneralGameData.GameVersion = EditorGUILayout.TextField("GameVersion", GeneralGameData.CurrentGeneralGameData.GameVersion);

        EditorUtility.SetDirty(GeneralGameData.CurrentGeneralGameData);
    }
}

The problem is that it wont save my changes after i hit play or restart unity. any solutions??

3

There are 3 answers

0
Nailuj29 On BEST ANSWER

A ScriptableObject is intended to be saved as an asset. Add the line [CreateAssetMenu(menuName = "MyGame/GeneralGameData")] above your ScriptableObject class declaration, then right click in the project pane, click Create > MyGame > GeneralGameData. Fill in all the fields you need. Any script that needs to reference it can just add a public field of type GeneralGameData and add that asset in the inspector.

0
mahdi movahedian On

This is the fixed code based on Nailuj29s answer but you do not need to get reference to it by having a public field , instead you just need to use GeneralGameData.CurrentGeneralGameData.

public class GeneralGameData : ScriptableObject { private static GeneralGameData _currentGeneralGameData;

    public static GeneralGameData CurrentGeneralGameData
    {
        get
        {
            if (_currentGeneralGameData == null)
            {
                if (AssetDatabase.FindAssets("GeneralGameData", new []{ "Assets/CoreMaker" }).Length != 1)
                {
                    _currentGeneralGameData = CreateInstance<GeneralGameData>();

                    if (!AssetDatabase.IsValidFolder("Assets/CoreMaker"))
                    {
                        AssetDatabase.CreateFolder("Assets", "CoreMaker");
                    }

                    AssetDatabase.CreateAsset(_currentGeneralGameData,"Assets/CoreMaker/GeneralGameData.asset");
                }
            }

            _currentGeneralGameData = AssetDatabase.LoadAssetAtPath<GeneralGameData>("Assets/CoreMaker/GeneralGameData.asset");

            return _currentGeneralGameData;
        }
    }

    public string GameName;
  

    public string GameVersion;
    
}

Keep in mind that when you reference GeneralGameData.CurrentGeneralGameData it is going to create and asset , if you delete that asset you are going to lose you data.

0
Francois Bertrand On

The reason you lose your data is because there are 2 ways people use ScriptableObject for singletons. The key difference is how the static value is set. (e.g. the _currentGeneralGameData = CreateInstance<GeneralGameData>() line in your code)

From an ASSET RESOURCE: This is a shared, "actual" object/file that exists in your project Resources. When you change it, the changes are "permanent" (saved to that actual object), which I believe is what you're looking for. In your case, this would mean simply grabbing a reference to your existing asset resource, instead of creating a new instance, something along the lines of:

_currentGeneralGameData = Resources.Load<GeneralGameData>("GeneralGameData");

or

_currentGeneralGameData = Resources.FindObjectsOfTypeAll<GeneralGameData>().FirstOrDefault();

Note: Skipping verification/error handling code for clarity. Important: Runtime (standalone) versus editor will get different behavior: you may need to put an explicit reference to the ScriptableObject asset in your scene to make sure it is not optimized out. More details at: https://baraujo.net/unity3d-making-singletons-from-scriptableobjects-automatically/

From an INSTANCE of an asset: This is your current code. Instead of grabbing a shared asset, you create a FRESH instance, disconnected from what is saved on disk, which means you will not be saving any of the changes you make at runtime.

This is perfectly fine, and possibly preferable in some cases, but if you do want to keep changes then you should use the asset resource method.