版本: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,一開始看的時候我也是一臉懵逼,不過這些方法确實很有用,有空的時候需要深入研究一下。