Search Unity

Scriptable Objects workflow

Discussion in 'Open Projects' started by Neonage, Oct 4, 2020.

  1. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    In this thread I want to discuss how we would work with ScriptableObjects and create architectures using them.

    If you don't know what ScriptableObject is, here's short introduction:

    With this cool blog-post attached!

    Me and guys are working on Scriptable State Machine in this thread.

    Leave all your thoughts in there, especially if you've had trouble with SO in the past.
    I'd love to research common usaged of them and establish best workflow for everyone!
     
  2. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    How can we leave out one of the most watched videos on the Unity YouTube channel? (and for a reason)



    What do you think of this talk?
     
    kcastagnini likes this.
  3. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    Because it's already in blog-post I've linked :D

    I feel like what they've made in not completely right way to go, but fundaments are great!

    What there is mostly missing is nice editor support for SO organization, debugging and easier inspection.
    Like for example, ability to attach Variables to the scriptable object asset:
    upload_2020-10-4_21-10-30.png
    That's the one I'm working on right now :)

    Modular data-driven architecture is the future!
     
  4. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,091
    SO make sense for data but not for all types of events. In terms of this project it will also be tough to see source-control changes and figure out what exactly happened (code is much cleaner is this aspect).

    From different thread about SO events https://forum.unity.com/threads/scene-loading-system.980178/:
    I dislike this approach a lot but who knows, maybe I don't know something.

    1. How do you find references in project to a ScriptableObject event?
    2. Can you modify respond to an event (adding/removing listeners) without modifying a scene? (in case where there's GameObject on a scene that uses ScriptableObject).
    (...)
    I dislike SO events in general, tried them in my project and they were terrible. I would only use them if I had 9 designers and 1 programmer in a team.

    With other approaches you can quickly see all references/listeners/raisers.


    In this example wrappers can be made to let designers use them in editor (for ex. ButtonLoadMenu) and they give more context than very generic "GameEvent".
     
    Last edited: Dec 19, 2020
    kcastagnini and MUGIK like this.
  5. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,091
    Simple example from current scene loading solution. Main menu scene has Start button with
    GameEvent_LoadNextLevelWithProgress
    SO event attached in inspector, let's say someone wants to change it to different one. Here's what git will show:

    Well, what can I say, have fun deciphering that. Feel free to use SO events and learn how bad they are the hard way, especially when project gets bigger.
     
  6. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,618
    MUGIK likes this.
  7. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    I don't think you would look at the data itself outside of the editor (why would you?)

    As a programmer, I very much hate boilerplate delegates hardly written in code.
    They're much harder to keep track of, like with singletons but worse.
    And it's not very scalable, ether modular.

    With SO events you always know why error is caused: it's a problem with behaviour tree or a missing data component.

    As a designer, I love to simply drag objects here and there and just change the game logic like that, it helps to came up with a new ideas really easily

    You just look at the GO that references some data/event, why would you need all of the references at once?
    If you'd want to, it's not so hard to write editor window for that.

    I didn't get that. GO listeners should live only in the scene, aren't they?
     
  8. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    It's an intresting feature request.
    But I totally dislike usage of Prefabs as data containers. (it's an abuse of concept!)
    ScriptableObjects are easy to reference, share, and edit. They're beautiful data containers with logic support.
    I would love not to break that ecosystem ever.

    We can have "Variants" support just by extending Variable Objects concept.

    Also, it would be dope to have "Add Component" in the SO inspector that just adds sub-asset of specific type :)
     
  9. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,091
    Github

    Not sure what you mean but even singletons can be modular and testable if properly made (default implementation).

    Usually you don't know what GO references an event, you also don't know in what scene/prefab that GO is. Sometimes it's trivial to find and sometimes it's not (for ex. GO that was just destroyed in playmode). Much slower then "Find All References" in VS that takes 3 seconds.
    Yes, there are tools for that already but they get slow quickly with project size and complexity.
     
  10. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    @cirocontinisio is it really mandatory to review guid meta data outside of Unity?

    That's the point, we'll need to make some "Event Logger" window for that.

    Then they writen not smartly
     
  11. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,091
    It's about reviewing PRs without having to open Unity, the less meta data changes, the better.

    Case A


    You:
    1. Don't know what changed.
    2. Don't know on what object the change occurred, could scroll up to see GO name but it's so far I couldn't fit it in one screenshot:



    Well, now you know the name but you still don't know what it is exactly.
    3. After finding GO in Unity, now you have to review event listeners - would have to use 3rd party tools to find them or guess.

    Case B:

    1. You know what changed.
    2. Still don't know on what object change occurred but now it's a lot simpler to find it.
    3. F9 on LoadNextWithProgress to find all listeners.
     
  12. MUGIK

    MUGIK

    Joined:
    Jul 2, 2015
    Posts:
    481
    So, you are saying that you don't like abuse Prefabs workflow, but you literally recreating Prefabs functionality using scriptable objects, lol.

    Do you know what really cool about using Prefabs over ScriptableObjects?
    - their component nature by design
    - they support nesting
    - they support overrides out of the box which is very handy (https://gameprogrammingpatterns.com/prototype.html#prototypes-for-data-modeling)

    I think if we are going to abuse ScriptableObjects then it's better to abuse Prefabs (and add some editor scripts to protect such Prefabs from being instantiated and misused. It's simpler than recreate the whole system)

    Anyway, this project is community-driven so there is definitely a lack of communication between all of us cuz we are not working in the same company/office/workspace. Let's just use a standard way of doing things.
    I think it's better to allow designers to do their job - create content and level design.
    Developers will do the rest.
     
  13. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    upload_2020-10-5_2-8-14.png

    Uninstanced Prefab seem more like recreation of ScriptableObject :)
    And I'm not, I'm just extending upon whats already there

    You can't reference nested game object in the inspector.

    Here's simplest example of SO variation:
    upload_2020-10-5_2-18-57.png
    It's just referencing data from base and has single override variable.

    You can use these objects anywhere, it's super modular and natural for design.

    @superpig may we have your opinion on that? :)
     
  14. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    Btw, Rider has integrated support for Unity assets usages
     
  15. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,091
    Correct if I'm wrong but not really, it has support for code usages by Unity assets. Since GameEvent SO is very generic you can only see all references to all unknown events - that's kind of useless.
     
    Last edited: Oct 4, 2020
    MUGIK likes this.
  16. Jirushi

    Jirushi

    Joined:
    Jan 17, 2020
    Posts:
    33
    What about an SO with a list or array of listener interfaces? It's debug-friendly, serializable and promotes class granularity.
     
  17. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    It kinda looks for references, but it doesn't show all of them at once
    upload_2020-10-5_12-44-16.png

    upload_2020-10-5_12-43-19.png

    upload_2020-10-5_12-45-40.png
     
  18. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    I think so too! :)
    There is really no other way to call SO logic rather than by reference
     
  19. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,659
    My opinion is that it is a bad idea to make decisions about which tool to use without a clear understanding of which problem you are using it to solve ;)

    It's true that if you need a very 'component-based' design, using an uninstantiated prefab might be less work than an SO (though you can create it out of SOs as well, if you want). The addition of prefab workflows like overrides and variants is an interesting angle as well. I stand by the arguments I made before about their downsides, but just because they have some downsides doesn't mean you couldn't use them (especially if you're just going to recreate the same downsides with SOs anyway).

    Ultimately, both approaches are just tools, and having more tools available to you is always better. It's good to understand the pros and cons of each tool, as well as principles about how to choose (e.g. why using a simpler, less capable design might be preferable), but trying to make up-front decisions about which tool to use in the absence of specific problems to solve is a bad idea.
     
    cirocontinisio, MUGIK and Neonage like this.
  20. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,091
    It is a bit better in your example with SimpleParticleEvent instead generic GameEvent but still from your screenshots it's unclear what
    SimpleParticleEvent particles
    event does, what event it is in a context etc and it will get worse the more that event is used. Also not everyone has Rider.
     
    kcastagnini likes this.
  21. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    It's very clear what it's doing, cause it just plays particle that we give reference to :)
     
  22. Little-Big-Monkey

    Little-Big-Monkey

    Joined:
    Mar 4, 2014
    Posts:
    40
    Hello,

    I agree with Kamyker, it's way easier to manage things in code, especially as he stated for code review before publishing changes. (You always review every changes before commiting them, right ? - Even scenes changes !)
    I would say it's way better to have a good API where you can easily find references in VS (yeah shift-F12 is so useful) to debug effectively then searching every single scenes for a script that could have missing references.
    Scriptableobjects are only great for holding data or code block to plug in the logic.

    You can't rely on the designer to do the logic themselves in the editor or it will be just an horrible mess.
    When you have to create system and architecture, you only expose in the editor things that are relevant.

    I would also recommend to use standard c# event over unityevent in most case, it's even more easier to use and more in c# coding convention.
     
  23. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    First of all, designers are not stupid. If they see there's an error with Behaviour Tree - it's their responsibility to fix it.
    And a programmer responsibility is to show them a nice error message that component they want to invoke was not attached to an instance or something.
    It's a really nice and productive separation of work in my opinion.

    And UnityEvent was a mistake :)
     
  24. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    This is actually what I have right now.
    NpcBehaviour attaches components from exposed fields onto StateMachine and States are working upon that data
     
    Last edited: Oct 5, 2020
  25. Little-Big-Monkey

    Little-Big-Monkey

    Joined:
    Mar 4, 2014
    Posts:
    40
    Haha yeah, but your code logic still have to be idiot proof, you know it :p
    So yes, I agree, that's a good practice.


    I'm catching up on this, sounds good so far.
     
    Neonage likes this.
  26. kcastagnini

    kcastagnini

    Joined:
    Dec 14, 2019
    Posts:
    61
    Nobody said or even implied designers are stupid.
    Please avoid making this kind of assumptions just to make your point more "convincing" because we aren't stupid either.
     
  27. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    That was thrown out of the wild, don't take so seriously :)

    Pardon my english, I didn't catch up that line
     
  28. kcastagnini

    kcastagnini

    Joined:
    Dec 14, 2019
    Posts:
    61
    I have a question on the Scriptable Objects workflow.
    Let's say I have an instance of a SO representing a simple int value that is shared between a Player class and a DisplayScore class that displays this number. I want the Player class to be able to write that int and the DisplayScore to only read it. How do I ensure that this constraint will be respected?
     
  29. MUGIK

    MUGIK

    Joined:
    Jul 2, 2015
    Posts:
    481
    Using several layers of abstraction, idk?o_O:)

    Like:
    Code (CSharp):
    1. public abstract class ReadonlyVariable<T>
    2. {
    3.   public T Value { get; }
    4. }
    5.  
    6. public abstract class Variable<T> : ReadonlyVariable<T>
    7. {
    8.   public T Value { get; set; }
    9. }
    10.  
    11.  
    12. // In the player script use this
    13. [SerializeField] private Varialbe<float> _health;
    14.  
    15. // In the UI script use read-only version
    16. [SerializeField] private ReadonlyVariable<float> _health;
    Anyway, I don't really like this approach, because the game state will be scattered across these small assets all over the project. It's a mess, in my opinion.

    UPD
    Forgot that for this SO workflow you also need the References.
    So there also will be ReadonlyReferece and Reference classes. And assets to represent these references.
    Now, that's a nightmare:D

    UPD2
    Aaaand!
    You would like to also have something like
    Code (CSharp):
    1. public class SerializedReference<T>
    2. {
    3.     [SerializeField] private T _plainValue;
    4.     [SerializeField] private Variable<T> _variableReference;
    5.  
    6.     public T Value
    7.     {
    8.         get
    9.         {
    10.             // if variable reference is set in the inspector, then use value from it
    11.             if (_variableReference != null)
    12.                 return _variableReference.Value;
    13.             // otherwise, use a plain value, that set from inspector
    14.             return _plainValue;
    15.         }
    16.         set
    17.         {
    18.             // the same logic for setter
    19.             if (_variableReference != null)
    20.                 _variableReference.Value = value;
    21.             _plainValue = value;
    22.         }
    23.     }
    24. }
    Which then will be used in the scripts for things like maxHealth, walkSpeed, runSpeed etc. For variables that will not change or we don't want to track their changes or we are not sure if we really would use an asset to store the value...

    Please, guys, stop this:(
     
    Last edited: Oct 8, 2020
  30. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    Yes, I wanted to suggest the same. Tho' it's a little harder to do in 2019.4, as we don't have generic types serialization and we need to make a lot of sub-classes :(

    Agree, it can became nightmare if implemented poorly.
    We need nice custom editors and organization for this in order to work, I'm doing a lot of experiments in this area :)
    For now we'll go without SO Variables, until I came up with something we can discuss more on
     
  31. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    In this case, Character instance would use base SO reference, not Variables one-by-one.
    Only generic-systems like DisplayScore are working with single-variables.

    With this approach, in theory, we can write "Overrides" (some variables that we want to be unique on some instances)
     
  32. MUGIK

    MUGIK

    Joined:
    Jul 2, 2015
    Posts:
    481
    Last edited: Oct 8, 2020
  33. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,618
    What problem is the "ScriptableObject Variable" supposed to solve and how often does that problem occur?
     
  34. MUGIK

    MUGIK

    Joined:
    Jul 2, 2015
    Posts:
    481
    For example: when you want easily track variable changes. You would like to have access to debug value from the inspector, also it would be cool to have some on-change events. And you'd like to inject them through Inspector.

    This is very similar to a reactive property, but you can reference and inspect them through the inspector.

    This approach most valuable for small games like prototypes where you don't want to spend much time on writing boilerplate code for literally 10 variables in your game. When you have more variables - it's a pain in the ass to manage all the things.
     
  35. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    1) SO Variation:
    2) Referencing only Variable instead of whole object.
    Example: UI and Audio systems that reading same Health object instead of
    player.health

    Such asset based systems can be easily reused across projects, avoiding unnecessary modifications.

    I'm not sure about this, it just adds another layer of complexity without solving much problem.
    This kind of thing needs to be handled by StateMachine IMO.

    It looks like over-overcomplicated mess in combination with ugly UI.
    That's not what I want to achieve in the end
     
    Last edited: Oct 8, 2020
  36. kcastagnini

    kcastagnini

    Joined:
    Dec 14, 2019
    Posts:
    61
    MUGIK likes this.
  37. MUGIK

    MUGIK

    Joined:
    Jul 2, 2015
    Posts:
    481
    Yes, using this approach you need to explicitly create all the variables variants: IntVariable, FloatVariable, BoolVariable, GameObjectVariable, ParticleSystemVariable, and so on.

    UPD
    Oh, I get what you mean. I've changed SerializedVariable to have TVariable generic.
     
    kcastagnini likes this.
  38. kcastagnini

    kcastagnini

    Joined:
    Dec 14, 2019
    Posts:
    61
    The fact that you specified 2019.4 made me wonder if this is possible in 2020.1 and it is, I didn't even know it!

    https://unity3d.com/unity/alpha/2020.1.0a3
    • Scripting: The serializer can now serialize fields of generic types (e.g. MyClass someField) directly; it is no longer necessary to derive a concrete subclass from a generic type in order to serialize it.
    This is huge for editor scripting!
     
  39. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    Yes! I don't know why they won't backport it :(

    You mean from the user perspective? The Editor scripting is remaning the same, as you've been always able to create custom editors for generic classes like this:
    upload_2020-10-8_22-6-30.png
     
  40. kcastagnini

    kcastagnini

    Joined:
    Dec 14, 2019
    Posts:
    61
    Let's say you have a [SerializeField] Variable<float> var; field in your class. In 2019.4 you can edit the float field contained in Variable but you would lost the changes when entering/exiting PlayMode.
    In 2020.1 on the other hand the value will be preserved because Unity can now serialize generic field types.
    Am I missing something?
     
  41. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    You're talking about 'Player scripting', but I thought about the 'Editor' :)

    As it's not serializable, you won't even see it in inspector :p

    But in 2020.1 you simply do this:
    upload_2020-10-8_23-13-50.png
    and...
    Tada-a-a!
    upload_2020-10-8_23-13-34.png
     
    kcastagnini likes this.
  42. shuttle127

    shuttle127

    Joined:
    Oct 1, 2020
    Posts:
    183
    @Neonage Out of context, "Intense Shaking Child," yikes!
     
    Neonage likes this.
  43. kcastagnini

    kcastagnini

    Joined:
    Dec 14, 2019
    Posts:
    61
    Yes exactly, and now we can build inspectors that work with generic property fields directly instead of non generic ones.
    That's why I say that this is huge for editor scripting!
     
  44. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    I said that we can do this in 2019.4 as well :confused:
    Same goes for [CustomEditor]
     
  45. kcastagnini

    kcastagnini

    Joined:
    Dec 14, 2019
    Posts:
    61
    That's not what I am talking about! I am talking about bulding an inspector for a class with generic FIELDS.

    Code (CSharp):
    1.  
    2. [Serializable]
    3. public class ValueWrapper<T>
    4. {
    5.     public T Value;
    6. }
    7.  
    8. public class GenericComponent<T> : MonoBehaviour
    9. {
    10.     [SerializeField] private ValueWrapper<T> Wrapper; //generic class field
    11. }
    12.  
    13. [CustomEditor(typeof(GenericComponent<>), true)]
    14. public class GenericComponentEditor : Editor
    15. {
    16.     private void OnEnable()
    17.     {
    18.         //prints "False" in 2020.1 and "True" in 2019.4 and before
    19.         Debug.Log($"Property is null: {serializedObject.FindProperty("Wrapper") == null}");
    20.     }
    21. }
    22.  
    23. //this is attached to a GameObject in the scene
    24. public class FloatGenericComponent : GenericComponent<float> { }
     
  46. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    You need to declare sub-class for field in order to serialize it
    upload_2020-10-9_0-37-26.png
     
  47. kcastagnini

    kcastagnini

    Joined:
    Dec 14, 2019
    Posts:
    61
    wat
    Not in 2020.1
    Please take a look at my example, did you understand what I am trying to say?
     
  48. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    If you declare field with sub-class in 2019.4 it will work with generic editor! :confused:
     
  49. kcastagnini

    kcastagnini

    Joined:
    Dec 14, 2019
    Posts:
    61
    I know that! But the point is to be able to create serializable generic fields so that I don't have to create sub-classes fields in order to be able to serialize them!
    That's a huge advantage for editor scripting!
    How would you handle inspector editing for the Wrapper field in GenericComponent in 2019.4 for example? How would you access that property from the inspector?
     
    Neonage likes this.
  50. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    Oh wait, now I see what you mean.
    Man, it requires a whole sh** show in order to work in 2019.4:
    upload_2020-10-9_1-35-11.png
    How can I unsee it?