• 仿LOL项目开发第二天


    仿LOL项目开发第二天

                                          by草帽

    接着上节来讲,上节更新还没开始写代码逻辑,今天我们补充完整。

    我们找到VersionManager脚本里面的CheckVersion方法:

    首先我们想到检测版本,需要从服务器下载信息,那么肯定要提前检测下网络是否良好,并比较版本信息。

    所以,我们写个BeforeCheck方法:

     

        /// <summary>
        /// 检测网络状况并对照版本信息是否一致
        /// </summary>
        /// <param name="AsynResult">版本信息是否一致的处理委托</param>
        /// <param name="OnError">错误处理委托</param>
        public void BeforeCheck(Action<bool> AsynResult, Action OnError)
        {
            CheckTimeout checkTimeout = new CheckTimeout();
            checkTimeout.AsynIsNetworkTimeout((success) => 
            {
                //如果网络良好,开始下载服务器版本xml
                if (success)
                {
    
                }
                else 
                {
                    if (OnError != null)
                    {
                        OnError();
                    }
                }
            });
        }
    

     在写网络良好判断的带参匿名委托之前,我们先来思考一个问题,网络良好接下来需要做什么?

    就是下载服务端的版本信息,但是得需要一个url地址来访问?

    那么这个服务端版本信息的URL在哪里?难道要把绝对的URl定死的写入到代码中。

    假如我以后服务器的URL换了呢?那么就要重新改写代码。这种方案否决,不可取。

    所以呢,这个服务器的URL应该得写入到配置文件中,从服务器那边下载。

    非常nice,之前我们不是写个一个SystemConfig,我们就在里面初始化服务器的URL。

    所以SystemConfig需要一个Init()的初始方法。

    初始配置文件分为两类:

    1.从服务器那边获取的配置信息,比如版本信息,游戏服务器各个大区的信息

    2.本地配置信息,比如声音大小,屏幕分辨率等等

    首先,先来加载服务器相关的配置信息,与服务器相关的,我们需要分类:所以写个CfgInfo类:

     

        public class CfgInfo
        {
            public int id { get; set; }
            public string name { get; set; }
            public string url { get; set; }
        }

    比如版本信息类id=0,游戏服务器大区类id=1,然后分别对应着不同的url,这样我们管理起来就清晰多了。

    那么这个CfgInfo信息是哪里来的,肯定也需要访问服务器那边的url获取,所以呢,我们在Resources文件夹下面创建一个txt,里面写着这个Url,然后访问URl,读取里面的内容初始化所有的CfgInfo类存到列表中List<CfgInfo>,最后还要把Url文本保存在持久文件夹下,以便以后使用,OK分析完之后。

    在SystemConfig类下创建常量:CfgPath持久路径

     

    public readonly static string CfgPath = Application.persistentDataPath + "/cfg.xml";

     

    然后我们在SystemConfig里面创建LoadCfgInfo()方法,在Init()里面调用:

     

    private static bool LoadCfgInfo()
        {
            string cfgStr = null;
            //如果存在持久路径,就直接加载文本
            if (File.Exists(CfgPath))
            {
                cfgStr = UnityTools.LoadFileText(CfgPath);
            }
            else 
            {
                //从Resources从加载配置文本
                TextAsset cfgUrl = Resources.Load("cfg") as TextAsset;
                if (cfgUrl)
                {
                    //从网页上下载与服务端有关的所有配置xml字符串
                    cfgStr = DownloadMgr.Instance.DownLoadHtml(cfgUrl.text);
                }
                else
                {
                    cfgStr = null;
                }
            }
            //加载xml内容为列表类
            CfgInfoList = LoadXMLText<CfgInfo>(cfgStr);
            return true;
        }
    

     

     

     

    LoadXMLText<T>(string xmlText):

     

        /// <summary>
        /// 将xml转换成list<T>列表类
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xmlText"></param>
        /// <returns></returns>
        private static List<T> LoadXMLText<T>(string xmlText)
        {
            List<T> list = new List<T>();
            try
            {
                if (string.IsNullOrEmpty(xmlText))
                {
                    return list;
                }
                Type type = typeof(T);
                XmlDocument doc = XmlResAdapter.GetXmlDocument(xmlText);
                Dictionary<int,Dictionary<string,string>> map = XmlResAdapter.LoadXMLToMap(doc,xmlText);
                var props = type.GetProperties(~System.Reflection.BindingFlags.Static);
                foreach (var item in map)
                {
                    var obj = type.GetConstructor(Type.EmptyTypes).Invoke(null);
                    foreach (var prop in props)
                    {
                        if (prop.Name == "id")
                        {
                            prop.SetValue(obj,item.Key,null);
                        }
                        else
                        {
                            try
                            {
                                if (item.Value.ContainsKey(prop.Name))
                                {
                                    var value = UnityTools.GetValue(item.Value[prop.Name],prop.PropertyType);
                                    prop.SetValue(obj,value,null);
                                }
                            }
                            catch(Exception e)
                            {
                                Debug.LogException(e);
                            }
                        }
                    }
                    list.Add((T)obj);
                }
            }
            catch(Exception e)
            {
                Debug.LogException(e);
            }
            return list;
        }
    

     

      

     

    XmlResAdapter.LoadXMLToMap():

     

            /// <summary>
            /// 将xml内容转换成map
            /// </summary>
            /// <param name="doc"></param>
            /// <param name="content"></param>
            /// <returns></returns>
            public static Dictionary<int, Dictionary<string, string>> LoadXMLToMap(XmlDocument doc, string content)
            {
                var result = new Dictionary<int, Dictionary<string, string>>();
                int index = 0;
                foreach (XmlNode item in doc.SelectSingleNode("root").ChildNodes)
                {
                    index++;
                    if (item.ChildNodes == null || item.ChildNodes.Count == 0)
                    {
                        continue;
                    }
                    int key = int.Parse(item.ChildNodes[0].InnerText);
                    if (result.ContainsKey(key))
                    {
                        continue;
                    }
                    var children = new Dictionary<string, string>();
                    result.Add(key, children);
                    for (int i = 1; i < item.ChildNodes.Count; i++)
                    {
                        XmlNode node = item.ChildNodes[i];
                        string tag = null;
                        if (node.Name.Length < 3)
                        {
                            tag = node.Name;
                        }
                        else 
                        {
                            string tagTial = node.Name.Substring(node.Name.Length - 2, 2);
                            if (tagTial == "_i" || tagTial == "_s" || tagTial == "_f" || tagTial == "_l" || tagTial == "_k" || tagTial == "_m")
                            {
                                tag = node.Name.Substring(0, node.Name.Length - 2);
                            }
                            else 
                            {
                                tag = node.Name;
                            }
                        }
                        if (node != null && !children.ContainsKey(tag))
                        {
                            if (string.IsNullOrEmpty(node.InnerText))
                            {
                                children.Add(tag, "");
                            }
                            else
                            {
                                children.Add(tag, node.InnerText.Trim());
                            }
                        }
                    }
                }
                return result;
            }
    

     

      

     

    所以这里我们就需要用到第一节提到的准备工具:WampServer集成的网页开发工具:

    因为这里我们需要涉及到访问服务器的url,所以我们自己架设一个http服务器站点。

    打开WampServer,找到www目录

    然后新建一个文件夹,命名为LOLGameDemo

    在这个文件夹下面新建一个xml,命名为Cfg.xml

    然后回到Unity的Resources文件下面新建一个txt,也命名为Cfg.txt。

    编辑txt里面的内容:


    然后编辑Cfg.xml的内容:

    然后在LOLGameDemo文件夹下,新创建一个ServerVersion.xml文本,为服务器的版本信息:

    OK,大功告成。接着我们回到VersionManager里面的BeforeCheck方法里面:

    我们要获取到服务器版本的url,所以得回到SystemConfig编写一个GetCfgInfoByName()方法或者GetCfgInfoById()的接口。

    GetCfgInfoByName():

        /// <summary>
        /// 根据名字取得服务端配置信息Url
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static string GetCfgInfoUrlByName(string name)
        {
            string result = "";
            foreach (var item in CfgInfoList)
            {
                if (item.name == name)
                {
                    result = item.url;
                    break;
                }
            }
            return result;
        }

     

    GetCfgInfoById():

     

        /// <summary>
        /// 根据id取得服务端配置信息Url
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public static string GetCfgInfoUrlById(int id)
        {
            string result = "";
            foreach (var item in CfgInfoList)
            {
                if (item.id == id)
                {
                    result = item.url;
                    break;
                }
            }
            return result;
        }

     

    因为我们下载的服务器的版本文本也要保存在持久文件目录,所以:

     

    public readonly static string ServerVersionPath = Application.persistentDataPath + "/serverVersion.xml";

    还有之前我们只是初始化本地版本信息类的实例,所以现在我们在VersionManager创建一个服务端的版本信息类:

     

        /// <summary>
        /// 本地版本信息属性
        /// </summary>
        public VersionManagerInfo LocalVersion { get; private set; }
        /// <summary>
        /// 服务版本信息属性
        /// </summary>
        public VersionManagerInfo ServerVersion { get; private set; }

     

    然后再次回到BeforeCheck方法里面,终于回来了!0.0!

     

    public void BeforeCheck(Action<bool> AsynResult, Action OnError)
        {
            CheckTimeout checkTimeout = new CheckTimeout();
            checkTimeout.AsynIsNetworkTimeout((success) => 
            {
                //如果网络良好,开始下载服务器版本xml
                if (success)
                {
                    DownloadMgr.Instance.AsynDownLoadHtml(SystemConfig.GetCfgInfoUrlByName("version"), 
                    (serverVersion) => 
                    {
                        //如果本地存在服务端的版本信息文本,覆盖下载的服务器文本
                        if (File.Exists(SystemConfig.ServerVersionPath))
                        {
                            serverVersion = UnityTools.LoadFileText(SystemConfig.ServerVersionPath);
                        }
                        //将文本转换成版本信息类
                        ServerVersion = GetVersionInXml(serverVersion);
                //开始进行比较版本号 bool programVersion = ServerVersion.ProgramVersionCodeInfo.Compare(LocalVersion.ProgramVersionCodeInfo) > 0; bool resourceVersion = ServerVersion.ResourceVersionCodeInfo.Compare(LocalVersion.ResourceVersionCodeInfo) > 0; //执行是否更新的委托 AsynResult(programVersion || resourceVersion); },OnError); } else { if (OnError != null) { OnError(); } } }); }

     然后回到VersionManager的CheckVersion方法中,将BeforeCheck方法写入到里面。

     

        public void CheckVersion(Action<bool> fileDecompress,Action<int,int,string> taskProgress,Action<int,long,long> progress,Action finished,Action<Exception> error)
        {
            BeforeCheck((result) =>
            {
                if (result)
                {
                    //需要更新
                    Debug.Log("需要更新");
                }
                else
                {
                    //不需要更新
                    Debug.Log("不需要更新");
                }
            }, () => { error(new Exception("下载版本文件超时!")); });
        }
    

     

      

     

    运行程序,发现在打印窗口输出:

    不需要更新的字样,如果我们想要需要更新,就需要修改ServerVersion.xml文件:

    将Resource资源版本提高一个版本号:这样就比本地LocalVersion高一点。

    运行程序,再次看打印窗口:

    这次就出现了更新字样。这里我只是打印消息,接下来,我们正式进入开始下载资源包代码编写:

    创建一个方法CheckAndDownload():

    在写这个方法之前,先分析一下,下载一个需要更新的资源包,我们怎么知道这个资源包得需要更新,还有我们得验证这个资源包的md5码,所以综上,我们得封装一个资源包类:PackageInfo

     

    /// <summary>
    /// 资源包
    /// </summary>
    public class PackageInfo 
    {
        /// <summary>
        /// 包名
        /// </summary>
        public string Name;
        /// <summary>
        /// 资源适用最低版本号
        /// </summary>
        public VersionCodeInfo LowVersion;
        /// <summary>
        /// 资源适用最高版本号
        /// </summary>
        public VersionCodeInfo HighVersion;
        /// <summary>
        /// 资源md5码
        /// </summary>
        public string MD5;
    }
    

     然后从之前我们下载的服务器版本信息取得PackageMd5List的url,然后访问这个url,根据内容初始化需要下载Pakcage的信息。

    DownloadPackageInfoList:

     

        /// <summary>
        /// 取得下载包的信息
        /// </summary>
        /// <param name="AsynResult"></param>
        /// <param name="OnError"></param>
        private void DownloadPackageInfoList(Action<List<PackageInfo>> AsynResult, Action OnError)
        {
            //从服务器版本信息中取得下载Url,然后初始化下载包的信息
            DownloadMgr.Instance.AsynDownLoadHtml(ServerVersion.PackageMd5List, 
            (content) => 
            {
                XmlDocument doc = XmlResAdapter.GetXmlDocument(content);
                if (null == doc)
                {
                    if (OnError != null)
                    {
                        OnError();
                    }
                }
                else 
                {
                    List<PackageInfo> packagesList = new List<PackageInfo>();
                    foreach (XmlNode node in doc.SelectSingleNode("root").ChildNodes)
                    {
                        PackageInfo package = new PackageInfo();
                        string packagetName = node.Attributes["name"].Value;
                        package.Name = packagetName;
                        //从第7个开始,也就是说前7个是资源包文件名,后4位是包后缀名.zip
                        string version = packagetName.Substring(7, packagetName.Length - 11);
                        //中间是低版本和高版本===>比如0.0.0.0-0.0.0.3
                        string firstVersion = version.Substring(0,version.IndexOf("-"));
                        package.LowVersion = new VersionCodeInfo(firstVersion);
                        string endVersion = version.Substring(version.Length + 1);
                        package.HighVersion = new VersionCodeInfo(endVersion);
                        //然后内容是md5码
                        package.MD5 = node.InnerText;
                        packagesList.Add(package);
                        ServerVersion.PackageMd5Dic.Add(package.Name, package.MD5);
                    }
                    AsynResult(packagesList);
                }
            }, OnError);
        }
    

     

      

     

    然后提供给上层的委托的参数是List<PackageInfo>,上层在取得所有的包,然后判断哪些包是需要下载的,然后进行下载。

    AsynDownloadUpdatePackage:

     

    private void AsynDownloadUpdatePackage(Action<bool> fileDecompress, Action<int, int, string> taskProgress, Action<int, long, long> progress, Action finished, Action<Exception> error)
        {
            DownloadPackageInfoList((packageList) =>
            {
                //如果资源包最低版本比游戏的版本高,资源包的最高要求版本比游戏低则需要更新
                var downloadList = (from packageInfo in packageList
                                    where packageInfo.LowVersion.Compare(LocalVersion.ResourceVersionCodeInfo) >= 0 && packageInfo.HighVersion.Compare(ServerVersion.ResourceVersionCodeInfo) <= 0
                                    select new KeyValuePair<string, string>(packageInfo.HighVersion.ToString(), packageInfo.Name)).ToList();
                string packageUrl = ServerVersion.PackageUrl;
                if (downloadList.Count != 0)
                {
                    Debug.Log("开始下载资源包列表");
                }
                else 
                {
                    Debug.Log("更新资源包数目为0");
                    if (finished != null)
                    {
                        finished();
                    }
                }
            }, () => { error(new Exception("下载资源包信息出错")); });
        }

    CheckAndDownload:

     

    public bool CheckAndDownload(Action<bool> fileDecompress, Action<int, int,string> taskProgress, Action<int, long, long> progress, Action finished, Action<Exception> error)
        {
            //资源需要更新,还有就是程序需要更新我不写了,基本上用不到
            if (ServerVersion.ResourceVersionCodeInfo.Compare(LocalVersion.ResourceVersionCodeInfo) > 0)
            {
                //开始下载资源包
                AsynDownloadUpdatePackage(fileDecompress, taskProgress, progress, finished, error);
                return true;
            }
            if (finished != null)
            {
                finished();
            }
            return false;
        } 

     回到CheckVersion方法:

    在需要更新的地方调用:CheckDownload()

     

    public void CheckVersion(Action<bool> fileDecompress,Action<int,int,string> taskProgress,Action<int,long,long> progress,Action finished,Action<Exception> error)
        {
            BeforeCheck((result) =>
            {
                if (result)
                {
                    //需要更新
                    Debug.Log("需要更新");
                    CheckAndDownload(fileDecompress, taskProgress, progress, finished, error);
                }
                else
                {
                    //不需要更新
                    Debug.Log("不需要更新");
                }
            }, () => { error(new Exception("下载版本文件超时!")); });
        }

    然后回到ServerVersion.xml修改PackageMd5List标签:

    然后去www目录下的LOLGameDemo文件夹下创建PackageMd5List.xml文件:

    OK,点击运行程序,观察打印窗口:

    程序运行是我们所期望的那样。OK,接着我们就开始写如何下载资源包列表。

     首先呢下载一个资源包就相当于一个下载任务,你看迅雷的下载,你每下载一个资源的时候,他都是封装成一个任务来管理,所以呢,这里我们也新建一个任务类来管理:DownloadTask.cs:

     

    using UnityEngine;
    using System.Collections;
    using System;
    /// <summary>
    /// 下载任务类
    /// </summary>
    public class DownloadTask
    {
        public string Url { get; set; }
        public string FileName { get; set; }
        public Action<int, long, long> TotalProgress { get; set; }
        public Action<int> Progress { get; set; }
        public Action<long> TotalBytesToReceive { get; set; }
        public Action<long> BytesReceived { get; set; }
        public String MD5 { get; set; }
        public Action Finished { get; set; }
        public Action<Exception> Error { get; set; }
        public bool bFineshed = false;//文件是否下载完成
        public bool bDownloadAgain = false;//是否需要从新下载,如果下载出错的时候会从新下
        public void OnTotalBytesToReceive(long size)
        {
            if (TotalBytesToReceive != null)
                TotalBytesToReceive(size);
        }
        public void OnBytesReceived(long size)
        {
            if (BytesReceived != null)
                BytesReceived(size);
        }
        public void OnTotalProgress(int p, long totalSize, long receivedSize)
        {
            if (TotalProgress != null)
                TotalProgress(p, totalSize, receivedSize);
        }
        public void OnProgress(int p)
        {
            if (Progress != null)
                Progress(p);
        }
        public void OnFinished()
        {
            if (Finished != null)
                Finished();
        }
        public void OnError(Exception ex)
        {
            if (Error != null)
                Error(ex);
        }
    }

    OK,我们在VersionManager新建一个方法:DownloadPackageList()下载资源包列表的方法,然后在AsynDownloadUpdatePackage方法的开始下载资源包委托里面调用。

     

    private void DownloadPackageList(Action<bool> fileDecompress, string packageUrl, List<KeyValuePair<String, String>> downloadList, Action<int, int, string> taskProgress, Action<int, long, long> progress, Action finished, Action<Exception> error)
        {
            //下载列表
            List<DownloadTask> allTasks = new List<DownloadTask>();
            for (int i = 0; i < downloadList.Count; i++)
            {
                KeyValuePair<string, string> kvp = downloadList[i];
                string localFile = string.Concat(SystemConfig.ResourceFolder, kvp.Value);//dataPath+"/Resources/"+文件名
                //下载完成之后回调委托
                Action OnDownloadFinished =()=>
                {
                    //进行解压,以后再来
                };
                string fileUrl = string.Concat(packageUrl, kvp.Value);//"http://127.0.0.1/LOLGameDemo/LOLPackage/"+文件名
                //初始化任务
                var task = new DownloadTask
                {
                    FileName = localFile,//dataPath+"/Resources/"+文件名
                    Url = fileUrl,//下载url
                    Finished = OnDownloadFinished,//解压更新版本信息
                    Error = error,
                    TotalProgress = progress
                };
                string fileNameNoExtension = kvp.Value;
                if (ServerVersion.PackageMd5Dic.ContainsKey(fileNameNoExtension))
                {
                    task.MD5 = ServerVersion.PackageMd5Dic[fileNameNoExtension];
                    allTasks.Add(task);
                }
                else 
                {
                    error(new Exception("下载包不存在:" + fileNameNoExtension));
                    return;
                }
            }
            //全部任务下载完成回调
            Action AllFinished = () => 
            {
                Debug.Log("全部下载完成");
                finished();
            };
            //添加taskProgress的回调
            Action<int, int, string> TaskProgress = (total, current, filename) =>
            {
                if (taskProgress != null)
                    taskProgress(total, current, filename);
            };
            //添加文件解压的回调函数
            Action<bool> filedecompress = (decompress) =>
            {
                if (fileDecompress != null)
                    fileDecompress(decompress);
            };
            DownloadMgr.Instance.Tasks = allTasks;
            DownloadMgr.Instance.AllDownloadFinished = AllFinished;
            DownloadMgr.Instance.TaskProgress = TaskProgress;
            DownloadMgr.Instance.FileDecompress = filedecompress;
            DownloadMgr.Instance.CheckDownloadList();
        }

    看到了委托的好处了吧,LOLGameDriver里面的委托直接传递到Download里面去执行,真正做到解耦和。符合迪米特法则,不是朋友的类,坚决不要持有他的依赖。

    Ok,我们在下载之前先要检测下任务列表,因为可能我们是断点下载,比如说有些文件我们已经下载完成了,放在磁盘上,但是第二天起来又继续更新下载,那么已经下载的这些任务我们得检测下,看是否已经下载过了。

    所以在DownloadMgr创建一个CheckDownloadList()方法:

     

        /// <summary>
        /// 检测下载列表
        /// </summary>
        public void CheckDownloadList()
        {
            if (Tasks.Count == 0)
            {
                //下载列表为空
                return;
            }
            //已经下载完成的数目
            int finishedCount = 0;
            foreach (var task in Tasks)
            {
                if (task.bFineshed && !task.bDownloadAgain)
                {
                    finishedCount++;
                }
                else 
                {
                    //是否文件存放的目录已经存在,如果不存在就创建
                    string dir = Path.GetDirectoryName(task.FileName);
                    if (!string.IsNullOrEmpty(dir) && !Directory.Exists(task.FileName))
                    {
                        Directory.CreateDirectory(dir);
                    }
                    //开始断点下载
                }
                break;
            }
            //说明全部任务下载完成
            if (finishedCount > m_listTasks.Count - 1)
            {
                m_listTasks.Clear();
                m_listTasks = null;
                if (AllDownloadFinished != null)
                {
                    AllDownloadFinished();
                    AllDownloadFinished = null;
                }
            }
        }

    OK,我们开始写断点下载的代码:

    之前我们讲过一个类要具有单一职责,但是发现单例模式好像违背了单一职责,单例的好像什么事情都干,就拿这个DownloadMgr来说,我们知道,下载分为两种:

    1.断点下载(可继续)

    2.非断点下载(重新下载)

    那么,因为都是下载,所以我们得吧代码全部写到DownloadMrg里面去,那么这样的职责就分的不清不楚。那么怎么避免呢?

    所以我们可以重新封装一个职责类,比如断点下载:ThreadDownloadBreakPoint.cs

     

    using UnityEngine;
    using System.Collections;
    /// <summary>
    /// 断点下载类
    /// </summary>
    public class ThreadDownloadBreakPoint
    {
        public DownloadMgr Mgr { get; set; }
        public DownloadTask Task { get; set; }
        public ThreadDownloadBreakPoint()
        {
     
        }
        public ThreadDownloadBreakPoint(DownloadMgr mgr, DownloadTask task)
        {
            Mgr = mgr;
            Task = task;
        }
        public void Download()
        {
            Mgr.DownloadFileBreakPoint(Task.Url, Task.FileName);
        }
    }

     实则就是将DownloadMgr重新封装一层,但这个非常有用,因为职责变的清晰,我们完全可以不理会DownloadMrg里面是怎么实现断点下载的,我们只知道ThreadDownloadBreakPoint有提供断点下载的方法接口。

    因为这个项目是我一个人开发的,所以我们必须得知道底层的实现。

    回到DownloadMrg新建一个方法:DownloadFileBreakPoint

     

        /// <summary>
        /// 断点下载文件
        /// </summary>
        /// <param name="url">网站资源url</param>
        /// <param name="filePath">保存文件路径</param>
        public void DownloadFileBreakPoint(string url, string filePath)
        {
            try
            {
                var requestUrl = new Uri(url);
                var request = (HttpWebRequest)WebRequest.Create(requestUrl);
                var response = (HttpWebResponse)request.GetResponse();
                long contentLength = response.ContentLength;
                response.Close();
                request.Abort();
                long leftSize = contentLength;
                long position = 0;
                if (File.Exists(filePath))
                {
                    Debug.Log("需要下载的文件已经存在");
                    using (FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate,FileAccess.ReadWrite, FileShare.ReadWrite))
                    {
                        leftSize = contentLength - fs.Length;
                        position = fs.Length;
                    }
                }
                var partRequest = (HttpWebRequest)WebRequest.Create(requestUrl);
                if (leftSize > 0)
                {
                    partRequest.AddRange((int)position, (int)(position + leftSize));
                    var partResponse = (HttpWebResponse)partRequest.GetResponse();
                    ReadBytesFromResponseToFile(url, partResponse, position, leftSize, contentLength, filePath);
                    partResponse.Close();
                }
                partRequest.Abort();
                //下载完成
                Finished(url);
            }
            catch (Exception e)
            {
                Finished(url, e);
            }
        }

    ReadBytesFromResponseToFile:

     

       /// <summary>
        /// 从网上下载资源字节到存到文件中
        /// </summary>
        /// <param name="requestUrl"></param>
        /// <param name="response"></param>
        /// <param name="allFilePointer"></param>
        /// <param name="length"></param>
        /// <param name="totalSize"></param>
        /// <param name="filePath"></param>
        private void ReadBytesFromResponseToFile(string requestUrl,WebResponse response,long allFilePointer,long length,long totalSize,string filePath)
        {
            try
            {
                int bufferLength = (int)length;
                byte[] buffer = new byte[bufferLength];
                //本块位置指针
                int currentChunkPointer = 0;
                //指针偏移量
                int offset = 0;
                using (Stream resStream = response.GetResponseStream())
                {
                    //下载的字节数
                    int receivedBytesCount;
                    do
                    {
                        receivedBytesCount = resStream.Read(buffer, offset, bufferLength - offset);
                        offset += receivedBytesCount;
                        if (receivedBytesCount > 0)
                        {
                            byte[] bufferCopyed = new byte[receivedBytesCount];
                            Buffer.BlockCopy(buffer, currentChunkPointer, bufferCopyed, 0, bufferCopyed.Length);
                            using (FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
                            {
                                fs.Position = allFilePointer;
                                fs.Write(bufferCopyed,0,bufferCopyed.Length);
                                fs.Close();
                            }
                            float progress = (allFilePointer + bufferCopyed.Length) / totalSize;
                            //执行进度委托
                            Action action = () => 
                            {
                                if (m_listTasks.Any(task => task.Url == requestUrl))
                                {
                                    DownloadTask task = this.m_listTasks.FirstOrDefault(t => t.Url == requestUrl);
                                    task.TotalProgress((int)(progress*100), 0, 0);
                                    if (TaskProgress != null)
                                    {
                                        int finishedCount = this.m_listTasks.Count(t => t.bFineshed);
                                        string filename = task.FileName.Substring(task.FileName.LastIndexOf("/") + 1);
                                        TaskProgress(m_listTasks.Count, finishedCount, filename);
                                    }
                                }
                            };
                            action.Invoke();
                            currentChunkPointer += receivedBytesCount;
                            allFilePointer += receivedBytesCount;
                        }
                    } while (receivedBytesCount != 0);
                }
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
        }

    Finished:

     

        private void Finished(string url, Exception e = null)
        {
            Debug.Log("下载完成!");
            DownloadTask task = this.m_listTasks.FirstOrDefault(t => t.Url == url);
            if (task != null)
            {
                if (e != null)
                {
                    Debug.LogWarning("下载出错!" + e.Message);
                }
                else 
                {
                    //验证MD5码
                    DownloadFinishedWithMd5(task);
                }
            }
        }
    

     

    最后进行Md5的验证,成功就修改任务finished布尔为true。

     

        /// <summary>
        /// 下载完成之后验证MD5码
        /// </summary>
        /// <param name="task"></param>
        private void DownloadFinishedWithMd5(DownloadTask task)
        {
            string md5 = UnityTools.BuildFileMd5(task.FileName);
            if ("123".Trim() != task.MD5.Trim())
            {
                //MD5验证失败
                if (File.Exists(task.FileName))
                {
                    File.Delete(task.FileName);
                }
                task.bDownloadAgain = true;
                task.bFineshed = false;
                CheckDownloadList();
                return;
            }
            if (FileDecompress != null)
            {
                FileDecompress(true);
            }
            task.bDownloadAgain = false;
            task.bFineshed = true;
            task.Finished();
            if (FileDecompress != null)
            {
                FileDecompress(false);
            }
            CheckDownloadList();
        }

    因为我做下测试,所以我这里只是用“123”来代替md5,实际上的话,得自己取得下载后文件的MD5码和服务器上的MD5验证,如果通过的话,就完成,没通过的话重新下载。

    UnityTools.BuildFileMd5:

     

            /// <summary>
            /// 生成文件的md5码
            /// </summary>
            /// <param name="filePath"></param>
            /// <returns></returns>
            public static string BuildFileMd5(string filePath)
            {
                string fileMd5 = null;
                try
                {
                    using (FileStream fs = File.OpenRead(filePath))
                    {
                        MD5 md5 = MD5.Create();
                        byte[] fileMd5bytes = md5.ComputeHash(fs);
                        fileMd5 = System.BitConverter.ToString(fileMd5bytes).Replace("_", "").ToLower();
                    }
                }
                catch (Exception e)
                {
                    Debug.Log(e);
                }
                return fileMd5;
            }
    

     

      

     

    最后回到DownloadMgr里面的CheckDownloadList方法里面,在开始断点下载处,添加代码:

     

        /// <summary>
        /// 检测下载列表
        /// </summary>
        public void CheckDownloadList()
        {
            if (Tasks.Count == 0)
            {
                //下载列表为空
                return;
            }
            //已经下载完成的数目
            int finishedCount = 0;
            foreach (var task in Tasks)
            {
                if (task.bFineshed && !task.bDownloadAgain)
                {
                    finishedCount++;
                }
                else 
                {
                    //是否文件存放的目录已经存在,如果不存在就创建
                    string dir = Path.GetDirectoryName(task.FileName);
                    if (!string.IsNullOrEmpty(dir) && !Directory.Exists(task.FileName))
                    {
                        Directory.CreateDirectory(dir);
                    }
                    //开始断点下载
                    ThreadDownloadBreakPoint bpDownload = new ThreadDownloadBreakPoint(this, task);
                    Thread t = new Thread(bpDownload.Download);
                    t.Start();
                }
                break;
            }
            if (finishedCount > m_listTasks.Count - 1)
            {
                m_listTasks.Clear();
                m_listTasks = null;
                if (AllDownloadFinished != null)
                {
                    AllDownloadFinished();
                    AllDownloadFinished = null;
                }
            }
        }

    开启一个线程来断点下载。

    OK,欧了。我们来做下测试。

    到www目录下的LOLGameDemo文件夹下新建一个文件夹:LOLPackage,跟我们的ServerVersion.xml里面的PackageUrl的文件目录一致:

    这个文件主要是来存放需要更新的资源文件包。然后在里面新建一个压缩包,和我们的PackageMd5List的name属性一致:

    然后就启动程序,观察unity:

    可以看到在Project的Resources文件夹下,多了一个压缩包:就是我们放在Http服务器的压缩包,说明下载成功了。

     

    仿LOL项目开发第三天链接地址

     

  • 相关阅读:
    Eclipse无法导入一些相关类的方法,出现Multiple markers at this line
    查看共享命令
    eclipse历史版本的下载地址
    MyEclipse的快捷键
    Hibernate的隔离级别
    eclipse插件下载地址
    UML类图几种关系的总结
    Oracle正则表达式函数:regexp_like、regexp_substr、regexp_instr、regexp_replace
    回滚段的学习
    Oracle 字符集的查看和修改
  • 原文地址:https://www.cnblogs.com/CaomaoUnity3d/p/5459265.html
Copyright © 2020-2023  润新知