最近轉戰unity3d,接的第一個任務就是關於資源管理的部分。html
因爲項目是web和standalone(微端)並存的,因此但願保證業務邏輯儘可能保持一致,跟以前ios,android的執行流程略有不一樣,好比在web模式下,FILE類是被禁用的,因此期望經過寫文件來操做相關功能參數的方法是不可行的。下面,是我對這方面的一些理解。本文中若是沒有特殊說明,下載的資源都是AssetBundleandroid
web程序的運行流程大體是ios
首先加載web.html,這是整個程序的入口web
他會加載相關的unity3d文件(本例中的web.unity3d)和unity3d平臺所須要的文件(各類js文件)算法
基本資源加載完成後,進入界面。瀏覽器
其餘使用時才下載的資源所有以AssetBundle的形式存放在遠程目錄,經過LoadFromCacheOrDownload函數傳入版本號進行下載。緩存
注意:unity爲web版本的程序啓用了50M的cache空間,全部下載的文件都存放在C:\Users\你的用戶名\AppData\LocalLow\Unity\WebPlayer\Cache\Shared目錄下函數
通過驗證,web程序對於文件下載執行以下步驟測試
請求文件:http://xxxx/test/a.ab
1.代碼中AB池裏是否存在
2.unity cache裏是否存在
3.瀏覽器緩存中是否存在
若是都不存在,則啓動下載
下載完成後,根據瀏覽器的policy存入瀏覽器緩存,而後根據unity的policy存入unity的cache,最後代碼中寫入AB池以備後續使用。this
standalone程序的運行流程跟web不太同樣
1.exe文件包含打包進去的unity3d文件(scene文件)
2.目錄,文件操做是啓用的,好比Directory,File等
注意:
使用LoadFromCacheOrDownload方式下載的文件都是存放在C:\Users\你的用戶名\AppData\LocalLow\Unity\WebPlayer\Cache目錄下,web版會放在Shard和Temp目錄下,而standalone版則會放在單獨的子目錄(一個默認工程的名字大體是DefaultCompany_xxx)
經過以上流程,能夠看出,standalone和web版本的加載方式不一樣,可是咱們能夠經過合理的處理達到實現不一樣可是使用一致的結果。
先放一張整圖,讓你們有個總體的概念
整個流程描述大體是
啓動遊戲的時候,經過HTTP or WWW的方式下載version文件,裏面保存【文件名, 修改時間, 依賴表】
當須要加載某個資源的時候,首先經過文件名獲取文件的依賴關係表,而後生成下載文件列表
好比你想下載a.ab,可是a.ab文件依賴於c1.ab, c4.ab,那麼最終生成的下載表就是[c1.ab, c4.ab, a.ab],製做之初我有個誤區,覺得有依賴關係的文件必須先下載依賴關係文件再下載目標文件,還特地寫了個有向圖的算法來計算關聯關係及執行順序,實際上通過測試,不須要。只要保證下載列表徹底下載完成再使用ab就ok了。
而後判斷一下在ABPool裏(咱們本身實現的AssetBundle池)是否已經存在這個ab,若是存在就直接返回ab的句柄
若是沒有,則執行分平臺下載流程
關於下載,上面已經解釋過,standalone和web版的加載方式是不同的,因此這裏對不一樣平臺作了專門的處理
standalone:
首先判斷本地是否有這個文件,若是有,則判斷本地這個文件的版本號是不是線上最新的版本號,若是不是,則經過HTTP方式下載文件,並保存到本地,而後經過WWW的方式將文件load到內存中,寫入ABPool以備後續使用
web:
傳入下載地址和版本號,unity本身會判斷是否須要下載文件。
通過研究,LoadFromCacheOrDownload後面的version參數其實至關於一個key,並無順序的概念,當你經過5下載某個文件成功以後,底層會將這個文件的版本號改寫成5,下次再調用的時候,就不會啓動下載而是從cache裏直接獲取。若是你使用4做爲版本號調用LoadFromCacheOrDownload的時候,底層會發現本地cache裏並無這個版本號的這個文件,而後從新下載。全部,只要版本號是變更的(惟一的,好比用文件修改時間做爲版本號)
最終返回List表示已經加載結束
好了,大體流程就是這樣,下面開始詳細講解每一個功能
1.版本文件生成
咱們這裏的版本文件存放幾個數據【文件名,verison,依賴表】
完整代碼是
1 public class VersionList 2 { 3 protected class Data { //元數據,存放文件名,修改時間和依賴表 4 public Data(string _key, int _modifyTime, List<string> _depends) { 5 key = _key; 6 depends = _depends; 7 modifyTime = _modifyTime; 8 } 9 10 public string key { get; set; } 11 public int modifyTime { get; set; } 12 public List<string> depends { get; set; } 13 } 14 15 static string s_bundleDir = "Res"; //導出目錄 16 static string s_versionFile = s_bundleDir + "/version.txt"; //資源文件 17 static Dictionary<string, Data> s_files = new Dictionary<string, Data>(); //文件信息字典 18 19 //計算時間戳並轉換成小時數(注意:這裏使用的是小時數做爲版本號,開發期間可能一個小時會打包屢次,可是對於線上這是不被容許的<若是出現這種狀況,說明開發和測試的工做沒作好>) 20 public static int dtToHours (DateTime dateTime) 21 { 22 var start = new DateTime(2016, 1, 1, 0, 0, 0, dateTime.Kind); 23 return Convert.ToInt32((dateTime - start). TotalHours); 24 } 25 26 //遍歷目錄,獲取相關信息 27 static void GetObjectInfoToArray(string path) 28 { 29 string[] fs = Directory.GetFiles (path); 30 foreach (string f in fs) { 31 if(f.Contains(".meta")) //忽略meta文件 32 continue; 33 FileInfo fi = new FileInfo(f); 34 if (!fi.Exists) 35 { 36 continue; 37 } 38 39 string _f = f.Replace( "\\", "/" ); //統一目錄分隔符 40 41 List<string> _depends = new List<string>(); 42 string []paths = AssetDatabase.GetDependencies(new string[]{_f}); //獲取當前文件的所依賴的文件列表 43 foreach(string p in paths){ 44 string _p = p.Replace("\\", "/"); 45 if (_p != _f) { 46 _depends.Add(_p); 47 } 48 } 49 50 //將文件名,更新時間,依賴列表寫入字典 51 s_files.Add(_f, new Data(_f, dtToHours (fi.LastWriteTime), _depends)); 52 } 53 54 try { 55 //遍歷子目錄 56 string[] ds = Directory.GetDirectories(path); 57 foreach(string d in ds) { 58 GetObjectInfoToArray (d); 59 } 60 } catch (System.IO.DirectoryNotFoundException) { 61 Debug.Log ("The path encapsulated in the " + path + "Directory object does not exist."); 62 } 63 } 64 65 [MenuItem( "VersionList/Generator" )] 66 static void Generator() 67 { 68 Debug.Log ("Begin Generator VersionList"); 69 s_files.Clear (); 70 71 //建立res目錄 72 if (!Directory.Exists(s_bundleDir)) 73 { 74 Directory.CreateDirectory(s_bundleDir); 75 } 76 77 //刪除老的version文件 78 if (File.Exists (s_versionFile)) { 79 File.Delete(s_versionFile); 80 } 81 82 //遍歷生成Assets下全部文件的版本信息 83 GetObjectInfoToArray ("Assets"); 84 if (s_files.Keys.Count == 0) { 85 return; 86 } 87 88 //自定義格式寫入文件 89 FileInfo vf = new FileInfo (s_versionFile); 90 StreamWriter sw = vf.CreateText (); 91 92 foreach (KeyValuePair<string, Data> kv in s_files) { 93 string tmp = kv.Key+";"+kv.Value.modifyTime; 94 if (kv.Value.depends != null && kv.Value.depends.Count > 0) { 95 tmp += ","; 96 for (int i=0; i<kv.Value.depends.Count-1; ++i) { 97 tmp += kv.Value.depends[i]; 98 tmp += ":"; 99 } 100 101 tmp += kv.Value.depends[kv.Value.depends.Count - 1]; 102 } 103 sw.WriteLine(tmp); 104 } 105 106 sw.Close(); 107 sw.Dispose(); 108 109 Debug.Log ("End Generator VersionList"); 110 } 111 }
加載version文件
1 IEnumerator LoadVersionFile() 2 { 3 Debug.Log("Begin Read VersionList"); 4 5 s_files.Clear(); 6 WWW www = new WWW("http://ldr123.mobi/webAU/test/version.ab"); 7 yield return www; 8 9 string result = www.assetBundle.mainAsset.ToString(); 10 string[] r = result.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); 11 foreach (string line in r) 12 { 13 string[] res = line.Split(';'); 14 if (res.Length == 2) 15 { 16 string key = res[0]; 17 string value = res[1]; 18 string[] values = value.Split(','); 19 int modifyTime = int.Parse(values[0]); 20 List<string> depends = null; 21 if (values.Length == 2) 22 { 23 string d = values[1]; 24 string[] ds = d.Split(':'); 25 if (ds.Length > 0) 26 { 27 depends = new List<string>(); 28 foreach (string x in ds) 29 { 30 depends.Add(x); 31 } 32 } 33 } 34 35 s_files.Add(key, new Data(key, modifyTime, depends)); 36 } 37 } 38 39 Debug.Log("End Read VersionList"); 40 }
AB池的代碼,以文件名爲key存放全部已經加載的AssetBundle句柄
1 public class AssetBundlePool { 2 private class Data 3 { 4 public Data(AssetBundle _ab, int _version) 5 { 6 ab = _ab; 7 version = _version; 8 } 9 10 public AssetBundle ab { get; set; } 11 public int version { get; set; } 12 } 13 14 private Dictionary<string, Data> abContent = null; 15 16 public static AssetBundlePool instance = null; 17 public static AssetBundlePool Instance() 18 { 19 if (instance == null) 20 { 21 instance = new AssetBundlePool(); 22 } 23 24 return instance; 25 } 26 27 public AssetBundlePool() 28 { 29 abContent = new Dictionary<string, Data>(); 30 } 31 32 public AssetBundle getAssetBundle(string name, int version = -1) 33 { 34 if (abContent.ContainsKey(name)) 35 { 36 Data d = abContent[name]; 37 if (version == -1 || d.version == version) 38 { 39 return d.ab; 40 } 41 } 42 43 return null; 44 } 45 46 public void setAssetBundle(string name, AssetBundle ab, int version) 47 { 48 if (getAssetBundle(name, version) == null) 49 { 50 abContent.Add(name, new Data(ab, version)); 51 } 52 } 53 54 //todo:unload all 55 //todo:unload single 56 }
AssetLoader的代碼
1 using UnityEngine; 2 using System.Collections; 3 using System.Collections.Generic; 4 5 #if UNITY_EDITOR || UNITY_STANDALONE 6 using System.IO; 7 #endif 8 9 public class AssetLoader 10 { 11 protected class Data 12 { 13 public Data(string _key, int _version, List<string> _depends) 14 { 15 key = _key; 16 depends = _depends; 17 version = _version; 18 } 19 20 public string key { get; set; } 21 public int version { get; set; } 22 public List<string> depends { get; set; } 23 } 24 25 private string m_strWWWAssetPath = "http://ldr123.mobi/webAU/test/"; 26 private string m_strStandaloneAssetPath = "Res/"; 27 private delegate void wwwCallback (string name); 28 private delegate void downCallback (); 29 30 private List<List<string>> m_downloadingRes = null; 31 private List<string> m_processing = null; 32 private MonoBehaviour m_mbHelper = null; 33 public bool m_bReady = false; 34 35 static Dictionary<string, Data> s_remoteFiles = new Dictionary<string, Data>(); 36 static Dictionary<string, Data> s_localFiles = new Dictionary<string, Data>(); 37 38 public AssetLoader (MonoBehaviour mb) 39 { 40 m_mbHelper = mb; 41 } 42 43 #if UNITY_WEBPLAYER 44 private IEnumerator webdownload(string filename, int version, wwwCallback callback) 45 { 46 while (!Caching.ready) 47 yield return null; 48 49 50 string assetPath = m_strWWWAssetPath + filename; 51 WWW www = WWW.LoadFromCacheOrDownload(assetPath, version); 52 yield return www; 53 54 if (!string.IsNullOrEmpty(www.error)) 55 { 56 yield return null; 57 } 58 else 59 { 60 AssetBundlePool.Instance().setAssetBundle(filename, www.assetBundle, version); 61 www.Dispose(); 62 www = null; 63 } 64 65 if (callback != null) 66 { 67 callback(filename); 68 } 69 } 70 #endif 71 72 #if UNITY_EDITOR || UNITY_STANDALONE 73 private IEnumerator standalonedownload(string filename, int version, wwwCallback callback) 74 { 75 string assetPath = m_strStandaloneAssetPath + filename; 76 bool needDownload = true; 77 FileInfo fi = new FileInfo(assetPath); 78 if (fi.Exists) 79 { 80 if (s_localFiles.ContainsKey(filename)) 81 { 82 if (s_localFiles[filename].version == version) 83 { 84 needDownload = false; 85 } 86 } 87 } 88 89 if (needDownload) 90 { 91 string assetWWWPath = m_strWWWAssetPath + filename; 92 WWW www = new WWW(assetWWWPath); 93 yield return www; 94 95 if (!string.IsNullOrEmpty(www.error)) 96 { 97 yield return null; 98 } 99 else 100 { 101 if (File.Exists(assetPath)) 102 { 103 File.Delete(assetPath); 104 } 105 106 using (FileStream fsWrite = new FileStream(assetPath, FileMode.Create, FileAccess.Write)) 107 { 108 using (BinaryWriter bw = new BinaryWriter(fsWrite)) 109 { 110 bw.Write(www.bytes); 111 } 112 } 113 114 if (s_localFiles.ContainsKey(filename)) 115 { 116 s_localFiles[filename].version = version; 117 } 118 else 119 { 120 Data d = s_remoteFiles[filename]; 121 s_localFiles.Add(filename, new Data(d.key, version, d.depends)); 122 } 123 124 AssetBundlePool.Instance().setAssetBundle(filename, www.assetBundle, version); 125 www.Dispose(); 126 www = null; 127 } 128 } 129 130 if (callback != null) 131 { 132 callback(filename); 133 } 134 } 135 #endif 136 137 private void download(string filename, wwwCallback callback) 138 { 139 if (!s_remoteFiles.ContainsKey(filename)) 140 { 141 return; 142 } 143 144 int version = s_remoteFiles[filename].version; 145 if (version == 0) 146 { 147 return; 148 } 149 150 if (AssetBundlePool.Instance().getAssetBundle(filename, version) != null) 151 { 152 if (callback != null) 153 { 154 callback(filename); 155 } 156 } 157 else 158 { 159 #if UNITY_WEBPLAYER 160 m_mbHelper.StartCoroutine(webdownload(filename, version, callback)); 161 #elif UNITY_EDITOR || UNITY_STANDALONE 162 m_mbHelper.StartCoroutine(standalonedownload(filename, version, callback)); 163 #endif 164 } 165 } 166 167 private void www_callback (string name) 168 { 169 m_processing.Remove (name); 170 171 //use ab 172 // AssetBundle ab = AssetBundlePool.Instance().getAssetBundle(name); 173 } 174 175 private IEnumerator startSubDownload (downCallback callback) 176 { 177 if (m_processing.Count > 0) { 178 foreach (string x in m_processing) { 179 download (x, www_callback); 180 } 181 } 182 183 while (m_processing.Count != 0) { 184 yield return null; 185 } 186 187 m_downloadingRes.RemoveAt (0); 188 189 if (callback != null) { 190 callback (); 191 } 192 } 193 194 private void _startDownload () 195 { 196 if (m_downloadingRes.Count == 0) { 197 return; 198 } 199 200 m_processing = m_downloadingRes[0]; 201 m_mbHelper.StartCoroutine (startSubDownload (delegate() { 202 _startDownload (); 203 })); 204 } 205 206 public IEnumerator startDownload (List<List<string>> lst) 207 { 208 m_bReady = false; 209 m_downloadingRes = lst; 210 if (m_downloadingRes.Count > 0) { 211 _startDownload (); 212 213 while (m_downloadingRes.Count != 0) { 214 yield return null; 215 } 216 } 217 218 m_bReady = true; 219 220 } 221 }
測試代碼
1 using UnityEngine; 2 using UnityEngine.UI; 3 using System.Collections; 4 using System.Collections.Generic; 5 6 public class load : MonoBehaviour 7 { 8 public Button click; 9 10 private AssetLoader dh = null; 11 IEnumerator loadRes (List<List<string>> lst) 12 { 13 log("begin loadRes"); 14 StartCoroutine (dh.startDownload (lst)); 15 while (!dh.m_bReady) { 16 yield return null; 17 } 18 19 click.gameObject.SetActive(true); 20 log("end loadRes"); 21 } 22 23 void Start () 24 { 25 dh = new AssetLoader(this); 26 click.GetComponent<Button> ().onClick.AddListener (delegate() { 27 this.Click (); 28 }); 29 } 30 31 void Click () 32 { 33 click.gameObject.SetActive(false); 34 StartCoroutine(loadRes(new List<List<string>>() 35 { 36 new List<string> (){"a.ab", "b.ab", "c.ab"}, 37 new List<string> (){"00.ab"} 38 })); 39 } 40 }