【GFFrameWork】代码热更及资源下载

版权声明:本文为博主原创文章,未经博主允许不得转载。Unity交流群:159875734 https://blog.csdn.net/s10141303/article/details/89181307

前言

现在比较主流的逻辑更新是Lua方式,最常用的ToLua、SLua,XLua等等,但笔者是不太喜欢Lua开发,相比较直接写C#,lua的效率要低得多,这里GFFrameWork选择的是C#热更方案ILRuntime,这个方案由掌趣公司蓝大大维护,据我了解目前用这方案开发的项目还是不少的,ET框架也是采用这种方案,ILRuntime的前身其实是李总开发的L#方案,由于在大概15年左右,逻辑热更方案大战中L#方案没有sLua、uLua宣传的好,所以用的人就少了,然后李总就不太维护了,后面就有掌趣的蓝大接手维护,直到现在18年左右用的人还算不少,可能大家都比较信赖大厂维护吧,这跟Lua更新方案xLua类似,腾讯专人维护,后来居上的Lua热更方案,我用了一下ILRuntime,用一个demo尝试了一下,感觉还是蛮方便的,所以GFFrameWork就采用这种方案。关于AB更新和资源下载,我之前有写过一篇资源更新也可以先看一下。

C# VS Lua

Lua是一个已经非常成熟的解决方案,但是对于Unity项目而言,也有非常明显的缺点。就是如果使用Lua来进行逻辑开发,就势必要求团队当中的人员需要同时对Lua和C#都特别熟悉,或者将团队中的人员分成C#小组和Lua小组。不管哪一种方案,对于中小型团队都是非常痛苦的一件事情。

用C#来作为热更语言最大的优势就是项目可以用同一个语言来进行开发,对Unity项目而言,这种方式肯定是开发效率最高的。

Lua的优势在于解决方案足够成熟,之前的C++团队可能比起C#,更加习惯使用Lua来进行逻辑开发。此外借助luajit,在某些情况下的执行效率会非常不错,但是luajit现在维护情况也不容乐观,官方还是推荐使用公版Lua来开发。

效果

在这里插入图片描述
下方会有资源下载进度,这也是游戏首页下载资源的常用方式,当然也可以显示下载百分比。正式上线的游戏一般还会提示要下载多少M的文件,提醒玩家尽量选择Wifi环境下下载。

代码更新

ILRuntime

具体ILRuntime相关的文章可以参考,我这边搬运一点注意点主要是方便给自己看的。

优势
  • 无缝访问C#工程的现成代码,无需额外抽象脚本API
  • 直接使用VS2015进行开发,ILRuntime的解译引擎支持.Net 4.6编译的DLL
  • 执行效率是L#的10-20倍
  • 选择性的CLR绑定使跨域调用更快速,绑定后跨域调用的性能能达到slua的2倍左右(从脚本调用GameObject之类的接口)
  • 支持跨域继承
  • 完整的泛型支持
  • 拥有Visual Studio的调试插件,可以实现真机源码级调试。支持Visual Studio 2015 Update3 以及Visual Studio 2017
代码案例
ILRuntime.Runtime.Enviorment.AppDomain appdomain;
void Start()
{
    StartCoroutine(LoadILRuntime());
}

IEnumerator LoadILRuntime()
{
    appdomain = new ILRuntime.Runtime.Enviorment.AppDomain();
#if UNITY_ANDROID
    WWW www = new WWW(Application.streamingAssetsPath + "/Hotfix.dll");
#else
    WWW www = new WWW("file:///" + Application.streamingAssetsPath + "/Hotfix.dll");
#endif
    while (!www.isDone)
        yield return null;
    if (!string.IsNullOrEmpty(www.error))
        D.error(www.error);
    byte[] dll = www.bytes;
    www.Dispose();
#if UNITY_ANDROID
    www = new WWW(Application.streamingAssetsPath + "/Hotfix.pdb");
#else
    www = new WWW("file:///" + Application.streamingAssetsPath + "/Hotfix.pdb");
#endif
    while (!www.isDone)
        yield return null;
    if (!string.IsNullOrEmpty(www.error))
        D.error(www.error);
    byte[] pdb = www.bytes;
    using (System.IO.MemoryStream fs = new MemoryStream(dll))
    {
        using (System.IO.MemoryStream p = new MemoryStream(pdb))
        {
            appdomain.LoadAssembly(fs, p, new Mono.Cecil.Pdb.PdbReaderProvider());
        }
    }
    OnILRuntimeInitialized();
}

void OnILRuntimeInitialized()
{
    appdomain.Invoke("Hotfix.Game", "Initialize", null, null);
}
官方Demo

https://github.com/Ourpalm/ILRuntimeU3D

调试插件

https://github.com/Ourpalm/ILRuntime/releases

GFFrameWork打包dll

操作

在这里插入图片描述

说明

这里两种编译方式,分别是对应用Mono的Api编译和.Net的Api编译,编译会生成dll和pdb(.net)/mdb(mono),我们是Unity开发所以基本首选mono编译,pdb和mdb是调试文件。

编译代码
using System.Collections.Generic;
using System.CodeDom.Compiler;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Debug = UnityEngine.Debug;
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;

#endif
public class ScriptBiuldTools
{
    public enum BuildStatus
    {
        Success = 0,
        Fail
    }

#if UNITY_EDITOR
    /// <summary>
    /// 编译DLL
    /// </summary>
    static public void GenDllByMono(string dataPath, string outPath)
    {
        //这里是引入unity所有引用的dll
#if UNITY_EDITOR_OSX
        var u3dUI =  EditorApplication.applicationContentsPath+@"/UnityExtensions/Unity/GUISystem";
        var u3dEngine = EditorApplication.applicationContentsPath+@"/Managed/UnityEngine";
#else
        var u3dUI = EditorApplication.applicationContentsPath + @"\UnityExtensions\Unity\GUISystem";
        var u3dEngine = EditorApplication.applicationContentsPath + @"\Managed\UnityEngine";
#endif

        if (Directory.Exists(u3dUI) == false || Directory.Exists(u3dEngine) == false)
        {
            EditorUtility.DisplayDialog("提示", "u3d根目录不存在,请修改ScriptBiuld_Service类中,u3dui 和u3dengine 的dll目录", "OK");
            return;
        }

        //编译项目的base.dll
        EditorUtility.DisplayProgressBar("编译服务", "准备编译dll", 0.1f);

        //准备工作
        var path = outPath + "/Hotfix";
        if (Directory.Exists(path))
        {
            Directory.Delete(path, true);
        }

        Directory.CreateDirectory(path);


        string[] searchPath = new string[] { "3rdPlugins", "Code", "Plugins", "Resource" };

        for (int i = 0; i < searchPath.Length; i++)
        {
            searchPath[i] = IPath.Combine(dataPath, searchPath[i]);
        }

        List<string> files = new List<string>();
        foreach (var s in searchPath)
        {
            var fs = Directory.GetFiles(s, "*.*", SearchOption.AllDirectories).ToList();
            var _fs = fs.FindAll(f =>
            {
                var exten = Path.GetExtension(f).ToLower();
                if (f.ToLower().Contains("editor") == false && (exten.Equals(".dll") || exten.Equals(".cs")))
                {
                    return true;
                }

                return false;
            });

            files.AddRange(_fs);
        }

        files = files.Distinct().ToList();
        for (int i = 0; i < files.Count; i++)
        {
            files[i] = files[i].Replace('\\', '/').Trim();
        }

        EditorUtility.DisplayProgressBar("编译服务", "开始整理script", 0.2f);

        var refDlls = files.FindAll(f => f.EndsWith(".dll"));
        var baseCs = files.FindAll(f => !f.EndsWith(".dll") && !f.Contains("@hotfix"));
        var hotfixCs = files.FindAll(f => !f.EndsWith(".dll") && f.Contains("@hotfix"));


#if UNITY_EDITOR_OSX
        var tempDirect = Application.persistentDataPath + "/bd_temp";
#else
        var tempDirect = "c:/bd_temp";
#endif
        if (Directory.Exists(tempDirect))
        {
            Directory.Delete(tempDirect, true);
        }

        Directory.CreateDirectory(tempDirect);

        //除去不需要引用的dll
        for (int i = refDlls.Count - 1; i >= 0; i--)
        {
            var str = refDlls[i];
            if (str.Contains("iOS") || str.Contains("Android"))
            {
                refDlls.RemoveAt(i);
            }
        }

        //去除同名 重复的dll
        for (int i = 0; i < refDlls.Count; i++)
        {
            var copyto = IPath.Combine(tempDirect, Path.GetFileName(refDlls[i]));
            File.Copy(refDlls[i], copyto, true);
            refDlls[i] = copyto;
        }

        refDlls.Add("System.dll");
        refDlls.Add("System.Core.dll");
        refDlls.Add("System.XML.dll");
        refDlls.Add("System.Data.dll");

        //dll1
        var u3ddlls1 = Directory.GetFiles(u3dUI, "*.dll", SearchOption.TopDirectoryOnly);
        foreach (var d in u3ddlls1)
        {
            refDlls.Add(d);
        }

        //dll2
        var u3ddlls2 = Directory.GetFiles(u3dEngine, "*.dll", SearchOption.AllDirectories);
        foreach (var d in u3ddlls2)
        {
            refDlls.Add(d);
        }

        //
        var baseDllPath = outPath + "/hotfix/base.dll";
        EditorUtility.DisplayProgressBar("编译服务", "复制编译代码", 0.3f);

        //为解决mono.exe error: 文件名太长问题
        //全部拷贝到临时目录

        for (int i = 0; i < baseCs.Count; i++)
        {
            var copyto = IPath.Combine(tempDirect, Path.GetFileName(baseCs[i]));
            int count = 1;
            while (File.Exists(copyto))
            {
                copyto = copyto.Replace(".cs", "") + count + ".cs";
                count++;
            }

            File.Copy(baseCs[i], copyto);
            baseCs[i] = copyto.Replace("\\", "/");
        }

        //建立目标目录
        var outDirectory = Path.GetDirectoryName(baseDllPath);
        if (Directory.Exists(outDirectory))
        {
            Directory.Delete(outDirectory, true);
        }
        //
        try
        {
            Directory.CreateDirectory(outDirectory);
        }
        catch (Exception e)
        {
            EditorUtility.ClearProgressBar();
            EditorUtility.DisplayDialog("提示", "Unity拒绝创建文件夹,请重试!", "OK");
            return;
        }

        EditorUtility.DisplayProgressBar("编译服务", "[1/2]开始编译base.dll", 0.4f);
        //编译 base.dll
        try
        {

            //转换shortname
            //            for (int i = 0; i < baseCs.Count; i++)
            //            {
            //                var cs = baseCs[i];
            //                baseCs[i] = FileNameHelper.GetShortPath(cs);
            //            }
            //
            Build(refDlls.ToArray(), baseCs.ToArray(), baseDllPath);
            AssetDatabase.Refresh();
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
            EditorUtility.ClearProgressBar();
            throw;
        }

        //

        EditorUtility.DisplayProgressBar("编译服务", "[2/2]开始编译hotfix.dll", 0.7f);
        var dependent = outDirectory + "/dependent";
        Directory.CreateDirectory(dependent);

        //将base.dll加入
        refDlls.Add(baseDllPath);
        //编译hotfix.dll
        var outHotfixDirectory = outPath + "/hotfix/hotfix.dll";
        try
        {
            //转换shortname
            //            for (int i = 0; i < hotfixCs.Count; i++)
            //            {
            //                var cs = hotfixCs[i];
            //                hotfixCs[i] = FileNameHelper.GetShortPath(cs);
            //            }

            Build(refDlls.ToArray(), hotfixCs.ToArray(), outHotfixDirectory);
            AssetDatabase.Refresh();
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
            EditorUtility.ClearProgressBar();
            throw;
        }
        //拷贝依赖的dll
        //        foreach (var f in refDlls)
        //        {
        //            if (File.Exists(f) ==false)
        //            {
        //                continue;
        //            }
        //            var fn = Path.GetFileName(f);
        //            var outpath = IPath.Combine(dependent, fn);
        //            File.Copy(f,outpath,true);
        //        }

        EditorUtility.DisplayProgressBar("编译服务", "清理临时文件", 0.9f);
        Directory.Delete(tempDirect, true);
        EditorUtility.ClearProgressBar();
    }
#endif

    static public void BuildDLL_DotNet(string codeSource, string export)
    {
        string str1 = Application.dataPath;
        string str2 = Application.streamingAssetsPath;
        string exePath = Application.dataPath + "/" + "Code/GFFramework/Tools/ILRBuild/build.exe";
        if (File.Exists(exePath))
        {
            Debug.Log(".net编译工具存在!");
        }

        //这里是引入unity所有引用的dll
        var u3dUI = string.Format(@"""{0}\UnityExtensions\Unity\GUISystem""", EditorApplication.applicationContentsPath);
        var u3dEngine = string.Format(@"""{0}\Managed\UnityEngine""", EditorApplication.applicationContentsPath);

        if (Directory.Exists(u3dUI.Replace(@"""", "")) == false || Directory.Exists(u3dEngine.Replace(@"""", "")) == false)
        {
            EditorUtility.DisplayDialog("提示", "u3d根目录不存在,请修改ScriptBiuld_Service类中,u3dui 和u3dengine 的dll目录", "OK");
            return;
        }
        //
        Process.Start(exePath, string.Format("{0} {1} {2} {3}", codeSource, export, u3dUI, u3dEngine));
    }


    //    static public void BuildDLL_DotNet(string dataPath, string streamingAssetsPath,
    //        string u3dDllPath1, string u3dDllPath2)
    //    {
    //        //编译项目的base.dll
    //        Console.WriteLine("准备编译dll 10%");
    //
    //        //准备工作
    //        var path = streamingAssetsPath + "/hotfix";
    //        if (Directory.Exists(path))
    //        {
    //            Directory.Delete(path, true);
    //        }
    //
    //        Directory.CreateDirectory(path);
    //        //
    //        string[] searchPath = new string[] {"3rdPlugins", "Code", "Plugins", "Resource"};
    //
    //        for (int i = 0; i < searchPath.Length; i++)
    //        {
    //            searchPath[i] = IPath.Combine(dataPath, searchPath[i]);
    //        }
    //
    //        List<string> files = new List<string>();
    //        foreach (var s in searchPath)
    //        {
    //            var fs = Directory.GetFiles(s, "*.*", SearchOption.AllDirectories).ToList();
    //            var _fs = fs.FindAll(f =>
    //            {
    //                var _f = f.ToLower();
    //                var exten = Path.GetExtension(_f);
    //                if ((!_f.Contains("editor")) &&
    //                    (exten.Equals(".dll") || exten.Equals(".cs")))
    //                {
    //                    return true;
    //                }
    //
    //                return false;
    //            });
    //
    //            files.AddRange(_fs);
    //        }
    //
    //        files = files.Distinct().ToList();
    //        for (int i = 0; i < files.Count; i++)
    //        {
    //            files[i] = files[i].Replace('/', '\\').Trim('\\');
    //        }
    //
    //        Console.WriteLine("开始整理script 20%");
    //
    //        var refDlls = files.FindAll(f => f.EndsWith(".dll"));
    //        var baseCs = files.FindAll(f => !f.EndsWith(".dll") && !f.Contains("@hotfix"));
    //        var hotfixCs = files.FindAll(f => !f.EndsWith(".dll") && f.Contains("@hotfix"));
    //
    //
    //        var tempDirect = "c:/bd_temp";
    //        if (Directory.Exists(tempDirect))
    //        {
    //            Directory.Delete(tempDirect, true);
    //        }
    //
    //        Directory.CreateDirectory(tempDirect);
    //
    //        //除去不需要引用的dll
    //        for (int i = refDlls.Count - 1; i >= 0; i--)
    //        {
    //            var str = refDlls[i];
    //            if (str.Contains("iOS") || str.Contains("Android"))
    //            {
    //                refDlls.RemoveAt(i);
    //            }
    //        }
    //
    //        //去除同名 重复的dll
    //        for (int i = 0; i < refDlls.Count; i++)
    //        {
    //            var copyto = IPath.Combine(tempDirect, Path.GetFileName(refDlls[i]));
    //            File.Copy(refDlls[i], copyto, true);
    //            refDlls[i] = copyto;
    //        }
    //
    //        refDlls.Add("System.dll");
    //        refDlls.Add("System.Core.dll");
    //        refDlls.Add("System.XML.dll");
    //        refDlls.Add("System.Data.dll");
    //        //dll1
    //        var u3ddlls1 = Directory.GetFiles(u3dDllPath1, "*.dll", SearchOption.TopDirectoryOnly);
    //        foreach (var d in u3ddlls1)
    //        {
    //            refDlls.Add(d);
    //        }
    //
    //        //dll2
    //        var u3ddlls2 = Directory.GetFiles(u3dDllPath2, "*.dll", SearchOption.AllDirectories);
    //        foreach (var d in u3ddlls2)
    //        {
    //            refDlls.Add(d);
    //        }
    //
    //        //
    //        var baseDllPath = streamingAssetsPath + "/hotfix/base.dll";
    //
    //
    //        Console.WriteLine("复制编译代码 30%");
    //
    //        //为解决mono.exe error: 文件名太长问题
    //        tempDirect = "c:/bd_temp";
    //        for (int i = 0; i < baseCs.Count; i++)
    //        {
    //            var copyto = IPath.Combine(tempDirect, Path.GetFileName(baseCs[i]));
    //            int count = 1;
    //            while (File.Exists(copyto))
    //            {
    //                copyto = copyto.Replace(".cs", "") + count + ".cs";
    //                count++;
    //            }
    //
    //            File.Copy(baseCs[i], copyto);
    //            baseCs[i] = copyto;
    //        }
    //
    //        //建立目标目录
    //        var outDirectory = Path.GetDirectoryName(baseDllPath);
    //        if (Directory.Exists(outDirectory))
    //        {
    //            Directory.Delete(outDirectory, true);
    //        }
    //
    //
    //        Directory.CreateDirectory(outDirectory);
    //        Console.WriteLine("[1/2]开始编译base.dll 40%");
    //        //编译 base.dll
    //        try
    //        {
    //            Build(refDlls.ToArray(), baseCs.ToArray(), baseDllPath);
    //        }
    //        catch (Exception e)
    //        {
    //            Console.WriteLine(e);
    //            throw;
    //        }
    //
    //        Console.WriteLine("[2/2]开始编译hotfix.dll 70%");
    //
    //        var dependent = outDirectory + "/dependent";
    //        Directory.CreateDirectory(dependent);
    //
    //        //将base.dll加入
    //        refDlls.Add(baseDllPath);
    //        //编译hotfix.dll
    //        var outHotfixDirectory = streamingAssetsPath + "/hotfix/hotfix.dll";
    //        try
    //        {
    //            Build(refDlls.ToArray(), hotfixCs.ToArray(), outHotfixDirectory);
    //        }
    //        catch (Exception e)
    //        {
    //            Console.WriteLine(e);
    //            throw;
    //        }
    //
    //        Console.WriteLine("清理临时文件 95%");
    //        Directory.Delete(tempDirect, true);
    //        Console.WriteLine("编译成功!位于StreamingAssets下! 100%");
    //        //拷贝依赖的dll
    //        //        foreach (var f in refDlls)
    //        //        {
    //        //            if (File.Exists(f) == false)
    //        //            {
    //        //                continue;
    //        //            }
    //        //            var fn = Path.GetFileName(f);
    //        //            var outpath = IPath.Combine(dependent, fn);
    //        //            File.Copy(f, outpath, true);
    //        //        }
    //    }

    /// <summary>
    /// 编译dll
    /// </summary>
    /// <param name="rootpaths"></param>
    /// <param name="output"></param>
    static public BuildStatus Build(string[] refAssemblies, string[] codefiles, string output)
    {
        // 设定编译参数,DLL代表需要引入的Assemblies
        CompilerParameters cp = new CompilerParameters();
        //
        cp.GenerateExecutable = false;
        //在内存中生成
        cp.GenerateInMemory = true;
        //生成调试信息
        cp.IncludeDebugInformation = true;
        cp.TempFiles = new TempFileCollection(".", true);
        cp.OutputAssembly = output;
        //warning和 error分开,不然各种warning当成error,改死你
        cp.TreatWarningsAsErrors = false;
        cp.WarningLevel = 1;
        //编译选项
        cp.CompilerOptions = "/optimize /unsafe";

        if (refAssemblies != null)
        {
            foreach (var d in refAssemblies)
            {
                cp.ReferencedAssemblies.Add(d);
            }
        }

        // 编译代理
        CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
        CompilerResults cr = provider.CompileAssemblyFromFile(cp, codefiles);


        if (true == cr.Errors.HasErrors)
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            foreach (System.CodeDom.Compiler.CompilerError ce in cr.Errors)
            {
                sb.Append(ce.ToString());
                sb.Append(System.Environment.NewLine);
            }

#if !UNITY_EDITOR
            Console.WriteLine(sb);

#else
            Debug.LogError(sb);
#endif
        }
        else
        {
            return BuildStatus.Success;
        }


        return BuildStatus.Fail;
    }
}

思路:搜集工程目标目录中所有的.dll和.cs文件,然后通过API CompilerParameters cp = new CompilerParameters()编译dll。
在这里插入图片描述

资源更新

操作步骤

1.Tools/资源一键打包->输入版本号->选择要生成的平台的资源->一键导出->选择要导出的路径->生成目标平台的AB资源
2.资源加密:选择资源转hash格式,将刚刚生成的资源生成一份服务器用的资源
在这里插入图片描述

注意事项

我们上传文件到服务器,估计很多人会选择Filezilla,之前用感觉挺好用的,因为我一直用window端的客户端,但mac上Filezilla有bug,上传的文件大小变了,因为这个bug,我困扰了好久,好不容易查到是打包出来的文件跟服务器下载下来的文件大小不一致,一开始怀疑是不是Unity的bug,用Unity的WebClient的DownloadFileTaskAsync下载的然后换了一个api还是不行,然后换UnityWebRequest.Get,换unity版本,最后才细心的发现上传到服务器端文件大小就不一致,还好用的内网服务器能在服务器目录上看到服务器上文件的大小,如果是远程CDN估计不容易发觉这个bug,然后我就采用U盘拷贝的方式,将生成的资源放到服务器上才解决了问题。心得:碰到问题要沉着冷静,发现蛛丝马迹,感觉程序员能当侦探,哈哈!

下载文件

打包好的服务器用的资源文件由一堆hash文件和config文件组成,config文件是资源清单文件,记录了资源版号和资源hash键值对。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
下载文件的正规流程,首先下载查看本地是否有资源清单文件,如果没有则下载资源,如果有则对比资源版本号,如果相同则跳过,如果不同则对比资源hash找出不一样的资源下载覆盖然后下载config文件覆盖本地。
在这里插入图片描述
demo中为了演示下载效果,就每次点击下载都会直接下载,没有按照正规的流程来,在正式商业项目可以自己修改要一下。

温馨提示

上面流程图软件可以用processon,在线web软件,可以画流程图,可以画UML,拓扑图,思维导图等等非常方便!

在这里插入图片描述

代码
using GF.Debug;
using GFFramework.Helper;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;

namespace GFFramework
{
    public class AssetConfig
    {
        public string Platfrom = "";
        public double Version = 0.1d;
        public List<AssetItem> Assets = new List<AssetItem>();
    }

    public class AssetItem
    {
        public string HashName = "";
        public string LocalPath = "";
    }

    /// <summary>
    /// 版本控制类
    /// </summary>
    static public class VersionContorller
    {
        private static List<AssetItem> curDownloadList = null;
        private static int curDonloadIndex = 0;
        private static string serverConfig = null;


        static public void Start(string serverConfigPath, string localConfigPath, Action<int, int> onProcess, Action<string> onError)
        {
            IEnumeratorTool.StartCoroutine(IE_Start(serverConfigPath, localConfigPath, onProcess, onError));
        }

        /// <summary>
        /// 开始任务
        /// </summary>
        /// <param name="serverConfigPath">服务器配置根目录</param>
        /// <param name="localConfigPath">本地根目录</param>
        /// <param name="onProcess"></param>
        /// <param name="onError"></param>
        /// 返回码: -1:error  0:success
        static private IEnumerator IE_Start(string serverConfigPath, string localConfigPath, Action<int, int> onProcess, Action<string> onError)
        {
            var platform = Utils.GetPlatformPath(Application.platform);

            if (curDownloadList == null || curDownloadList.Count == 0)
            {
                //开始下载服务器配置
                var serverPath = serverConfigPath + "/" + platform + "_Server/" + platform + "_VersionConfig.json";
                Debugger.Log("server:" + serverPath);

                //下载config
                {
                    var wr = UnityWebRequest.Get(serverPath);
                    yield return wr.SendWebRequest();
                    if (wr.error == null)
                    {
                        serverConfig = wr.downloadHandler.text;
                        Debugger.Log("服务器资源配置:" + serverConfig);
                    }
                    else
                    {
                        Debug.LogError(wr.error);
                    }
                }

                var serverconf = LitJson.JsonMapper.ToObject<AssetConfig>(serverConfig);
                AssetConfig localconf = null;
                var localPath = string.Format("{0}/{1}/{2}_VersionConfig.json", localConfigPath, platform, platform);

                if (File.Exists(localPath))
                {
                    localconf = LitJson.JsonMapper.ToObject<AssetConfig>(File.ReadAllText(localPath));
                }

                //对比差异列表进行下载
                curDownloadList = CompareConfig(localconf, serverconf);
                if (curDownloadList.Count > 0)
                {
                    //预通知要进入热更模式
                    onProcess(0, curDownloadList.Count);
                }
            }


            while (curDonloadIndex < curDownloadList.Count)
            {

                var item = curDownloadList[curDonloadIndex];

                var sp = serverConfigPath + "/" + platform + "_Server/" + item.HashName;
                var lp = localConfigPath + "/" + platform + "/" + item.LocalPath;

                //创建目录
                var direct = Path.GetDirectoryName(lp);
                if (Directory.Exists(direct) == false)
                {
                    Directory.CreateDirectory(direct);
                }

                //下载
                var wr = UnityWebRequest.Get(sp);
                yield return wr.SendWebRequest();
                if (wr.error == null)
                {
                    File.WriteAllBytes(lp, wr.downloadHandler.data);
                    Debugger.Log("下载成功:" + sp);
                    onProcess(curDonloadIndex, curDownloadList.Count - 1);
                }
                else
                {
                    Debugger.LogError("下载失败:" + wr.error);
                    onError(wr.error);
                    yield break;
                }

                //自增
                curDonloadIndex++;
            }

            //写到本地  写confgig
            if (curDownloadList.Count > 0)
            {
                File.WriteAllText(string.Format("{0}/{1}/{2}_VersionConfig.json", localConfigPath, platform, platform), serverConfig);
            }
            else
            {
                Debugger.Log("不用更新");
                onProcess(1, 1);
            }


            //重置
            curDownloadList = null;
            curDonloadIndex = 0;
            serverConfig = null;
        }


        /// <summary>
        /// 对比
        /// </summary>
        static public List<AssetItem> CompareConfig(AssetConfig local, AssetConfig server)
        {
            if (local == null)
            {

                return server.Assets;
            }

            var list = new List<AssetItem>();
            //比对平台
            if (local.Platfrom == server.Platfrom)
            {
                foreach (var serverAsset in server.Assets)
                {
                    //比较本地是否有 hash、文件名一致的资源
                    var result = local.Assets.Find((a) => a.HashName == serverAsset.HashName && a.LocalPath == serverAsset.LocalPath);

                    if (result == null)
                    {
                        list.Add(serverAsset);
                    }
                }
            }
            return list;
        }
    }
}

File.WriteAllBytes(路径,bytes) 写二进制到本地
File.WriteAllText(路径,string) 写字符串到本地 默认是UTF8,当然也可以指定编码

这里也没有考虑断点续传,如果因为网络原因中途下载失败,会重头开始下,项目大的话这是不能容忍的,当然也是可以优化的一个地方,可以参考前言里面我给的之前的断点续传的文章优化一下。

GFFramework地址

https://github.com/dingxiaowei/GFFrameWork

更多教程

http://dingxiaowei.cn/

猜你喜欢

转载自blog.csdn.net/s10141303/article/details/89181307