Unity中能否创建含可序列化ScriptableObject数组的ScriptableObject?
Great question! Let's break this down clearly:
原方案的问题(不完全可行)
Your current setup has a few critical issues that will cause headaches in the Unity Inspector and serialization:
- Redundant
[Serializable]: Marking aScriptableObjectsubclass with[System.Serializable]is unnecessary—ScriptableObjectis already handled by Unity's serialization system, so this does nothing here. - Abstract Class Serialization Limitation: Since
BehaviourSOis an abstract class, Unity's default serialization won't properly render theTransitionalBehaviourarray in the Inspector. You won't be able to select or create concrete subclass instances for the array, which defeats the purpose of having the array in the first place. - Potential Circular References: Having a
ScriptableObjectreference an array of its own type opens the door to accidental circular references (e.g., Behaviour A references Behaviour B, which references back to A). While Unity can handle this in some cases, it often leads to weird Inspector glitches or load-time issues.
Recommended Alternative Solutions
1. Use [SerializeReference] (Unity 2020.1+) – Best Modern Approach
This is the cleanest solution if you're on a recent Unity version. [SerializeReference] enables polymorphic serialization, meaning Unity can properly handle abstract class/interface references in arrays and show them correctly in the Inspector.
Here's the adjusted code:
public abstract class BehaviourSO : ScriptableObject { public string Name = "New Behaviour"; [SerializeReference] public BehaviourSO[] TransitionalBehaviour = new BehaviourSO[1]; public abstract BehaviourAI Initialize(); }
Key Notes:
- Add
[System.Serializable]to all concrete subclasses ofBehaviourSO(required forSerializeReferenceto recognize them) - In the Inspector, you'll now see a dropdown for each array element where you can select existing
BehaviourSOinstances or create new ones directly - This fully supports polymorphism, so you can mix different subclasses in the same array
2. Object Array with Validation (For Older Unity Versions)
If you're stuck on a pre-2020.1 Unity version, use an Object[] as a backing field, then add type validation to ensure only BehaviourSO instances are allowed.
using System.Collections.Generic; using UnityEngine; public abstract class BehaviourSO : ScriptableObject { public string Name = "New Behaviour"; [SerializeField, Tooltip("Only add instances of BehaviourSO subclasses!")] private Object[] _transitionalBehaviours = new Object[1]; // Type-safe public accessor public BehaviourSO[] TransitionalBehaviour { get { var validBehaviours = new List<BehaviourSO>(); foreach (var obj in _transitionalBehaviours) { if (obj is BehaviourSO behaviour) { validBehaviours.Add(behaviour); } } return validBehaviours.ToArray(); } } public abstract BehaviourAI Initialize(); // Validate in edit mode to catch invalid entries private void OnValidate() { var validObjects = new List<Object>(); foreach (var obj in _transitionalBehaviours) { if (obj is BehaviourSO) { validObjects.Add(obj); } else if (obj != null) { Debug.LogWarning($"Object {obj.name} is not a BehaviourSO subclass – removed from array", this); } } _transitionalBehaviours = validObjects.ToArray(); } }
Key Notes:
- The
OnValidatemethod ensures only validBehaviourSOinstances stay in the array, even if someone accidentally adds the wrong object - The public
TransitionalBehaviourproperty gives you type-safe access without exposing the rawObject[]
3. Serializable Wrapper Container (Alternative for Older Versions)
If you prefer a more explicit approach without SerializeReference, create a simple serializable container class to wrap each BehaviourSO reference:
using UnityEngine; [System.Serializable] public class BehaviourSOContainer { public BehaviourSO Behaviour; } public abstract class BehaviourSO : ScriptableObject { public string Name = "New Behaviour"; public BehaviourSOContainer[] TransitionalBehaviour = new BehaviourSOContainer[1]; public abstract BehaviourAI Initialize(); }
Key Notes:
- Unity will render the array correctly in the Inspector, showing a foldout for each container where you can select a
BehaviourSOinstance - The downside is you'll need to access
container.Behaviourto get the actual reference, which adds a tiny bit of boilerplate code
内容的提问来源于stack exchange,提问作者Branden

