仿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项目开发第二天的更多相关文章
- 仿LOL项目开发第三天
仿LOL项目开发第二天 by草帽 昨个我们已经实现了下载功能,但是发现没有,下载的包是压缩的,没有解压开,那么Unity是识别不了的. 所以今个我们来讲讲如何实现解压文件. 还记得吗,我们在Downl ...
- 仿LOL项目开发第一天
---恢复内容开始--- 仿LOL项目开发第一天 by---草帽 项目源码研究群:539117825 最近看了一个类似LOL的源码,颇有心得,所以今天呢,我们就来自己开发一个类似于LOL的游戏demo ...
- 仿LOL项目开发第六天
仿LOL项目开发第六天 by草帽 OK,因为更新模块已经处理好了,接着开始登陆的编写.那么我们就需要状态机的管理. 所谓状态机就是在哪个状态执行那个状态的代码逻辑: 那么我们开始编写GameState ...
- 仿LOL项目开发第四天
---恢复内容开始--- 仿LOL项目开发第四天 by草帽 上节讲了几乎所有的更新版本的逻辑,那么这节课我们来补充界面框架的搭建的讲解. 我们知道游戏中的每个界面都有自己的一个类型:比如登陆界面,创建 ...
- 仿LOL项目开发第九天
仿LOL项目开发第九天 by 草帽 OK,今天我们完全换了一种风格,抛弃了Unity3d的c#语法,我们来写写java的项目. 说到java服务器,当然有些人可能鄙视java的服务器速度太慢,但是相对 ...
- 仿LOL项目开发第八天
仿LOL项目开发第八天 by 草帽 这节我们继续上节所讲的内容,上节我们初始化好了LoginWindow,当我们点击确认选择服务器按钮的时候,就发送服务器id给游戏服务器. 这里就开始涉及到客户端需要 ...
- 仿LOL项目开发第七天
仿LOL项目开发第七天 by 草帽 不知不觉已经写到了第七篇这种类型的博客,但是回过头看看之前写的,发现都只能我自己能看懂. 我相信在看的童鞋云里雾里的,因为我基本上没怎么详细讲一个脚本怎么用?但是你 ...
- 仿LOL项目开发第五天
仿LOL项目开发第五天 by草帽 今天呢,我们看下能开发什么内容,首先上节我们已经讲了UI框架的搭建,上节还遗留下很多问题,比如说消息的字符是代码里面自己赋值的. 那么就比较死板,按照正常的逻辑,那些 ...
- Android项目开发第二天,关于GitHub
一. 今天在网上学习了如何使用GitHub,了解了GitHub是干什么的. 作为开源代码库以及版本控制系统,Github拥有超过900万开发者用户.随着越来越多的应用程序转移到了云上,Github已经 ...
随机推荐
- 三十分钟理解计算图上的微积分:Backpropagation,反向微分
神经网络的训练算法,目前基本上是以Backpropagation (BP) 反向传播为主(加上一些变化),NN的训练是在1986年被提出,但实际上,BP 已经在不同领域中被重复发明了数十次了(参见 G ...
- jmeter-----GUI运行和非GUI运行的区别
gui:界面会消耗很多资源,并且运行的结果是保存在Jmeter运行的内存中.当时间一长,内存增长到一定程度,就会报错,甚至假死. 非gui:实时的将运行log文件保存到本地文件中,不会撑爆内存.并且对 ...
- 设置或者获取CheckboxList控件的选中值
1.设置CheckBoxList选中的值 /// <summary> /// 设置CheckBoxList中哪些是选中了的 /// </summary> /// <par ...
- TeX Live & TeXstudio 安装手记
数据库课上又看到了那位用 beamer 做 slides 的师兄,想到自己一拖再拖的LaTeX入门,决定赶快动手装个环境再说~在经过一番搜索和研究之后决定先在 windows 底下试用,选择 TeX ...
- 05 java 基础:运算符、程序结构
赋值运算符 : = 三元运算符 : ? 算术运算符 : +.- .*./.% 自增自减运算符: ++.-- 关系运算符:>.<.==.>=.<=.!= 逻辑运算符 :& ...
- 解决 VUE 微信 IOS 路由跳转问题
watch: { "$route"(){ if (/iPhone|mac|iPod|iPad/i.test(navigator.userAgent)) { location.hre ...
- AngularJS Intellisense in Visual Studio 2012
Recently, a lot of people have asked for Intellisense support for AngularJS in the Visual Studio HTM ...
- python3 怎么统计英文文档常用词?(附解释)
# coding: utf-8 # In[32]: #import requests #from bs4 import BeautifulSoup #res = requests.get(" ...
- JavaScript三种数据类型之间的互转
一:number<===>string 数字类型和字符串类型之间的互相转换 number===>string 数字转字符串有三种方式: 1.在数字后面 +“ ”; 2.利用字符串的 ...
- javascript的优缺点和内置对象
1)优点:简单易用,与Java有类似的语法,可以使用任何文本编辑工具编写,只需要浏览器就可执行程序,并且事先不用编译,逐行执行,无需进行严格的变量声明,而且内置大量现成对象,编写少量程序可以完成目标: ...