Scriptable object corruption

We have structured our game so that data objects are instances of scriptable objects, but we have been having many occurrences of them becoming corrupt. When that happens we can simply make a small change to the scriptable object script and then the data asset will fix itself, but it is becoming increasingly common and wasting our team a huge amount of time.

This is a small, simplified sample of our code:

[CreateAssetMenu(fileName = "newVenue", menuName = "AG/Venue", order = 1)]
public class Venue : ScriptableObject
{
    [Tooltip("Name that will be displayed for the venue.")]
    public string m_name = "Venue name";
  
    [Tooltip("Level at which the venue becomes available.")]
    public int m_unlockLevel = 1;
  
    // More fields....
  
    public bool Enabled { get { return m_unlockLevel <= DataLookup.Level; } }
  
    public float Reputation
    {
        get { return MoneyManager.Instance.GetReputation(m_name); }
        set { MoneyManager.Instance.SetReputation(m_name, value); }
    }
  
    public string ReputationVarName { get { return m_name + "_Reputation"; } }
  
    public void Init() // NOTE(AG_TL): Makes sure the vars are always properly initialized!
    {
        MoneyManager.Instance.GetReputation(this);
    }
}

We drag and drop a Venue asset into UI components (like this one):

[RequireComponent(typeof(Button))]
public class VenueButton : MonoBehaviour
{
    public Venue m_venue;
  
    private Action<VenueButton> _onVenueButtonPressed;
    private Button m_button;
  
    public bool Enabled { get { return m_button.interactable; } }
  
    void Awake()
    {
        m_button = GetComponent<Button>();
    }
  
    public void Init(Action<VenueButton> onVenueButtonPressed)
    {
        _onVenueButtonPressed = onVenueButtonPressed;
        m_venue.Init();
    }
  
}

Here’s an example of the corrupted assets:

This only seems to happen when running from the editor where it is very common to Play/Stop the running game.
Specifically it manifests when going through different scenes (we are loading them async if that makes a difference). We have it yet to happen if we Play/Stop from a single scene, although we can’t assure it doesn’t happen (since we haven’t been able to get a 100% repro on this).
Another interesting thing is, if Venue becomes corrupt, ALL its instances become corrupt, EXCEPT if you have any of them selected from the editor. For some reason, instances that are selected cannot become corrupt.

We are using this as a dirty workaround (select all scriptable objects from the inspector before Play).

We have already investigated quite a bit, so things that we’ve checked/tried are:

  • Scriptable objects should never share a file with other classes and the class name should match the file name (VenueVenue.cs).
  • Stopped using prefabs that would contain components that held a reference to scriptable objects.

The issue is very easily reproducible in our project (about 2/5 repro) if we run from a scene, load into another scene that has this kind of setup and then stop.

Any help would be deeply appreciated, since each time we run into this it takes 2 minutes to go into the script, make a change, go back into Unity, check that the assets are correct again, re-run the game…

Has anyone had any of these issues with Scriptable Objects in their own projects?

We are very happy with how clean our code got with the use of SOs, but it is also somewhat annoying (and very much an inefficient use of our time) to keep having to deal with this.

We looked into this other post (https://forum.unity3d.com/threads/prefab-loses-scriptable-object-reference.377804/) and although the result is similar (corrupted or unassignable SO asset) the setup is not exactly the same.

We have found, however, that we do in fact use SOs in a couple prefabs, but this is somewhat important in our particular setup. So, since we do use our SO assets in both prefabs and Scene objects, the issues mentioned might be related to this duality of use and how Unity’s internal serialization works.

We will continue to investigate and update if we find a better way to deal with this, but a word from someone from Unity’s team or anyone with more experience on the subject would be highly valuable.

@SanityIsOverrated seemed very well informed about this topic.
Would you mind sharing your wisdom on the subject?

Didn’t know you get an email ping when you’re mentioned in a third party post, that’s cool.

Without delving deeper into your description, your ‘correct’ image has an issue too. Notice that the (disabled) field is empty? That shouldn’t be the case and is an indication that Unity lost your script reference. For whatever reason, that can happen.

Try the following: Switch the inspector to debug view (via the small burger menu on the top, next to the lock), then you can edit the ‘Script’ field. Drag in your script. Go back to normal view, save everything and try whether your problem still occurs.

Behind the scenes: The unity editor actually uses two methods of retrieving script instances: It serializes a class reference with the asset, or it retrieves a script file with the same name as the class. Obviously, the second method works only within the editor and I think this is what is used when you keep the asset select. In a build the concept of script files (MonoScript) is lost. The first method will break if said script reference is lost.

Let me know if this changes anything.

I was having a similar issue. It turned out that the script name had a space in it. Removing the space fixed it.

I am having this issue, but none of the above worked, and not even editing the script helps. Getting this error beforehand "UnityException: GetCount is not allowed to be called during serialization, call it from OnEnable instead. Called from ScriptableObject " But I am not calling that in the script associated with the message.

I had the same problem and I had to search long time to find any hints on how to solve this. Thats why I want to add my solution to this rather old thread.

My mistake was to have two classes in one .cs file (dialogueThread.cs) like that (simplified):

[CreateAssetMenu,Serializable]
public class Dialogue : ScriptableObject
{
    public QaA[] dialogue;
}

[CreateAssetMenu,Serializable]
public class DialogueThread : Dialogue
{
    public string topicName; 
}

By putting both classes in one file I broke the system. “Dialogue” Assets created got lost after scene transitions, but not the “DialogueThread” assets. No errors or warnings given in the console. And the problem only showed when not-showing the asset in the inspector while runing the code (same behaviour as with the OP).

Solution was to put the classes into seperate files.