You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

继承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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.13 07:24:03