ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

14-03-YooAsset进阶-Unity自定义加载策略与扩展

14-03-YooAsset进阶-Unity自定义加载策略与扩展 进阶-自定义加载策略与扩展篇章14-进阶篇状态完成阅读时间约 25 分钟一、引言YooAsset 的加载系统由 Provider 体系构成。每个加载操作对应一个 Provider 实例Provider 负责管理从资源定位到加载完成的整个生命周期。通过扩展 Provider 体系开发者可以实现自定义的加载策略。二、Provider 体系架构2.1 Provider 的职责Provider 是 YooAsset 加载系统的核心组件承担着资源加载全流程的管理职责。它的工作从接收到加载请求开始到返回加载完成的资源对象结束。具体来说Provider 负责以下几个环节。首先是资源定位根据加载请求中的地址查找清单中的资源条目。然后是依赖解析递归分析资源的依赖链确定需要加载的所有 Bundle。接着是 Bundle 加载将依赖的 Bundle 文件从文件系统读取到内存。之后是资源反序列化将 Bundle 中的资源数据转换为 Unity 引擎可以使用的对象。最后是回调通知通过事件或委托通知调用者加载完成。Provider 还负责错误处理。当任何一个环节出错时Provider 需要将错误信息传递给调用者。加载失败的原因可能是资源不存在、文件损坏、磁盘空间不足或者网络连接断开Provider 需要区分这些不同的错误类型并给出明确的错误信息。2.2 Provider 的生命周期Provider 的生命周期包含四个状态等待中、加载中、已完成和已释放。理解这四个状态的转换关系有助于正确使用 Provider。等待中状态是 Provider 刚被创建时的状态。此时 Provider 已经知道需要加载哪个资源但还没有开始执行加载操作。在这个状态Provider 可以被取消而不会产生任何副作用。加载中状态是 Provider 正在执行加载操作的状态。这个阶段可能需要几毫秒到几秒钟取决于资源的大小和当前系统负载。在这个状态取消加载会导致部分资源被预加载到缓存中但不会被完全反序列化。已完成状态是资源加载成功后的状态。此时 Provider 的 AssetObject 属性非空调用者可以获取并使用资源。在这个状态调用 Release 会触发资源引用计数的减少。已释放状态是 Provider 被销毁后的状态。此时 Provider 不再引用任何资源对象继续使用 Provider 会得到空引用。三、自定义 Provider3.1 创建自定义 Provider自定义 Provider 的常见场景是添加自定义的后处理逻辑。例如在加载完成后自动设置资源参数或者记录加载统计信息。using YooAsset; using UnityEngine; /// summary /// 带日志记录的自定义 Provider /// /summary public class LoggingProvider : AssetProviderBase { private readonly AssetProviderBase innerProvider; private readonly string location; private float startTime; public LoggingProvider(AssetProviderBase innerProvider, string location) { this.innerProvider innerProvider; this.location location; } public override void Initialize() { startTime Time.realtimeSinceStartup; Debug.Log($[自定义Provider] 开始加载: {location}); innerProvider.Initialize(); } public override void Update() { innerProvider.Update(); if (innerProvider.Status EOperationStatus.Succeed) { float elapsed (Time.realtimeSinceStartup - startTime) * 1000f; Debug.Log($[自定义Provider] 加载完成: {location}, 耗时: {elapsed:F1}ms, 资源类型: {innerProvider.AssetObject?.GetType().Name}); } else if (innerProvider.Status EOperationStatus.Failed) { Debug.LogError($[自定义Provider] 加载失败: {location}, 错误: {innerProvider.Error}); } Progress innerProvider.Progress; Status innerProvider.Status; AssetObject innerProvider.AssetObject; } }3.2 优先级调度在某些场景中不同的资源有不同的加载优先级。例如进入战斗场景时角色模型的加载优先级应该高于场景背景的加载优先级。YooAsset 的 Provider 体系支持优先级调度通过设置 Provider 的 Priority 属性来实现。public class PriorityResourceLoader { public enum LoadPriority { Critical 0, // 关键资源立即加载 High 1, // 高优先级 Normal 2, // 普通 Low 3, // 低优先级后台加载 Background 4 // 后台最低优先级 } private Dictionarystring, AssetHandle pendingLoads new Dictionarystring, AssetHandle(); public AssetHandle LoadWithPriority(string packageName, string location, LoadPriority priority) { var package YooAssets.GetPackage(packageName); var handle package.LoadAssetAsyncUnityEngine.Object(location); return handle; } }四、预加载策略4.1 基于场景的预加载预加载是优化用户体验的重要策略。通过在场景切换时提前加载下一个场景需要的资源可以消除加载过程中的卡顿感。基于场景的预加载策略需要建立场景和资源之间的映射关系。每个场景维护一个预加载列表包含该场景需要的所有资源。在进入场景 A 时同时启动场景 B 的预加载流程。4.2 基于用户行为的预加载一种更智能的预加载策略是基于用户行为预测。通过分析用户的操作模式预测用户下一步可能需要的资源并提前加载。例如在关卡选择界面如果用户经常在选择关卡 A 后选择关卡 B系统可以在用户进入关卡 A 时就开始预加载关卡 B 的资源。这种策略需要收集和分析用户行为数据并建立预测模型。4.3 预加载管理器实现using System.Collections.Generic; using UnityEngine; using YooAsset; public class PreloadManager : MonoBehaviour { [System.Serializable] public class PreloadEntry { public string sceneName; public string[] tags; public string packageName DefaultPackage; } public PreloadEntry[] preloadConfigs; private Dictionarystring, bool preloadedScenes new Dictionarystring, bool(); public void PreloadForScene(string sceneName) { if (preloadedScenes.ContainsKey(sceneName)) return; foreach (var entry in preloadConfigs) { if (entry.sceneName sceneName) { var package YooAssets.GetPackage(entry.packageName); var downloader package.CreateResourceDownloader(entry.tags, null); if (downloader.TotalDownloadCount 0) { downloader.BeginDownload(); StartCoroutine(WaitPreload(downloader, sceneName)); } else { preloadedScenes[sceneName] true; } break; } } } private System.Collections.IEnumerator WaitPreload(DownloaderOperation downloader, string sceneName) { yield return downloader; preloadedScenes[sceneName] downloader.Status EOperationStatus.Succeed; } }五、总结自定义加载策略和 Provider 扩展是 YooAsset 进阶使用的重要内容。通过深入理解 Provider 的工作机制开发者可以根据项目需求定制加载行为。优先级调度和预加载策略是两种最常见的扩展方案在实际项目中能显著提升用户体验。五、加载策略的性能考量5.1 Provider 创建的开销Provider 的创建和销毁是有开销的。每个 Provider 在创建时会分配一些内部数据结构注册到 OperationSystem 中。虽然这个开销不大通常在 0.1ms 以下但当同时创建数百个 Provider 时累积的开销就会变得显著。减少 Provider 创建开销的方法是复用。YooAsset 内部已经实现了 Provider 对象池当 Provider 完成任务后它会被回收到对象池而不是被销毁。当新的加载请求到达时从对象池中获取一个空闲的 Provider 重复使用。5.2 优先级反转问题优先级调度系统面临的一个重要问题是优先级反转。当多个不同优先级的加载任务同时进行时低优先级的任务可能持有高优先级任务需要的锁或资源导致高优先级任务被低优先级任务阻塞。在 YooAsset 的 Provider 体系中优先级反转主要发生在 Bundle 加载阶段。一个高优先级的资源需要加载一个 Bundle而这个 Bundle 可能正在被一个低优先级的加载任务使用。此时高优先级的任务只能等待低优先级任务完成才能加载。解决方案主要有两个。一是资源预分配确保高优先级资源使用的 Bundle 在系统空闲时预加载完成。二是依赖分析优化在设计资源分组时避免高优先级和低优先级资源共享同一个 Bundle。5.3 预加载的时机选择预加载策略的效果很大程度上取决于时机的选择。过早的预加载会导致资源长时间占用内存增加运行时的内存压力。过晚的预加载则无法掩盖加载延迟用户仍然会感到卡顿。预加载的时机选择需要分析玩家的行为模式。在分析用户行为时需要重点关注两个数据平均决策时间和平均加载时间。平均决策时间是从用户进入当前场景到做出下一个选择的平均时间。平均加载时间是从发起加载请求到资源就绪的平均时间。当平均决策时间大于平均加载时间时可以在用户进入场景时立即开始预加载。当平均决策时间小于平均加载时间时需要在用户做出选择之前更早地开始预加载。这种情况下基于用户历史行为的预测预加载就更重要了。六、加载策略的测试方案自定义加载策略需要经过充分的测试才能上线。测试方案应该包括单元测试、集成测试和压力测试。单元测试验证 Provider 的基本功能是否正确集成测试验证自定义 Provider 与 YooAsset 框架的协作是否正常压力测试验证在高并发加载场景下系统的稳定性。在压力测试中需要关注几个指标加载成功率、平均加载时间、最大加载时间和系统资源消耗。加载成功率反映了系统的可靠性平均加载时间和最大加载时间反映了系统的性能表现。系统资源消耗则决定了加载策略对游戏主循环的影响程度。建议在测试环境中模拟各种极端情况包括网络断开、磁盘空间不足、内存压力等。只有在这些极端情况下表现稳定的加载策略才能被用于生产环境。七、加载策略的监控与调优加载策略上线后需要持续监控其效果。监控的核心指标是加载成功率和加载时间分布。加载成功率应该保持在 99.9% 以上如果低于这个值说明加载策略存在问题。加载时间分布反映了加载策略的性能表现关注的是 P50、P95 和 P99 三个分位数。P50 反映了大多数用户的体验P95 反映了较差的体验P99 反映了极端情况下的体验。根据监控数据对加载策略进行调优是一个持续的过程。如果 P99 加载时间过高需要检查极端情况下的表现。可能是某些大文件的加载耗时过长需要拆分为小文件。也可能是某些依赖链过长的资源需要优化依赖结构。如果加载成功率下降需要检查失败的原因分布。是网络问题导致的失败还是资源不存在导致的失败或者是文件校验失败导致的失败。不同的失败原因需要不同的处理方式。八、加载策略与业务场景的结合不同的业务场景需要不同的加载策略没有一种放之四海而皆准的方案。理解业务场景的特征是设计加载策略的第一步。关卡游戏的加载策略比较特殊。玩家在关卡之间切换时有明确的加载界面作为过渡。这个场景下可以使用全量加载策略将下一关需要的所有资源一次性加载到内存中。全量加载的好处是进入关卡后不会出现资源加载的卡顿坏处是加载界面等待时间较长。为了优化用户体验可以在加载界面上展示进度条和提示信息让玩家了解当前的加载进度。开放世界游戏的加载策略完全不同。玩家在大地图上自由移动时不能有明显的加载界面。这个场景下需要使用流式加载策略根据玩家的位置和视距动态加载和卸载资源。流式加载的实现比全量加载复杂得多需要建立场景资源的空间索引实现基于距离的加载优先级排序。YooAsset 的 Package 机制可以很好地支持流式加载场景通过将地图按照区块划分为多个子 Package在玩家移动时动态加载和卸载这些子 Package。大厅类游戏的加载需求介于两者之间。玩家在大厅中时需要加载所有公共界面和功能模块。进入战斗时需要快速加载战斗场景的资源。这个场景下推荐使用混合加载策略大厅资源在启动时全量加载战斗资源在进入战斗前预加载战斗中不再加载任何资源。九、加载策略与性能的平衡设计加载策略时最核心的挑战是平衡加载速度和内存占用之间的关系。加载速度越快往往意味着更多的资源同时驻留在内存中内存占用就越高。反之内存占用越低往往意味着需要更频繁地从磁盘读取资源加载速度就越慢。在这个权衡中需要根据项目的目标设备配置确定平衡点。对于高端设备如 8GB RAM 以上的手机和 PC可以偏向加载速度将更多的资源缓存在内存中。对于中端设备如 4GB 到 8GB RAM 的手机需要在加载速度和内存占用之间取中。对于低端设备如 2GB 到 4GB RAM 的手机必须偏向内存占用即使这意味着加载速度会变慢。除了设备配置游戏类型也会影响这个平衡。实时竞技类游戏对加载速度的要求极高任何一帧的卡顿都可能导致玩家输掉比赛。这类游戏的加载策略应该偏向速度甚至可以在游戏启动时就将所有竞技资源加载到内存中。卡牌游戏对加载速度的要求相对较低玩家可以接受几秒钟的加载等待。这类游戏的加载策略可以偏向内存在需要时才加载资源。最合适的加载策略取决于具体的业务场景。在设计加载策略时需要深入了解业务场景的特征。用户在这个场景下的行为是什么用户在等待加载时的心理预期是什么加载过程中是否有过渡界面可以使用这些问题的答案会影响加载策略的设计。开发者需要深入理解业务场景也需要熟练掌握 YooAsset 的 Provider 体系和加载接口。只有在两个层面都有深入的理解才能设计出高效的加载策略。自定义加载策略的实现过程也是一个加深理解的过程。通过实现自定义 Provider开发者可以更深入地理解 YooAsset 的内部工作机制。这种理解反过来可以帮助开发者更好地使用 YooAsset 的内置功能在更多场景下使用内置方案而不是自己实现。内置方案经过了充分的测试和验证比自己实现的方案更加可靠。上一篇自定义文件系统开发下一篇自定义更新与下载策略
返回列表