UE5源码分析之Editor——Blutility模块全面分析

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 这个插件。它提供了一些编辑器操作的蓝图函数库(比如 RenameAssetGetSelectedAssets 等)。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 把它们有机地组合在了一起,形成了一个完整的编辑器工具开发框架。


目录

  1. 模块概述
  2. 模块整体架构解析
  3. 类级代码注释详解
  4. 功能使用示例编写
  5. [在 UE5 编辑器中使用 Blutility 的完整流程](#在 UE5 编辑器中使用 Blutility 的完整流程)
  6. 总结与最佳实践
  7. 写在最后
  8. 附录

### 文章目录

  • [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/ 目录下的 基础设施层(负责把蓝图注册到编辑器的各个菜单、工具栏中)

核心依赖的模块链条相当长------EditorSubsystemMainFrameUMGToolMenusContentBrowserUnrealEd 等等,几乎覆盖了编辑器开发的半壁江山。这也意味着从 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 负责"执行"------管理运行实例、任务调度和生命周期。 两者的接口是 IBlutilityModuleUEditorUtilitySubsystem 之间的互相调用。

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() 关键流程:

  1. 创建 SDockTab(如果 bSpawnAsNomadTab 设为 Nomad Tab)
  2. 调用 CreateUtilityWidget() 创建 UMG 内容
  3. 绑定 Tab 关闭委托 → UpdateRespawnListIfNeeded() 更新持久化列表
  4. 绑定 OnCompiled()RegenerateCreatedTab() 重编译时刷新内容
  5. 绑定地图变更 → 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 函数执行时的参数转发机制

当用户点击某个菜单函数时,执行流程如下:

  1. 创建临时对象NewObject<UObject>(GetTransientPackage(), UtilClass) ------ 不在 CDO 上执行,避免副作用
  2. 检查函数参数
    • 如果函数有参数:
      • 遍历参数列表,填充默认值
      • 检查第一个参数是否匹配选中对象类型(FAssetDataAActor*
      • 如果第一个参数匹配 没有其他参数:直接转发选中对象,不弹对话框
      • 如果还有额外参数:弹出 SFunctionParamDialog 让用户填写参数
    • 如果函数无参数:直接调用
  3. 转发选中对象 (如果第一个参数匹配):对每个选中的资产/Actor,逐一传入并调用 ProcessEvent
  4. 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 组件族: UEditorUtilityButtonUEditorUtilityCheckBoxUEditorUtilitySlider 等------这些类直接继承自 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);

这个方法内部做的事情:

  1. 把原来的 UWidgetBlueprint 重命名到瞬态包
  2. 创建一个新的 UEditorUtilityWidgetBlueprint
  3. 把所有子对象(WidgetTree、生成的类等)迁移到新蓝图下
  4. 复制所有属性
  5. 更新所有引用

示例 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

  1. 在 Content Browser 中右键 → Editor Utilities → Editor Utility Widget
  2. 选择父类为 EditorUtilityWidget
  3. 给蓝图起名,双击打开 UMG 编辑器
  4. 像普通 UMG Widget 一样设计 UI
  5. 在蓝图的 Event Graph 中写逻辑
  6. 右键蓝图 → Run Editor Utility Widget
  7. Widget 会出现在一个独立的 Dock Tab 中

此时 UEditorUtilitySubsystem 自动做了这些事:

  • 为这个蓝图注册了一个 FTabSpawner
  • 生成 Tab,把 UMG Widget 放进去
  • 标记 Widget 为瞬态(避免污染关卡文件)
  • 把蓝图路径保存到 LoadedUIs 配置中

5.2 创建一个 Asset Action Utility

  1. 在 Content Browser 中右键 → Editor Utilities → Editor Utility Blueprint
  2. 选择父类为 AssetActionUtility
  3. 在 Class Defaults 中设置 SupportedClasses(例如 [Texture2D]
  4. 添加一个函数,勾选 CallInEditor,确保没有返回值
  5. 编译保存
  6. 在 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 核心要点

  1. Blutility 是编辑器基础设施,不需要额外插件。它是 UE5 编辑器原生的一部分,任何项目开箱即用。

  2. 基类选择决定了工具的呈现方式

    • UEditorUtilityObject → 双击执行,最轻量
    • UEditorUtilityWidget → 独立面板,适合复杂 UI
    • UAssetActionUtility / UActorActionUtility → 右键菜单,适合批量操作
    • UEditorUtilityTask → 后台任务,适合耗时操作
  3. CallInEditor + 函数签名 = 自动菜单 :这个设计非常精妙------只要在蓝图函数上勾选 CallInEditor,引擎会在编译时自动把函数信息序列化到 Asset Registry 标签中,右键菜单触发时直接读取缓存,完全不加载蓝图。

  4. FAssetActionUtilityPrototype 的缓存设计值得学习:在自己做编辑器工具时,如果需要在菜单弹出时决定显示哪些项、匹配哪些条件,把关键信息打入 Asset Registry 标签是一个高性价比的优化方案。

  5. Task 的 GIsRunningUnattendedScript 处理:在做编辑器自动化时,记得参考这个机制------它用全局标志位静默掉了大部分可能阻塞的对话框,任务结束后恢复。

  6. Transient 标记防止关卡污染MarkTransientRecursive() 确保 Editor Utility Widget 的属性变更不会把编辑器世界标记为 Dirty。

6.2 最佳实践

  1. UEditorFunctionLibrary 做公共逻辑 :如果在多个 Blutility 蓝图中需要复用同一段逻辑,抽象到一个 UEditorFunctionLibrary 派生蓝图中,其他蓝图直接调用其静态函数。

  2. 为 AssetActionUtility 设置合适的 SupportedClasses:如果不设置(留空),右键任何资产都会显示这个工具的所有函数,既不美观也容易误操作。

  3. 利用 SupportedConditions 做细粒度过滤 :例如,可以配置 bShowInMenuIfFilterFails = true,让不满足条件的资产也能看到菜单项(灰显),并显示自定义的失败原因文字,给用户一个清晰的反馈。

  4. Widget 蓝图的 TabDisplayName 要有意义 :这个字段不只是显示在 Tab 标题上------它同时被标记为 AssetRegistrySearchable,意味着在 Content Browser 搜索框中可以按这个名称搜索到你的工具。

  5. bAutoRunDefaultAction 的两种模式

    • true:双击直接执行 Run(),适合"只有一个行为"的工具
    • false:双击打开属性面板,适合"多个功能按钮"的复杂 Widget(推荐)
  6. Task 中记得适时调用 WasCancelRequested():如果任务体量较大,在循环中适当位置检查取消标志,避免用户点了取消却要等很久才停下来。

  7. UEditorUtilitySubsystem::OnBeginPIE / OnEndPIE 做 PIE 联动:例如在 PIE 开始时自动打开某个调试面板,在 PIE 结束时自动清理临时数据。

6.3 潜在改进点

参照 AsyncMixin 和 CommonGame 的分析方式,Blutility 在设计上也存在一些可以进一步优化的空间:

  1. Task 进度回调不够细粒度 :目前 UEditorUtilityTask 只提供了 SetTaskNotificationText() 来更新进度文字,但没有提供数值型进度(0-100%)的回调接口。开发者如果需要显示精确的进度条,需要自己额外实现。

  2. 菜单函数签名限制较严格 :只有标记了 CallInEditor 的、在 UEditorUtilityBlueprint 派生类中定义的、无返回值的函数才会被收集。如果一个函数通过接口继承获得,或者带有 BlueprintPure 等标记,它不会被自动识别为菜单项。

  3. SupportedClasses 的运行时扩展能力 :当前 SupportedClasses 定义在 CDO 上作为编译期常量,在运行时无法动态修改。如果需要基于运行时条件(如项目配置、插件状态)动态调整支持的资产类,需要重新设计方案。

  4. Widget 的 PIE 状态管理 :Editor Utility Widget 通过 bIsEnabledInPIE 控制 PIE 期间是否可用,但当地图切换或 PIE 重启时,Widget 会被销毁重建。对于需要跨 PIE Session 保持状态的工具面板,目前没有方便的持久化机制。

  5. Tab 布局持久化依赖配置文件LoadedUIs 通过 SaveConfig() 保存到配置文件中,但这里保存的是蓝图路径而非布局信息(如停靠位置、大小)。Tab 的布局状态由 FTabManager 负责,在某些情况下恢复的效果可能不如预期。

  6. 菜单项的性能边界 :虽然 FAssetActionUtilityPrototype 的标签缓存方案已经大幅优化了性能,但当项目中存在大量 Blutility 蓝图(例如 50+)且每个都有多个 CallInEditor 函数时,标签解析和 JSON 反序列化也会产生可测量的开销。

  7. ActorActionUtility 缺乏 SupportedConditions :对比 UAssetActionUtilityUActorActionUtility 只提供了 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 的某个函数

  • 通过 UEditorUtilitySubsystemFindUtilityWidgetFromBlueprint() 获取 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 插件分析
相关推荐
日月云棠3 小时前
UE5源码分析之Editor——BehaviorTreeEditor模块全面分析
ue5
远离UE41 天前
UE5 显存 虚拟内存 深入学习笔记
笔记·学习·ue5
日月云棠1 天前
UE5源码分析之Editor——AnimationSettings模块全面分析
ue5
远离UE42 天前
UE5 SF_VertexShader 如何使用
ue5
电子云与长程纠缠2 天前
UE5 Lyra PocketWorld进行3D内容UI预览 - 上
开发语言·学习·3d·ue5·游戏引擎
电子云与长程纠缠2 天前
UE5 Lyra PocketWorld进行3D内容UI预览 - 下
开发语言·学习·游戏·ui·ue5
远离UE43 天前
UE5 GPU内部异步
ue5
1204157137 肖哥3 天前
UE5 PCG PCGBlueprintElement简介
ue5
我救我自己3 天前
UE5分层渲染
ue5