Search Unity

ScriptableObject behaviour discussion (how Scriptable Objects work)

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

  1. GetBrinxed

    GetBrinxed

    Joined:
    Feb 21, 2018
    Posts:
    12
    Ever since the well-known talk by Richard Fine about ScriptableObjects (link below) they've become more and more prevalent in Unity system architecture. Ryan Hipple took it a step further in his own talk (link below) and gave us a ton of practical examples on how to use them.

    Despite both of these AND the Unity documentation on ScriptableObjects (link below) I've found that this powerful tool still has a bunch of gotchas. So I'd like to clear them up by talking about EXACTLY how they behave and why.

    Here's everything I know about them and how they work. If you can fill in a '???' or provide additional insight into any of these points, please comment below. Note that all tests to verify these behaviours were done in Unity 2018.1.0f2 on a Windows OS.

    ScriptableObjects - Their Callbacks And When They Receive Them

    Awake() - From docs: This function is called when the ScriptableObject script is started.
    Specifically, there are 3 cases in which a ScriptableObject receives an Awake() message from Unity:
    1 - When the ScriptableObject is created (in editor or at runtime)
    2 - When the ScriptableObject is selected from the project window in the Editor (??? what's the root cause of this? Inspector?)
    3 - When a scene is loaded IF at least one MonoBehaviour in that scene is referencing the ScriptableObject asset

    Important! ScriptableObjects will only receive Awake() if OnDisable() was previously called on the object. So if Awake() was called because of case 2 and case 3 occurs before other callbacks, it won't receive Awake() again.

    OnEnable() - From docs: This function is called when the object is loaded.
    Specifically, there are 3 cases in which a ScriptableObject receives an OnEnable() message from Unity:
    1 - Immediately after the ScriptableObject's Awake() (before other callbacks on this or other objects)
    2 - When the Unity Editor reloads IF in a scene that has a MonoBehaviour referencing that ScriptableObject asset (right after OnDisable())
    3 - When entering play mode IF in a scene that has a MonoBehaviour referencing that ScriptableObject asset (right after OnDisable())

    OnDisable() - From docs: This function is called when the scriptable object goes out of scope.
    Specifically, there are 4 cases in which a ScriptableObject receives an OnDisable() message from Unity:
    1 - When a scene is loaded and there are no MonoBehaviours in that scene that reference the ScriptableObject asset
    2 - When the Unity Editor reloads IF in a scene that has a MonoBehaviour referencing that ScriptableObject
    3 - When entering play mode IF in a scene that has a MonoBehaviour referencing that ScriptableObject
    4 - Right before any OnDestroyed() callback

    Important! OnDisable() will only be called if OnEnable() was previously called on the ScriptableObject.

    OnDestroy() - From docs: This function is called when the scriptable object will be destroyed.
    As far as I can tell, that's completely accurate. What's worth mentioning here are the 3 (???) causes for ScriptableObject to be destroyed in the first place:
    1 - The ScriptableObject is deleted in code
    2 - The ScriptableObject is deleted from the assets folder in the Editor
    3 - The ScriptableObject was created at runtime and the application is quitting (or exiting play mode)

    ScriptableObjects - Other Behaviour

    ScriptableObject vs. MonoBehaviour callbacks
    It's important to note that, when a scene is loaded, a ScriptableObject will normally receive its Awake() and OnEnable() messages before any MonoBehaviours receive their Awake() or any other callbacks, no matter the Script Execution Order. (??? anyone know why that is?)

    Execution Order
    ScriptableObjects themselves do not follow the Script Execution Order. If called at the same time, the order in which ScriptableObjects receive Awake() is unclear. (??? what determines the order of execution of ScriptableObjects?)

    Runtime Creation
    When creating a ScriptableObject at runtime, it does not appear in the Project's Assets folder, nor does it appear in the Hierarchy. (??? is there a way to view them in Editor?) ScriptableObjects created this way will get deleted either when the application quits or when exiting play mode.

    Scene References
    If you have a ScriptableObject that is meant to hold references to scene objects, be aware that the object fields for those references will display:
    (type mismatch)

    The reference will still point to the correct scene object if clicked on in the inspector. This is because, by default, object fields in assets can only contain references to other non-scene assets. This can be overridden by writing a custom property drawer for the referenced behaviour, or a custom inspector for the ScriptableObject. Even if that is done, scene objects still cannot be dragged and dropped into object field on ScriptableObjects.

    Deletion
    When deleting a ScriptableObject, note that other components still have the C# reference to them

    Please comment below if you have anything constructive to add.

    Resources
    Richard Fine panel/talk:


    Ryan Hipple panel/talk:


    Unity Documentation for Scriptable Objects:
    https://docs.unity3d.com/Manual/class-ScriptableObject.html
    https://docs.unity3d.com/ScriptReference/ScriptableObject.html

    Unity Documentation for custom inspectors and property drawers:
    https://docs.unity3d.com/2018.1/Documentation/ScriptReference/Editor.html
    https://docs.unity3d.com/2018.1/Documentation/ScriptReference/PropertyDrawer.html
     
    Last edited: Jul 19, 2018
  2. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,566
    I would not use scriptable objects for anything other than user-defined assets, in all honesty.

    It is an object that is detached from scene hieararchy and is best suitable for persistent data and configs.

    Trying to make it run scripts is something I wouldn't bother doing.
     
    n_i_e, io-games, chingwa and 2 others like this.
  3. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    9,051
    I use them for all types of things, though almost all a structured container of some sort. (Ui meshes, animation flows, vfx, event stacks, etc). I agree with you that using them to run gameplay scripts isn’t really I would do. They all have their own methods for managing/using the contents, but that is different. I typically wouldn’t use any of standard event in an so, with exception of onenable for de-serelization of custom (and some native) structs.
     
    Last edited: Jul 18, 2018
  4. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    I think Unity has tried to push ScriptableObjects further then they are really designed to go.

    Ultimately ScriptableObjects are flexible data containers. You can use them to hold all sorts of interesting data. You can modify the data at edit time, and have it be persistent. You can make them smart so that they do all sorts of useful operations with the data they hold when accessed.

    But there are a few rules to remember with ScriptableObjects. They are assets. That means they should be treated as immutable. It also means they aren't related to specific scenes, and should never have references to scene objects. It also means you shouldn't be creating them at runtime. And it means you should only be using Unity's magic messages at edit time, not at runtime.
     
    n_i_e, april_4_short, NotaNaN and 4 others like this.
  5. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    That's very true of ScriptableObject assets. But you don't have to save a ScriptableObject as an asset. They're just serializable objects. You can create ScriptableObject instances at runtime just like you can create an instance of any class.

    And just like any other object in object-oriented programming, they typically contain data and methods. It's been a while since I watched Richard Fine's presentation, but I believe he put AI methods in ScriptableObject classes and saved instances as assets. This allowed him to plug different AI assets (blitz, snipe, etc.) into agents to give them different behavior. So I don't think there's anything wrong with putting code in a ScriptableObject class just like any other class.

    Ultimately I think we're all in agreement, though, that ScriptableObjects are nothing magical. It's just a class that Unity can serialize.
     
  6. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    So I can kind of see the value of instantiating a ScriptableObject and using it as a configuration plugin. Kind of like a light prefab. It lets you set up the data in the inspector on a single instance, then treat that a template. But I really struggle to see what advantage this has over using regular components or vanilla C# classes. It seems like a very narrow use case where ScriptableObjects would be the go to. (I'll have to watch the video to see exactly what they are talking about. I may be missing something.)

    As far as I can see, the only advantage ScriptableObjects have is as persistent assets in the editor. If you don't need that persistence, a ScriptableObject is the wrong choice.
     
  7. ChrisDirkis

    ChrisDirkis

    Joined:
    Jun 1, 2017
    Posts:
    38
    Cross scene references/communication (through a pub/sub interface), is the other advantage. Objects from any scene can reference a ScriptableObject, so if you have a, say, MonsterScriptableObject, with a Color property, you can set the color in your UI scene, and if the Game scene is open, the Monster component on some gameobject can go "Hey, i'm a monster. What's my current color? Can you tell me when someone changes my color?"

    Since cross scene editing is _so_ much better than monoscene, especially with source control and collaborate, having these weak links/pubsub systems makes it really easy to decouple behaviour. If you separate out those events, too, you can hook up new behaviour to them pretty easily (eg. using the monster, have an OnColorChanged event that makes a cute particle burst when the color is changed), without having to actually change any existing code -- or, indeed, without having to write any code at all.
     
  8. GetBrinxed

    GetBrinxed

    Joined:
    Feb 21, 2018
    Posts:
    12
    That's exactly the kind of behaviour I'm making very heavy use of in the system I'm currently building. Using the concepts and examples from Ryan Hipple's talk as a basis, I took it a step further and built the variables in such a way that anything can listen for when their value changes (without needing to check the variable all the time).

    But adding that conceptually simple behaviour has required all those simple container variables to have some logic of their own. And getting that logic to correctly hook up has proven to be a challenge, mostly due to not completely understanding when SOs received their Unity callbacks. So if any of you have any tips to add to the above, please do, for all our sakes. :)
     
    arcanevibe, ModLunar and ChrisDirkis like this.
  9. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    I think some of the confusion comes from ScriptableObject instances vs. ScriptableObject assets.

    ScriptableObject.Awake() is only called on instances, when you create the instance. It's not called on existing assets. (But it is called on the asset when you first create it as an instance, before saving it as an asset.)

    ScriptableObject.OnEnable(), like you mentioned, is only called on assets when the editor loads the asset for the inspector, or when the asset is first referenced in a build. I don't think it's guaranteed to be called when switching into playmode.
     
  10. GetBrinxed

    GetBrinxed

    Joined:
    Feb 21, 2018
    Posts:
    12
    What's the difference between an SO instance and an SO asset?
    Aren't all SO assets instances?
     
    Last edited: Jul 19, 2018
  11. GetBrinxed

    GetBrinxed

    Joined:
    Feb 21, 2018
    Posts:
    12
    Just discovered that Awake() CAN be called more than once while the editor is open, all depending on scene loading.
    Similarly, OnDisable()'s call seem to also be tied in part to scene loading.

    Will update top description to match.
     
    Last edited: Jul 19, 2018
  12. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    An instance is just an object in memory. Here's an instance of a simple class, allocated using new:
    Code (csharp):
    1. public class Foo
    2. {
    3. }
    4.  
    5. Foo foo = new Foo();
    For ScriptableObjects, Unity provides a special ScriptableObject.CreateInstance() method to use instead of new:
    Code (csharp):
    1. ScriptableObject so = ScriptableObject.CreateInstance<T>();
    (And a special Destroy() method to cleanly deallocate it.) But apart from that, and the special methods and handling that it inherits, it's just an object in memory like any other.

    Only when you save it to an asset file in your Unity project does it become an asset.

    I guess it gets a little muddy because Unity will technically load an instance of the asset when it gets referenced. In the editor, Unity will serialize changes back into the asset, too.
     
  13. GetBrinxed

    GetBrinxed

    Joined:
    Feb 21, 2018
    Posts:
    12
    Ah, so you're saying the asset is just saved data (which doesn't do anything), but when it gets loaded for any reason, it creates an instance, and that instance is what receives the Mono callbacks.
     
  14. Marble

    Marble

    Joined:
    Aug 29, 2005
    Posts:
    1,268
    I've been really happy using SO for data in my current project. It's a serializable object that also supports inheritance, which is huge.

    For example, I can create a SO that defines the fundamental behavior of a game stage, then extend it to create assets that represent each game stage. At runtime, a stage can be instantiated from its 'template' asset when appropriate, modified during its lifetime with state information, and then automatically garbage collected afterwards. It makes a great state machine and has been a boon for nice event-driven behavior.

    They're also convenient for inclusion in Asset Bundles.
     
  15. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Right. But the editor will keep an instance around for a while; it only gets reloaded under certain circumstances.
     
  16. Marble

    Marble

    Joined:
    Aug 29, 2005
    Posts:
    1,268
    I've been curious, though: is there any pragmatic difference between using MonoBehaviour on a prefab and a SO asset?
     
  17. GetBrinxed

    GetBrinxed

    Joined:
    Feb 21, 2018
    Posts:
    12
    Go watch the linked Richard Fine talk. He explains some of the differences between SO vs Monobehaviour on a prefab.
     
    Marble likes this.
  18. Carlos9x

    Carlos9x

    Joined:
    Aug 5, 2017
    Posts:
    12
    I have tried and discovered that when entering play mode, OnDisable is called before OnEnable and I don't understand why. Has anyone here encountered this before?
     
  19. SugoiDev

    SugoiDev

    Joined:
    Mar 27, 2013
    Posts:
    395
    The current state is destroyed right before entering playmode, then re-created in playmode (a reload). So, your SO is indeed disabled before entering, then enabled after entering.

    You can check that Application.IsPlaying will be false in that OnDisable call.
     
    Guitoon, GetBrinxed and Carlos9x like this.
  20. Paul_Bronowski

    Paul_Bronowski

    Joined:
    Sep 3, 2014
    Posts:
    55
    From what I'm finding, my gut tells me something along these lines...

    If you call anything that causes a ScriptableObject asset to be instantiated (cloned) or loaded, Awake() will be called. Loading an asset for the first time creates the (let's call it) root_instance. Consider the reports in the forums of Awake() not being called when loading an asset. This happens because the root_instance is already loaded. You didn't ask for a new instance (clone), you asked for the root_instance which is already in memory and is returned to you, so no new instance, and therefore no Awake().

    When the Editor is involved it adds another complication. While you may tear down your ScriptableObject instances in code, that doesn't mean the Editor unloaded the asset. i.e. the root_instance is still alive.

    The presence of the loaded root_instance also seems to explain why OnDisable() may be called when entering playmode. It might make sense that root_instances would be disabled on play, as you may have been doing naughty things with them in the Editor and you are given the opportunity to clean up (reset). As playmode begins, OnEnable() is called, giving you the chance to re-initialize, just as if the game were running standalone. You definitely have the opportunity to create mayhem with root_instances in the Editor, if not careful. Try loading a root_instance from Editor code, give it some bad data and in some scene code, load the asset to get the root_instance and take a look at the data. I'd wager the badness persisted into playmode, if not fixed-up by OnDisable()/OnEnable().

    If you instantiate (clone) the root instance, Awake(), OnEnable(), etc. will be called for that instance, per usual.

    If you destroy an instance, OnDisable()/OnDestroy() should be called. But when you unload the root_instance, I'm not entirely sure either will be called.

    What's more, some editor APIs, like AssetDatabase.GetMainAssetTypeAtPath() appear to load the root_instance. I'm sitting here watching the new Addressable Asset System code peg my Awake() for an asset marked as Addressable, but not used/referenced in the executing code, when I did not ask for a load...
    upload_2018-9-17_0-23-39.png


    Does all this seem accurate?
     

    Attached Files:

  21. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    We use scriptable objects pretty much in the way you dont see them useful. We have a in-game tutorial each step in the tutorial is a scriptable object. A tutorial is just a collection of scriptable objects executed in sequence. Whats nice is that you can reuse teh same configured .asset multiple times.
     
    Paul_Bronowski likes this.
  22. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    This seems like perfect place to ask a noobish (ScriptableObject behavior related) question;

    Why should I use ScriptableObject.CreateInstance, instead of Instantiate?

    I've seen some examples here and there talking about this, but I've also seen people use Instantiate as well. Seems like both create exactly the same end result. If I have something like this:
    Code (CSharp):
    1.  
    2. public SO someSO; // ScriptableObject Asset ref, that only has two fields, int and float
    3. public SO so1; // For testing
    4. public SO so2; // For testing
    5.  
    6. void Start ()
    7. {
    8.     so1 = Instantiate(someSO);
    9.     Debug.Log("so1: " + so1.ToString() + " intValue: " + so1.intValue + " floatValue: " + so1.floatValue);
    10.     // Result:
    11.     // so1: soAsset(Clone) (SO) intValue: 2 floatValue: 3
    12.  
    13.     so2 = ScriptableObject.CreateInstance<SO>();
    14.     Debug.Log("so2: " + so2.ToString() + " intValue: " + so2.intValue + " floatValue: " + so2.floatValue);
    15.     // Result:
    16.     // so2:  (SO) intValue: 2 floatValue: 3
    17. }
    18.  
    If I run this, the end result is nearly the same, only name of the version created by CreateInstance will be missing the part "soAsset(Clone)". I can double click both in inspector to see what they contain, and they both look just the same. Only with different instanceIDs.

    Edit (I can at least answer partially to my question):
    If I got this right, it seems like Instantiate "clones" the Created asset, as it is. If I change the values (2 and 3) from the asset to 1 and 2, the result will be like below. Instantiate takes values from Asset, whereas CreateInstance takes the values from script file:
    Code (CSharp):
    1. // so1: soAsset(Clone) (SO) intValue: 1 floatValue: 3
    2. // so2:  (SO) intValue: 2 floatValue: 3
    But I have to say, I haven't found any threads or sources to mention this, or how this exactly works.
     
    Last edited: Sep 17, 2018
  23. Paul_Bronowski

    Paul_Bronowski

    Joined:
    Sep 3, 2014
    Posts:
    55
    You can do the same thing with a prefab. Both are just assets that can be loaded and then instantiated (copied). Prefabs are a bit different in that MonoBehaviour is involved. Depends on what you want.
     
  24. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    9,051
  25. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    Each step really dont have a relationship to a transform

    Here is an example,

    upload_2018-9-17_18-38-54.png

    A step that displays a popup message.

    A tutorial are a collection of these
    upload_2018-9-17_18-42-46.png

    Offcourse it could have been made without scriptable objects, but I liked the approach
     
    MD_Reptile likes this.
  26. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    @zombiegorilla - I did read that page, but I guess it didn't sink in to my head then... :/

    I guess I should have underlined that I haven't found anything that explains this clearly and thoroughly... it's not newbie friendly or very easy to understand. When you already understand the system, the words probably make sense, but not in my bonehead... All I got from this page are cryptic sentences like these:

    "CreateInstance - Creates an instance of a scriptable object. "
    "Instantiate - Clones the object original and returns the clone."

    The "Clones the object original" for Instantiate doesn't really sound too clear. They are calling the actual Asset file a "object original" and this actually means that this creates an exact copy of the existing ScriptableObject Asset file, along with it's inspector / however assigned values (not the ones defined in script file). Just wondering why they have to be so terse. But anyway, IMHO there are several such vague definitions in Unity docs that I find hard to understand clearly without experimentation, meaning I'm guessing... until I've figured the meaning by trial and error.
     
    ModLunar likes this.
  27. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    9,051
    It does state clearly that one is an instance and one returns a clone of the object. I’m not sure how it could be clearer.

    The api documentation isn’t an introduction to programming, it’s a description of what the api does. If therms used are unclear, there are introductory tutorials in the learn section as well as some great resources on the web.
     
  28. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    ScriptableObject.CreateInstance is a bit outdated since they added the CreateAssetMenu attribute which took away alot of the boilerplate code

    https://docs.unity3d.com/ScriptReference/CreateAssetMenuAttribute.html

    It can still be used to batch create stuff etc
     
  29. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    I do not know but it is weird for me as I use scriptableobjects for anything.
    I know it is awesome that I create FloatVariable, IntVariable, IntArray, etc and assign the scriptableobjects to several mono classes.

    In "PlayerHealth" example, I have already used it, created it and assign it to PlayerHealthUI class, etc but it is weird that there is only one field in a class (scriptableobject), share data to other mono classes.

    Have you seen this approach in other frameworks and environments?
    I would really like to consider scriptableObjects like initializers and immutable. If other classes like Healthbar want to access current health, they can do using the mediator mono class or events like ValueChangedEventHandler.
    I know it decreases modularity but ...

    ScriptableObjects create dependency hiddenly. Sometimes, they can be seen like controllers in MVC patterns.

    Code (CSharp):
    1. public class Player:MonoBehaviour{
    2.    [SerializeField]
    3.    private IntVariable _maxHealth;
    4.    private int _currentHealth;
    5.    void Awake(){
    6.       _currentHealth=_maxHealth.Value;
    7.    }
    8. }
     
    Last edited: Oct 27, 2018
  30. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Sure. Ultimately, it's just an object instance. The only difference is that you can assign a reference to it at design time.
     
  31. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    Yes it encapsulates values in references.
    I meant you see classes with only one field in other places?!
    Is it normal to use classes with one field?
     
  32. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Sure, if that perfectly represents its single responsibility, then yes. In some frameworks I've even seen classes that have no fields whatsoever, used sort of like tags.
     
    mahdiii likes this.
  33. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    I see it like css. One approach is to create css blocks with one properties like background-color or color or font and combine the classes to reach a special class in tag html.
    Some developers do not like this approach but others like.
    So you agree to modulate in the lowest level for example FloatVariable maxHealth (immutable) or currentHealth(muttable)?
    If I faced situations that I need a field (mutable or immutable) in two or more mono scripts, it will be appropriate to use scriptableObjects for the field?
     
  34. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    As long as it keeps your code concise, readable, maintainable, and reliable, I think that would be fine.
     
  35. mvinc006

    mvinc006

    Joined:
    Jan 1, 2018
    Posts:
    91
    I use scriptable objects as much as possible to act as a sort of global variable to avoid using singletons every where, I’m able to change the variables value from anywhere leaving my event system free to just call methods when needed without needing to pass variables.

    This topic is an interesting read but Oh god please don’t tell me I’ve done it wrong again. I’ve already revised my project twice now, last time was to remove singletons lol.

    Edit: maybe time to make a seperate thread again for me to ensure I’m on track
     
    ModLunar and mahdiii like this.
  36. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    One of my coworkers created a tool for that. You can do the same thing yourself - you just have to find them with the general Find methods and show them somewhere.


    An important thing to note is what happens if you create an SO, assign it to a MonoBehaviour in a scene, and don't save the SO as an asset. When you save the scene, that SO gets appended to the scene file. So you can have SO's that just live in a scene, if that's somehow useful.

    Unity also seems to do a perfect job of "garbage collecting" the appended SO's, so if no MonoBehaviours are referencing them anymore, they get automatically deleted when you save the scene. AFAIK, none of this
    is documented.


    We use SO for a bunch of runtime data. For example, all of the npc's in our game is defined in an SO. Methods to modify them are placed in the same file.

    The way this works is that we define all of our NPC's as ScriptableObject assets in the assets folder. At builds, those are written to a save file (ie. the state of a save file , and then copies are instantiated from the save file. We could have done this with an xml/json/whatever instead, but using SO's for this have some nice advantages:
    - Serialization is already solved
    - It's easy to create editors for the data
    - We can reuse the editors for the runtime data, so it's not that much work to create debug views of the game

    There's a big downside here, in that we had to make some special tooling for some kinds of data referencing other kinds of data. Both npcs and locations are Scriptableobjects like this, and both are fetched from the save file. This meant that we had to create special wrapper-types for fields, that allow designers to drag-and-drop assets like usual, but use IDs for serialization of the assets so the clones can be picked up on deserialization from a save file. It might sound bad, but the alternative is creating a completely from-scratch toolset around eg. xml, or doing some kind of creative leap where all SOs contain all of their data in a container that's cloned on startup.
     
    Socrates and TonyLi like this.
  37. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    676
    Fascinating discussion. Keeping your SOs immutable avoids a lot of problems, but I am not sure it is utterly necessary. What seems to happen is that Unity uses reflection to extract state and remembers it. Interestingly, for each SO you have created, a call to that SO's class's constructor occurs when you enter play mode, so I have to guess that Unity does not keep instances alive between exits and subsequent re-entries to play mode. However, it does restore the prior state it memorized for each instance. That includes state that changed during the last time your entered, then left, play mode.

    This leads to the somewhat odd behavior that, when you first create an SO, if its constructor initializes a private field, that initial value is retained for the lifetime of that SO, even if you modify its constructor to set the same field to another value. So, if you have
    private int x
    in your SO class, and your SO's constructor includes
    x = 3
    , when you create the SO, x will be given the value 3. But Unity remembers that value from then on, so that if you change your constructor and replace
    x = 3
    with
    x = 2
    , new instances of that SO will show x = 2, but instances you created before that change continue to show x = 3. This would make sense if the instances were kept around after the first time you create them, but that's not what appears to be happening. For whatever reason, Unity creates a full set of instances of all your SOs every time it enters play mode, restoring the state it extracted after it created them the first time. This leads to some inconsistent persistence. For example, multicast delegates do not appear to retain their subscribers across multiple entries to play mode. I am guessing that Unity either can't, or just doesn't, extract those lists, so they can't be retained (and, therefore, can't be restored).

    Ryan Hipple's video emphasizes the elimination of singletons by use of SOs. His example code shows how to avoid persistent state being a problem by using OnEnable and OnDisable handlers in his Monobehaviour objects, such that whatever sorts of mutation he applies when play mode is entered, all of that is undone when play mode is exited. It would be nice to do that with OnEnable and OnDisable handlers in the SOs themselves, but Unity appears to call these somewhat erratically and in no predictable order across SOs. (I can't consistently duplicate it, but Unity does call OnDisable for SOs that have never had OnEnable called.)

    I think I would be willing to use a very lean singleton to call all of my SOs in a given resource folder and, say, tell them to restore themselves to their initial state, then follow that with a call to all of those SOs to do whatever communication among each other is required to prepare for game-play. In a sense, Unity's run-time engine is such a singleton, sending OnEnable and OnDisable messages when it deems it appropriate to do so. Having a singleton of one's own do something similar, particularly if that singleton is immutable, does not seem all that awful, and it does allow for mutable SOs which can maintain all sorts of changing state independent of any GameObject.
     
    oscarAbraham likes this.
  38. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    I sometimes use scriptableObjects instead of singletons. Also, scriptableObjects are like controllers in MVC patterns.
    You can remove controllers and instead use scriptableObjects.
    Finally I utilize it for configure settings and read only data.
    Before, I applied scriptableObjects only for immutable data but I knew it is so useful and increases maintainability, if I use it for mutable data as well.
    scriptableObjects are very helpful. If you do not use scriptableObjects, you sometimes need to create many same prefabs(same meshRenderer, scripts, etc) with different values. It is awful.
    Also, you can create one scriptableObject and use it in several prefabs and monobehaviours.
     
    Last edited: Oct 31, 2018
  39. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    676
    I tend to see them more as models in the MVC pattern. Controllers were really just part of the Smalltalk language. They pretty much don't exist in the current MVC pattern, which is more of a Document-Observer pattern, I think.
     
  40. r618

    r618

    Joined:
    Jan 19, 2009
    Posts:
    1,305
    that whole roundtrip is called serialization btw :) in unity land at least [it can cross native/managed boundary that way]
    did you by chance have [SerializeField] attribute on that private field ?

    EDIT: Oh, and btw scriptable objects are special in that changes from play mode in the editor are persistent - as opposed to 'normal' MonoBehaviours where changes from play mode are not transferred when stopping it (i.e. are restored back (via serialization system))
     
    Last edited: Nov 1, 2018
  41. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    676
    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:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. [CreateAssetMenu]
    4. public class TwoValues : ScriptableObject
    5. {
    6.     public int x;
    7.  
    8.     private int y;
    9.  
    10.     private void OnEnable()
    11.     {
    12.         Debug.Log("Before: " + y);
    13.         y = x;
    14.         Debug.Log("After:  " + y);
    15.     }
    16. }
    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:

    Before: 0
    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.
     
    Last edited: Nov 1, 2018
    mvinc006 likes this.
  42. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    676
    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.
     
  43. mvinc006

    mvinc006

    Joined:
    Jan 1, 2018
    Posts:
    91
    Ohh boy I can see how that could make debugging unusual behaviour in editor a total nightmare. I will definitely keep this in mind.
     
  44. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    Which one of them do you use?

    Code (CSharp):
    1.  
    2. public ClassSpawner:MonoBehaviour{
    3.    [SerializeField]
    4.    private GameObject _prefab;
    5.    [SerializeField]
    6.    private IntReference _value;
    7.    void Spawn(){
    8.       var obj=Instantiate(_prefab);
    9.       obj.GetComponent<Class1>().Initialize(_value); //or
    10.       obj.GetComponent<Class1>().Initialize(_value.Value);
    11.    }
    12. }
    13.  
    14. public class Class1:MonoBehaviour{
    15.    [SerializeField]
    16.    private IntReference _data;  //SO
    17. }
    18. //---------------------------------------------------------------------------
    19.  
    20. public class Class1:MonoBehaviour{
    21.    private int  _data;
    22.    public void Initialize(int data){
    23.       _data=data;
    24.    }
    25. }
    26.  
    27. //---------------------------------------------------------------------------
    28. public class Class1:MonoBehaviour{
    29.    private IntReference _data;
    30.    public void Initialize(IntReference data){
    31.       _data=data;
    32.    }
    33. }
     
  45. mvinc006

    mvinc006

    Joined:
    Jan 1, 2018
    Posts:
    91
    Currently I’m doing it this way
    Code (csharp):
    1. obj.GetComponent<Class1>().Initialize(_value.Value);
     
  46. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    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 :)

    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?
     
  47. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    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.
     
    MD_Reptile likes this.
  48. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    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 :p

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

    Gekigengar

    Joined:
    Jan 20, 2013
    Posts:
    738
    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?
     
  50. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,694
    Yes.