仿LOL项目开发第二天
仿LOL项目开发第二天
by草帽
接着上节来讲,上节更新还没开始写代码逻辑,今天我们补充完整。
我们找到VersionManager脚本里面的CheckVersion方法:
首先我们想到检测版本,需要从服务器下载信息,那么肯定要提前检测下网络是否良好,并比较版本信息。
所以,我们写个BeforeCheck方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | /// <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类:
1 2 3 4 5 6 | 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持久路径
1 | public readonly static string CfgPath = Application.persistentDataPath + "/cfg.xml" ; |
然后我们在SystemConfig里面创建LoadCfgInfo()方法,在Init()里面调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | /// <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():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | /// <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():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /// <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():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /// <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; } |
因为我们下载的服务器的版本文本也要保存在持久文件目录,所以:
1 | public readonly static string ServerVersionPath = Application.persistentDataPath + "/serverVersion.xml" ; |
还有之前我们只是初始化本地版本信息类的实例,所以现在我们在VersionManager创建一个服务端的版本信息类:
1 2 3 4 5 6 7 8 | /// <summary> /// 本地版本信息属性 /// </summary> public VersionManagerInfo LocalVersion { get ; private set ; } /// <summary> /// 服务版本信息属性 /// </summary> public VersionManagerInfo ServerVersion { get ; private set ; } |
然后再次回到BeforeCheck方法里面,终于回来了!0.0!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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);<br> //开始进行比较版本号 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方法写入到里面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /// <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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | /// <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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | 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方法的开始下载资源包委托里面调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | 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()方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | /// <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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | /// <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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | /// <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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | /// <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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | /// <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方法里面,在开始断点下载处,添加代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | /// <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服务器的压缩包,说明下载成功了。
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET制作智能桌面机器人:结合BotSharp智能体框架开发语音交互
· 软件产品开发中常见的10个问题及处理方法
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(四):结合BotSharp
· Vite CVE-2025-30208 安全漏洞
· 《HelloGitHub》第 108 期
· MQ 如何保证数据一致性?
· 一个基于 .NET 开源免费的异地组网和内网穿透工具