懒人的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 基类里的, 这样就可以扩展了...