ScriptableObject behaviour discussion (how Scriptable Objects work)

No, I didn’t, and that was part of why this is so surprising. Add in that subscribers of multicast delegates do not persist and you have a (to me) pretty confusing situation. Now, delegates are immutable and I notice that anything marked readonly also does not persist, including any List<> objects (and, of course, their actual lists). Maybe those two facts are related?

EDIT: Turns out you don’t have to mark simple private fields with [SerializeField] for them to be persisted and serialized across hot reloads. Here’s the relevant passage from the Unity doc:

One should note that this not only prevents serialization, it also prevents persistence when entering and leaving play mode in the editor. Consider this code:

using UnityEngine;

[CreateAssetMenu]
public class TwoValues : ScriptableObject
{
    public int x;

    private int y;

    private void OnEnable()
    {
        Debug.Log("Before: " + y);
        y = x;
        Debug.Log("After:  " + y);
    }
}

If you create a TwoValues SO and set its x value to 5 in the Inspector, then enter play mode, you see this on your Console:

UnityEngine.Debug:Log(Object)

After: 5
UnityEngine.Debug:Log(Object)```

When you leave play mode and set the Inspector to Debug mode, you will see that y is set to 5. Enter play mode again and you will this on your Console:

```Before: 5
UnityEngine.Debug:Log(Object)

After: 5
UnityEngine.Debug:Log(Object)```

But if you add ```[System.NonSerialized]``` ahead of ```private int y;```, y will always be initialized to zero when you enter play mode (it will also no longer be visible in the Inspector, even in Debug mode).

This might be of some help in coping with problems arising from persistence when using mutable SOs.
1 Like

Further to the above: the documentation appears to be correct for SOs, but not for MBs: If you use that same code as a MB, the value of y is not saved from one entry into play mode to another. Now, you can give y the [SerializeField] attribute, but this does not make its value persistent. Rather, it allows you to set its starting value in the Inspector. If you do, Unity will set it to that value every time you entire play mode. The code, as written, will change the value of y to whatever you have set for x, and you can see that in the Inspector while in play mode. But, when you exit play mode, you will see y revert to its pre-play value in the Inspector. This is the opposite of how SOs work.

Ohh boy I can see how that could make debugging unusual behaviour in editor a total nightmare. I will definitely keep this in mind.

Which one of them do you use?

public ClassSpawner:MonoBehaviour{
   [SerializeField]
   private GameObject _prefab;
   [SerializeField]
   private IntReference _value;
   void Spawn(){
      var obj=Instantiate(_prefab);
      obj.GetComponent<Class1>().Initialize(_value); //or
      obj.GetComponent<Class1>().Initialize(_value.Value);
   }
}

public class Class1:MonoBehaviour{
   [SerializeField]
   private IntReference _data;  //SO
}
//---------------------------------------------------------------------------

public class Class1:MonoBehaviour{
   private int  _data;
   public void Initialize(int data){
      _data=data;
   }
}

//---------------------------------------------------------------------------
public class Class1:MonoBehaviour{
   private IntReference _data;
   public void Initialize(IntReference data){
      _data=data;
   }
}

Currently I’m doing it this way obj.GetComponent<Class1>().Initialize(_value.Value);

I want to bring up something I have been struggling with about SO’s - but frankly its… nutty stuff so hang in there lmao. Let me go ahead and apologize right now for my usual wall-o-text that I’m about to drop on you guys :slight_smile:

What if I want an editor script that can generate a world, and save some data about the world as ScriptableObjects? And what if that actually contained inside that SO, references to other SO’s to create a sort of hierarchy of SO references?

Perhaps the short version of this problem I am about to describe is, how do you save one scriptable object as an asset, with a reference to another not-yet-saved instance of another scriptable object? If you do that - and then save the not-yet-saved instance of an SO later, does the reference get updated to the asset rather than the instance created? In my testing so far I’ve had some trouble doing that.

Here is the long version:

Imagine your working on a road network for AI drivers, and you want to make perhaps 1000 waypoints around your map for the vehicles to drive around in. Now the way I work through this idea is like this:

1)first generate a gameobject based network of waypoints, with normal monobehaviors attached holding all the relevant data (this is wasteful but who cares it is an editorscript and the client won’t deal with this)
2)when the network has finished generating and connecting (meaning saving a reference to the next waypoints on a waypoint) then go and create scriptable object instances to represent that same data (without gameobjects/monobehaviours involved).
3)these SO instances are “Tiles” which contain references to every waypoint in a certain area, so to do that you might have the tile itself be a scriptable object, and the waypoints another scriptable object.

Here is where the problem is - if you had instances of all the “tiles” and “waypoints” of the world, then go to save the assets, where you might expect them to have the same references (the Tile knows its Waypoints, the Waypoints know the next waypoints connected to it) - it doesn’t seem to work properly. Of course I may just be doing things wrong!

But it seems that some sort of ScriptableObject-ception happens, where you need to assign an asset that doesn’t yet exist to the “tile” scriptable object (the scriptable object instance for its waypoints perhaps hasn’t actually been saved to a file yet) so you end up with “type mismatch” in place of some of the variables in the “tile” or the “waypoint” ref to its next waypoints. Is it just an issue of bad coding practice by trying to mix/match the instances and the actual assets?

How do you deal with that? I can only imagine one way (which I’ll be testing after slamming on some food, happy turkey day ya’ll), which I suppose would be to create all the assets ahead of time rather than just instances, you know just loop to generate and save all those “instances” as assets, and then assign all the appropriate values to the asset itself somehow (I haven’t yet tried do this, but I hope its possible) so that the references are already set to the EXISTING scriptable object asset? Does that even make sense? Does any of this make sense? Haha… no seriously is what I’m trying to do a huge nonsense way of going about it? I ultimately just want to get away from monobehaviours and gameobjects to represent waypoints and the tiles they are in, because it would be lighter on the built game if I didn’t have an extra set of gameobjects/scripts sitting around basically holding data… and that is why I expected SO’s to solve this. While I have worked with them for way more simple tasks, I never tried this sort of referencing between them, so if anybody can suggest the proper way to do that, it would be fantastic! Or if you can let me know what would make more sense in this type of situation, where you wouldn’t need to do that at all… I’d like to hear that too!

As I understand it, just using “EditorUtility.SetDirty(someSOAsset);” and “AssetDatabase.SaveAssets();” should update the changes to the existing saved asset for the SO… is that correct? If not, how do you go about that?

If you’re converting your in-scene tiles to assets, why not create a single ScriptableObject that contains all of the map info, and then save that single ScriptableObject as an asset? You won’t be able to link waypoints with object references (because serialization), but you could link them using indices. For example, say waypoint 1 links to waypoints 5, 9, and 11. Waypoint 1 would have a list of integers {5, 9, 11}. Or use IProxySerializationCallbackReceiver; but I think the list of integers is simpler.

I suggest this because updating those object references can be a huge headache. Quest Machine uses object references. It works, but it added more than a month of careful extra development (and hair-pulling) to get it designed and tested.

If you want to use object references, then yes, you’ll have to update the references when copying instances to assets.

You might also want to save sub-assets inside the main asset. For example, you might have a single Map asset file in your project containing some number of Tile sub-assets, each of which contains some number of Waypoint sub-assets. You can use AssetDatabase.AddObjectToAsset() for this. A common set of steps is to set the sub-asset’s hideFlags to HideFlags.HideInHierarchy, then call AddObjectToAsset, ImportAsset, SaveAssets, and Refresh.

1 Like

I will look into that - as having one asset would be a lot cleaner on my projects view. I have started to make some progress on things and have done some basic experiments to try and wrap my head around it. It is tough though when your eyes are heavy from turkey day dinner :stuck_out_tongue:

Haha yes, this has proven to be one of the harder things to get going. Thanks for the suggestions!

So, must an instance created with ScriptableObject.CreateInstance() be destroyed with Destroy() method when its no longer in use? Will the instance remain as long as the application is running?

Yes.

I’m going to use normal classes. It is very comfortable to edit a scriptableobject in the editor but I prefer to read-save a file. To make data structures such as: world, player, options. Or inventory, inventory item, item, consumable, weapons, etc. polymorphism inheritance: why not normal c # classes.

in the editor but I prefer to read-save a file.

4902311--474272--upload_2019-8-27_13-5-38.png

I use a preset editor in the SO Editor to save, reload and update, remove. I wrote a generic LibraryManager that this uses.

With Unity 2019 I’m getting a catastrophic failure of that “garbage collecting” by Unity, in a non-deterministic fashion. Randomly, Unity decides to wipe everything from the scene and will NOT restore any of it - and Unity overwrites the scene file on disk without permission! (as if it were doing SO assets, instead of SO instances).

Is anyone else still using ScriptableObject instances intensively and seen any problems in 2019.2/2019.3?

My experience today:

  1. Simple ScriptableObject, containing plain data, called “LocalDataPoint”
  2. MonoBehaviour that had a List<> of those objects
  3. Only using ScriptableObject.CreateInstance<>() to create instances
  4. No object destruction ANYWHERE (I never alter the List<> except using Add() - checked with IDE search)
  5. … Everything working fine in-editor, and fine when saving and quitting and restarting Editor
  6. Hit Play mode, and suddenly all the SO’s inside all the List’s became “()” - their name changed from “(LocalDataPoint)” to “()” in the Inspector’s List-of-items, and in script they all became null references
  7. Left Play mode, and all SO’s were gone, everywhere. Restarted Unity (without saving) and still: all SO’s gone, everywhere (Unity Editor had persisted the corrupt data, without having permission to!)
  8. Fiddled around, many restarts, everything broken (but now showing “null” instead of “()”), added and removed some [SerializeField] flags, and … the SO’s started working.
  9. Removed each change, 1 by 1, to see what made the difference: ended up with the identical code I had before, but SO’s now working, both in and out of Play mode. I used source-control here to verify that literally every line of code was identical, I did a restore of all Csharp classes
  10. …but SO’s still working.
  11. One time in 10, when entering Play Mode, UnityEditor globally wipes all SO’s and replaces them with null. It also magically saves the scene data (even though I haven’t saved anything), so that they are still corrupt on restart of Editor.

For reference:

// This flag surrounds all the changes I made in 8 above, which magically brought SO's back to working,
// ... but when I removed all the code again, they continued working.
//#define UNITY_2019_MAJOR_BUG_CORRUPTS_ALL_SCRIPTABLE_OBJECTS

public class LocalDataPoint : ScriptableObject
{
public Vector3 localPosition;
public Vector3 localDirection;
#if UNITY_2019_MAJOR_BUG_CORRUPTS_ALL_SCRIPTABLE_OBJECTS
[SerializeField]
#endif
public ParentMonoBehaviour parent;
public bool blendWidths = false;

#if UNITY_2019_MAJOR_BUG_CORRUPTS_ALL_SCRIPTABLE_OBJECTS
public void OnEnable() { hideFlags = HideFlags.HideAndDontSave; }
#endif

#if UNITY_2019_MAJOR_BUG_CORRUPTS_ALL_SCRIPTABLE_OBJECTS
private void OnDestroy()
{
  Debug.Log( "Unity is destroying this LCP" );
}
#endif

public Vector3 worldPosition
{
  get { return parent.transform.TransformPoint(localPosition); }
}

public Vector3 worldDirection
{
  get { return parent.transform.TransformVector(localDirection); }
}

public static LocalDataPoint CreateFromWorld(Vector3 pos, ParentMonoBehaviour r, Vector3 dir)
{
  LocalDataPoint rcp = CreateInstance<LocalDataPoint>();
  rcp.localPosition = r.transform.InverseTransformPoint( pos );
  rcp.localDirection = r.transform.InverseTransformVector( dir );
  rcp.parent = r;
  return rcp;
}

#if UNITY_2019_MAJOR_BUG_CORRUPTS_ALL_SCRIPTABLE_OBJECTS
#else
  public override bool Equals(object obj)
{
  LocalDataPoint other = obj as LocalDataPoint;
  if( other == null ) return false;

  return other.parent == parent && other.localPosition == localPosition && other.localDirection == localDirection;
}

public override int GetHashCode()
{
  return parent.GetHashCode() + localPosition.GetHashCode() + localDirection.GetHashCode();
}
#endif
}

and:

public class ParentMonoBehaviour : MonoBehaviour
{
#if UNITY_2019_MAJOR_BUG_CORRUPTS_ALL_SCRIPTABLE_OBJECTS
[SerializeField]
#endif
public List<LocalDataPoint> localPoints;
}
2 Likes

@a436t4ataf I remember reading in the release notes that I think 2019.3 is changing how the editor play mode serializes and this has ScriptableObject front and center.

I am still using 2019.1 for a major project I am working with.

Maybe this is a bug you need to report to Unity.

Top of the post mainly talks about the changes.

“We’ve added Enter Play Mode options in Project Settings > Editor > Enter Play Mode options as an Experimental feature.”

Thanks. The problem first is that I can’t get it to reproduce reliably - so Unity wont accept a bug report. I was hoping someone else might have seen something similar and I can narrow down the problem, and make it more reproducible (especially if it’s something wrong with my code, then I can fix it :))

1 Like

I’ve also just realised that even in 2019, Ctrl-D breaks non-trivial uses of ScriptableObject instances - unlike MBs, the isntance-SO’s don’t get duplicated, and so all your parent objects end up tied together as if they were sharing an asset-SO … literally the opposite of what should happen, given the SO’s are instances not assets.

(this was a problem years ago, but I assumed it had been fixed by now, given all the official Unity talks that reference it. I have to wonder if the people giving those talks fully tested their toolchains).

If you’re happy teaching everyone to stop using Unity’s own common keyboard shortcuts and menus … great. If not … Instance-SO’s are probably unusable in anything except toy situations. Huge disappointment.

Time to go rewrite my codebase and remove all SO’s again.

@a436t4ataf Have you solved problem? I think I met similar problem with SO. Using newest unity, 2019.2.7f2

I spoke to a few Unity people about it at Unite, we brainstormed some ideas on how to try and create reproducible test case. I’m working on that this week, trying to see if I can get a recurring example that I can then submit as a bug report.

On the Duplicate/Ctrl-D issue … I have nothing. It appears unfixable. Unity needs to do something (unless someone has a solution - again, asking around at Unity, no-one had anything beyond “'don’t hit Ctrl-D in the editor”)

Am I going crazy or is OnDestroy never getting called on ScriptableObjects in the editor?

From the docs and OP, it should be called when the asset is destroyed from the Asset Database.

But deleting my assets doesn’t seem to trigger it. Neither does entering play mode.

I have this super simple test case, which doesn’t log anything when deleted in 2018.4 and 2019.3:

using UnityEngine;

[CreateAssetMenu]
[ExecuteInEditMode]
public class TestAsset : ScriptableObject
{
    void OnDestroy()
    {
        Debug.Log("OnDestroy");
    }
}

I tried [ExecuteInEditMode] out of desperation, but it doesn’t work with or without.

EDIT: Likewise, OnDisable doesn’t get called either when deleting the asset. But it is called when entering play mode.

Is there any callback I can get when the asset gets deleted? (Without using AssetModificationProcessor.OnWillDeleteAsset)

I can confirm the same behavior. If you explicitly destroy the SO using Destroy at runtime or DestroyImmediate in editor code, OnDestroy is called. But if you delete the asset from the Project view, OnDestroy won’t be called. I even tested calling AssetDatabase.SaveAssets and Refresh to see if it didn’t get called until writing changes to disk and doing garbage collection, but no luck.

I use AssetModificationProcessor.OnWillDeleteAsset, as you mentioned above. It works fine, though I’d prefer not to have to deal with this inconsistency with OnDestroy.

3 Likes