Skip to content

Adds the "SOVariant" attribute to a ScriptableObject and override, or not, certain fields (similar to prefab variants but for scriptable objects).

License

Notifications You must be signed in to change notification settings

GieziJo/ScriptableObjectVariant

Repository files navigation

Releases openupm License: MIT

Scriptable Object Variant for Unity (Scriptable Object Data Overrider)

Caution

If you are upgrading from a pre-2.0.0 version, please read the upgrade section

Table of contents

Description

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.

Usage

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;
}

Create Scriptable Object Variant from context menu

Context Menu

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.

Advanced usage in Editor Script

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}});

Implementation

The visual interface is implemented in Odin's OdinPropertyProcessor.

The data with the parent and the overriden fields is kept in a library object located at Assets/Plugins/SOVariant/Editor/SOVariantDataLibrary.asset

Before 2.0.0: 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`.

Installation

Warning

Requires Odin version 3.2.1.0 to be installed before adding the package.

Warning

This package is maintained for Unity version 2022.3.16f1 LTS.

Using Unity's package manager

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....

Using OpenUPM

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 with Assets > Import Package > Custom Package....

the package will be added as a scoped registry, which you can inspect under Project Settings > Package Manager > OpenUPM.

Alternative

Download and copy all files inside your project.

Upgrading to 2.0.0 from previous versions

In version 2.0.0 of the package, the data moved from within each object's metadata to using a Scriptable Object library. Unity's importer being too unstable, reading and writing the data to the metadata became too complicated and often led to errors and failures.

If you were using the package pre-2.0.0, a tool has been written to migrate to the new version. Run Tools/GieziTools/SOVariant/Upgrade user data to new version. This will read all data contained in the metadata and build the library. If necessary, you can run Tools/GieziTools/SOVariant/Fix SOVs. This will loop through each object and rebuild the parent/child relationship, as well as propagate the parent values to the children. Because the previous version of the tool was quite unstable, this step is highly encouraged Additionally, you should check the data to make sure each object has the right values.

This new version should fix a lot of the issues that appeared with newer versions of Unity and with the package being unstable.

Known issues and tweaks to be made

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.

About

Adds the "SOVariant" attribute to a ScriptableObject and override, or not, certain fields (similar to prefab variants but for scriptable objects).

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages