当下游戏开发少不热更流程,作为游戏开发,我们必须熟悉几个关键的目录
 Unity中几个特殊路径的说明:
[dataPath} :返回程序的数据文件所在的文件夹的路径(只读)。返回路径为相对路径,一般是 相对于程序安装目录的位置。不同游戏平台的数据文件保存路径不同。
 StreamingAssetsPath: 此属性用于返回数据流的缓存目录,返回路径为相对路径,适合设置一些外部数据文件的路径。(只读)
[PersistentDataPath]:返回一个持久化数据存储目录的路径(可读可写),可以在此路径下存储一些持久化的数据文件。对应同一平台,在不同程序中调用此属性时,其返回值是相同的,但是在不同的运行平台下,其返回值会不一样。
[temporaryCachePath] :此属性用于返回一个临时数据的缓冲目录(可读可写)。对于同一平台,在不同程序中调用此属性时,其返回值是相同的,但是在不同的运行平台下,其返回值是不一样的。
persistentDataPath和temporaryCachePath的返回值一般是程序所在平台的固定位置,适合程序在运行过程中产生的数据文件。
IOS:
 Application.dataPath : Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxx.app/Data
 Application.streamingAssetsPath : Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxx.app/Data/Raw
 Application.persistentDataPath : Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/Documents
 Application.temporaryCachePath : Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/Library/Caches
Android:
 Application.dataPath : /data/app/xxx.xxx.xxx.apk
 Application.streamingAssetsPath : jar:file:///data/app/xxx.xxx.xxx.apk/!/assets
 Application.persistentDataPath : /data/data/xxx.xxx.xxx/files
 Application.temporaryCachePath : /data/data/xxx.xxx.xxx/cache
Windows:
 Application.dataPath : /Assets
 Application.streamingAssetsPath : /Assets/StreamingAssets
 Application.persistentDataPath : C:/Users/xxxx/AppData/LocalLow/CompanyName/ProductName
 Application.temporaryCachePath : C:/Users/xxxx/AppData/Local/Temp/CompanyName/ProductName
Mac:
 Application.dataPath : /Assets
 Application.streamingAssetsPath : /Assets/StreamingAssets
 Application.persistentDataPath : /Users/xxxx/Library/Caches/CompanyName/Product Name
 Application.temporaryCachePath : /var/folders/57/6b4_9w8113x2fsmzx_yhrhvh0000gn/T/CompanyName/Product Name
热更流程
步骤一、在Resources目录下新建一个文本,名称是bundle_list(后缀是.txt),内容如下: {“id”:0,“version”:“1.0”,“manifest”:“android”,“resource”:{xxxxx : hashMd5}},当然您可以根据自己项目 实际情况来设计json格式。资源服务器上也会有一份格式相同的bundle_list
步骤二、如果是第一次进入游戏,Application.persistentDataPath目录下还没有bundle_list文件,这 时候就需要用Resources.Load方法从Resources目录中加载出来。否则 加载Application.persistentDataPath目录下的bundle_list
步骤三、从资源服务器下载bundle_list文件
步骤四、获取本地bundle_list的id和资源服务器下载的bundle_list中的id,做对比,如果前者等于后者,则不需要更新,如果前者小于后者,则需要更新。
步骤五、分别解析出本地和资源服务器bundle_list中的资源路径名称,名称相同的,对比hash值,相同 则不需要更新,反之,更新。如果资源服务器有的名称本地没有,则表示是新增资源,需要 下载到本地。
步骤六、把资源服务器的bundle_list覆盖本地bundle_list。热更新完成。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Text;
using System.IO;
//using LitJson;
/**  * 资源增量更新  
* 1.检查本地Application.persistentDataPath目录中是否有bundle_list文件  
* 2.如果没有,则从Resources目录中读取bundle_list文件
 * 3.从服务器上下载bundle_list文件,判断版本是否一致,如果一致就不用更新
 * 4.版本不一致,需要更新,更新  
* 5.将最新的bundle_list存入Application.persistentDataPath目录中
 **/
public class TestHot : MonoBehaviour
{
    private static readonly string VERSION_FILE = "bundle_list";
    private string SERVER_RES_URL = "";
    private string LOCAL_RES_URL = "";
    private string LOCAL_RES_PATH = "";
    /// 本地版本json对象
    private JsonData jdLocalFile;
    /// 服务端版本json对象
    private JsonData jdServerFile;
    /// 本地资源名和路径字典
    private Dictionary<string, string> LocalBundleVersion;
    /// 服务器资源名和路径字典
    private Dictionary<string, string> ServerBundleVersion;
    /// 需要下载的文件List
    private List<string> NeedDownFiles;
    /// 是否需要更新本地版本文件
    private bool NeedUpdateLocalVersionFile = false;
    /// 下载完成委托
    public delegate void HandleFinishDownload(WWW www);
    /// 本次一共需要更新的资源数
    int totalUpdateFileCount = 0;
    void Start()
    {
#if UNITY_EDITOR && UNITY_ANDROID
SERVER_RES_URL = "file:///" + Application.streamingAssetsPath + "/android/";
LOCAL_RES_URL = "file:///" + Application.persistentDataPath + "/res/";
LOCAL_RES_PATH = Application.persistentDataPath + "/res/";
#elif UNITY_EDITOR && UNITY_IOS
SERVER_RES_URL = "file://" + Application.streamingAssetsPath + "/ios/";
LOCAL_RES_URL =  "file:///" + Application.persistentDataPath + "/res/";
LOCAL_RES_PATH =  Application.persistentDataPath + "/res/";
#elif UNITY_ANDROID
//安卓下需要使用www加载StreamingAssets里的文件,Streaming Assets目录在安卓下的路径为 "jar:file://" + Application.dataPath + "!/assets/"
SERVER_RES_URL =  "jar:file://" + Application.dataPath + "!/assets/" + "android/";
LOCAL_RES_URL =  "jar:file://" + Application.persistentDataPath + "!/assets/" + "/res/";
//LOCAL_RES_URL =  "file://" + Application.persistentDataPath + "/res/";
LOCAL_RES_PATH =  Application.persistentDataPath + "/res/";
#elif UNITY_IOS
 SERVER_RES_URL = "http://127.0.0.1/resource/ios/"
LOCAL_RES_URL =  "file:///" + Application.persistentDataPath + "/res/";
LOCAL_RES_PATH =  Application.persistentDataPath + "/res/";
#endif
        //初始化    
        LocalBundleVersion = new Dictionary<string, string>();
        ServerBundleVersion = new Dictionary<string, string>();
        NeedDownFiles = new List<string>();
        //加载本地version配置    
        string tmpLocalVersion = "";
        if (!File.Exists(LOCAL_RES_PATH + VERSION_FILE))
        {
            //persisten 不存在,去Resources里面Load
            TextAsset text = Resources.Load(VERSION_FILE) as TextAsset;
            tmpLocalVersion = text.text;
        }
        else
        {
            //加载persisten 里面的
            tmpLocalVersion = File.ReadAllText(LOCAL_RES_PATH + VERSION_FILE);
        }
        //保存本地的version    
        ParseVersionFile(tmpLocalVersion, LocalBundleVersion, 0);
        //加载服务端version配置    
        StartCoroutine(this.DownLoad(SERVER_RES_URL + VERSION_FILE, delegate (WWW serverVersion)
        {
            //保存服务端version    
            ParseVersionFile(serverVersion.text, ServerBundleVersion, 1);
            //计算出需要重新加载的资源    
            CompareVersion();
            //加载需要更新的资源    
            DownLoadRes();
        }));
    }
    //依次加载需要更新的资源  
    private void DownLoadRes()
    {
        if (NeedDownFiles.Count == 0)
        {
            UpdateLocalVersionFile();
            return;
        }
        string file = NeedDownFiles[0];
        NeedDownFiles.RemoveAt(0);
        StartCoroutine(this.DownLoad(SERVER_RES_URL + file, delegate (WWW w)
        {
            //将下载的资源替换本地就的资源    
            ReplaceLocalRes(file, w.bytes);
            //循环下载
            DownLoadRes();
        }));
    }
    private void ReplaceLocalRes(string fileName, byte[] data)
    {
        try
        {
            string filePath = LOCAL_RES_PATH + fileName;
            if (!File.Exists(filePath))
            {
                string p = Path.GetDirectoryName(filePath);
                if (!Directory.Exists(p))
                {
                    Directory.CreateDirectory(p);
                }
            }
            File.WriteAllBytes(filePath, data);
        }
        catch (System.Exception e)
        {
            Debug.Log("e is " + e.Message);
        }
    }
    //更新本地的version配置    
    private void UpdateLocalVersionFile()
    {
        if (NeedUpdateLocalVersionFile)
        {
            if (!Directory.Exists(LOCAL_RES_PATH))
            {
                Directory.CreateDirectory(LOCAL_RES_PATH);
            }
            StringBuilder versions = new StringBuilder(jdServerFile.ToJson());
            FileStream stream = new FileStream(LOCAL_RES_PATH + VERSION_FILE, FileMode.Create);
            byte[] data = Encoding.UTF8.GetBytes(versions.ToString());
            stream.Write(data, 0, data.Length);
            stream.Flush();
            stream.Close();
        }
    }
}
private void CompareVersion()
{
    int localVersionId;
    int serverVersionId;
    if (jdLocalFile != null && jdLocalFile.Keys.Contains("id"))
    {
        localVersionId = (int)jdLocalFile["id"];
    }
    if (jdServerFile != null && jdServerFile.Keys.Contains("id"))
    {
        serverVersionId = (int)jdServerFile["id"];
    }
#if UNITY_ANDROID || UNITY_EDITOR
    NeedDownFiles.Add("android");
#endif
#if UNITY_IOS
#endif
    foreach (var version in ServerBundleVersion)
    {
        string fileName = version.Key;
        string serverHash = version.Value;
        //新增的资源    
        if (!LocalBundleVersion.ContainsKey(fileName))
        {
            NeedDownFiles.Add(fileName);
        }
        else
        {
            //需要替换的资源    
            string localHash;
            LocalBundleVersion.TryGetValue(fileName, out localHash);
            if (!serverHash.Equals(localHash))
            {
                NeedDownFiles.Add(fileName);
            }
        }
    }
    totalUpdateFileCount = NeedDownFiles.Count;
    //本次有更新,同时更新本地的version.txt    
    NeedUpdateLocalVersionFile = NeedDownFiles.Count > 0;
}
//0表示本地版本文件,1表示服务器版本文件
private void ParseVersionFile(string content, Dictionary<string, string> dict, int flag)
{
    if (content == null || content.Length == 0)
    {
        return;
    }
    JsonData jd = null;
    try
    {
        jd = JsonMapper.ToObject(content);
    }
    catch (System.Exception e)
    {
        Debug.LogError(e.Message);
        return;
    }
    if (flag == 0)//本地
    {
        jdLocalFile = jd;
    }
    else if (flag == 1)//服务器
    {
        jdServerFile = jd;
    }else
    {
        return;
    }
    //获取资源对象
    JsonData resObjs = null;
    if (jd.Keys.Contains("resource"))
        resObjs = jd["resource"];
    if (resObjs != null && resObjs.IsObject && resObjs.Count > 0)
    {
        string[] resNames = new string[resObjs.Count];
        resObjs.Keys.CopyTo(resNames, 0);
        for (int i = 0; i < resNames.Length; i++)
        {
            if (resObjs.Keys.Contains(resNames[i]))
                dict.Add(resNames[i], resObjs[resNames[i]].ToString());
        }
    }
}
private IEnumerator DownLoad(string url, HandleFinishDownload finishFun)
{
    WWW www = new WWW(url);
    yield return www;
    if (!string.IsNullOrEmpty(www.error))
    {
        Debug.LogError("www.error is " + www.error);
        yield break;
    }
    if (finishFun != null)
    {
        finishFun(www);
    }
    www.Dispose();
}
}
 
参考文档
 https://blog.csdn.net/qq_31967569/article/details/83059662?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1.pc_relevant_default&utm_relevant_index=2










