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.
  2. Dismiss Notice

Attribute, magic fieldname or interface to assign a value?

Discussion in 'General Discussion' started by Peter77, Dec 10, 2020.

  1. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,444
    I'm writing a "ScriptableObject Container" (later available on github) that allows to add a ScriptableObject as sub-asset to the container. The container acts basically as a "GameObject" and the ScriptableObject sub-asset as "Component".

    In the Inspector it's presented in a similar way to how Components on a GameObject look.

    When you create a new container it's empty first:
    upload_2020-12-10_18-17-58.png

    ... then you click the "Add Object" button and can select what ScriptableObject type you want to add as sub-asset. When added, it looks like this:

    upload_2020-12-10_18-19-4.png

    The code for the TestThing sub-asset is just a regular ScriptableObject:
    Code (CSharp):
    1. [CreateSubAssetMenu(menuName = "Test Thing")]
    2. public class TestThing : ScriptableObject
    3. {
    4.     public Color color;
    5. }
    I don't want to implement a base "SubAsset type" to allow maximum flexibility.

    The "ScriptableObject Container" on the other hand is a custom ScriptableObject class, because it tracks which sub-assets it contains, provides
    GetObject
    and
    GetObjects
    methods, similar to how a GameObject provides the GetComponent and GetComponents methods.

    So far so good, now I get to the problem description.

    I want to allow that ScriptableObject sub-assets can have a reference to its container, which gets automatically set. This is where I can't decide what's the "best" approach from an users point of view. I can think of three different approaches:

    Attribute
    Use an attribute (this is the current approach I implemented):
    Code (CSharp):
    1. [CreateSubAssetMenu(menuName = "Test Thing")]
    2. public class TestThing : ScriptableObject
    3. {
    4.     public Color color;
    5.  
    6.     [SubAssetOwner]
    7.     [SerializeField] MyContainerType m_ScriptableObjectContainer;
    8. }
    You decorate a field with the
    [SubAssetOwner]
    attribute and the editor automatically assigns the
    m_ScriptableObjectContainer
    field to the container asset. It allows to pick any name for the field and it does not force my own naming/codestyle on usercode. it feels very Unity'ish to me, they seem to use attributes for all sorts of things too.

    Magic Fieldname
    Use a hard-coded fieldname to describe you want to have this field set to the container:
    Code (CSharp):
    1. [CreateSubAssetMenu(menuName = "Test Thing")]
    2. public class TestThing : ScriptableObject
    3. {
    4.     public Color color;
    5.  
    6.     [SerializeField] MyContainerType m_ScriptableObjectContainer;
    7. }
    If a sub-asset type has a field named
    m_ScriptableObjectContainer
    which is of type
    ScriptableObjectContainer
    , it gets set to the container asset automatically. Also kinda Unity'ish, similar to the magic methodnames for Awake, Update, etc.

    Interface
    Use an interface that must be implemented on the sub-asset type:
    Code (CSharp):
    1. [CreateSubAssetMenu(menuName = "Test Thing")]
    2. public class TestThing : ScriptableObject, IScriptableObjectContainerProperty
    3. {
    4.     public Color color;
    5.  
    6.     [SerializeField] ScriptableObjectContainer m_Container;
    7.  
    8.     public ScriptableObjectContainer container // the interface implementation
    9.     {
    10.         get => m_Container;
    11.         set => m_Container = value;
    12.     }
    13. }
    If a sub-asset type implements the
    IScriptableObjectContainerProperty
    (or whatever name), the editor uses this to set the container. This adds more complexity to it than the other two approaches in my opinion and also forces my naming for it on the public members in usercode.

    I prefer the attribute approach, because it seems most flexible to me. I don't force a specific (public) property to the user like with the interface and I don't force specific fieldnames to the user as well.

    What would be your preferred approach and why?
     
    Last edited: Dec 11, 2020
  2. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,877
    Attribute seems the best approach and does not seem to limit you much. I would say after that interface is next best approach.

    But seeing as this is for unity, attribute would be most in "house style" I think.
     
    Peter77 likes this.
  3. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    Attributes are a old relic that should be avoided when possible. For example fluent mapping is a better approach.
    That said Unity framework is a special case since you are so tightly coupled to it anyway.

    But just know that attributes out of the box means high coupling. Something we try to minimize in software development.
     
    Last edited: Dec 11, 2020
    Peter77 likes this.
  4. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    What happens when multiple containers add same object?

    I'd rather avoid any cyclic references at all in this case, and rather made a lookup utility class for searching container(s) by ScriptableObject reference.

    Otherwise - interface - for best transparency.
    Also, please don't use lowercase property names, stick to the MS naming scheme. (container should be Container)
     
    Havyx, Peter77 and MadeFromPolygons like this.
  5. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,444
    Thank you all for the feedback! :)

    I think this can't happen. Unity allows to add a sub-asset to one parent asset only, it's using AssetDatabase.AddObjectToAsset. The sub-asset is "physically" part of the container:
    upload_2020-12-12_9-48-55.png

    You could go and overwrite the "container" field after the sub-asset has been added to the container, but that seems more like trying to break it on purpose.
     
    xVergilx likes this.