using HybridCLR; using Sirenix.Utilities; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceLocations; using UnityEngine.ResourceManagement.ResourceProviders; using UnityEngine.SceneManagement; public class GameLauncher : MonoBehaviour { private class MethodExecutionInfo { public MethodInfo method; public int sequnence; public MethodExecutionInfo(MethodInfo method, int sequnence) { this.method = method; this.sequnence = sequnence; } } [Serializable] private class DownloadContent { public List catalogs = new(); } const string START_SCENE_NAME = "Assets/Scenes/StartScene.unity"; const string META_DATA_DLL_PATH = "Assets/HotUpdateDlls/MetaDataDll/"; const string HOT_UPDATE_DLL_PATH = "Assets/HotUpdateDlls/HotUpdateDll/"; const string PHXH_DLL_NAME = "Phxh.dll"; const string FRAMEWORK_DLL_NAME = "FrameWork.dll"; const string GAMEPLAY_DLL_NAME = "GamePlay.dll"; //记录在playerPres里的需要下载的catalogs的ID const string DOWNLOAD_CATALOGS_ID = "DownloadCatalogs"; //最大能容忍的下载卡住时长,一旦超过则重新开始下载 // private const float DOWNLOAD_STUCK_TIME_MAX = 10; private Coroutine _launchCoroutine; private byte[] _dllBytes; private Dictionary _allHotUpdateAssemblies = new(); //version update private AsyncOperationHandle _downloadOP; private UI_VersionUpdate _versionUpdateUI; private Type _versionUpdaterType; // private float _downloadProgress; // private float _downloadStuckTime; private List _KeysNeedToDownload = new(); //此对象里保存了需要下载的catalog,每次获取新的catalog会将此对象保存到手机上,如果在下载的过程中关闭了游戏,下次打开还能拿到catalog继续下载 private DownloadContent _downloadContent = new(); private bool HasContentToDownload => _downloadContent != null && _downloadContent.catalogs != null && _downloadContent.catalogs.Count > 0; private readonly List _phxhDependencyDlls = new List() { }; private readonly List _frameWorkDependencyDlls = new List() { //不要随意改顺序! }; private readonly List _gamePlayDependencyDlls = new List() { //不要随意改顺序! "BehaviorDesigner.Runtime.dll", //"BestHTTP.dll", "DOTweenCSharp.dll", }; //所有使用了RuntimeInitializeOnLoadMethod attribute的程序集 private readonly List _hasRuntimeInitializeOnLoadMethodAssemblies = new List() { "BehaviorDesigner.Runtime", //"BestHTTP", "GamePlay", }; public bool enableHotUpdate = true; private void Start() { _launchCoroutine = StartCoroutine(Launch()); } private void OnDestroy() { StopCoroutine(_launchCoroutine); _launchCoroutine = null; } // private void _SafeProtect() // { // var typeList = new List(); // typeList.Add(typeof(BestHTTP.HTTPRequest)); // typeList.Add(typeof(BestHTTP.HTTPResponse)); // for (int i = 0; i < typeList.Count; i++) // { // var type = typeList[i]; // DebugUtil.LogError("type = {0}", type); // } // } private IEnumerator Launch() { // _SafeProtect(); #if UNITY_EDITOR enableHotUpdate = false; #endif Debug.Log($"Launch Game! enableHotUpdate:{enableHotUpdate}"); yield return VersionCheck(); if (HasContentToDownload) yield return VersionUpdate(); if (enableHotUpdate) yield return LoadAssemblies(); yield return EnterGame(); } private IEnumerator VersionCheck() { //todo 如果包体版本不一致则提示用户去app store重新下载 var checkUpdateOP = Addressables.CheckForCatalogUpdates(false); yield return checkUpdateOP; if (checkUpdateOP.Status == AsyncOperationStatus.Succeeded) { _downloadContent.catalogs = checkUpdateOP.Result; if (HasContentToDownload) { //如果服务器上的catalog和本地不一致,则覆盖上一次需要下载的被内容 var jsonStr = JsonUtility.ToJson(_downloadContent); PlayerPrefs.SetString(DOWNLOAD_CATALOGS_ID, jsonStr); PlayerPrefs.Save(); } else { if (PlayerPrefs.HasKey(DOWNLOAD_CATALOGS_ID)) { var jsonStr = PlayerPrefs.GetString(DOWNLOAD_CATALOGS_ID); JsonUtility.FromJsonOverwrite(jsonStr, _downloadContent); } } if (HasContentToDownload) { var updateCatalogOP = Addressables.UpdateCatalogs(_downloadContent.catalogs, false); yield return updateCatalogOP; if (updateCatalogOP.Status == AsyncOperationStatus.Succeeded) { _KeysNeedToDownload.Clear(); foreach (var resourceLocator in updateCatalogOP.Result) { _KeysNeedToDownload.AddRange(resourceLocator.Keys); } } else { Debug.LogError($"Update catalog failed!exception:{updateCatalogOP.OperationException.Message}"); } Addressables.Release(updateCatalogOP); } } else { Debug.LogError($"CheckUpdate failed!exception:{checkUpdateOP.OperationException.Message}"); } Addressables.Release(checkUpdateOP); yield return ReloadAddressableCatalog(); Debug.Log($"版本检查结束,是否有需要下载的内容:{HasContentToDownload}"); } private IEnumerator VersionUpdate() { yield return OpenVersionUpdateUI(); yield return Download(); Debug.Log($"版本更新结束!"); } private IEnumerator LoadAssemblies() { yield return LoadPhxhDependencyAssemblies(); yield return LoadPhxhAssembies(); yield return LoadFrameworkDependencyAssemblies(); yield return LoadFrameWorkAssemblies(); yield return LoadMetadataForAOTAssemblies(); yield return LoadGamePlayDependencyAssemblies(); yield return LoadGamePlayAssemblies(); yield return ReloadAddressableCatalog(); ExecuteRuntimeInitializeOnLoadMethodAttribute(); Debug.Log("加载程序集结束!"); yield return null; } //补充元数据 private IEnumerator LoadMetadataForAOTAssemblies() { var aotAssemblies = AOTGenericReferences.PatchedAOTAssemblyList; if (aotAssemblies == null) { Debug.LogError($"LoadMetadataForAOTAssemblies failed, PatchedAOTAssemblyList is null!"); yield break; } foreach (var aotDllName in aotAssemblies) { var path = $"{META_DATA_DLL_PATH}{aotDllName}.bytes"; yield return ReadDllBytes(path); if (_dllBytes != null) { var err = HybridCLR.RuntimeApi.LoadMetadataForAOTAssembly(_dllBytes, HomologousImageMode.SuperSet); Debug.Log($"LoadMetadataForAOTAssembly:{aotDllName}. ret:{err}"); } } Debug.Log("补充元数据结束!"); } private IEnumerator LoadPhxhDependencyAssemblies() { foreach (var dllName in _phxhDependencyDlls) { yield return LoadSingleHotUpdateAssembly(dllName); } Debug.Log("加载Phxh依赖程序集结束!"); } private IEnumerator LoadPhxhAssembies() { yield return LoadSingleHotUpdateAssembly(PHXH_DLL_NAME); Debug.Log("加载Phxh程序集结束!"); } //加载FrameWork依赖的第三方热更序集 private IEnumerator LoadFrameworkDependencyAssemblies() { foreach (var dllName in _frameWorkDependencyDlls) { yield return LoadSingleHotUpdateAssembly(dllName); } Debug.Log("加载Framework依赖程序集结束!"); } //加载Framework程序集 private IEnumerator LoadFrameWorkAssemblies() { yield return LoadSingleHotUpdateAssembly(FRAMEWORK_DLL_NAME); Debug.Log("加载Framework程序集结束!"); } //通过加载场景打开版本更新界面 private IEnumerator OpenVersionUpdateUI() { _versionUpdateUI = FindObjectOfType(true); if (_versionUpdateUI == null) { Debug.LogError("cant find UI_VersionUpdate"); } _versionUpdateUI.gameObject.SetActive(true); return null; } //下载资源 private IEnumerator Download() { var downloadSizeOp = Addressables.GetDownloadSizeAsync((IEnumerable)_KeysNeedToDownload); yield return downloadSizeOp; Debug.Log($"download size:{downloadSizeOp.Result / (1024f * 1024f)}MB"); if (downloadSizeOp.Result > 0) { //_downloadStuckTime = 0; Addressables.Release(downloadSizeOp); // //下载经常在100%的时候卡住,参考:https://forum.unity.com/threads/addressables-1-14-2-downloaddependenciesasync-does-not-complete-randomly.966671/ // //倒数第二个回答 // var asyncLoadResources = Addressables.LoadResourceLocationsAsync((IEnumerable)_KeysNeedToDownload, // Addressables.MergeMode.Union, null); // IList dependencyLoadList = new List(); // yield return asyncLoadResources; // foreach (var item in asyncLoadResources.Result) // { // dependencyLoadList.Add(item); // } // // Addressables.Release(asyncLoadResources); // // _downloadOP = // Addressables.DownloadDependenciesAsync(dependencyLoadList); _downloadOP = Addressables.DownloadDependenciesAsync((IEnumerable)_KeysNeedToDownload, Addressables.MergeMode.Union, false); _versionUpdateUI.SetParams(_downloadOP); // while (!_downloadOP.IsDone) // { // RefreshDownLoadStatus(); // if (!CheckIfNeedReDownload()) // yield return 0; // else // { // //下载会经常卡在99%,此时还剩下很少的资源要下载,可以直接放弃下载,进入游戏 // //经过尝试,上述方法行不通,_downloadOp不能被终止,如果直接release的话会导致后续逻辑报错 // Debug.LogError($"下载卡住超过时间上限,放弃下载!"); // yield return _downloadOP; // } // } yield return _downloadOP; if (_downloadOP.Status == AsyncOperationStatus.Succeeded) Debug.Log($"下载完毕!"); else Debug.LogError($"Download Update Content Failed! exception:{_downloadOP.OperationException.Message} \r\n {_downloadOP.OperationException.StackTrace}"); Addressables.Release(_downloadOP); } //清除需要下载的内容 Debug.Log($"delete key:{DOWNLOAD_CATALOGS_ID}"); PlayerPrefs.DeleteKey(DOWNLOAD_CATALOGS_ID); } // private void RefreshDownLoadStatus() // { // var currDownloadProgress = _downloadOP.GetDownloadStatus().Percent; // if (Math.Abs(currDownloadProgress - _downloadProgress) < float.Epsilon) // _downloadStuckTime += Time.deltaTime; // else // _downloadStuckTime = 0; // _downloadProgress = currDownloadProgress; // //Debug.Log($"下载进度:{_downloadProgress} 下载卡顿时间:{_downloadStuckTime}"); // } // // private bool CheckIfNeedReDownload() => _downloadStuckTime >= DOWNLOAD_STUCK_TIME_MAX; //加载GamePlay依赖的第三方程序集 private IEnumerator LoadGamePlayDependencyAssemblies() { foreach (var dllName in _gamePlayDependencyDlls) { yield return LoadSingleHotUpdateAssembly(dllName); } Debug.Log("加载GamePlay依赖程序集结束!"); } //加载GamePlay程序集 private IEnumerator LoadGamePlayAssemblies() { yield return LoadSingleHotUpdateAssembly(GAMEPLAY_DLL_NAME); Debug.Log("加载GamePlay程序集结束!"); } /// /// Addressable初始化时热更新代码所对应的ScriptableObject的类型会被识别为System.Object,需要在热更新dll加载完后重新加载一下Addressable的Catalog /// https://hybridclr.doc.code-philosophy.com/docs/help/commonerrors /// /// private IEnumerator ReloadAddressableCatalog() { var op = Addressables.LoadContentCatalogAsync($"{Addressables.RuntimePath}/catalog.json"); yield return op; if (op.Status != AsyncOperationStatus.Succeeded) { Debug.LogError( $"load content catalog failed, exception:{op.OperationException.Message} \r\n {op.OperationException.StackTrace}"); } } /// /// 反射执行被RuntimeInitializeOnLoadMethod attribute标注的函数,HybirdCLR不支持该attribute /// private void ExecuteRuntimeInitializeOnLoadMethodAttribute() { var runtimeInitializedAttribute = typeof(RuntimeInitializeOnLoadMethodAttribute); List runtimeMethods = new(); foreach (var assemblyName in _hasRuntimeInitializeOnLoadMethodAssemblies) { var assembly = GetAssembly(assemblyName); if (assembly == null) { Debug.LogError($"找不到使用过RuntimeInitializeOnLoadMethod的assembly,name:{assemblyName}"); continue; } foreach (var type in assembly.GetTypes()) { type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).ForEach(method => { if (!method.IsStatic) return; var attri = method.GetCustomAttribute(runtimeInitializedAttribute); if (attri == null) return; var sequence = (int)(attri as RuntimeInitializeOnLoadMethodAttribute).loadType; var methodInfo = new MethodExecutionInfo(method, sequence); runtimeMethods.Add(methodInfo); }); } } runtimeMethods.Sort((a, b) => b.sequnence.CompareTo(a.sequnence)); foreach (var methodInfo in runtimeMethods) { Debug.Log($"call method methodName:{methodInfo.method.Name} sequnence:{methodInfo.sequnence}"); methodInfo.method.Invoke(null, null); } Debug.Log("调用RuntimeInitializeOnLoadMethod结束!"); } private IEnumerator ReadDllBytes(string path) { var op = Addressables.LoadAssetAsync(path); yield return op; if (op.Status == AsyncOperationStatus.Succeeded) { _dllBytes = op.Result.bytes; } else { Debug.LogError( $"cant load dll,exception:{op.OperationException.Message}\r\n{op.OperationException.StackTrace}"); _dllBytes = null; } Addressables.Release(op); } private IEnumerator LoadSingleHotUpdateAssembly(string dllName) { var path = $"{HOT_UPDATE_DLL_PATH}{dllName}.bytes"; yield return ReadDllBytes(path); if (_dllBytes != null) { var assembly = Assembly.Load(_dllBytes); _allHotUpdateAssemblies.Add(assembly.FullName, assembly); Debug.Log($"Load Assembly success,assembly Name:{assembly.FullName}"); // foreach (var type in GetAssembly(dllName).GetTypes()) // { // Debug.Log($"type:{type} in assembly:{GAMEPLAY_DLL_NAME}"); // } } yield return null; } private IEnumerator EnterGame() { var op = Addressables.LoadSceneAsync(START_SCENE_NAME, LoadSceneMode.Single); yield return op; if (op.Status != AsyncOperationStatus.Succeeded) { Debug.LogError( $"load scene failed,exception:{op.OperationException.Message} \r\n {op.OperationException.StackTrace}"); yield break; } } private Assembly GetAssembly(string assemblyName) { assemblyName = assemblyName.Replace(".dll", ""); IEnumerable allAssemblies = enableHotUpdate ? _allHotUpdateAssemblies.Values : AppDomain.CurrentDomain.GetAssemblies(); return allAssemblies.First(assembly => assembly.FullName.Contains(assemblyName)); } }