Blutility 模块全面系统分析
源文档:https://gitee.com/chouchouxia/share-ue5
前言
Blutility 这个名字在 UE5 编辑器开发圈子里被提到的频率越来越高,但真正把它从头到尾梳理清楚的文章并不多。我在学习这个模块的过程中,反复翻阅了它的源码、对照了引擎里其他编辑器扩展机制的实现方式,才逐渐理解它为什么被设计成现在这个样子。
这篇文章不是 Blutility 的 API 列表翻译,而是尝试从"为什么需要它"开始,把它的架构设计、核心类实现、数据流走向、以及在实际项目中的使用方式,完整地串联起来。如果大家正在考虑给自己的团队搭建一套编辑器工具链,或者单纯想搞清楚"编辑器里右键菜单里那些 Scripted Actions 到底是怎么来的",希望这篇分析能帮上忙。
在这之前,我们已经在 AsyncMixin 插件分析 和 CommonGame 插件分析 中分别探讨了异步加载和游戏框架的设计思路。今天这篇 Blutility 分析,聚焦的是另一个维度------如何让非 C++ 开发者也能够参与到编辑器工具的开发中来。
必须知道的问题
在深入代码之前,先回答几个我在学习过程中反复追问自己的问题。这些问题如果一开始没想清楚,后面看代码很容易陷入细节而迷失方向。
1. 为什么要做 Blutility 模块?如果没有它会怎么样?
核心定位
Blutility 的全称是 Blutility (Blueprint Utility),它做的事情用一句话就能概括:让蓝图能够编写编辑器工具。
在 Blutility 出现之前,想在 UE 编辑器里加一个自定义工具,只有一条路------写 C++ 插件,继承对应的编辑器类,注册菜单扩展,编译,重启编辑器。这条路对程序员来说不算什么,但对美术、策划、TA 来说,基本就是一道无法逾越的墙。
Blutility 把这个"墙"拆掉了。它提供了几个关键基类,任何人在编辑器里右键创建蓝图、继承这些基类、写蓝图逻辑,就能做出:
- 一个独立的编辑器面板(
UEditorUtilityWidget) - 一个右键资产就能批量操作的菜单项(
UAssetActionUtility) - 一个右键 Actor 就能批量操作的菜单项(
UActorActionUtility) - 一个带进度条的编辑器后台任务(
UEditorUtilityTask)
如果没有 Blutility 会怎么样?
每个需要编辑器工具的团队,要么依赖程序员手写 C++ 插件(成本高、迭代慢),要么用 Python 脚本(需要额外配置、功能受限),要么干脆不做自动化------重复劳动全压在美术和策划身上。
Blutility 本质上是一个"编辑器工具开发的民主化"方案。它把编辑器扩展的能力从 C++ 程序员手里,下放到了所有会用蓝图的人手里。
2. Blutility 和 EditorScriptingUtilities 插件是什么关系?
如果用过 UE4 的早期版本,可能会记得 EditorScriptingUtilities 这个插件。它提供了一些编辑器操作的蓝图函数库(比如 RenameAsset、GetSelectedAssets 等)。Blutility 可以看作是这套思想的完全体。
两者的关系可以这样理解:
| EditorScriptingUtilities | Blutility | |
|---|---|---|
| 定位 | 提供编辑器操作的蓝图函数库 | 提供编辑器工具的蓝图框架 |
| 能做什么 | 在蓝图里调用编辑器 API | 在蓝图里创建完整的编辑器工具 |
| 入口 | 需要自己写触发逻辑 | 双击运行、右键菜单、独立面板 |
| 依赖 | 插件,需要手动启用 | 引擎内置模块,开箱即用 |
Blutility 内部大量使用了 EditorScriptingUtilities 提供的函数,但它多了一层"框架"------决定了工具怎么被触发、怎么展示、怎么管理生命周期。
3. 这个模块涉及哪些核心技术?
Blutility 虽然叫"蓝图工具",但它底层的技术栈相当丰富:
| 技术/类 | 用途 |
|---|---|
UEditorSubsystem |
Blutility 的管理器基类,随编辑器生命周期自动创建和销毁 |
UToolMenus |
动态注册 Level Editor 和 Content Browser 的右键菜单 |
FAssetRegistry |
扫描 Blutility 蓝图资产,获取类型和支持信息 |
FTabManager |
注册和管理 Editor Utility Widget 的 Dock Tab |
FTSTicker |
提供每帧 Tick,用于 Task 队列调度 |
FAsyncTaskNotification |
编辑器右下角的进度通知 |
FAssetActionUtilityPrototype |
资产标签缓存代理,避免每次都加载蓝图 |
UWidgetBlueprint |
Widget 蓝图编译和管理 |
FStreamableManager |
异步延迟和异步场景截图等操作 |
这些技术单独拿出来每一个都值得深入研究,但 Blutility 把它们有机地组合在了一起,形成了一个完整的编辑器工具开发框架。
目录
- 模块概述
- 模块整体架构解析
- 类级代码注释详解
- 功能使用示例编写
- [在 UE5 编辑器中使用 Blutility 的完整流程](#在 UE5 编辑器中使用 Blutility 的完整流程)
- 总结与最佳实践
- 写在最后
- 附录
### 文章目录
- [Blutility 模块全面系统分析](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [前言](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [必须知道的问题](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [1. 为什么要做 Blutility 模块?如果没有它会怎么样?](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [核心定位](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [如果没有 Blutility 会怎么样?](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [2. Blutility 和 EditorScriptingUtilities 插件是什么关系?](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [3. 这个模块涉及哪些核心技术?](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [目录](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [@toc](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [1 模块概述](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [1.1 基本信息](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [1.2 Blutility 到底能做什么?](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [2 模块整体架构解析](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [2.1 架构图](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [2.2 模块间依赖关系](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [2.3 数据流走向](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [2.4 核心技术栈](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [3 类级代码注释详解](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [3.1 FBlutilityModule --- 模块入口](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [StartupModule 做了什么](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [3.2 UEditorUtilityObject --- 最轻量的入口](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [3.3 UEditorUtilityWidget --- 编辑器面板 Widget](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [3.4 UEditorUtilityWidgetBlueprint --- Widget 蓝图资产管理](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [3.5 UEditorUtilityTask --- 后台任务框架](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [3.6 UAssetActionUtility --- 资产右键菜单动作](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [3.7 UEditorUtilitySubsystem --- 核心调度引擎](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [Initialize 过程](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [Tick 中的任务调度](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [TryRun ------ 执行逻辑的核心](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [3.8 FAssetActionUtilityPrototype --- 资产标签缓存代理](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [缓存了哪些关键信息?](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [标签写入的触发时机](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [性能收益](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [3.9 菜单扩展机制详解 --- 从 Hook 安装到菜单项生成](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [3.9.1 两套独立的扩展入口](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [3.9.2 动态菜单生成的核心流程](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [3.9.3 函数执行时的参数转发机制](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [3.9.4 `SFunctionParamDialog` --- 参数输入对话框](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [3.10 辅助类一览](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [4 功能使用示例编写](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [示例 1:最简单的"一键执行"工具](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [示例 3:带参数的资产批量操作](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [示例 4:创建 Task 做耗时操作](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [示例 5:创建带 UI 的编辑器面板](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [5 在 UE5 编辑器中使用 Blutility 的完整流程](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [5.1 创建一个 Editor Utility Widget](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [5.2 创建一个 Asset Action Utility](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [5.3 配置自启动工具](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [5.4 用 Python 调用](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [6 总结与最佳实践](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [6.1 核心要点](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [6.2 最佳实践](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [6.3 潜在改进点](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [6.4 常见问题](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [写在最后](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [附录](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [A. 文件结构参考](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源) - [B. 相关资源](#文章目录 Blutility 模块全面系统分析 前言 必须知道的问题 1. 为什么要做 Blutility 模块?如果没有它会怎么样? 核心定位 如果没有 Blutility 会怎么样? 2. Blutility 和 EditorScriptingUtilities 插件是什么关系? 3. 这个模块涉及哪些核心技术? 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 Blutility 到底能做什么? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 数据流走向 2.4 核心技术栈 3 类级代码注释详解 3.1 FBlutilityModule — 模块入口 StartupModule 做了什么 3.2 UEditorUtilityObject — 最轻量的入口 3.3 UEditorUtilityWidget — 编辑器面板 Widget 3.4 UEditorUtilityWidgetBlueprint — Widget 蓝图资产管理 3.5 UEditorUtilityTask — 后台任务框架 3.6 UAssetActionUtility — 资产右键菜单动作 3.7 UEditorUtilitySubsystem — 核心调度引擎 Initialize 过程 Tick 中的任务调度 TryRun —— 执行逻辑的核心 3.8 FAssetActionUtilityPrototype — 资产标签缓存代理 缓存了哪些关键信息? 标签写入的触发时机 性能收益 3.9 菜单扩展机制详解 — 从 Hook 安装到菜单项生成 3.9.1 两套独立的扩展入口 3.9.2 动态菜单生成的核心流程 3.9.3 函数执行时的参数转发机制 3.9.4
SFunctionParamDialog— 参数输入对话框 3.10 辅助类一览 4 功能使用示例编写 示例 1:最简单的"一键执行"工具 示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget 示例 3:带参数的资产批量操作 示例 4:创建 Task 做耗时操作 示例 5:创建带 UI 的编辑器面板 5 在 UE5 编辑器中使用 Blutility 的完整流程 5.1 创建一个 Editor Utility Widget 5.2 创建一个 Asset Action Utility 5.3 配置自启动工具 5.4 用 Python 调用 6 总结与最佳实践 6.1 核心要点 6.2 最佳实践 6.3 潜在改进点 6.4 常见问题 写在最后 附录 A. 文件结构参考 B. 相关资源)
1 模块概述
1.1 基本信息
| 属性 | 值 |
|---|---|
| 模块名称 | Blutility |
| 类型 | Editor(仅编辑器) |
| 描述 | 编辑器工具蓝图(Editor Utility Blueprint)框架,提供蓝图化的编辑器扩展能力 |
整个模块的代码分布在两类文件中:
Classes/目录下的 可继承基类(提供给蓝图继承用)Private/和Public/目录下的 基础设施层(负责把蓝图注册到编辑器的各个菜单、工具栏中)
核心依赖的模块链条相当长------EditorSubsystem、MainFrame、UMG、ToolMenus、ContentBrowser、UnrealEd 等等,几乎覆盖了编辑器开发的半壁江山。这也意味着从 Blutility 入手了解编辑器扩展机制,是一个非常高效的学习路径。
1.2 Blutility 到底能做什么?
用一句话概括:在蓝图里写代码,在编辑器里跑出来。具体到场景上,大概有这几类:
| 场景 | 对应基类 | 典型用途 |
|---|---|---|
| 编辑器工具 Widget | UEditorUtilityWidget |
做一个独立的编辑器面板,上面有按钮、输入框,点击后执行自定义逻辑 |
| 资产批量操作 | UAssetActionUtility |
在 Content Browser 里右键一批资产,批量重命名、批量修改属性、批量导出 |
| 关卡 Actor 操作 | UActorActionUtility |
在关卡视口中右键一批 Actor,批量对齐、批量替换、批量设置属性 |
| 编辑器后台任务 | UEditorUtilityTask |
跑一个耗时操作,带进度通知和取消支持,不阻塞编辑器主界面 |
| 纯逻辑工具 | UEditorUtilityObject |
最轻量的形式,双击即执行 Run() 函数 |
| 编辑器场景 Actor | AEditorUtilityActor |
放在关卡里、可以在编辑器中接收输入的特殊 Actor |
| 编辑器函数库 | UEditorFunctionLibrary |
纯静态函数的蓝图库,供其他 Blutility 蓝图调用 |
也就是说,任何在编辑器里"如果能右键一键搞定就好了"的重复操作,都可以包装成一个 Blutility 蓝图来解决。
2 模块整体架构解析
2.1 架构图
Blutility 模块的分层非常清晰:基类层 (蓝图继承的入口)、管理调度层 (UEditorUtilitySubsystem)、扩展绑定层(菜单注册),三层各司其职:
┌────────────────────────────────────────────────────────────────────────────────┐
│ 用户蓝图 (User Blueprint) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Editor │ │ AssetAction │ │ ActorAction │ │ Editor │ │
│ │ Utility │ │ Utility │ │ Utility │ │ Utility │ │
│ │ Widget 蓝图 │ │ 蓝图 │ │ 蓝图 │ │ Task 蓝图 │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
└─────────┼─────────────────┼─────────────────┼─────────────────┼──────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌────────────────────────────────────────────────────────────────────────────────┐
│ Blutility 基类层 (Classes/) │
│ ┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │UEditorUtility │ │UAssetAction │ │UActorAction │ │
│ │Widget │ │Utility │ │Utility │ │
│ │(继承 UUserWidget)│ │(继承 EditorObj) │ │(继承 EditorObj) │ │
│ └────────┬────────┘ └────────┬─────────┘ └────────┬─────────┘ │
│ │ │ │ │
│ ┌────────┴────────┐ │ │ │
│ │UEditorUtility │ │ │ │
│ │WidgetBlueprint │ ▼ ▼ │
│ │(继承 WidgetBP) │ ┌──────────────────────────────────────┐ │
│ └─────────────────┘ │UEditorUtilityObject │ │
│ │(继承 UObject, IAssetRegistryTag) │ │
│ └──────────────────┬───────────────────┘ │
│ │ │
│ ┌──────────────────────┼──────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐ │
│ │AEditorUtilityActor │ │UEditorUtilityTask │ │UEditorUtility │ │
│ │(继承 AActor) │ │(继承 UObject) │ │ActorComponent │ │
│ └────────────────────┘ └────────────────────┘ └────────────────────┘ │
│ │
│ 辅助: UEditorUtilityCamera | UEditorUtilityToolMenu* | UEditorFunctionLibrary │
│ UI组件: UEditorUtilityButton/CheckBox/Slider 等 (统一 Editor 风格) │
└────────────────────────────────────────────────────────────────────────────────┘
│
│ 每个基类对应一种蓝图类型,被 UEditorUtilityBlueprint /
│ UEditorUtilityWidgetBlueprint 管理编译和注册
▼
┌────────────────────────────────────────────────────────────────────────────────┐
│ 管理调度层 (UEditorUtilitySubsystem) │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ 职责: │ │
│ │ - 管理所有加载的 Editor Utility Widget 的生命周期 │ │
│ │ - 提供 Tab 注册 / 生成 / 关闭的 API │ │
│ │ - 管理 UEditorUtilityTask 的任务队列和调度 │ │
│ │ - 响应 PIE 事件 (BeginPIE / EndPIE) │ │
│ │ - 启动时自动运行 StartupObjects 列表中的工具 │ │
│ │ - 注册 RunTask / CancelAllTasks 控制台命令 │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ 关键数据结构: │ │
│ │ - LoadedUIs: 持久化加载的 Widget 蓝图路径 │ │
│ │ - StartupObjects: 启动时自动运行的资源路径 │ │
│ │ - RegisteredTabs: 注册的 Tab 名称 -> WidgetBlueprint 映射 │ │
│ │ - ActiveTaskStack: 当前活跃任务栈(支持父子任务) │ │
│ │ - PendingTasks: 待处理任务映射 │ │
│ │ - ObjectInstances: 已运行的蓝图实例缓存 │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────────┘
│
│ UEditorUtilitySubsystem 是 FBlutilityModule 的"打工人",
│ 它本身不负责菜单注册,菜单注册在扩展绑定层完成
▼
┌────────────────────────────────────────────────────────────────────────────────┐
│ 扩展绑定层 (FBlutilityModule) │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ FBlutilityModule (IBlutilityModule 实现) │ │
│ │ - StartupModule: 注册资产类别、编译器、安装各种 Menu 扩展 Hook │ │
│ │ - ShutdownModule: 移除 Hook │ │
│ │ - 管理 ScriptedEditorWidgetsGroup 工作区菜单 │ │
│ │ - 管理 LoadedUIs 的持久化和重新注册 │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────┐ ┌──────────────────────────────┐ │
│ │FBlutilityContentBrowser │ │FBlutilityLevelEditor │ │
│ │Extensions │ │Extensions │ │
│ │(ContentBrowser 右键菜单) │ │(关卡 Viewport 右键菜单) │ │
│ │├─ 扫描 AssetActionUtility │ │├─ 扫描 ActorActionUtility │ │
│ │├─ 类过滤 + 条件过滤 │ │├─ 类过滤 │ │
│ │└─ 动态生成 Scripted Actions │ │└─ 动态生成 Scripted Actions │ │
│ └─────────────────────────────┘ └──────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │FBlutilityMenuExtensions │ │
│ │(菜单生成核心逻辑) │ │
│ │├─ GetBlutilityClasses() │ │
│ │├─ CreateActor/Asset Actions │ │
│ │├─ 参数对话框 (SFunctionParam│ │
│ ││ Dialog) │ │
│ │└─ 函数执行 + 选中对象转发 │ │
│ └─────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────────┘
2.2 模块间依赖关系
FBlutilityModule (模块入口)
│
├─→ 注册 Blutility 基类的蓝图编译器覆盖
│ (KismetCompilerModule.OverrideBPTypeForClass)
│
├─→ 创建 "Editor Utility Widgets" 工作区菜单组
│
├─→ 安装三套扩展 Hook:
│ ├─ FBlutilityContentBrowserExtensions (资产右键菜单)
│ ├─ FBlutilityLevelEditorExtensions (Actor 右键菜单)
│ └─ FBlutilityUMGEditorExtensions (UMG 编辑器扩展)
│
└─→ 依赖 UEditorUtilitySubsystem 管理运行时状态
UEditorUtilitySubsystem (运行时管理)
↓ 管理
├─ Tab 生命周期 (Spawn / Register / Close / Unregister)
├─ Task 调度 (RegisterAndExecuteTask, Tick, 父子任务栈)
├─ PIE 事件分发 (OnBeginPIE / OnEndPIE)
├─ 启动时执行 (HandleStartup → TryRun)
└─ 控制台命令 (RunTask, CancelAllTasks)
如果觉得这个依赖链看起来有点绕,可以简单这么理解:FBlutilityModule 负责"展示"------把 Blutility 蓝图注册到编辑器的各个菜单里;UEditorUtilitySubsystem 负责"执行"------管理运行实例、任务调度和生命周期。 两者的接口是 IBlutilityModule 和 UEditorUtilitySubsystem 之间的互相调用。
2.3 数据流走向
Editor Utility Widget 从创建到显示:
1. 用户在 Content Browser 中双击 UEditorUtilityWidget 派生蓝图
↓
2. UEditorUtilitySubsystem::SpawnAndRegisterTabAndGetID()
↓
3. RegisterTabAndGetID() → 注册 TabSpawner 到 FTabManager
↓
4. SpawnRegisteredTabByID() → 调用 TryInvokeTab()
↓
5. UEditorUtilityWidgetBlueprint::SpawnEditorUITab()
↓
6. CreateUtilityWidget() → CreateWidget<UEditorUtilityWidget>()
↓
7. MarkTransientRecursive() 标记为瞬态对象(避免污染关卡)
↓
8. 将 Widget 放到 SDockTab 中,添加到编辑器界面
↓
9. FBlutilityModule::AddLoadedScriptUI() → 持久化记录到 LoadedUIs
AssetActionUtility 从右键触发到执行:
1. 用户在 Content Browser 中右键选中一些资产
↓
2. FBlutilityContentBrowserExtensions 的动态菜单项触发
↓
3. GetBlutilityClasses(UAssetActionUtility) 扫描所有符合条件的蓝图
↓
4. 对每个蓝图:
a. FAssetActionUtilityPrototype 从资产标签缓存中读取 SupportedClasses
b. 判断选中的资产是否匹配 SupportedClasses 过滤
c. 如果不匹配,跳过
↓
5. ExtractFunctions() 提取所有标有 CallInEditor 且有正确签名的函数
↓
6. 在右键菜单中生成 "Scripted Asset Actions" 子菜单
↓
7. 用户点击某个函数:
a. 如果有参数,弹出 SFunctionParamDialog 让用户填写
b. 如果第一个参数匹配选中对象类型,自动转发选中对象
c. 创建临时对象实例,ProcessEvent 执行函数
d. 完成后删除临时对象
UEditorUtilityTask 的任务调度:
1. 用户创建 UEditorUtilityTask 蓝图子类,实现 BeginExecution / CancelRequested
↓
2. 调用 UEditorUtilitySubsystem::RegisterAndExecuteTask(Task)
↓
3. Task 进入 PendingTasks 队列,作为当前活跃任务的子任务(如果没有活跃任务则为根任务)
↓
4. UEditorUtilitySubsystem::Tick() 中检测到 PendingTasks 有待处理任务
↓
5. StartTask() → ActiveTaskStack.Add → Task->StartExecutingTask()
↓
6. StartExecutingTask 中:
a. 将 GIsRunningUnattendedScript 设为 true(阻止大部分弹出对话框)
b. 创建 FAsyncTaskNotification 进度通知
c. 调用 BeginExecution() / ReceiveBeginExecution()
↓
7. 任务运行中:
- SetTaskNotificationText() 更新进度文字
- WasCancelRequested() 检查用户是否点了取消按钮
- Tick() 中如果检测到取消,调用 RequestCancel()
↓
8. 任务完成:
- FinishExecutingTask() → RemoveTaskFromActiveList()
- 恢复 GIsRunningUnattendedScript
- OnFinished.Broadcast() 通知所有监听者
2.4 核心技术栈
| 技术/类 | 用途 |
|---|---|
UEditorSubsystem |
Blutility 的管理器基类 (继承自 UEditorSubsystem) |
UToolMenus |
动态注册 Level Editor 和 Content Browser 的右键菜单 |
FAssetRegistry |
扫描 Blutility 蓝图资产,获取它们的类型和支持信息 |
FTabManager |
注册和管理 Editor Utility Widget 的 Dock Tab |
FTSTicker |
提供每帧 Tick,用于 Task 队列调度和异步节点 |
FAsyncTaskNotification |
编辑器右下角的进度通知(和引擎编译进度条一样的外观) |
FAssetActionUtilityPrototype |
资产标签缓存代理,避免每次都加载蓝图才能判断支持类型 |
UWidgetBlueprint / UWidgetBlueprintGeneratedClass |
Widget 蓝图编译和管理 |
FStreamableManager |
异步延迟和异步场景截图等操作 |
IAssetRegistryTagProviderInterface |
允许 UEditorUtilityObject 的 CDO 标签传递到蓝图类 |
3 类级代码注释详解
3.1 FBlutilityModule --- 模块入口
FBlutilityModule 是整个 Blutility 系统的启动入口,实现了 IBlutilityModule 接口和 FGCObject(用于 GC 引用管理)。
StartupModule 做了什么
cpp
// BlutilityModule.cpp - StartupModule 关键代码
virtual void StartupModule() override
{
// 1. 注册 "Editor Utilities" 高级资产分类
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
EditorUtilityAssetCategory = AssetTools.RegisterAdvancedAssetCategory(
FName(TEXT("EditorUtilities")),
LOCTEXT("EditorUtilitiesAssetCategory", "Editor Utilities")
);
// 2. 注册 Widget 蓝图编译器
FKismetCompilerContext::RegisterCompilerForBP(
UEditorUtilityWidgetBlueprint::StaticClass(),
&UWidgetBlueprint::GetCompilerForWidgetBP
);
// 3. 覆盖蓝图类型映射 ------ 这是最关键的一步
// 告诉 KismetCompiler:当遇到这些基类的子类蓝图时,
// 使用对应的 Blutility 蓝图类来编译
IKismetCompilerInterface& KismetCompilerModule =
FModuleManager::LoadModuleChecked<IKismetCompilerInterface>("KismetCompiler");
KismetCompilerModule.OverrideBPTypeForClass(
AEditorUtilityActor::StaticClass(), UEditorUtilityBlueprint::StaticClass());
KismetCompilerModule.OverrideBPTypeForClass(
AEditorUtilityCamera::StaticClass(), UEditorUtilityBlueprint::StaticClass());
KismetCompilerModule.OverrideBPTypeForClass(
UEditorUtilityObject::StaticClass(), UEditorUtilityBlueprint::StaticClass());
KismetCompilerModule.OverrideBPTypeForClass(
UEditorFunctionLibrary::StaticClass(), UEditorUtilityBlueprint::StaticClass());
KismetCompilerModule.OverrideBPTypeForClass(
UEditorUtilityWidget::StaticClass(), UEditorUtilityWidgetBlueprint::StaticClass());
// 4. 安装三套菜单扩展 Hook
FBlutilityContentBrowserExtensions::InstallHooks();
FBlutilityLevelEditorExtensions::InstallHooks();
FBlutilityUMGEditorExtensions::InstallHooks();
// 5. 创建 "Editor Utility Widgets" 工作区菜单组
ScriptedEditorWidgetsGroup = WorkspaceMenu::GetMenuStructure().GetToolsCategory()->AddGroup(
LOCTEXT("WorkspaceMenu_EditorUtilityWidgetsGroup", "Editor Utility Widgets"),
LOCTEXT("ScriptedEditorWidgetsGroupTooltipText", "Custom editor UI created with Blueprints or Python."),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "WorkspaceMenu.AdditionalUI"),
true);
// 6. 监听编辑器事件
FLevelEditorModule& LevelEditorModule =
FModuleManager::LoadModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
LevelEditorModule.OnTabManagerChanged().AddRaw(this, &FBlutilityModule::ReinitializeUIs);
LevelEditorModule.OnMapChanged().AddRaw(this, &FBlutilityModule::OnMapChanged);
FEditorSupportDelegates::PrepareToCleanseEditorObject.AddRaw(
this, &FBlutilityModule::OnPrepareToCleanseEditorObject);
// 7. 监听资产删除事件
FAssetRegistryModule& AssetRegistryModule =
FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
AssetRegistryModule.Get().OnAssetRemoved().AddRaw(this, &FBlutilityModule::HandleAssetRemoved);
}
关键设计点:
OverrideBPTypeForClass 这个调用是整个 Blutility 框架的基石。它意味着即使蓝图继承的是 UEditorUtilityObject(它本身继承自 UObject),编译器也会把它编译为 UEditorUtilityBlueprint 类型,从而让它在资产注册表中被正确识别。如果没有这一步,Blutility 蓝图在 Content Browser 中就是普通的 UBlueprint,后续的菜单扫描、类型过滤全部无从谈起。
3.2 UEditorUtilityObject --- 最轻量的入口
cpp
// EditorUtilityObject.h
UCLASS(Abstract, Blueprintable, meta = (ShowWorldContextPin))
class BLUTILITY_API UEditorUtilityObject : public UObject, public IAssetRegistryTagProviderInterface
{
GENERATED_UCLASS_BODY()
// 核心函数:蓝图中实现 Run 事件即可
UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "Editor")
void Run();
// 实现 IAssetRegistryTagProviderInterface ------ 允许 CDO 上的属性标签
// 传递到蓝图子类的 Asset Registry Tags 中
virtual bool ShouldAddCDOTagsToBlueprintClass() const override
{
return true;
}
};
UEditorUtilityObject 是所有非 Widget、非 Actor 类 Blutility 工具的基类。它只定义了一个核心函数 Run(),这是一个 BlueprintImplementableEvent,意味着在蓝图中实现即可,不需要 C++ 重写。
ShouldAddCDOTagsToBlueprintClass() 返回 true 是一个容易被忽略但非常重要的设计------它让 UAssetActionUtility 的子类能够在 CDO 上配置 SupportedClasses,然后这些配置会自动序列化到蓝图子类的 Asset Registry Tags 中。这就是 FAssetActionUtilityPrototype 能够在不加载蓝图的情况下读取支持类型信息的根本原因。
3.3 UEditorUtilityWidget --- 编辑器面板 Widget
cpp
// EditorUtilityWidget.h
UCLASS(Abstract, meta = (ShowWorldContextPin), config = Editor)
class BLUTILITY_API UEditorUtilityWidget : public UUserWidget
{
GENERATED_BODY()
public:
// 默认动作 ------ 当 bAutoRunDefaultAction=true 时被调用
UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "Editor")
void Run();
// 执行默认动作(C++ 侧入口)
void ExecuteDefaultAction();
bool ShouldAlwaysReregisterWithWindowsMenu() const
{
return bAlwaysReregisterWithWindowsMenu;
}
bool ShouldAutoRunDefaultAction() const
{
return bAutoRunDefaultAction;
}
FText GetTabDisplayName() const
{
return TabDisplayName;
}
// 标记为 Editor Utility ------ 引擎内部很多地方用这个标志做判断
virtual bool IsEditorUtility() const override { return true; }
protected:
// Tab 标题显示名称,同时标记为 AssetRegistrySearchable
UPROPERTY(Category = Config, EditDefaultsOnly, BlueprintReadWrite, AssetRegistrySearchable)
FText TabDisplayName;
UPROPERTY(Category = Config, EditDefaultsOnly, BlueprintReadWrite, AssetRegistrySearchable)
FString HelpText;
// 关闭 Tab 后是否重新出现在 Window 菜单中
UPROPERTY(Config, Category = Settings, EditDefaultsOnly)
bool bAlwaysReregisterWithWindowsMenu;
// true = 双击直接执行 Run();false = 双击打开属性面板
UPROPERTY(Category = Settings, EditDefaultsOnly, BlueprintReadOnly)
bool bAutoRunDefaultAction;
};
| 属性 | 类型 | 说明 |
|---|---|---|
TabDisplayName |
FText |
在 Dock Tab 标题上显示的名称,支持资产注册表搜索 |
HelpText |
FString |
辅助说明文字 |
bAlwaysReregisterWithWindowsMenu |
bool |
关闭 Tab 后是否重新出现在 Window 菜单中 |
bAutoRunDefaultAction |
bool |
是否双击蓝图直接执行 Run()(否则显示属性面板) |
IsEditorUtility() 被覆写为 return true,这个标志在引擎内部很多地方用于判断"这个 Widget 不应该走运行时逻辑"。比如在 UUserWidget 的某些初始化流程中,Editor Utility Widget 会跳过一些不必要的运行时初始化步骤。
3.4 UEditorUtilityWidgetBlueprint --- Widget 蓝图资产管理
cpp
// EditorUtilityWidgetBlueprint.h
UCLASS()
class BLUTILITY_API UEditorUtilityWidgetBlueprint : public UWidgetBlueprint
{
GENERATED_BODY()
public:
virtual void BeginDestroy() override;
// 生成编辑器 UI Tab ------ 这是创建 Widget 面板的核心入口
TSharedRef<SDockTab> SpawnEditorUITab(const FSpawnTabArgs& SpawnTabArgs);
// 创建 UMG Widget 内容
TSharedRef<SWidget> CreateUtilityWidget();
// 重编译时刷新 Tab 内容
void RegenerateCreatedTab(UBlueprint* RecompiledBlueprint);
// Tab 关闭时更新持久化列表
void UpdateRespawnListIfNeeded(TSharedRef<SDockTab> TabBeingClosed);
// 获取当前创建的 Widget 实例
UEditorUtilityWidget* GetCreatedWidget() const { return CreatedUMGWidget; }
void SetRegistrationName(FName InRegistrationName) { RegistrationName = InRegistrationName; }
FName GetRegistrationName() const { return RegistrationName; }
FText GetTabDisplayName() const;
// 递归标记 Widget 及其所有子 Widget 为瞬态
static void MarkTransientRecursive(UEditorUtilityWidget* UtilityWidget);
private:
bool IsWidgetEnabled() const;
void ChangeTabWorld(UWorld* World, EMapChangeType MapChangeType);
private:
// PIE 中是否启用
UPROPERTY(Category = Settings, EditDefaultsOnly)
bool bIsEnabledInPIE = false;
// 调试 BP 时是否启用
UPROPERTY(Category = Settings, EditDefaultsOnly)
bool bIsEnabledInDebugging = false;
// 是否作为 Nomad Tab(可自由拖动)
UPROPERTY(Category = Settings, EditDefaultsOnly)
bool bSpawnAsNomadTab = false;
FName RegistrationName;
TWeakPtr<SDockTab> CreatedTab;
UPROPERTY(Transient)
TObjectPtr<UEditorUtilityWidget> CreatedUMGWidget;
};
UEditorUtilityWidgetBlueprint 是 Editor Utility Widget 的"资产类"。一个 Widget 蓝图的创建、显示、重编译、关闭,都由它来协调。
SpawnEditorUITab() 关键流程:
- 创建
SDockTab(如果bSpawnAsNomadTab设为 Nomad Tab) - 调用
CreateUtilityWidget()创建 UMG 内容 - 绑定 Tab 关闭委托 →
UpdateRespawnListIfNeeded()更新持久化列表 - 绑定
OnCompiled()→RegenerateCreatedTab()重编译时刷新内容 - 绑定地图变更 →
ChangeTabWorld()在地图切换时重建 Widget
MarkTransientRecursive() 是一个重要的方法------它将 Widget 及其所有子 Widget 标记为 RF_Transient。这确保了 Editor Utility Widget 的属性修改不会把编辑器 World 标记为"脏"状态。如果不做这个标记,每次在 Editor Utility Widget 里修改一个属性,编辑器就会提示"有未保存的更改"。
3.5 UEditorUtilityTask --- 后台任务框架
cpp
// EditorUtilityTask.h
UCLASS(Abstract, Blueprintable, meta = (ShowWorldContextPin))
class BLUTILITY_API UEditorUtilityTask : public UObject
{
GENERATED_BODY()
public:
FOnEditorUtilityTaskDelegate OnFinished;
UEditorUtilityTask();
UFUNCTION()
void Run();
virtual UWorld* GetWorld() const override;
// 蓝图调用:标记任务完成
UFUNCTION(BlueprintCallable, Category=Task)
void FinishExecutingTask();
// 蓝图调用:更新进度通知文字
UFUNCTION(BlueprintCallable, Category = Task)
void SetTaskNotificationText(const FText& Text);
// 请求取消任务
void RequestCancel();
// 蓝图调用:检查用户是否点了取消
UFUNCTION(BlueprintCallable, Category = Task)
bool WasCancelRequested() const;
protected:
// C++ 虚函数 ------ 子类可重写
virtual void BeginExecution() {}
virtual void CancelRequested() {}
virtual FText GetTaskTitle() const;
// 蓝图事件 ------ 在蓝图中实现
UFUNCTION(BlueprintImplementableEvent, Category=Task, meta=(DisplayName="BeginExecution"))
void ReceiveBeginExecution();
UFUNCTION(BlueprintImplementableEvent, Category=Task, meta=(DisplayName="CancelRequested"))
void ReceiveCancelRequested();
UFUNCTION(BlueprintImplementableEvent, Category=Task)
FText GetTaskTitleOverride() const;
private:
void CreateNotification();
void StartExecutingTask();
private:
UPROPERTY(Transient)
TObjectPtr<UEditorUtilitySubsystem> MyTaskManager;
UPROPERTY(Transient)
TObjectPtr<UEditorUtilityTask> MyParentTask;
UPROPERTY(Transient)
bool bCancelRequested = false;
// 缓存原始的 GIsRunningUnattendedScript 值
bool Cached_GIsRunningUnattendedScript = false;
TUniquePtr<FAsyncTaskNotification> TaskNotification;
friend UEditorUtilitySubsystem;
};
UEditorUtilityTask 是一次非常重要的设计------它让耗时编辑器操作有了一个标准化的执行框架。概念上很像 UE5 里编译着色器时的进度通知,但它是开放给开发者自己使用的。
生命周期流程:
BeginExecution / ReceiveBeginExecution (开始)
│
├─ 蓝图中实现具体逻辑
├─ 随时调用 SetTaskNotificationText() 更新进度文字
├─ 随时检查 WasCancelRequested() 响应取消
│
▼
FinishExecutingTask() (任务完成)
│
└─ OnFinished.Broadcast(this)
关键设计细节:
Cached_GIsRunningUnattendedScript:开始执行时强制把GIsRunningUnattendedScript设为true,防止执行过程中弹出模态对话框卡住编辑器;执行完毕后恢复CreateNotification():创建FAsyncTaskNotification,标题按优先级取GetTaskTitleOverride()→GetTaskTitle()→GetClass()->GetDisplayNameText()RequestCancel():由子系统 Tick 检测到通知中的取消按钮按下后调用,分别触发 C++ 虚函数CancelRequested()和蓝图事件ReceiveCancelRequested(),然后直接FinishExecutingTask()
任务嵌套: 子系统通过 MyParentTask 字段支持父子任务关系。子任务在父任务完成后才被调度执行。
3.6 UAssetActionUtility --- 资产右键菜单动作
cpp
// AssetActionUtility.h
UCLASS(Abstract, hideCategories=(Object), Blueprintable)
class BLUTILITY_API UAssetActionUtility : public UEditorUtilityObject, public IEditorUtilityExtension
{
GENERATED_BODY()
public:
// 获取静态配置的支持类列表
UFUNCTION(BlueprintPure, Category = "Assets")
const TArray<TSoftClassPtr<UObject>>& GetSupportedClasses() const { return SupportedClasses; }
// 是否为蓝图专用操作
UFUNCTION(BlueprintPure, BlueprintNativeEvent, Category="Assets")
bool IsActionForBlueprints() const;
// 将 SupportedClasses 等信息序列化到 Asset Registry Tags
virtual void GetAssetRegistryTags(FAssetRegistryTagsContext Context) const override;
protected:
// true = SupportedClasses 匹配的是蓝图的父类而非资产本身的类型
UPROPERTY(EditDefaultsOnly, Category="Asset Support")
bool bIsActionForBlueprints = false;
// 支持的资产类列表 ------ 只有选中资产的类型匹配时,菜单项才显示
UPROPERTY(EditDefaultsOnly, Category="Asset Support", meta=(AllowAbstract))
TArray<TSoftClassPtr<UObject>> SupportedClasses;
// 支持条件列表 ------ 基于 Content Browser 筛选语法的额外过滤
UPROPERTY(EditDefaultsOnly, Category="Asset Support")
TArray<FAssetActionSupportCondition> SupportedConditions;
};
FAssetActionSupportCondition 结构体:
cpp
USTRUCT()
struct FAssetActionSupportCondition
{
GENERATED_BODY()
// Content Browser 风格的筛选表达式
// 例如: "..._N AND VirtualTextureStreaming=FALSE"
UPROPERTY(EditAnywhere, Category=Condition)
FString Filter;
// 条件失败时显示的错误原因
UPROPERTY(EditAnywhere, Category=Condition, meta=(MultiLine=true))
FString FailureReason;
// true = 条件失败时菜单项灰显(保留在菜单中);false = 直接隐藏
UPROPERTY(EditDefaultsOnly, Category = "Asset Support")
bool bShowInMenuIfFilterFails = true;
};
函数签名规则: 在子蓝图中,被标记为 CallInEditor 且没有返回值的函数,会被自动收集并显示在右键菜单中。如果函数的第一个参数类型匹配当前选中的对象类型(资产菜单匹配 FAssetData,Actor 菜单匹配 AActor*),引擎会自动将选中对象转发给函数,不需要手动在函数里查询选中集。
3.7 UEditorUtilitySubsystem --- 核心调度引擎
UEditorUtilitySubsystem 是整个 Blutility 的"后台大脑",继承自 UEditorSubsystem。
Initialize 过程
cpp
// EditorUtilitySubsystem.cpp - Initialize
void UEditorUtilitySubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
// 1. 注册控制台命令
if (!IConsoleManager::Get().FindConsoleObject(TEXT("RunTask")))
{
RunTaskCommandObject = IConsoleManager::Get().RegisterConsoleCommand(
TEXT("RunTask"),
TEXT(""),
FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateUObject(
this, &UEditorUtilitySubsystem::RunTaskCommand),
ECVF_Default
);
}
if (!IConsoleManager::Get().FindConsoleObject(TEXT("CancelAllTasks")))
{
CancelAllTasksCommandObject = IConsoleManager::Get().RegisterConsoleCommand(
TEXT("CancelAllTasks"),
TEXT(""),
FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateUObject(
this, &UEditorUtilitySubsystem::CancelAllTasksCommand),
ECVF_Default
);
}
// 2. 等待主窗口创建完成后执行启动工具
IMainFrameModule& MainFrameModule = IMainFrameModule::Get();
if (MainFrameModule.IsWindowInitialized())
{
HandleStartup();
}
else
{
MainFrameModule.OnMainFrameCreationFinished().AddUObject(
this, &UEditorUtilitySubsystem::MainFrameCreationFinished);
}
// 3. 注册每帧 Tick
TickerHandle = FTSTicker::GetCoreTicker().AddTicker(
FTickerDelegate::CreateUObject(this, &UEditorUtilitySubsystem::Tick), 0);
// 4. 监听 PIE 事件
FEditorDelegates::BeginPIE.AddUObject(this, &UEditorUtilitySubsystem::HandleOnBeginPIE);
FEditorDelegates::EndPIE.AddUObject(this, &UEditorUtilitySubsystem::HandleOnEndPIE);
// 5. 监听地图变更
FLevelEditorModule& LevelEditor =
FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
LevelEditor.OnMapChanged().AddUObject(this, &UEditorUtilitySubsystem::OnMapChanged);
}
Tick 中的任务调度
cpp
// EditorUtilitySubsystem.cpp - Tick
bool UEditorUtilitySubsystem::Tick(float DeltaTime)
{
// 1. 检查是否有待处理的子任务
UEditorUtilityTask* CurrentOrParentTask = GetActiveTask();
TArray<TObjectPtr<UEditorUtilityTask>>* PendingChildTasks =
PendingTasks.Find(CurrentOrParentTask);
if (PendingChildTasks && PendingChildTasks->Num())
{
UEditorUtilityTask* PendingChildTask = (*PendingChildTasks)[0];
PendingChildTasks->RemoveAt(0);
StartTask(PendingChildTask);
}
// 2. 检查活跃任务是否被用户取消了
// 注意:取消检测不是事件驱动的,而是每帧轮询 FAsyncTaskNotification 的状态
if (GetActiveTask() && GetActiveTask()->WasCancelRequested())
{
GetActiveTask()->RequestCancel();
}
return true;
}
TryRun ------ 执行逻辑的核心
cpp
// EditorUtilitySubsystem.cpp - TryRun
bool UEditorUtilitySubsystem::TryRun(UObject* Asset)
{
if (!Asset || !IsValidChecked(Asset) || Asset->IsUnreachable())
{
UE_LOG(LogEditorUtilityBlueprint, Warning,
TEXT("Could not run: %s"), Asset ? *Asset->GetPathName() : TEXT("None"));
return false;
}
UClass* ObjectClass = Asset->GetClass();
if (UBlueprint* Blueprint = Cast<UBlueprint>(Asset))
{
ObjectClass = Blueprint->GeneratedClass;
}
if (!ObjectClass)
{
UE_LOG(LogEditorUtilityBlueprint, Warning,
TEXT("Missing class: %s"), *Asset->GetPathName());
return false;
}
// Actor 类型的 Blutility 不能在 CDO 上执行 ------ 需要放到关卡中
if (ObjectClass->IsChildOf(AActor::StaticClass()))
{
UE_LOG(LogEditorUtilityBlueprint, Warning,
TEXT("Could not run because functions on actors can only be called "
"when spawned in a world: %s"), *Asset->GetPathName());
return false;
}
// 查找 Run 函数 ------ 必须是 0 参数的
static const FName RunFunctionName("Run");
UFunction* RunFunction = ObjectClass->FindFunctionByName(RunFunctionName);
if (RunFunction && RunFunction->ParmsSize == 0)
{
UObject* Instance = NewObject<UObject>(this, ObjectClass);
ObjectInstances.Add(Asset, Instance);
FEditorScriptExecutionGuard ScriptGuard;
Instance->ProcessEvent(RunFunction, nullptr);
return true;
}
else
{
UE_LOG(LogEditorUtilityBlueprint, Warning,
TEXT("Missing 0 param function named 'Run': %s"), *Asset->GetPathName());
}
return false;
}
Tab 管理 API 一览:
| 方法 | 功能 |
|---|---|
SpawnAndRegisterTabAndGetID |
注册并生成 Tab,返回 Widget 实例和 TabID |
SpawnAndRegisterTabWithId |
用自定义 TabID 生成 Tab(Python/BP 友好) |
RegisterTabAndGetID |
仅注册不生成 |
SpawnRegisteredTabByID |
用 ID 生成已注册的 Tab |
DoesTabExist / CloseTabByID / UnregisterTabByID |
Tab 查询和管理 |
FindUtilityWidgetFromBlueprint |
从蓝图资产反查 Widget 实例 |
3.8 FAssetActionUtilityPrototype --- 资产标签缓存代理
这是一个"原型模式"的产物,解决了一个实际问题:右键菜单弹出时,不可能为了判断每个 Blutility 蓝图匹配哪些资产而去加载所有蓝图。那样的话每右键一次就要等好几秒。
FAssetActionUtilityPrototype 的思路是:在蓝图编译时,把关键信息全部序列化到 Asset Registry 的隐藏标签中。右键菜单触发时,直接从缓存标签中读取,完全不需要加载蓝图本身。
缓存了哪些关键信息?
FAssetActionUtilityPrototype 持有的是一个 FAssetData 引用(UtilityBlueprintAsset),它提供了以下纯标签读取的方法:
| 方法 | 读取的标签 | 用途 |
|---|---|---|
IsLatestVersion() |
BlutilityTagVersion |
检查蓝图是否已升级到最新版本,旧版本需要重新保存 |
GetSupportedClasses() |
SupportedClasses |
获取该工具支持的资产类列表,用于类过滤 |
AreSupportedClassesForBlueprints() |
IsActionForBlueprint |
bIsActionForBlueprints 标志------true 表示按蓝图的父类匹配,而非资产本身类型 |
GetAssetActionSupportConditions() |
SupportedConditions |
获取 Content Browser 筛选表达式条件列表(JSON 序列化) |
GetCallableFunctions() |
CallableFunctions |
获取所有 CallInEditor 函数的签名和元数据(JSON 序列化) |
这些标签是在 UAssetActionUtility::GetAssetRegistryTags() 中通过静态辅助函数写入的:
cpp
// AssetActionUtility.cpp
void UAssetActionUtility::GetAssetRegistryTags(FAssetRegistryTagsContext Context) const
{
Super::GetAssetRegistryTags(Context);
FAssetActionUtilityPrototype::AddTagsFor_Version(Context);
FAssetActionUtilityPrototype::AddTagsFor_SupportedClasses(SupportedClasses, Context);
FAssetActionUtilityPrototype::AddTagsFor_SupportedConditions(SupportedConditions, Context);
FAssetActionUtilityPrototype::AddTagsFor_IsActionForBlueprints(IsActionForBlueprints(), Context);
FAssetActionUtilityPrototype::AddTagsFor_CallableFunctions(this, Context);
}
AddTagsFor_CallableFunctions 的实现非常关键 ------它遍历蓝图生成类上的所有蓝图函数,收集那些标记了 CallInEditor 且没有返回值的函数,将函数名、分类、Tooltip、所属类路径等信息序列化为 JSON 存入标签。这样右键菜单在生成时就能直接知道每个 Blutility 提供了哪些可调用函数。
标签写入的触发时机
关键依赖链路是:
蓝图编译成功
→ UEditorUtilityBlueprint / UEditorUtilityWidgetBlueprint 生成
→ GeneratedClass 上的 CDO 触发 GetAssetRegistryTags()
→ FAssetActionUtilityPrototype 的静态方法写入标签
→ Asset Registry 扫描并缓存
→ 右键菜单触发时直接读取缓存,零蓝图加载
性能收益
这个设计把"右键弹出的延迟"从"加载 N 个蓝图"压缩到了"读 N 个资产标签",性能差异是数量级的。同时,IsLatestVersion() 检查确保过时的蓝图能被识别,在 MessageLog 中向用户报告"需要重新保存并升级"。
3.9 菜单扩展机制详解 --- 从 Hook 安装到菜单项生成
参考文档中 AsyncMixin 和 CommonGame 都花了大量篇幅分析核心执行流程。对于 Blutility 来说,最关键的流程之一就是右键菜单的动态生成。这一节把它拆开来看。
3.9.1 两套独立的扩展入口
Blutility 在 Content Browser 和 Level Editor 中分别注册了两套扩展:
Content Browser 扩展(FBlutilityContentBrowserExtensions):
cpp
void FBlutilityContentBrowserExtensions::InstallHooks()
{
UToolMenus::RegisterStartupCallback(
FSimpleMulticastDelegate::FDelegate::CreateStatic(
&FBlutilityContentBrowserExtensions::RegisterMenus));
}
注册到 ContentBrowser.AssetContextMenu 菜单的 CommonAssetActions Section 中,使用 AddDynamicEntry 添加动态菜单项(每次右键菜单弹出时重新计算)。
Level Editor 扩展(FBlutilityLevelEditorExtensions):
注册到 LevelEditor.ActorContextMenu 菜单的 ActorOptions Section 中,同样使用动态条目。
3.9.2 动态菜单生成的核心流程
以 Content Browser 扩展为例,每次右键菜单弹出时的处理流程:
Step 1: 收集所有 Blutility 蓝图
cpp
TArray<FAssetData> UtilAssets;
FBlutilityMenuExtensions::GetBlutilityClasses(UtilAssets,
UAssetActionUtility::StaticClass()->GetClassPathName());
GetBlutilityClasses() 内部做了几件事:
- 通过
AssetRegistry.GetDerivedClassNames()找到所有继承自UAssetActionUtility的类 - 用
FARFilter过滤出所有UEditorUtilityBlueprint资产 - 排除 Abstract 和 Deprecated 的蓝图(通过检查
CLASS_Abstract | CLASS_Deprecated标志) - 排除其他开发者的 Developer 文件夹下的 Blutility(只包含当前用户自己的)
- 如果开启了
bSearchGeneratedClassesForScriptedActions,还会扫描UBlueprintGeneratedClass资产
Step 2: 对每个蓝图进行类匹配过滤
cpp
auto ProcessAssetAction = [&](const TSharedRef<FAssetActionUtilityPrototype>& Prototype)
{
// 从 Asset Registry 标签中读取 SupportedClasses(不加载蓝图!)
TArray<TSoftClassPtr<UObject>> SupportedClassPtrs = Prototype->GetSupportedClasses();
const bool bIsActionForBlueprints = Prototype->AreSupportedClassesForBlueprints();
for (int32 SelectedAssetIndex = 0; SelectedAssetIndex < SelectedAssets.Num(); ++SelectedAssetIndex)
{
// 根据 bIsActionForBlueprints 决定匹配方式:
// - false: 用选中资产的类型匹配 SupportedClasses
// - true: 用选中蓝图的父类匹配 SupportedClasses
bool bPassesClassFilter = bIsActionForBlueprints
? Blueprint_ParentClass->IsChildOf(ClassPtr.Get())
: AssetClass->IsChildOf(ClassPtr.Get());
if (bPassesClassFilter)
{
UtilityAndSelectionIndices.FindOrAdd(Prototype).Add(Index);
}
}
};
注意这里的关键优化:Prototype->GetSupportedClasses() 是从 Asset Registry 标签中读取的(通过 FAssetActionUtilityPrototype),不需要加载蓝图本身。
Step 3: 构建菜单项
cpp
FBlutilityMenuExtensions::CreateAssetBlutilityActionsMenu(
InSection, UtilityAndSelectionIndices, SupportedAssets);
CreateBlutilityActionsMenu() 内部调用 ExtractFunctions() 从每个 Utility 的 FAssetActionUtilityPrototype 中提取函数列表,按 Category 分组,生成多级子菜单:
右键菜单
└─ Scripted Asset Actions
├─ [Category A 子菜单]
│ ├─ 函数1 (Icon)
│ └─ 函数2 (Icon)
├─ [Category B 子菜单]
│ └─ 函数3 (Icon)
└─ 函数4 (无分类) (Icon)
3.9.3 函数执行时的参数转发机制
当用户点击某个菜单函数时,执行流程如下:
- 创建临时对象 :
NewObject<UObject>(GetTransientPackage(), UtilClass)------ 不在 CDO 上执行,避免副作用 - 检查函数参数 :
- 如果函数有参数:
- 遍历参数列表,填充默认值
- 检查第一个参数是否匹配选中对象类型(
FAssetData或AActor*) - 如果第一个参数匹配 且 没有其他参数:直接转发选中对象,不弹对话框
- 如果还有额外参数:弹出
SFunctionParamDialog让用户填写参数
- 如果函数无参数:直接调用
- 如果函数有参数:
- 转发选中对象 (如果第一个参数匹配):对每个选中的资产/Actor,逐一传入并调用
ProcessEvent - Shift+Click 快捷操作:按住 Shift 点击菜单项,会直接跳转到蓝图中该函数的定义位置
cpp
// 关键代码片段
UObject* TempObject = NewObject<UObject>(GetTransientPackage(), UtilClass);
TempObject->AddToRoot(); // 防止 GC
if (bForwardUserSelection)
{
// 对每个选中对象,逐一传入第一个参数并执行
for (const int32& SelectionIndex : FunctionAndUtil.SelectionIndices)
{
FirstParamProperty->CopySingleValue(/*...*/);
TempObject->ProcessEvent(Function, FuncParams->GetStructMemory());
}
}
else
{
TempObject->ProcessEvent(Function, FuncParams->GetStructMemory());
}
TempObject->RemoveFromRoot();
3.9.4 SFunctionParamDialog --- 参数输入对话框
当函数有多个参数(且第一个参数不匹配选中对象类型)时,引擎会弹出 SFunctionParamDialog:
- 使用
IStructureDetailsView展示函数参数结构体 - 隐藏已自动转发给选中对象的第一个参数(
HiddenPropertyName机制) - 只显示标记为
CPF_Parm的属性 - 用户点击 OK 后执行函数,点击 Cancel 取消
这是一个纯 Slate 对话框,使用 FStructOnScope 承载运行时参数数据。
3.10 辅助类一览
| 类名 | 父类 | 说明 |
|---|---|---|
UEditorFunctionLibrary |
UBlueprintFunctionLibrary |
哨兵类(Sentinel Class),标记某个蓝图为"编辑器函数库",让其他 Blutility 蓝图可以调用其中的静态函数 |
AEditorUtilityActor |
AActor |
放在关卡中的编辑器 Actor,支持 bReceivesEditorInput 在编辑器中接收键盘/鼠标输入 |
UEditorUtilityActorComponent |
UActorComponent |
编辑器的 Actor Component(空壳,用于扩展) |
AEditorUtilityCamera |
ACameraActor |
编辑器专用 Camera Actor |
UEditorUtilityToolMenuEntry |
UToolMenuEntryScript |
可蓝图化的 ToolMenu 条目 |
UEditorUtilityToolMenuSection |
UToolMenuSectionDynamic |
可蓝图化的 ToolMenu 分节 |
UEditorUtilityBlueprintFactory |
UFactory |
内容浏览器"新建 Editor Utility 蓝图"的工厂 |
UEditorUtilityWidgetBlueprintFactory |
UFactory |
内容浏览器"新建 Editor Utility Widget 蓝图"的工厂 |
UAsyncEditorDelay |
UEditorUtilityBlueprintAsyncActionBase |
异步延迟节点 |
UAsyncEditorWaitForGameWorld |
UEditorUtilityBlueprintAsyncActionBase |
等待 PIE 世界的异步节点 |
UAsyncEditorOpenMapAndFocusActor |
UEditorUtilityBlueprintAsyncActionBase |
异步打开地图并聚焦到指定 Actor |
UAsyncCaptureScene |
UBlueprintAsyncActionBase |
异步场景截图 |
UAsyncImageExport |
UBlueprintAsyncActionBase |
异步图片导出 |
UToolMenuWidget |
UWidget |
可嵌入 Editor Utility Widget 中的工具菜单组件 |
UEditorUtilityWidgetProjectSettings |
UWidgetEditingProjectSettings |
项目设置中的 Blutility 配置页(权限列表等) |
Editor Utility 专有 UI 组件族: UEditorUtilityButton、UEditorUtilityCheckBox、UEditorUtilitySlider 等------这些类直接继承自 UMG 的对应组件,只改变默认样式为编辑器风格。它们被单独定义是为了避免在构造函数中条件式修改样式导致的 CDO 比较问题。
4 功能使用示例编写
示例 1:最简单的"一键执行"工具
创建一个继承自 UEditorUtilityObject 的蓝图,实现 Run 事件:
cpp
// 蓝图内逻辑(伪代码):
Event Run
{
// 获取 Content Browser 中当前选中的资产
Assets = GetSelectedAssets()
ForEach Asset in Assets:
if Asset is Texture2D:
修改压缩格式
保存资产
Log("处理完成")
}
双击这个蓝图,UEditorUtilitySubsystem::TryRun() 会自动创建实例并调用 Run()。
示例 2:把常规 Widget 蓝图转换为 Editor Utility Widget
有时我们可能已经有一个做好的 Widget 蓝图,想把它放进编辑器面板里。Blutility 提供了一个便捷方法:
cpp
UEditorUtilityLibrary::ConvertToEditorUtilityWidget(WidgetBP);
这个方法内部做的事情:
- 把原来的
UWidgetBlueprint重命名到瞬态包 - 创建一个新的
UEditorUtilityWidgetBlueprint - 把所有子对象(WidgetTree、生成的类等)迁移到新蓝图下
- 复制所有属性
- 更新所有引用
示例 3:带参数的资产批量操作
创建一个继承自 UAssetActionUtility 的蓝图:
蓝图类默认设置:
- SupportedClasses: [Texture2D]
- bIsActionForBlueprints: false
添加 CallInEditor 函数:
- BatchResizeTextures(int32 NewMaxSize)
{
// 由于第一个参数不是 FAssetData 也不是无参,
// 点击后会自动弹出参数对话框,让用户输入 NewMaxSize
// 通过 GetSelectedAssets() 手动获取选中资产
Assets = GetSelectedAssets()
ForEach Asset in Assets:
ResizeTexture(Asset, NewMaxSize)
}
如果函数的第一个参数是 FAssetData 类型:
- RenameTextures(FAssetData Asset, FString Prefix)
{
// 由于第一个参数是 FAssetData,
// 引擎会自动把每个选中的资产逐一传入 Asset 参数
// Prefix 通过参数对话框输入
NewName = Prefix + Asset.AssetName
RenameAsset(Asset.GetAsset(), NewName)
}
示例 4:创建 Task 做耗时操作
继承 UEditorUtilityTask:
蓝图事件实现:
Event ReceiveBeginExecution
{
SetTaskNotificationText("正在处理...")
// 批量处理
AllAssets = GetAllAssetsOfClass(StaticMesh)
TotalCount = AllAssets.Length
for i = 0 to TotalCount-1:
if WasCancelRequested():
return
SetTaskNotificationText("处理中 " + (i+1) + "/" + TotalCount)
ProcessMesh(AllAssets[i])
FinishExecutingTask()
}
Event ReceiveCancelRequested
{
// 清理临时状态
Log("用户取消了任务")
}
然后在控制台里输入 RunTask MyBatchMeshProcessor 即可启动,也可以在蓝图的其他地方调用 UEditorUtilitySubsystem::RegisterAndExecuteTask()。
示例 5:创建带 UI 的编辑器面板
继承 UEditorUtilityWidget 的蓝图,设计一个 UMG 界面,上面放几个按钮、一个 ListView。在蓝图中处理每个按钮的点击事件,调用 UEditorUtilityLibrary 里的静态函数来获取选中资产、操作 Content Browser 等。
将 Widget 蓝图的 bAutoRunDefaultAction 设为 false,然后设置一个有意义的 TabDisplayName。
拖入一个 ToolMenuWidget 到 UI 中,设置其 MenuName,然后在 Python 或另一个 Blutility 蓝图中通过 ToolMenus API 来动态添加菜单项。
5 在 UE5 编辑器中使用 Blutility 的完整流程
5.1 创建一个 Editor Utility Widget
- 在 Content Browser 中右键 → Editor Utilities → Editor Utility Widget
- 选择父类为
EditorUtilityWidget - 给蓝图起名,双击打开 UMG 编辑器
- 像普通 UMG Widget 一样设计 UI
- 在蓝图的 Event Graph 中写逻辑
- 右键蓝图 → Run Editor Utility Widget
- Widget 会出现在一个独立的 Dock Tab 中
此时 UEditorUtilitySubsystem 自动做了这些事:
- 为这个蓝图注册了一个
FTabSpawner - 生成 Tab,把 UMG Widget 放进去
- 标记 Widget 为瞬态(避免污染关卡文件)
- 把蓝图路径保存到
LoadedUIs配置中
5.2 创建一个 Asset Action Utility
- 在 Content Browser 中右键 → Editor Utilities → Editor Utility Blueprint
- 选择父类为
AssetActionUtility - 在 Class Defaults 中设置
SupportedClasses(例如[Texture2D]) - 添加一个函数,勾选
CallInEditor,确保没有返回值 - 编译保存
- 在 Content Browser 中选中一张纹理,右键 → Scripted Asset Actions → 你的函数名
5.3 配置自启动工具
在项目的 EditorPerProjectUserSettings.ini 中(或者通过 UEditorUtilitySubsystem 的配置):
ini
[/Script/Blutility.EditorUtilitySubsystem]
+StartupObjects=/Game/MyTools/StartupCheck.StartupCheck
这个工具会在编辑器主框架创建完成后自动执行一次。
5.4 用 Python 调用
python
import unreal
# 获取子系统
subsystem = unreal.get_editor_subsystem(unreal.EditorUtilitySubsystem)
# 生成一个 Editor Utility Widget
widget_bp = unreal.EditorAssetLibrary.load_blueprint_class("/Game/MyTools/MyPanel")
tab_id = unreal.Name()
widget = subsystem.spawn_and_register_tab_and_get_id(widget_bp, tab_id)
# 通过 ID 查找或关闭
subsystem.close_tab_by_id(tab_id)
6 总结与最佳实践
6.1 核心要点
-
Blutility 是编辑器基础设施,不需要额外插件。它是 UE5 编辑器原生的一部分,任何项目开箱即用。
-
基类选择决定了工具的呈现方式:
UEditorUtilityObject→ 双击执行,最轻量UEditorUtilityWidget→ 独立面板,适合复杂 UIUAssetActionUtility/UActorActionUtility→ 右键菜单,适合批量操作UEditorUtilityTask→ 后台任务,适合耗时操作
-
CallInEditor + 函数签名 = 自动菜单 :这个设计非常精妙------只要在蓝图函数上勾选
CallInEditor,引擎会在编译时自动把函数信息序列化到 Asset Registry 标签中,右键菜单触发时直接读取缓存,完全不加载蓝图。 -
FAssetActionUtilityPrototype的缓存设计值得学习:在自己做编辑器工具时,如果需要在菜单弹出时决定显示哪些项、匹配哪些条件,把关键信息打入 Asset Registry 标签是一个高性价比的优化方案。 -
Task 的
GIsRunningUnattendedScript处理:在做编辑器自动化时,记得参考这个机制------它用全局标志位静默掉了大部分可能阻塞的对话框,任务结束后恢复。 -
Transient 标记防止关卡污染 :
MarkTransientRecursive()确保 Editor Utility Widget 的属性变更不会把编辑器世界标记为 Dirty。
6.2 最佳实践
-
用
UEditorFunctionLibrary做公共逻辑 :如果在多个 Blutility 蓝图中需要复用同一段逻辑,抽象到一个UEditorFunctionLibrary派生蓝图中,其他蓝图直接调用其静态函数。 -
为 AssetActionUtility 设置合适的
SupportedClasses:如果不设置(留空),右键任何资产都会显示这个工具的所有函数,既不美观也容易误操作。 -
利用
SupportedConditions做细粒度过滤 :例如,可以配置bShowInMenuIfFilterFails = true,让不满足条件的资产也能看到菜单项(灰显),并显示自定义的失败原因文字,给用户一个清晰的反馈。 -
Widget 蓝图的
TabDisplayName要有意义 :这个字段不只是显示在 Tab 标题上------它同时被标记为AssetRegistrySearchable,意味着在 Content Browser 搜索框中可以按这个名称搜索到你的工具。 -
bAutoRunDefaultAction的两种模式:true:双击直接执行 Run(),适合"只有一个行为"的工具false:双击打开属性面板,适合"多个功能按钮"的复杂 Widget(推荐)
-
Task 中记得适时调用
WasCancelRequested():如果任务体量较大,在循环中适当位置检查取消标志,避免用户点了取消却要等很久才停下来。 -
用
UEditorUtilitySubsystem::OnBeginPIE/OnEndPIE做 PIE 联动:例如在 PIE 开始时自动打开某个调试面板,在 PIE 结束时自动清理临时数据。
6.3 潜在改进点
参照 AsyncMixin 和 CommonGame 的分析方式,Blutility 在设计上也存在一些可以进一步优化的空间:
-
Task 进度回调不够细粒度 :目前
UEditorUtilityTask只提供了SetTaskNotificationText()来更新进度文字,但没有提供数值型进度(0-100%)的回调接口。开发者如果需要显示精确的进度条,需要自己额外实现。 -
菜单函数签名限制较严格 :只有标记了
CallInEditor的、在UEditorUtilityBlueprint派生类中定义的、无返回值的函数才会被收集。如果一个函数通过接口继承获得,或者带有BlueprintPure等标记,它不会被自动识别为菜单项。 -
SupportedClasses的运行时扩展能力 :当前SupportedClasses定义在 CDO 上作为编译期常量,在运行时无法动态修改。如果需要基于运行时条件(如项目配置、插件状态)动态调整支持的资产类,需要重新设计方案。 -
Widget 的 PIE 状态管理 :Editor Utility Widget 通过
bIsEnabledInPIE控制 PIE 期间是否可用,但当地图切换或 PIE 重启时,Widget 会被销毁重建。对于需要跨 PIE Session 保持状态的工具面板,目前没有方便的持久化机制。 -
Tab 布局持久化依赖配置文件 :
LoadedUIs通过SaveConfig()保存到配置文件中,但这里保存的是蓝图路径而非布局信息(如停靠位置、大小)。Tab 的布局状态由 FTabManager 负责,在某些情况下恢复的效果可能不如预期。 -
菜单项的性能边界 :虽然
FAssetActionUtilityPrototype的标签缓存方案已经大幅优化了性能,但当项目中存在大量 Blutility 蓝图(例如 50+)且每个都有多个CallInEditor函数时,标签解析和 JSON 反序列化也会产生可测量的开销。 -
ActorActionUtility 缺乏
SupportedConditions:对比UAssetActionUtility,UActorActionUtility只提供了SupportedClasses过滤,缺少类似FAssetActionSupportCondition的细粒度过滤机制。无法通过 Content Browser 筛选表达式来进一步限制 Actor 的菜单项显示。
6.4 常见问题
问题 1:我的 AssetActionUtility 右键菜单不显示
- 检查
SupportedClasses是否设置正确 - 检查蓝图类是否标记为 Abstract 或 Deprecated(这些会被排除)
- 检查蓝图是否在 Developer 文件夹的其他开发者目录下(非自己的 Developer 目录会被排除)
- 尝试重新编译蓝图(Asset Registry 标签在编译时更新)
问题 2:Editor Utility Widget 在地图切换后内容消失
- 这是预期行为------地图切换时旧的 World 被 TearDown,Widget 需要重建
- 引擎会在
ChangeTabWorld中自动处理重建
问题 3:Task 运行中弹出了模态对话框卡住编辑器
GIsRunningUnattendedScript会被设为 true,但并不是所有编辑器子系统都检查这个标志- 确保在 Task 的
BeginExecution中避免可能弹出对话框的操作
问题 4:想从 C++ 中调用 Blutility 的某个函数
- 通过
UEditorUtilitySubsystem的FindUtilityWidgetFromBlueprint()获取 Widget 实例,然后直接调用其 C++ 或蓝图接口 - 也可以用
RegisterAndExecuteTask()启动一个 Task
写在最后
Blutility 的代码量虽然不小,但结构非常清晰。它本质上是一个将蓝图能力注入编辑器菜单系统的桥梁------上层提供简单的蓝图基类,中间靠 Asset Registry 标签做缓存优化,下层用 ToolMenus / TabManager 完成注册和展示。理解了这个三层结构,不管是扩展 Blutility 还是模仿它做自己的编辑器工具框架,都能少走很多弯路。
写这篇文章的过程,其实也是我重新把 Blutility 源码从头到尾捋顺的过程。有几个点让我印象特别深刻:
-
OverrideBPTypeForClass的巧妙之处:它用一行代码解决了"让普通 UObject 子类蓝图被识别为特定蓝图类型"的问题,而不是去修改引擎的蓝图类型判断逻辑。这种"在编译阶段做映射"的思路,比"在运行时做类型判断"要优雅得多。 -
FAssetActionUtilityPrototype的缓存策略:右键菜单弹出时,用户期望的是瞬时响应。如果每次弹出都要加载十几个蓝图再做类型匹配,体验会非常差。把关键信息打入 Asset Registry 标签,是一个教科书级别的"以空间换时间"优化。 -
MarkTransientRecursive的防御性设计 :Editor Utility Widget 的属性修改不应该污染关卡文件。这个看似简单的RF_Transient标记,背后是对编辑器工作流的深刻理解------用户不会希望因为打开了一个工具面板,编辑器就提示"有未保存的更改"。
如果你正在考虑为自己的团队搭建编辑器工具链,我建议从 Blutility 入手,而不是上来就写 C++ 插件。它的学习曲线平缓,迭代速度极快,而且团队里所有人都能参与进来。等蓝图方案满足不了性能或功能需求时,再考虑用 C++ 扩展 Blutility 的基类,也不失为一个渐进式的路径。
附录
A. 文件结构参考
Blutility/
├── Blutility.Build.cs
├── Classes/
│ ├── ActorActionUtility.h
│ ├── AssetActionUtility.h
│ ├── EditorUtilityActor.h
│ ├── EditorUtilityActorComponent.h
│ ├── EditorUtilityBlueprint.h
│ ├── EditorUtilityBlueprintFactory.h
│ ├── EditorUtilityCamera.h
│ ├── EditorUtilityObject.h
│ ├── EditorUtilitySubsystem.h
│ ├── EditorUtilityTask.h
│ ├── EditorUtilityToolMenu.h
│ ├── EditorUtilityWidget.h
│ ├── EditorUtilityWidgetBlueprint.h
│ ├── EditorUtilityWidgetBlueprintFactory.h
│ ├── EditorUtilityWidgetComponents.h
│ ├── EditorUtilityWidgetProjectSettings.h
│ ├── GlobalEditorUtilityBase.h
│ └── PlacedEditorUtilityBase.h
├── Private/
│ ├── ActorActionUtility.cpp
│ ├── AssetActionUtility.cpp
│ ├── BlutilityContentBrowserExtensions.cpp
│ ├── BlutilityContentBrowserExtensions.h
│ ├── BlutilityLevelEditorExtensions.cpp
│ ├── BlutilityLevelEditorExtensions.h
│ ├── BlutilityMenuExtensions.cpp
│ ├── BlutilityMenuExtensions.h
│ ├── BlutilityModule.cpp
│ ├── BlutilityShelf.cpp
│ ├── BlutilityUMGEditorExtensions.cpp
│ ├── BlutilityUMGEditorExtensions.h
│ ├── EditorUtilityActor.cpp
│ ├── EditorUtilityActorComponent.cpp
│ ├── EditorUtilityBlueprint.cpp
│ ├── EditorUtilityBlueprintFactory.cpp
│ ├── EditorUtilityCamera.cpp
│ ├── EditorUtilityCommon.cpp
│ ├── EditorUtilityCommon.h
│ ├── EditorUtilityObject.cpp
│ ├── EditorUtilitySubsystem.cpp
│ ├── EditorUtilityTask.cpp
│ ├── EditorUtilityWidget.cpp
│ ├── EditorUtilityWidgetBlueprint.cpp
│ ├── EditorUtilityWidgetBlueprintFactory.cpp
│ └── EditorUtilityWidgetProjectSettings.cpp
└── Public/
├── BlutilityModule.h
└── IBlutilityModule.h
B. 相关资源
- UE5 官方文档:Editor Utility Widgets
- UE5 官方文档:Scripted Asset Actions
- UE5 官方文档:Editor Subsystems
- UE5 官方文档:Tool Menus
- UE5 官方文档:Asset Registry
- AsyncMixin 插件分析
- CommonGame 插件分析