UE5.8 Editor Utility Widgets 编辑器工具

📑 目录

UE5.8 Editor Utility Widgets 编辑器工具

一个混乱的周五晚上

周五晚上八点,关卡美术小李还在工位上对着 Content Browser 发呆。上周导入了三百多个道具模型,命名五花八门:有的叫 Box_01,有的叫 SM_Box_01,还有的叫 Box_01_Final_Final。策划要他在今晚之前把所有静态网格体统一改成 SM_道具名_序号 的格式,同时给每个模型设置好 LOD 距离,并且检查哪些材质没有指定贴图。

小李一开始打算手动改。改了二十个之后,他意识到按这个速度要改到后半夜。他想起隔壁技术美术老王说过,UE5 有一种只在编辑器里运行的 UI 工具,可以批量操作资产,于是抱着试试看的心态敲开了老王的门。老王看了两眼需求,说:做个 Editor Utility Widget,半小时搞定。

这篇文章就从小李那个晚上学到的东西讲起,把 Editor Utility Widget 在 UE5.8 里的用法完整拆开。内容会覆盖基础概念、编辑器蓝图与运行时蓝图的本质区别、对选中资产的操作、批量处理、编辑器菜单和工具栏扩展、自定义窗口面板,以及与 Python 工具的配合。看完应该能做出一套像样的小型编辑器插件。

Editor Utility Widget 基础

Editor Utility Widget 是 UE4.22 之后引入的一套编辑器扩展机制,到 UE5.8 已经相当成熟。它的本质是一个继承自 UEditorUtilityWidget 的 UserWidget 变体,只能在编辑器状态下使用,不能在游戏中显示。你可以把它理解成给内容创作者做的内部工具界面。

在 Content Browser 里右键,选择 Editor Utilities > Editor Utility Widget,就能创建一个新资产。打开它后看到的界面和 Widget Blueprint 几乎一样:左侧是 Palette,中间是 Designer,右侧是 Details,下方是 Graph。但注意右上角的模式切换里只有 Designer 和 Graph,没有 Class Defaults 里那些运行时相关的选项。

Editor Utility Widget 的生命周期由编辑器本身管理。点击窗口右上角的 Run 按钮,会实例化一个窗口;关闭窗口时,Widget 会被销毁。如果勾选了 Run on Startup,每次打开项目时它都会自动弹出来。

下面这张图展示了 Editor Utility Widget 在整个编辑器架构中的位置。

flowchart TD
    subgraph editor ["编辑器层"]
    end
    subgraph slate ["Slate 框架"]
    end
    subgraph asset ["资产系统"]
        eu["Editor Utility Widget"]
        eub["Editor Utility Blueprint"]
        ed["编辑器事件"]
        sw["Slate Widget"]
        wb["Widget Blueprint"]
        cb["Content Browser"]
        ar["Asset Registry"]
        fa["资产工厂"]
    end
    eu --> eub
    eu --> ed
    eu --> sw
    sw --> wb
    eub --> cb
    cb --> ar
    cb --> fa

图中 Editor Utility Widget 通过 Slate 渲染界面,通过 Editor Utility Blueprint 执行编辑器逻辑,再通过 Content Browser 和 Asset Registry 读写资产。UE5.8 在这几层之间做了更多暴露,比如可以直接在蓝图里调用 Asset Registry 的查询节点,也可以监听 Content Browser 的选中变化。

和运行时 UMG 最大的不同在于:Editor Utility Widget 里拿不到 PlayerController、Pawn、GameMode 这些对象。它运行在世界尚未播放的状态下,因此能访问的是 GEngineGEditorFAssetRegistryModuleUAssetToolsSubsystem 这些编辑器级别的 API。

编辑器蓝图与运行时蓝图的区别

很多初学者第一次接触 Editor Utility Widget 时会困惑:这个蓝图标着 Widget,怎么连 Event BeginPlay 都没有?原因很简单,它不是为游戏准备的。

运行时蓝图的生命周期由 World 驱动。Actor 有 BeginPlay、Tick、EndPlay;Widget 有 Construct、Tick、Destruct。这些事件都发生在 PIE 或 Standalone 模式下,依赖一个正在运行的游戏世界。

编辑器蓝图的生命周期由编辑器本身驱动。Editor Utility Widget 能响应的是 ConstructDestructOn Editor Utility StartedOn Editor Utility Closed 这类事件。它的 Tick 由 Slate 提供,节点叫 On Tick,但默认不开启。

功能上,运行时蓝图可以调用 Get Player ControllerGet All Actors of ClassSpawn Actor from Class。编辑器蓝图调用这些节点会报错,因为根本没有运行的世界。反过来,编辑器蓝图可以调用 Get Selected AssetsGet Selected Level ActorsRename AssetSet LOD Distance。运行时蓝图想做这些就只能通过 C++ 的编辑器模块或者编辑器脚本。

UE5.8 在蓝图节点分类上做了更清晰的划分。编辑器相关节点集中在 Editor Scripting > UtilitiesEditor Utility 分类下,节点图标上带有一个小扳手标志,提醒你这东西只能在编辑器里用。如果你在运行时蓝图里连了一个编辑器节点,编译时会有明确的红色波浪线提示。

两者可访问的子系统也不一样。编辑器蓝图常用的是:

  • UEditorAssetLibrary
  • UAssetToolsSubsystem
  • UContentBrowserAssetContextMenuContext
  • FLevelEditorModule

运行时蓝图常用的是:

  • UGameplayStatics
  • UKismetSystemLibrary
  • UWidgetBlueprintLibrary

下面这张流程图对比了运行时 Widget 和 Editor Utility Widget 的创建链路。

flowchart TD
    A["右键创建 Widget"] --> B{用途}
    B -->|"游戏内 HUD/菜单"| C["选择 Widget Blueprint"]
    B -->|"编辑器内部工具"| D["选择 Editor Utility Widget"]

    C --> E["运行时 World"]
    C --> F["PlayerController / GameMode"]
    C --> G["Add to Viewport"]

    D --> H["编辑器 Slate"]
    D --> I["Editor Utility Subsystem"]
    D --> J["Run / 窗口扩展"]

    E --> K["Event BeginPlay"]
    H --> L["On Editor Utility Started"]

小李听完这部分,才明白为什么自己第一次做工具时,试图在 Editor Utility Widget 里调用 Get Player Character 会返回 None。不是 Bug,是使用了错误的生命周期模型。

操作选中资产

Content Browser 和视口中的选中状态是编辑器工具最常见的入口。Editor Utility Widget 里有两个常用节点:

  • Get Selected Assets:返回 Content Browser 里当前选中的所有资产引用。
  • Get Selected Level Actors:返回当前关卡视口中选中的所有 Actor。

这两个节点都返回数组,可以用 For Each Loop 逐个处理。UE5.8 在这两个节点上增加了 Include Class Filter 的可选引脚,可以直接过滤出某种类型的资产,减少后面 Cast 的麻烦。

小李的第一个需求是批量重命名。老王教他做一个简单的工具:一个 Text Box 输入前缀,一个 Button 点击执行。点击后读取 Content Browser 的选中资产,把每个资产的名字改成 前缀_原名称_序号

sequenceDiagram
    participant User as "用户"
    participant Widget as "Editor Utility Widget"
    participant CB as "Content Browser"
    participant AR as "Asset Registry"
    participant Disk as "磁盘资产"

    User->>Widget: 输入前缀并点击重命名
    Widget->>CB: Get Selected Assets
    CB->>AR: 查询选中引用
    AR-->>Widget: 返回资产数组
    loop 遍历每个资产
        Widget->>Disk: Rename Asset
        Disk-->>Widget: 返回结果
    end
    Widget->>User: 弹出完成提示

上面这个序列图展示了批量重命名的完整流程。用户在 UI 里触发操作,Widget 向 Content Browser 要选中列表,Content Browser 通过 Asset Registry 解析引用,Widget 拿到数组后逐个调用 Rename Asset,最后给用户一个反馈。

蓝图里大致是这样连接的:

UFUNCTION(BlueprintCallable, Category = "Editor Utility")
void BatchRenameSelectedAssets(const FString& Prefix);

具体实现可以用 Editor Asset Library 的 Rename Asset 节点。注意 UE5.8 中这个节点被标记为 Experimental,实际项目里建议先用一个测试目录验证,避免把正式资产改坏。老王的习惯是:凡是会修改资产的工具,第一步先弹一个确认对话框,第二步把操作记录写到日志文件里。

操作选中 Actor 的逻辑也类似。关卡里选了二十盏灯,想让它们的亮度都变成 5000,可以在 Editor Utility Widget 里调用 Get Selected Level Actors,然后 Cast 成 PointLight 类型,修改 Intensity 属性。这种需求做建筑可视化项目时很常见。

批量处理工具

重命名只是开始。小李的第二个需求是给所有静态网格体设置 LOD 距离。UE5.8 的 Static Mesh Editor 支持直接在蓝图里改 LOD 参数,这对批量处理非常友好。

批量设置 LOD 的思路是:选中一组 Static Mesh 资产,读取每个资产的 LODGroupLODNumber,然后按规则设置 Screen Size 阈值。LOD 切换的数学依据是屏幕占比。一个模型在屏幕上占的像素面积 AscreenA_{screen} 与它的世界空间包围盒面积 AworldA_{world}、摄像机距离 dd 和视场角 FF 有关,近似关系可以写成:

AscreenAworldkd2tan2(F/2)A_{screen} \approx A_{world} \cdot \frac{k}{d^2 \cdot \tan^2(F/2)}

其中 kk 是投影常数,dd 是模型中心到摄像机的距离,FF 是水平视场角。当 AscreenA_{screen} 低于某个阈值时,就应该切换到更简化的 LOD。Editor Utility Widget 可以批量把 Static Mesh 的 LOD Screen Size 按这个思路设置成几个固定档位,比如 1.0、0.5、0.25、0.1。

第三个需求是检查材质是否缺少贴图。做法是遍历选中材质,逐个查看它的 Texture Sample 节点。如果某个 Texture Sample 的 Texture 属性为空,就记录下来。UE5.8 在 Material Editing 分类下新增了若干节点,可以直接读取材质图中的节点列表,不需要打开材质编辑器手动看。

下面这张图展示了批量检查工具的工作流程。

graph LR
    A["选中资产"] --> B{资产类型}
    B -->|"Static Mesh"| C["设置 LOD"]
    B -->|Material| D["检查贴图"]
    B -->|Texture| E["检查尺寸格式"]
    C --> F["写入资产"]
    D --> G["生成缺失报告"]
    E --> H["标记异常资产"]
    F --> I["输出日志"]
    G --> I
    H --> I

老王把这个工具做成了一个 Tab:左边是选中资产列表,中间是按类型分类的处理按钮,右边是结果输出区。处理完成后,结果会写到一个 CSV 文件里,方便第二天开会 review。

批量处理工具最重要的不是功能多炫,而是安全可靠。老王给小李定了三条规矩:

  • 任何会修改资产的操作必须先备份,或者用版本控制提交后再跑。
  • 处理前显示将要影响的资产数量,让用户二次确认。
  • 处理失败时给出具体原因,比如哪个资产被占用、哪个节点类型不支持。

编辑器菜单与工具栏扩展

Editor Utility Widget 默认通过右键资产选择 Run Editor Utility Widget 打开,或者双击资产后点 Run 按钮。但如果每次都要去 Content Browser 里找这个资产,效率也不高。UE5.8 允许把 Editor Utility Widget 注册到编辑器菜单和工具栏上。

打开 Editor Utility Widget 资产,在 Details 面板里找到 Editor Utility Extension 分组。这里可以配置:

  • Menu:把工具挂到某个菜单下,比如 Tools > MyTools
  • Toolbar:把工具挂到关卡编辑器工具栏上。
  • Tab:把工具作为一个可停靠的 Tab 打开。
  • Mode:把工具注册到编辑器模式面板里。

配置好后,不需要重启编辑器,保存资产即可生效。UE5.8 在这一点上的改进是热重载更稳定,5.4 之前经常要重启编辑器才能看到新菜单项。

老王把小李的批量重命名工具挂到了 Tools > TA Tools > 批量重命名 菜单下。小李只要选中资产,点一下菜单,工具窗口就会弹出来。后来他又给工具加了一个快捷键,绑定到 Ctrl + Shift + R,操作更快。

工具栏扩展适合放一些高频小功能。比如一个 一键对齐选中 Actor 到地面 的按钮,或者一个 刷新关卡中所有 Lightmass 重要体积 的按钮。按钮图标可以用 Slate Brush 指定,UE5.8 支持从资产直接引用一个 40x40 的 PNG 或 SVG。

下面这张图展示了 Editor Utility Widget 与编辑器菜单、工具栏扩展的关系。

flowchart TD
    subgraph ext ["编辑器扩展点"]
    end
    subgraph tool ["自定义工具"]
        menu["主菜单"]
        toolbar["工具栏"]
        tab["Tab 面板"]
        mode["模式面板"]
        euw["Editor Utility Widget"]
        eup["Editor Utility Blueprint"]
    end
    menu --> eup
    toolbar --> eup
    tab --> eup
    mode --> eup
    eup --> euw

图中的 Editor Utility Blueprint 是配置扩展点的核心。一个 Editor Utility Widget 资产可以同时注册到多个扩展点,但同一个扩展点最好只挂一个入口,避免菜单过于拥挤。

Editor Utility Blueprint

Editor Utility Blueprint 和 Editor Utility Widget 经常一起出现,但职责不同。Widget 负责界面,Blueprint 负责没有界面的编辑器逻辑。

在 Content Browser 里右键选择 Editor Utilities > Editor Utility Blueprint,会创建一个继承自 UEditorUtilityBlueprint 的蓝图类。它不能显示 UI,但可以被执行,也支持注册到菜单和工具栏。适合做纯后台批处理任务,比如打开项目时自动检查一遍命名规范,或者每天导出一份资产统计报告。

Editor Utility Blueprint 的执行入口是 Run 函数。你可以在蓝图里实现一个 Event Run,然后通过菜单或命令触发它。UE5.8 给 Editor Utility Blueprint 增加了异步执行支持,长时间任务不会卡住编辑器主线程。

小李用 Editor Utility Blueprint 做了一个 项目规范检查器。每天早上打开项目后自动运行,检查内容:

  • 是否有 Static Mesh 没有生成碰撞体。
  • 是否有 Texture 尺寸不是 2 的幂。
  • 是否有 Material Instance 的父材质丢失。
  • 是否有蓝图变量的命名不符合团队规范。

检查结果输出到 Output Log 和一个 Markdown 报告文件里。这个工具没有界面,所以用 Editor Utility Blueprint 比 Widget 更合适。

flowchart TD
    A["项目启动"] --> B{是否启用自动检查}
    B -->|"是"| C["Editor Utility Blueprint 自动运行"]
    B -->|"否"| D["手动从菜单触发"]
    C --> E["读取项目资产列表"]
    D --> E
    E --> F["逐项检查规则"]
    F --> G["生成 Markdown 报告"]
    G --> H["写入磁盘并输出日志"]

这个流程图展示了 Editor Utility Blueprint 在项目启动时的自动检查链路。是否启用可以在项目设置里配置,手动触发和自动触发最终都走到同一个检查逻辑。

自定义窗口与面板

Editor Utility Widget 最强大的地方是可以完全自定义窗口布局。你可以做出接近专业 DCC 工具的面板,比如 Maya 的 Shelf、Blender 的 Sidebar。

UE5.8 支持在 Editor Utility Widget 里嵌套其他 Widget。你可以在自定义工具面板里放:

  • 树形控件显示项目目录结构。
  • 表格显示资产属性。
  • 搜索框过滤资产。
  • 进度条显示批量处理进度。
  • 日志文本框显示实时输出。

老王帮小李做了一个 关卡资产检查面板。左侧是项目目录树,中间是选中目录下的资产列表,右侧是选中资产的预览信息和可操作按钮。面板底部有一个文本框,实时输出处理日志。

做这种复杂面板时,建议把 UI 拆成多个小的 Editor Utility Widget,再用一个主 Widget 组合。每个小 Widget 负责一个独立功能,比如一个专门做 LOD 设置,一个专门做贴图检查。主 Widget 用 Widget SwitcherTab 切换。这样维护起来比把所有逻辑塞在一个蓝图里轻松得多。

自定义窗口的位置和大小可以被 UE5.8 记住。在 Details 面板里设置 Restore Layout 为 true,下次打开项目时窗口会自动出现在上次关闭的位置。对于每天都要用的工具,这个小功能能省不少时间。

下面这段代码展示了如何在 C++ 里以编程方式打开一个 Editor Utility Widget 窗口。UE5.8 的 API 比之前更简洁:

TSubclassOf<UEditorUtilityWidget> WidgetClass = LoadClass<UEditorUtilityWidget>(
    nullptr,
    TEXT("/Game/Tools/EUW_AssetChecker.EUW_AssetChecker_C")
);

if (WidgetClass)
{
    UEditorUtilitySubsystem* EditorUtilitySubsystem = GEditor->GetEditorSubsystem<UEditorUtilitySubsystem>();
    EditorUtilitySubsystem->SpawnAndRegisterTab(WidgetClass);
}

这段代码首先加载 Editor Utility Widget 的蓝图类,然后通过 UEditorUtilitySubsystem 打开一个 Tab。适合做自动化测试或从其他 C++ 插件里触发工具窗口。

与 Python 工具的结合

UE 从 4 开始就内置了 Python 脚本支持,UE5.8 的 Python API 已经能覆盖大部分编辑器操作。Editor Utility Widget 和 Python 不是二选一的关系,而是可以互相调用。

常见的组合方式是:Python 负责底层资产操作,Editor Utility Widget 负责界面和流程控制。Python 的字符串处理和文件读写比蓝图方便,蓝图的事件驱动和 UI 搭建比 Python 顺手。两者结合能做出既有好界面又有强功能的工具。

小李后来学了几招。他在 Editor Utility Widget 里放一个 Button,点击后调用 Execute Python Script 节点。这个节点接收一个 Python 脚本字符串,或者引用一个 .py 文件。Python 脚本里用 unreal.EditorAssetLibraryunreal.AssetToolsHelpers 做具体操作,比如批量导入 FBX、批量生成 Lightmap UV、批量设置 Nanite。

下面这张图展示了 Editor Utility Widget 调用 Python 脚本的流程。

sequenceDiagram
    participant UI as "Editor Utility Widget UI"
    participant BP as "蓝图逻辑"
    participant PS as "Python 脚本"
    participant API as "Unreal Python API"
    participant Asset as "项目资产"

    UI->>BP: 点击执行按钮
    BP->>PS: Execute Python Script
    PS->>API: 调用 EditorAssetLibrary
    PS->>API: 调用 AssetToolsHelpers
    API->>Asset: 读写资产数据
    Asset-->>API: 返回结果
    API-->>PS: 返回结果
    PS-->>BP: 返回字符串/字典
    BP-->>UI: 更新进度条和日志

Python 脚本可以返回结果给蓝图,蓝图再更新 UI。比如批量处理一百个 Static Mesh,Python 每处理完一个就打印一行日志,蓝图把日志追加到文本框里,同时推进进度条。

UE5.8 的 Python 模块还新增了 unreal.EditorUtilityLibrary,里面封装了一些 Editor Utility Widget 常用的辅助函数。比如 get_selected_assets()get_selected_level_actors() 可以直接在 Python 里拿到选中项,和蓝图里的对应节点返回的数据结构一致。

老王建议小李把常用的 Python 函数整理成一个 ta_utils.py 模块,放到项目的 Content/Python 目录下。Editor Utility Widget 通过 import ta_utils 调用里面的函数,而不是每个工具都写一遍重复代码。这种做法让工具和工具之间能复用逻辑,也便于版本控制。

下面是一个 Python 脚本示例,用来批量检查选中 Static Mesh 是否启用了 Nanite:

import unreal

selected_assets = unreal.EditorUtilityLibrary.get_selected_assets()
missing_nanite = []

for asset in selected_assets:
    if isinstance(asset, unreal.StaticMesh):
        has_nanite = asset.get_editor_property('nanite_settings').enabled
        if not has_nanite:
            missing_nanite.append(asset.get_name())

print('未启用 Nanite 的模型:')
for name in missing_nanite:
    print(name)

这段代码首先拿到选中的资产,然后遍历每个 Static Mesh,读取 Nanite 设置,把未启用的模型名字打印出来。它可以在 Editor Utility Widget 的 Button 事件里通过 Execute Python Script 节点调用。

Python 和 Editor Utility Widget 的边界可以这样划分:

  • 界面、事件、进度反馈用 Editor Utility Widget 做。
  • 复杂字符串处理、文件 IO、批量资产读写用 Python 做。
  • 需要调用 C++ 编辑器子系统的底层操作,可以写 Python 封装,也可以直接写 C++ 模块暴露给蓝图。

一些踩过的坑

小李在那天晚上和之后几周踩了不少坑,这里挑几个常见的说说。

第一个坑是资产被占用时无法修改。批量重命名或设置 LOD 时,如果某个资产正被其他编辑器窗口打开,调用 Rename AssetSave Asset 会失败。UE5.8 不会自动帮你处理这种冲突。老王的办法是在操作前先尝试 Checkout Asset,失败了就跳过并记录下来。

第二个坑是蓝图里的事件执行顺序。Editor Utility Widget 的 Construct 事件在窗口打开时触发,但此时 Slate 可能还没完成布局。如果在 Construct 里立刻读取 UI 控件的大小,可能拿到 0。需要把这类操作放到 On Initialized 或者延迟一帧后再执行。

第三个坑是选中资产的类型判断。Get Selected Assets 返回的是 UObject 引用数组,必须用 Cast 判断类型。如果只选了 Static Mesh,但用户误点了文件夹,Cast 会失败。工具要做容错,而不是直接崩溃。

第四个坑是 Undo 支持。Editor Utility Widget 默认不会把操作加入编辑器的 Undo 栈。如果批量修改了几十个资产,用户想撤销,Ctrl+Z 没用。UE5.8 可以通过 Begin TransactionEnd Transaction 节点把一组操作打包成一个 Undo 记录,这个在批量工具里很重要。

第五个坑是性能。Content Browser 里选中几千个资产时,Get Selected Assets 会返回一个巨大的数组。如果每个资产都打开编辑器再修改,会非常慢。应该把操作尽量改成内存中的属性修改,最后统一 Save。Python 工具尤其要注意不要在循环里频繁调用 unreal.EditorAssetLibrary.load_asset

从工具到工作流

单个 Editor Utility Widget 解决的是点状问题。当团队有十几个这样的工具时,就需要考虑怎么把它们组织成工作流。

老王的做法是在项目里建一个 Tools 目录,所有 Editor Utility Widget、Editor Utility Blueprint、Python 脚本、图标都放在里面。目录结构大致如下:

Content/Tools/
├── EUW_AssetChecker.uasset
├── EUW_BatchRename.uasset
├── EUW_LODTools.uasset
├── EUB_ProjectValidator.uasset
├── Python/
│   ├── ta_utils.py
│   ├── batch_import.py
│   └── lod_generator.py
└── Icons/
    ├── Icon_Rename_40.png
    └── Icon_LOD_40.png

每个工具在创建时就在 Details 面板里配置好菜单路径和图标,新成员打开项目后能在 Tools > TA Tools 下直接看到全部工具。团队规范文档里也写上每个工具的使用场景,避免大家用错。

UE5.8 的 Editor Utility Extension 配置可以导出为 JSON,方便在不同项目之间复用。老王把常用工具的配置存成了一个模板,新项目初始化时直接拷贝过去,省去了重复配置的时间。

工具化的价值不只是省时间。它让重复工作变得可预测、可审计、可回滚。小李那个周五晚上如果用三个小时手动改完三百个资产,下周再来一次同样的事情,他可能还要三个小时。但做了工具后,下次同样的需求只要点两下按钮,五分钟搞定。

小结

Editor Utility Widget 是 UE5.8 里构建内部工具最轻量的方式。它不需要写 C++ 插件,不需要重启编辑器,就能做出带界面的资产处理工具。核心要点可以归纳为:

  • Editor Utility Widget 只在编辑器状态下运行,和运行时 UMG 生命周期不同。
  • 操作资产时优先用 Get Selected AssetsGet Selected Level Actors 作为入口。
  • 批量处理工具要注重安全:确认、备份、日志、Undo 缺一不可。
  • 把工具挂到菜单、工具栏或 Tab 上,才能真正融入日常流程。
  • Editor Utility Blueprint 适合无界面的后台任务,比如自动检查、报告生成。
  • 复杂工具可以拆成多个小 Widget 组合,避免单个蓝图过于臃肿。
  • Python 负责底层批量操作,Editor Utility Widget 负责界面和流程,两者配合效果最佳。

小李那个周五晚上最后十一点就下班了。老王帮他做的批量重命名和 LOD 工具只用了二十分钟跑完全部资产,剩下的时间他在检查报告里发现了几张命名混乱的贴图,顺手修完。周一开会时他把工具演示给策划看,策划又提了几个新需求:批量设置物理材质、批量检查碰撞复杂度、批量导出缩略图。小李打开 UE5.8,新建了一个 Editor Utility Widget,开始写下一个工具。