zoukankan      html  css  js  c++  java
  • 【原创】我所理解的资源加载方式

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

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

    web程序的运行流程大致是

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

    他会加载相关的unity3d文件(本例中的web.unity3d)和unity3d平台所需要的文件(各种js文件)

    基本资源加载完成后,进入界面。

    其他使用时才下载的资源全部以AssetBundle的形式存放在远程目录,通过LoadFromCacheOrDownload函数传入版本号进行下载。

    注意:unity为web版本的程序启用了50M的cache空间,所有下载的文件都存放在C:Users你的用户名AppDataLocalLowUnityWebPlayerCacheShared目录下

    经过验证,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你的用户名AppDataLocalLowUnityWebPlayerCache目录下,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[] { "
    " }, 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 }
  • 相关阅读:
    一起talk C栗子吧(第九十回:C语言实例--使用管道进行进程间通信三)
    集群技术(三)MySQL集群深度解析
    ZOJ 3609 Modular Inverse(扩展欧几里德)
    8,16小感
    Dagger2----一个最简单的Dagger2依赖的实现
    android:模拟水波效果的自己定义View
    SQL Server 运行计划操作符具体解释(1)——断言(Assert)
    参数类型 (实体类层)eneity或pojo 常用参数类型
    参数类型 (@Controller层)
    参数类型 (@Service层) impl
  • 原文地址:https://www.cnblogs.com/ldr123/p/5754250.html
Copyright © 2011-2022 走看看