自定义序列化字段进入Unity游戏模式后值丢失,求解决方案
实现了自定义序列化字段,能在Unity Inspector面板显示并设置值,但切换到游戏模式后字段值会被重置。不想让模块继承MonoBehaviour,也不想为了初始化字段重载无关的函数与属性。
相关代码:
using System; using UnityEditor; using UnityEngine; using Helpers.Utils; using System.Reflection; using System.Collections.Generic; namespace GameEnviroment.Assembling { public class GAssembly : MonoBehaviour { private readonly static List<FieldInfo> GSerializedModulesFields = new(); public static LabModule LabModule { get; private set; } = new LabModule(); public static EvalModule EvalModule { get; private set; } = new EvalModule(); public static AdsModule AdsModule { get; private set; } = new AdsModule(); private readonly static List<GModule> Modules = new() { LabModule, EvalModule, AdsModule }; private void Awake() { DontDestroyOnLoad(this); } #if UNITY_EDITOR [CustomEditor(typeof(GAssembly))] internal sealed class GAssemblyInspector : Editor { public override void OnInspectorGUI() { DrawDefaultInspector(); serializedObject.Update(); if (Modules == null) return; for (int i = 0; i < GSerializedModulesFields.Count; i++) { if (i == 0 || GSerializedModulesFields[i].DeclaringType.Name != GSerializedModulesFields[i - 1].DeclaringType.Name) { GUILayout.Space(10f); GUILayout.Label(GUtility.HandleFieldName(GSerializedModulesFields[i].DeclaringType.Name, '-', false, '_'), EditorStyles.boldLabel); GUILayout.Space(5f); } if (GUtility.GetFinalBaseType(GSerializedModulesFields[i].FieldType) == typeof(UnityEngine.Object)) { GModule module = Modules.Find(m => m.GetType() == GSerializedModulesFields[i].DeclaringType); GSerializedModulesFields[i].SetValue(module, EditorGUILayout.ObjectField( label: GUtility.HandleFieldName(GSerializedModulesFields[i].Name, exclude: '_'), obj: GSerializedModulesFields[i].GetValue(module) as UnityEngine.Object, objType: GSerializedModulesFields[i].FieldType, allowSceneObjects: true)); } else if (GSerializedModulesFields[i].FieldType == typeof(float)) { GModule module = Modules.Find(m => m.GetType() == GSerializedModulesFields[i].DeclaringType); GSerializedModulesFields[i].SetValue(module, EditorGUILayout.FloatField( label: GUtility.HandleFieldName(GSerializedModulesFields[i].Name, exclude: '_'), value: Convert.ToSingle(GSerializedModulesFields[i].GetValue(module)))); } // else if (GSerializedModulesFields[i].FieldType == typeof(string)) // { // ... // } // // else if .... } serializedObject.ApplyModifiedProperties(); } } #endif [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = true)] internal sealed class SerializeFieldAttribute : Attribute { public SerializeFieldAttribute() { } public static void InitializeFields(Type targetObject) { IEnumerable<FieldInfo> fields = GUtility.GetFieldsWithAttribute<SerializeFieldAttribute>(targetObject); foreach (FieldInfo field in fields) { GSerializedModulesFields.Add(field); } } } } }
添加了GAssembly.SerializeField属性的字段能正常显示和设置,但进入游戏模式后值全部丢失。推测是游戏启动时Unity会序列化字段,而我的字段未被序列化保存。尝试过给模块类添加Serializable属性、给字段加UnityEngine.SerializeField属性,但均无效。请问需要自行实现序列化系统,还是可以利用Unity自带的序列化系统解决该问题?
不用自己实现序列化系统,完全可以靠Unity自带的序列化机制解决,核心问题出在静态字段不被Unity序列化,你的自定义Inspector只是在编辑时修改了内存中的值,没有把数据持久化到GAssembly的序列化数据里。
具体修改步骤:
将静态模块实例改为可序列化的非静态字段
把原来的静态属性改成带[SerializeField]的非私有字段(或私有字段加[SerializeField]),同时保留静态属性供外部访问,还要给每个模块类加上[Serializable]特性:[SerializeField] private LabModule _labModule = new LabModule(); [SerializeField] private EvalModule _evalModule = new EvalModule(); [SerializeField] private AdsModule _adsModule = new AdsModule(); // 保留静态访问入口 public static LabModule LabModule => Instance._labModule; public static EvalModule EvalModule => Instance._evalModule; public static AdsModule AdsModule => Instance._adsModule; private static GAssembly Instance { get; set; } private void Awake() { Instance = this; DontDestroyOnLoad(this); }模块类示例:
[Serializable] public class LabModule : GModule { // 用Unity自带的[SerializeField]标记需要序列化的字段 [SerializeField] private float _testFloat; [SerializeField] private GameObject _testPrefab; // ...其他业务逻辑 }简化自定义Inspector逻辑
现在模块已经是Unity序列化的字段,直接用EditorGUILayout.PropertyField就能自动渲染子字段,还能自动处理序列化持久化:#if UNITY_EDITOR [CustomEditor(typeof(GAssembly))] internal sealed class GAssemblyInspector : Editor { public override void OnInspectorGUI() { serializedObject.Update(); // 获取模块的序列化属性 var labModuleProp = serializedObject.FindProperty("_labModule"); var evalModuleProp = serializedObject.FindProperty("_evalModule"); var adsModuleProp = serializedObject.FindProperty("_adsModule"); // 分模块显示 DrawModuleSection("Lab Module", labModuleProp); DrawModuleSection("Eval Module", evalModuleProp); DrawModuleSection("Ads Module", adsModuleProp); serializedObject.ApplyModifiedProperties(); } private void DrawModuleSection(string title, SerializedProperty moduleProp) { GUILayout.Space(10f); GUILayout.Label(title, EditorStyles.boldLabel); GUILayout.Space(5f); EditorGUI.indentLevel++; // true表示自动展开所有子字段 EditorGUILayout.PropertyField(moduleProp, true); EditorGUI.indentLevel--; } } #endif移除自定义的序列化逻辑
删掉自定义的SerializeFieldAttribute和GSerializedModulesFields相关的反射代码,直接用Unity自带的[SerializeField]标记需要序列化的字段即可。
这样修改后,Inspector中设置的值会被Unity自动序列化保存,进入游戏模式后不会重置,同时模块不需要继承MonoBehaviour,完全符合你的需求。
内容的提问来源于stack exchange,提问作者Kyzlyk

