编辑器扩展:自定义PlaceActors

前言

image-20210813111004071

有的时候, 为了方便项目需求, 我们需要自定义PlaceActors标签以及对应的类

本文记录如何实现这一扩展

自定义标签

开始之前我们需要了解一下UE4编辑器模块的启动顺序, 可以参考之前的一篇文章 UE4模块启动顺序

关键的一点是, 我们对编辑器添加新的PlaceActors标签需要在编辑器初始化之后, 因为IPlacementModeModule::Get()会启动PlacementMode模块, 而该模块启动时需要构造FPlaceableItem对象, FPlaceableItem对象构造时需要依赖GEditor->FindActorFactoryByClass()函数, 该函数实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
UActorFactory* UEditorEngine::FindActorFactoryByClass( const UClass* InClass ) const
{
for( int32 i = 0 ; i < ActorFactories.Num() ; ++i )
{
UActorFactory* Factory = ActorFactories[i];

if( Factory != NULL && Factory->GetClass() == InClass )
{
return Factory;
}
}

return NULL;
}

ActorFactories数组会在UEditorEngine::InitEditor()时完成, 意味着我们注册标签必须在编辑器初始化完成之后.

所以我们实现自定义PlaceActors标签的插件模块必须是PostEngineInit, 或者我们还有一个更安全的办法, 即通过监听编辑器初始化完成的回调事件来实现

1
FCoreDelegates::OnPostEngineInit.AddRaw(this, &FAdvancedFrameworkEditorModule::RegisterPlaceActors);
1
2
3
4
5
6
7
8
void FAdvancedFrameworkEditorModule::RegisterPlaceActors()
{
FPlacementCategoryInfo MyCategoryInfo(FText::FromString("AdvancedFramework"), "AdvancedFrameworkActors", "AdvancedFrameworkTags", 99);
if (IPlacementModeModule::Get().IsAvailable())
{
IPlacementModeModule::Get().RegisterPlacementCategory(MyCategoryInfo); IPlacementModeModule::Get().RegisterPlaceableItem(MyCategoryInfo.UniqueHandle, MakeShareable(new FPlaceableItem(*UActorFactoryEmptyActor::StaticClass())));
}
}

这里我们先添加一个叫AdvancedFrameworkActors的标签, 然后使用引擎默认的EmptyActor来先添加一个对象

先看一下数据结构FPlacementCategoryInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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;
};

我们显示的名称是构造函数的第二个字符串, 然后第4个参数决定我们的排序优先级, 我们打算放到最下面所以设置成了99

添加对象

接下来要看的是RegisterPlaceableItem()

1
2
3
4
5
6
7
8
9
10
11
TOptional<FPlacementModeID> FPlacementModeModule::RegisterPlaceableItem(FName CategoryName, const TSharedRef<FPlaceableItem>& InItem)
{
FPlacementCategory* Category = Categories.Find(CategoryName);
if (Category && !Category->CustomGenerator)
{
FPlacementModeID ID = CreateID(CategoryName);
Category->Items.Add(ID.UniqueID, InItem);
return ID;
}
return TOptional<FPlacementModeID>();
}

需要传入一个FPlaceableItem对象, 再来看FPlaceableItem的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
FPlaceableItem(UActorFactory* InFactory, const FAssetData& InAssetData, TOptional<int32> InSortOrder = TOptional<int32>());

FPlaceableItem(UClass& InAssetClass, TOptional<int32> InSortOrder = TOptional<int32>());

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>()
)

理论上讲上述构造函数都可以使用, 但是我们本着能方便就方便, 能偷懒就偷懒的原则, 先去看一看引擎自己咋使用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//PlacementModeModule.cpp  L123
FPlacementCategory* Category = Categories.Find(CategoryName);
Category->Items.Add(CreateID(), MakeShareable(new FPlaceableItem(*UActorFactoryEmptyActor::StaticClass(), SortOrder += 10)));
Category->Items.Add(CreateID(), MakeShareable(new FPlaceableItem(*UActorFactoryCharacter::StaticClass(), SortOrder += 10)));
Category->Items.Add(CreateID(), MakeShareable(new FPlaceableItem(*UActorFactoryPawn::StaticClass(), SortOrder += 10)));
Category->Items.Add(CreateID(), MakeShareable(new FPlaceableItem(*UActorFactoryPointLight::StaticClass(), SortOrder += 10)));
Category->Items.Add(CreateID(), MakeShareable(new FPlaceableItem(*UActorFactoryPlayerStart::StaticClass(), SortOrder += 10)));
// Cube
Category->Items.Add(CreateID(), MakeShareable(new FPlaceableItem(*UActorFactoryBasicShape::StaticClass(), FAssetData(LoadObject<UStaticMesh>(nullptr, *UActorFactoryBasicShape::BasicCube.ToString())), FName("ClassThumbnail.Cube"), BasicShapeColorOverride, SortOrder += 10, NSLOCTEXT("PlacementMode", "Cube", "Cube"))));
// Sphere
Category->Items.Add(CreateID(), MakeShareable(new FPlaceableItem(*UActorFactoryBasicShape::StaticClass(), FAssetData(LoadObject<UStaticMesh>(nullptr, *UActorFactoryBasicShape::BasicSphere.ToString())), FName("ClassThumbnail.Sphere"), BasicShapeColorOverride, SortOrder += 10, NSLOCTEXT("PlacementMode", "Sphere", "Sphere"))));
// Cylinder
Category->Items.Add(CreateID(), MakeShareable(new FPlaceableItem(*UActorFactoryBasicShape::StaticClass(), FAssetData(LoadObject<UStaticMesh>(nullptr, *UActorFactoryBasicShape::BasicCylinder.ToString())), FName("ClassThumbnail.Cylinder"), BasicShapeColorOverride, SortOrder += 10, NSLOCTEXT("PlacementMode", "Cylinder", "Cylinder"))));
// Cone
Category->Items.Add(CreateID(), MakeShareable(new FPlaceableItem(*UActorFactoryBasicShape::StaticClass(), FAssetData(LoadObject<UStaticMesh>(nullptr, *UActorFactoryBasicShape::BasicCone.ToString())), FName("ClassThumbnail.Cone"), BasicShapeColorOverride, SortOrder += 10, NSLOCTEXT("PlacementMode", "Cone", "Cone"))));
// Plane
Category->Items.Add(CreateID(), MakeShareable(new FPlaceableItem(*UActorFactoryBasicShape::StaticClass(), FAssetData(LoadObject<UStaticMesh>(nullptr, *UActorFactoryBasicShape::BasicPlane.ToString())), FName("ClassThumbnail.Plane"), BasicShapeColorOverride, SortOrder += 10, NSLOCTEXT("PlacementMode", "Plane", "Plane"))));

Category->Items.Add(CreateID(), MakeShareable(new FPlaceableItem(*UActorFactoryTriggerBox::StaticClass(), SortOrder += 10)));
Category->Items.Add(CreateID(), MakeShareable(new FPlaceableItem(*UActorFactoryTriggerSphere::StaticClass(), SortOrder += 10)));

看上述代码, 所以引擎自己都使用一个UActorFactory对象, 观察这个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

UCLASS(collapsecategories, hidecategories=Object, editinlinenew, config=Editor, abstract, transient)
class UNREALED_API UActorFactory : public UObject
{
GENERATED_UCLASS_BODY()

/** Name used as basis for 'New Actor' menu. */
UPROPERTY()
FText DisplayName;

/** Indicates how far up the menu item should be. The higher the number, the higher up the list.*/
UPROPERTY(config)
int32 MenuPriority;

/** name of actor subclass this actorfactory creates - dynamically loaded. Overrides NewActorClass. */
UPROPERTY(config)
FString NewActorClassName;

/** AActor subclass this ActorFactory creates. */
UPROPERTY()
TSubclassOf<class AActor> NewActorClass;
//...........
};

看了一些引擎的实现, 重点就是上面这些成员变量, 以及部分虚函数的实现, 比如

1
2
virtual bool CanCreateActorFrom( const FAssetData& AssetData, FText& OutErrorMsg );
virtual AActor* SpawnActor( UObject* Asset, ULevel* InLevel, const FTransform& Transform, EObjectFlags ObjectFlags, const FName Name );

其实一些类比如骨骼模型SkeletalMesh, 直接拖拽到编辑器世界空间中他会创建一个Actor来完成骨骼模型的显示, 而我们其实可以不需要这一步骤(如果我们的对象本身就是一个普通的actor)

所以可以这样添加

1
IPlacementModeModule::Get().RegisterPlaceableItem(MyCategoryInfo.UniqueHandle, MakeShareable(new FPlaceableItem(nullptr, FAssetData(XXX:StaticClass()))));

我这边在项目设置里丢进去一个数组TArray<TSubclassOf<AActor>> PlacementActors;

那就可以动态的扩展这一个标签下的显示对象

1
2
3
4
5
6
7
8
9
10
11
if(UAF_GameSetting_Default* st = UAF_BlueprintLibrary::GetDefaultSetting())
{
if (st->PlacementActors.Num()>0)
{
for (auto i : st->PlacementActors)
{
IPlacementModeModule::Get().RegisterPlaceableItem(MyCategoryInfo.UniqueHandle, MakeShareable(new FPlaceableItem(nullptr, FAssetData(i))));
}
}
}
IPlacementModeModule::Get().RegisterPlaceableItem(MyCategoryInfo.UniqueHandle, MakeShareable(new FPlaceableItem(*UAF_ActorFactory::StaticClass())));

最后实现效果如下

image-20210813110941324

image-20210813111004071