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 }
  • 相关阅读:
    Codeforces 1255B Fridge Lockers
    Codeforces 1255A Changing Volume
    Codeforces 1255A Changing Volume
    leetcode 112. 路径总和
    leetcode 129. 求根到叶子节点数字之和
    leetcode 404. 左叶子之和
    leetcode 104. 二叉树的最大深度
    leetcode 235. 二叉搜索树的最近公共祖先
    450. Delete Node in a BST
    树的c++实现--建立一棵树
  • 原文地址:https://www.cnblogs.com/ldr123/p/5754250.html
Copyright © 2011-2022 走看看