Attribute, magic fieldname or interface to assign a value?

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:

… 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:

The code for the TestThing sub-asset is just a regular ScriptableObject:

[CreateSubAssetMenu(menuName = "Test Thing")]
public class TestThing : ScriptableObject
{
    public Color color;
}

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):

[CreateSubAssetMenu(menuName = "Test Thing")]
public class TestThing : ScriptableObject
{
    public Color color;

    [SubAssetOwner]
    [SerializeField] MyContainerType m_ScriptableObjectContainer;
}

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:

[CreateSubAssetMenu(menuName = "Test Thing")]
public class TestThing : ScriptableObject
{
    public Color color;

    [SerializeField] MyContainerType m_ScriptableObjectContainer;
}

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:

[CreateSubAssetMenu(menuName = "Test Thing")]
public class TestThing : ScriptableObject, IScriptableObjectContainerProperty
{
    public Color color;

    [SerializeField] ScriptableObjectContainer m_Container;

    public ScriptableObjectContainer container // the interface implementation
    {
        get => m_Container;
        set => m_Container = value;
    }
}

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?

4 Likes

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.

1 Like

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.

1 Like

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)

3 Likes

Thank you all for the feedback! :slight_smile:

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:
6614479--753010--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.

1 Like