Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

Discussion ScriptableObject + SerializeReference = Universal Data Container?

Discussion in 'Editor & General Support' started by mitaywalle, Oct 14, 2022.

  1. mitaywalle

    mitaywalle

    Joined:
    Jul 1, 2013
    Posts:
    207
    ScriptableObject is actively pushed by UT as Architectual solution:
    - Better Data with Scriptable Objects in Unity! (Tutorial)
    - Unite 2016 - Overthrowing the MonoBehaviour Tyranny in a Glorious Scriptable Object Revolution
    - Game architecture with ScriptableObjects | Open Projects Devlog
    - Unite Austin 2017 - Game Architecture with Scriptable Objects

    And you can find hundreds tutorials from community with this approach

    - Brackeys : SCRIPTABLE OBJECTS in Unity
    - just serch at youtube

    Today at 2022 is it still optimal solution? Is it really universal, at all approaches? Lets Consider different approaches to data in development, and how we can (if can) resolve different requirements by ScriptableObject, and is this solution optimal or we need better solution from UT.

    Shared State
    Data example: Max HP

    This approach is in every video-tutorial, as you can found, ScriptableObject is greatly share data between many MonoBehaviour-Instances.

    How we can resolve this with ScriptableObject?
    We create one ScriptableObject and assign it to all our target-user-scripts

    Are available solutions optimal and by my opinion fully resolve approach?

    - yes

    Non shared State
    Data example: Current HP


    The opposite for Shared, this approach expect that data is unique for every instance of target user-script.

    How we can resolve this with ScriptableObject?
    We can use ScriptableObject.CreateInstance() in MonoBehaviour.Start()

    Are available solutions optimal and by my opinion fully resolve approach?
    - almost. only one problem is that if you create ScriptableObject dynamicly it has no file, and you can't observe them all in one list. We can write some DynamicScriptableObjectExplorer, but may be UT should do this for us?

    Temporal

    Data example: Current HP

    Expects that value shouldn't be saved between playmode-sessions and between buildrun-sessions.

    How we can resolve this with ScriptableObject?
    Buildrun: default ScriptableObject behaviour resets values to defaults at buildrun
    Playmode: We can use MonoBehaviour.Start() to reset values in ScriptableObjects or InitializeOnEnterPlayMode attribute, but it should be used with static function so you need to implement some logic reinit all your instances of scriptableObjects.

    Are available solutions optimal and by my opinion fully resolve approach?
    - partialiy resolved. In terms of Shared non-shared state Temporal values are always a problem, cause sometime we wanna share Temporal values between systems (share current HP for DamageSystem and HUD), but not share them between instances (Player, Enemy1, Enemy2 etc).

    What is your expirience and approach with Temporal Data in ScriptableObjects?

    Default for Temporal
    Data example: Start HP

    Shared or non-shared this value need to be available for our ResetTemporal() logic.

    How we can resolve this with ScriptableObject?
    Shared approach work fine here, we create one ScriptableObject and assign reference in our MonoBehaviours to it

    Are available solutions optimal and by my opinion fully resolve approach?

    - yes

    Hardcoded
    Data example: Item ID

    At this moment mutabilty of values of ScriptableObject is bad for identifiers. It can lead to human-factor errors, duplicative IDs, force developer to write boilerplate validation-systems.

    How we can resolve this with ScriptableObject?
    At this moment UT itself use code generation for hardcoded data, you can found example of this in UT Input System. It's widely used to generate Enum, with hardcoded items list, this can be used for Item ID, with human-readable names, instead of simple Int32.

    Are available solutions optimal and by my opinion fully resolve approach?
    - yes

    Non Hardcoded
    Data example: Item Database

    We need to allow gamedesigners to expand item database, if possible - without programmer participation.

    How we can resolve this with ScriptableObject?
    We create Item : ScriptableObject and input-point for ScriptableObject-list (separated ItemsDB : ScriptableObject or allow to load Item : ScriptableObject by ItemID : int / string , from fixed address ).

    Are available solutions optimal and by my opinion fully resolve approach?
    - yes

    Overrides (prefab variant analogue)
    Data example: Item Database

    Items in database can differs from eachother only few values, for example Heal Potion 50%, Heal Potion 10% - this items has same Name, Icon, Description, behaviour, pickup prefab, pickup sound, only difference - heal value. Overrides allow to simplify long support, when we have dozens of mostly similar items.

    How we can resolve this with ScriptableObject?
    - there is user-defined solutions for ScriptableObject variants, based on reflection:
    -- free: https://github.com/GieziJo/ScriptableObjectVariant has specific GUI
    -- paid: https://assetstore.unity.com/packages/tools/utilities/asset-variants-224010
    - we can use prefabs instead of ScriptableObjects, but we lose serialization.
    - in Unity 2022 Material Variants are implemented, we are really waiting same for ScriptableObjects

    Are available solutions optimal and by my opinion fully resolve approach?
    - no. We are waiting builtin ScriptableObject Variants

    Later:
    Hierarchical
    Flat (Sibling)
    Serialization
    polymorphysm
    Version Control (Git)
     
    Last edited: Oct 14, 2022
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    3,377
    Or you can use one or more scriptable objects encapsulating a plain class or struct, and just copy that object per run-time instance. No need to instance scriptable objects at runtime, it's pointless imo.

    Not really? C# has these things called Properties of which you can make read only through code. You can also, with minimal editor coding, make read only fields. There, problem solved.

    I've been working on this, actually, using the old school prototype pattern as described by the that one book on game design patterns.

    Here's a little sample:
    upload_2022-10-14_23-5-56.png

    (Lots of Odin Inspector making this possible).

    So not so much 'overrides', but you can make objects with various properties and them mash them together to build bigger items out of them. Right now I've only been playing with names, but the concept can be applied to... basically any property you could define in a component, which, of course, are being serialised with SerializeReference.

    But honestly, SerializeReference is good at what it says on the box, serialising values by reference, so anything that's not a UnityEngine.Object or a value type.

    Same with scriptable objects. They're good as sacks of data, sometimes for transmitting data between scenes, and a few other things if you get creative.

    And if you want designer friendly, learn editor coding.
     
    mitaywalle and Homicide like this.
  3. mitaywalle

    mitaywalle

    Joined:
    Jul 1, 2013
    Posts:
    207
    Shure it's fine to clone plain class, instead of ScriptableObject. There is pros and cons with using ScriptableObject vs C# plain class, and main + of ScriptableObject is seprated Inspector.

    Properties won't help to defend user defined ID, you need to write own validation. And I've wroten that it's not problem, there're other approaches, point wasn't to count and discuss them all. Certainly, you can write custom create function for creation ScriptableObject, define there unique ID and hide it from inspector. But you can't disallow user to duplicate existing ScriptableObject, then you again need to validate ID's externaly.

    As you tell yourself - this is not overrides. That mean, that we can simplify maintenance of huge ScriptableObject-based databases. Your solution is just about solving another problem, I'm planning to write about it at this thread later, in Flat and Hierarchical approaches.

    I've no problems with Editor coding. It doesn't mean, that basic system and concepts shouldn't progress.
    For example:
    - earlier we haven't SerializeReference, and no polymorphism with at plan classes. And it changed, and it's great
    - earlier we haven't prefab overrides
    - earlier we haven't Material Variants
    etc
     
    frarf likes this.
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    31,167
    mitaywalle likes this.
  5. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    3,377
    You know you can make plain classes editable in the inspector?

    Code (CSharp):
    1. public class MyScriptableObject : ScriptableObject
    2. {
    3.     [SerializeField]
    4.     private MyClass myClass = new myClass();
    5.    
    6.     public MyClass GetMyClassInstance() => return new MyClass(myClass);
    7. }
    8.  
    9. [System.Serializable]
    10. public class MyClass
    11. {
    12.     [SerializeField]
    13.     private int _someInt;
    14.    
    15.     [SerializeField]
    16.     private string _someString;
    17.    
    18.     public int SomeInt => _someInt;
    19.    
    20.     public string SomeStrong => _someString;
    21.    
    22.     public MyClass() { }
    23.    
    24.     public MyClass(MyClass myClass)
    25.     {
    26.         _someInt = myClass._someInt;
    27.         _someString = myClass._someString;
    28.     }
    29. }
    See, simple. Best of both worlds.

    I mean, and that's a problem Unity is never going to solve for us. Once again it comes down to producing the editor tools you and your team need to suit your work-flow, or getting addons that provide that functionality out of the box.