版本:unity 5.4.1 语言:C#
实战核心技术来到了第五章,这一章我准备分两篇来分析一下其中的代码,这一篇重点讲一下文件的读取,以及获取到数据后如何反射出对应类。作者的注释寥寥数语,所以很多代码要自己去分析、尝试。
首先我们来看一下我们要读取的数据:
//character.csv
id,name,maxHp,atk,def,spd
1,Ex,100,10,5,7
2,pop,200,5,5,5
3,tang,150,7,8,9
一些角色的数据,定义的比较简单,然后是对应的类,或者说是bean:
// 角色类
[System.Serializable] //序列化,能在Inspector中显示类成员变量,该参数不影响下面MyDataPath的配置
[MyDataPath("/Script/Encrypt/character.csv")] //配置读取路径
public class Character
{
public int id; //一些属性
public string name;
public int maxHp;
public int atk;
public int def;
public int spd;
public override string ToString()
{
return "id = " + id + ", name = " + name + ", maxHp = " + maxHp + ", atk = " + atk + ", def = " + def + ", spd = " + spd;
}
}
很好理解吧,不过可能大家没有看到过[MyDataPath(路径)]的写法,这是一种自定义的属性参数配置,具体说明如下:
/*
* AttributeUsage声明一个Attribute的使用范围与使用原则
* All 可以对任何应用程序元素应用属性
* Assembly 可以对程序集应用属性
* Class 可以对类应用属性
* Constructor 可以对构造函数应用属性
* Delegate 可以对委托应用属性
* Enum 可以对枚举应用属性
*
* 参数:
* AllowMultiple 为true,则返回特性可对单个实体应用多次
* Inherited 为false,则该特性不从特性化的派生类的类继承
*
* 该参数所定义的类会自动生成一个去掉Attribute的参数,即MyDataPath,
* 使用的时候以[MyDataPath(路径)]的形式就可以进行配置
*/
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class MyDataPathAttribute : Attribute
{
public string filePath { get; set; }
public MyDataPathAttribute(string _filePath)
{
filePath = _filePath;
}
}
接下来就是正式的代码了:
public class EncryptTest : MonoBehaviour {
// 游戏数据保存容器
static List<Character> characters = new List<Character>();
// Use this for initialization
void Start ()
{
ParserFromTextFile(characters);
foreach(var v in characters)
{
Debug.Log(v.ToString());
}
}
// 从文件中解析出文件,并加入List中
public static void ParserFromTextFile<T>(List<T> list, bool bRefResource = false)
{
// 获取路径
string file = ((MyDataPathAttribute)Attribute.GetCustomAttribute(typeof(T), typeof(MyDataPathAttribute))).filePath;
Debug.Log(file);
// 获取文件内容
string asset = null;
if (bRefResource)
{
// 读取文本资源
// 类似,可以以Resources.LoadAll(path, typeof(Texture2D))的方式读取Resources路径下的所有图片
//以下是Resources.Load读取的路径,csv和txt文件都是支持的,记得不要写后缀名,文件是放在Resources文件夹下的
//[MyDataPath("Script/Encrypt/character")]
asset = ((TextAsset)Resources.Load(file, typeof(TextAsset))).text;
}
else
{
// 使用C#的方法读取数据
asset = File.ReadAllText(Application.dataPath + file);
}
// 解析文本内容
StringReader reader = null;
try
{
bool isHeadLine = true;
string[] headLine = null;
string stext = string.Empty;
reader = new StringReader(asset);
// 每当读取到一行时,进行处理
while((stext = reader.ReadLine())!=null)
{
if (isHeadLine)
{
// 第一行组成头部
headLine = stext.Split(',');
isHeadLine = false;
}
else
{
// 余下的是数据
string[] data = stext.Split(',');
list.Add(CreateDataModule<T>(new List<string>(headLine), data));
}
}
}
catch(Exception e)
{
Debug.Log("file:" + file + ", message:" + e.Message);
}
finally
{
if (reader != null)
reader.Close();
}
}
// 运用反射,创建数据对应的类
static T CreateDataModule<T>(List<string> headLine, string[] data)
{
// 因为T是泛型,所以无法使用new的方式创建
T result = Activator.CreateInstance<T>();
// 运用反射获取所有的字段
FieldInfo[] fis = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach(FieldInfo fi in fis)
{
// 使用linq判断该字段是否存在
string column = headLine.Where(tempstr => tempstr == fi.Name).FirstOrDefault();
if(!string.IsNullOrEmpty(column))
{
// 存在的情况获取值
string baseValue = data[headLine.IndexOf(column)];
// 判断字段类型
object setValueObj = null;
Type setValueType = fi.FieldType;
if(setValueType.Equals(typeof(int)))
{
setValueObj = string.IsNullOrEmpty(baseValue.Trim()) ? 0 : Convert.ToInt32(baseValue);
}
else if(setValueType.Equals(typeof(string)))
{
setValueObj = baseValue;
}
else
{
Debug.LogError("暂时不支持该类型的转换");
}
// 赋值,相当于result调用fi方法赋予setValueObj的值
fi.SetValue(result, setValueObj);
}
}
return result;
}
}
运用到了反射和linq,一开始看的时候我也是一脸懵逼,不过这些方法确实很有用,有空的时候需要深入研究一下。