# Plugin02 **Repository Path**: weekend/plugin02 ## Basic Information - **Project Name**: Plugin02 - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-06-30 - **Last Updated**: 2025-10-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 一个基于 UEditorUtilityWidget 的插件: Asset 检索插件 Everything * "EngineAssociation": "5.5", ![Search Result](other/image15.gif) ## 从新建 Editor Standalone Window Plugin 开始 ### 1. 新c++工程 ### 2. Edit/Plugins/Add/Editor Standalone Window ![New Plugins](other/image01.png) | 模板名称 | 主要功能 | 适用场景 | 特点/限制 | |---------------------|-----------------------------------|--------------------------------|---------------------------------| | **Blank** | 最小化代码的空白插件 | 自定义非可视化插件 | 无默认按钮/菜单,需手动扩展 | | **Content Only** | 仅包含内容的插件 | 资产管理 | 无代码功能 | | **Blueprint Library** | 包含 Blueprint Function Library | 提供静态 Blueprint 节点 | 适合工具函数开发 | | **Editor Mode** | 带有编辑器模式的插件 | 自定义“Modes”选项卡工具 | 包含 UI 和撤销/重做支持 | | **Editor Standalone Window** | 添加独立窗口按钮插件 | 自定义编辑器窗口 | 提供空窗口框架,需自行填充 | | **Editor Toolbar Button** | 添加工具栏按钮插件 | 简单工具或命令 | 需实现 `OnButtonClick` 事件 | | **Third Party Library** | 包含第三方库的插件 | 集成外部库 | 包含示例,方便学习集成方法 | ## 加载 `UEditorUtilityWidget` 蓝图作为编辑器界面 1. 在插件的Content目录下新建蓝图`WBP_SearchPanel`:Editor Utilities/Editor Utility Widget 2. 编辑`WBP_SearchPanel`蓝图并添加一些UI控件,比如用TileView来展示搜索结果 3. 插件c++代码`OnSpawnPluginTab`中加载`WBP_SearchPanel` ```cpp TSharedRef FEverythingModule::OnSpawnPluginTab(const FSpawnTabArgs &SpawnTabArgs) { // 这里Everything是插件名,不需要实际的Content目录路径 UBlueprint *MyBlueprint = LoadObject( nullptr, TEXT("Blueprint'/Everything/WBP_SearchPanel.WBP_SearchPanel'")); if (!MyBlueprint) { FText ErrorText = FText::FromString(TEXT("Failed to load Blueprint WBP_SearchPanel")); UE_LOG(LogTemp, Error, TEXT("%s"), *ErrorText.ToString()); return CreateErrorDockTab(ErrorText); } TSharedRef TabWidget = SNullWidget::NullWidget; UEditorUtilityWidget *CreatedUMGWidget = nullptr; UClass *BlueprintClass = MyBlueprint->GeneratedClass; TSubclassOf WidgetClass = BlueprintClass; UWorld *World = GEditor->GetEditorWorldContext().World(); if (World) { if (CreatedUMGWidget) { CreatedUMGWidget->Rename(nullptr, GetTransientPackage()); } CreatedUMGWidget = CreateWidget(World, WidgetClass); } if (CreatedUMGWidget) { TabWidget = SNew(SVerticalBox) + SVerticalBox::Slot().HAlign( HAlign_Fill)[CreatedUMGWidget->TakeWidget()]; } return SNew(SDockTab).TabRole(ETabRole::NomadTab)[ // Put your tab content here! TabWidget]; } ``` ## 展示数据的`TileView` 1. 先往`WBP_SearchPanel`中添加一个`TileView` 2. `TileView`属性Entry Widget Class上点击⊕, Create New Blueprint: `WBP_SearchItem` 3. 打开`WBP_SearchItem`,并添加一个`TextBlock`用于展示数据 4. 给`WBP_SearchItem`添加一个接口`UserObjectListEntry`, 并删除默认的`UserListEntry`(这俩接口互斥) Graph/Class Setting/Details/Implemented Interface/Add/UserObjectListEntry 5. 添加一个继承自`Object`的蓝图`BP_SearchItemData`用于存储Item数据(这里不能用FStruct, 后面的步骤中传递参数用的数据结构是Object),添加一个Text成员SearchAssetName(Instance Editable/Expose on Spawn) 6. `WBP_SearchItem`添加事件`On List Item Object Set` ![Event On List Item Object Set](other/image03.png) 7. `WBP_SearchPanel`中加一些测试数据 ![Event Construct](other/image04.png) ![SearchPanel](other/image05.png) ## `VerticalBox` 填充整个屏幕 1. 选中`VerticalBox` 2. Size To Content = false 3. 设置 ​​Anchors​​ 为 ​​全拉伸​​(左上角和右下角均锚定到父控件边缘)。 4. 设置 ​​Alignment​​ 为 (0, 0)(左上对齐,但填充整个空间)。 ![VerticalBox](other/image06.png) ## `Get Assets` 查找所有资产 | **节点名称** | **功能描述** | |----------------------------------|-----------------------------------------------------------------------------| | **`Get Assets by Class`** | 根据指定的资产类(如 `Blueprint`、`Texture`)获取所有匹配的资产。 | | **`Get Assets by Path`** | 根据指定的虚拟路径(如 `/Game/Characters`)获取该路径下的所有资产。 | | **`Get Assets with Filter`** | 使用 `FARFilter`(资产注册表过滤器)自定义条件查询资产。 | ![Get Assets](other/image07.png) ![filter](other/image08.png) ![Search Result](other/image09.png) ## TileView 选中 Entry Widget 后更新 Details ![Selection change](other/image14.png) ## `SNew`和`SAssignNew` ### SNew 是用来创建一个新的 Slate 小部件的宏。它通常用在构建 UI 时,定义新的 Slate 小部件并返回一个实例。 ```cpp TSharedRef MyWidget = SNew(SButton) .Text(FText::FromString("Click Me")); ``` ### SAssignNew 是用来分配一个新的 Slate 小部件实例并将其赋值给一个指定的变量。它不仅创建新的小部件,还将小部件的实例赋给你指定的变量。 ```cpp SAssignNew(MyButton, SButton) .Text(FText::FromString("Click Me")); ``` ## 插件资产 ### 默认是无法添加插件资产的,需要打开开关 ```Plugins/Everything/Everything.uplugin "CanContainContent": true, ``` ### 然后在设置里面打开`Show Plugin Content`开关 ![Show Plugin Content](other/image02.png) ## 资产路径 | 字段 | 含义 | 示例 | 用途 | |------|------|------|------| | **Package Name** | 资产的完整路径,包括文件名和扩展名 | `/Game/Textures/MyTexture.MyTexture` | 唯一标识和加载资产 | | **Package Path** | 资产所在的目录路径,不包括文件名和扩展名 | `/Game/Textures/` | 批量操作某个目录下的所有资产 | | **Asset Name** | 资产的名称,不包括路径和扩展名 | `MyTexture` | 引用资产的名称,通常需要结合路径来唯一标识资产 | | **Asset Class** | 资产的类,表示资产的类型 | `UTexture2D`(纹理类)
`UBlueprintGeneratedClass`(蓝图类) | 检查资产的类型 | | **Asset Class Path** | 资产类的路径,通常是指资产类的完整路径,包括文件名和扩展名 | `/Script/Engine.Texture2D`
`/Script/Engine.BlueprintGeneratedClass` | 唯一标识和加载资产类 | ## Asset Data 和 Typed Element Asset Data Reference | 特性 | Asset Data | Typed Element Asset Data Reference | | -------- | ----------- | ---------------------------------- | | **定义** | 存储资产元数据的结构体 | 直接引用和操作资产的数据结构 | | **用途** | 搜索和引用资产 | 直接操作资产,类型安全 | | **加载步骤** | 需要额外步骤加载资产 | 直接操作,无需额外加载 | | **类型安全** | 不提供类型安全 | 提供类型安全接口 | ### Asset Data ```cpp FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); FARFilter Filter; Filter.ClassNames.Add(UTexture2D::StaticClass()->GetFName()); Filter.PackagePaths.Add(TEXT("/Game/Textures/")); TArray Assets; AssetRegistryModule.Get().GetAssets(Filter, Assets); ``` ### Typed Element Asset Data Reference ```cpp UObject* Asset = UEditorAssetLibrary::LoadAsset(TEXT("/Game/Textures/MyTexture.MyTexture")); FTypedElementHandle ElementHandle = FTypedElementHandle::MakeHandle(Asset); if (ElementHandle.IsValid()) { // 使用 ElementHandle 操作资产 } ``` ## EDITOR/Details View 显示资产详情 ![Details View](other/image10.png) ![Details View](other/image11.png) ## TileView/Entry Widget 选中效果 ![selected](other/image14.png) ![selected](other/image13.png) ![selected](other/image15.gif) ## SWidget ``` SWidget ├── SCompoundWidget │ ├── SButton - 按钮控件,用于触发事件。 │ ├── SCheckBox - 复选框控件,用于选择或取消选择。 │ ├── SColorBlock - 显示颜色块的控件。 │ ├── SComboBox - 下拉选择框控件,用于从多个选项中选择一个。 │ ├── SEditableTextBox - 可编辑文本框控件,用于输入多行文本。 │ ├── SEditableText - 可编辑文本控件,用于输入单行文本。 │ ├── SHyperlink - 超链接控件,用于导航到其他页面或触发事件。 │ ├── SImage - 图像控件,用于显示图片。 │ ├── SInlineEditableTextBlock - 内联可编辑文本块控件,用于在文本块中直接编辑文本。 │ ├── SProgressBar - 进度条控件,用于显示进度。 │ ├── SSpinBox - 数值输入框控件,用于输入数值。 │ ├── STextBlock - 文本块控件,用于显示文本。 │ ├── SUserWidget - 用户自定义控件,用于创建自定义的 UI 组件。 │ ├── SWindow - 窗口控件,用于创建独立的窗口。 │ ├── SListView - 列表视图控件,用于显示和操作列表数据。 │ ├── STableViewBase - 表格视图基类,用于显示和操作表格数据。 │ ├── STreeView - 树形视图控件,用于显示和操作树形结构数据。 │ ├── SPropertyEditor - 属性编辑器控件,用于编辑对象的属性。 │ ├── SAssetPicker - 资产选择器控件,用于选择项目中的资产。 │ ├── SColorPicker - 颜色选择器控件,用于选择颜色。 │ └── ... ├── SPanel │ ├── SBorder - 带有边框的面板控件,用于装饰子控件。 │ ├── SCanvas - 画布面板,允许自由布局。 │ ├── SOverlay - 叠加面板,允许多个控件重叠。 │ ├── SScrollBox - 可滚动的面板,用于显示大量内容。 │ ├── SVerticalBox - 垂直布局的面板,用于垂直排列子控件。 │ ├── SHorizontalBox - 水平布局的面板,用于水平排列子控件。 │ ├── SWrapBox - 自动换行的面板,用于自动换行排列子控件。 │ ├── SUniformGridPanel - 均匀网格面板,用于均匀排列子控件。 │ ├── SGridPanel - 网格面板,用于按行和列排列子控件。 │ └── ... ├── SLeafWidget │ ├── SBox - 基础的盒子控件,用于布局。 │ ├── SColorBlock - 显示颜色块的控件。 │ ├── SImage - 图像控件,用于显示图片。 │ ├── STextBlock - 文本块控件,用于显示文本。 │ ├── SThrobber - 旋转等待动画控件,用于显示加载状态。 │ ├── SProgressBar - 进度条控件,用于显示进度。 │ ├── SSlider - 滑块控件,用于选择数值范围。 │ ├── SScrollBar - 滚动条控件,用于滚动内容。 │ ├── SSplitter - 分割条控件,用于分割和调整面板大小。 │ └── ... ├── SWeakWidget - 用于定义逻辑上的归属而非事件上的归属。 └── SWindow - 窗口控件,用于创建独立的窗口。 ``` ## Delegate(委托)/ Event(事件)/ Function(函数) / Macro(宏) | **特性** | **Delegate(委托)** | **Event(事件)** | **Function(函数)** | **Macro(宏)** | | - | - | - | - | - | | **本质** | 可绑定的回调机制 | 可视化触发点或委托封装(事件分发器) | 可重用的代码块 | 预处理指令,编译时展开 | | **绑定/调用方式** | 动态绑定(运行时增减回调) | 事件分发器需绑定,普通事件节点硬编码 | 直接调用 | 编译时替换 | | **多播支持** | 支持单播和多播 | 事件分发器支持多播,普通事件节点通常是单播 | 不支持 | 不支持 | | **跨蓝图/C++通信** | 支持(C++声明,蓝图绑定) | 事件分发器支持跨蓝图通信 | 支持(通过 `UFUNCTION`) | 不支持(纯编译时行为) | | **可视化操作** | 需手动连接节点(如 `Bind Event` 和 `Broadcast`) | 通过蓝图节点直接操作(如 `Bind Event to Dispatcher`) | 直接调用 | 不可见(编译前展开) | | **典型用途** | 观察者模式、动态响应事件 | 蓝图内部逻辑触发、蓝图间通信 | 封装重复逻辑、提供接口 | 简化代码、与引擎系统交互 | | **性能** | 运行时绑定/解绑有轻微开销 | 事件分发器调用有轻微开销 | 直接调用,无额外开销 | 无运行时开销 | ## 性能分析 > 插件使用的时候出现了一个很严重的卡顿现象,正好试试用 Unreal Insight 分析一下 1. UE5中自带的Trace工具, 点 Start/Stop 记录并自动存档 2. 打开 Unreal Insight(Session Browser), 选择刚才的记录双击打开 ![Unreal Insight](other/image16.png) 3. 从 Frames 窗口可以看到一根很突出的线 ![Unreal Insight](other/image17.png) 4. 放大后可以在下方的堆栈(这,,啥时候一不小心写了个无限递归……) ![Unreal Insight](other/image18.png) 5. 没了,改bug去了…… ## 参考 - [x] [插件](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/plugins-in-unreal-engine) - [x] [Slate 架构](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/slate-architecture?application_version=4.27) - [x] [控制台Slate调试器](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/console-slate-debugger?application_version=4.27) - [x] [Creating A Content Only Plugin In Unreal Engine](https://dev.epicgames.com/community/learning/tutorials/Y5Zm/creating-a-content-only-plugin-in-unreal-engine) - [x] [编辑器工具控件](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/editor-utility-widgets?application_version=4.27) - [x] [委托](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/delegates-and-lambda-functions-in-unreal-engine) - [x] [Declaring Delegates For Blueprint](https://dev.epicgames.com/community/learning/tutorials/pqG/unreal-engine-declaring-delegates-for-blueprint) - [x] [Details 面板自定义](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/details-panel-customization?application_version=4.27) - [x] [Live Coding](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/using-live-coding-to-recompile-unreal-engine-applications-at-runtime)