开发过程中常把需要更新的资源文件放到云服务器中,每次游戏运行时候去比对版本文件信息,定量更新本地文件和增量下载远程文件。
版本文件生成代码:
private void OnCreateVersionTextCallBack()
{
string path = Application.dataPath + "/../AssetBundles/" + arrBuildTarget[buildTargetIndex];
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
string strVersionTextPath = path + "/VersionFule.txt";//版本文件路径
//如果版本文件存在 则删除
IOUtil.DeleteFile(strVersionTextPath);
StringBuilder sbContent = new StringBuilder();
DirectoryInfo dirctory = new DirectoryInfo(path);
//拿到文件夹下所有文件
FileInfo[] arrFiles = dirctory.GetFiles("*", SearchOption.AllDirectories);
for (int i = 0; i < arrFiles.Length; i++)
{
FileInfo file = arrFiles[i];
string fullName = file.FullName;//全名 包括路径扩展名
//相对路径
string name = fullName.Substring(fullName.IndexOf(arrBuildTarget[buildTargetIndex]) + arrBuildTarget[buildTargetIndex].Length + 1);
string md5 = EncryptUtil.GetFileMD5(fullName);//文件的MD5
if (md5 == null) continue;
string size = Math.Ceiling(file.Length / 1024f).ToString();//文件大小
bool isFirstData = false;//是否初始数据
bool isBreak = false;
for (int j = 0; j < m_lst.Count; j++)
{
foreach (string xmlPath in m_lst[j].PathList)
{
string tempPath = xmlPath;
if (xmlPath.IndexOf(".") != -1)
{
tempPath = xmlPath.Substring(0, xmlPath.IndexOf("."));
}
if (name.IndexOf(tempPath, StringComparison.CurrentCultureIgnoreCase) != -1)
{
isFirstData = m_lst[j].IsFirstData;
isBreak = true;
break;
}
}
if (isBreak) break;
}
if (name.IndexOf("DataTable") != -1 || name.IndexOf("Windows") != -1)
{
isFirstData = true;
}
string strLine = string.Format("{0} {1} {2} {3}", name, md5, size, isFirstData ? 1 : 0);
sbContent.AppendLine(strLine);
}
IOUtil.CreateTextFile(strVersionTextPath, sbContent.ToString());
Debug.Log("生成版本文件");
}
运行代码后生成VersionFule.txt 版本信息文件——文件名(文件相对下载路径),文件MD5值,文件大小,是否初始文件。
为了模拟云服务器下载过程,可以在本地安装HFS服务器模拟一下下载过程
把StreamingAsset中文件解压到PersistentDataPath文件夹下(权限高,可读可写):
/// <summary>
/// 读取streamingAssetsPath资源目录的版本文件
/// </summary>
/// <param name="fileUrl"></param>
/// <param name="onReadStreamingAssetOver"></param>
/// <returns></returns>
private IEnumerator ReadStreamingAssetVersionFile(string fileUrl, Action<string> onReadStreamingAssetOver)
{
UISceneInitCtrl.Instance.SetProgress("正在准备进行资源初始化", 0);
using (WWW www = new WWW(fileUrl))
{
yield return www;
if (www.error == null)
{
if (onReadStreamingAssetOver != null)
{
onReadStreamingAssetOver(Encoding.UTF8.GetString(www.bytes));
}
}
else
{
onReadStreamingAssetOver("");
}
}
}
//读取StreamingAsset文件回调
private void OnReadStreamingAssetOver(string obj)
{
GlobalInit.Instance.StartCoroutine(InitStreamingAssetList(obj));
}
///循环解压StreamingAsset文件到persistentDataPath 文件夹下
private IEnumerator InitStreamingAssetList(string conent)
{
if (string.IsNullOrEmpty(conent))
{
InitCheckVersion();
yield break;
}
string[] arr = conent.Split('\n');
//循环解压
for (int i = 0; i < arr.Length; i++)
{
string[] arrInfo = arr[i].Split(' ');
string fileUrl = arrInfo[0];//短路径
yield return GlobalInit.Instance.StartCoroutine(AsserLoadToLocal(m_StreamingAssetsPath + fileUrl, LocalFilePath + fileUrl));
float value = (i + 1) / (float)arr.Length;
UISceneInitCtrl.Instance.SetProgress(string.Format("初始化资源不消耗流量 {0}/{1}", i + 1, arr.Length), value);
}
//解压版本文件
yield return GlobalInit.Instance.StartCoroutine(AsserLoadToLocal(m_StreamingAssetsPath + m_VersionFuleName, LocalFilePath + m_VersionFuleName));
InitCheckVersion();
}
封装一下下载文件实体类:
public class DownloadDataEntity
{
/// <summary>
/// 资源名称
/// </summary>
public string FullName;
/// <summary>
/// MD5
/// </summary>
public string MD5;
/// <summary>
/// 资源大小(k)
/// </summary>
public int Size;
/// <summary>
/// 是否初始资源
/// </summary>
public bool IsFirstData;
}
WWW获取服务器版本文件数据,比对本地版本文件数据,将需要下载的数据列表传给文件下载中心。
private void OnInitVersionCallBack(List<DownloadDataEntity> serverDownloadData)
{
if (serverDownloadData == null)
{
//直接读取本地文件
Debug.LogError("没有网络,直接加载本地文件");
UISceneInitCtrl.Instance.SetProgress("资源更新完毕", 1);
if (OnInitComplete != null)
{
OnInitComplete(2);
}
}
else
{
//需要下载的数据列表
m_ServerDataList = serverDownloadData;
if (File.Exists(m_LoaclVersionPath))
{
//与服务器对比
//服务器数据
//服务器数据VersionFule中的 fullname 和 MD5
Dictionary<string, string> serverDic = PackDownloadDataDic(serverDownloadData);
//本地VersionFule数据
string content = IOUtil.GetFileText(m_LoaclVersionPath);
// 本地VersionFule中的 fullname 和 MD5
Dictionary<string, string> clientDic = PackDownloadDataDic(content);
//本地 VersionFule 转化成 List<DownloadDataEntity>
m_LocalDataList = PackDownloadData(content);
//1.新加的初始资源
for (int i = 0; i < serverDownloadData.Count; i++)
{
if (serverDownloadData[i].IsFirstData && !clientDic.ContainsKey(serverDownloadData[i].FullName))
{
Debug.Log("download " + serverDownloadData[i].FullName);
m_NeddDownloadDataList.Add(serverDownloadData[i]);
}
}
//2.对比已经下载过的 但是有更新的资源
foreach (var item in clientDic)
{
//如果md5不一样
if (serverDic.ContainsKey(item.Key) && serverDic[item.Key] != item.Value)
{
DownloadDataEntity entity = GetDownloadData(item.Key, serverDownloadData);
if (entity != null)
{
Debug.Log("download " + entity.FullName);
m_NeddDownloadDataList.Add(entity);
}
}
}
}
else
{
//如果本地没有版本文件,初始资源需要下载的文件
for (int i = 0; i < serverDownloadData.Count; i++)
{
if (serverDownloadData[i].IsFirstData)
{
Debug.Log("download " + serverDownloadData[i].FullName);
m_NeddDownloadDataList.Add(serverDownloadData[i]);
}
}
}
if (m_NeddDownloadDataList.Count == 0)
{
Debug.LogError("不需要网络数据更新");
UISceneInitCtrl.Instance.SetProgress("资源更新完毕", 1);
if (OnInitComplete != null)
{
OnInitComplete(0);
}
SaveLocalData();
return;
}
//进行下载 拿到下载列表
AssetBundleDownload.Instance.DownloadFiles(m_NeddDownloadDataList);
SaveLocalData();
}
}
封装一个文件异步主下载器:
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
/// <summary>
/// 主下载器 检查版本 分配下载器任务
/// </summary>
public class AssetBundleDownload : SingletonMono<AssetBundleDownload>
{
private string m_VersionUrl;
private Action<List<DownloadDataEntity>> m_OnInityVersion;
/// <summary>
/// 下载器的数组
/// </summary>
private AssetBundleDownloadRoutine[] m_Routines = new AssetBundleDownloadRoutine[DownloadMgr.DownloadRoutineNum];
private int m_RoutineIndex = 0;//下载器索引
private bool m_IsDownloadover;
protected override void OnStart()
{
base.OnStart();
//真正的运行
StartCoroutine(DownLoadVersion(m_VersionUrl));
}
private float m_Time = 2;//采样时间
private float m_AleadyTime = 0;//已经下载的时间
private float m_NeedTime = 0;//剩余时间
private float m_Speed = 0;//下载速度
protected override void OnUpdate()
{
base.OnUpdate();
//如果需要下载的数量大于0 并且没有下载完成
if (TotalCount > 0 && !m_IsDownloadover)
{
int totalCompleteCount = CurrCompeletTotalCount();
totalCompleteCount = totalCompleteCount == 0 ? 1 : totalCompleteCount;
int totalCompleteSize = CurrCompeletTotalSize();
m_AleadyTime += Time.deltaTime;
if (m_AleadyTime > m_Time && m_Speed == 0)
{
m_Speed = totalCompleteSize / m_Time;
}
if (m_Speed > 0)
{
//剩余时间=(总大小-已经下载的大小)/速度
m_NeedTime = (ToalSize - totalCompleteSize) / m_Speed;
}
string str = string.Format("资源正在下载{0}/{1}", totalCompleteCount, TotalCount);
// string strProgress = string.Format("下载进度={0}",totalCompleteSize/(float)ToalSize);
UISceneInitCtrl.Instance.SetProgress(str, totalCompleteCount / (float)TotalCount);
if (m_NeedTime > 0)
{
string strNeedTime = string.Format("剩余{0}秒", m_NeedTime);
}
if (totalCompleteCount == TotalCount)
{
m_IsDownloadover = true;
///DebugApp.Log("资源更新完毕");
UISceneInitCtrl.Instance.SetProgress("资源更新完毕", 1);
if (DownloadMgr.Instance.OnInitComplete != null)
{
DownloadMgr.Instance.OnInitComplete(1);
}
}
}
}
/// <summary>
/// 初始化服务器的版本信息
/// </summary>
/// <param name="url"></param>
/// <param name="onInityVersion"></param>
public void InitServerVersion(string url, Action<List<DownloadDataEntity>> onInityVersion)
{
m_VersionUrl = url;
m_OnInityVersion = onInityVersion;
}
/// <summary>
/// 查看CDN服务器上Version信息
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
private IEnumerator DownLoadVersion(string url)
{
WWW www = new WWW(url);
float timeOut = Time.time;
float progress = www.progress;
while (www != null && www.isDone)
{
if (progress < www.progress)
{
timeOut = Time.time;
progress = www.progress;
}
if ((Time.time - timeOut) > DownloadMgr.DownLoadTimeOut)
{
DebugApp.Log("下载超时");
yield break;
}
}
yield return www;
if (www != null && www.error == null)
{
string conten = www.text;
Debug.Log(conten);
if (m_OnInityVersion != null)
{
m_OnInityVersion(DownloadMgr.Instance.PackDownloadData(conten));
}
}
else
{
if (m_OnInityVersion != null)
{
m_OnInityVersion(null);
}
DebugApp.Log("下载失败 原因:" + www.error);
}
}
/// <summary>
/// 总大小
/// </summary>
public int ToalSize
{
get;
private set;
}
/// <summary>
/// 总数量
/// </summary>
public int TotalCount
{
get;
private set;
}
/// <summary>
/// 当前已经下载的文件总大小
/// </summary>
/// <returns></returns>
public int CurrCompeletTotalSize()
{
int compeleteTotalSize = 0;
for (int i = 0; i < m_Routines.Length; i++)
{
if (m_Routines[i] == null) continue;
compeleteTotalSize += m_Routines[i].DownloadSize;
}
return compeleteTotalSize;
}
/// <summary>
/// 当前已经下载的文件总数量
/// </summary>
/// <returns></returns>
public int CurrCompeletTotalCount()
{
int compeleteTotalCount = 0;
for (int i = 0; i < m_Routines.Length; i++)
{
if (m_Routines[i] == null) continue;
compeleteTotalCount += m_Routines[i].CompleteCount;
}
return compeleteTotalCount;
}
/// <summary>
/// 下载文件
/// </summary>
/// <param name="downloadLst"></param>
public void DownloadFiles(List<DownloadDataEntity> downloadLst)
{
ToalSize = 0;
TotalCount = 0;
//初始化下载器
for (int i = 0; i < m_Routines.Length; i++)
{
if (m_Routines[i] == null)
{
m_Routines[i] = gameObject.AddComponent<AssetBundleDownloadRoutine>();
}
}
for (int i = 0; i < downloadLst.Count; i++)
{
m_RoutineIndex = m_RoutineIndex % m_Routines.Length;//0-4
//其中一个下载器 分配一个文件
m_Routines[m_RoutineIndex].AddDownload(downloadLst[i]);
m_RoutineIndex++;
ToalSize += downloadLst[i].Size;
TotalCount++;
}
//让下载器开始下载
for (int i = 0; i < m_Routines.Length; i++)
{
if (m_Routines[i] == null) continue;
m_Routines[i].StartDownload();
}
}
public IEnumerator DownloadData(DownloadDataEntity currDownLoadData, Action<bool> onComplete)
{
string dataUrl = DownloadMgr.DownloadUrl + currDownLoadData.FullName;//资源下载路径
//短路径 用于创建文件夹
string path = currDownLoadData.FullName.Substring(0, currDownLoadData.FullName.LastIndexOf('\\'));
//得到本地路径
string localFilePath = DownloadMgr.Instance.LocalFilePath + path;
if (!Directory.Exists(localFilePath))
{
Directory.CreateDirectory(localFilePath);
}
WWW www = new WWW(dataUrl);
float timeOut = Time.time;
float progress = www.progress;
while (www != null && www.isDone)
{
if (progress < www.progress)
{
timeOut = Time.time;
progress = www.progress;
}
if (Time.time - timeOut > DownloadMgr.DownLoadTimeOut)
{
DebugApp.LogError("下载超时");
onComplete(false);
yield break;
}
yield return null; //等一帧 会卡死
}
yield return www;
if (www != null && www.error == null)
{
using (FileStream fs = new FileStream(DownloadMgr.Instance.LocalFilePath + currDownLoadData.FullName, FileMode.Create, FileAccess.ReadWrite))
{
fs.Write(www.bytes, 0, www.bytes.Length);
}
}
//写入本地文件
DownloadMgr.Instance.ModifyLocalData(currDownLoadData);
if (onComplete != null)
{
onComplete(true);
}
}
}
封装一个文件异步下载器:
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
/// <summary>
/// 下载器
/// </summary>
public class AssetBundleDownloadRoutine : MonoBehaviour
{
//需要下载的文件列表
private List<DownloadDataEntity> m_List = new List<DownloadDataEntity>();
private DownloadDataEntity m_CurrDownloadData;//当前正在下载的数据
/// <summary>
/// 需要下载的数量
/// </summary>
public int NeedDownloadCount
{
private set;
get;
}
/// <summary>
/// 已经下载完成的数量
/// </summary>
public int CompleteCount
{
private set;
get;
}
private int m_DownloadSize;//已经下载好的文件的总大小
private int m_CurrDownloadSize;//当前下载的文件大小
/// <summary>
/// 这个下载器已经下载的大小
/// </summary>
public int DownloadSize
{
get { return m_DownloadSize + m_CurrDownloadSize; }
}
//是否开始下载
public bool IsStartDownload
{
private set;
get;
}
/// <summary>
/// 添加一个下载对象
/// </summary>
/// <param name="entity"></param>
public void AddDownload(DownloadDataEntity entity)
{
Debug.LogError("添加需要下载数据" + entity.FullName);
m_List.Add(entity);
}
/// <summary>
/// 开始下载
/// </summary>
public void StartDownload()
{
IsStartDownload = true;
NeedDownloadCount = m_List.Count;
}
private void Update()
{
if (IsStartDownload)
{
IsStartDownload = false;
StartCoroutine(DownloadData());
}
}
private IEnumerator DownloadData()
{
if (NeedDownloadCount == 0) yield break;
m_CurrDownloadData = m_List[0];
string dataUrl = DownloadMgr.DownloadUrl + m_CurrDownloadData.FullName;//资源下载路径
int lastIndex = m_CurrDownloadData.FullName.LastIndexOf('\\');
if (lastIndex > -1)
{
//短路径 用于创建文件夹
string path = m_CurrDownloadData.FullName.Substring(0, lastIndex);
//得到本地路径
string localFilePath = DownloadMgr.Instance.LocalFilePath + path;
if (!Directory.Exists(localFilePath))
{
Directory.CreateDirectory(localFilePath);
}
}
WWW www = new WWW(dataUrl);
float timeOut = Time.time;
float progress = www.progress;
while (www != null && www.isDone)
{
if (progress < www.progress)
{
timeOut = Time.time;
progress = www.progress;
m_CurrDownloadSize = (int)(m_CurrDownloadData.Size * progress);
}
if (Time.time - timeOut > DownloadMgr.DownLoadTimeOut)
{
DebugApp.LogError("下载超时");
yield break;
}
yield return null; //等一帧 会卡死
}
yield return www;
if (www != null && www.error == null)
{
using (FileStream fs = new FileStream(DownloadMgr.Instance.LocalFilePath + m_CurrDownloadData.FullName, FileMode.Create, FileAccess.ReadWrite))
{
fs.Write(www.bytes, 0, www.bytes.Length);
}
}
//下载成功
m_CurrDownloadSize = 0;
m_DownloadSize += m_CurrDownloadData.Size;
//写入本地文件
DownloadMgr.Instance.ModifyLocalData(m_CurrDownloadData);
m_List.RemoveAt(0);
CompleteCount++;
if (m_List.Count == 0)
{
m_List.Clear();
}
else
{
IsStartDownload = true;
}
}
}