unity简单易懂的版本对比和资源更新

项目中没有使用lua脚本,所以热更只能资源更新,一旦逻辑代码上有改变,需要玩家自行更新游戏客户端.

本人把公司项目中的更新模块精简了下,还是画张流程图解释:

unity简单易懂的版本对比和资源更新_第1张图片

一般资源打入安装包有3种情况:

1.安装包没有资源,进入游戏后资源全部从资源服务器下载.   

    (包体最小 ,但第一次进入游戏耗时最长)

2.安装包有部分资源,剩余的资源还是从服务器下载.

    (包体介于1-3之间,耗时也介于1-3之间)

3.安装包包含所有资源. 

    (包体最大,耗时最短)

 

所以在资源更新前先判断了是否第一次进入游戏,是否需要做解压缩的操作. 

另外为了让逻辑更清晰版本对比和资源更新分开写成了2个单例类. 

 

不扯淡直接干 ̄へ ̄:

 

1.版本对比管理器逻辑:  

        服务器有一份版本号文件version.txt 像这样:  

        获取该文件使用get方式,unity自带有类UnityWebRequest,他专门操作web请求.当然用www也是可以的,不过貌似官方要逐渐舍弃掉www?

    (只要能取下文件就行方法不重要,小哥写代码从来都是复制粘贴稳得很  ✧(≖ ◡ ≖✿ )

 

       客户端版本号在这里设置:unity简单易懂的版本对比和资源更新_第2张图片

        获取直接使用Application.version


  版本号对比也不用傻不拉几的手写实现,c#库中已经提供了这玩意儿(System.Version类)为什么不用呢?  

 

版本管理器类

UpdateVersionManager.cs

public class UpdateVersionManager:MonoSingleton
{
    private System.Version curVersion;
    private System.Version onlineVersion;
    public bool IsNeedUpdate;
    public void CheckVersion(UnityAction onComplate = null) {
        IsNeedUpdate = false;
        StartCoroutine(progress(onComplate));
    }
    IEnumerator progress(UnityAction onComplate) {
        //拉取服务器版本
        MsgDispatcher.GetInstance().Fire(GameEvents.Msg_ShowLoadingContent, "检测版本号...");
        UnityWebRequest req = UnityWebRequest.Get(GameConfigs.ServerVersionUrl);
        yield return req.SendWebRequest();
        if(req.isHttpError || req.isNetworkError) {
            Debug.LogError(req.error);
            yield break;
        }
        onlineVersion = new System.Version(req.downloadHandler.text);
        curVersion = new System.Version( Application.version);
        if(onlineVersion != curVersion) {
            Debug.LogFormat("当前版本不是最新版本({0}),请及时更新到最新版本({1})",curVersion,onlineVersion);
            IsNeedUpdate = true;
        }
        Debug.Log("版本检测完成!");
        if (onComplate != null) {
            onComplate(IsNeedUpdate);
        }
        yield return null;
    }
}

 

2.资源更新逻辑:

    

    解压缩安装包数据(第一次)  -> 加载本地ab根目录下的manifest文件,该文件包含所有资源的信息和hash值  ->通过UnityWebRequest获取资源服务器上ab根目录的manifest文件,并加载他

 ->对比本地和资源的每个文件hash值,只要有hash不同的或者本地没有的统统放入下载列表中 -> 根据下载列表下载资源 ->全部下载完成,更新本地manifset文件.

 

资源更新管理类

UpdateAssetManager.cs

public class UpdateAssetManager:MonoSingleton
{
    private AssetBundleManifest curManifest;
    private AssetBundleManifest onlineManifest;
    public void CheckAsset(UnityAction onComplete =null) {
        MsgDispatcher.GetInstance().Fire(GameEvents.Msg_ShowLoadingContent, "检测资源...");
        StartCoroutine(progress(onComplete));
    }
    IEnumerator progress(UnityAction onComplete) {
        //第一次进入游戏 把streamingassets文件夹数据解压缩到指定的下载目录
        if(true || PlayerPrefs.GetString("IsFirstLaunch","true") == "true") {
            yield return StartCoroutine(streamingAssetfolderCopyToDownloadFolder());
        }
        // 加载本地 manifest文件
        if (File.Exists(GameConfigs.LocalManifestPath)) {
            var manifestAB = AssetBundle.LoadFromFile(GameConfigs.LocalManifestPath);
            curManifest = manifestAB.LoadAsset("AssetBundleManifest");
            manifestAB.Unload(false);
        } else {
            Debug.Log("本地资源文件丢失:" + GameConfigs.LocalManifestPath);
        }
        //获取资源服务器端manifest
        Debug.Log("获取资源服务器资源manifest :"+ GameConfigs.OnlineManifestPath);
        MsgDispatcher.GetInstance().Fire(GameEvents.Msg_ShowLoadingContent, "检测是否更新资源...");
        UnityWebRequest webReq = UnityWebRequest.Get(GameConfigs.OnlineManifestPath);
        yield return webReq.SendWebRequest();
        if (webReq.isNetworkError || webReq.isHttpError) {
            Debug.Log(webReq.error);
        } else {
            if(webReq.responseCode == 200) {
                byte[] result = webReq.downloadHandler.data;
                AssetBundle onlineManifestAB = AssetBundle.LoadFromMemory(result);
                onlineManifest = onlineManifestAB.LoadAsset("AssetBundlemanifest");
                onlineManifestAB.Unload(false);
                //更新本地manifest
                writeFile(GameConfigs.LocalManifestPath, webReq.downloadHandler.data);
            }
            yield return StartCoroutine(download());
            if (onComplete != null) {
                onComplete();
            }
        }
    }
  
    // streamingAsset文件夹数据解压缩到下载文件夹
    IEnumerator streamingAssetfolderCopyToDownloadFolder() {
        Debug.Log("初次运行,解压缩包数据到本地下载文件夹!");
        MsgDispatcher.GetInstance().Fire(GameEvents.Msg_ShowLoadingContent, "解压缩包数据...");
        string srcmanifestpath = GameConfigs.StreamingAssetManifestPath;

        if (Directory.Exists(GameConfigs.GameResExportPath)) {
            Debug.Log("存在:" + GameConfigs.GameResExportPath);
            //获取该文件夹下所有文件(包含子文件夹)
            var list = PathUtils.GetFilesPath(GameConfigs.GameResExportPath);
            int total = list.Length;
            int count = 0;
            foreach (var iter in list) {
                string srcPath = iter;
                string tarPath = iter.Replace(GameConfigs.GameResExportPath, GameConfigs.LocalABRootPath);
                UnityWebRequest req = UnityWebRequest.Get(srcmanifestpath);
                yield return req.SendWebRequest();
                if (req.isNetworkError || req.isHttpError) {
                    Debug.Log(req.error);
                } else {
                    if (File.Exists(tarPath)) {
                        File.Delete(tarPath);
                    } else {
                        PathUtils.CreateFolderByFilePath(tarPath);
                    }
                    FileStream fs2 = File.Create(tarPath);
                    fs2.Write(req.downloadHandler.data, 0, req.downloadHandler.data.Length);
                    fs2.Flush();
                    fs2.Close();
                    Debug.LogFormat("->解压缩文件{0}到{1}成功", srcPath, tarPath);
                }
                count++;
                MsgDispatcher.GetInstance().Fire(GameEvents.Msg_DownloadProgress, string.Format("解压缩包数据...({0}/{1})",count,total));
            }
        } else {
            Debug.Log("无需解压缩!");
        }
       
    }
    IEnumerator download() {
        var downloadFileList = getDownloadFileName();
        int totalCount = downloadFileList.Count;
        int count = 0;
        if (totalCount <= 0) {
            Debug.Log("没有需要更新的资源");
        } else {
            foreach (var iter in downloadFileList) {
                string path = GameConfigs.ResServerUrl + "/" + GameConfigs.CurPlatformName + "/" + iter;
                UnityWebRequest req = UnityWebRequest.Get(path);
                yield return req.SendWebRequest();
                if (req.isNetworkError) {
                    Debug.Log(req.error);
                    yield return null;
                } else {
                    if (req.responseCode == 200) {
                        byte[] result = req.downloadHandler.data;
                        //save file
                        string downloadPath = GameConfigs.LocalABRootPath + "/" + iter;
                        writeFile(downloadPath, result);
                        Debug.LogFormat("写入:{0} 成功 -> {1} | len =[{2}]", path, downloadPath, result.Length);
                        AssetBundle onlineManifestAB = AssetBundle.LoadFromMemory(result);
                        onlineManifest = onlineManifestAB.LoadAsset("AssetBundlemanifest");
                        onlineManifestAB.Unload(false);
                    }
                }
                count++;
                MsgDispatcher.GetInstance().Fire(GameEvents.Msg_DownloadProgress, string.Format("下载资源...({0}/{1})", count, totalCount));
                yield return new WaitForEndOfFrame();
            }
        }
    }
    //获取需要下载的文件列表
    private List getDownloadFileName() {
        if(curManifest == null) {
            if(onlineManifest == null) {
                return new List();
            } else {
                return new List(onlineManifest.GetAllAssetBundles());
            }
        }
        List tempList = new List();
        var curHashCode = curManifest.GetHashCode();
        var onlineHashCode = onlineManifest.GetHashCode();
        if (curHashCode != onlineHashCode) {
            // 比对筛选
            var curABList = curManifest.GetAllAssetBundles();
            var onlineABList = onlineManifest.GetAllAssetBundles();
            Dictionary curABHashDic = new Dictionary();
            foreach(var iter in curABList) {
                curABHashDic.Add(iter, curManifest.GetAssetBundleHash(iter));
            }
            foreach(var iter in onlineABList) {
                if (curABHashDic.ContainsKey(iter)) { //本地有该文件 但与服务器不同
                    Hash128 onlineHash = onlineManifest.GetAssetBundleHash(iter);
                    if(onlineHash != curABHashDic[iter]) {
                        tempList.Add(iter);
                    }
                } else {
                    tempList.Add(iter);
                }
            }
        }
        return tempList;
    }
    private void writeFile(string path,byte[] data) {
        FileInfo fi = new FileInfo(path);
        DirectoryInfo dir = fi.Directory;
        if (!dir.Exists) {
            dir.Create();
        }
        FileStream fs = fi.Create();
        fs.Write(data, 0, data.Length);
        fs.Flush();
        fs.Close();
    }
}

 

在运行上面流程之前,我们需要:

1.把资源打成ab包和ab包的使用请参见:https://blog.csdn.net/hl1991825/article/details/84327622

2.搭建一个本地资源服务器(云服务器大善!).

    我使用的xampp,一键安装方便.安装好后启动Apache服务(浏览器访问下127.0.0.1 可以看看本地web端口是否开启):

    unity简单易懂的版本对比和资源更新_第3张图片

    

    然后把ab包资源放入这个文件夹(具体地址按照你的xampp安装目录来):

    unity简单易懂的版本对比和资源更新_第4张图片

   整个ResServer文件夹应该是这样,对应平台就看项目需求了: 

 

大功告成,来写个测试类运行:

Launcher.cs

public class Launcher : MonoBehaviour ,IMsgReceiver{
    public Text Content;
    public Image Img;
    public Button Btn;
    public GameObject MsgBox;
    void OnEnable() {
        MsgDispatcher.GetInstance().Subscribe(GameEvents.Msg_ShowLoadingContent, this);
        MsgDispatcher.GetInstance().Subscribe(GameEvents.Msg_DownloadProgress, this);
        MsgDispatcher.GetInstance().Subscribe(GameEvents.Msg_DownloadFinish, this);
    }
    void OnDisable() {
        MsgDispatcher.GetInstance().UnSubscribe(GameEvents.Msg_ShowLoadingContent, this);
        MsgDispatcher.GetInstance().UnSubscribe(GameEvents.Msg_DownloadProgress, this);
        MsgDispatcher.GetInstance().UnSubscribe(GameEvents.Msg_DownloadFinish, this);
    }
    // Use this for initialization
    void Start () {
        Btn.gameObject.SetActive(false);
        Img.gameObject.SetActive(false);
        MsgBox.SetActive(false);
        Btn.onClick.AddListener(onClickedBtn);
        UpdateVersionManager.Instance.CheckVersion((bool needUpdate) => {
            if (needUpdate) {
                MsgBox.SetActive(true);
            } else {
                UpdateAssetManager.Instance.CheckAsset(() => {
                    MsgDispatcher.GetInstance().Fire(GameEvents.Msg_DownloadFinish);
                });
            }
        });
    }
    public bool OnMsgHandler(string msgName, params object[] args) {
        switch (msgName) {
            case GameEvents.Msg_ShowLoadingContent:
                Content.text = (string)args[0];
                break;
            case GameEvents.Msg_DownloadProgress:
                Content.text = (string)args[0];
                break;
            case GameEvents.Msg_DownloadFinish: {
                    AssetManager.Instance.InitMode(GameConfigs.LoadAssetMode);
                    Content.text = "资源更新完成";
                    Btn.gameObject.SetActive(true);
                    Img.gameObject.SetActive(true);
                  
                }
                break;
        }
        return true;
    }
   
    void onClickedBtn() {
        AssetManager.Instance.LoadAssetAsync(GameConfigs.GetSpriteAtlasPath("ui_atlas"), (SpriteAtlas sp) => {
            Sprite p = sp.GetSprite("icon_2");
            Img.sprite = sp.GetSprite(string.Format("icon_{0}",Random.Range(0,sp.spriteCount-1)));
        });
    }
}

 

效果:

 

 

 

注意:

1.跨平台路径问题 , StreamingAssetPath , DataPath , PersisentDataPath , TempDataPath ...

    这篇大牛文章讲得非常清除:https://www.cnblogs.com/murongxiaopifu/p/4199541.html

2.资源服务器的地址是直接写入代码的,最好从服务端获取.

3.资源更新和版本号无关了,无论是大版本差异还是小版本差异,反正有差异我就下(大版本直接提醒更新客户端了都,而且这样也不用操心跨版本资源下载的问题,简单暴力)

4.消息派发瞅瞅这个:https://blog.csdn.net/hl1991825/article/details/84112869

 

 

移动端仍旧没有测试,码字已经掏空我的热情,请自行测试吧.

以上代码只起抛砖引玉的作用,欢迎各位看官把玉借我瞅瞅 ( ̄▽ ̄)~*

 

项目工程(unity2017):https://pan.baidu.com/s/1eiySubZm4ZyK8ux8DYYSkA

提取码:d9rx

 

你可能感兴趣的:(unity)