diff --git "a/Books/3-5-2-UI-Toolkit-AssetBundle-\347\203\255\346\233\264\346\226\260\347\240\224\347\251\266-2026-06-11.md" "b/Books/3-5-2-UI-Toolkit-AssetBundle-\347\203\255\346\233\264\346\226\260\347\240\224\347\251\266-2026-06-11.md" new file mode 100644 index 0000000000..31be2a488b --- /dev/null +++ "b/Books/3-5-2-UI-Toolkit-AssetBundle-\347\203\255\346\233\264\346\226\260\347\240\224\347\251\266-2026-06-11.md" @@ -0,0 +1,207 @@ +# TEngine UI Toolkit + AssetBundle / YooAsset 热更新研究报告 + +**日期**: 2026-06-11 +**研究目标**: Unity UI Toolkit (UXML/USS) 从 AssetBundle/YooAsset 运行时加载的可行性、限制、性能与设计建议(服务于 HybridCLR + YooAsset 热更新移动游戏)。 +**来源**: Unity 官方文档 (Unity 6 / 6000.x)、论坛讨论、GitHub 真实代码示例、Addressables/YooAsset 集成模式。 + +## 1. 可行性结论 + +**完全可行**。UI Toolkit 的 `VisualTreeAsset` (UXML) 和 `StyleSheet` (USS) 是普通 Unity 资产,可通过 AssetBundle、Addressables 或 YooAsset 正常加载和运行时使用。 + +- 官方推荐路径:**Addressables**(Unity 文档明确提供完整示例)。 +- TEngine 实际路径:**YooAsset**(与 Addressables 底层同为 AssetBundle + 清单机制),将 UXML/USS 作为普通资产打进资源包即可。 +- 无版本硬性阻断:Unity 2021.3+(TEngine 最低要求)至 Unity 6 均支持。不能在运行时从原始文本动态生成 VisualTreeAsset(Importer 是 Editor-only),但预构建的资产加载完全没问题。 + +**核心证据**: +- Unity 官方文档《Load UI assets with Addressables》:https://docs.unity3d.com/6000.6/Documentation/Manual/ui-systems/load-ui-assets-with-addressables.html (提供 MonoBehaviour + PanelRenderer 完整异步加载 + 应用代码)。 +- Unity 官方文档《Load UXML and USS in C# scripts》:https://docs.unity3d.com/6000.6/Documentation/Manual/UIE-manage-asset-reference.html (明确列出 Addressables、序列化引用、Resources 三种运行时加载方式)。 +- 性能最佳实践文档强调:“Use Asset Bundles or Addressables: When possible, only load the UI documents and style sheets required... Unload assets when not needed”。 + +## 2. 加载 VisualTreeAsset (UXML) 和 StyleSheet (USS) 的标准模式 + +### 2.1 Addressables 官方推荐模式(可直接映射到 YooAsset) + +```csharp +// 伪代码,Addressables 示例(YooAsset 几乎等价) +private AsyncOperationHandle uxmlHandle; +private AsyncOperationHandle ussHandle; +private VisualTreeAsset loadedUxml; +private StyleSheet loadedUss; + +void LoadUI() +{ + uxmlHandle = Addressables.LoadAssetAsync("uxmlexample"); // 或自定义 key + uxmlHandle.Completed += OnUxmlLoaded; + + ussHandle = Addressables.LoadAssetAsync(ussAssetReference); + ussHandle.Completed += OnUssLoaded; +} + +void OnUxmlLoaded(AsyncOperationHandle handle) +{ + if (handle.Status == AsyncOperationStatus.Succeeded) + { + loadedUxml = handle.Result; + // 方式A:赋值给 PanelRenderer / UIDocument + panelRenderer.visualTreeAsset = loadedUxml; + // 方式B:手动 CloneTree + // loadedUxml.CloneTree(rootVisualElement); + } +} + +void OnUssLoaded(AsyncOperationHandle handle) +{ + if (handle.Status == AsyncOperationStatus.Succeeded) + { + loadedUss = handle.Result; + // 必须检查重复添加(reload 回调常见问题) + if (!rootElement.styleSheets.Contains(loadedUss)) + rootElement.styleSheets.Add(loadedUss); + } +} + +void Unload() +{ + if (uxmlHandle.IsValid()) Addressables.Release(uxmlHandle); + if (ussHandle.IsValid()) Addressables.Release(ussHandle); +} +``` + +**YooAsset 映射**(TEngine 实际使用): +- 使用 `YooAssets.GetPackage(packageName).LoadAssetAsync(location)`。 +- `handle.AssetObject as VisualTreeAsset` 或泛型结果。 +- 释放使用 `handle.Release()` 或框架上层 `GameModule.Resource.UnloadAsset` / `AssetsReference` 机制。 +- 与 TEngine 现有 `IUIResourceLoader` + `UIResourceLoader` 集成点:新增 `LoadVisualTreeAssetAsync` / `LoadStyleSheetAsync`。 + +### 2.2 CloneTree() / Instantiate() 运行时用法 + +- `VisualTreeAsset.CloneTree(VisualElement target)` —— 最常用,把树克隆到已有根节点。 +- `VisualTreeAsset.Instantiate()` —— 返回新的 `TemplateContainer` 根。 +- 两者在运行时加载的 VisualTreeAsset 上**完全可用**,无特殊限制。 +- 新版还支持带 `VisualElementAssetReferenceTable` 的重载,用于按 AuthoringId 事后查找元素。 + +性能提示(来自官方最佳实践): +- CloneTree 本身成本不高,**主要成本在它引用的纹理/字体等资产被拉入内存** + 后续布局/样式解析。 +- 大型 UXML 建议拆成多个小模板,按需动态加载。 +- 列表类 UI 必须用 `ListView` 虚拟化,不要为每个条目都 CloneTree。 + +## 3. PanelSettings 管理 + +- `PanelSettings` 是 **ScriptableObject 资产**,可以像普通资产一样打进 AB / 放到 YooAsset 组 / 标记 Addressable,然后运行时加载。 +- 它是 UIDocument / PanelRenderer 的渲染配置载体(缩放模式、排序、主题等)。 +- 多个 UIDocument 可共享同一个 PanelSettings 以优化性能。 +- **重要限制/坑**(论坛实测): + - 纯运行时 `new PanelSettings()` 创建的实例,在构建后可能拿不到默认样式表(因为默认样式表是 Editor 资产,只有通过序列化引用打包进去才会被包含)。 + - 推荐做法:**在 Editor 里提前创建好 PanelSettings 资产**,配置好 Theme Style Sheet,然后通过资源系统加载该资产实例并赋值。 + - 热更新主题:可以运行时加载不同的 PanelSettings,或修改已加载 PanelSettings 的 `themeStyleSheet` 引用(需测试具体版本行为)。 + +## 4. USS 引用解析、@import、Theme 文件的运行时坑 + +这是最容易踩的区域: + +- **@import 链在运行时不可靠**: + - UI Builder 和 Editor 里预览正常,构建后或运行时加载后样式丢失/部分生效。 + - 论坛多帖反馈:“Unreferenced theme file seems to change USS load order”、“How to use Themes (TSS) properly for runtime UI”。 + - 根因:运行时样式表加载顺序与 Editor 不同,@import 解析依赖 AssetDatabase 行为。 + +- **推荐的运行时稳妥做法**(社区 + 实测总结): + 1. **UXML 里显式声明**:`