Adds a field to any scriptable object tagged with the [SOVariant]
attribute that lets you select an original SO (parent) and override selected fields in the child object.
When changing values in the original, values are automagically propagated to the children.
Add the tag [SOVariant]
before the class header of any ScriptableObject class you want to be overridable, i.e. to be able to create a variant of.
Example:
using Giezi.Tools;
[SOVariant]
[CreateAssetMenu(fileName = "TestScriptable", menuName = "Create new TestScriptable")]
public class TestScriptable : ScriptableObject
{
[SerializeField] private float myFloat = 3L;
[SerializeField] private GameObject myGameObject;
[SerializeField] private int myInt;
[SerializeField] private TestScriptable myTestScriptable;
}
In Unity, you can right click any scriptable object tagged SOVariant
to create a variant of this object (Create > Create SO Variant
).
The new object will have the selected object as parent.
A helper script has been implemented (SOVariantHelper.cs
) which allows you to changed parents, override states and values from within other editor scripts.
Set a new parent:
ScriptableObject target = AssetDatabase.LoadAssetAtPath<ScriptableObject>("Assets/Tests/child.asset");
ScriptableObject parent = AssetDatabase.LoadAssetAtPath<ScriptableObject>("Assets/Tests/parent.asset");
SOVariantHelper<ScriptableObject>.SetParent(target, parent);
Set a field overridable:
ScriptableObject target = AssetDatabase.LoadAssetAtPath<ScriptableObject>("Assets/Tests/child.asset");
SOVariantHelper<ScriptableObject>.ChangeFieldOverrideState(target, "MyFloat", true);
Set a new value of a field (automatically propagates to children):
ScriptableObject target = AssetDatabase.LoadAssetAtPath<ScriptableObject>("Assets/Tests/child.asset");
SOVariantHelper<ScriptableObject>.ChangeFieldValue(target, "MyFloat", 45f);
Set a filed to be overridden and set new value (automatically propagates to children):
ScriptableObject target = AssetDatabase.LoadAssetAtPath<ScriptableObject>("Assets/Tests/child.asset");
SOVariantHelper<ScriptableObject>.SetFieldOverrideAndSetValue(target, "MyFloat", 45f);
Set a parent and set new overridden value (automatically propagates to children):
ScriptableObject target = AssetDatabase.LoadAssetAtPath<ScriptableObject>("Assets/Tests/child.asset");
ScriptableObject parent = AssetDatabase.LoadAssetAtPath<ScriptableObject>("Assets/Tests/parent.asset");
SOVariantHelper<ScriptableObject>.SetParentOverrideValue(target, parent, "MyFloat", 45f);
Set a parent and set new overridden values (automatically propagates to children):
ScriptableObject target = AssetDatabase.LoadAssetAtPath<ScriptableObject>("Assets/Tests/child.asset");
ScriptableObject parent = AssetDatabase.LoadAssetAtPath<ScriptableObject>("Assets/Tests/parent.asset");
SOVariantHelper<ScriptableObject>.SetParentOverrideValues(target, parent, new Dictionary<string, object>(){{"MyFloat", 45f},{"MyInt", 12}});
The visual interface is implemented in Odin's OdinPropertyProcessor
.
The data with the parent and the overriden fields is kept serialized inside the asset's metadata, set in unity with AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(targetObject)).userData
.
Requires Odin to be installed before adding the package
Add the line
"ch.giezi.tools.scriptableobjectvariant": "https://github.com/GieziJo/ScriptableObjectVariant.git#master"
to the file Packages/manifest.json
under dependencies
, or in the Package Manager
add the link https://github.com/GieziJo/ScriptableObjectVariant.git#master
under + -> "Add package from git URL...
.
The package is available on OpenUPM. OpenUPM packages can be installed in different ways:
- via OpenUPM CLI:
openupm add ch.giezi.tools.scriptableobjectvariant
- by downloading the
.unitypackage
and adding it to your project withAssets > Import Package > Custom Package...
.
the package will be added as a scoped registry, which you can inspect under Project Settings > Package Manager > OpenUPM
.
Download and copy all files inside your project.
List of known issues
The attribute [SOVariant]
only acts as tagger, which is then looked for in SOVariantAttributeProcessor:OdinPropertyProcessor -> ProcessMemberProperties
, where the first line reads:
if(!Property.Attributes.Select(attribute => attribute.GetType()).Contains(typeof(SOVariantAttribute)))
return;
The problem with this is that SOVariantAttributeProcessor
is thus set to be called for every ScriptableObject
:
public class SOVariantAttributeProcessor<T> : OdinPropertyProcessor<T> where T : ScriptableObject
There is probably a way to directly call SOVariantAttributeProcessor
from the attribute, but I haven't found how.
The selected parent should be of the exact same class as the overriden item (otherwise fields might be missing) and should not be the child itself. This check is currently done when setting the parent as:
if (parent.GetType() != target.GetType())
{
Debug.Log("Only equal types can be selected as parent");
return;
}
if (AssetDatabase.GetAssetPath(parent) == AssetDatabase.GetAssetPath(target))
{
Debug.Log("You can't select the same object as parent");
return;
}
It would be alot better to directly filter the possible candidates when selecting in the object, but adding the AssetSelector
attribute with a filter, or building a custom ValueDropdown
both did not work, not sure why.