killapp/Assets/Scripts/Base/NetworkCtrl.cs

340 lines
12 KiB
C#
Raw Permalink Normal View History

2025-11-18 09:18:48 +08:00
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
namespace Kill.Base
{
public class NetworkCtrl : MonoBehaviour
{
public static NetworkCtrl Instance;
public string localPath;
public string streamingPath;
void Awake()
{
Instance = this;
localPath = Application.persistentDataPath;
streamingPath = Application.streamingAssetsPath;
}
async Task Start()
{
nowResourceUrl = resourceUrls[nowResourceIndex];
#if UNITY_EDITOR_WIN
platform = "Windows";
#elif UNITY_EDITOR_OSX
platform = "Mac";
#elif UNITY_ANDROID
platform = "Android";
#elif UNITY_IOS
platform = "iOS";
#endif
await Init();
}
public async Task Init()
{
Debug.Log("初始化网络控制");
GetNowBundleList();
await GetBundleList();
await UpdateResources();
await LoadRes.Instance.Init();
}
#region
public string[] resourceUrls;
public int nowResourceIndex;
string nowResourceUrl;
public string resourceVersion;
string platform;
public AssetBundleInfoList nowBundleList;
public AssetBundleInfoList newBundleList;
public void GetNowBundleList()
{
if (File.Exists(localPath + "/list.json"))
{
nowBundleList = JsonUtility.FromJson<AssetBundleInfoList>(File.ReadAllText(localPath + "/list.json"));
}
else if (File.Exists(streamingPath + "/list.json"))
{
nowBundleList = JsonUtility.FromJson<AssetBundleInfoList>(File.ReadAllText(streamingPath + "/list.json"));
}
else
{
nowBundleList = null;
}
}
public async Task GetBundleList()
{
string url = nowResourceUrl + $"{resourceVersion}/{platform}/list.json";
using (UnityWebRequest webRequest = UnityWebRequest.Get(url))
{
var operation = webRequest.SendWebRequest();
while (!operation.isDone)
{
await Task.Yield();
}
if (webRequest.result == UnityWebRequest.Result.Success)
{
AssetBundleInfoList list = JsonUtility.FromJson<AssetBundleInfoList>(webRequest.downloadHandler.text);
newBundleList = list;
}
else
{
Debug.LogError(url + "获取资源列表失败" + webRequest.error);
ErrorTip.Instance.Init("获取资源列表失败", url, async (msg) =>
{
await GetBundleList();
});
}
}
}
public async Task UpdateResources()
{
// 获取更新列表
List<AssetBundleInfo> updateList = GetUpdateList();
if (updateList.Count > 0)
{
// 开始下载更新
bool success = await DownloadUpdateFiles(updateList, (downloaded, total) =>
{
float progress = (float)downloaded / total;
Debug.Log($"更新进度: {progress:P}");
});
if (success)
{
Debug.Log("资源更新完成");
}
else
{
Debug.LogError("资源更新失败");
}
}
else
{
Debug.Log("资源已是最新版本");
}
}
/// <summary>
/// 对比当前资源列表和新资源列表,找出需要更新的文件
/// </summary>
/// <returns>需要更新的资源信息列表</returns>
public List<AssetBundleInfo> GetUpdateList()
{
List<AssetBundleInfo> updateList = new List<AssetBundleInfo>();
// 如果没有当前列表,则全部都需要下载
if (nowBundleList == null || nowBundleList.assetbundles == null)
{
return newBundleList.assetbundles ?? new List<AssetBundleInfo>();
}
// 创建当前资源字典以便快速查找
Dictionary<string, AssetBundleInfo> nowBundleDict = new Dictionary<string, AssetBundleInfo>();
foreach (var bundle in nowBundleList.assetbundles)
{
nowBundleDict[bundle.name] = bundle;
}
// 对比新资源列表
foreach (var newBundle in newBundleList.assetbundles)
{
// 检查文件是否存在或MD5是否不匹配
if (!nowBundleDict.ContainsKey(newBundle.name) ||
nowBundleDict[newBundle.name].md5 != newBundle.md5)
{
updateList.Add(newBundle);
}
}
return updateList;
}
/// <summary>
/// 计算需要更新的总大小
/// </summary>
/// <param name="updateList">需要更新的资源列表</param>
/// <returns>总大小(字节)</returns>
public long CalculateUpdateSize(List<AssetBundleInfo> updateList)
{
long totalSize = 0;
foreach (var bundle in updateList)
{
totalSize += bundle.size;
}
return totalSize;
}
/// <summary>
/// 下载更新文件并实时返回进度
/// </summary>
/// <param name="updateList">需要更新的资源列表</param>
/// <param name="progressCallback">进度回调(已下载字节数, 总字节数)</param>
/// <returns>是否下载成功</returns>
public async Task<bool> DownloadUpdateFiles(List<AssetBundleInfo> updateList, Action<long, long> progressCallback)
{
long totalSize = CalculateUpdateSize(updateList);
long downloadedSize = 0;
// 初始化 nowBundleList如果为空
if (nowBundleList == null || nowBundleList.assetbundles == null)
{
nowBundleList = new AssetBundleInfoList(new List<AssetBundleInfo>());
}
// 创建当前资源字典以便快速查找和更新
Dictionary<string, AssetBundleInfo> nowBundleDict = new Dictionary<string, AssetBundleInfo>();
foreach (var bundle in nowBundleList.assetbundles)
{
nowBundleDict[bundle.name] = bundle;
}
foreach (var bundle in updateList)
{
string url = nowResourceUrl + $"{resourceVersion}/{platform}/{bundle.name}";
string savePath = Path.Combine(localPath, bundle.name);
// 确保目录存在
string directory = Path.GetDirectoryName(savePath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
bool downloadSuccess = await DownloadSingleFile(url, savePath, bundle.md5);
if (!downloadSuccess)
{
Debug.LogError($"下载文件失败: {bundle.name}");
return false;
}
// 下载成功后,更新 nowBundleList
nowBundleDict[bundle.name] = bundle;
downloadedSize += bundle.size;
progressCallback?.Invoke(downloadedSize, totalSize);
// 简短延迟避免请求过于频繁
await Task.Yield();
}
// 更新完整的 nowBundleList
nowBundleList.assetbundles = new List<AssetBundleInfo>(nowBundleDict.Values);
// 下载完成后保存新的list.json
SaveNewBundleList();
return true;
}
/// <summary>
/// 下载单个文件并校验MD5
/// </summary>
/// <param name="url">文件URL</param>
/// <param name="savePath">保存路径</param>
/// <param name="expectedMd5">期望的MD5值</param>
/// <returns>是否下载并校验成功</returns>
private async Task<bool> DownloadSingleFile(string url, string savePath, string expectedMd5)
{
int maxRetries = 2;
for (int i = 0; i <= maxRetries; i++)
{
using (UnityWebRequest webRequest = UnityWebRequest.Get(url))
{
var operation = webRequest.SendWebRequest();
while (!operation.isDone)
{
await Task.Yield();
}
if (webRequest.result == UnityWebRequest.Result.Success)
{
// 保存文件
File.WriteAllBytes(savePath, webRequest.downloadHandler.data);
// 校验MD5
string actualMd5 = CalculateMD5(savePath);
if (actualMd5.ToLower() == expectedMd5.ToLower())
{
return true;
}
else
{
Debug.LogError($"MD5校验失败 {url}: 期望 {expectedMd5}, 实际 {actualMd5}");
// MD5校验失败时删除文件
if (File.Exists(savePath))
{
File.Delete(savePath);
}
}
}
else
{
Debug.LogError($"下载失败 {url}: {webRequest.error} (尝试 {i + 1}/{maxRetries + 1})");
}
// 如果还没达到最大重试次数,等待一段时间后重试
if (i < maxRetries)
{
await Task.Delay(1000 * (i + 1)); // 递增延迟时间
}
}
}
// 所有重试都失败后显示错误提示
ErrorTip.Instance.Init("下载资源包失败", url, async (msg) =>
{
// 错误处理回调 - 重新尝试下载更新资源流程
Debug.Log("用户选择重试下载资源包");
await UpdateResources();
});
return false;
}
/// <summary>
/// 计算文件的MD5值
/// </summary>
/// <param name="filePath">文件路径</param>
/// <returns>MD5值</returns>
private string CalculateMD5(string filePath)
{
using (var md5 = System.Security.Cryptography.MD5.Create())
{
using (var stream = File.OpenRead(filePath))
{
byte[] hash = md5.ComputeHash(stream);
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
}
}
/// <summary>
/// 保存新的资源列表到本地
/// </summary>
public void SaveNewBundleList()
{
if (newBundleList != null)
{
string json = JsonUtility.ToJson(newBundleList, true);
string savePath = Path.Combine(localPath, "list.json");
File.WriteAllText(savePath, json);
Debug.Log("新的资源列表已保存到本地");
}
}
#endregion
#region
#endregion
}
}