【转载】[UE4] 扩展 Place Actors(插件)

477 阅读4分钟

原文链接

[UE4] Extend Place Actors(Plugin) | 凡尘旧事

正文

扩展 UE4 的 PlaceActors 窗口,便于我们自定义的资产/工具(StaticMeshBP...)直接拖拽到窗口中。

在 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;
};
  1. FText InDisplayName:显示的名字就是和 RecentlyPlacedBasicLights... 并列的名字
  2. FName InHandle:分类的句柄
  3. FString InTag:不清楚有什么用,有大佬知道的还请告知
  4. int32 InSortOrder:分类所在的位置,默认 0 的话会排在 RecentlyPlaced 下方
  5. 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 中再找了。

Ref

  1. [UE4] レベルエディタの配置ツールに独自の項目とアセットを追加する方法|株式会社ヒストリア​

  2. UE4智能指针学习记录_YakSue的博客-CSDN博客​

  3. 云鸽:虚幻4:智能指针基础85 赞同 · 9 评论文章

  4. 玄冬Wong:[UE4]C++实现动态(Runtime)加载的问题:LoadClass()和LoadObject()13 赞同 · 3 评论文章