【虚幻5蓝图Editor Utility Widget:创建高效模型材质自动匹配和资产管理工具,从3DMax到Unreal和Unity引擎_系列第二篇】

虚幻5蓝图Editor Utility Widget


一、基础框架搭建

背景:

在第一篇中【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布局、数据绑定和核心逻辑。

    学习资源推荐

  1. Editor Utility Widgets 官方指南
  2. Editor Scripting 插件说明
  3. UMG 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学习资源推荐

Editor Utility Widgets 指南



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
  • 调整字体大小和颜色。具体的属性配置可以看着调整,不做详细的提示。
  1. 添加标题文本
  • Window Title Bar Area 内拖拽 Text ,命名为 TitleText

2. 构建滚动列表区域

静态网格列表(左侧)

  • 拖拽 ScrollBoxStackBox内 → 命名为 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_LISTprint_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 执行长时间任务时显示进度条。

六、学习资源链接

  1. Unreal Python API 官方文档
    https://docs.unrealengine.com/5.0/en-US/PythonAPI/

  2. Editor Scripting 教程
    https://docs.unrealengine.com/5.0/en-US/editor-scripting-in-unreal-engine/

  3. StaticMesh API 参考
    StaticMesh Class

  4. 材质系统文档
    Material System Overview

最终效果展示

相关推荐
DvLee102421 小时前
UnityGLTF 材质创建与赋值流程
unity·材质
HahaGiver6661 天前
从0到1做一个“字母拼词”Unity小游戏(含源码/GIF)- 字母拼词正确错误判断
unity·游戏引擎·游戏程序
njsgcs2 天前
tekla python 获取所有材质信息 截面类型
材质
一个小狼娃2 天前
Android集成Unity避坑指南
android·游戏·unity
极客柒2 天前
Unity 协程GC优化记录
java·unity·游戏引擎
黄思搏2 天前
Unity SpriteRenderer 进度条 Shader 实现
unity·游戏引擎
猫屋小鱼丸2 天前
手把手教你在unity中实现一个视觉小说系统(一)
unity
国服第二切图仔2 天前
Rust开发实战之简单游戏开发(piston游戏引擎)
开发语言·rust·游戏引擎
HahaGiver6662 天前
Unity与Android原生交互开发入门篇 - 打开Unity游戏的设置
android·unity·交互
@LYZY3 天前
Unity TextMeshPro 文本对齐方式详解
unity·游戏引擎·textmeshpro·tmp