虚幻5蓝图Editor Utility Widget
-
- 一、基础框架搭建
-
- 背景:
- [1. 创建Editor Utility Widget](#1. 创建Editor Utility Widget)
- 2.根控件选择窗口
- 3.界面功能定位与阶段
- 4.查看继承树
- 5.目标效果
- 二、模块化设计流程
- 三、可视化界面UI布局
-
- [1. 添加标题栏](#1. 添加标题栏)
- [2. 构建滚动列表区域](#2. 构建滚动列表区域)
- [3. 构建Apply按钮](#3. 构建Apply按钮)
- [4. Apply按钮点击功能添加](#4. Apply按钮点击功能添加)
- [5. 变量模块分组](#5. 变量模块分组)
- [6. 新增变量`材质实例`](#6. 新增变量
材质实例
) - 7.定义Python参数输入元素
- 8.整体展示:
- 四、代码的详细解析
-
- [1. 材质路径获取模块](#1. 材质路径获取模块)
- [2. 材质优先级配置模块](#2. 材质优先级配置模块)
- [3. 静态网格材质处理模块](#3. 静态网格材质处理模块)
- [4. 骨骼网格材质处理模块](#4. 骨骼网格材质处理模块)
- [5. 批量处理主逻辑模块](#5. 批量处理主逻辑模块)
- [五、Unreal API 关键用途总结](#五、Unreal API 关键用途总结)
- 六、学习资源链接
一、基础框架搭建
背景:
在第一篇中【3DMax脚本MaxScript开发:创建高效模型虚拟体绑定和材质管理系统,从3DMax到Unreal和Unity引擎_系列第一篇】,设计了一个3Dmax模型虚拟体绑定和材质统一分配的插件,现在根据上一篇的工具设计,在UE5中用Editor Utility Widget来实现一个网格体材质自动匹配功能。
在UE5中,我将通过一个具体的逻辑结构和GUI设计示例,解决模型资产因多来源导致的材质命名混乱、手动分配效率低下等问题,对模型材质进行批量调控。在UE5的Editor Utility Widget开发中设计一款网格体材质批量匹配工具 。
通过可视化界面实现材质名称与网格体逻辑的自动化映射,简化美术流程并提升资源管理一致性。
1. 创建Editor Utility Widget
启用必要插件
:
打开 Edit > Plugins ,搜索并启用 "Editor Scripting Utilities" (这是创建Editor Utility Widget的必要前提)。
通过主工具栏的"Add"菜单创建
:选择 Editor Utilities > Editor Utility Widget 。
右键创建
:在内容浏览器(Content Browser)中,右键点击目标文件夹的空白区域。
选择
Editor Utilities
>Editor Utility Widget
。
这里我创建后命名为"BP_Widget_BatchAssignMaterials
",并将其保存至/Game/BatchAssignMaterials
目录下。接下来,我们将逐步实现UI布局、数据绑定和核心逻辑。
学习资源推荐
2.根控件选择窗口
- 虚幻引擎创建Editor Utility Widget时的根控件选择窗口,创建后弹出这个窗口,选择一个根控件作为UI的基础。
- 选择一个合适的根控件,这里我选择"
Stack Box
",点击"Select
"按钮。
控件分类系统
- COMMON区:推荐布局容器(Stack Box/Grid Panel),适合快速构建基础界面框架
- ALL CLASSES区:提供完整控件库(含20个可用项),支持EditorUtility系列专用组件
- Stack Box:支持垂直/水平自动排列(Orientation参数可配置)
- Grid Panel:表格布局容器(自动保持列宽一致性)
3.界面功能定位与阶段
下图里面展示了Unreal Engine的Editor Utility Widget设计界面。下图
- 左侧红框内是UI设计时的控件列表,按钮和布局组件,"
Tile Bar Area
"等元素。
其中"BP_Widget_BatchAssignMaterial
"的继承关系如下:
- 中间是设计区域,显示了具体的控件和属性设置,可视化拖拽布局,属性调试。
- 右侧红框则是"Batch Assign Materials"面板,列出了多种材质选项,这应该是运行时美术实际看到的界面。也是本次制作的目标效果。
区域 | 功能定位 | 对应阶段 |
---|---|---|
左侧红框区域 | UI设计面板(Editor Utility Widget 的控件库与层级管理) | 设计阶段 |
中间主区域 | UI编辑区(可视化拖拽布局,属性调试) | 设计阶段 |
右侧红框区域 | 运行时功能面板(用户实际操作的批量材质分配界面) | 运行阶段 |

Widget Blueprint学习资源推荐
4.查看继承树
创建完**
Editor Utility Widget
** 后,可以在Class Viewer中查看和搜索相关类。在Unreal Engine上方菜单栏的 Tools > Class Viewer 打开窗口。如下图所示:
开发中的典型应用场景
想了解某个类的属性,可以在Class Viewer来查找或确认。
在开发过程中需要确认某个Widget的继承关系或属性是否正确设置,或者在处理资源路径过长的问题。
比如这里我的命名是BP_Widget_BatchAssignMaterials,那么可以在层级结构Widget继承体系找到:Visual → Widget → UserWidget → EditorUtilityWidget → 当前类
继承关系节点 | 用途说明 |
---|---|
Visual | UI元素的基类,提供渲染能力。 |
Widget | UMG控件的核心父类,包含布局、交互等通用功能。 |
UserWidget | 用户自定义控件的直接父类,支持蓝图逻辑绑定。 |
EditorUtilityWidget | 专用于编辑器工具的子类,提供与Unreal编辑器交互的API(如菜单扩展、资产操作等)。 |
如需进一步扩展功能,可参考Unreal官方文档中的Editor Scripting Utilities 。
5.目标效果

二、模块化设计流程
1.材质替换核心流程:
思路如下:
骨骼网格处理 静态网格处理 否 是 StaticMesh SkeletalMesh 是 否 是 否 获取材质数组 调用process_skeletal_mesh 插槽名称是否匹配关键字? 创建SkeletalMaterial对象 保持原材质 更新材质接口 遍历材质插槽 调用process_static_mesh 插槽名称是否匹配关键字? 加载新材质 保持原材质 替换材质插槽 开始运行脚本 获取选中资产 是否包含 Static/Skeletal Mesh? 弹出警告并终止 创建进度条 遍历每个网格资产 资产类型判断 保存所有修改 弹出完成提示 结束
2.完整代码如下
python
import unreal
def print_material_path(material_asset):
if material_asset is None:
print("材质未找到!")
return
return material_asset.get_outer().get_path_name()
MATERIAL_PRIORITY_LIST = [
("Interior_Other_FrostedPlastic", print_material_path(plastic)),
("Wheel_FrostedMetal", print_material_path(plastic)),
("DaytimeRunningLight", print_material_path(daytime_running_light)),
("RearRunningLight", print_material_path(backup_light)),
("DoorOuterMirror", print_material_path(door_outer_mirror)),
("RearLightGlass", print_material_path(rear_light_glass)),
("FrostedPlasticRed", print_material_path(frosted_plastic_red)),
("FrostedPlastic", print_material_path(frosted_plastic)),
("glass1", print_material_path(glass1)),
("WheelDisc", print_material_path(wheel_disc)),
("GlassSkylight", print_material_path(glass_skylight)),
("FrostedMetal", print_material_path(frosted_metal)),
("BrakeLight", print_material_path(backup_light)),
("BackupLight", print_material_path(backup_light)),
("ClothDown", print_material_path(cloth_down)),
("TurnLight", print_material_path(turn_light)),
("Leather", print_material_path(leather)),
("ClothUp", print_material_path(cloth_up)),
("Chrome", print_material_path(chrome)),
("Mirror", print_material_path(mirror)),
("Paint", print_material_path(paint)),
("CCAScreen", print_material_path(cca_screen)),
("Plate", print_material_path(plate)),
("Black", print_material_path(black)),
("Int", print_material_path(int)),
("Plastic", print_material_path(plastic)),
("Glass", print_material_path(glass)),
("WheelTire", print_material_path(wheeltire)),
]
def process_static_mesh(staticmesh_assets):
modified = False
material_slots = staticmesh_assets.static_materials
for index, material_slot in enumerate(material_slots):
slot_name = str(material_slot.material_slot_name)
matched_material = None
for keyword, material_path in MATERIAL_PRIORITY_LIST:
if keyword.lower() in slot_name.lower():
material = unreal.load_asset(material_path)
if material:
matched_material = material
break
if matched_material:
current_material = material_slot.material_interface
if current_material != matched_material:
staticmesh_assets.set_material(index, matched_material)
modified = True
unreal.log(f"StaticMesh替换成功:{staticmesh_assets.get_name()} [{slot_name}] -> {matched_material.get_name()}")
return modified
def process_skeletal_mesh(skeletal_mesh):
modified = False
materials = list(skeletal_mesh.get_editor_property('materials'))
for mat_idx in range(len(materials)):
original_slot = materials[mat_idx]
slot_name = str(original_slot.material_slot_name)
target_material = None
for keyword, material_path in MATERIAL_PRIORITY_LIST:
if keyword.lower() in slot_name.lower():
target_material = unreal.load_asset(material_path)
if target_material:
break
if target_material:
new_slot = unreal.SkeletalMaterial()
new_slot.material_slot_name = original_slot.material_slot_name
new_slot.material_interface = target_material
materials[mat_idx] = new_slot
modified = True
unreal.log(f"SkeletalMesh替换成功:{slot_name} => {target_material.get_name()}")
if modified:
skeletal_mesh.set_editor_property('materials', materials)
return modified
def batch_assign_materials():
# 获取所有选中资产并过滤
selected_assets = unreal.EditorUtilityLibrary.get_selected_assets()
mesh_assets = [
asset for asset in selected_assets
if isinstance(asset, (unreal.StaticMesh, unreal.SkeletalMesh))
]
if not mesh_assets:
unreal.log_warning("未选中有效的网格资产!如果Mesh插槽内已有资产,请查找资产后重新载入。")
return
total_meshes = len(mesh_assets)
processed_meshes = 0
with unreal.ScopedSlowTask(total_meshes, "批量替换材质中...") as slow_task:
slow_task.make_dialog(True)
for mesh_idx, mesh in enumerate(mesh_assets):
if slow_task.should_cancel():
break
progress_percent = (mesh_idx + 1) / total_meshes * 100
slow_task.enter_progress_frame(1,
f"处理 {mesh.get_name()} ({progress_percent:.1f}%)")
modified = False
if isinstance(mesh, unreal.StaticMesh):
modified = process_static_mesh(mesh)
elif isinstance(mesh, unreal.SkeletalMesh):
modified = process_skeletal_mesh(mesh)
if modified:
unreal.EditorAssetLibrary.save_loaded_asset(mesh)
processed_meshes += 1
result_msg = f"批量材质替换完成!成功处理 {processed_meshes}/{total_meshes} 个网格"
unreal.log(result_msg)
unreal.EditorDialog.show_message("操作完成", result_msg, unreal.AppMsgType.OK)
if __name__ == "__main__":
batch_assign_materials()
三、可视化界面UI布局
1. 添加标题栏
创建Window Title Bar Area
- 拖拽 Horizontal Box 到StackBox内,命名为
WindowTitleBarArea
。- 调整字体大小和颜色。具体的属性配置可以看着调整,不做详细的提示。

- 添加标题文本
- 在
Window Title Bar Area
内拖拽Text
,命名为TitleText
。
2. 构建滚动列表区域
静态网格列表(左侧)
- 拖拽
ScrollBox
到StackBox
内 → 命名为StaticMesh_ScrollBox
。
- 在
ScrollBox
内添加DetailsView
作为列表项容器。
添加滚动列表(ScrollBox)
,具体的属性配置可以看着调整,不做详细的提示。
骨骼网格列表(左侧)
可以直接复制上述操作完的结构,主要放置在**StackBox
**内。也可以直接重新拖入,更新命名 。
3. 构建Apply按钮
- 拖拽 Button 到StackBox内 → 命名为
Button_Apply
;
- 拖拽 Text 到Button_Apply内 → 设置文本为 "Apply" ,背景色为绿色。
左侧为 蓝图逻辑编辑区 (节点图表),右侧为 GUI设计界面 (UMG可视化布局),中间红色箭头指示二者的 动态绑定关系 。
4. Apply按钮点击功能添加
进入蓝图逻辑编辑
- 在UMG编辑器中,点击Button_Apply按钮,在Details面板中找到 Events 部分,点击On Clicked 。这时界面自动跳转到 图表(Graph) 模式。
- 如果没有跳转可以在UMG编辑器顶部切换至 图表(Graph) 模式(非设计模式)。
此时会自动在图表中生成OnClicked
事件节点。将
OnClicked
事件的输出引脚(白色箭头)连接到功能节点的输入引脚。
拖拽OnClicked(Button_Apply)
事件的执行Exec
引脚(白色箭头),弹出检索窗口搜索**Execute Python Script
**功能,并链接节点的输入引脚。
演示如下:
5. 变量模块分组
- **
VARIABLES
** 是蓝图编辑器中用于集中管理当前蓝图所有变量的区域。
在蓝图中,Category 用于将变量、函数等元素按功能模块分组,提升可维护性。
在 Details 面板的Category
字段输入分类名称(如StaticMesh
)。
分组如下图
变量模块分组演示如下:
6. 新增变量材质实例
点击加号,新增变量
材质实例
输入接口,Assign Materials
:MaterialInterface
类型,存储待批量分配的材质实例。
材质实例的配置,根据项目需求,可能会有所不同。以下是我的材质实例命名和分类;
7.定义Python参数输入元素

8.整体展示:
黄颜色的表示:执行Python脚本节点(也就是**
Execute Python Script
**的蓝图节点),定义Python参数输入元素。蓝色的表示:变量的节点展示。
整体展示如下:
四、代码的详细解析
1. 材质路径获取模块
python
def print_material_path(material_asset):
if material_asset is None:
print("材质未找到!")
return
return material_asset.get_outer().get_path_name()
- 功能:获取材质资源的完整路径。
- 核心Unreal API :
material_asset.get_outer()
: 返回材质所属的外部对象(如Package)。get_path_name()
: 获取对象在Unreal中的完整路径(例如/Game/Materials/MyMaterial
)。
- 问题说明 :
- 在
MATERIAL_PRIORITY_LIST
的print_material_path(plastic)
存在未定义变量问题,需确保plastic
等变量是已加载的材质实例。
- 在
2. 材质优先级配置模块
python
MATERIAL_PRIORITY_LIST = [
("Interior_Other_FrostedPlastic", print_material_path(plastic)),
# ... 其他条目
]
- 功能:定义材质插槽名称与目标材质的映射关系,按优先级匹配。
- 逻辑说明 :
- 每个条目包含一个关键字(如
"FrostedPlastic"
)和材质路径。 - 通过字符串匹配插槽名称(不区分大小写)来动态加载材质。
- 每个条目包含一个关键字(如
3. 静态网格材质处理模块
python
def process_static_mesh(staticmesh_assets):
material_slots = staticmesh_assets.static_materials
staticmesh_assets.set_material(index, matched_material)
- 功能:遍历静态网格的材质插槽,按优先级列表替换材质。
- 核心Unreal API :
static_materials
: 获取静态网格的所有材质插槽(StaticMaterial
列表)。set_material(index, material)
: 替换指定索引的材质。load_asset(material_path)
: 根据路径加载材质资源。
- 关键操作 :
- 通过插槽名称匹配关键字,动态加载并替换材质。
4. 骨骼网格材质处理模块
python
def process_skeletal_mesh(skeletal_mesh):
materials = list(skeletal_mesh.get_editor_property('materials'))
new_slot = unreal.SkeletalMaterial()
skeletal_mesh.set_editor_property('materials', materials)
- 功能:处理骨骼网格材质,逻辑类似静态网格。
- 核心Unreal API :
get_editor_property('materials')
: 获取骨骼网格的材质列表。unreal.SkeletalMaterial()
: 创建新的骨骼网格材质插槽对象。set_editor_property()
: 修改骨骼网格的材质属性。
- 差异点 :
- 骨骼网格材质需通过
SkeletalMaterial
对象封装。
- 骨骼网格材质需通过
5. 批量处理主逻辑模块
python
def batch_assign_materials():
selected_assets = unreal.EditorUtilityLibrary.get_selected_assets()
with unreal.ScopedSlowTask(...):
unreal.EditorAssetLibrary.save_loaded_asset(mesh)
- 功能:批量处理选中的网格资产,支持进度条和撤销操作。
- 核心Unreal API :
EditorUtilityLibrary.get_selected_assets()
: 获取用户在内容浏览器中选中的资产。ScopedSlowTask
: 显示进度条,防止编辑器卡死。EditorAssetLibrary.save_loaded_asset()
: 保存修改后的资产。
- 流程控制 :
- 过滤静态/骨骼网格 → 遍历处理 → 保存修改 → 反馈结果。
五、Unreal API 关键用途总结
API/属性 | 用途 |
---|---|
load_asset() |
根据路径加载资源(材质、网格等)。 |
set_material() |
替换静态网格的材质插槽。 |
get_editor_property() |
获取资产的编辑器属性(如材质列表)。 |
EditorUtilityLibrary |
处理编辑器交互(选中资产、弹窗等)。 |
ScopedSlowTask |
执行长时间任务时显示进度条。 |
六、学习资源链接
-
Unreal Python API 官方文档
https://docs.unrealengine.com/5.0/en-US/PythonAPI/ -
Editor Scripting 教程
https://docs.unrealengine.com/5.0/en-US/editor-scripting-in-unreal-engine/ -
StaticMesh API 参考
StaticMesh Class -
材质系统文档
Material System Overview
最终效果展示