using Cysharp.Threading.Tasks; using System.Threading.Tasks; using UnityEngine; using YooAsset; using static YooAsset.DownloaderOperation; public class PatchOperationData { public string packageName; public EPlayMode playMode; public bool useBuildinFileSystem; public DownloadError downloadError; public DownloaderFinish downloadFinish; public DownloadUpdate downloadUpdate; public int downloadingMaxNum = 10; public int failedTryAgain = 3; } public class PatchOperation { public PatchOperationData data; public ResourcePackage package; public ResourceDownloaderOperation downloader; public string packageVersion; public string PkgVersionKey; public PatchOperation(PatchOperationData data) { this.data = data; PkgVersionKey = $"{Application.productName}_{data.packageName}"; #if !UNITY_EDITOR PkgVersionKey = $"Editor_{Application.productName}_{data.packageName}"; #endif } #region 初始化相关 public async UniTask InitializePackage() { package = YooAssets.TryGetPackage(data.packageName); if (package == null) package = YooAssets.CreatePackage(data.packageName); InitializationOperation initializationOperation = null; switch (data.playMode) { case EPlayMode.EditorSimulateMode: initializationOperation = InitializeEditorMode(package); break; case EPlayMode.OfflinePlayMode: initializationOperation = InitializeOfflineMode(package); break; case EPlayMode.HostPlayMode: initializationOperation = InitializeHostMode(package); break; case EPlayMode.WebPlayMode: initializationOperation = InitializeWebPlayMode(package); break; case EPlayMode.CustomPlayMode: break; default: break; } await initializationOperation.ToUniTask(); if (initializationOperation.Status != EOperationStatus.Succeed) { MessageBox.Show() .SetTitle($"{data.packageName}初始化") .SetContent($"{initializationOperation.Error}") .AddButton("退出", (box) => { Application.Quit(); }); } else { PatchEvent.UpdateStatus($"初始化成功{data.packageName}"); Debug.Log($"初始化成功{data.packageName}"); } return initializationOperation.Status == EOperationStatus.Succeed; } private InitializationOperation InitializeEditorMode(ResourcePackage package) { var buildResult = EditorSimulateModeHelper.SimulateBuild(data.packageName); var packageRoot = buildResult.PackageRootDirectory; var createParameters = new EditorSimulateModeParameters(); createParameters.EditorFileSystemParameters = FileSystemParameters.CreateDefaultEditorFileSystemParameters(packageRoot); return package.InitializeAsync(createParameters); } private InitializationOperation InitializeOfflineMode(ResourcePackage package) { var createParameters = new OfflinePlayModeParameters(); createParameters.BuildinFileSystemParameters = FileSystemParameters.CreateDefaultBuildinFileSystemParameters(); return package.InitializeAsync(createParameters); } private InitializationOperation InitializeHostMode(ResourcePackage package) { FileSystemParameters buildinFileSystemParams = null; if (data.useBuildinFileSystem) { // 注意:设置参数COPY_BUILDIN_PACKAGE_MANIFEST,可以初始化的时候拷贝内置清单到沙盒目录 buildinFileSystemParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters(); buildinFileSystemParams.AddParameter(FileSystemParametersDefine.COPY_BUILDIN_PACKAGE_MANIFEST, true); } string defaultHostServer = GetHostServerURL(); string fallbackHostServer = GetHostServerURL(); IRemoteServices remoteServices = new RemoteServices(defaultHostServer, fallbackHostServer); // 注意:设置参数INSTALL_CLEAR_MODE,可以解决覆盖安装的时候将拷贝的内置清单文件清理的问题。 var cacheFileSystemParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters(remoteServices); cacheFileSystemParams.AddParameter(FileSystemParametersDefine.INSTALL_CLEAR_MODE, EOverwriteInstallClearMode.ClearAllManifestFiles); var createParameters = new HostPlayModeParameters(); createParameters.BuildinFileSystemParameters = buildinFileSystemParams; createParameters.CacheFileSystemParameters = cacheFileSystemParams; return package.InitializeAsync(createParameters); } private InitializationOperation InitializeWebPlayMode(ResourcePackage package) { #if UNITY_WEBGL && WEIXINMINIGAME && !UNITY_EDITOR var createParameters = new WebPlayModeParameters(); string defaultHostServer = GetHostServerURL(); string fallbackHostServer = GetHostServerURL(); string packageRoot = $"{WeChatWASM.WX.env.USER_DATA_PATH}/__GAME_FILE_CACHE"; //注意:如果有子目录,请修改此处! IRemoteServices remoteServices = new RemoteServices(defaultHostServer, fallbackHostServer); createParameters.WebServerFileSystemParameters = WechatFileSystemCreater.CreateFileSystemParameters(packageRoot, remoteServices); return package.InitializeAsync(createParameters); #else var createParameters = new WebPlayModeParameters(); createParameters.WebServerFileSystemParameters = FileSystemParameters.CreateDefaultWebServerFileSystemParameters(); return package.InitializeAsync(createParameters); #endif } private string GetHostServerURL() { string hostServerIP = $"https://home.gtuantuan.online:9444/{Application.productName}"; string appVersion = "v1"; #if UNITY_EDITOR if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.Android) // return $"{hostServerIP}/CDN/Android/{packageName}/{appVersion}"; return $"{hostServerIP}/CDN/PC/{data.packageName}/{appVersion}"; else if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.iOS) return $"{hostServerIP}/CDN/IPhone/{data.packageName}/{appVersion}"; else if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.WebGL) return $"{hostServerIP}/CDN/WebGL/{data.packageName}/{appVersion}"; else return $"{hostServerIP}/CDN/PC/{data.packageName}/{appVersion}"; #else if (Application.platform == RuntimePlatform.Android) return $"{hostServerIP}/CDN/Android/{data.packageName}/{appVersion}"; else if (Application.platform == RuntimePlatform.IPhonePlayer) return $"{hostServerIP}/CDN/IPhone/{data.packageName}/{appVersion}"; else if (Application.platform == RuntimePlatform.WebGLPlayer) return $"{hostServerIP}/CDN/WebGL/{data.packageName}/{appVersion}"; else return $"{hostServerIP}/CDN/PC/{data.packageName}/{appVersion}"; #endif } private class RemoteServices : IRemoteServices { private readonly string _defaultHostServer; private readonly string _fallbackHostServer; public RemoteServices(string defaultHostServer, string fallbackHostServer) { _defaultHostServer = defaultHostServer; _fallbackHostServer = fallbackHostServer; } string IRemoteServices.GetRemoteMainURL(string fileName) { return $"{_defaultHostServer}/{fileName}"; } string IRemoteServices.GetRemoteFallbackURL(string fileName) { return $"{_fallbackHostServer}/{fileName}"; } } #endregion 初始化相关 #region 版本和资源清单相关 public async UniTask RequestPackageVersion(bool showBox = true) { var operation = package.RequestPackageVersionAsync(true, 5); await operation.ToUniTask(); if (operation.Status != EOperationStatus.Succeed) { if (showBox) { if(await CheckUseLocalVersion(operation)) { string cachedVersion = GetCachedPackageVersion(); if (!string.IsNullOrEmpty(cachedVersion)) { packageVersion = cachedVersion; return true; } } } } else { packageVersion = operation.PackageVersion; PatchEvent.UpdateStatus($"获取版本成功{data.packageName}"); Debug.Log($"获取版本成功{data.packageName}:{packageVersion}"); } return operation.Status == EOperationStatus.Succeed; } public async UniTask CheckUseLocalVersion(RequestPackageVersionOperation operation) { var completionSource = new UniTaskCompletionSource(); MessageBox.Show() .SetTitle($"{data.packageName}请求版本") .SetContent($"{operation.Error}") .AddButton("继续", (box) => { completionSource.TrySetResult(true); }) .AddButton("退出", (box) => { completionSource.TrySetResult(false); Application.Quit(); }); bool shouldContinue = await completionSource.Task; return shouldContinue; } public async Task GetBuildinPackageVersion() { var operation = new GetBuildinPackageVersionOperation(data.packageName); YooAssets.StartOperation(operation); await operation; if (operation.Status == EOperationStatus.Succeed) { return operation.PackageVersion; } else { return null; } } public string GetCachedPackageVersion() { if (PlayerPrefs.HasKey(PkgVersionKey)) { return PlayerPrefs.GetString(PkgVersionKey); } return null; } public void SaveVersionToCache() { PlayerPrefs.SetString(PkgVersionKey, packageVersion); PlayerPrefs.Save(); PatchEvent.UpdateStatus($"更新完成{data.packageName}"); Debug.Log($"更新{data.packageName}完成,版本号{packageVersion}"); } public async UniTask UpdatePackageManifest(bool showBox = true) { var operation = package.UpdatePackageManifestAsync(packageVersion,10); await operation.ToUniTask(); if (operation.Status != EOperationStatus.Succeed) { if (showBox) { MessageBox.Show() .SetTitle($"{data.packageName}更新清单") .SetContent($"{operation.Error}") .AddButton("退出", (box) => { Application.Quit(); }); } } else { PatchEvent.UpdateStatus($"获取资源清单成功{data.packageName}"); Debug.Log($"获取资源清单成功{data.packageName}"); } return operation.Status == EOperationStatus.Succeed; } #endregion 版本和资源清单相关 #region 下载相关 public bool CreateDownloader() { downloader = package.CreateResourceDownloader(data.downloadingMaxNum, data.failedTryAgain); downloader.DownloadErrorCallback = data.downloadError; downloader.DownloadFinishCallback = data.downloadFinish; downloader.DownloadUpdateCallback = data.downloadUpdate; if (downloader.TotalDownloadCount == 0) return false; return true; } public async UniTask CheckDownloadOrSkip() { var completionSource = new UniTaskCompletionSource(); MessageBox.Show() .SetTitle($"{data.packageName}发现更新") .SetContent($"发现资源更新\n{GetCachedPackageVersion()}=>{packageVersion}: {FormatFileSize(downloader.TotalDownloadBytes)}") .AddButton("下载", async (box) => { bool success = await DownloadPackageFiles(); completionSource.TrySetResult(success); }) .AddButton("跳过", async (box) => { downloader.CancelDownload(); packageVersion = GetCachedPackageVersion(); await UpdatePackageManifest(); completionSource.TrySetResult(true); }) .AddButton("退出", (box) => { downloader.CancelDownload(); completionSource.TrySetResult(false); #if UNITY_EDITOR UnityEditor.EditorApplication.isPlaying = false; #else Application.Quit(); #endif }); bool shouldContinue = await completionSource.Task; return shouldContinue; } public async UniTask DownloadPackageFiles(bool showBox = true) { if (downloader.TotalDownloadCount == 0) return true; Debug.Log($"{data.packageName} DownloadPackageFiles {downloader.TotalDownloadCount}"); downloader.BeginDownload(); await downloader.ToUniTask(); if (downloader.Status != EOperationStatus.Succeed) { if (showBox) { MessageBox.Show() .SetTitle($"{data.packageName}下载文件") .SetContent($"{downloader.Error}") .AddButton("退出", (box) => { Application.Quit(); }); } return false; } return downloader.Status == EOperationStatus.Succeed; } public async UniTask ClearCacheBundle(bool showBox = true) { var operation = package.ClearCacheFilesAsync(EFileClearMode.ClearUnusedBundleFiles); await operation.ToUniTask(); if (operation.Status != EOperationStatus.Succeed) { if (showBox) { MessageBox.Show() .SetTitle($"{data.packageName}清除缓存") .SetContent($"{operation.Error}") .AddButton("退出", (box) => { Application.Quit(); }); } } return operation.Status == EOperationStatus.Succeed; } public string FormatFileSize(long size) { if (size < 1024 * 1024) return $"{(size / 1024f):F1}KB"; else return $"{size / 1024f / 1024f:F1}MB"; } #endregion 下载相关 }