Unity框架YouYouFramework学习第1篇:游戏入口
YouYouFramework 是一款面向游戏开发的U3D框架,主要针对Unity环境设计,通过模块化封装简化开发流程。
- 是一款适合开发商业级游戏的U3D代码框架,分别参考了多款主流代码框架。
- 框架主要对游戏开发过程中常用模块进行了封装,很大程度地规范开发过程、加快开发速度并保证产品质量。
- 追求轻量化,上手难度相对比较简单,模块之间的依赖比较少, 方便自行删除或扩展模块。
- 基于Addressble的资源管理, 支持HybridCLR的代码热更新。
- 支持UniTask异步编程,告别委托回调式地狱写法。
游戏框架入口
游戏入口函数MainEntry, 初始场景Scene_Launch,在对象MainEntry挂载脚本MainEntry脚本
MainEntry.cs脚本
namespace YouYouMain
{public class MainEntry : MonoBehaviour{//预加载相关事件public Action ActionPreloadBegin;public Action<float> ActionPreloadUpdate;public Action ActionPreloadComplete;public static MainEntry Instance { get; private set; }private void Awake(){Instance = this;}private void Start(){//开始检查更新CheckVersionCtrl.Instance.CheckVersionChange(async () =>{//检查更新完成, 加载Hotfix代码(HybridCLR)await HotfixCtrl.Instance.LoadHotifx();//启动YouYouFramework框架入口GameObject gameEntryAsset = await Addressables.LoadAssetAsync<GameObject>("Assets/Game/Download/Prefab/GameEntry.prefab");Instantiate(gameEntryAsset);});}}
}
1. 三个预加载流程相关事件
(简单介绍下,后续预加载流程ProcedurePreload用到相关事件)
public Action ActionPreloadBegin 预加载开始事件通知
public Action<float> ActionPreloadUpdate 预加载更新进度事件,接收float类型参数,表示预加载的进度。
public Action ActionPreloadComplete 预加载完成事件通知。
2. Start函数
第一件事情:首先版本检查,判断是否需要热更新,如果需要热更新,等待热更新的完成。
第二件事情:启动YouYouFramework框架入口,将框架入口封装预制体,通过Addressables方式加载预制体,加载完成后克隆入口预制体对象。
private void Start(){//开始检查更新CheckVersionCtrl.Instance.CheckVersionChange(async () =>{//检查更新完成, 加载Hotfix代码(HybridCLR)await HotfixCtrl.Instance.LoadHotifx();//启动YouYouFramework框架入口GameObject gameEntryAsset = await Addressables.LoadAssetAsync<GameObject>("Assets/Game/Download/Prefab/GameEntry.prefab");Instantiate(gameEntryAsset);});}
3. 检查更新版本控制CheckVersionCtrl.cs
这是一个游戏资源热更新控制类,专门用于在游戏启动时检查并下载新版本的资源。它主要基于 Unity 的 Addressables 系统来管理资源,并使用了 Cysharp.Threading.Tasks(UniTask
)来处理异步操作,使得代码结构比传统的 Coroutine
(协程)更清晰。
using Cysharp.Threading.Tasks;
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using YouYouMain;#if UNITY_EDITOR
using UnityEditor.AddressableAssets;
#endifpublic class CheckVersionCtrl
{public static CheckVersionCtrl Instance { get; private set; } = new CheckVersionCtrl();public event Action CheckVersionBeginDownload;internal event Action<DownloadStatus> CheckVersionDownloadUpdate;public event Action CheckVersionDownloadComplete;private Action CheckVersionComplete;/// <summary>/// 检查更新/// </summary>public async void CheckVersionChange(Action onComplete){CheckVersionComplete = onComplete;#if UNITY_EDITOR// 获取 Addressables 配置var settings = AddressableAssetSettingsDefaultObject.Settings;if (settings.ActivePlayModeDataBuilderIndex == 0){MainEntry.Log("编辑器加载模式 不需要检查更新");CheckVersionComplete.Invoke();return;}
#endif//自定义新地址Addressables.InternalIdTransformFunc = (location) =>{//Debug.Log($"默认地址=={location.InternalId}");if (location.InternalId.StartsWith("http", System.StringComparison.Ordinal)){string new_location = location.InternalId.Replace("http://test", ChannelModel.Instance.CurrChannelConfig.RealSourceUrl);//Debug.Log($"默认地址=={location.InternalId}, 自定义的新地址=={new_location}");return new_location;}return location.InternalId;};var updateHandle = Addressables.CheckForCatalogUpdates();await updateHandle.Task;if (updateHandle.Status == AsyncOperationStatus.Failed){//资源清单请求失败MainEntry.LogError("资源清单请求失败, 请检查ChannelConfigEntity脚本的路径配置");return;}if (updateHandle.Result.Count == 0){MainEntry.Log("资源清单没变化 不需要检查更新");CheckVersionComplete?.Invoke();return;}MainEntry.Log("旧的资源清单==" + updateHandle.Result.ToJson());// 下载新版本 Catalogvar updateOp = Addressables.UpdateCatalogs(true, updateHandle.Result, false);MainEntry.Log("开始更新资源清单");await updateOp.Task;MainEntry.Log("资源清单更新完毕==" + updateOp.Result.ToJson());updateOp.Release();//开始检查更新MainEntry.Instance.StartCoroutine(DownloadCoroutine());}IEnumerator DownloadCoroutine(){CheckVersionBeginDownload?.Invoke();// 开始下载资源及其依赖项AsyncOperationHandle _downloadHandle = Addressables.DownloadDependenciesAsync("default");// 轮询更新进度while (!_downloadHandle.IsDone){CheckVersionDownloadUpdate?.Invoke(_downloadHandle.GetDownloadStatus());yield return null; // 每帧更新}// 处理完成状态if (_downloadHandle.Status == AsyncOperationStatus.Succeeded){Addressables.Release(_downloadHandle); // 释放资源句柄MainEntry.Log("检查更新下载完毕, 进入预加载流程");CheckVersionDownloadComplete?.Invoke();CheckVersionComplete?.Invoke();}else{MainEntry.LogError("检查更新失败, 请点击重试");MainDialogForm.ShowForm("检查更新失败, 请点击重试", "Error", "重试", "", MainDialogForm.DialogFormType.Affirm, () =>{CheckVersionChange(CheckVersionComplete);});Debug.Log($"下载失败:{_downloadHandle.OperationException}");}}
}
核心职责是:在游戏启动时,检查远程服务器上是否有更新的资源清单(Catalog)和资源包(AssetBundle)。如果有,就下载它们,为游戏的后续运行做好准备。
解析函数CheckVersionChange
#if UNITY_EDITOR// 获取 Addressables 配置var settings = AddressableAssetSettingsDefaultObject.Settings;if (settings.ActivePlayModeDataBuilderIndex == 0){MainEntry.Log("编辑器加载模式 不需要检查更新");CheckVersionComplete.Invoke();return;}
#endif
AddressableAssetSettingsDefaultObject
是一个编辑器类,它的主要作用是提供一个全局的、便捷的访问点,让你能够在代码中获取到当前项目的主 Addressables 配置实例 (AddressableAssetSettings
)。
#if UNITY_EDITOR
: 因为 AddressableAssetSettingsDefaultObject
是一个编辑器类,所以任何使用它的代码都必须放在 UNITY_EDITOR
条件编译块中。否则,当你尝试为目标平台(如 Android, iOS, PC)打包时,会出现编译错误。
ActivePlayModeDataBuilderIndex == 0
判断当前是否使用 "Use Existing Build"(使用现有构建)模式。这是 Addressables 在编辑器中最常用的模式,它直接从本地加载资源,而不是模拟远程服务器。
动态地址转换 (关键的多渠道配置)
//自定义新地址Addressables.InternalIdTransformFunc = (location) =>{//Debug.Log($"默认地址=={location.InternalId}");if (location.InternalId.StartsWith("http", System.StringComparison.Ordinal)){string new_location = location.InternalId.Replace("http://test", ChannelModel.Instance.CurrChannelConfig.RealSourceUrl);//Debug.Log($"默认地址=={location.InternalId}, 自定义的新地址=={new_location}");return new_location;}return location.InternalId;};
InternalIdTransformFunc
: 这是 Addressables 提供的一个强大的回调。每次 Addressables 准备加载一个资源时,都会先调用这个函数来获取最终的资源地址(InternalId
)。
它检查当前资源的地址是否以 "http" 开头(即是否是远程资源)。
如果是,它就用一个占位符 (http://test
) 和当前渠道的真实服务器地址 (ChannelModel.Instance.CurrChannelConfig.RealSourceUrl
) 进行替换。
这样一来,开发者在编辑器中无需关心最终的发布地址。在打包时,只需根据目标渠道修改 ChannelModel
中的配置即可,实现了配置与代码的分离。
检查 Catalog 更新
var updateHandle = Addressables.CheckForCatalogUpdates();await updateHandle.Task;if (updateHandle.Status == AsyncOperationStatus.Failed){//资源清单请求失败MainEntry.LogError("资源清单请求失败, 请检查ChannelConfigEntity脚本的路径配置");return;}if (updateHandle.Result.Count == 0){MainEntry.Log("资源清单没变化 不需要检查更新");CheckVersionComplete?.Invoke();return;}MainEntry.Log("旧的资源清单==" + updateHandle.Result.ToJson());
Addressables.CheckForCatalogUpdates()
: 向远程服务器请求最新的资源清单(Catalog)信息。这个清单记录了所有资源的名称、哈希值、大小和下载地址。
await updateHandle.Task
: 异步等待检查请求完成。这行代码会 “暂停”CheckVersionChange
方法的执行,但不会冻结游戏画面,直到服务器响应为止。
下载并更新 Catalog
// 下载新版本 Catalogvar updateOp = Addressables.UpdateCatalogs(true, updateHandle.Result, false);MainEntry.Log("开始更新资源清单");await updateOp.Task;MainEntry.Log("资源清单更新完毕==" + updateOp.Result.ToJson());updateOp.Release();
Addressables.UpdateCatalogs()
: 如果发现有新的 Catalog,就下载它。
await updateOp.Task
: 等待新的 Catalog 下载并合并到本地。完成这一步后,Addressables 就知道了哪些具体的资源包(AssetBundle)需要更新。
下载资源包 (使用协程)
//开始检查更新MainEntry.Instance.StartCoroutine(DownloadCoroutine());
在更新完 Catalog 之后,流程切换到了一个传统的协程 DownloadCoroutine
。
为什么用协程? 因为下载资源是一个持续的过程,需要每帧更新 UI 进度。协程的 yield return null
机制非常适合做这件事。
DownloadCoroutine
详解
IEnumerator DownloadCoroutine(){CheckVersionBeginDownload?.Invoke();// 开始下载资源及其依赖项AsyncOperationHandle _downloadHandle = Addressables.DownloadDependenciesAsync("default");// 轮询更新进度while (!_downloadHandle.IsDone){CheckVersionDownloadUpdate?.Invoke(_downloadHandle.GetDownloadStatus());yield return null; // 每帧更新}// 处理完成状态if (_downloadHandle.Status == AsyncOperationStatus.Succeeded){Addressables.Release(_downloadHandle); // 释放资源句柄MainEntry.Log("检查更新下载完毕, 进入预加载流程");CheckVersionDownloadComplete?.Invoke();CheckVersionComplete?.Invoke();}else{MainEntry.LogError("检查更新失败, 请点击重试");MainDialogForm.ShowForm("检查更新失败, 请点击重试", "Error", "重试", "", MainDialogForm.DialogFormType.Affirm, () =>{CheckVersionChange(CheckVersionComplete);});Debug.Log($"下载失败:{_downloadHandle.OperationException}");}}
Addressables.DownloadDependenciesAsync("default")
: 这是关键的下载调用。它会根据新的 Catalog,分析 "default" 这个标签(Label)下所有资源及其依赖项,并开始下载所有需要更新的资源包。
_downloadHandle.GetDownloadStatus()
: 获取当前的下载状态,包含 Percent
(进度 0-1)、TotalBytes
(总大小)、DownloadedBytes
(已下载大小)等信息。
CheckVersionDownloadUpdate?.Invoke(...)
: 将下载状态通过事件发送出去,UI 模块可以订阅这个事件来更新进度条和显示下载速度。
private void Start(){//开始检查更新CheckVersionCtrl.Instance.CheckVersionChange(async () =>{//检查更新完成, 加载Hotfix代码(HybridCLR)await HotfixCtrl.Instance.LoadHotifx();//启动YouYouFramework框架入口GameObject gameEntryAsset = await Addressables.LoadAssetAsync<GameObject>("Assets/Game/Download/Prefab/GameEntry.prefab");Instantiate(gameEntryAsset);});}
完成Version检查更新:
1. 检查更新完成, 加载Hotfix代码(HybridCLR) 后续的热更新详细介绍
2. 启动YouYouFramework框架入口, 第2篇ouYouFramework框架启动将详细介绍。