Unity serializes the data of components into lua files and deserializes them

Function Description:

When using maximized bones, you need to add dynamic bones, but for different fashions, each dynamic bone component needs different data. Therefore, you want to serialize and store the data of all dynamic bones in lua folder, which has good readability and is easy to parse using xlua. Later, if you want to adjust the original parameters, you only need to re import the data file or directly modify a parameter in lua file;

Inspector UI extension:

The first is to write an extension of inspector UI, a storage button and an analysis button;

    public override void OnInspectorGUI()
    {
        DrawButtons();
        base.OnInspectorGUI();
    }

    private void DrawButtons(){
        if (GUILayout.Button("Export LuaConfig"))
        {
            Serialized();
        }
        if (GUILayout.Button("Import LuaConfig"))
        {
            Parse();
        }
    }

Store to lua file:

When exporting lua, you may encounter various errors. Here, an enumeration is provided to represent various results, and then a pop-up interface is written to display the results;

    public enum EExportRetType {
        success = 0,
        wrongGameObject = 1,
        noDBCom = 2,
    };
    
private void ShowExportMessageBox(EExportRetType ret){
        switch (ret){
            case EExportRetType.success :
                EditorUtility.DisplayDialog("Export profile", "Winner Winner Chicken Dinner", "determine");
                break;
            case EExportRetType.wrongGameObject :
                EditorUtility.DisplayDialog("Export profile", "Please select Bip001 or head_guadian Export bones", "determine");
                break;
            case EExportRetType.noDBCom :
                EditorUtility.DisplayDialog("Export profile", "There are no moving bone components under the current exported bone", "determine");
                break;
        }
    }

To store all the dynamic bone data under the bone, we need to get all the dynamic bone components, and then get each dynamic bone data; Here, we use the game object name of the moving bone as the key, and each key corresponds to all its data; Next, the problem becomes how to serialize the data of an animal bone component. First, simplify the thinking: if you only store an int data, it should be very simple. Get the animal bone component, get the data, use the field name as the key, and then get its value to store. The same is true for other basic data types, and some custom classes are actually the combination of these data, Including some classes defined by Unity, there is an AnimationCurve in DynamicBone. Its composition granularity is some basic data types. If you understand this, you can process various data separately. Here I use a FormatToLua to overload and store various data types;

Some syntax details to note:

  • When bool is converted to string, true will become true, so ToLower() is used here;
  • The enumeration value is stored with int, and can be forcibly converted with (int);
  • In string In format, {} is a special character. If you want to print, you need to use "{}}", that is, double;
    private string FormatToLua(string key, string val){
        return string.Format("        {0} = '{1}',\n", key, val);
    }

    private string FormatToLua(string key, bool val){
        return string.Format("        {0} = {1},\n", key, val.ToString().ToLower());
    }

    private string FormatToLua(string key, float val){
        return string.Format("        {0} = {1},\n", key, val);
    }
    private string FormatToLua(string key, int val){
        return string.Format("        {0} = {1},\n", key, val);
    }

    private string FormatToLua(string key, Vector3 val){
        return FormatToLua(key, val.x, val.y, val.z);
    }

    private string FormatToLua(string key, params string[] val){
        var valueStr = "{";
        for(int i = 0; i < val.Length; i++){
            valueStr += string.Format("'{0}'", val[i]);
            if (i != val.Length - 1){
                valueStr += ", ";
            }else{
                valueStr += "}";
            }
        }
        return string.Format("        {0} = {1},\n", key, valueStr);
    }

    private string FormatToLua(string key, params float[] val){
        var valueStr = "{";
        for(int i = 0; i < val.Length; i++){
            valueStr += val[i];
            if (i != val.Length - 1){
                valueStr += ", ";
            }else{
                valueStr += "}";
            }
        }
        return string.Format("        {0} = {1},\n", key, valueStr);
    }
    private string FormatToLua(string key, AnimationCurve curve){
        var valueStr = "{\n";
        var keys = curve.keys;
        for(int i = 0; i < keys.Length; i++){
            valueStr += FormatToLua((i + 1).ToString(), keys[i]);
        }
        valueStr += "        }";
        return string.Format("        {0} = {1},\n", key, valueStr);
    }
    private string FormatToLua(string key, Keyframe kf){
        var valueStr = "{\n";
        var fmtStr = "        ";
        valueStr += fmtStr + FormatToLua("time", kf.time);
        valueStr += fmtStr + FormatToLua("value", kf.value);
        valueStr += fmtStr + FormatToLua("inTangent", kf.inTangent);
        valueStr += fmtStr + FormatToLua("inWeight", kf.inWeight);
        valueStr += fmtStr + FormatToLua("outTangent", kf.outTangent);
        valueStr += fmtStr + FormatToLua("outWeight", kf.outWeight);
        valueStr += fmtStr + FormatToLua("weightedMode", (int)kf.weightedMode);
        valueStr += fmtStr + "    }";
        return string.Format("            [{0}] = {1},\n", key, valueStr);
    }

Then you can store the data of the component into a string, as shown below. Originally, you wanted to use reflection, but the object values obtained by reflection are of object type; Then write to lua file;

    private string SerializedSingleCom(DynamicBone dbCom){
        string content = "";
        content += string.Format("    ['{0}'] = {{\n", dbCom.name);
        // foreach(var fieldInfo in typeDB.GetFields(BindingFlags.Public | BindingFlags.Instance)){
        //     string name = fieldInfo.Name;
        //     var value = fieldInfo.GetValue(dbCom);
        //     var fieldTypeStr = fieldInfo.FieldType.ToString();
        //     Type fieldType = fieldInfo.FieldType.AsType();
        //     if (!CheckIsInBlackList(fieldTypeStr) && value != null){
        //         sw.WriteLine(FormatToLua(name, value as fieldType));
        //     }
        // }
        content += FormatToLua("lodToggle", dbCom.lodToggle);
        content += FormatToLua("m_Root", dbCom.m_Root.name);
        content += FormatToLua("m_UpdateRate", dbCom.m_UpdateRate);
        content += FormatToLua("m_Damping", dbCom.m_Damping);
        content += FormatToLua("m_Elasticity", dbCom.m_Elasticity);
        content += FormatToLua("m_Stiffness", dbCom.m_Stiffness);
        content += FormatToLua("m_Inert", dbCom.m_Inert);
        content += FormatToLua("m_Radius", dbCom.m_Radius);
        content += FormatToLua("m_EndLength", dbCom.m_EndLength);
        content += FormatToLua("m_DistantDisable", dbCom.m_DistantDisable);
        content += FormatToLua("m_DistanceToObject", dbCom.m_DistanceToObject);
        content += FormatToLua("m_FreezeAxis", (int)dbCom.m_FreezeAxis);
        content += FormatToLua("m_EndOffset", dbCom.m_EndOffset);
        content += FormatToLua("m_Gravity", dbCom.m_Gravity);
        content += FormatToLua("m_Force", dbCom.m_Force);
        content += FormatToLua("m_DampingDistrib", dbCom.m_DampingDistrib);
        content += FormatToLua("m_ElasticityDistrib", dbCom.m_ElasticityDistrib);
        content += FormatToLua("m_StiffnessDistrib", dbCom.m_StiffnessDistrib);
        content += FormatToLua("m_InertDistrib", dbCom.m_InertDistrib);
        content += FormatToLua("m_RadiusDistrib", dbCom.m_RadiusDistrib);
        content += "    },\n";
        return content;
    }

To recover the dynamic bone component data from lua file:

It is very simple to recover the dynamic bone data. Just solve one problem: how to get the desired data from the lua file; Here is only the idea of this problem. First, load the lua file to get a LuaTable, and then use this table to get data; After we get table, we can use table Getkeys(), which returns an iterator. We can get all the key Vals and get the data of each component,

        LuaEnv luaEnv = new LuaEnv();
        luaEnv.AddLoader(CustomLoader);
        string luaPath = string.Format("{0}.lua", path);
        object[] objs = luaEnv.DoString("return require('Common.ActorBone.4000002_DB')");
        LuaTable table = objs[0] as LuaTable;
        foreach (var item in table.GetKeys())
        {
            var tb = table.Get<LuaTable>(item);
            var ret = ParseSingleDBCom(item.ToString(), tb);
            if (!ret){
                return;
            }
        }

The LuaTable class provides such an interface to get the data in the table, whether it is the array segment table or the hash segment table. We can get all the data layer by layer, and then use the instantiation function of the component to complete all the assignment operations:

        public TValue Get<TValue>(string key)
        {
            TValue ret;
            Get(key, out ret);
            return ret;
        }
        tb.Get<float>(1)
        tb.Get<bool>("inTangent")
        tb.Get<LuaTable>(i)

 

The complete code is as follows:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ActorBoneSerialize : MonoBehaviour
{
}
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Text;
using System;
using System.Reflection;
using System.Text.RegularExpressions;
using XLua;
using LuaInterface;

[SerializeField]
[CustomEditor(typeof(ActorBoneSerialize), true)]
public class ActorBoneSerializeEditor : Editor
{
    public enum EExportRetType {
        success = 0,
        wrongGameObject = 1,
        noDBCom = 2,
    };
    public enum EImportRetType {
        success = 0,
        wrongGameObject = 1,
        noCfg = 2,
        wrongCfg = 3,
    };

    private Type typeDB = typeof(DynamicBone);
    private string[] blackList = {
        "System.Collections.Generic.List`1[DynamicBoneCollider]",
        "System.Collections.Generic.List`1[Transform]",
    };
    public override void OnInspectorGUI()
    {
        DrawButtons();
        base.OnInspectorGUI();
    }

    private void DrawButtons(){
        if (GUILayout.Button("Export LuaConfig"))
        {
            Serialized();
        }
        if (GUILayout.Button("Import LuaConfig"))
        {
            Parse();
        }
    }
    public void Serialized(){
        var go = (target as ActorBoneSerialize).gameObject;
        if(go.name != "Bip001" && go.name != "head_guadian"){
            ShowExportMessageBox(EExportRetType.wrongGameObject);
            return;
        }
        SerializedObjectDB(go);
    }

    private void SerializedObjectDB(GameObject go){
        var dbArray = go.GetComponentsInChildren<DynamicBone>();
        if(dbArray.Length == 0){
            ShowExportMessageBox(EExportRetType.noDBCom);
        }
        var fileName = go.transform.parent.name + "_DB";
        string pathF = string.Format("{0}/{1}.lua", Application.dataPath + "/Lua/Common/ActorBone", fileName);
        var content = "";
        content += "return {\n";
        for(int i = 0; i < dbArray.Length; i++){
            var dbCom = dbArray[i];
            content += SerializedSingleCom(dbCom);
        }
        content += "}";
        WriteAsset(pathF, content, false);
        ShowExportMessageBox(EExportRetType.success);
    }
    
    // Serialize all properties of a single component
    private string SerializedSingleCom(DynamicBone dbCom){
        string content = "";
        content += string.Format("    ['{0}'] = {{\n", dbCom.name);
        // foreach(var fieldInfo in typeDB.GetFields(BindingFlags.Public | BindingFlags.Instance)){
        //     string name = fieldInfo.Name;
        //     var value = fieldInfo.GetValue(dbCom);
        //     var fieldTypeStr = fieldInfo.FieldType.ToString();
        //     Type fieldType = fieldInfo.FieldType.AsType();
        //     if (!CheckIsInBlackList(fieldTypeStr) && value != null){
        //         sw.WriteLine(FormatToLua(name, value as fieldType));
        //     }
        // }
        content += FormatToLua("lodToggle", dbCom.lodToggle);
        content += FormatToLua("m_Root", dbCom.m_Root.name);
        content += FormatToLua("m_UpdateRate", dbCom.m_UpdateRate);
        content += FormatToLua("m_Damping", dbCom.m_Damping);
        content += FormatToLua("m_Elasticity", dbCom.m_Elasticity);
        content += FormatToLua("m_Stiffness", dbCom.m_Stiffness);
        content += FormatToLua("m_Inert", dbCom.m_Inert);
        content += FormatToLua("m_Radius", dbCom.m_Radius);
        content += FormatToLua("m_EndLength", dbCom.m_EndLength);
        content += FormatToLua("m_DistantDisable", dbCom.m_DistantDisable);
        content += FormatToLua("m_DistanceToObject", dbCom.m_DistanceToObject);
        content += FormatToLua("m_FreezeAxis", (int)dbCom.m_FreezeAxis);
        content += FormatToLua("m_EndOffset", dbCom.m_EndOffset);
        content += FormatToLua("m_Gravity", dbCom.m_Gravity);
        content += FormatToLua("m_Force", dbCom.m_Force);
        content += FormatToLua("m_DampingDistrib", dbCom.m_DampingDistrib);
        content += FormatToLua("m_ElasticityDistrib", dbCom.m_ElasticityDistrib);
        content += FormatToLua("m_StiffnessDistrib", dbCom.m_StiffnessDistrib);
        content += FormatToLua("m_InertDistrib", dbCom.m_InertDistrib);
        content += FormatToLua("m_RadiusDistrib", dbCom.m_RadiusDistrib);
        content += "    },\n";
        return content;
    }

    private void ShowExportMessageBox(EExportRetType ret){
        switch (ret){
            case EExportRetType.success :
                EditorUtility.DisplayDialog("Export profile", "Winner Winner Chicken Dinner", "determine");
                break;
            case EExportRetType.wrongGameObject :
                EditorUtility.DisplayDialog("Export profile", "Please select Bip001 or head_guadian Export bones", "determine");
                break;
            case EExportRetType.noDBCom :
                EditorUtility.DisplayDialog("Export profile", "There are no moving bone components under the current exported bone", "determine");
                break;
        }
    }

    private string FormatToLua(string key, string val){
        return string.Format("        {0} = '{1}',\n", key, val);
    }

    private string FormatToLua(string key, bool val){
        return string.Format("        {0} = {1},\n", key, val.ToString().ToLower());
    }

    private string FormatToLua(string key, float val){
        return string.Format("        {0} = {1},\n", key, val);
    }
    private string FormatToLua(string key, int val){
        return string.Format("        {0} = {1},\n", key, val);
    }

    private string FormatToLua(string key, Vector3 val){
        return FormatToLua(key, val.x, val.y, val.z);
    }

    private string FormatToLua(string key, params string[] val){
        var valueStr = "{";
        for(int i = 0; i < val.Length; i++){
            valueStr += string.Format("'{0}'", val[i]);
            if (i != val.Length - 1){
                valueStr += ", ";
            }else{
                valueStr += "}";
            }
        }
        return string.Format("        {0} = {1},\n", key, valueStr);
    }

    private string FormatToLua(string key, params float[] val){
        var valueStr = "{";
        for(int i = 0; i < val.Length; i++){
            valueStr += val[i];
            if (i != val.Length - 1){
                valueStr += ", ";
            }else{
                valueStr += "}";
            }
        }
        return string.Format("        {0} = {1},\n", key, valueStr);
    }
    private string FormatToLua(string key, AnimationCurve curve){
        var valueStr = "{\n";
        var keys = curve.keys;
        for(int i = 0; i < keys.Length; i++){
            valueStr += FormatToLua((i + 1).ToString(), keys[i]);
        }
        valueStr += "        }";
        return string.Format("        {0} = {1},\n", key, valueStr);
    }
    private string FormatToLua(string key, Keyframe kf){
        var valueStr = "{\n";
        var fmtStr = "        ";
        valueStr += fmtStr + FormatToLua("time", kf.time);
        valueStr += fmtStr + FormatToLua("value", kf.value);
        valueStr += fmtStr + FormatToLua("inTangent", kf.inTangent);
        valueStr += fmtStr + FormatToLua("inWeight", kf.inWeight);
        valueStr += fmtStr + FormatToLua("outTangent", kf.outTangent);
        valueStr += fmtStr + FormatToLua("outWeight", kf.outWeight);
        valueStr += fmtStr + FormatToLua("weightedMode", (int)kf.weightedMode);
        valueStr += fmtStr + "    }";
        return string.Format("            [{0}] = {1},\n", key, valueStr);
    }

    private bool CheckIsInBlackList(string str){
        for(int i = 0; i < blackList.Length; i++){
            if (blackList[i] == str){
                return true;
            }
        }
        return false;
    }
    
    // Import lua configuration*****************************************************************
    public void Parse(){
        var go = (target as ActorBoneSerialize).gameObject;
        if(go.name != "Bip001" && go.name != "head_guadian"){
            ShowImportMessageBox(EImportRetType.wrongGameObject);
            return;
        }
        var fileName = go.transform.parent.name + "_DB";
        string pathF = string.Format("{0}/{1}.lua", Application.dataPath + "/Lua/Common/ActorBone", fileName);
        string relativePath = "Common/ActorBone/" + fileName;
        if (!File.Exists(pathF)){
            ShowImportMessageBox(EImportRetType.noCfg);
            return;
        }
        ParseLuaCfg(relativePath);
        ShowImportMessageBox(EImportRetType.success);
    }

    private void ParseLuaCfg(string path){
        LuaEnv luaEnv = new LuaEnv();
        luaEnv.AddLoader(CustomLoader);
        string luaPath = string.Format("{0}.lua", path);
        object[] objs = luaEnv.DoString("return require('Common.ActorBone.4000002_DB')");
        LuaTable table = objs[0] as LuaTable;
        foreach (var item in table.GetKeys())
        {
            var tb = table.Get<object>(item);
            var ret = ParseSingleDBCom(item.ToString(), tb as LuaTable);
            if (!ret){
                return;
            }
        }
    }

    private bool ParseSingleDBCom(string boneName, LuaTable tb){
        var go = GameObject.Find(boneName);
        if (go == null){
            ShowImportMessageBox(EImportRetType.wrongCfg);
            return false;
        }
        var dbCom = go.GetComponent<DynamicBone>();
        if (dbCom == null){
            dbCom = go.AddComponent<DynamicBone>();
        }

        // tb.Get<string, bool>("lodToggle", out dbCom.lodToggle);
        dbCom.lodToggle = tb.Get<bool>("lodToggle");
        dbCom.m_Root = GetTransformByName(tb.Get<string>("m_RootName"));
        dbCom.m_UpdateRate = tb.Get<float>("m_UpdateRate");
        dbCom.m_Damping = tb.Get<float>("m_Damping");
        dbCom.m_Elasticity = tb.Get<float>("m_Elasticity");
        dbCom.m_Stiffness = tb.Get<float>("m_Stiffness");
        dbCom.m_Inert = tb.Get<float>("m_Inert");
        dbCom.m_Radius = tb.Get<float>("m_Radius");
        dbCom.m_EndLength = tb.Get<float>("m_EndLength");
        dbCom.m_DistantDisable = tb.Get<bool>("m_DistantDisable");
        dbCom.m_FreezeAxis = (DynamicBone.FreezeAxis) tb.Get<int>("m_FreezeAxis");

        dbCom.m_EndOffset = GetVector3FromTable(tb.Get<LuaTable>("m_EndOffset"));
        dbCom.m_Gravity = GetVector3FromTable(tb.Get<LuaTable>("m_Gravity"));
        dbCom.m_Force = GetVector3FromTable(tb.Get<LuaTable>("m_Force"));

        dbCom.m_DampingDistrib = GetCurveFromTable(tb.Get<LuaTable>("m_DampingDistrib"));
        dbCom.m_ElasticityDistrib = GetCurveFromTable(tb.Get<LuaTable>("m_ElasticityDistrib"));
        dbCom.m_StiffnessDistrib = GetCurveFromTable(tb.Get<LuaTable>("m_StiffnessDistrib"));
        dbCom.m_InertDistrib = GetCurveFromTable(tb.Get<LuaTable>("m_InertDistrib"));
        dbCom.m_RadiusDistrib = GetCurveFromTable(tb.Get<LuaTable>("m_RadiusDistrib"));
        return true;
    }
    
    private Transform GetTransformByName(string name){
        var go = GameObject.Find(name);
        if (go == null){
            return null;
        }else{
            return go.transform;
        }
    }

    private Vector3 GetVector3FromTable(LuaTable tb){
        return new Vector3(tb.Get<float>(1), tb.Get<float>(2), tb.Get<float>(3));
    }
    private AnimationCurve GetCurveFromTable(LuaTable tb){
        if (tb.Length == 0){
            return null;
        }
        Keyframe[] keys = new Keyframe[tb.Length];
        for (int i = 1; i < tb.Length; i++){
            keys[i - 1] = GetKeyFrameFromTable(tb.Get<LuaTable>(i));
        }
        AnimationCurve curve = new AnimationCurve(keys);
        return curve;
    }
    private Keyframe GetKeyFrameFromTable(LuaTable tb){
        var kf = new Keyframe(tb.Get<float>("time"), tb.Get<float>("value"));
        kf.inTangent = tb.Get<float>("inTangent");
        kf.inWeight = tb.Get<float>("inWeight");
        kf.outTangent = tb.Get<float>("outTangent");
        kf.outWeight = tb.Get<float>("outWeight");
        kf.weightedMode = (WeightedMode) tb.Get<float>("weightedMode");
        return kf;
    }

    private void ShowImportMessageBox(EImportRetType ret){
        switch (ret){
            case EImportRetType.success :
                EditorUtility.DisplayDialog("Import profile", "Import succeeded", "determine");
                break;
            case EImportRetType.wrongGameObject :
                EditorUtility.DisplayDialog("Import profile", "Please select Bip001 or head_guadian Import bones", "determine");
                break;
            case EImportRetType.noCfg :
                EditorUtility.DisplayDialog("Import profile", "Lack of corresponding lua configuration file", "determine");
                break;
            case EImportRetType.wrongCfg :
                EditorUtility.DisplayDialog("Import profile", "The configuration file and bone do not match", "determine");
                break;
        }
    }

    // Custom loader
    private byte[] CustomLoader(ref string fileName)
    {
        return ResourceManager.Instance.LoadLua(fileName);
    }

    private string ReadLuaAsset(string resPath)
    {
        string text = "";
        StreamReader streamReader = new StreamReader(resPath);
        text = streamReader.ReadToEnd();
        streamReader.Close();
        return text;
    }

    // Save the file to the specified location
    private void WriteAsset(string desPath, string text, bool encoderShouldEmitUTF8Identifier = true)
    {
        bool throwOnInvalidBytes = false;
        UTF8Encoding encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier, throwOnInvalidBytes);
        bool append = false;
        StreamWriter streamWriter = new StreamWriter(desPath, append, encoding);
        streamWriter.Write(text);
        streamWriter.Close();
        AssetDatabase.ImportAsset(desPath);
    }
}

 

Tags: lua

Posted by geoldr on Fri, 20 May 2022 10:23:45 +0300