Unity表管理_读取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填写方式
- 使用方法(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