Search Unity

ScriptableObject behaviour discussion (how Scriptable Objects work)

Discussion in 'Editor & General Support' started by GetBrinxed, Jul 18, 2018.

  1. Barrrettt

    Barrrettt

    Joined:
    Feb 19, 2016
    Posts:
    1
    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.
     
  2. TeotiGraphix

    TeotiGraphix

    Joined:
    Jan 11, 2011
    Posts:
    145
    > in the editor but I prefer to read-save a file.

    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<T> that this uses.
     
  3. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    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:

    Code (CSharp):
    1.  
    2. // This flag surrounds all the changes I made in 8 above, which magically brought SO's back to working,
    3. // ... but when I removed all the code again, they continued working.
    4. //#define UNITY_2019_MAJOR_BUG_CORRUPTS_ALL_SCRIPTABLE_OBJECTS
    5.  
    6. public class LocalDataPoint : ScriptableObject
    7. {
    8. public Vector3 localPosition;
    9. public Vector3 localDirection;
    10. #if UNITY_2019_MAJOR_BUG_CORRUPTS_ALL_SCRIPTABLE_OBJECTS
    11. [SerializeField]
    12. #endif
    13. public ParentMonoBehaviour parent;
    14. public bool blendWidths = false;
    15.  
    16. #if UNITY_2019_MAJOR_BUG_CORRUPTS_ALL_SCRIPTABLE_OBJECTS
    17. public void OnEnable() { hideFlags = HideFlags.HideAndDontSave; }
    18. #endif
    19.  
    20. #if UNITY_2019_MAJOR_BUG_CORRUPTS_ALL_SCRIPTABLE_OBJECTS
    21. private void OnDestroy()
    22. {
    23.   Debug.Log( "Unity is destroying this LCP" );
    24. }
    25. #endif
    26.  
    27. public Vector3 worldPosition
    28. {
    29.   get { return parent.transform.TransformPoint(localPosition); }
    30. }
    31.  
    32. public Vector3 worldDirection
    33. {
    34.   get { return parent.transform.TransformVector(localDirection); }
    35. }
    36.  
    37. public static LocalDataPoint CreateFromWorld(Vector3 pos, ParentMonoBehaviour r, Vector3 dir)
    38. {
    39.   LocalDataPoint rcp = CreateInstance<LocalDataPoint>();
    40.   rcp.localPosition = r.transform.InverseTransformPoint( pos );
    41.   rcp.localDirection = r.transform.InverseTransformVector( dir );
    42.   rcp.parent = r;
    43.   return rcp;
    44. }
    45.  
    46. #if UNITY_2019_MAJOR_BUG_CORRUPTS_ALL_SCRIPTABLE_OBJECTS
    47. #else
    48.   public override bool Equals(object obj)
    49. {
    50.   LocalDataPoint other = obj as LocalDataPoint;
    51.   if( other == null ) return false;
    52.  
    53.   return other.parent == parent && other.localPosition == localPosition && other.localDirection == localDirection;
    54. }
    55.  
    56. public override int GetHashCode()
    57. {
    58.   return parent.GetHashCode() + localPosition.GetHashCode() + localDirection.GetHashCode();
    59. }
    60. #endif
    61. }
    and:

    Code (CSharp):
    1.  
    2. public class ParentMonoBehaviour : MonoBehaviour
    3. {
    4. #if UNITY_2019_MAJOR_BUG_CORRUPTS_ALL_SCRIPTABLE_OBJECTS
    5. [SerializeField]
    6. #endif
    7. public List<LocalDataPoint> localPoints;
    8. }
    9.  
     
    april_4_short and TheHeftyCoder like this.
  4. TeotiGraphix

    TeotiGraphix

    Joined:
    Jan 11, 2011
    Posts:
    145
    @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.

    https://blogs.unity3d.com/2019/08/27/unity-2019-3-beta-is-now-available/

    "We’ve added Enter Play Mode options in Project Settings > Editor > Enter Play Mode options as an Experimental feature."
     
  5. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    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 :))
     
    TeotiGraphix likes this.
  6. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    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.
     
  7. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,476
    @a436t4ataf Have you solved problem? I think I met similar problem with SO. Using newest unity, 2019.2.7f2
     
  8. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    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")
     
  9. LazloBonin

    LazloBonin

    Joined:
    Mar 6, 2015
    Posts:
    812
    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:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. [CreateAssetMenu]
    4. [ExecuteInEditMode]
    5. public class TestAsset : ScriptableObject
    6. {
    7.     void OnDestroy()
    8.     {
    9.         Debug.Log("OnDestroy");
    10.     }
    11. }
    12.  
    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)
     
  10. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    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.
     
  11. Nit_Ram

    Nit_Ram

    Joined:
    May 8, 2016
    Posts:
    27
    Same behaviour here in Unity 2019.3. I guess it's a bug. Anything else doesn't make sense to me. So annoying.

    Is there a Unity staff member who can confirm that it's a bug? Or explain why OnDisable & OnDestroy dont't get called when deleting a ScriptableObject from the assets?
     
  12. TheHeftyCoder

    TheHeftyCoder

    Joined:
    Oct 29, 2016
    Posts:
    91
    This is ridiculous. It's been months on end and the problem persists even in the official 2019.3 version. Instance SOs
    are unusable... And they don't address it? I mean, being able to make scene-only SOs was great.. Are they ignoring this because you couldn't reproduce this? In 2019.3, whenever I reload the scene, the SO is corrupted and that's the end of it.

    EDIT: This may work correctly if the scene is dirtied so it knows what's modified. Needs more testing to see if it works in 2019.3
     
    Last edited: Mar 13, 2020
  13. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    I don't know if this was mentioned in the thread already, but one gotcha I just ran across is that auto properties are backed by a serialized field.

    So if you have an bool auto property and in PlayMode you change the value to true, exiting and reentering play mode will cause the property to still be true.

    This shouldn't be an issue in a standalone build as scriptable objects are not persistent across different gaming sessions, but it will cause some errors in the editor if you're not careful.

    Edit: I switched to using a local private non serialized field and I am seeing the same behavior. You have to be really careful with these things, as others said you can't treat them like a Monobehaviour. It should really be just a store of data.
     
    Last edited: Apr 18, 2020
    mateomogar likes this.
  14. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    Yeah, that keeps biting me as well. It won't be serialized if you mark them as [NonSerialized].
     
    sharkwithlasers likes this.
  15. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    Yes, I had problems some years ago as well. It is really annoying and should be careful in the development phase.
    That's why scriptableobjects are more suitable for immutable data.
    By the way, you can write Initialize method and call it from outside scriptableobjects or using OnEnable and a bunch of codes to reset fields.
     
    Last edited: Apr 19, 2020
    oscarAbraham and Kiwasi like this.
  16. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Yeah, I tend to treat my scriptable objects as immutable, or create a duplicate at runtime if I need to treat them as mutable.

    Scriptable objects are assets, which means any changes made to them during playtime are immediately applied.
     
  17. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    Yes, that is what I am currently doing. The problem is I had a variable storing whether the Scriptable Object had been Initialized yet, because there could potentially be multiple objects using the scriptable object and calling the initialize method and I only want initialization to happen once.

    I have taken to unloaded the asset using Resources.UnloadAsset in one of my monobehaviours OnDestroy method.
     
  18. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    As noted in my edit, the issue isn't necessarily with the field being serialized, as a private field not marked serializable also keeps its value between play sessions in the editor.
     
  19. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Except when they're not.

    Seriously, though: they aren't assets. They are a magic, half-designed, poorly implemented, undocumented feature in the engine that I suspect was mostly a leftover of some half-implemented internal superclass / refactoring that someone realised "actually, we could also use this for something else...".

    They *can act as* assets, in some cases. And some of the common cases fall into that. But not all.
     
    april_4_short likes this.
  20. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    I have used scriptableobjects as an inventory or collection too with some methods.
    However we do not have many options to keep data (whether mutable or immutable) especially between scenes such as singletons, service locator with dontdestroyonload,prefabs or scriptableobjects.
    I have removed the option storing data in files.

    I sometimes utilize scriptableobjects for mutable data when I need them in many scripts and scenes (shared data even if they are mutable)
     
  21. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    If you explicitly mark them as [NonSerialized], then they won't keep values between play sessions. It's a bit dumb, since the fields wouldn't be serialized by the default Unity serializer, so the rules are inconsistent.
     
  22. r618

    r618

    Joined:
    Jan 19, 2009
    Posts:
    1,305
    MonoBehaviours and ScriptableObjects are almost identical on the engine side (except user callbacks), so no ;)

    edit: agreed on the documentation part though
     
  23. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Do you have any more info about "almost identical"? My own hacking/disassambly suggested the word "almost" is hiding some significant differences - one of the surprises to me was how much Unity internally selected on the object type and did magic things in response. Conversations with Unity folks who've worked on SO's often start with "oh, it's the same as MB" and end with "oh, it's nothing like MB". Conceptually similar, significant differences under the hood.
     
  24. r618

    r618

    Joined:
    Jan 19, 2009
    Posts:
    1,305
    i haven't seen actual implementation - so I rely on word of mouth wisdom from UT staff too
    I think more MB related things are hardcoded in the Editor ( such as 1 script : 1 class requirement for a component - and things are 'interesting' when having more than one or combining MB + SO in one script for sure )
    In any case SOs are far from being "a refactor remains"
     
  25. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    That defies reason but is great to know, thanks!
     
  26. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    Hi. Hm, yeah this is a problem. I'd normally say that this is no trouble because I usually set my private fields in an onEnable call. That way, it works even without the NonSerialized attribute, and I can also reset the private fields at the start of Play Mode according to the value other public fields. The problem is that none of this solutions work if one disables Domain Reload, which is a bummer.

    NonSerialized works because when the domain is reloaded, the scriptable objects are constructed again. Normally, all public and private variables get their values restored to what they were before the domain reload, for consistency; but NonSerialized stops that process. It lets the fields keep their default value. In other words: it makes fields reset the same way static fields are reset. So it means that without the domain reload, the object never gets constructed again, NonSerialized fields suffer the same problem that static fields do. Also, it is harder and more expensive to solve it for NonSerialized fields than for static fields, because you'd have to get all the SOs of certain type and reset their variables inside a RuntimeInitializeOnLoadMethod call.

    You'd think this could be solved by resetting the values in OnEnable, but it never gets called when entering Play Mode if Domain Reload is disabled. I think this is a bug. What do you think? Should I make a bug report on this? MonoBehaviours get their OnEnable methods called when entering play mode, even without DomainReload; I think it is to be expected to have a way to initialize SOs when entering Play Mode. And the most inconsistent thing about it is that SOs do get their OnDisable method called, just once, when exiting Play Mode; then, it seems they stay disabled.

    I mean, I don't think SOs keeping their private values is a bug. If you think about it, it makes sense: MonoBehaviours get their private values reset when exiting play mode; not when entering. That's because MBs are supposed to be reset when exiting play mode. SOs private values don't get reset because they are supposed to be the same object even outside Play Mode. Yet, it becomes a problem when you lose the ability to initialize them at the start of the game. That's the real unexpected behavior. Am I wrong about this? Do you think I should file a bug about OnEnable not being called? Or is it more of a Feature Request?
     
    Last edited: Apr 27, 2020
  27. WryMim

    WryMim

    Joined:
    Mar 16, 2019
    Posts:
    69
    It's 2022, people still can'tunderstand what these clowns have programmed...
     
  28. giraffe1

    giraffe1

    Joined:
    Nov 1, 2014
    Posts:
    302
    *fixed
     
    oakus and Jos-Yule like this.
  29. Epsilon_Delta

    Epsilon_Delta

    Joined:
    Mar 14, 2018
    Posts:
    258
    One thing that can be done (and may or may not be helpful depending on the circumstances) is to expose it in the inspector by
    [field: SerializeField]
    attribute
     
  30. Epsilon_Delta

    Epsilon_Delta

    Joined:
    Mar 14, 2018
    Posts:
    258
    I would like to ask if somebody knows what exactly happens in this scenario:

    1. I have a scriptable object script that is completely encapsulated in #if UNITY_EDITOR:
    Code (CSharp):
    1. #if UNITY_EDITOR
    2. [CreateAssetMenu(menuName = "SO")]
    3. public class SO : ScriptableObject
    4. {
    5.     [RuntimeInitializeOnLoadMethod]
    6.     public static void Init()
    7.     {
    8.         Resources.Load<SO>("SO");
    9.     }
    10. }
    11. #endif
    2. I create an asset "SOinstance" of this scriptable object and then put it in the Resources folder.
    3. Create build for target platform (e.g. Android)

    It seems the compiler is smart enough to delete the asset "SOinstance" if the underlying script does not exist.
    Any potential gotchas?

    I use this scenario for some local (only on my machine) settings ignored by version control, but I want the builds to be exactly the same (byte by byte) as from other developers
     
    Last edited: Sep 23, 2022
  31. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    I would avoid that like the plague, it's the kind of thing that makes the editor/builds go badly wrong sooner or later. But I don't understand what it is you're tyring to do - I've spent a huge amount of time exploring all the options for storing settings at different levels of granularity, for different combos of user/team/editor/runtime (and all the sub-combinations), and sitll a bit confused what you're trying to achieve there. BUT a lot of things that theoretically (or initially) work in that area break unfixably long term due to nasty bugs and legacy internal design choices in Unity that almost never get fixed.
     
  32. Epsilon_Delta

    Epsilon_Delta

    Joined:
    Mar 14, 2018
    Posts:
    258
    It's basically a script that says "Skip tutorial", "Skip cinematics" etc., exposed as serialized bools so I can turn it on and off without recompilation. It is used at runtime, but I don't want it in release builds, just in the editor (and debug builds, the #if is different in real scenario). Moreover, I don't want it in version control (becuase it will change constantly) and want the release builds to be completely unaffected by this (same hash).
    It seems to work, when I find the time, I will compare the builds with and without these assets in the project if they are completely identical
     
    a436t4ataf likes this.
  33. RageAgainstThePixel

    RageAgainstThePixel

    Joined:
    Mar 11, 2020
    Posts:
    66
    I'm curious as to if this is actually a bug or if this is intentional. There should really be a callback for when the asset is deleted.