Unity 读取CSV配置文件(二)

【配置表】Unity中Excel导出CSV文件(一)

功能

1、反序列化CSV文件。
2、Table管理。
3、自定义查找key。
4、可扩展数据模型管理。

  • 目前支持数据 : 表中写法
    int
    short
    byte
    float
    double
    bool
    string
    enum : 枚举字符
    Vector3 : 3,3,3
    Vector2 : 2,2
    int[] : , 分割
    float[] :,分割
    Vector3[] : ,分割Vector3数据 |分割元素(例:0.3,0.3,0.3|3,3,3)
    Vector2[] : ,分割Vector3数据 |分割元素(例:0.2,0.2|2,2)
    Dictionary<int, int> : ,分割键值对 |分割元素(例:1,-1|2,-2)
    Dictionary<int, float> : ,分割键值对 |分割元素(例:1,0.1|2,0.2)

使用方法

1、标识CSV文件字段行_默认1

位置:Horo\Scripts\ReadTable\TableParser -> PROPERTY_ROW

  • csv文件字段行以下必须是数据
public static class TableParser
{
    //字段信息行(当前.py中只导出字段与有效数据 所以为1 *这行之下必须数据)
    private const int PROPERTY_ROW = 1;

2、设置读取CSV文件路径

位置:Horo\Scripts\ReadTable\TableParser -> TABLE_PATH

public static class TableParser
{
    //表路径
    private const string TABLE_PATH = "csv/{0}";

3、设置读取文件方式

位置:Horo\Scripts\ReadTable\TableParser -> Parse 方法中

public static class TableParser
{
    public static T[] Parse<T>(string tableName)
    {
        string path = string.Format(TABLE_PATH, tableName);
        //TODO: Load 方式
        TextAsset textAsset = Resources.Load<TextAsset>(path);

4、定义数据结构

需继承 ITableModel 接口实现 Key()

public class TestModel : ITableModel
{
    public int Id;
    public short Short;
    public byte Byte;
    public float Float;
    public double Double;
    public bool Bool;
    public string Str;
    public TestEnum ETestEnum;
    public Vector3 V3;
    public Vector2 V2;
    public int[] ArrInt;
    public float[] ArrFloat;
    public Vector2[] ArrV2;
    public Vector3[] ArrV3;
    public Dictionary<int, int> DictInt;
    public Dictionary<int, float> DictFloat;
	//自定义key 管理中取数据用
    public object Key()
    {
        return Id;
    }
}

5、创建数据管理类

创建数据模型管理类 需继承 TableManager<继承ITableModel,当前类(单例用)>。
在管理类中可重写 Init 做初始化,还可添加扩展方法。

public class TestModelMgr : TableManager<TestModel, TestModelMgr>
{
    public override string TableName()
    {
    	//csv文件名
        return "Test";
    }
}

6、Excel填写方式

见:【配置表】Unity中Excel导出CSV文件(一)

  • 使用方法(1,2)

TableManager代码

public interface ITableModel
{
    object Key();
}

public interface ITableManager
{
    string TableName();
    object TableData { get; }
}

public abstract class TableManager<T, S> : Singleton<S>, ITableManager where T : ITableModel
{
    public abstract string TableName();

    public object TableData
    {
        get { return mModelArray; }
    }

    private T[] mModelArray;
    Dictionary<object,int> mKeyModelDict = new Dictionary<object, int>();

    internal TableManager()
    {
        mModelArray = TableParser.Parse<T>(TableName());

        for (int i = 0; i < mModelArray.Length; i++)
            mKeyModelDict[mModelArray[i].Key()] = i;

        Init();
    }

    protected virtual void Init() { }

    public T GetModel(object key)
    {
        int index;
        if (mKeyModelDict.TryGetValue(key, out index))
            return mModelArray[index];
        return default(T);
    }

    public T[] GetAllModel()
    {
        return mModelArray;
    }
    
	//lambda表达式
    public List<T> GetAllModel(Func<T,bool> comp)
    {
        List<T> list = new List<T>();

        for (int i = 0; i < mModelArray.Length; i++)
        {
            if (comp(mModelArray[i]))
                list.Add(mModelArray[i]);
        }

        return list;
    }

    /// <summary>
    /// 包括开始,不包括结束
    /// </summary>
    public T[] GetSomeModel(int s_ind, int e_ind)
    {
        if (mModelArray.Length < s_ind)
            return null;

        int endLength = mModelArray.Length > e_ind ? e_ind : mModelArray.Length;
        T[] array = new T[endLength - s_ind];
        for (int i = s_ind; i < endLength; i++)
            array[i - s_ind] = mModelArray[i];

        return array;
    }
}

TableParser部分代码

public static class TableParser
 {
     //表路径
     private const string TABLE_PATH = "csv/{0}";
     //字段信息行(当前.py中只导出字段与有效数据 所以为1 *这行之下必须数据)
     private const int PROPERTY_ROW = 1;

     public static T[] Parse<T>(string tableName)
     {
         string path = string.Format(TABLE_PATH, tableName);

         //TODO: Load 方式
         TextAsset textAsset = Resources.Load<TextAsset>(path);
         Debug.AssertFormat(textAsset != null, "{0} not found.",path);

         string[] lines = textAsset.text.Split("\r\n".ToCharArray(),StringSplitOptions.RemoveEmptyEntries);
         Debug.Assert(lines.Length > PROPERTY_ROW, "table row number error or no data.");

         //分析字段信息
         Dictionary<int, FieldInfo> propertyInfos = GetGetPropertyInfos<T>(lines[PROPERTY_ROW - 1]);

         //分析数据
         T[] list = new T[lines.Length - PROPERTY_ROW];
         for (int i = PROPERTY_ROW; i < lines.Length; i++)
         {
             T t = ParseObject<T>(lines[i], propertyInfos);
             if (t == null) continue;
             list[i - PROPERTY_ROW] = t;
         }

         return list;
     }

     private static char[] sep_1 = { ',' };
     private static char[] sep_2 = { '|' };
     private static char[] sep = {'.'};
     static void ParsePropertyValue<T>(T obj, FieldInfo fieldInfo, string valueStr)
     {
         System.Object val;
         if (string.IsNullOrEmpty(valueStr) || valueStr.ToLower() == "null")
         {
             
             val = default(T);
             fieldInfo.SetValue(obj, val);
             return;
         }

         if (fieldInfo.FieldType == typeof(int))
             val = int.Parse(valueStr.Split(sep)[0]);
         else if (fieldInfo.FieldType == typeof(short))
             val = short.Parse(valueStr.Split(sep)[0]);
         else if (fieldInfo.FieldType == typeof(byte))
             val = byte.Parse(valueStr.Split(sep)[0]);
         else if (fieldInfo.FieldType == typeof(float))
             val = float.Parse(valueStr);
         else if (fieldInfo.FieldType == typeof(double))
             val = double.Parse(valueStr);
         else if (fieldInfo.FieldType == typeof(bool))
             val = int.Parse(valueStr) != 0;
         else if (fieldInfo.FieldType == typeof(string))
             val = valueStr;
         else if (fieldInfo.FieldType == typeof(Vector2))
             val = SplitStringToVector2(valueStr, sep_1);
         else if (fieldInfo.FieldType == typeof(Vector3))
             val = SplitStringToVector3(valueStr, sep_1);
         else if (fieldInfo.FieldType == typeof(int[]))
             val = SplitStringToIntArray(valueStr, sep_1);
         else if (fieldInfo.FieldType == typeof(float[]))
             val = SplitStringToFloatArray(valueStr, sep_1);
         else if (fieldInfo.FieldType == typeof(Vector2[]))
             val = SplitStringToVector2Array(valueStr, sep_2,sep_1);
         else if (fieldInfo.FieldType == typeof(Vector3[]))
             val = SplitStringToVector3Array(valueStr, sep_2, sep_1);
         else if (fieldInfo.FieldType == typeof(Dictionary<int, int>))
             val = SplitStringToDictInt_Int(valueStr, sep_2, sep_1);
         else if (fieldInfo.FieldType == typeof(Dictionary<int, float>))
             val = SplitStringToDictInt_Float(valueStr, sep_2, sep_1);
         else if (fieldInfo.FieldType.IsEnum)
             val = Enum.Parse(fieldInfo.FieldType, valueStr);
         else
             val = default(T);
		 //TODO:需要可以自己加
         fieldInfo.SetValue(obj,val);
     }

     #region SplitString

     static Vector2 SplitStringToVector2(string valueStr, char[] separator)
     {
         string[] datas = valueStr.Split(separator, StringSplitOptions.RemoveEmptyEntries);
         return new Vector2(float.Parse(datas[0]), float.Parse(datas[1]));
     }
	 //TODO:这里方法有点多暂时删了,这里可以自己加需要的 完整的在工程中
     
     #endregion
	 //获得字段
     static Dictionary<int, FieldInfo> GetGetPropertyInfos<T>(string memberLine)
     {
         Type tType = typeof(T);

         string[] members = memberLine.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
         Dictionary<int, FieldInfo> propertyInfos = new Dictionary<int, FieldInfo>();

         //表中字段信息对应Model字段
         for (int i = 0; i < members.Length; i++)
         {
             FieldInfo fieldInfo = tType.GetField(members[i]);
             if(fieldInfo == null) continue;
             propertyInfos[i] = fieldInfo;
         }

         //表中字段信息未查找到有效数据,就以Model字段顺序为字段信息
         if (propertyInfos.Count == 0)
         {
             FieldInfo[] fieldInfos = tType.GetFields();
             for (int i = 0; i < fieldInfos.Length; i++)
                 propertyInfos[i] = fieldInfos[i];
         }

         return propertyInfos;
     }

     static T ParseObject<T>(string line, Dictionary<int, FieldInfo> propertyInfos)
     {
         T obj = Activator.CreateInstance<T>();
         List<string> values = ParseLine(line);

         foreach (var item in propertyInfos)
         {
             if (item.Key >= values.Count) break;

             string _value = values[item.Key];
             try
             {
                 ParsePropertyValue(obj, item.Value, _value);
             }
             catch (Exception e)
             {
                 Debug.LogError(string.Format("ParseError: Column={0} Name={1} Want={2} Get={3} Line={4}",
                     item.Key + 1,
                     item.Value.Name,
                     item.Value.FieldType.Name,
                     _value, line));
                 Debug.LogError(e);
             }
         }
         return obj;
     }

     private const char _csvSeparator = ',';

     private static List<string> ParseLine(string line)
     {
         StringBuilder _columnBuilder = new StringBuilder();
         List<string> Fields = new List<string>();
         bool inColumn = false;  //是否是在一个列元素里
         bool inQuotes = false;  //是否需要转义
         bool isNotEnd = false;  //读取完毕未结束转义
         _columnBuilder.Remove(0, _columnBuilder.Length);

         // 遍历行中每一个字符
         for (int i = 0; i < line.Length; i++)
         {
             char character = line[i];

             // 当前不在列中
             if (!inColumn)
             {
                 // 如果当前字符是双引号 字段需要转义
                 inColumn = true;
                 if (character == '"')
                 {
                     inQuotes = true;
                     continue;
                 }
             }

             // 需要转义
             if (inQuotes)
             {
                 if ((i + 1) == line.Length)//这个字符已经结束了整行
                 {
                     if (character == '"') //正常转义结束,且该行已经结束
                     {
                         inQuotes = false;
                         continue;     //当前字符不用添加,跳出后直结束后会添加该元素
                     }
                     else //异常结束,转义未收尾
                     {
                         isNotEnd = true;
                     }
                 }
                 else if (character == '"' && line[i + 1] == _csvSeparator) //结束转义,且后面有可能还有数据
                 {
                     inQuotes = false;
                     inColumn = false;
                     i++; //跳过下一个字符
                 }
                 else if (character == '"' && line[i + 1] == '"') //双引号转义
                 {
                     i++; //跳过下一个字符
                     if (line.Length - 1 == i)//异常结束,转义未收尾
                     {
                         isNotEnd = true;
                     }
                 }
                 else if (character == '"') //双引号单独出现(这种情况实际上已经是格式错误,为了兼容可暂时不处理)
                 {
                     throw new Exception(string.Format("[{0}]:格式错误,错误的双引号转义 near [{1}] ", i, line));
                 }
                 //其他情况直接跳出,后面正常添加

             }
             else if (character == _csvSeparator)
                 inColumn = false;

             // If we are no longer in the column clear the builder and add the columns to the list
             if (!inColumn) //结束该元素时inColumn置为false,并且不处理当前字符,直接进行Add
             {
                 Fields.Add(_columnBuilder.ToString());
                 _columnBuilder.Remove(0, _columnBuilder.Length);
             }
             else // append the current column
                 _columnBuilder.Append(character);
         }

         // If we are still inside a column add a new one
         // (标准格式一行结尾不需要逗号结尾,而上面for是遇到逗号才添加的,为了兼容最后还要添加一次)
         if (inColumn)
         {
             Fields.Add(_columnBuilder.ToString());
         }
         //如果inColumn为false,说明已经添加,因为最后一个字符为分隔符,所以后面要加上一个空元素
         //另外一种情况是line为""空行,(空行也是一个空元素,一个逗号是2个空元素),正好inColumn为默认值false,在此处添加一空元素
         //当前导出规则不会出现
         else
         {
             Fields.Add("");
         }
         Debug.AssertFormat(!isNotEnd, "格式错误 转义字符结束 缺少 \" line: {0}", line);
         return Fields;
     }
}

完整工程

gitee:https://gitee.com/horooo/UnityExcelToCSV

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值