Unity表格配置编辑工具

前言

游戏开发中表格配置是必有的功能,我们会将一些游戏数据配置在表格中,一般流程是Excel编辑数据,然后导出C#的Model类和Unity中可用的二进制或者json/xml数据等,但如果碰到要一个需求,策划想要频繁修改数据并且在游戏中测试,这就发现我们需要频繁的在Excel中修改然后导出,然后启动Unity测试,这样的流程就显得很繁琐,而且我们不能保证我们填写的数据就是正确的,难免有手滑手误的时候,但也有针对这种情况的解决方案是程序写一个表格检查工具来检查Excel配置的数据书否合法,但还是显得比较麻烦,针对这一问题可以做的优化,在Unity中写一个Editor编辑工具来编辑对应的Excel数据,并且对应的数据根据类型来给一个约束,例如:范围类型的数据,可以配置一个Range滑动类型,这样策划修改的数据就不会超出这个范围,再比如配置一个bool类型,我们给一个toggle选择,如果其他类型我们可以写一个数据监测,如果策划配置的数据不正确会立马给出提示,这样就能确保数据的正确性,并且可以及时的在游戏中进行测试,确保配置的数据是自己想要的效果,然后在Editor中保存到Excel,就不需要再Excel中进行配置。Excel的读写我用的NPOI,然后Excel类型的读取我用的反射,这样可以确保策划可以随意的修改Excel的字段类型确保工具的健壮性。关于C#的反射,不熟的或者不了解的可以看我之前写的一篇,关于C#的反射,你真的运用自如嘛?

效果

在这里插入图片描述
在这里插入图片描述
编辑数据区域是简单的编辑修改,没有对类型做约束和类型指定和判断。

代码

using Base.Framework.ExcelTool;
using Game.Data;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using UnityEditor;
using UnityEngine;

public class SkillTableEditor : EditorWindow
{
    Vector2 scrollPos;
    Vector2 scrollEditorPos;
    Vector2 scrollEditPos;
    PropertyInfo[] properties;
    List<skills_raw> skillDatas;
    int currentSelectIndex = 0;
    List<bool> toggleValues = new List<bool>();
    List<int> widths = new List<int>();
    skills_raw tempModifyData;
    List<string> tempModifyDataValues;
    [MenuItem("Tools/Excel/SkillTableEditor")]
    public static void GetWindow()
    {
        Rect rect = new Rect(0, 0, 1920, 656);
        var window = EditorWindow.GetWindowWithRect(typeof(SkillTableEditor), rect, true, "技能编辑");
        window.Show();
    }

    private void OnEnable()
    {
        Base.Framework.SQLite4Unity3d.SqliteLoder.Load(Application.streamingAssetsPath);
        skillDatas = Game.Data.skills_raw.GetDataList();
        properties = typeof(skills_raw).GetProperties();
    }

    private void OnDisable()
    {
        BDFramework.Sql.SqliteHelper.DB.Close();
    }

    private GUIStyle GetStyle(int section, TextAnchor anchor = TextAnchor.MiddleCenter, int fontSize = 12)
    {
        return new GUIStyle()
        {
            alignment = anchor,
            fontSize = fontSize,
            normal = new GUIStyleState()
            {
                textColor = new Color(1, 1, 1),
            },
        };
    }

    private int GetCurrentProperityWidth(int section, int defaultWidth = 30)
    {
        return (properties[section].Name.Length / 5 + 1) * defaultWidth;
    }

    private void OnGUI()
    {
        GUI.Label(new Rect(0, 0, 600, 40), "注意:加载数据需要在游戏运行的时候,Excel->DB必须在游戏非运行的时候点!");

        GUILayout.Space(30);
        DrawTableDatas();
        GUILayout.Space(30);

        if (currentSelectIndex > 0)
        {
            GUILayout.Label(new GUIContent("编辑区域"), new GUIStyle()
            {
                alignment = TextAnchor.MiddleLeft,
                fontSize = 16,
                normal = new GUIStyleState()
                {
                    textColor = new Color(1, 1, 1),
                }
            });
            GUILayout.Space(30);
            EditorGUILayout.BeginHorizontal();
            scrollEditorPos = EditorGUILayout.BeginScrollView(scrollEditorPos, GUILayout.Width(1850), GUILayout.Height(100));
            //显示title
            GUILayout.BeginHorizontal();
            for (int section = 0; section < NumberOfSectionsInTableView(); section++)
            {
                GUILayout.Label(new GUIContent(properties[section].Name), GetStyle(section), GUILayout.Width(GetCurrentProperityWidth(section, 33)));
            }
            GUILayout.EndHorizontal();
            GUILayout.Space(20);
            //显示数据
            GUILayout.BeginHorizontal();
            for (int i = 0; i < properties.Length; i++)
            {
                tempModifyDataValues[i] = EditorGUILayout.TextField(tempModifyDataValues[i], GetStyle(i), GUILayout.Width(GetCurrentProperityWidth(i, 33)));
            }
            GUILayout.EndHorizontal();
            EditorGUILayout.EndScrollView();
            EditorGUILayout.EndHorizontal();
            GUILayout.Space(20);
            GUILayout.BeginHorizontal();
            if (GUILayout.Button("保存数据到Excel"))
            {
                if (tempModifyData != null)
                {
                    var mBuilder = new ExcelConverter();
                    List<ExcelDataValue[]> values = new List<ExcelDataValue[]>();
                    List<string> attributesStringValues = new List<string>();
                    var assembly = typeof(skills_exceldata).Assembly;
                    for (int i = 0; i < properties.Length; i++)
                    {
                        var v = properties[i].GetValue(tempModifyData);
                        foreach (var m in GetExtensionMethods(assembly, v.GetType()))
                        {
                            if (m.Name.Contains("GetSaveToExcelStringValue"))
                            {
                                var stringValue = m.Invoke(null, new object[] { v });
                                attributesStringValues.Add(stringValue.ToString());
                            }
                        }
                    }
                    var attributesValues = attributesStringValues.ToArray();
                    ExcelDataValue[] datavalues = new ExcelDataValue[attributesValues.Length];
                    for (int i = 0; i < attributesValues.Length; i++)
                    {
                        datavalues[i] = new ExcelDataValue(attributesValues[i]);
                    }
                    values.Add(datavalues);
                    mBuilder.WriteDatas("skills", values);
                    Debug.Log("保存修改");
                }
                else
                {
                    Debug.LogError("没有选择要编辑保存的数据");
                }
            }
            if (GUILayout.Button("保存测试数据"))
            {
                Debug.Log("测试数据");
                SaveModityDataToTempSkillData();
                Game.Data.skills_raw.SetTestData(tempModifyData);
                if (tempModifyData.id > skillDatas.Count)
                {
                    skillDatas.Add(tempModifyData);
                }
                currentSelectIndex = 0;
            }
            GUILayout.EndHorizontal();
        }
        else
        {
            GUILayout.Label(new GUIContent("选择某一行编辑数据"), new GUIStyle() { fontSize = 16, normal = new GUIStyleState() { textColor = new Color(0.8f, 0, 0) } });
            if (GUILayout.Button("添加数据"))
            {
                tempModifyData = new skills_raw()
                {
                    id = skillDatas.Count + 1,
                    loc_skill_name = "LOC_DASH",
                    skill_type = 1,
                    state_mask = 3,
                    prepare_time = 0,
                    duration = 1,
                    end_time = 0,
                    accept_move_control = false,
                    follow_move_dir = true,
                    move_speed = Vector3.zero,
                    disable_gravity = false,
                    jump_height = 0,
                    projectile_id = 0,
                    projectile_dir = Vector3.zero,
                    //weapon_projectile_index = 1,
                    skill_param_1 = 0,
                    skill_param_2 = 0,
                    skill_param_3 = 0,
                };

                GetDataStringValues();
                currentSelectIndex = tempModifyData.id;
                toggleValues[0] = false;
            }
        }
    }

    IEnumerable<MethodInfo> GetExtensionMethods(Assembly assembly,
       Type extendedType)
    {
        var query = from type in assembly.GetTypes()
                    where type.IsSealed && !type.IsGenericType && !type.IsNested
                    from method in type.GetMethods(BindingFlags.Static
                        | BindingFlags.Public | BindingFlags.NonPublic)
                    where method.IsDefined(typeof(ExtensionAttribute), false)
                    where method.GetParameters()[0].ParameterType == extendedType
                    select method;
        return query;
    }

    private void GetDataStringValues()
    {
        if (tempModifyDataValues == null)
            tempModifyDataValues = new List<string>();
        else
            tempModifyDataValues.Clear();
        for (int i = 0; i < properties.Length; i++)
        {
            var value = properties[i].GetValue(tempModifyData);
            if (value == null)
            {
                throw new Exception("对象字段值不存在");
            }
            tempModifyDataValues.Add(value.ToString());
        }
    }

    /// <summary>
    /// convert string to Type
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="type"></param>
    /// <returns></returns>
    private object ConvertObject(object obj, Type type)
    {
        if (type == null) return obj;
        if (obj == null) return type.IsValueType ? Activator.CreateInstance(type) : null;

        Type underlyingType = Nullable.GetUnderlyingType(type);
        if (type.IsAssignableFrom(obj.GetType())) // 如果待转换对象的类型与目标类型兼容,则无需转换
        {
            return obj;
        }
        else if ((underlyingType ?? type).IsEnum) // 如果待转换的对象的基类型为枚举
        {
            if (underlyingType != null && string.IsNullOrEmpty(obj.ToString())) // 如果目标类型为可空枚举,并且待转换对象为null 则直接返回null值
            {
                return null;
            }
            else
            {
                return Enum.Parse(underlyingType ?? type, obj.ToString());
            }
        }
        else if (typeof(IConvertible).IsAssignableFrom(underlyingType ?? type))
        {
            try
            {
                return Convert.ChangeType(obj, underlyingType ?? type, null);
            }
            catch
            {
                return underlyingType == null ? Activator.CreateInstance(type) : null;
            }
        }
        else if (type.IsAssignableFrom(typeof(Vector3)))
        {
            string v = obj.ToString();
            v = v.Replace("(", "").Replace(")", "");
            var strValues = v.Split(',');
            return new Vector3(Convert.ToSingle(strValues[0]), Convert.ToSingle(strValues[1]), Convert.ToSingle(strValues[2]));
        }
        else
        {
            TypeConverter converter = TypeDescriptor.GetConverter(type);
            if (converter.CanConvertFrom(obj.GetType()))
            {
                return converter.ConvertFrom(obj);
            }
            ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes);
            if (constructor != null)
            {
                object o = constructor.Invoke(null);
                PropertyInfo[] propertys = type.GetProperties();
                Type oldType = obj.GetType();
                foreach (PropertyInfo property in propertys)
                {
                    PropertyInfo p = oldType.GetProperty(property.Name);
                    if (property.CanWrite && p != null && p.CanRead)
                    {
                        property.SetValue(o, ConvertObject(p.GetValue(obj, null), property.PropertyType), null);
                    }
                }
                return o;
            }
        }
        return obj;
    }

    private void SaveModityDataToTempSkillData()
    {
        for (int i = 0; i < properties.Length; i++)
        {
            var v = ConvertObject(tempModifyDataValues[i], properties[i].PropertyType);
            properties[i].SetValue(tempModifyData, v);
        }
    }

    void DrawTableDatas()
    {
        if (properties == null)
        {
            Debug.LogError("properties为空,需要在运行的情况下,点击SkillEditor功能");
            return;
        }
        EditorGUILayout.BeginHorizontal();
        scrollPos = EditorGUILayout.BeginScrollView(scrollPos, GUILayout.Width(1850), GUILayout.Height(350));
        EditorGUILayout.BeginHorizontal();
        if (toggleValues.Count < 1)
            toggleValues.Add(true);
        if (toggleValues[0] = EditorGUILayout.Toggle(toggleValues[0]))
        {
            if (currentSelectIndex != 0)
            {
                ChangeSelect(0);
            }
        }
        widths.Clear();
        for (int i = 0; i < properties.Length; i++)
        {
            int w = (properties[i].Name.Length / 5 + 1) * 34;
            widths.Add(w);
            EditorGUILayout.LabelField(properties[i].Name, GUILayout.Width(w));
        }
        EditorGUILayout.EndHorizontal();
        for (int i = 0; i < skillDatas.Count; i++)
        {
            EditorGUILayout.BeginHorizontal();
            if (toggleValues.Count <= i + 1)
                toggleValues.Add(false);
            if (toggleValues[i + 1] = EditorGUILayout.Toggle(toggleValues[i + 1]))
            {
                if (currentSelectIndex != i + 1)
                {
                    ChangeSelect(i + 1);
                }
            }
            var data = skillDatas[i];
            for (int j = 0; j < properties.Length; j++)
            {
                EditorGUILayout.LabelField(properties[j].GetValue(data).ToString(), GUILayout.Width(widths[j]));
            }
            EditorGUILayout.EndHorizontal();
        }
        EditorGUILayout.EndScrollView();
        EditorGUILayout.EndHorizontal();
    }

    void ChangeSelect(int index)
    {
        if (toggleValues.Count < 1)
            throw new Exception("没有toggle组");
        if (index < 0 || index > toggleValues.Count - 1)
            throw new Exception("toggle索引超过范围");

        if (currentSelectIndex != index && currentSelectIndex <= skillDatas.Count)
        {
            toggleValues[currentSelectIndex] = false;
            toggleValues[index] = true;
        }
        currentSelectIndex = index;
        Debug.Log("选择" + index);
        if (index > 0)
        {
            tempModifyData = skillDatas[index - 1];
            GetDataStringValues();
        }
    }
    int NumberOfSectionsInTableView()
    {
        return properties.Length;
    }
}


下载

https://gitee.com/dingxiaowei/ExcelConfigEditor

更多教程

http://dingxiaowei.cn

猜你喜欢

转载自blog.csdn.net/s10141303/article/details/95016322
今日推荐