- A+
Unity 默认可以序列化值类型, Serializable属性修饰的类型, 派生自UnityEngine.Object的类型, 通常这些类型已经足以供日常使用了.
但是有时我们希望在编辑器面板上序列化一个接口或者抽象类, 则需要用到 SerializeReference属性.
假定我们有一个接口IEatable
, 并实现了两个类Bread
和Bun
:
public interface IEatable { int Calorie { get; } } public class Bread : IEatable { [field: SerializeField] public int Calorie { get; private set; } = 100; } public class Bun : IEatable { public enum Fillings { [InspectorName("肉馅")] Meat, [InspectorName("韭菜鸡蛋")] LeekAndAgg, } [field: SerializeField] public int Calorie { get; private set; } = 200; [field: SerializeField] public Fillings Filling { get; private set; } }
相应的, 我们定义了一个餐盘类去盛放食物Plate
:
[CreateAssetMenu(menuName = "Plate for Food")] public class Plate : ScriptableObject { private IEatable _eatable; public IEatable Eatable { get => _eatable; set => _eatable = value; } }
在Unity编辑器内右键新建一个Plate
, 可见Inspector面板上没有显示Eatable
字段.
此时我们为_eatable
字段添加SerializeReference
属性.
注意, 添加
SerializeReference
后, 即使字段是私有的, 也无需添加SerializeField
属性, 二者同有将私有字段序列化的能力.
[CreateAssetMenu(menuName = "Plate for Food")] public class Plate : ScriptableObject { [SerializeReference] private IEatable _eatable; public IEatable Eatable { get => _eatable; set => _eatable = value; } }
添加SerializeReference
属性后, Inspector面板上已经可以显示Eatable
字段了, 但是由于此时_eatable
字段的值为null
, 所以并没有显示其他信息.
SerializeReference
属性允许字段为null
, 这点与默认序列化行为不同, 默认序列化会自动实例化一个值
接下来我们在Plate
中定义一个方法ServeBread
, 将_eatable
字段设置为Bread
实例, 并使用ContextMenuItem
属性将此方法设置为_eatable
字段的上下文菜单:
[ContextMenuItem("盛放面包", "ServeBread", order = 0)] [ContextMenuItem("盛放包子", "ServeBun", order = 1)] [SerializeReference] private IEatable _eatable; ... private void ServeBread() => _eatable = new Bread(); private void ServeBun() => _eatable = new Bun(); ...
回到Unity编辑器, 此时我们就可以右键点击Eatable
字段并在弹出菜单中选择一项来为_eatable
字段赋值了.
添加
[field: SerializeField]
后, 属性也可以像字段一样被序列化, 但是其label
会显示为<属性名>k__BackingField
, 如果不希望这种现象,可以将属性转化为完整属性并为对应的私有字段添加SerializeField
.
用文本编辑器打开Plate.asset
文件, 可以看到使用SerializeReference
属性进行序列化后的内容, 可以对比一下普通的序列化方式.
其中, type
记录了字段内容的具体类型class
, 所在命名空间ns
, 所在的程序集asm
. 而data
则记录了实例的可序列化字段及内容.
ContextMenuItem
方式只是为了演示, 合理的做法应该是自行实现对应的PropertyDrawer
.
SerializeReference
还可以修饰List<T>
和T[]
, 具体情况可以查看Unity官方文档, 这里就不赘述了.