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(File.ReadAllText(localPath + "/list.json")); } else if (File.Exists(streamingPath + "/list.json")) { nowBundleList = JsonUtility.FromJson(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(webRequest.downloadHandler.text); newBundleList = list; } else { Debug.LogError(url + "获取资源列表失败" + webRequest.error); ErrorTip.Instance.Init("获取资源列表失败", url, async (msg) => { await GetBundleList(); }); } } } public async Task UpdateResources() { // 获取更新列表 List 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("资源已是最新版本"); } } /// /// 对比当前资源列表和新资源列表,找出需要更新的文件 /// /// 需要更新的资源信息列表 public List GetUpdateList() { List updateList = new List(); // 如果没有当前列表,则全部都需要下载 if (nowBundleList == null || nowBundleList.assetbundles == null) { return newBundleList.assetbundles ?? new List(); } // 创建当前资源字典以便快速查找 Dictionary nowBundleDict = new Dictionary(); 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; } /// /// 计算需要更新的总大小 /// /// 需要更新的资源列表 /// 总大小(字节) public long CalculateUpdateSize(List updateList) { long totalSize = 0; foreach (var bundle in updateList) { totalSize += bundle.size; } return totalSize; } /// /// 下载更新文件并实时返回进度 /// /// 需要更新的资源列表 /// 进度回调(已下载字节数, 总字节数) /// 是否下载成功 public async Task DownloadUpdateFiles(List updateList, Action progressCallback) { long totalSize = CalculateUpdateSize(updateList); long downloadedSize = 0; // 初始化 nowBundleList(如果为空) if (nowBundleList == null || nowBundleList.assetbundles == null) { nowBundleList = new AssetBundleInfoList(new List()); } // 创建当前资源字典以便快速查找和更新 Dictionary nowBundleDict = new Dictionary(); 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(nowBundleDict.Values); // 下载完成后保存新的list.json SaveNewBundleList(); return true; } /// /// 下载单个文件并校验MD5 /// /// 文件URL /// 保存路径 /// 期望的MD5值 /// 是否下载并校验成功 private async Task 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; } /// /// 计算文件的MD5值 /// /// 文件路径 /// MD5值 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(); } } } /// /// 保存新的资源列表到本地 /// 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 } }