继承MonoBehaviour时序列化失效,如何同时满足序列化与游戏循环更新需求
解决方案
首先明确问题根源:Unity 序列化规则不支持将继承自 MonoBehaviour 的类作为普通序列化值类型内嵌编辑,MonoBehaviour 只能作为挂载在 GameObject 上的组件引用存在,这就是继承后序列化失效的原因。
推荐方案:职责分离实现
将数据编辑与运行时更新逻辑拆分,分别由两个类实现:
- 纯可序列化数据类:不继承任何Unity基类,仅用来存储可编辑属性,支持自定义PropertyDrawer绘制
- 运行时逻辑类:继承
MonoBehaviour,持有数据类实例,在游戏生命周期方法中实现更新逻辑
实现代码
1. 可序列化数据类
[Serializable] public class MySmallTestPropData { [SerializeField] internal string _name; [SerializeField] internal float _someFloat; public string Name { get => _name; set => _name = value; } public float SomeFloat { get => _someFloat; set => _someFloat = value; } }
2. 运行时逻辑类
public class MySmallTestPropRunner : MonoBehaviour { // 字段直接初始化,自动实现空引用默认实例化 public MySmallTestPropData propData = new MySmallTestPropData(); private void Update() { // 此处编写游戏循环更新逻辑,直接操作propData中的属性即可 } }
3. 自定义PropertyDrawer
[CustomPropertyDrawer(typeof(MySmallTestPropData))] public class MySmallTestPropPropertyDrawer : PropertyDrawer { private float rowHeight; private const int rowSpacing = 5; private int index; private Rect currentPosition; public override float GetPropertyHeight(SerializedProperty prop, GUIContent label) { rowHeight = base.GetPropertyHeight(prop, label); // 行数可根据实际需要展示的属性数量调整 const int rows = 3; return rowHeight * rows + rowSpacing * (rows - 1); } public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { index = 0; currentPosition = position; EditorGUI.BeginProperty(position, label, property); EditorGUI.LabelField(NextPosition(), label); EditorGUI.indentLevel++; var nameProp = property.FindPropertyRelative("_name"); EditorGUI.PropertyField(NextPosition(), nameProp, new GUIContent("名称")); var floatProp = property.FindPropertyRelative("_someFloat"); EditorGUI.PropertyField(NextPosition(), floatProp, new GUIContent("浮点值")); EditorGUI.indentLevel--; EditorGUI.EndProperty(); } private Rect NextPosition() { var res = currentPosition; res.height = rowHeight; res.y += (rowHeight + rowSpacing) * index++; return res; } }
空引用自动实例化补充
如果需要在任意持有MySmallTestPropData的脚本中自动实例化空引用,除了字段直接初始化外,还可以通过实现ISerializationCallbackReceiver接口兜底:
public class AnyHostScript : MonoBehaviour, ISerializationCallbackReceiver { public MySmallTestPropData propData; public void OnBeforeSerialize() { } public void OnAfterDeserialize() { // 序列化完成后自动补全空实例 propData ??= new MySmallTestPropData(); } }
替代方案(不推荐)
如果不想拆分类,也可以将MySmallTestProp作为独立组件挂载,在PropertyDrawer中增加自动挂载逻辑:给类添加[DisallowMultipleComponent]特性,检测到字段为空时自动给当前游戏对象添加该组件并赋值到字段。该方案耦合度较高,且会增加场景中的组件数量,仅适合小型项目使用。
内容的提问来源于stack exchange,提问作者Daarwin
相关产品推荐
相关产品推荐

