Scriptable objects loosing their public fields when building

Hello,

i am kind of lost on this one and spent now around 10h tying to get this into my head. However at this point i assume it is unintended behavior. v 2019.4.34f1

So i do have scriptable objects

public abstract class ModifierType : ScriptableObject
{
    [SerializeField] string soid;

#if UNITY_EDITOR
    protected virtual void OnValidate()
    {
        string path = AssetDatabase.GetAssetPath(this);
        soid = AssetDatabase.AssetPathToGUID(path);
    }
#endif
}

So they get their guid in the editor. These scriptable objects are a field of another class

[Serializable]
public class Stat
{
    public ModifierType type;
    [SerializeField] string _typeSOID;
    public string TypeSOID
    {
        get { return _typeSOID; }
        set
        {
                _typeSOID = value;
        }

    //other fields
    }
}

This class is the field of another scriptable object

public class Item : ScriptableObject
{
    public List<Stat> stats;

    #if UNITY_EDITOR
    void OnValidate()
    {
        string path = AssetDatabase.GetAssetPath(this);
        soid = AssetDatabase.AssetPathToGUID(path);

        foreach (var i in stats)
        {
            i.TypeSOID = i.type.soid;
        }
    }
    #endif
}

So i do have a field in a scriptable object that depends on another field of another scriptable object. (this is because items get instantiated and i need this information for serialization)

This works totally fine as long as i am in the editor. However, when i built, random “Stat” objects pass an empty string (“”) as their soid to the item.

Some random observations of mine:

  • the stats are not totally random. from the 200 available it is always a random number of the same 10 (the first 1-4 of an item and the last, for what it is worth)

  • a debug message in the item class gives way more messages then deleted strings or stats. this means OnValidate gets called way more then id expect when building

  • the number of null soids is different from build to build

  • if all stats are referenced by either a prefab or other scriptable object everything is fine

  • it happens only for the ModifierType class. if i remove the OnValidate code from Item the soids persist correctly

  • it only happens to the string field of “stats”. other fields are remembered correctly. this makes me assume, that it is indeed the “ModifierType” object that loose value during the building process. however if i check them after the build, they all have their ID, they are just not correctly passed to the item

At some point i assumed that it had something to do with script execution order. But the order can only be set for monobehaviours, if i read the documentation correctly. Even then, their should be no point in which a string field of ModifierType should be null. They have to be reset during the built or something. Other than that i am aware that string is a reference type, but at no point is a soid set to “”, so this should also not be the cause.
Id like to avoid the adressables package if possible. I made sure, that the soid of ModifierType is not set by another class (it is actually only acessible through a property, which is not shown). And just to be sure: i am not trying to change values at runtime, the information is lost when i built.

So thanks for reading, i am glad for every hint

Just a cross-check: you do have actual instances of each of these things sitting on disk, correct? Since it is abstract, you must have made a concrete type on disk of something derived from this, correct? And it would also have to call base.OnValidate() and do the whole protected → child cascade.

In other words, you’re not just trying to keep them in the build by virtue of it being created and referenced in the scene, because that won’t keep it. Every asset must be written to disk.

Given that it is on disk, what do you see when you pull up the SO itself (it’s just a text file)?

1 Like

Agree with above; this seems to be a case of checking if values are being serialised/written to disk or not. Most likely, they just aren’t being written.

I’ll note that this sort of inter-object communication is not the intended use of OnValidate. This is the kind of things that need to be handled by custom editor work.

1 Like

Hey

first of all thanks for checking. i am not sure if i understand all parts of the questions, but ill try my best to answer

here is a picture

edit: picture did not seem to integrate. here is the link: Imgur: The magic of the Internet

From left to right.
i) what “ModifierTypes” look like in my asset folder. so as you said i made a small script that inherits from stat, and created its scr obj. base.onvalidate is not needed, because its protected and never overridden. i can check this by trying to change the id and watching it update. that is shown in ii)
iii) shows what an item looks like in the inspector after building with crtl+b. the empty fields were not empty before

if i change the soid field of “stat” to this

    public string TypeSOID
    {
        get { return _typeSOID; }
        set
        {
            if (value != "")
                _typeSOID = value;

            if (value == "")
                Debug.Log("null value passed");
        }
    }

my console is cluttered with plety cases where an empty string was passed

We honestly weren’t asking questions, but telling you the problem. Simply put, the values aren’t being written to disk.

I’ll quote what Kurt already said:

If you open one of these problem scriptable object files in notepad I think you’ll find these ‘soid’ values have not been written to disk.

Whenever you change the values of objects via code you need to dirty the object (by use of functions such as UnityEditor.EditorUtility.SetDirty()), otherwise Unity doesn’t know it needs to re-write its values from memory to disk. It’s important to note there is a difference between what Unity shows (which exists in memory), and what is written to your drives.

And I’ll repeat this use is NOT what OnValidate is intended for. It should only be used for very basic validation within the same object. Anything beyond that you should use more robust editor scripting.

2 Likes

ok i see. partly this answer is a little disheartening, because the main reason i did this exercise was to avoid custom editor code. the second was to have an id system sight

anyway, i much appreciate the answers. I think i learned something, or at least i understand the problem.

For the record you guys were right about the values not written to disk thing

csharp* *%YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!114 &11400000 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: e19226d5972677f449c453fc842209ac, type: 3} m_Name: _e_damagePercAddOnProtection m_EditorClassIdentifier: _soid:* *

Maybe, you can give me some key words to research to tackle this problem with editor coding? Otherwise thanks again for clarification

I highly recommend staying away from these sorts of identifiers.

Even if you get them working and write all the editor tooling, you STILL invite disaster as soon as one of them somehow (there’s myriad ways) gets the same number as the other.

So then you need more editor tooling to enforce that.

And if some other object (or save game) keeps that ID, and it changes, now you have ANOTHER mysterious problem.

Adjunct IDs like this are just bad news all around.

Instead just use the name of the ScriptableObject itself on disk.

This way if you always put all of the instances in a single directory then you can know they are unique.