【原创】我所理解的资源加载方式

最近转战unity3d,接的第一个任务就是关于资源管理的部分。

由于项目是web和standalone(微端)并存的,所以希望保证业务逻辑尽量保持一致,跟之前ios,android的执行流程略有不同,比如在web模式下,FILE类是被禁用的,所以指望通过写文件来操作相关功能参数的方法是不可行的。下面,是我对这方面的一些理解。本文中如果没有特殊说明,下载的资源都是AssetBundle

web程序的运行流程大致是

首先加载web.html,这是整个程序的入口

他会加载相关的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池以备后续使用。

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 }

 

posted @ 2016-08-09 18:23  ldr123  阅读(513)  评论(0编辑  收藏  举报