【原創】我所理解的資源加載方式

最近轉戰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 }
相關文章
相關標籤/搜索