脚本 3 序列化

今天我们聊聊序列化。

可序列化对象

序列化对象包括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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值