UE4编辑器主界面的各个Tab页
Place Actors
在PlacementModeModule.h里实现了IPlacementModeModule接口。
调用该接口的Register系列函数即可实现向Place Actors中添加自定义分类及其子项。
具体步骤如下:
调用该接口的Register系列函数即可实现向Place Actors中添加自定义分类及其子项。
具体步骤如下:
- 创建一个Editor模块,因为定制PlacementMode的行为与编辑器UI相关,设该模块名为XUI
- XUI.build.cs文件里需要修改两处:PrivateIncludePathModuleNames与DynamicallyLoadedModuleNames均添加【PlacementMode】模块
- XUI.build.cs文件里视具体要求添加对其他runtime模块的引用
- 需要注意要添加的子项Actor,其代码实现中需要有XXX_API前缀
- 修改XUI所在插件的uplugin文件:将XUI的LoadingPhase改为PostEngineInit
- 这是因为EngineInit过程中,PlacementMode模块会初始化完毕。因此XUI在EngineInit之后加载,能保证成功获取到IPlacementModeModule接口
- XUI里的代码实现:
关于这方面的具体实例,可以参考插件
- SuraControlPanel: 该插件演示了很多Slate相关的用法
- SuraControlPanelCommonBPLibrary.h
- SuraControlPanel.cpp
- 注册编辑器工具栏的启动回调:UToolMenus::RegisterStartupCallback()
- 在UE编辑器工具栏指定位置添加菜单项和按钮等
- 向UE注册自定义Tab页及返回系统为其分配的ID
FAssetData对象的创建
见#480
FAssetData ShadowCaptureActorAssetData( TEXT("/SURAPlugin/ARShadow/ShadowGenerator/BluePrints/ShadowCapture"), TEXT("/SURAPlugin/ARShadow/ShadowGenerator/BluePrints"), TEXT("ShadowCapture"), TEXT("Blueprint") ); // asset路径在编辑器里通过Reference Viewer可以获得
UE5.1版的新API形式:
// Run test on a single TestPlan asset FName PackageName = FName(FPaths::GetBaseFilename(Path, false)); FName PackagePath = FName(FPaths::GetPath(Path)); FName AssetName = FName(FPaths::GetBaseFilename(Path, true)); FTopLevelAssetPath ClassName = UInterchangeImportTestPlan::StaticClass()->GetClassPathName(); FInterchangeImportTestData& TP = TestPlans.AddDefaulted_GetRef(); TP.AssetData = FAssetData(PackageName, PackagePath, AssetName, ClassName);
UObject类型获取其Asset Path
FTopLevelAssetPath ClassName = UObject::StaticClass()->GetClassPathName(); // // 注意这里UObject类型的AssetPath都是【/Script/Package.Class】的固定格式 // 之所以是这个固定格式,是因为其.generated.h里有DECLARE_CLASS宏调用,其注册的该UObject的PackagePath就是【/Script/Package】的格式 // 例如:/Script/Engine.Blueprint表示Engine模块的UBlueprint // UClass* USuraControlPanelCommonBPLibrary::LoadCppClass( const FString& AssetPath ) { return ::StaticLoadClass(UObject::StaticClass(), nullptr, *AssetPath); }
UObject Paths
Every Asset and Actor that the Unreal Editor has loaded in memory has a path that uniquely identifies it.
In general, these object paths follow the format:
/path/PackageName.ObjectName:SubObjectName.SubObject
Programmers who work with the Engine in C++ may recognize this format as the same one accepted by functions like FindObject() and StaticFindObject().
For example, to deconstruct an Actor path shown in the request examples above:
For example, to deconstruct an Actor path shown in the request examples above:
/Game/ThirdPersonBP/Maps/ | The path to the Asset in the Content Browser. |
ThirdPersonExampleMap.ThirdPersonExampleMap: | The name of the package and the object it contains (for many Assets, these will be the same). |
PersistentLevel.CubeMesh_5.StaticMeshComponent0 | The path through a hierarchy of sub-objects to the object you want to affect. |
正确引用第三方module
正确的引用方法是在M1的M1.build.cs文件中设定如下字段:
PublicDependencyModuleNames或PrivateDependencyModuleNames中添加M3,如果引用M3中功能的代码是在M1的Public源码中,则加到PublicDependency里,反之,如果是在M1的Private源码中引用M3中的功能,则加到PrivateDependency里。如果在M1的Public和Private源码中均引用了M3中的功能,则应该把M3同时添加到PublicDependency和PrivateDependency中。
上面对DependencyModule的设置对应toolchain里的静态链接,即保证M1对M3中的对象或功能的引用能找到实现。但是它不完全解决头文件包含层面的问题。不完全的意思是有时候不用处理头文件包含的事情也没问题发生,有时候不处理头文件包含的事情就有问题发生。不用处理也没有问题发生的情形是:M3中的功能实现,其对应的头文件都加到了M3的PublicIncludePaths中,那么M1中引用M3的该功能时,不用额外处理头文件包含事宜。相应的,有问题的情形是:M3中实现了某个功能,但其相关头文件没有加入到M3的PublicIncludePaths中。此时M1的某个cpp中调用M3里的一个函数时,编译器会报告找不到函数声明。这种有问题的情况,可以通过将M3中相应的路径加到M1的PublicIncludePaths或PrivateIncludePaths中来解决:
假设M3的某项功能实现在M3/Private中。当在M1的Public源码中使用M3的这项功能时,可将M3/Private添加到M1的PublicIncludePaths中。当在M1的Private源码中使用M3的这项功能时,可将M3/Private添加到M1的PrivateIncludePaths中。同时,在cpp中需用#include "M3/Private/xxx.h"来引用头文件。
PrivateIncludePaths.AddRange( new string[] { // ... add other private include paths required here ... System.IO.Path.Combine(GetModuleDirectory("LevelEditor"), "Private"), } );
还有一种需求:引用第三方module里的C++模板定义或抽象接口等,一般就是一些类模板和函数模板的C++头文件。这些头文件由于只包括模板或抽象接口,所以是功能完备且独立的,此种需求下,我们只需要访问这些模板或抽象接口,不需要除此之外的整个第三方module,那就可以设置PrivateIncludePathModuleNames和PublicIncludePathModuleNames这两个属性。这两个属性给予对第三方module里某些路径的代码访问权限,同时不会将第三方module的dll或lib链接到最终的exe中。
module的build.cs文件里的bPrecompile要设置为true
当以二进制方式发布插件时,对于4.22.0(不包括4.22.0)之后的版本,需要显式的设置
bPrecompile = true
然后对插件进行package操作,这样在插件的Intermediate目录中会生成对应的.precompiled文件,该文件用于指明obj文件。
发布后,还需要设置:
bUsePrecompiled = true
这样,用户端就能直接Link precompiled文件中指明的obj了,不依赖插件源码来打包。
在UE4.27里经过实测:package出来的Plugins,可以删除Intermediate目录,可以删除Binaries目录下的pdb文件,在module.build.cs里需要添加bUsePrecompiled=true;如此操作后,即便在visual studio里rebuild工程,也不会将插件rebuild,因此是安全的,但是rebuild操作会将插件Binaries目录下的dll文件删除,因此rebuild后需要将这些被删除的dll还原,无需其他操作。且整个过程中bPrecompile字段不是必须的。但与此同时,在保持这些设置的前提下,对整个工程进行打包时,又会提示找不到precompiled manifest文件,因此针对打包工程的需求,仍然需要插件的Intermediate目录。
module.build.cs文件常见字段
string path = Path.Combile(p1, p2, p3);
string absolutePath = Path.GetFullPath(path);
string absolutePath = Path.GetFullPath(path);
//https://docs.unrealengine.com/4.27/en-US/ProductionPipelines/BuildTools/UnrealBuildTool/ThirdPartyLibraries/
RuntimeDependencies.Add(Path.GetFullPath(absolutePath), StagedFileType.NonUFS);
RuntimeDependencies.Add(Path.GetFullPath(absolutePath), StagedFileType.NonUFS);
Public/PrivateDependencyModuleNames属于静态链接,通常其Public/PrivateIncludePaths会自行处理正确。
Public/PrivateIncludePathModuleNames与DynamicallyLoadedModuleNames配合属于动态链接,因此如果你使用了非抽象函数,则会提示无法找到链接符号。这一对配置的典型用法参考 #474,一般就是LoadModuleChecked,然后调用接口的抽象函数。
Public/PrivateIncludePathModuleNames与DynamicallyLoadedModuleNames配合属于动态链接,因此如果你使用了非抽象函数,则会提示无法找到链接符号。这一对配置的典型用法参考 #474,一般就是LoadModuleChecked,然后调用接口的抽象函数。
Details视图定制
一个Actor或Property的Details信息的展示界面,可以定制。这项工作涉及几个基本的接口:
- IDetailsView,其派生自SCompoundWidget。能看到的Details界面就是这个接口的实例。
- SDetailsView->SDetailsViewBase->IDetailsViewPrivate->IDetailsView
- FPropertyEditorModule::CreateDetailView(const FDetailsViewArgs& DetailsViewArgs); // 用户填写参数后调用该函数拿到一个IDetailsView实例。
- IDetailLayoutBuilder接口是一个工具类,它定义了一套支持Category的显示控制、Property的显示控制、隐藏等等功能的接口。至于这些功能的实际表现由具体实现来完成
- IDetailLayoutBuilder接口在UE4里有一个实现:FDetailLayoutBuilderImpl。这个实现按照一套确定的风格完成了上面说的功能概念,UE4里如上功能均由该实现来完成
- UE4额外提供了一个IDetailCustomization接口,用户需要实现其CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)函数,目的是集中在这里将IDetailLayoutBuilder需要的数据输入给它。这也是通常用户要定制Details视图时唯一需要实现的接口。
- FPropertyEditorModule::RegisterCustomClassLayout()实现将IDetailCustomization信息提交给引擎的功能。
以上需要引用【PropertyEditor】模块。
代码示例:
针对特定Class类型的所有实例定制其Details面板的情形:
针对特定Class类型的所有实例定制其Details面板的情形:
StarupModule() { FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>(TEXT("PropertyEditor")); // Custom Class Layouts auto AddCustomClass = [this, InPropertyModule = &PropertyModule](FName Name, FOnGetDetailCustomizationInstance InstanceGetter) { InPropertyModule->RegisterCustomClassLayout(Name, InstanceGetter); CustomClassLayoutNames.Add(Name); }; AddCustomClass(ASURACameraDriver::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FSURACameraDriverDetailsCustomization::MakeInstance)); }
ShutdownModule() { if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) { FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
for (FName ClassName : CustomClassLayoutNames) { PropertyModule.UnregisterCustomClassLayout(ClassName); } CustomClassLayoutNames.Reset(); } }
// .h TArray<FName> CustomClassLayoutNames;
针对特定Class类型的某个实例定制Details面板。需要在该实例对应的IDetailsView中设置,详见代码 Customizations/SURACameraDriverDetailsCustomization代码
IDetailsView面板与UObject建立关联:
TSharedRef<SWidget> SSURAAR_MainWnd::MakeSURACameraDriverDetails( ) { FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>(TEXT("PropertyEditor"));
FDetailsViewArgs Args; Args.bAllowSearch = false; // 隐藏Details面板的搜索框 Args.NameAreaSettings = FDetailsViewArgs::HideNameArea; // 隐藏Details面板的角色名框 TSharedRef<IDetailsView> View = PropertyModule.CreateDetailView(Args); View->RegisterInstancedCustomPropertyLayout(ASURACameraDriver::StaticClass(), FOnGetDetailCustomizationInstance::CreateStatic(&FSURACameraDriverDetailsCustomization::MakeInstance));
TArray<AActor*> OutActors; if (GWorld) { UGameplayStatics::GetAllActorsOfClass(GWorld, ASURACameraDriver::StaticClass(), OutActors); } if (OutActors.Num() > 0) { View->SetObject(OutActors[0], true); }
return View; }