How Are You Resetting Your Scriptable Objects Between Play Tests?

Every video/guide about why you should use Scriptable Objects seems to very quickly gloss over the fact that the data in a Scriptable Object does not get reset when you exit play mode in Unity.

i.e. If I have player health stored in an SO and I enter play mode to test/debug my game, and my player loses some health in that play session, the health in the SO stays at that level next time I enter play mode. Obviously making it a nightmare to play test and debug.

Potential Solution 1 - Create Instances

I’ve seen some people say they just create an instance of the SO at runtime, then the changes get made to that instance and the instance dies when you exit play mode so you’re all good… but that seems to have a huge problem unless I’m missing something. In most cases we’re using SOs to hold shared data, so if you create a separate instance then that’s no longer shared data - its a unique instance and other items referencing the SO will not see the same data. Now of course you could just create one instance and have all your other components refer to that one instance, but aren’t you basically ending up with a singleton and defeating the point of using SOs in the first place?

Potential Solution 2 - Reset Values To Defaults

So given how bad the first “solution” seems to be, this is the only real option I can think of. The idea being that you have a script that runs when the game first loads (or on a per scene basis) and automatically resets values on every SO to be their correct default values.

But where should that script get its values from? Obviously I want to avoid hard coding them in the script. So these are the two best options I can think of:

  1. It could pull them from an external file (XML etc) that I manually set the correct default values in.

  2. Instead of getting the values from an external file, the script would reference another SO in my project that acts as a “template”. This template SO would never be referenced elsewhere in my code so it would stick to whatever values I enter in the designer and never change. This approach seems better as it keeps everything easily visible and editable in the Unity designer, but it also feels a bit hacky and weird having to create two SOs for every SO. They would both be holding essentially the same data but one of them I just let get messed up by play testing and one of them I have to remember to never drag into any scenes or access in code (other than the initial loading script). Doesn’t that end up being just as bad as using uninstantiated prefabs instead of SOs, where you have to remember certain prefabs are “special” and not to be used in scenes like others. I guess I could just have one master “default settings” SO that contains default values for all other SOs, and the script reads from that and populates the relevant SOs, which feels a bit less weird but still not ideal.

With either of those options though there’s several issues I’m not keen on or unsure about. Like how this means for every single SO in the entire game, I need to remember to add its default values to my template/master list, and then add code to reset it to those values.

So yeah I’m keen to hear how other people deal with this stuff in the real world, and if I’m on the right track or if there’s some better alternative I’ve not thought of (quite likely). I should point out I’m very new to game development (but have several years of experience with .NET desktop app development) so by all means correct things I’ve got wrong or point me in a different direction if I’m suggesting doing things in a weird way :slight_smile:

@Chris128,

If you don’t want to instance SO the easiest way to deal with that is by creating a/any serialized private field used in editor, and another during runtime

public class Ability : ScriptableObject
{
    // Editor value
    [SerializeField] private float baseCoolDown = 1f;            // Base cooldown
    // Internal variables
    // Ability CoolDown
    private float coolDown;
    public float CoolDown { get { return coolDown; } }

    // Initialize coolDown with editor's value
    private void OnEnable()
    {
        coolDown = baseCoolDown;
    }

    // You can also use OnAfterDeserialize for the other way around
    public void OnAfterDeserialize() 
    {
    }
}

Sure it kinda looks clumzy tho if your code gets more complicated it allows you to easily and safely control your class own properties.

Also instancing SO could make sense since you are changing your SO’s values at runtime which means you’re using SO as any custom class but with standardized initial values - like for spells in a fantasy game for ex, if i want my player A and B to have a cooldowns on this ability depending on their stats, i’ll define a baseCooldown and then at initialization (or even when the ability is triggered) modify their cooldown depending on their stats (i.e. independantly for player A and B).

Hi.
We used a different way which worked for us :

have an editor script with a

    private static void OnPlayModeChanged(PlayModeStateChange state)
    {
        if (!EditorApplication.isPlaying)
        {
            
        }
       else
        {
            
        }
    {

When start play, store the things that you not want to change

And when stopped you can have a popup to accept / decline for some that you might want to change after all. (don’t forget to use a EditorUtility.SetDirty(); on the scriptables )

Alternatively, you can save SO values as Preset in upper right corner

Here’s another useful tidbit to add onto what has been said. Create an interface for clearing on exit play mode. Then in a static class create some methods to find all ScriptableObject assets in the project and, for each one, call its Reset method if it implements the interface. Use [InitializeOnLoadMethod] on a static method to subscribe those methods to EditorApplication.playModeStateChanged.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;

static class SOPlayModeResetter
{
    [InitializeOnLoadMethod]
    static void RegisterResets()
    {
        EditorApplication.playModeStateChanged += ResetSOsWithIResetOnExitPlay; 
    }

    static void ResetSOsWithIResetOnExitPlay(PlayModeStateChange change)
    {
        if (change == PlayModeStateChange.ExitingPlayMode)
        {
            var assets = FindAssets<ScriptableObject>();
            foreach (var a in assets)
            {
                if (a is IResetOnExitPlay)
                {
                    (a as IResetOnExitPlay).ResetOnExitPlay();
                }
            }
        }
    }

    static T[] FindAssets<T>() where T : Object
    {
        var guids = AssetDatabase.FindAssets($"t:{typeof(T)}");
        var assets = new T[guids.Length];
        for (int i = 0; i < guids.Length; i++)
        {
            var path = AssetDatabase.GUIDToAssetPath(guids*);*

assets = AssetDatabase.LoadAssetAtPath(path);
}
return assets;
}
}
#endif

public interface IResetOnExitPlay
{
public void ResetOnExitPlay();
}
And an example ScriptableObject:
using UnityEngine;

public class SOExample : ScriptableObject, IResetOnExitPlay
{
[SerializeField] int value;
public int Value { get; set; }

private void OnEnable()
{
Value = value;
}

public void ResetOnExitPlay()
{
Value = value;
}
}