今天我们聊聊序列化。
文章目录
可序列化对象
序列化对象包括2种:
MonoBehaviour
我们可以从该类派生我们的游戏逻辑组件,并将该类添加到GameObject实现自己的游戏逻辑,同时我们在该类上定义的用来驱动逻辑的数据,会被序列化到文件中。如果GameObject是在场景中,则数据会被序列化到场景文件中,XXX.unity,如果GameObject是个预制,则数据被序列化到预制文件XXX.prefab中。
ScriptableObject
从该类派生的对象,序列化的过程跟MonoBehaviour是一样的,不同的是,可以直接创建改类对象,作为资源数据文件,而不需要作为脚本挂到GameObject上,其数据直接存储在该类的资源文件内。
文件存储方式
Unity提供了2种文件存储方式
- Binary : 二进制方式,文件小,保存,加载速度快,但是不可读
- Text:文本方式,文件相对大,保存加载速度慢,但是可读,便于进行版本管理,例如解决冲突,建议开发过程选择该方式。
以及3种选项:
- Force Binary:强制将现有资源改为二进制格式
- Force Text:强制将现有资源改为文本格式
- Mixed:之前是什么格式,依然保留,但是对于新建资源,采用二进制格式
序列化属性定义
Unity提供了多种方式来定义数据是否被序列化
- public 数据成员,默认序列化,并在Inspector面板可编辑
- [HideInInspector] 修饰 pubic 数据成员,可序列化,但不可在Inspector面板编辑
- [System.NonSerializable] 修饰数据成员,不可被序列化
- private 数据成员,默认不序列化,不在Inspector 面板显示
- [SerializeField] 定义数据成员可以被序列化
- [System.Serializable] 修饰class/struct可以被序列化
public 并且为被[HideInInspector]修饰的数据成员,会在Inspector面板种显示,并且可以被编辑。如果想要在Inspector显示所有可以被编辑的数据成员,可以在Inspector顶部右侧菜单中,选择Debug。
SerializedObject 以及 SerializedProperty
如果我们觉得数据成员在Inspector面板的编辑方式不适合我们,可以利用SerializedObject来进行自定义:
更新数据:serializedObject.Update();
获取属性:SerializedProperty prop = serializedObject.FindProperty(“propertyName”);
显示并编辑:prop.intValue = EditorGUILayout.IntField(“name”, prop.intValue);
或者用系统绘制:EditorGUILayout.PropertyField(prop, true);
保存数据:serializedObject.ApplyModifiedProperties();
监听数据更新
我们可能需要监听属性变化,执行一些特定的逻辑。Unity提供了2种方式:
- 在OnInspectorGUI中,调用 EditorGUI.BeginChangeCheck(),并在编辑后,调用if(EditorGUI.EndChangeCheck()){…}
- 实现 MonoBehaviour.OnValidate()方法,当序列化的属性发生变化时,会调用。
监听序列化事件
我们可以在序列化数据前,和反序列化数据后,执行我们的逻辑,对数据进行处理,可以定义 MonoBehaiour 派生类实现 ISerializationCallbackReceiver接口,实现其 OnBeforeSerialize() 和 OnAfterDeserialize() 接口。
编辑属性
Unity 提供了属性[Range(min,max)]来修饰数据的取值范围,但改范围时浮点数,我们可以实现自己的类型,这时需要实现:
PropertyAttribute
PropertyDrawer
ScriptableObject
如果我们想利用Inspector的数据编辑和序列化功能,但是又不想派生MonoBehaviour并挂到GameObject,再创建预制的方式,则可以派生ScriptableObject,并为派生类加上[CreateAssetMenu],则可以在Project窗口,直接右键,创建我们的ScriptableObject类型的资源,然后在运行中,MyScriptableObject mso = Resources.Load(“filename”);,就可以访问其数据了。
序列化声明,及Inspector自定义例子
using UnityEngine;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
/// <summary>
/// 派生 ISerializationCallbackReceiver 监听序列化事件
/// </summary>
public class MySerialize : MonoBehaviour, ISerializationCallbackReceiver
{
[System.NonSerialized]
public int intNoSerialize;
[Header("系统绘制")]
public bool defGUI;
[HideInInspector]
public int hideInt;
public int publicInt;
[SerializeField]
private int privateInt;
[System.Serializable]
public class Data
{
public int mInt;
public float mFloat;
}
[SerializeField]
Data data;
public Object prefab;
private void OnValidate()
{
Debug.Log("数据更新!!!");
}
[SerializeField]
List<int> intValues = new List<int>();
[SerializeField]
List<string> strValues = new List<string>();
public Dictionary<int, string> dic = new Dictionary<int, string>();
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
intValues.Clear();
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(MySerialize))]
public class MySerializeInspector : Editor
{
public override void OnInspectorGUI()
{
// 更新数据
serializedObject.Update();
//EditorGUI.BeginChangeCheck();
MySerialize sc = target as MySerialize;
if (sc.defGUI)
{
base.OnInspectorGUI();
}
else
{
sc.defGUI = EditorGUILayout.Toggle("系统绘制", sc.defGUI);
// 自己绘制
SerializedProperty sp = serializedObject.FindProperty("publicInt");
sp.intValue = EditorGUILayout.IntField("整数", sp.intValue);
// 使用默认样式绘制
sp = serializedObject.FindProperty("data");
EditorGUILayout.PropertyField(sp, true);
// 保存数据
serializedObject.ApplyModifiedProperties();
}
//if(EditorGUI.EndChangeCheck())
//{
// Debug.Log("数据更新22222");
//}
if(GUI.changed)
{
Debug.Log("数据更新33333");
}
}
}
#endif
例子 序列化事件监听
using UnityEngine;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
/// <summary>
/// 派生 ISerializationCallbackReceiver 监听序列化事件
/// </summary>
public class SerializeEvent : MonoBehaviour, ISerializationCallbackReceiver
{
[SerializeField]
List<int> intValues = new List<int>();
[SerializeField]
List<string> strValues = new List<string>();
public Dictionary<int, string> dic = new Dictionary<int, string>();
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
intValues.Clear();
strValues.Clear();
foreach(KeyValuePair<int,string> kv in dic)
{
intValues.Add(kv.Key);
strValues.Add(kv.Value);
}
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
dic.Clear();
for(int i = 0; i < intValues.Count; ++i)
{
dic.Add(intValues[i], strValues[i]);
}
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(SerializeEvent))]
public class SerializeEventInspector : Editor
{
public override void OnInspectorGUI()
{
// 更新数据
serializedObject.Update();
SerializedProperty spKey = serializedObject.FindProperty("intValues");
SerializedProperty spValue = serializedObject.FindProperty("strValues");
int size = spKey.arraySize;
EditorGUILayout.BeginVertical();
for(int i = 0; i < size; ++i)
{
EditorGUILayout.BeginHorizontal();
SerializedProperty k = spKey.GetArrayElementAtIndex(i);
k.intValue = EditorGUILayout.IntField("key", k.intValue);
SerializedProperty v = spValue.GetArrayElementAtIndex(i);
v.stringValue = EditorGUILayout.TextField("value", v.stringValue);
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
EditorGUILayout.BeginHorizontal();
if(GUILayout.Button("+"))
{
SerializeEvent sc = target as SerializeEvent;
sc.dic.Add(size, "");
}
EditorGUILayout.EndHorizontal();
serializedObject.ApplyModifiedProperties();
serializedObject.ApplyModifiedProperties();
}
}
#endif
例子 ScriptableObject
using UnityEngine;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
[CreateAssetMenu]
public class CustomScriptableObject : ScriptableObject
{
public List<PlayerInfo> playInfos = new List<PlayerInfo>();
[System.Serializable]
public class PlayerInfo
{
public int id;
public int hp;
}
}
#if UNITY_EDITOR
public class CreateObjects
{
[MenuItem("Assets/Create/ 2 ScriptableObject")]
static void Create()
{
// 创建资源
CustomScriptableObject sc = ScriptableObject.CreateInstance<CustomScriptableObject>();
sc.playInfos.Add(new CustomScriptableObject.PlayerInfo() { id = 1001, hp = 100});
// 保存资源
AssetDatabase.CreateAsset(sc, "Assets/Resources/manualCreateScriptable.asset");
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
}
#endif
public class MyScriptableObject : MonoBehaviour
{
public CustomScriptableObject myObj;
private void Start()
{
myObj = Resources.Load<CustomScriptableObject>("myScriptObject");
if(myObj.playInfos.Count > 0)
Debug.Log($"player id:{myObj.playInfos[0].id} hp:{myObj.playInfos[0].hp}");
}
}
例子 自定义属性标签 RangeInt
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class RangeIntAttributeExample : MonoBehaviour
{
[RangeInt(1,100)]
public int myIntValue;
}
public sealed class RangeInt : PropertyAttribute
{
public readonly int min;
public readonly int max;
public RangeInt(int _min, int _max)
{
min = _min;
max = _max;
}
}
#if UNITY_EDITOR
public class RangeIntDrawer : PropertyDrawer
{
// 面板高度
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return 100;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
RangeInt ri = attribute as RangeInt;
EditorGUI.HelpBox(new Rect(position.x, position.y, position.width, 30), $"范围{ri.min}-{ri.max}", MessageType.Info);
EditorGUI.PropertyField(new Rect(position.x, position.y + 35, position.width, 20.0f), property, label);
property.intValue = Mathf.Clamp(property.intValue, ri.min, ri.max);
}
}
#endif