天天看點

Unity 3D 檔案加密上 檔案讀取 組成對應資料類

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