text:
using System.IO;

namespace ReadyGamerOne.Data
{
	/// <summary>
	/// 想要通过TxtManager
	/// </summary>
	public interface ITxtSerializable
	{
		/// <summary>
		/// 标志位
		/// </summary>
		string Sign { get; }

		/// <summary>
		/// 读取标志行
		/// </summary>
		/// <param name="initLine"></param>
		void SignInit(string initLine);

		/// <summary>
		/// 读取大括号以内的部分
		/// </summary>
		/// <param name="sr"></param>
		void LoadTxt(StreamReader sr);
	}
}


text:
fileFormatVersion: 2
guid: 94974ac1212fc554e834393640a8335d
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:

mime_type: text/plain


text:
fileFormatVersion: 2
guid: f3f54d23b1f44c545899331f6bb2753f
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:


text:
using System;

namespace ReadyGamerOne.Data
{
    /// <summary>
    /// 可以使用Csv数据加载的都可以实现此接口
    /// </summary>
    public interface IUseCsvData
    {

        CsvMgr CsvData { get; }

        /// <summary>
        /// Csv生成的Item的类型定义类
        /// </summary>
        Type DataType { get; }

        /// <summary>
        /// 加载数据
        /// </summary>
        /// <param name="data"></param>
        void LoadData(CsvMgr data);
    }
}


text:
fileFormatVersion: 2
guid: 39633e9954fe02a4ca00c39659989e0e
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:


text:
fileFormatVersion: 2
guid: 2cdff65c45d9f4244b71968941286ee5
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:


text:
fileFormatVersion: 2
guid: 8c1e87eac5064054896bb53239315257
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:


text:
fileFormatVersion: 2
guid: e5e55b3fef9c19f4a9fbc39055aab223
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:


text:
using System;
using System.Collections.Generic;
using System.IO;
using ReadyGamerOne.Const;
using UnityEngine;

namespace ReadyGamerOne.Data
{
    public static class TxtManager
    {
        public static char SplitChar => TxtSign.txtSplitChar;

        /// <summary>
        /// 记录下所有物品的工厂
        /// </summary>
        private static Dictionary<string, IDataFactory> factoryDic = new Dictionary<string, IDataFactory>();

        private static Stack<ITxtSerializable> handler = new Stack<ITxtSerializable>();

        /// <summary>
        /// 注册不同种类的工厂
        /// </summary>
        /// <param name="skillType"></param>
        /// <param name="factory"></param>
        public static void RegisterDataFactory<T>(string dataType)
            where T : class, ITxtSerializable, new()
        {
            if (!factoryDic.ContainsKey(dataType))
                factoryDic.Add(dataType, DataFactory<T>.Instance);
            else
                Debug.LogWarning("重复添加物品: " + dataType);
        }

        /// <summary>
        /// 读取有效行
        /// </summary>
        /// <param name="sr"></param>
        /// <returns></returns>
        public static string ReadUntilValue(StreamReader sr)
        {
            string line = null;
            do
            {
                line = sr.ReadLine();
                if (string.IsNullOrEmpty(line))
                    continue;
                if (line.StartsWith("//"))
                    continue;
                line = line.Trim();
                break;
            } while (true);

            return line;
        }

        /// <summary>
        /// 根据路径从文件中加载数据
        /// </summary>
        /// <param name="streamingAssertPath"></param>
        /// <param name="action"></param>
        /// <typeparam name="DataType"></typeparam>
        public static void LoadDataFromFile<DataType>(string streamingAssertPath, Action<DataType> action)
            where DataType : class, ITxtSerializable
        {
            streamingAssertPath = Application.streamingAssetsPath + "\\" + streamingAssertPath;
            var sr = new StreamReader(streamingAssertPath);
            do
            {
                var item = TxtManager.LoadData(sr) as DataType;
                if (item == null)
                {
                    Debug.LogWarning("item is null");
                    break;
                }

                if (action != null)
                    action(item);
            } while (true);

            sr.Close();
        }

        /// <summary>
        /// 从文件流中解析数据
        /// </summary>
        /// <param name="sr"></param>
        /// <returns></returns>
        public static object LoadData(StreamReader sr)
        {
            bool backet = false;
            ITxtSerializable data = null;
            do
            {
                string line = sr.ReadLine();
                if (line == null)
                {
//					Debug.Log("line is null, break");
                    break;
                }

                line = line.Trim();

                //注释写法   //注释
                if (line.StartsWith("//") || line == "")
                    continue;

                if (line.StartsWith("End"))
                    break;

                //解析文件

                if (line.StartsWith("{")) //开始大括号
                {
                    backet = true;
//					Debug.Log(handler.Peek().Sign + " {");
                    handler.Peek().LoadTxt(sr);
                }
                else if (line.StartsWith("}")) //结束大括号
                {
                    backet = false;
                    data = handler.Peek();
//					Debug.Log(data.Sign + " }");
                    handler.Pop();
                    return data;
                }
                else
                {
                    if (backet == false)
                    {
                        var sign = line.Split(SplitChar)[0];
                        data = factoryDic[sign].CreateData(line);
                        handler.Push(data);
                    }
                }
            } while (true);

            return data;
        }
    }
}


text:
using System;
using System.Collections;
using System.IO;
using System.Text;
using UnityEngine;

namespace ReadyGamerOne.Data
{
	public class CsvStreamReader
	{
		private  ArrayList  rowAL;       //行链表,CSV文件的每一行就是一个链
		private  string  fileName;       //文件名

		private  Encoding encoding;       //编码

		/// <summary>
		///
		/// </summary>
		/// <param name="fileName">文件名,包括文件路径</param>
		public CsvStreamReader(string fileName)
		{
			this.rowAL = new ArrayList();
			this.fileName = fileName;
			this.encoding = Encoding.Default;
			LoadCsvFile();
		}

		/// <summary>
		///
		/// </summary>
		/// <param name="fileName">文件名,包括文件路径</param>
		/// <param name="encoding">文件编码</param>
		public CsvStreamReader(string fileName,Encoding encoding)
		{
			this.rowAL = new ArrayList();
			this.fileName = fileName;
			this.encoding = encoding;
			LoadCsvFile();
		}

		/// <summary>
		/// 文件名,包括文件路径
		/// </summary>
		public string FileName
		{
			set
			{
				this.fileName = value;
				LoadCsvFile();
			}
		}

		/// <summary>
		/// 文件编码
		/// </summary>

		public Encoding FileEncoding
		{
			set
			{
				this.encoding = value;
			}
		}

		/// <summary>
		/// 获取行数
		/// </summary>
		public int RowCount
		{
			get
			{
				return this.rowAL.Count;
			}
		}

		/// <summary>
		/// 获取列数
		/// </summary>
		public int ColCount
		{
			get
			{
				int maxCol;

				maxCol = 0;
				for (int i = 0;i<this.rowAL.Count;i++)
				{
					ArrayList colAL = (ArrayList) this.rowAL[i];    //rowAL中的某个元素依然是ArrayList类型

					maxCol = (maxCol > colAL.Count)?maxCol:colAL.Count;     //取最大值
				}

				return maxCol;
			}
		}

		/// <summary>
		/// 获取某行某列的数据

		/// row:行,row = 1代表第一行

		/// col:列,col = 1代表第一列
		/// </summary>
		public string this[int row,int col]
		{
			get
			{
				//数据有效性验证

				CheckRowValid(row);
				CheckColValid(col);
				ArrayList colAL = (ArrayList) this.rowAL[row-1];

				//如果请求列数据大于当前行的列时,返回空值

				if (colAL.Count < col)
				{
					return "";
				}

				return colAL[col-1].ToString();
			}
		}

		/// <summary>
		/// 根据最小行，最大行，最小列，最大列，来生成一个DataTable类型的数据

		/// 行等于1代表第一行

		/// <summary>
		/// 检查行数是否是有效的

		/// </summary>
		/// <param name="col"></param>
		private void CheckRowValid(int row)
		{
			if (row <= 0)
			{
				throw new Exception("行数不能小于0");
			}
			if (row > RowCount)
			{
				throw new Exception("没有当前行的数据");
			}
		}

		/// <summary>
		/// 检查最大行数是否是有效的

		/// </summary>
		/// <param name="col"></param>
		private void CheckMaxRowValid(int maxRow)
		{
			if (maxRow <= 0 && maxRow != -1)
			{
				throw new Exception("行数不能等于0或小于-1");
			}
			if (maxRow > RowCount)
			{
				throw new Exception("没有当前行的数据");
			}
		}

		/// <summary>
		/// 检查列数是否是有效的

		/// </summary>
		/// <param name="col"></param>
		private void CheckColValid(int col)
		{
			if (col <= 0)
			{
				throw new Exception("列数不能小于0");
			}
			if (col > ColCount)
			{
				throw new Exception("没有当前列的数据");
			}
		}

		/// <summary>
		/// 检查检查最大列数是否是有效的

		/// </summary>
		/// <param name="col"></param>
		private void CheckMaxColValid(int maxCol)
		{
			if (maxCol <= 0 && maxCol != -1)
			{
				throw new Exception("列数不能等于0或小于-1");
			}
			if (maxCol > ColCount)
			{
				throw new Exception("没有当前列的数据");
			}
		}

		/// <summary>
		/// 载入CSV文件
		/// </summary>
		private void LoadCsvFile()
		{
			//对数据的有效性进行验证

			if (this.fileName == null)
			{
				throw new Exception("请指定要载入的CSV文件名");
			}
			else if (!File.Exists(this.fileName))
			{
				throw new Exception("指定的CSV文件不存在，fileName:"+fileName);
			}
			else
			{
			}
			if (this.encoding == null)
			{
				this.encoding = Encoding.Default;
			}

			StreamReader sr = new StreamReader(this.fileName,this.encoding);
			string   csvDataLine;

			csvDataLine = "";
			while (true)
			{
				string fileDataLine;

				fileDataLine = sr.ReadLine();
				if (fileDataLine == null)
				{
					break;
				}
				if (csvDataLine == "")
				{
					csvDataLine = fileDataLine;//GetDeleteQuotaDataLine(fileDataLine);
				}
				else
				{
					csvDataLine += "/r/n" + fileDataLine;//GetDeleteQuotaDataLine(fileDataLine);
				}
				//如果包含偶数个引号，说明该行数据中出现回车符或包含逗号
				if (!IfOddQuota(csvDataLine))
				{
					AddNewDataLine(csvDataLine);
					csvDataLine = "";
				}
			}

			sr.Close();
			//数据行出现奇数个引号
			if (csvDataLine.Length > 0)
			{
				throw new Exception("CSV文件的格式有错误");
			}
		}

		/// <summary>
		/// 获取两个连续引号变成单个引号的数据行
		/// </summary>
		/// <param name="fileDataLine">文件数据行</param>
		/// <returns></returns>
		private string GetDeleteQuotaDataLine(string fileDataLine)
		{
			return fileDataLine.Replace("\"\"","\"");
		}

		/// <summary>
		/// 判断字符串是否包含奇数个引号
		/// </summary>
		/// <param name="dataLine">数据行</param>
		/// <returns>为奇数时，返回为真；否则返回为假</returns>
		private bool IfOddQuota(string dataLine)
		{
			int  quotaCount;
			bool oddQuota;

			quotaCount = 0;
			for (int i = 0;i < dataLine.Length;i++)
			{
				if (dataLine[i] == '\"')
				{
					quotaCount++;
				}
			}

			oddQuota = false;
			if (quotaCount % 2 == 1)
			{
				oddQuota = true;
			}

			return oddQuota;
		}

		/// <summary>
		/// 判断是否以奇数个引号开始

		/// </summary>
		/// <param name="dataCell"></param>
		/// <returns></returns>
		private bool IfOddStartQuota(string dataCell)
		{
			int  quotaCount;
			bool oddQuota;

			quotaCount = 0;
			for (int i = 0;i < dataCell.Length;i++)
			{
				if (dataCell[i] == '\"')
				{
					quotaCount++;
				}
				else
				{
					break;
				}
			}

			oddQuota = false;
			if (quotaCount % 2 == 1)
			{
				oddQuota = true;
			}

			return oddQuota;
		}

		/// <summary>
		/// 判断是否以奇数个引号结尾
		/// </summary>
		/// <param name="dataCell"></param>
		/// <returns></returns>
		private bool IfOddEndQuota(string dataCell)
		{
			int  quotaCount;
			bool oddQuota;

			quotaCount = 0;
			for (int i = dataCell.Length -1;i >= 0;i--)
			{
				if (dataCell[i] == '\"')
				{
					quotaCount++;
				}
				else
				{
					break;
				}
			}

			oddQuota = false;
			if (quotaCount % 2 == 1)
			{
				oddQuota = true;
			}

			return oddQuota;
		}

		/// <summary>
		/// 加入新的数据行

		/// </summary>
		/// <param name="newDataLine">新的数据行</param>
		private void AddNewDataLine(string newDataLine)
		{
			Debug.Log("NewLine:" + newDataLine);

			//return;

			ArrayList colAL = new ArrayList();
			string[] dataArray = newDataLine.Split(',');
			bool  oddStartQuota;       //是否以奇数个引号开始

			string      cellData;

			oddStartQuota = false;
			cellData = "";
			for (int i = 0 ;i < dataArray.Length;i++)
			{
				if (oddStartQuota)
				{
					//因为前面用逗号分割,所以要加上逗号
					cellData += "," + dataArray[i];
					//是否以奇数个引号结尾
					if (IfOddEndQuota(dataArray[i]))
					{
						colAL.Add(GetHandleData(cellData));
						oddStartQuota = false;
						continue;
					}
				}
				else
				{
					//是否以奇数个引号开始

					if (IfOddStartQuota(dataArray[i]))
					{
						//是否以奇数个引号结尾,不能是一个双引号,并且不是奇数个引号

						if (IfOddEndQuota(dataArray[i]) && dataArray[i].Length > 2 && !IfOddQuota(dataArray[i]))
						{
							colAL.Add(GetHandleData(dataArray[i]));
							oddStartQuota = false;
							continue;
						}
						else
						{

							oddStartQuota = true;
							cellData = dataArray[i];
							continue;
						}
					}
					else
					{
						colAL.Add(GetHandleData(dataArray[i]));
					}
				}
			}

			if (oddStartQuota)
			{
				throw new Exception("数据格式有问题");
			}
			this.rowAL.Add(colAL);
		}

		/// <summary>
		/// 去掉格子的首尾引号，把双引号变成单引号

		/// </summary>
		/// <param name="fileCellData"></param>
		/// <returns></returns>
		private string GetHandleData(string fileCellData)
		{
			if (fileCellData == "")
			{
				return "";
			}
			if (IfOddStartQuota(fileCellData))
			{
				if (IfOddEndQuota(fileCellData))
				{
					return fileCellData.Substring(1,fileCellData.Length-2).Replace("\"\"","\""); //去掉首尾引号，然后把双引号变成单引号
				}
				else
				{
					throw new Exception("数据引号无法匹配" + fileCellData);
				}
			}
			else
			{
				//考虑形如""    """"      """"""
				if (fileCellData.Length >2 && fileCellData[0] == '\"')
				{
					fileCellData = fileCellData.Substring(1,fileCellData.Length-2).Replace("\"\"","\""); //去掉首尾引号，然后把双引号变成单引号
				}
			}

			return fileCellData;
		}
	}
}


text:
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;

namespace ReadyGamerOne.Data
{
    public static class BitManager
    {
        public static T LoadData<T>(string fullPath) where T : class
        {
            StreamReader r = File.OpenText(fullPath);//_FileLocation是unity3D当前project的路径名，_FileName是xml的文件名。定义为成员变量了
                                                     //当然，你也可以在前面先判断下要读取的xml文件是否存在
            String _data = r.ReadLine();
            //			Debug.Log(_data);
            var myData = DeserializeObject<T>(Encoding.UTF8.GetBytes(_data));//myData是上面自定义的xml存取过程中要使用的数据结构UserData
            r.Close();
            return myData;
        }

        public static void SaveData<T>(T data, string fullPath) where T : class
        {
            StreamWriter writer;
            FileInfo t = new FileInfo(fullPath);
            t.Delete();
            writer = t.CreateText();
            var _data = SerializeObject(data); //序列化这组数据
            writer.WriteLine(Encoding.UTF8.GetString(_data)); //写入xml
                                                              //			writer.WriteLine(_data); //写入xml
            writer.Close();
        }

        public static byte[] SerializeObject<T>(T pObject) where T : class
        {
            MemoryStream memoryStream = new MemoryStream();
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(memoryStream, pObject);
            byte[] data = memoryStream.ToArray();
            memoryStream.Close();
            return data;
        }

        // Here we deserialize it back into its original form
        public static T DeserializeObject<T>(byte[] aSerializeData) where T : class
        {
            MemoryStream stream = new MemoryStream(aSerializeData);
            var obj = default(T);
            BinaryFormatter bf = new BinaryFormatter();
            obj = (T)bf.Deserialize(stream);
            stream.Close();

            return obj;
        }
    }
}


text:
fileFormatVersion: 2
guid: 234555e5e73bdc54687d01a20a9f0564
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:


text:
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace ReadyGamerOne.Data
{
    /// <summary>
    /// Xml序列化管理类
    /// </summary>
    public static class XmlManager
    {

        public static T LoadData<T>(string fullPath) where T : class
        {
            StreamReader r = File.OpenText(fullPath);//_FileLocation是unity3D当前project的路径名，_FileName是xml的文件名。定义为成员变量了
                                                     //当然，你也可以在前面先判断下要读取的xml文件是否存在
            var _data = r.ReadLine();
            //			Debug.Log(_data);
            var myData = DeserializeObject<T>(Encoding.UTF8.GetBytes(_data));//myData是上面自定义的xml存取过程中要使用的数据结构UserData
            r.Close();
            return myData;
        }

        public static void SaveData<T>(T data, string fullPath) where T : class
        {
            StreamWriter writer;
            FileInfo t = new FileInfo(fullPath);
            t.Delete();
            writer = t.CreateText();
            var _data = SerializeObject(data); //序列化这组数据
            writer.WriteLine(Encoding.UTF8.GetString(_data)); //写入xml
            writer.Close();
        }

        // Here we serialize our UserData object of myData
        public static byte[] SerializeObject<T>(T pObject) where T : class
        {
            MemoryStream memoryStream = new MemoryStream();
            XmlSerializer xs = new XmlSerializer(typeof(T));
            XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
            xs.Serialize(xmlTextWriter, pObject);
            memoryStream = (MemoryStream)xmlTextWriter.BaseStream; // (MemoryStream)
            return memoryStream.ToArray();
        }

        // Here we deserialize it back into its original form
        public static T DeserializeObject<T>(byte[] pXmlizedString) where T : class
        {
            XmlSerializer xs = new XmlSerializer(typeof(T));
            MemoryStream memoryStream = new MemoryStream((pXmlizedString));
            XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
            return xs.Deserialize(memoryStream) as T;
        }

    }
}


text:
fileFormatVersion: 2
guid: 9ab41fa8502aaaf4dbf934d7f013c03d
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:


text:
using System.Collections;
using System.IO;
using UnityEngine;

namespace ReadyGamerOne.Data
{
	public class CsvReaderByString{

		private  ArrayList  rowAL;        //行链表,CSV文件的每一行就是一个链

		public CsvReaderByString(string parseString)
		{
			this.rowAL = new ArrayList();
			StringReader sr=new StringReader(parseString);
			string csvDataLine="";

			while(true)
			{
				string fileDataLine;

				fileDataLine = sr.ReadLine();
				if (fileDataLine == null)
				{
					break;
				}
				if (csvDataLine == "")
				{
					csvDataLine = fileDataLine;//GetDeleteQuotaDataLine(fileDataLine);
				}
				else
				{
					csvDataLine += "/r/n" + fileDataLine;//GetDeleteQuotaDataLine(fileDataLine);
				}
				//如果包含偶数个引号，说明该行数据中出现回车符或包含逗号
				if (!IfOddQuota(csvDataLine))
				{
					AddNewDataLine(csvDataLine);
					csvDataLine = "";
				}
			}
			sr.Close();
			//数据行出现奇数个引号
			if (csvDataLine.Length > 0)
			{
				throw new UnityException("CSV文件的格式有错误");
			}
		}

		/// <summary>
		/// 获取行数
		/// </summary>
		public int RowCount
		{
			get
			{
				return this.rowAL.Count;
			}
		}

		/// <summary>
		/// 获取列数
		/// </summary>
		public int ColCount
		{
			get
			{
				int maxCol;

				maxCol = 0;
				for (int i = 0;i<this.rowAL.Count;i++)
				{
					ArrayList colAL = (ArrayList) this.rowAL[i];

					maxCol = (maxCol > colAL.Count)?maxCol:colAL.Count;
				}

				return maxCol;
			}
		}

		/// <summary>
		/// 获取某行某列的数据

		/// row:行,row = 1代表第一行

		/// col:列,col = 1代表第一列
		/// </summary>
		public string this[int row,int col]
		{
			get
			{
				//数据有效性验证

				CheckRowValid(row);
				CheckColValid(col);
				ArrayList colAL = (ArrayList) this.rowAL[row-1];

				//如果请求列数据大于当前行的列时,返回空值

				if (colAL.Count < col)
				{
					return "";
				}

				return colAL[col-1].ToString();
			}
		}

		/// <summary>
		/// 检查行数是否是有效的

		/// </summary>
		/// <param name="col"></param>
		private void CheckRowValid(int row)
		{
			if (row <= 0)
			{
				throw new UnityException("行数不能小于0");
			}
			if (row > RowCount)
			{
				throw new UnityException("没有当前行的数据");
			}
		}

		/// <summary>
		/// 检查最大行数是否是有效的

		/// </summary>
		/// <param name="col"></param>
		private void CheckMaxRowValid(int maxRow)
		{
			if (maxRow <= 0 && maxRow != -1)
			{
				throw new UnityException("行数不能等于0或小于-1");
			}
			if (maxRow > RowCount)
			{
				throw new UnityException("没有当前行的数据");
			}
		}

		/// <summary>
		/// 检查列数是否是有效的

		/// </summary>
		/// <param name="col"></param>
		private void CheckColValid(int col)
		{
			if (col <= 0)
			{
				throw new UnityException("列数不能小于0");
			}
			if (col > ColCount)
			{
				throw new UnityException("没有当前列的数据");
			}
		}

		/// <summary>
		/// 检查检查最大列数是否是有效的

		/// </summary>
		/// <param name="col"></param>
		private void CheckMaxColValid(int maxCol)
		{
			if (maxCol <= 0 && maxCol != -1)
			{
				throw new UnityException("列数不能等于0或小于-1");
			}
			if (maxCol > ColCount)
			{
				throw new UnityException("没有当前列的数据");
			}
		}

		/// <summary>
		/// 判断字符串是否包含奇数个引号
		/// </summary>
		/// <param name="dataLine">数据行</param>
		/// <returns>为奇数时，返回为真；否则返回为假</returns>
		private bool IfOddQuota(string dataLine)
		{
			int  quotaCount;
			bool oddQuota;

			quotaCount = 0;
			for (int i = 0;i < dataLine.Length;i++)
			{
				if (dataLine[i] == '\"')
				{
					quotaCount++;
				}
			}

			oddQuota = false;
			if (quotaCount % 2 == 1)
			{
				oddQuota = true;
			}

			return oddQuota;
		}

		/// <summary>
		/// 加入新的数据行

		/// </summary>
		/// <param name="newDataLine">新的数据行</param>
		private void AddNewDataLine(string newDataLine)
		{
			//Debug.Log("NewLine:" + newDataLine);

			//return;

			ArrayList colAL = new ArrayList();
			string[] dataArray = newDataLine.Split(',');
			bool  oddStartQuota;       //是否以奇数个引号开始

			string      cellData;

			oddStartQuota = false;
			cellData = "";
			for (int i = 0 ;i < dataArray.Length;i++)
			{
				if (oddStartQuota)
				{
					//因为前面用逗号分割,所以要加上逗号
					cellData += "," + dataArray[i];
					//是否以奇数个引号结尾
					if (IfOddEndQuota(dataArray[i]))
					{
						colAL.Add(GetHandleData(cellData));
						oddStartQuota = false;
						continue;
					}
				}
				else
				{
					//是否以奇数个引号开始

					if (IfOddStartQuota(dataArray[i]))
					{
						//是否以奇数个引号结尾,不能是一个双引号,并且不是奇数个引号

						if (IfOddEndQuota(dataArray[i]) && dataArray[i].Length > 2 && !IfOddQuota(dataArray[i]))
						{
							colAL.Add(GetHandleData(dataArray[i]));
							oddStartQuota = false;
							continue;
						}
						else
						{

							oddStartQuota = true;
							cellData = dataArray[i];
							continue;
						}
					}
					else
					{
						colAL.Add(GetHandleData(dataArray[i]));
					}
				}
			}
			if (oddStartQuota)
			{
				throw new UnityException("数据格式有问题");
			}
			this.rowAL.Add(colAL);
		}

		/// <summary>
		/// 判断是否以奇数个引号结尾
		/// </summary>
		/// <param name="dataCell"></param>
		/// <returns></returns>
		private bool IfOddEndQuota(string dataCell)
		{
			int  quotaCount;
			bool oddQuota;

			quotaCount = 0;
			for (int i = dataCell.Length -1;i >= 0;i--)
			{
				if (dataCell[i] == '\"')
				{
					quotaCount++;
				}
				else
				{
					break;
				}
			}

			oddQuota = false;
			if (quotaCount % 2 == 1)
			{
				oddQuota = true;
			}

			return oddQuota;
		}

		/// <summary>
		/// 去掉格子的首尾引号，把双引号变成单引号

		/// </summary>
		/// <param name="fileCellData"></param>
		/// <returns></returns>
		private string GetHandleData(string fileCellData)
		{
			if (fileCellData == "")
			{
				return "";
			}
			if (IfOddStartQuota(fileCellData))
			{
				if (IfOddEndQuota(fileCellData))
				{
					return fileCellData.Substring(1,fileCellData.Length-2).Replace("\"\"","\""); //去掉首尾引号，然后把双引号变成单引号
				}
				else
				{
					throw new UnityException("数据引号无法匹配" + fileCellData);
				}
			}
			else
			{
				//考虑形如""    """"      """"""
				if (fileCellData.Length >2 && fileCellData[0] == '\"')
				{
					fileCellData = fileCellData.Substring(1,fileCellData.Length-2).Replace("\"\"","\""); //去掉首尾引号，然后把双引号变成单引号
				}
			}

			return fileCellData;
		}

		/// <summary>
		/// 判断是否以奇数个引号开始

		/// </summary>
		/// <param name="dataCell"></param>
		/// <returns></returns>
		private bool IfOddStartQuota(string dataCell)
		{
			int  quotaCount;
			bool oddQuota;

			quotaCount = 0;
			for (int i = 0;i < dataCell.Length;i++)
			{
				if (dataCell[i] == '\"')
				{
					quotaCount++;
				}
				else
				{
					break;
				}
			}

			oddQuota = false;
			if (quotaCount % 2 == 1)
			{
				oddQuota = true;
			}

			return oddQuota;
		}
	}
}


text:
namespace ReadyGamerOne.Data
{
	public interface IDataFactory
	{
		ITxtSerializable CreateData(string initLine);
	}
}


text:
using System;
using System.Collections;
using System.Collections.Generic;
using ReadyGamerOne.Common;
using UnityEngine;
using UnityEngine.Assertions;
using Object = UnityEngine.Object;

namespace ReadyGamerOne.MemorySystem
{
    public class ResourcesResourceLoader:
        Singleton<ResourcesResourceLoader>,
        IResourceLoader
    {
        #region Fields

        private Dictionary<string, Object> sourceObjectDic;
        private IAssetConstUtil _assetConstUtil;

        #endregion

        #region Properties

        private Dictionary<string, Object> SourceObjects
        {
            get
            {
                if (sourceObjectDic == null)
                    sourceObjectDic = new Dictionary<string, Object>();

                return sourceObjectDic;
            }
            set { sourceObjectDic = value; }
        }

        #endregion

        #region Private

        /// <summary>
        /// 从缓存中获取资源，否则采用默认方式初始化
        /// </summary>
        /// <param name="key"></param>
        /// <param name="defaultGetMethod"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        private T GetSourceFromCache<T>(string key, Func<string, T> defaultGetMethod = null)
            where T : Object
        {
            if (SourceObjects.ContainsKey(key))
            {
                if (SourceObjects[key] == null)
                    throw new Exception("资源已经释放，但字典中引用亦然保留");

                var ans = SourceObjects[key] as T;
                if (ans == null)
                    Debug.LogWarning("资源引用存在，但类型转化失败，小心bug");

                return ans;
            }

            if (null == defaultGetMethod)
            {
                Debug.LogWarning("defaultGetMethod is null");
                return null;
            }

            var source = defaultGetMethod(key);
            if (source == null)
            {
                Debug.LogError("资源加载错误，错误路径：" + key);
                return null;
            }

            SourceObjects.Add(key, source);
            return source;
        }

        #endregion

        #region IResourceLoader

        public void Init(IHotUpdatePath pather, IOriginAssetBundleUtil originConstData,IAssetConstUtil assetConstUtil)
        {
//            Debug.Log(assetConstUtil.GetType().FullName);
            Assert.IsNotNull(assetConstUtil);
            Assert.IsNotNull(assetConstUtil.NameToPath);
            this._assetConstUtil = assetConstUtil;
        }

        public T GetAsset<T>(string objName, string bundleKey = null) where T : Object
        {
            Assert.IsFalse(string.IsNullOrEmpty(objName));
            if (_assetConstUtil == null)
                throw new Exception("_assetConstUtil为空，没有初始化？？");
            if (_assetConstUtil.NameToPath == null)
            {
                throw new Exception("字典为空？？？");
            }
            if (!_assetConstUtil.NameToPath.ContainsKey(objName))
            {
                throw new Exception("没有这个Key ："+objName);
            }
            return GetSourceFromCache(_assetConstUtil.NameToPath[objName], Resources.Load<T>);
        }

        public IEnumerator GetAssetAsync<T>(string objName, string bundleKey = null, Action<T> onGetObj = null) where T : Object
        {
            Assert.IsNotNull(onGetObj);

            //如果缓存中有，就直接使用缓存
            var temp = GetSourceFromCache<T>(objName);
            if (null != temp)
            {
                onGetObj(temp);
                yield break;
            }

            if (!_assetConstUtil.NameToPath.ContainsKey(objName))
            {
                throw new Exception("没有这个objectKey ："+objName);
            }

            var request = Resources.LoadAsync<T>(_assetConstUtil.NameToPath[objName]);
            yield return request;

            var asset = (T) request.asset;

            if (!asset)
                throw new Exception("获取资源为空，objName: " + objName);

            SourceObjects.Add(objName, asset);
            onGetObj(asset);
        }

        public void ClearCache()
        {
            SourceObjects.Clear();
        }

        #endregion
    }
}


text:
fileFormatVersion: 2
guid: 1aa81d49411e41245a5d0cdc6b94e747
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:


text:
fileFormatVersion: 2
guid: 6dab89c7742e4cf2b1910ccb3f043010
timeCreated: 1578973998


text:
fileFormatVersion: 2
guid: 1c66d8b021b746978d3d50a99e857551
timeCreated: 1578468359


text:
using System;
using System.Collections;

namespace ReadyGamerOne.MemorySystem
{
    public interface IResourceLoader
    {
        void Init(IHotUpdatePath pather, IOriginAssetBundleUtil originConstData,IAssetConstUtil assetConstUtil);
        T GetAsset<T>(string objKey, string bundleName = null)
            where T : UnityEngine.Object;
        IEnumerator GetAssetAsync<T>(string objName, string bundleKey = null, Action<T> onGetObj = null)
            where T:UnityEngine.Object;

        void ClearCache();
    }
}


text:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ReadyGamerOne.Const;
using ReadyGamerOne.Utility;
using UnityEngine;
using UnityEngine.Assertions;
using FileUtil = ReadyGamerOne.Utility.FileUtil;
using Object = UnityEngine.Object;
#if UNITY_EDITOR
using System.IO;
using ReadyGamerOne.Global;
using UnityEditor;

#endif

namespace ReadyGamerOne.MemorySystem
{
    public enum ResourceManagerType
         {
             Resource,
             AssetBundle
         }

    /// <summary>
    /// 这个类提供关于内存的优化和管理
    /// 1、所有资源只会从Resources目录加载一次，再取的时候会从这个类的字典中取，尤其是一些预制体，经常频繁加载，使用这个类封装的Instantiate方法可以很好地解决这个问题
    /// 2、提供一些释放资源的接口
    /// 3、以后会提供关于AssetBubble的方法和接口
    /// 4、提供从Resources目录运行时加载一整个目录资源的接口，比如，加载某个文件夹下所有图片，音频
    /// </summary>
    public class ResourceMgr
#if UNITY_EDITOR
        : IEditorTools
#endif
    {
        #region Fields

        private static IResourceLoader _resourceLoader;
        private static IAssetConstUtil _assetConstUtil;

        public static void Init(IResourceLoader resourceLoader, IHotUpdatePath pather,
            IOriginAssetBundleUtil originConstData, IAssetConstUtil assetConstUtil)
        {
            Assert.IsNotNull(resourceLoader);
            Assert.IsNotNull(assetConstUtil);
            _assetConstUtil = assetConstUtil;
            _resourceLoader = resourceLoader;
//            Debug.Log("初始化");
            _resourceLoader.Init(pather, originConstData, assetConstUtil);
        }

        #endregion

        /// <summary>
        /// 根据objKey获取路径
        /// </summary>
        /// <param name="objKey"></param>
        /// <returns></returns>
        public string NameToPath(string objKey)
        {
            string path = null;
            return !_assetConstUtil.NameToPath.TryGetValue(objKey, out path) ? null : path;
        }

        /// <summary>
        /// 显示调试信息
        /// </summary>
        public static void ShowDebugInfo()
        {
            var abl = _resourceLoader as AssetBundleResourceLoader;
            abl?.ShowDebugInfo();
        }

        public static T GetAsset<T>(string objKey, string bundleName = null)
            where T : UnityEngine.Object
        {
            return _resourceLoader.GetAsset<T>(objKey, bundleName);
        }

        public static IEnumerator GetAssetAsync<T>(string objName, string bundleKey = null, Action<T> onGetObj = null)
            where T : UnityEngine.Object
        {
            yield return _resourceLoader.GetAssetAsync<T>(objName, bundleKey, onGetObj);
        }

        /// <summary>
        /// 异步从本地读取AssetBundle
        /// 条件：1、AB包没有做过任何加密
        ///       2、AB包存储在本地
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="crc"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        public static IEnumerator LoadAssetBundleFromLocalAsync(string filePath, uint crc = 0u, ulong offset = 0ul)
        {
            yield return AssetBundle.LoadFromFileAsync(filePath, crc, offset);
        }

        /// <summary>
        /// 同步从本地读取AssetBundle
        /// 条件：1、AB包没有做过任何加密
        ///       2、AB包存储在本地
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="crc"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        public static AssetBundle LoadAssetBundleFromLocal(string filePath, uint crc = 0u, ulong offset = 0ul)
        {
            return AssetBundle.LoadFromFile(filePath, crc, offset);
        }

        /// <summary>
        /// 释放游离资源
        /// </summary>
        public static void ReleaseUnusedAssets()
        {
            _resourceLoader.ClearCache();
            Resources.UnloadUnusedAssets();
        }

        /// <summary>
        /// 根据路径实例化对象
        /// </summary>
        /// <param name="path"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static T Instantiate<T>(string objName) where T : Object
        {
            return Object.Instantiate(_resourceLoader.GetAsset<T>(objName, OriginBundleKey.Self));
        }

        #region 实例化GameObject

        public static GameObject InstantiateGameObject(string objName, Transform parent = null)
        {
            var source = _resourceLoader.GetAsset<GameObject>(objName);
            Assert.IsTrue(source);
            return Object.Instantiate(source, parent);
        }

        public static GameObject InstantiateGameObject(string objName, Vector3 worldPos, Quaternion quaternion,
            Transform parent = null)
        {
            Assert.IsNotNull(_resourceLoader);
            var source = _resourceLoader.GetAsset<GameObject>(objName);
            Assert.IsTrue(source);
            return Object.Instantiate(source, worldPos, quaternion, parent);
        }

        public static GameObject InstantiateGameObject(string objName, Vector3 worldPos, Transform parent = null)
        {
            var source = _resourceLoader.GetAsset<GameObject>(objName);
            Assert.IsTrue(source);
            return Object.Instantiate(source, worldPos, Quaternion.identity, parent);
        }

        #endregion

        /// <summary>
        /// 从Resources目录中动态加载指定目录下所有内容
        /// </summary>
        /// <param name="nameClassType">目录下所有资源的名字要作为一个静态类的public static 成员，这里传递 这个静态类的Type</param>
        /// <param name="dirPath">从Resources开始的根目录，比如“Audio/"</param>
        /// <param name="onLoadAsset">加载资源时调用的委托，不能为空</param>
        /// <typeparam name="TAssetType">加载资源的类型</typeparam>
        /// <exception cref="Exception">委托为空会抛异常</exception>
        public static void LoadAssetFromResourceDir<TAssetType>(Type nameClassType, string dirPath = "",
            Action<string, TAssetType> onLoadAsset = null)
            where TAssetType : Object
        {
            var infoList = nameClassType.GetFields(BindingFlags.Static | BindingFlags.Public);
            foreach (var fieldInfo in infoList)
            {
                string assetName = fieldInfo.GetValue(null) as string;

                var res = _resourceLoader.GetAsset<TAssetType>(dirPath + assetName);

                if (onLoadAsset == null)
                    throw new Exception("onLoadAsset为 null, 那你加载资源干啥？？ ");

                onLoadAsset.Invoke(assetName, res);
            }
        }

        #region IEditorTools

#if UNITY_EDITOR
#pragma warning disable 414
        static string Title = "资源管理";
#pragma warning restore 414

        private static ResourceManagerType _resourceManagerType = ResourceManagerType.Resource;

        #region AssetBundle_Auto_Generate

        private static string assetToBundleDir = "未设置";
        private static string outputDir = "未设置";
        private static BuildAssetBundleOptions assetBundleOptions = 0;
        private static BuildTarget buildTarget = BuildTarget.StandaloneWindows;
        private static bool clearOutputDir = false;

        private static bool createPathDataClass = false;

        private static bool useForRuntime = true;

        private static string streamingAbName = "self";
        private static bool useWeb = false;
        private static List<string> assetBundleNames = new List<string>();

        #endregion

        #region Resources_Auto_Generate

        private static List<string> autoClassName = new List<string>();
        private static Dictionary<string, string> otherResPathDic = new Dictionary<string, string>();
        private static Dictionary<string, string> otherResFileNameDic = new Dictionary<string, string>();

        private static Dictionary<string, string> allResPathDic = new Dictionary<string, string>();
        private static Dictionary<string, string> allResFileNameDic = new Dictionary<string, string>();

        private static bool createPathFile = false;
        #endregion

        static void OnToolsGUI(string rootNs, string viewNs, string constNs, string dataNs, string autoDir,
            string scriptDir)
        {

            EditorGUILayout.Space();
            _resourceManagerType = (ResourceManagerType) EditorGUILayout.EnumPopup("资源加载类型", _resourceManagerType);

            switch (_resourceManagerType)
            {
                #region ResourceManagerType.Resource

                case ResourceManagerType.Resource:

                    EditorGUILayout.LabelField("自动常量文件生成目录",
                        Application.dataPath + "/" + rootNs + "/" + constNs + "/" + autoDir);
                    EditorGUILayout.LabelField("常量工具类生成目录",
                        Application.dataPath + "/" + rootNs + "/Utility/" + autoDir + "/ConstUtil.cs");
                    EditorGUILayout.Space();

                    createPathFile = EditorGUILayout.Toggle("是否生成资源路径文件", createPathFile);
                    EditorGUILayout.Space();

                    if (GUILayout.Button("生成常量"))
                    {
                        GenerateResourcesConst(rootNs,constNs,autoDir);
                    }

                    break;

                #endregion

                #region ResourceManagerType.AssetBundle

                case ResourceManagerType.AssetBundle:

                    var newestVersion = PlayerPrefs.GetString(VersionDefine.PrefKey_LocalVersion, "0.0");

                    if (GUILayout.Button("显示本地版本"))
                    {
                        var versionData = PlayerPrefs.GetString(newestVersion);
                        Debug.Log("本地版本号：" + newestVersion + "\n版本信息：" + versionData);
                    }

                    EditorGUI.BeginChangeCheck();
                    newestVersion = EditorGUILayout.DelayedTextField("本地版本", newestVersion);
                    if (EditorGUI.EndChangeCheck())
                    {
                        PlayerPrefs.SetString(VersionDefine.PrefKey_LocalVersion, newestVersion);
                    }

                    EditorGUILayout.Space();
                    EditorGUILayout.LabelField("打包目录", assetToBundleDir);
                    if (GUILayout.Button("设置要打包的目录"))
                    {
                        assetToBundleDir = EditorUtility.OpenFolderPanel("选择需要打包的目录", Application.dataPath, "");
                    }

                    EditorGUILayout.Space();
                    EditorGUILayout.LabelField("输出目录", outputDir);
                    if (GUILayout.Button("设置输出目录"))
                    {
                        outputDir = EditorUtility.OpenFolderPanel("选择输出目录", Application.dataPath, "");
                    }

                    EditorGUILayout.Space();
                    assetBundleOptions =
                        (BuildAssetBundleOptions) EditorGUILayout.EnumFlagsField("打包选项", assetBundleOptions);

                    buildTarget = (BuildTarget) EditorGUILayout.EnumPopup("目标平台", buildTarget);
                    clearOutputDir = EditorGUILayout.Toggle("清空生成目录", clearOutputDir);
                    EditorGUILayout.Space();

                    useForRuntime = EditorGUILayout.Foldout(useForRuntime, "使用用于生成运行时直接使用的AB包");
                    if (useForRuntime)
                    {
                        EditorGUILayout.LabelField("生成HotUpdatePath.cs路径",
                            Application.dataPath + "/" + rootNs + "/" + constNs + "/" + autoDir + "/HotUpdatePath.cs");
                        useWeb = EditorGUILayout.Toggle("是否使用网络", useWeb);
                        EditorGUILayout.LabelField("游戏自身AB包名字", streamingAbName);

                        EditorGUILayout.Space();
                        if (GUILayout.Button("重新生成常量类【会覆盖】"))
                        {
                            if (!outputDir.Contains(Application.streamingAssetsPath))
                            {
                                Debug.LogError("运行时使用的AB包必须在StreamingAssets目录下");
                                return;
                            }

                            if (assetBundleNames.Count == 0)
                            {
                                Debug.LogError("AB包数组未空");
                                return;
                            }

                            CreatePathDataClass(rootNs, constNs, autoDir, assetBundleNames);
                            AssetDatabase.Refresh();
                            Debug.Log("生成完成");
                        }
                    }

                    if (GUILayout.Button("开始打包", GUILayout.Height(3 * EditorGUIUtility.singleLineHeight)))
                    {
                        if (createPathDataClass)
                        {
                            if (!outputDir.Contains(Application.streamingAssetsPath))
                            {
                                Debug.LogError("运行时使用的AB包必须在StreamingAssets目录下");
                                return;
                            }
                        }

                        if (assetBundleNames.Count != 0)
                            assetBundleNames.Clear();
                        if (!Directory.Exists(assetToBundleDir))
                        {
                            Debug.LogError("打包目录设置异常");
                            return;
                        }

                        if (!Directory.Exists(outputDir))
                        {
                            Debug.LogError("输出目录设置异常");
                            return;
                        }

                        if (clearOutputDir)
                        {
                            if (Directory.Exists(outputDir))
                            {
                                //获取指定路径下所有文件夹
                                string[] folderPaths = Directory.GetDirectories(outputDir);

                                foreach (string folderPath in folderPaths)
                                    Directory.Delete(folderPath, true);
                                //获取指定路径下所有文件
                                string[] filePaths = Directory.GetFiles(outputDir);

                                foreach (string filePath in filePaths)
                                    File.Delete(filePath);
                            }
                        }

                        var builds = new List<AssetBundleBuild>();
                        foreach (var dirPath in System.IO.Directory.GetDirectories(assetToBundleDir))
                        {
                            var dirName = new DirectoryInfo(dirPath).Name;
                            var paths = new List<string>();
                            var assetNames = new List<string>();
                            FileUtil.SearchDirectory(dirPath,
                                fileInfo =>
                                {
                                    if (fileInfo.Name.EndsWith(".meta"))
                                        return;
                                    var pureName = Path.GetFileNameWithoutExtension(fileInfo.FullName);
                                    assetNames.Add(pureName);
                                    var fileLoadPath = fileInfo.FullName.Replace("\\", "/")
                                        .Replace(Application.dataPath, "Assets");
                                    var ai = AssetImporter.GetAtPath(fileLoadPath);
                                    ai.assetBundleName = dirName;
                                    paths.Add(fileLoadPath);
                                }, true);

                            assetBundleNames.Add(dirName);
                            FileUtil.CreateConstClassByDictionary(
                                dirName + "Name",
                                Application.dataPath + "/" + rootNs + "/" + constNs,
                                rootNs + "." + constNs,
                                assetNames.ToDictionary(name => name));
                            builds.Add(new AssetBundleBuild
                            {
                                assetNames = paths.ToArray(),
                                assetBundleName = dirName
                            });
                        }

                        if (createPathDataClass)
                        {
                            CreatePathDataClass(rootNs, constNs, autoDir, assetBundleNames);
                        }

                        BuildPipeline.BuildAssetBundles(outputDir, builds.ToArray(), assetBundleOptions, buildTarget);
                        AssetDatabase.Refresh();
                        Debug.Log("打包完成");
                    }

                    break;

                #endregion
            }
        }

        public static void GenerateResourcesConst(string rootNs,string constNs,string autoDir)
        {
             var resourceDir = Application.dataPath + "/Resources";
             var rootDir = Application.dataPath + "/" + rootNs;

             autoClassName.Clear();
             otherResPathDic.Clear();
             otherResFileNameDic.Clear();
             allResPathDic.Clear();
             allResFileNameDic.Clear();

             if (!Directory.Exists(resourceDir))
             {
                 Directory.CreateDirectory(resourceDir);
             }

             foreach (var fullName in Directory.GetFileSystemEntries(resourceDir))
             {
//                 Debug.Log(fullName);
                 if (Directory.Exists(fullName))
                 {
                     //如果是文件夹
                     OprateDir(new DirectoryInfo(fullName), rootNs, constNs, autoDir);
                 }
                 else
                 {
                     //是文件
                     OprateFile(new FileInfo(fullName));
                 }
             }

             //生成其他常量文件
             if (otherResPathDic.Count > 0)
             {
//                 Debug.Log("创建文件OtherResPath");
                 FileUtil.CreateConstClassByDictionary("OtherResPath",
                     rootDir + "/" + constNs + "/" + autoDir,
                     rootNs + "." + constNs, otherResPathDic);
                 FileUtil.CreateConstClassByDictionary("OtherResName",
                     rootDir + "/" + constNs + "/" + autoDir,
                     rootNs + "." + constNs, otherResFileNameDic);
                 autoClassName.Add("OtherRes");
             }

             //生成常量工具类
             if (allResPathDic.Count > 0)
             {
                 var content =
                     "\t\tprivate System.Collections.Generic.Dictionary<string,string> nameToPath \n" +
                     "\t\t\t= new System.Collections.Generic.Dictionary<string,string>{\n";

                 foreach (var kv in allResFileNameDic)
                 {
                     content += "\t\t\t\t\t{ @\"" + kv.Value + "\" , @\"" + allResPathDic[kv.Key] +
                                "\" },\n";
                 }

                 content += "\t\t\t\t};\n";

                 content +=
                     "\t\tpublic override System.Collections.Generic.Dictionary<string,string> NameToPath => nameToPath;\n";

                 FileUtil.CreateClassFile("AssetConstUtil",
                     rootNs + ".Utility",
                     rootDir + "/Utility/" + autoDir,
                     parentClass:"ReadyGamerOne.MemorySystem.AssetConstUtil<AssetConstUtil>",
                     helpTips: "这个类提供了Resources下文件名和路径字典访问方式，同名资源可能引起bug",
                     fileContent: content,
                     autoOverwrite: true,
                     true);
             }

             AssetDatabase.Refresh();
             Debug.Log("生成结束");
        }

        /// <summary>
        /// 创建用于使用AB包管理资源的路径定义类
        /// </summary>
        /// <param name="rootNs"></param>
        /// <param name="constNs"></param>
        /// <param name="autoDir"></param>
        private static void CreatePathDataClass(string rootNs, string constNs, string autoDir, List<string> names)
        {
            var outputDir = ResourceMgr.outputDir.Replace(Application.streamingAssetsPath, "");
            var mainAssetBundleName = new DirectoryInfo(ResourceMgr.outputDir).Name;
            var content = "";
            var otherClassBody = "\n";

            #region OriginBundleKey

            otherClassBody += "\tpublic class OriginBundleKey : ReadyGamerOne.MemorySystem.OriginBundleKey\n" +
                              "\t{\n";
            foreach (var name in names)
            {
                var pureName = name.GetAfterLastChar('/');
                if (pureName == "Audio" || pureName == "File")
                    continue;
                otherClassBody += "\t\tpublic static readonly string " + name.Trim().GetAfterLastChar('/') + " = " +
                                  "@\"" + name.Trim() + "\";\n";
            }

            otherClassBody += "\t}\n";

            #endregion

            #region OriginBundleUtil

            otherClassBody += "\n\tpublic class OriginBundleUtil : OriginBundleUtil<OriginBundleUtil>\n" +
                              "\t{\n";

            #region KeyToName

            otherClassBody += "\t\tprivate Dictionary<string,string> _keyToName = new Dictionary<string,string>\n" +
                              "\t\t{\n" +
                              "\t\t\t{\"Self\" , @\"self\"},\n";
            foreach (var name in names)
            {
                if (name.GetAfterLastChar('/') == "self")
                    continue;
                otherClassBody += "\t\t\t{\"" + name.Trim().GetAfterLastChar('/') + "\" , " +
                                  "@\"" + name.Trim().ToLower() + "\"},\n";
            }

            otherClassBody += "\t\t};\n";
            otherClassBody += "\t\tpublic override Dictionary<string, string> KeyToName => _keyToName;\n";

            #endregion

            #region KeyToPath

            otherClassBody += "\t\tprivate Dictionary<string,string> _keyToPath = new Dictionary<string,string>\n" +
                              "\t\t{\n" +
                              "\t\t\t{\"Self\" , Path.Combine(Application.streamingAssetsPath + @\"" + outputDir +
                              "\", @\"self\")},\n";
            foreach (var name in names)
            {
                if (name.GetAfterLastChar('/') == "self")
                    continue;
                otherClassBody += "\t\t\t{\"" + name.Trim().GetAfterLastChar('/') + "\" , " +
                                  "Path.Combine(Application.streamingAssetsPath + @\"" + outputDir + "\", @\"" +
                                  name.Trim().ToLower() + "\")},\n";
            }

            otherClassBody += "\t\t};\n";
            otherClassBody += "\t\tpublic override Dictionary<string, string> KeyToPath => _keyToPath;\n";

            #endregion

            #region NameToPath

            otherClassBody += "\t\tprivate Dictionary<string,string> _nameToPath = new Dictionary<string,string>\n" +
                              "\t\t{\n" +
                              "\t\t\t{@\"self\" , Path.Combine(Application.streamingAssetsPath + @\"" + outputDir +
                              "\", @\"self\")},\n";
            foreach (var name in names)
            {
                if (name.GetAfterLastChar('/') == "self")
                    continue;
                otherClassBody += "\t\t\t{@\"" + name.Trim().ToLower() + "\" , " +
                                  "Path.Combine(Application.streamingAssetsPath + @\"" + outputDir + "\", @\"" +
                                  name.Trim().ToLower() + "\")},\n";
            }

            otherClassBody += "\t\t};\n";
            otherClassBody += "\t\tpublic override Dictionary<string, string> NameToPath => _nameToPath;\n";

            #endregion

            otherClassBody += "\t}\n";

            #endregion

            if (useWeb)
            {
                content =
                    "\t\tpublic string OriginMainManifest => @Path.Combine(Application.streamingAssetsPath + @\"" +
                    outputDir + "\", \"" +
                    mainAssetBundleName + "\");\n" +
                    "\t\tpublic string LocalMainPath => @Path.Combine(Application.persistentDataPath, \"AssetBundles\");\n" +
                    "\t\tpublic string WebServeMainPath => @\"file:/C:\\Users\\ReadyGamerOne\\Downloads\\webserver\";\n" +
                    "\t\tpublic string WebServeMainManifest => WebServeMainPath + \"\\\\ManifestFile\";\n" +
                    "\t\tpublic string WebServeVersionPath => WebServeMainPath + \"\\\\ServeVersion.html\";\n" +
                    "\t\tpublic string WebServeBundlePath => WebServeMainPath + \"\\\\AssetBundles\";\n" +
                    "\t\tpublic string WebServeConfigPath => WebServeMainPath + \"\\\\ServeConfig\";\n" +
                    "\t\tpublic Func<string, string> GetServeConfigPath => version =>$\"{WebServeConfigPath}/{version}.html\";\n" +
                    "\t\tpublic Func<string, string, string> GetServeBundlePath => (bundleName,bundleVersion)=>$\"{WebServeBundlePath}/{bundleVersion}/{bundleName}\";\n" +
                    "\t\tpublic Func<string, string, string> GetLocalBundlePath => (bundleName,bundleVersion)=>$\"{LocalMainPath}/{bundleVersion}/{bundleName}\";\n";
            }
            else
            {
                content =
                    "\t\tpublic string OriginMainManifest => @Path.Combine(Application.streamingAssetsPath + @\"" +
                    outputDir + "\", \"" +
                    mainAssetBundleName + "\");\n" +
                    "\t\tpublic string LocalMainPath => null;\n" +
                    "\t\tpublic string WebServeMainPath => null;\n" +
                    "\t\tpublic string WebServeMainManifest => null;\n" +
                    "\t\tpublic string WebServeVersionPath => null;\n" +
                    "\t\tpublic string WebServeBundlePath => null;\n" +
                    "\t\tpublic string WebServeConfigPath => null;\n" +
                    "\t\tpublic Func<string, string> GetServeConfigPath => null;\n" +
                    "\t\tpublic Func<string, string, string> GetServeBundlePath => null;\n" +
                    "\t\tpublic Func<string, string, string> GetLocalBundlePath => null;\n";
            }

            FileUtil.CreateClassFile(
                "HotUpdatePathData",
                rootNs + "." + constNs,
                Application.dataPath + "/" + rootNs + "/" + constNs,
                "Singleton<HotUpdatePathData>, IHotUpdatePath",
                "使用AB包管理资源时必须的定义路径的常量类",
                content,
                true,
                false,
                "using ReadyGamerOne.MemorySystem;\n" +
                "using UnityEngine;\n" +
                "using System.IO;\n" +
                "using System;\n" +
                "using System.Collections.Generic;\n" +
                "using ReadyGamerOne.Common;\n",
                otherClassBody: otherClassBody);
        }

        /// <summary>
        /// 遍历Resources目录的时候操作文件的函数
        /// </summary>
        /// <param name="fileInfo"></param>
        private static void OprateFile(FileInfo fileInfo)
        {
            if (fileInfo.FullName.EndsWith(".meta"))
            {
//                Debug.Log("跳过。meta");
                return;
            }

            var loadPath = fileInfo.FullName.GetAfterSubstring("Resources\\")
                .GetBeforeSubstring(Path.GetExtension(fileInfo.FullName));
            var fileName = Path.GetFileNameWithoutExtension(fileInfo.FullName);
            var varName = FileUtil.FileNameToVarName(fileName);

            if (allResPathDic.ContainsKey(varName))
            {
                Debug.LogWarning("相同Key: " + varName);
                Debug.LogWarning("已存：" + allResPathDic[varName]);
                Debug.LogWarning("现在：" + loadPath);
            }
            else
            {
//                Debug.Log("添加了");
                allResPathDic.Add(varName, loadPath);
                allResFileNameDic.Add(varName, fileName);

                otherResPathDic.Add(varName, loadPath);
                otherResFileNameDic.Add(varName, fileName);
            }
        }

        /// <summary>
        /// 遍历Resources目录时，操作目录的函数
        /// </summary>
        /// <param name="dirInfo"></param>
        /// <param name="rootNs"></param>
        /// <param name="constNs"></param>
        /// <param name="autoDir"></param>
        private static bool OprateDir(DirectoryInfo dirInfo, string rootNs, string constNs, string autoDir)
        {
//            Debug.Log(dirInfo.FullName);
            var dirName = dirInfo.FullName.GetAfterLastChar('\\');

            if (dirName == "Resources")
                return true;

//            Debug.Log("operateDir: " + dirName);
            if (dirName.StartsWith("Global"))
                return false;
            if (dirName.StartsWith("Class"))
            {
                autoClassName.Add(dirName.GetAfterSubstring("Class"));

                if (createPathFile)
                {
                         FileUtil.ReCreateFileNameConstClassFromDir(
                             dirName.GetAfterSubstring("Class") + "Path",
                             Application.dataPath + "/" + rootNs + "/" + constNs + "/" + autoDir,
                             dirInfo.FullName,
                             rootNs + "." + constNs,
                             (fileInfo, stream) =>
                             {
                                 if (!fileInfo.FullName.EndsWith(".meta"))
                                 {
                                     var fileName = Path.GetFileNameWithoutExtension(fileInfo.FullName);
                                     var varName = FileUtil.FileNameToVarName(fileName);
                                     var loadPath = fileInfo.FullName.GetAfterSubstring("Resources\\")
                                         .GetBeforeSubstring(Path.GetExtension(fileInfo.FullName));
                                     stream.Write("\t\tpublic static readonly string " + varName + " = @\"" + loadPath + "\";\n");
                                 }
                             }, true);
                }

                var className = dirName.GetAfterSubstring("Class") + "Name";
                FileUtil.ReCreateFileNameConstClassFromDir(
                    className,
                    Application.dataPath + "/" + rootNs + "/" + constNs + "/" + autoDir,
                    dirInfo.FullName,
                    rootNs + "." + constNs,
                    (fileInfo, stream) =>
                    {
                        if (!fileInfo.FullName.EndsWith(".meta"))
                        {
                            var fileName = Path.GetFileNameWithoutExtension(fileInfo.FullName);
                            var varName = FileUtil.FileNameToVarName(fileName);
                            var loadPath = fileInfo.FullName.GetAfterSubstring("Resources\\")
                                .GetBeforeSubstring(Path.GetExtension(fileInfo.FullName));

                            if (allResPathDic.ContainsKey(varName))
                            {
                                Debug.LogWarning("出现同名资源文件：" + fileInfo);
                            }
                            else
                            {
                                allResPathDic.Add(varName, loadPath);
                                allResFileNameDic.Add(varName, fileName);
                            }

                            stream.Write("\t\tpublic static readonly string " + varName + " = @\"" + fileName + "\";\n");
                        }
                    }, true);
            }
            else
                FileUtil.SearchDirectory(dirInfo.FullName, OprateFile,true);

            return true;
        }

#endif

        #endregion
    }
}


text:
fileFormatVersion: 2
guid: b24bc3d07c9a43338b1fd755268aeb59
timeCreated: 1578973979


text:
fileFormatVersion: 2
guid: a2ec19546d6d42c78be424cce99ffcf7
timeCreated: 1578975753


text:
using System;
using System.Collections.Generic;
using ReadyGamerOne.Common;

namespace ReadyGamerOne.MemorySystem
{
    public interface IHotUpdatePath
    {
        string OriginMainManifest { get; }
        string LocalMainPath { get; }
        string WebServeMainPath { get; }
        string WebServeMainManifest { get; }
        string WebServeVersionPath { get; }
        string WebServeBundlePath { get; }
        string WebServeConfigPath { get; }

        Func<string,string> GetServeConfigPath { get; }
        Func<string,string,string> GetServeBundlePath { get; }
        Func<string,string,string> GetLocalBundlePath { get; }
    }

    #region IOriginAssetBundleUtil

    public interface IOriginAssetBundleUtil
    {
        Dictionary<string, string> KeyToName { get; }
        Dictionary<string, string> KeyToPath { get; }
        Dictionary<string,string> NameToPath { get; }
    }

    public class OriginBundleUtil<T>:
        Singleton<T>,
        IOriginAssetBundleUtil
        where T :OriginBundleUtil<T>,new()
    {
        public virtual Dictionary<string, string> KeyToName => null;
        public virtual Dictionary<string, string> KeyToPath => null;
        public virtual Dictionary<string, string> NameToPath => null;
    }
    #endregion

    #region IAssetConstUtil

    public class AssetConstUtil<T> :
        Singleton<T>,
        IAssetConstUtil
        where T :AssetConstUtil<T>, new()
    {
        public virtual Dictionary<string, string> NameToPath
        {
            get { throw new Exception("就不该走到这里"); }
        }
    }
    public interface IAssetConstUtil
    {
        Dictionary<string,string> NameToPath { get; }
    }

    #endregion

    public class OriginBundleKey
    {
        public const string Self = @"Self";
        public const string Audio = @"Audio";
        public const string File = @"File";
    }

}


text:
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using ReadyGamerOne.Const;
using ReadyGamerOne.Script;
using ReadyGamerOne.Utility;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Networking;

namespace ReadyGamerOne.MemorySystem
{
    /// <summary>
    /// AB包下载器，负责检查本地和服务器资源结构的区别，并把新资源下载到本机
    /// 并且维护不同的资源
    /// </summary>
    internal class AssetBundleDownLoader
    {
        #region 流程

        //读取服务器端和版本相关的资源配置文档,非manifest
        //配置文档包括当前版本,资源文件对应的版本如
        //CurrentVersion:0.12
        //SomeBundleVersion:0.10
        //检查本地的配置。找到不同之后，重新下载
        //下载完成后，将最新版本的配置写入本地

        #endregion

        internal string currentServeVersion;

        public bool IsUpdateToServe { get; private set; }

        internal AssetBundleDownLoader(IHotUpdatePath pather)
        {
            this.pather = pather;
            Assert.IsFalse(string.IsNullOrEmpty(pather.WebServeMainManifest));
            bundleDownloaderTool = new BundleDownloaderTool(OnDownloadCompleteF, OnAllResourceFine, pather);
        }

        /// <summary>
        /// 检查本地和服务器资源结构并更新
        /// </summary>
        /// <returns></returns>
        internal IEnumerator CheckVersionAndUpdate()
        {
            //读取当前客户端资源结构
            ReadLocalCurentVersionData();
            //读取当前服务器资源结构
            yield return ReadServeNewestVersion();
            //比较服务器和当前版本差别
            var differenceConfig = CheckDifference();

            Debug.Log("区别：" + differenceConfig);
            //如果版本检查失败（网络链接异常什么的)
            if (VersionCheckFailed)
            {
                Debug.Log("版本检查失败");
                IsUpdateToServe = false;
                //直接结束
                yield break;
            }

            //如果没有区别
            if (differenceConfig == null)
            {
                Debug.Log("没有区别");
                //设置同步完成
                currentLocalVersion = currentServeVersion;
                IsUpdateToServe = true;
            }
            else
            {
                Debug.Log("有区别");
                //如果有区别，就要下载有区别的文件
                if (differenceConfig.Config.Count != 0)
                {
                    DownLoadAllDifferenceResource(differenceConfig);
                }
            }
        }

        #region Private

        private string currentLocalVersion;
        private BundleDownloaderTool bundleDownloaderTool;
        private ResourcesConfig serveConfig;
        private ResourcesConfig localConfig;
        private bool VersionCheckFailed = false;
        private IHotUpdatePath pather;

        /// <summary>
        /// 读取服务器版本信息
        /// </summary>
        /// <returns></returns>
        private IEnumerator ReadServeNewestVersion()
        {

            string versionMessage = null;
            yield return FetchMessageFromWeb(pather.WebServeVersionPath, output => versionMessage = output);
            if (!string.IsNullOrEmpty(versionMessage))
            {
                //记录当前服务器版本号
                currentServeVersion = versionMessage;

                var serveConfigURL = pather.GetServeConfigPath(versionMessage);
                //Debug.Log(NewestServeConfigURL);
                string serverData = null;
                yield return FetchMessageFromWeb(serveConfigURL, output => serverData = output);
                //Debug.Log(servedata);
                //记录当前服务器资源结构
                serveConfig = ResourcesConfig.Create(serverData);
            }
        }

        /// <summary>
        /// 从服务器获取信息
        /// </summary>
        /// <param name="webPath">从哪个网页获取信息</param>
        /// <param name="outMessage">如何操作获取的信息</param>
        /// <returns></returns>
        private IEnumerator FetchMessageFromWeb(string webPath, System.Action<string> outMessage)
        {
            using (var webRequest = UnityWebRequest.Get(webPath))
            {
                yield return webRequest.SendWebRequest();
                var text = webRequest.downloadHandler.text;
                outMessage(text);
                if (webRequest.isNetworkError || webRequest.isHttpError)
                {
                    VersionCheckFailed = true;
                    Debug.LogWarning(webRequest.error);
                }
            }
        }

        /// <summary>
        /// 读取当前本地客户端版本
        /// </summary>
        private void ReadLocalCurentVersionData()
        {
            //记录客户端版本号
            var newestVersion = PlayerPrefs.GetString(VersionDefine.PrefKey_LocalVersion);
            var versionData = PlayerPrefs.GetString(newestVersion);
            //记录客户端版本资源结构
            localConfig = ResourcesConfig.Create(versionData);
        }

        /// <summary>
        /// 比较服务器和本地版本的不同
        /// </summary>
        /// <returns></returns>
        private ResourcesConfig CheckDifference()
        {
            if (serveConfig.Config == null)
            {
                VersionCheckFailed = true;
                return null;
            }

            return serveConfig.CheckDifference(localConfig);
        }

        /// <summary>
        /// 下载不同的素材到不同的文件夹中
        /// </summary>
        /// <param name="differenceConfig"></param>
        private void DownLoadAllDifferenceResource(ResourcesConfig differenceConfig)
        {
            if (differenceConfig == null)
                return;
            //把所有不同的素材添加到下载队列中
            foreach (var item in differenceConfig.Config)
            {
                bundleDownloaderTool.AddDownloadBundleCommand(item.Key, item.Value);
            }
        }

        /// <summary>
        /// 下载完成时回调
        /// </summary>
        private void OnDownloadCompleteF()
        {
            bundleDownloaderTool.CheckFileDone(serveConfig);
        }

        /// <summary>
        /// 更新结束后回调
        /// </summary>
        private void OnAllResourceFine()
        {
            //设定更新完成
            IsUpdateToServe = true;
            //更新本地
            currentLocalVersion = currentServeVersion;
            PlayerPrefs.SetString(VersionDefine.PrefKey_LocalVersion, currentLocalVersion);
            PlayerPrefs.SetString(currentLocalVersion, serveConfig.ToString());
            Debug.Log("所有资源更新已就位");
        }

        #endregion

        #region 内部类

        /// <summary>
        /// Resources信息结构体，用于标识某一个AssetBundle及其版本
        /// </summary>
        [System.Serializable]
        private struct VersionedResource
        {
            /// <summary>
            /// AssetBundle名
            /// </summary>
            internal string BundleName;
            /// <summary>
            /// AB包版本，一般是小数
            /// </summary>
            internal string Version;
        }

        /// <summary>
        /// 资源结构类，标识大量资源，可以标识本地资源结构树，服务器资源结构树
        /// </summary>
        private class ResourcesConfig
        {
            #region 对外唯一创建函数

            public static ResourcesConfig Create(string inputMessage)
            {
                var ans = new ResourcesConfig();

                if (string.IsNullOrEmpty(inputMessage))
                    return ans;

                inputMessage = inputMessage.Replace("\r", string.Empty);
                var lines = inputMessage.Split('\n');
                if (lines.Length == 0)
                    return null;
                foreach (var line in lines)
                {
                    var sets = line.Split(':');
                    ans.Config.Add(sets[0], sets[1]);
                }
                return ans;
            }

            #endregion

            #region Private

            private Dictionary<string, string> config;
            private ResourcesConfig()
            {

            }
            #endregion

            internal Dictionary<string, string> Config
            {
                get
                {
                    if (null == config)
                        config = new Dictionary<string, string>();
                    return config;
                }
            }

            /// <summary>
            /// 将字典信息用"\n"拼成字符串
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                var sb = new System.Text.StringBuilder();
                int c = Config.Count;
                foreach (var item in Config)
                {
                    c--;
                    if (c > 0)
                    {
                        sb.AppendFormat("{0}:{1}\n", item.Key, item.Value);
                    }
                    else
                    {
                        sb.AppendFormat("{0}:{1}", item.Key, item.Value);
                    }
                }

                return sb.ToString();
            }

            /// <summary>
            /// 对比两个的不同，以自身为主
            /// </summary>
            /// <param name="inputConfig"></param>
            /// <returns></returns>
            internal ResourcesConfig CheckDifference(ResourcesConfig inputConfig)
            {
                ResourcesConfig differenceConfig = new ResourcesConfig();

                foreach (var item in Config)
                {
                    //对于每一个自己的item，如果对方也有
                    if (inputConfig.Config.ContainsKey(item.Key))
                    {
                        //判断版本是否相同
                        if (inputConfig.Config[item.Key] != item.Value)
                        {
                            // 如果版本不同就添加自身的数据到结果中，
                            // 这一行代码说明，这样的对比是默认采用本类数据的，所以，
                            // 调用的时候，应该是 服务器数据结构.CheckDifference(客户端数据结构）
                            differenceConfig.Config.Add(item.Key, item.Value);
                        }
                    }
                    else
                    {
                        //如果对方没有，直接加入
                        differenceConfig.Config.Add(item.Key, item.Value);
                    }
                }

                //如果没有不同，说明版本完全一样
                if (differenceConfig.Config.Count == 0)
                {
                    Debug.Log("Nothing Difference");
                    return null;
                }
                return differenceConfig;
            }
        }

        /// <summary>
        /// 包下载器，队列数据结构下载，提供添加下载任务接口
        /// </summary>
        private class BundleDownloaderTool
        {
            internal BundleDownloaderTool(Action onDownloadComplete, Action onAllFine, IHotUpdatePath pather)
            {
                OnDownloadComplete = onDownloadComplete;
                OnAllFine = onAllFine;
                this.pather = pather;
            }

            /// <summary>
            /// 添加下载任务
            /// </summary>
            /// <param name="bundle"></param>
            /// <param name="version"></param>
            internal void AddDownloadBundleCommand(string bundle, string version)
            {
                if (string.IsNullOrEmpty(bundle) || string.IsNullOrEmpty(version))
                    return;
                var downloadBundleCommand = new VersionedResource
                {
                    BundleName = bundle,
                    Version = version
                };
                downloadCommands.Push(downloadBundleCommand);
                if (!IsDownloading)
                    MainLoop.Instance.StartCoroutine(StartDownloading());
            }

            /// <summary>
            /// 判断资源包是否更行完毕
            /// </summary>
            /// <param name="checkConfig"></param>
            internal void CheckFileDone(ResourcesConfig checkConfig)
            {
                bool allFine = true;
                foreach (var item in checkConfig.Config)
                {
                    string checkpath = pather.GetLocalBundlePath(item.Key, item.Value);
                    if (!File.Exists(checkpath))
                    {
                        allFine = false;
                    }
                }

                if (OnAllFine != null && allFine)
                {
                    OnAllFine();
                }
            }

            #region Private

            //存储待下载的包
            private Stack<VersionedResource> downloadCommands = new Stack<VersionedResource>();
            private bool IsDownloading { get; set; }
            private Action OnDownloadComplete;
            private Action OnAllFine;
            private IHotUpdatePath pather;

            /// <summary>
            /// 下载资源包协程
            /// </summary>
            /// <returns></returns>
            private IEnumerator StartDownloading()
            {
                IsDownloading = true;
                while (downloadCommands.Count != 0)
                {
                    var cache = downloadCommands.Pop();
                    yield return DownloadAndSaveBundle(cache.BundleName, cache.Version);
                }

                OnDownloadComplete?.Invoke();
                IsDownloading = false;
            }

            /// <summary>
            /// 从服务器下载并保存新资源包
            /// </summary>
            /// <param name="bundleName"></param>
            /// <param name="bundleVersion"></param>
            /// <returns></returns>
            private IEnumerator DownloadAndSaveBundle(string bundleName, string bundleVersion)
            {
                //获取服务器请求url
                var requestUrl = pather.GetServeBundlePath(bundleName, bundleVersion);
                var webBytesLoadRequest = UnityWebRequest.Get(requestUrl);
                yield return webBytesLoadRequest.SendWebRequest();
                byte[] bs = webBytesLoadRequest.downloadHandler.data;
                //获取本地包存储路径
                var localPath = pather.GetLocalBundlePath(bundleName, bundleVersion);
                //将服务器获得的新包数据保存到本地
                FileUtil.CreateFile(localPath, bs);
            }

            #endregion

//    private IEnumerator DownloadAndSaveManifest(string manifestName, string manifestVersion)
//    {
//        UnityWebRequest webBytesLoadRequest = UnityWebRequest.Get(PathCreator.Instance.GetServeBundlePath(manifestName, manifestVersion));
//        yield return webBytesLoadRequest.SendWebRequest();
//        byte[] bs = webBytesLoadRequest.downloadHandler.data;
//        string FilePath = PathCreator.Instance.GetLocalBundlePath(manifestName, manifestVersion);
//        FileSaver.SaveAssetsBundle(bs, FilePath);
//    }
        }

        #endregion

    }
}


text:
fileFormatVersion: 2
guid: 3691601fbf3560240b4e7dcdd480e430
timeCreated: 1560998421
licenseType: Free
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:


text:
fileFormatVersion: 2
guid: dfa9454b008396d4a92da1fd7aa41931
timeCreated: 1560918683
licenseType: Free
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:


text:
fileFormatVersion: 2
guid: 7c80d89d1e914e2db030accafce18191
timeCreated: 1578974386


text:
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using ReadyGamerOne.Utility;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Networking;

namespace ReadyGamerOne.MemorySystem
{
    /// <summary>
    /// AB包加载器，管控已经位于本地的AB包在游戏中的加载和卸载
    /// 管理载入情况，管理依赖，管理引用次数
    /// </summary>
    internal class AssetBundleLoader
    {
        private Dictionary<string, BundleUsageInfo> HeldedBundles = new Dictionary<string, BundleUsageInfo>();

        private AssetBundleLoaderTool loadLoaderTool;
        private AssetBundleDownLoader assetBundleDownLoader;
        private ManifestReader manifestReader;
        private IOriginAssetBundleUtil _originAssetBundleUtil;

        /// <summary>
        /// 获取调试信息
        /// </summary>
        /// <returns></returns>
        public string DebugInfo()
        {
            var ans = "";
            foreach (var kv in HeldedBundles)
            {
                ans += kv.Key + " " + kv.Value.useTime + "\n";
            }

            return ans;
        }

        #region 资源管控

        /// <summary>
        /// 开启整个工作流程
        /// </summary>
        /// <returns></returns>
        internal IEnumerator StartBundleManager(IHotUpdatePath pather, IOriginAssetBundleUtil originAssetBundleUtil = null)
        {
            this._originAssetBundleUtil = originAssetBundleUtil;

            manifestReader = new ManifestReader(pather);
            yield return manifestReader.LoadManifest();

            if (!string.IsNullOrEmpty(pather.WebServeMainManifest))
                assetBundleDownLoader = new AssetBundleDownLoader(pather);

            loadLoaderTool = new AssetBundleLoaderTool(OnAddAssetBundle, assetBundleDownLoader, pather, originAssetBundleUtil);

            if (!string.IsNullOrEmpty(pather.WebServeMainManifest))
                yield return assetBundleDownLoader.CheckVersionAndUpdate();
        }

        /// <summary>
        /// 异步获取AB包并使用，如果没有加载AB包就异步加载，顺利调用会使对应AB包引用计数加一
        /// </summary>
        /// <param name="bundleName"></param>
        /// <param name="objectName"></param>
        /// <param name="getAction"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        internal IEnumerator GetBundleAsync(string bundleName, System.Func<AssetBundle, IEnumerator> getAction)
        {
            //首次加载时，需要先加载依赖
            yield return AddBundleWithDependenciesAsync(bundleName);

            Assert.IsTrue(HeldedBundles.ContainsKey(bundleName));

            var targetBundle = HeldedBundles[bundleName].heldAssetBundle;
            HeldedBundles[bundleName].useTime++;
            yield return getAction(targetBundle);

        }
        private IEnumerator AddBundleWithDependenciesAsync(string _bundleName)
        {
            //如果已经加载过，就无需在加载
            if (HeldedBundles.ContainsKey(_bundleName))
            {
                yield break;
            }

            //获取依赖
            var dependences = manifestReader.GetBundleDependencies(_bundleName);

            //如果没有依赖，直接加载自己，然后返回
            if (null == dependences)
            {
                yield return loadLoaderTool.LoadFileAsync(_bundleName);
                yield break;
            }

            //如果有依赖，逐个添加依赖
            foreach (var dependence in dependences)
            {
                //添加依赖，保证已经加载这个依赖
                yield return AddBundleWithDependenciesAsync(dependence);

                //添加一次依赖
                HeldedBundles[dependence].useTime++;
            }

            //如果有依赖，添加完依赖后，最后添加自己，这样，无论有没有以来，最后都会连自己都添加了
            yield return loadLoaderTool.LoadFileAsync(_bundleName);
        }

        /// <summary>
        /// 同步加载一个没有依赖的AB包，优先从缓存获取，顺利调用会使对应AB包引用计数加一
        /// </summary>
        /// <param name="bundleName"></param>
        /// <param name="defaultGetMethod"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        internal AssetBundle GetBundle(string bundleNameKey)
        {
            if (string.IsNullOrEmpty(bundleNameKey))
            {
                throw new Exception("Ab包Key不得为空");
            }
            Assert.IsNotNull(_originAssetBundleUtil);

            var bundleName = _originAssetBundleUtil.KeyToName[bundleNameKey];

            //待依赖添加资源
            AddBundleWithDependencies(bundleName,_originAssetBundleUtil);

            Assert.IsTrue(HeldedBundles.ContainsKey(bundleName));

            var targetBundle = HeldedBundles[bundleName].heldAssetBundle;
            HeldedBundles[bundleName].useTime++;

            return targetBundle;
        }

        #region Private

        /// <summary>
        /// 每次加载一个AssetBundle的回调
        /// </summary>
        /// <param name="bundle"></param>
        void OnAddAssetBundle(AssetBundle bundle)
        {
//            var assets = bundle.LoadAllAssets();
//            foreach (var asset in assets)
//            {
//                Debug.Log(asset.ToString());
//            }

            if (!HeldedBundles.ContainsKey(bundle.name))
            {
//                Debug.Log("Add  " + bundle.name);
                HeldedBundles.Add(bundle.name, new BundleUsageInfo(bundle));
            }
        }

        #endregion

        /// <summary>
        /// 同步添加依赖
        /// </summary>
        /// <param name="_bundleName"></param>
        void AddBundleWithDependencies(string _bundleName,IOriginAssetBundleUtil originAssetBundleUtil)
        {
            //如果已经加载过，就无需在加载
            if (HeldedBundles.ContainsKey(_bundleName))
            {
                return;
            }

            //获取依赖
            var dependences = manifestReader.GetBundleDependencies(_bundleName);

            //如果没有依赖，直接加载自己，然后返回
            if (null == dependences)
            {
                loadLoaderTool.LoadFile(originAssetBundleUtil.NameToPath[_bundleName]);
                return;
            }

            //如果有依赖，逐个添加依赖
            foreach (var dependence in dependences)
            {
                //添加依赖，保证已经加载这个依赖
                AddBundleWithDependencies(dependence,originAssetBundleUtil);

                //添加一次依赖
                HeldedBundles[dependence].useTime++;
            }

            //如果有依赖，添加完依赖后，最后添加自己，这样，无论有没有以来，最后都会连自己都添加了
            loadLoaderTool.LoadFile(originAssetBundleUtil.NameToPath[_bundleName]);
        }

        #endregion

        #region 内部类

        /// <summary>
        /// 记录AB包间相互依赖的数据结构
        /// </summary>
        private class BundleUsageInfo
        {
            public AssetBundle heldAssetBundle;
            public int useTime;

            public BundleUsageInfo(AssetBundle inbundle)
            {
                heldAssetBundle = inbundle;
            }
        }

        /// <summary>
        /// 读取资源包，提供多种方式
        /// 1.读取本地，并且使用
        /// 2.下载并且使用web服务器提供的
        /// 3.按字节读取本地或者web的，然后自己来解密
        /// </summary>
        private class AssetBundleLoaderTool
        {
            internal AssetBundleLoaderTool(Action<AssetBundle> onLoadEveryComplete, AssetBundleDownLoader version,
                IHotUpdatePath updatePath, IOriginAssetBundleUtil originAssetBundleUtil)
            {
                this.pather = updatePath;
                this.onLoadEveryComplete = onLoadEveryComplete;
                this.version = version;
                this._originAssetBundleUtil = originAssetBundleUtil;
            }

            private Action<AssetBundle> onLoadEveryComplete;
            private AssetBundleDownLoader version;
            private IHotUpdatePath pather;
            private IOriginAssetBundleUtil _originAssetBundleUtil;

            private class BundleContainer
            {
                public AssetBundle assetBundle;
                public byte[] bytes;
            }

            private enum BundleLoadMethod
            {
                WWW_LoadBundle = 0,
                WWW_LoadBytes = 1,
                WWW_LoadBundleAndBytes = 2,
                File_LocalBundle = 3,
                File_Bytes = 4,
                File_StreamingBundle = 5
            };

            /// <summary>
            /// 留给外部的接口，根据bundle名异步加载文件，回调需要提前赋值
            /// </summary>
            /// <param name="bundleName"></param>
            /// <returns></returns>
            internal IEnumerator LoadFileAsync(string bundleName)
            {
                //是否使用StreamingAssets?
                if (!string.IsNullOrEmpty(pather.OriginMainManifest))
                {
                    var streamingPath = @Path.Combine(Application.streamingAssetsPath, bundleName);
                    if (File.Exists(streamingPath))
                    {
//                        Debug.Log("LoadStreaming");
                        yield return LoadBundleAsync(bundleName, null, BundleLoadMethod.File_StreamingBundle);
                        yield break;
                    }
                }

                //是否使用服务器资源
                if (!string.IsNullOrEmpty(pather.WebServeMainManifest))
                {
                    Assert.IsNotNull(version);

                    while (!version.IsUpdateToServe)
                        yield return null;
                    var bundleVersion = version.currentServeVersion;

                    var localBundlePath = pather.GetLocalBundlePath(bundleName, bundleVersion);

                    if (File.Exists(localBundlePath))
                    {
//                        Debug.Log("LoadLocalFile");
                        yield return LoadBundleAsync(bundleName, bundleVersion, BundleLoadMethod.File_LocalBundle);
                        yield break;
                    }

//                    Debug.Log("LoadWWW");
                    yield return LoadBundleAsync(bundleName, bundleVersion, BundleLoadMethod.WWW_LoadBundleAndBytes,
                        true);
                    yield break;
                }

                Debug.LogError("没有这个AssetBundle: " + bundleName);
            }

            internal void LoadFile(string bundlePath)
            {
                Assert.IsNotNull(_originAssetBundleUtil);
                try
                {
                    var ab = AssetBundle.LoadFromFile(bundlePath);
                    onLoadEveryComplete(ab);
                }
                catch (KeyNotFoundException)
                {
                    throw new Exception("没有这个BundleName " + bundlePath);
                }

            }

            /// <summary>
            /// 以字节流的形式载入资源
            /// </summary>
            private IEnumerator LoadBundleAsync(string bundleName, string bundleVersion, BundleLoadMethod method,
                bool saveBundle = false)
            {
                string bundlePath;
                var container = new BundleContainer();
                switch (method)
                {
                    case BundleLoadMethod.File_StreamingBundle:
                        bundlePath = @Path.Combine(Application.streamingAssetsPath, bundleName);
                        break;
                    case BundleLoadMethod.File_LocalBundle:
                        bundlePath = pather.GetLocalBundlePath(bundleName, bundleVersion);
                        break;
                    default:
                        bundlePath = pather.GetServeBundlePath(bundleName, bundleVersion);
                        break;
                }

                yield return ExecuteLoadBundleAsync(bundlePath, method, container);

                onLoadEveryComplete(container.assetBundle);

                if (saveBundle && container.bytes != null)
                {
                    var filePath = pather.GetLocalBundlePath(bundleName, bundleVersion);
                    FileUtil.CreateFile(filePath, container.bytes);
                }
            }

            /// <summary>
            /// 读取字节
            /// 1.File.ReadAllBytes(path)
            /// 2.WWW
            /// </summary>
            /// <param name="path">路径</param>
            /// <param name="streamMethod">形式</param>
            /// <param name="cotiner">字节容器</param>
            /// <returns></returns>
            private IEnumerator ExecuteLoadBundleAsync(string path, BundleLoadMethod streamMethod,
                BundleContainer container)
            {
//                Debug.Log(streamMethod);
                switch (streamMethod)
                {
                    case BundleLoadMethod.WWW_LoadBundleAndBytes:
                    {
                        var webBbLoadRequest = UnityWebRequest.Get(path);
                        yield return webBbLoadRequest.SendWebRequest();
                        container.bytes = webBbLoadRequest.downloadHandler.data;
                        var bundleCreationRequest = AssetBundle.LoadFromMemoryAsync(container.bytes);
                        yield return bundleCreationRequest;
                        container.assetBundle = bundleCreationRequest.assetBundle;
                        break;
                    }

                    case BundleLoadMethod.WWW_LoadBytes:
                    {
                        var webBytesLoadRequest = UnityWebRequest.Get(path);
                        yield return webBytesLoadRequest.SendWebRequest();
                        container.bytes = webBytesLoadRequest.downloadHandler.data;
                        break;
                    }

                    case BundleLoadMethod.WWW_LoadBundle:
                    {
                        var webBundleRequest = UnityWebRequestAssetBundle.GetAssetBundle(path);
                        yield return webBundleRequest.SendWebRequest();
                        container.assetBundle = DownloadHandlerAssetBundle.GetContent(webBundleRequest);
                        break;
                    }

                    case BundleLoadMethod.File_LocalBundle:
                    {
                        var fileBundleRequest = AssetBundle.LoadFromFileAsync(path);
                        yield return fileBundleRequest;
                        container.assetBundle = fileBundleRequest.assetBundle;
                        break;
                    }

                    case BundleLoadMethod.File_StreamingBundle:
                    {
                        var fileBundleRequest = AssetBundle.LoadFromFileAsync(path);
                        yield return fileBundleRequest;
                        container.assetBundle = fileBundleRequest.assetBundle;
                        break;
                    }

                    case BundleLoadMethod.File_Bytes:
                        break;
                    default:
                        yield break;
                }
            }
        }

        /// <summary>
        /// 读取Manifest并且解析依赖关系
        /// </summary>
        private class ManifestReader
        {
            /// <summary>
            /// 记录各AB包依赖依赖
            /// </summary>
            private Dictionary<string, string[]> bundleDependencies = new Dictionary<string, string[]>();

            private IHotUpdatePath pather;

            internal ManifestReader(IHotUpdatePath updatePath)
            {
                this.pather = updatePath;
            }

            /// <summary>
            /// 从服务器获取Manifest，记录依赖
            /// </summary>
            /// <returns></returns>
            internal IEnumerator LoadManifest()
            {
                //如果使用服务器资源
                if (!string.IsNullOrEmpty(pather.WebServeMainManifest))
                {
                    var webBundleRequest = UnityWebRequestAssetBundle.GetAssetBundle(pather.WebServeMainManifest);
                    yield return webBundleRequest.SendWebRequest();

                    var serverAssetBundle = DownloadHandlerAssetBundle.GetContent(webBundleRequest);
                    if (!serverAssetBundle)
                        throw new Exception("server assetBundle is null");

                    var assetR = serverAssetBundle.LoadAssetAsync<AssetBundleManifest>("AssetBundleManifest");
                    yield return assetR;
                    var manifest = assetR.asset as AssetBundleManifest;

                    Assert.IsTrue(manifest);
                    foreach (var bundlesName in manifest.GetAllAssetBundles())
                    {
                        bundleDependencies.Add(bundlesName, manifest.GetAllDependencies(bundlesName));
                    }

                    serverAssetBundle.Unload(true);
                }

                //如果使用StreamingAsset资源
                if (!string.IsNullOrEmpty(pather.OriginMainManifest))
                {
                    var originBundleRequest = UnityWebRequestAssetBundle.GetAssetBundle(pather.OriginMainManifest);
                    yield return originBundleRequest.SendWebRequest();

                    var originAssetBundle = DownloadHandlerAssetBundle.GetContent(originBundleRequest);
                    if (!originAssetBundle)
                        throw new Exception("Streaming AssetBundle is null");

                    var assetR = originAssetBundle.LoadAssetAsync<AssetBundleManifest>("AssetBundleManifest");
                    yield return assetR;
                    var manifest = assetR.asset as AssetBundleManifest;

                    Assert.IsTrue(manifest);
                    foreach (var bundlesName in manifest.GetAllAssetBundles())
                    {
                        bundleDependencies.Add(bundlesName, manifest.GetAllDependencies(bundlesName));
                    }

                    originAssetBundle.Unload(true);
                }
            }

            /// <summary>
            /// 获取bundle依赖
            /// </summary>
            /// <param name="bundleName"></param>
            /// <returns></returns>
            internal string[] GetBundleDependencies(string bundleName)
            {
                if (!bundleDependencies.ContainsKey(bundleName))
                    return null;
                if (bundleDependencies[bundleName].Length == 0)
                    return null;
                else
                    return bundleDependencies[bundleName];
            }
        }

        #endregion
    }
}