原文链接
正文
扩展 UE4 的 PlaceActors 窗口,便于我们自定义的资产/工具(StaticMesh,BP...)直接拖拽到窗口中。
在 C++ 项目中新建一个空的 Plugin,名字就叫 MyPlugin ,创建好后目录如下:
先设置下 Plugin 的配置文件 MyPlugin.uplugin:
{
...
"CanContainContent": true,
...
"Modules": [
{
"Name": "MyPlugin",
"Type": "Editor",
"LoadingPhase": "PostEngineInit"
}
]
}
CanContainContent 设置为 true,一会我们要在 Content 中添加东西。
比较重要的是修改 加载策略 为 PostEngineInit
参见:加载策略
在 MyPlugin.cpp 文件中先注册一个 PlaceActor 的分类:
在源码中查询到 RegisterPlacementCategory 的方法:
/**
* Register a new category of placement items
*
* @param Info Information pertaining to the category
* @return true on success, false on failure (probably if the category's unique handle is already in use)
*/
virtual bool RegisterPlacementCategory(const FPlacementCategoryInfo& Info) = 0;
首先定义一个 CategoryInfo 然后进行注册:
...
// 添加 IPlacementModeModule 头文件
#include "PlacementMode/Public/IPlacementModeModule.h"
...
...
void FPlaceActorExtendModule::StartupModule()
{
// Register editor category
FPlacementCategoryInfo MyCategoryInfo(FText::FromString("MyCategory"), "MyCategory", "MyTags", 99);
IPlacementModeModule::Get().RegisterPlacementCategory(MyCategoryInfo);
}
查看 FPlacementCategoryInfo 的源码:
struct FPlacementCategoryInfo
{
FPlacementCategoryInfo(FText InDisplayName, FName InHandle, FString InTag, int32 InSortOrder = 0, bool bInSortable = true)
: DisplayName(InDisplayName), UniqueHandle(InHandle), SortOrder(InSortOrder), TagMetaData(MoveTemp(InTag)), bSortable(bInSortable)
{
}
/** This category's display name */
FText DisplayName;
/** A unique name for this category */
FName UniqueHandle;
/** Sort order for the category tab (lowest first) */
int32 SortOrder;
/** Optional tag meta data for the tab widget */
FString TagMetaData;
/** Optional generator function used to construct this category's tab content. Called when the tab is activated. */
TFunction<TSharedRef<SWidget>()> CustomGenerator;
/** Whether the items in this category are automatically sortable by name. False if the items are already sorted. */
bool bSortable;
};
FText InDisplayName:显示的名字就是和 RecentlyPlaced,Basic,Lights... 并列的名字FName InHandle:分类的句柄FString InTag:不清楚有什么用,有大佬知道的还请告知int32 InSortOrder:分类所在的位置,默认 0 的话会排在 RecentlyPlaced 下方bool bInSortable:规定了该分类下的 item 是否自动排序
- Category 顺序表:
OK,此时运行,可以看到 PlaceActor 下已经有了我们自定义的 MyCategory 分类,只是暂时还没有Item,接下来添加 Item。
这里我会添加 4 个 Item,
- 第一个是 Basic 分类中的
EmptyActor, - 第二个是 自定义 的一个 C++ 类,
- 第三个是 Content 中的 BP,
- 第四个是 Content 中的
StaticMesh。
通过查看源码中 IPlacementModelModule 类可以找到一个 Function :
/**
* Register a new placeable item for the specified category
*
* @param Item The placeable item to register
* @param CategoryName Unique handle to the category in which to place this item
* @return Optional unique identifier for the registered item, or empty on failure (if the category doesn't exist)
*/
virtual TOptional<FPlacementModeID> RegisterPlaceableItem(FName CategoryName, const TSharedRef<FPlaceableItem>& Item) = 0;
看一下 FPlaceableItem 的构造函数:
/** Default constructor */
FPlaceableItem()
: Factory(nullptr)
{}
/** Constructor that takes a specific factory and asset */
FPlaceableItem(UActorFactory* InFactory, const FAssetData& InAssetData,TOptional<int32> InSortOrder = TOptional<int32>())
: Factory(InFactory)
, AssetData(InAssetData)
, SortOrder(InSortOrder)
{
bAlwaysUseGenericThumbnail = false;
AutoSetDisplayName();
}
/** Constructor for any placeable class */
FPlaceableItem(UClass& InAssetClass, TOptional<int32> InSortOrder =TOptional<int32>())
: Factory(GEditor->FindActorFactoryByClass(&InAssetClass))
, AssetData(Factory ? Factory->GetDefaultActorClass( FAssetData() ) : FAssetData())
, SortOrder(InSortOrder)
{
bAlwaysUseGenericThumbnail = false;
AutoSetDisplayName();
}
/** Constructor for any placeable class with associated asset data, brush anddisplay name overrides */
FPlaceableItem(
UClass& InAssetClass,
const FAssetData& InAssetData,
FName InClassThumbnailBrushOverride = NAME_None,
TOptional<FLinearColor> InAssetTypeColorOverride = TOptional<FLinearColor>(),
TOptional<int32> InSortOrder = TOptional<int32>(),
TOptional<FText> InDisplayName = TOptional<FText>()
)
: Factory(GEditor->FindActorFactoryByClass(&InAssetClass))
, AssetData(InAssetData)
, ClassThumbnailBrushOverride(InClassThumbnailBrushOverride)
, AssetTypeColorOverride(InAssetTypeColorOverride)
, SortOrder(InSortOrder)
{
bAlwaysUseGenericThumbnail = true;
if (InDisplayName.IsSet())
{
DisplayName = InDisplayName.GetValue();
}
else
{
AutoSetDisplayName();
}
}
需要传入一个 CategoryName 和一个共享引用 TSharedRef<FPlaceableItem> ,OK,我们就用这个方法对 Item 进行注册,看一眼源码中是 如何注册 Item 的 ,以 Basic 中的 EmptyActor 为例:
FPlacementCategory* Category = Categories.Find(CategoryName);
Category->Items.Add(CreateID(), MakeShareable(new FPlaceableItem(*UActorFactoryEmptyActor::StaticClass(), SortOrder += 10)));
我们需要 重点 观察的就是:
MakeShareable(new FPlaceableItem(*UActorFactoryEmptyActor::StaticClass(), SortOrder += 10))
通过 MakeShareable() 辅助函数将 item 隐式转换为 TSharedRef
我们也添加一个 EmptyActor Item,在源码中找到 EmptyActor的头文件:
#include "ActorFactories/ActorFactoryEmptyActor.h"
注册 EmptyActor Item,排序为 1:
//Register item to category
IPlacementModeModule::Get().RegisterPlaceableItem(MyCategoryInfo.UniqueHandle,
MakeShareable(new FPlaceableItem(*UActorFactoryEmptyActor::StaticClass(), 1)));
因为用到了其他 Module ,所以我们要在 MyPlugin.Build.cs 中添加 UnrealEd 引用:
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"Core",
"UnrealEd",
// ... add private dependencies that you statically link with here ...
}
);
此时运行,就可以看到 MyCategory 下有了第一个 Item:EmptyActor
添加 自定义 的一个 C++ 类:
新建一个 C++ 类,继承 AActor,就叫 MyActor,注意 这个地方选择的是我们的插件,而不是我们的工程
在 MyActor.h 中随便添加一个变量:
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float length = 11.11f;
注册这个 item :
IPlacementModeModule::Get().RegisterPlaceableItem(MyCategoryInfo.UniqueHandle,
MakeShareable(new FPlaceableItem(nullptr, FAssetData(AMyActor::StaticClass()), 2)));
运行,item 多了一个 MyActor,拖拽到场景中,细节面板中有定义变量,添加成功。
接下来在插件的 Content 下新建 Folder :BluePrint,新建一个蓝图继承刚才创建的 MyActor,再建一个 StaticMesh Folder,里面随便放一个我们自己的 StaticMesh
我们 MyPlugin.h 中定义这两个 FAssetData :
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
#include "MyActor.h"
class FMyPluginModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
UClass* MyBPActor = LoadClass<AMyActor>(nullptr, TEXT("Blueprint'/MyPlugin/Blueprint/BP_MyActor.BP_MyActor_C'"));
UObject* MyStaticMesh = LoadObject<UObject>(nullptr, TEXT("StaticMesh'/MyPlugin/StaticMesh/SM_myStaticMesh.SM_myStaticMesh'"));
};
这里 注意 BP 路径后面需要加上 _C
注册:
IPlacementModeModule::Get().RegisterPlaceableItem(MyCategoryInfo.UniqueHandle,
MakeShareable(new FPlaceableItem(nullptr, MyBPActor, 3)));
IPlacementModeModule::Get().RegisterPlaceableItem(MyCategoryInfo.UniqueHandle,
MakeShareable(new FPlaceableItem(nullptr, MyStaticMesh, 4)));
OK,这样就可以在 PlaceActor 中随意使用我们自定义的 BP 或者 StaticMesh 等等
一些 BP 做的工具,比如
SplineMesh... 就方便使用,不用在 Content Browser 中再找了。