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?