懒人的UI变量序列化

  今天翻到一个以前写的序列化代码, 感觉有点惊艳啊, 懒人的典范, 必须记录一下才行.

  事情是这样的, 比如有一个模拟, 每次打开都要往面板里面设置数据才能开始, 本来要求是每次都填的, 然后直接逻辑就读取了UI变量了, 然后要改成能够保存的方式, 于是有了这个脚本...

比如一个界面如图 : 

  

  怎样把界面序列化, 很简单, 把UI元素的 Toggle, Text, DropDown 这些的变量找出来就行了...

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
using System.Linq;

namespace Tools
{
    /// <summary>
    /// 运行时 UI 序列化工具, 通过节点相对路径获取UI数值, 保存Json
    /// 运行时 UI 反序列化工具, 通过相对路径对UI赋值
    /// </summary>
    public class CustomUISerializer
    {
        public class SerializeData
        {
            public WrappedSerializeData<Text, string> Text = new WrappedSerializeData<Text, string>();
            public WrappedSerializeData<InputField, string> InputField = new WrappedSerializeData<InputField, string>();

            public WrappedSerializeData<Toggle, bool> Toggles = new WrappedSerializeData<Toggle, bool>();
            public WrappedSerializeData<SwitchButton, bool> SwitchButton = new WrappedSerializeData<SwitchButton, bool>();
            public WrappedSerializeData<Slider, float> Slider = new WrappedSerializeData<Slider, float>();
            public WrappedSerializeData<Dropdown, int> Dropdown = new WrappedSerializeData<Dropdown, int>();
        }

        public class WrappedSerializeData<TComp, TVal> where TComp : UnityEngine.Component where TVal : IConvertible
        {
            public Dictionary<string, List<TVal>> Datas = new Dictionary<string, List<TVal>>();

            #region Main Funcs
            /// <summary>
            /// Serialize UI in runtime to json
            /// </summary>
            /// <param name="root"></param>
            /// <param name="serializeFunc"></param>
            public void SerializeUI(Transform root, System.Func<TComp, TVal> serializeFunc)
            {
                Datas.Clear();
                var collections = CustomUISerializer.GetComponentCollections<TComp>(root);
                WrappedGetComponentCollections(collections, serializeFunc);
            }
            /// <summary>
            /// Deserialize UI runtime from json
            /// </summary>
            /// <param name="root"></param>
            /// <param name="applyFunc"></param>
            public void DeserializeUI(Transform root, System.Action<TComp, TVal> applyFunc)
            {
                if(applyFunc != null)
                {
                    CustomUISerializer.ApplyToComponentCollections<TComp, TVal>(root, Datas, applyFunc);
                }
            }
            #endregion

            #region Help Funcs
            private void WrappedGetComponentCollections(Dictionary<string, List<TComp>> collections, System.Func<TComp, TVal> serializeFunc)
            {
                foreach(var collection in collections)
                {
                    Datas[collection.Key] = collection.Value.Select(_item => serializeFunc.Invoke(_item)).ToList();
                }
            }
            #endregion
        }

        private static System.Text.StringBuilder _sb = new System.Text.StringBuilder();

        /// <summary>
        /// Serialize UI Data to Json
        /// </summary>
        /// <param name="root"></param>
        /// <returns></returns>
        public static string SerializeUIToJson<T>(Transform root) where T : SerializeData, new()
        {
            T data = SerializeUIToData<T>(root);
            var json = LitJson.JsonMapper.ToJson(data);
            return json;
        }

        /// <summary>
        /// Serialize UI Data to intermediate data
        /// </summary>
        /// <param name="root"></param>
        /// <returns></returns>
        public static T SerializeUIToData<T>(Transform root) where T : SerializeData, new()
        {
            T data = new T();

            // Text
            data.Text.SerializeUI(root, (_comp) => { return _comp.text; });
            // InputField
            data.InputField.SerializeUI(root, (_comp) => { return _comp.text; });

            // Toggle
            data.Toggles.SerializeUI(root, (_comp) => { return _comp.isOn; });
            // SwitchButton
            data.SwitchButton.SerializeUI(root, (_comp) => { return _comp.isOn; });
            // Slider
            data.Slider.SerializeUI(root, (_comp) => { return _comp.value; });
            // Dropdown
            data.Dropdown.SerializeUI(root, (_comp) => { return _comp.value; });return data;
        }

        /// <summary>
        /// DeSerialize UI data from SerializeData
        /// </summary>
        /// <param name="root"></param>
        /// <param name="data"></param>
        public static void ApplySerializedDataToUI<T>(Transform root, T data) where T : SerializeData, new()
        {
            // Text
            data.Text.DeserializeUI(root, (_comp, _str) => { _comp.text = _str; });
            // InputField
            data.InputField.DeserializeUI(root, (_comp, _str) => { _comp.text = _str; });

            // Toggle
            data.Toggles.DeserializeUI(root, (_comp, _bool) => { _comp.isOn = _bool; });
            // SwitchButton
            data.SwitchButton.DeserializeUI(root, (_comp, _bool) => { _comp.isOn = _bool; });
            // Slider
            data.Slider.DeserializeUI(root, (_comp, _float) => { _comp.value = _float; });
            // Dropdown
            data.Dropdown.DeserializeUI(root, (_comp, _int) => { _comp.value = _int; });
        }
        /// <summary>
        /// DeSerialize UI data from Json
        /// </summary>
        /// <param name="root"></param>
        /// <param name="json"></param>
        public static void ApplySerializedDataToUI(string json, Transform root)
        {
            ApplySerializedDataToUI(root, LitJson.JsonMapper.ToObject<SerializeData>(json));
        }

        #region Help Funcs
        // wrapped get component data to serialized value
        public static Dictionary<string, List<TVal>> GetComponentCollections<T, TVal>(Transform root, System.Func<T, TVal> serializeFunc) where T : UnityEngine.Component
        {
            var collections = GetComponentCollections<T>(root);
            var tagCollectoins = GetComponentCollections(collections, serializeFunc);
            return tagCollectoins;
        }
        // get component data to serialized value
        public static Dictionary<string, List<TVal>> GetComponentCollections<T, TVal>(Dictionary<string, List<T>> collections, System.Func<T, TVal> serializeFunc) where T : UnityEngine.Component
        {
            Dictionary<string, List<TVal>> retVal = new Dictionary<string, List<TVal>>();
            foreach(var collection in collections)
            {
                retVal[collection.Key] = collection.Value.Select(_item => serializeFunc.Invoke(_item)).ToList();
            }
            return retVal;
        }
        // get real components from hierarchy
        public static Dictionary<string, List<T>> GetComponentCollections<T>(Transform root) where T : UnityEngine.Component
        {
            Dictionary<string, List<T>> retVal = new Dictionary<string, List<T>>();
            var comps = root.GetComponentsInChildren<T>(true);
            foreach(var comp in comps)
            {
                var path = GetRelativePath(root, comp);
                retVal.GetValue(path).Add(comp);
            }
            return retVal;
        }

        public static void ApplyToComponentCollections<T, TVal>(Transform root, Dictionary<string, List<TVal>> collections, System.Action<T, TVal> access) where T : UnityEngine.Component
        {
            foreach(var collection in collections)
            {
                Transform node = string.IsNullOrEmpty(collection.Key) ? root : root.Find(collection.Key);
                if(node)
                {
                    var comps = node.GetComponents<T>();
                    if(comps != null)
                    {
                        var list = collection.Value;
                        for(int i = 0, imax = Mathf.Min(comps.Length, list.Count); i < imax; i++)
                        {
                            access.Invoke(comps[i], list[i]);
                        }
                    }
                }
            }
        }

        private static string GetRelativePath<T>(Transform root, T comp) where T : UnityEngine.Component
        {
            if(comp.transform == root)
            {
                return "";
            }
            _sb.Length = 0;
            string path = comp.name;
            _sb.Append(comp.name);
            var parent = comp.transform.parent;
            while(parent && parent != root)
            {
                _sb.Insert(0, "/");
                _sb.Insert(0, parent.name);
                parent = parent.parent;
            }
            return _sb.ToString();
        }
        #endregion

    }
}

  这里的核心数据使用了 Dictionary<string, List<TVal>> 这样的结构, Key 是UI层级路径, List<TVal> 是数据序列化, 使用了列表就说明路径是可以相同的, 比如同一个节点下的UI对象都叫TEXT, 没有问题. 直接调 SerializeUIToJson 就行了, 反序列化直接 ApplySerializedDataToUI 就行了.

  没什么扩展性, 应该把功能写在 SerializeData 基类里的, 这样就可以扩展了...

  

 

posted @ 2021-09-06 15:53  tiancaiKG  阅读(100)  评论(0编辑  收藏  举报