Hi everyone!
I’m currently working on implementing the ScriptableObject Variable framework from, of course, the famous Ryan’s talk about scriptable objects.
In my version, I want to support both concrete and polymorphic typing as follow:
public abstract class ScriptableVariable<T> : ScriptableObject
{
[field: SerializeField, SerializeReference, SerializeReferenceSelector] public virtual T Value { get; set; }
}
[System.Serializable]
public class ScriptableReference<T>
{
[SerializeField] private ScriptableReferenceMode _mode;
[SerializeField, SerializeReference, SerializeReferenceSelector] private T _constantValue;
[SerializeField] private ScriptableVariable<T> _scriptableValue;
public T Value => _mode == ScriptableReferenceMode.Constant ? _constantValue : _scriptableValue.Value;
}
// Example usage for SerializeField
public class VInt : ScriptableVariable<int> { }
// Example usage for SerializeReference
public class EffectConfig : ScriptableVariable<IEffect> { }
[System.Serializable]
public abstract class ConsumeEffect : IEffect { }
public interface IEffect
{
void Perform(GameObject target);
}
// Example MonoBehaviour
public class UseEffect : MonoBehaviour
{
public ScriptableReference<IEffect> Effect;
public ScriptableReference<int> Number;
}
As you can see, I use both SerializeField
and SerializeReference
for Value
and _constantValue
. In the editor, it seems to work just fine and Unity is able to serialize it in the correct way (Of course all of these require to write custom editor code to make things even show up in the inspector). But since no documentation has ever mentioned about using both attributes together, I wonder if this could lead to some bad unknown side effects?
I mean open the asset in a text editor and look at the serialized data. Is there data for both types of serialization?
I imagine for types that [SerializeField]
can’t serialize, but [SerializeReference]
can, or vice-versa, it might use only one or the other, and should work fine.
But when using a type that can technically be serialized both attributes, that’s where issues might arise. Either where its not supported or you end up with race conditions, or one is always applied after the other.
So I would test this with say: a non-abstract, non-sealed plain C# class which has derived types.
1 Like
First, from what I observed from the serialized data, only one type of data will be present depending on which attribute it ends up using.
Then, I tested in your way and found some interesting results.
- For Unity’s default supported serializable types such as int, string, Vector2, they are serialized using
[SerializeField]
.
- For concrete plain C# types either with derived types or not, they are serialized using
[SerializeReference]
.
- For custom struct types, an error message is shown:
The field ScriptableReference<CustomStruct>._constantValue has the [SerializeReference] attribute applied, but is of type CustomStruct, which is a value type. By definition, value types cannot be serialized by reference.
So, [SerializeField]
with Unity’s default supported serializable types will always take precedence over [SerializeReference]
. For [SerializeField]
with custom struct types, they will result in an error.
For reference:
public class Test : MonoBehaviour
{
// Case 1
public ScriptableReference<string> Str;
public ScriptableReference<Vector2> Position;
public ScriptableReference<GameObject> GameObject;
// Case 2
public ScriptableReference<Item> Item;
public ScriptableReference<ItemA> ItemA;
public ScriptableReference<ItemB> ItemB;
// Case 3
public ScriptableReference<CustomStruct> CustomStruct;
}
[System.Serializable]
public class Item { public int X; }
[System.Serializable]
public class ItemA : Item { }
[System.Serializable]
public class ItemB { }
[System.Serializable]
public struct CustomStruct { public int Y; }
Not quite the result I expected, but still pretty interesting.
Having this error for custom serializable structs does seem pretty limiting, however.