UE4指定一个Asset的编辑器界面

目标

一个Asset指内容浏览器中的一个资源,双击它将打开对应的编辑器。例如:“材质”将对应“材质编辑器”,“蓝图”将对应“蓝图编辑器”。假如一个Asset没有特别定义编辑器,那么双击它会默认打开细节面板以编辑UProperty。如果想要自定义一个Asset的编辑器,则需要:

  1. 继承一个FAssetEditorToolkit类,其中有编辑器界面的代码。
  2. 在一个AssetIAssetTypeActions中的OpenAssetEditor中指定上一步创建的FAssetEditorToolkit

本篇的目标是新建一个插件,在其中创建一个自定义的Asset,然后定义其编辑器界面。代码主要参考了Niagara插件中的UNiagaraScript这个Asset的相关处理。

0. 创建插件

空白为模板创建插件,名字取为TestAssetEditorPlg
在这里插入图片描述

1.创建一个Asset

一个Asset,本质的数据就是一个UObject,但是用IAssetTypeActions注册成了一种资源。另外还需要一个对应的UFactory指定如何创建这个资源。

接下来创建他们的C++,文件都放在\Plugins\TestAssetEditorPlg\Source\TestAssetEditorPlg\Private\下。

1.1 创建Asset的Uobject

YaksueTestAsset.h

#pragma once

#include "UObject/Object.h"

#include "YaksueTestAsset.generated.h"

UCLASS(EditInlineNew, config = Engine)
class UYaksueTestAsset : public UObject
{
	GENERATED_UCLASS_BODY()
public:

	UPROPERTY(EditAnywhere)
	int TestData;
};

YaksueTestAsset.cpp

#include"YaksueTestAsset.h"

UYaksueTestAsset::UYaksueTestAsset(const FObjectInitializer & ObjectInitializer)
	: Super(ObjectInitializer)
{}
1.2 创建Asset的IAssetTypeActions

接下来需要创建一个继承自FAssetTypeActions_Base的类,有四个纯虚函数必须实现,否则:
在这里插入图片描述
YaksueTestAssetTypeActions.h

#pragma once

#include "AssetTypeActions_Base.h"

class FYaksueTestAssetTypeActions : public FAssetTypeActions_Base
{
public:
	//Asset的名字
	virtual FText GetName() const override;
	//Asset图标的颜色
	virtual FColor GetTypeColor() const override;
	//Asset的UObject是什么
	virtual UClass* GetSupportedClass() const override;
	//Asset所属的分类
	virtual uint32 GetCategories() override;
};

YaksueTestAssetTypeActions.cpp

#include"YaksueTestAssetTypeActions.h"
#include"YaksueTestAsset.h"

#define LOCTEXT_NAMESPACE "YaksueTest"

//Asset的名字
FText FYaksueTestAssetTypeActions::GetName() const
{
	return LOCTEXT("YaksueTestAssetTypeActions", "YaksueTestAsset");
}
//Asset图标的颜色
FColor FYaksueTestAssetTypeActions::GetTypeColor() const
{
	return FColor(0, 255, 255);
}
//Asset的UObject是什么
UClass * FYaksueTestAssetTypeActions::GetSupportedClass() const
{
	return UYaksueTestAsset::StaticClass();
}
//Asset所属的分类
uint32 FYaksueTestAssetTypeActions::GetCategories()
{
	return EAssetTypeCategories::Misc;
}

#undef LOCTEXT_NAMESPACE
1.3 创建Asset的UFactory

YaksueTestAssetFactory.h

#pragma once

#include "Factories/Factory.h"

#include "YaksueTestAssetFactory.generated.h"

UCLASS(config = Editor)
class UYaksueTestAssetFactory : public UFactory
{
    GENERATED_UCLASS_BODY()
public:
	// UFactory interface
	virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
	virtual uint32 GetMenuCategories() const override;
	// End of UFactory interface
};

YaksueTestAssetFactory.cpp

#include "YaksueTestAssetFactory.h"
#include"YaksueTestAsset.h"

#include "AssetTypeCategories.h"

#define LOCTEXT_NAMESPACE "YaksueTest"

UYaksueTestAssetFactory::UYaksueTestAssetFactory(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	bCreateNew = true;
	bEditAfterNew = true;//true if the associated editor should be opened after creating a new object
	SupportedClass = UYaksueTestAsset::StaticClass();
}

UObject* UYaksueTestAssetFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
	UYaksueTestAsset* CreatedAsset = NewObject<UYaksueTestAsset>(InParent, Class, Name, Flags | RF_Transactional);
	return CreatedAsset;
}

uint32 UYaksueTestAssetFactory::GetMenuCategories() const
{
	return EAssetTypeCategories::Misc;
}

#undef LOCTEXT_NAMESPACE
1.4 注册Asset

首先要在模块的描述文件(TestAssetEditorPlg.Build.cs)中的PrivateDependencyModuleNames添加一项:"UnrealEd"


然后在TestAssetEditorPlg.cpp中注册:
先:

#include"AssetToolsModule.h"
#include"YaksueTestAssetTypeActions.h"

其次在模块的启动函数中添加注册:

void FTestAssetEditorPlgModule::StartupModule()
{
	// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module

	IAssetTools& AssetTools = FModuleManager::LoadModuleChecked< FAssetToolsModule >("AssetTools").Get();
	AssetTools.RegisterAssetTypeActions(MakeShareable(new FYaksueTestAssetTypeActions()));
}

UFactory就不用注册了,我想可能它是继承自UObject的,拥有“反射”机制,因此已经自动处理了(此处不确定)。


最后就可以右键在内容浏览器创建这个资源了:
在这里插入图片描述
双击可以打开它的编辑器,默认只是细节面板显示它的UProperty
在这里插入图片描述

2. 自定义资源的编辑器界面

2.1 创建Toolkit

FAssetEditorToolkit有几个必须要继承的纯虚函数,如果不继承则会:
在这里插入图片描述
这些函数是这个Toolkit的基本信息。

YaksueTestAssetToolkit.h

#pragma once

#include "Toolkits/AssetEditorToolkit.h"

class FYaksueTestAssetToolkit : public FAssetEditorToolkit
{
public:

	//~ Begin IToolkit Interface
	virtual FName GetToolkitFName() const override;
	virtual FText GetBaseToolkitName() const override;
	virtual FString GetWorldCentricTabPrefix() const override;
	virtual FLinearColor GetWorldCentricTabColorScale() const override;
	//~ End IToolkit Interface

	//初始化,在AssetTypeActions::OpenAssetEditor中调用
	void Initialize(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, class UYaksueTestAsset* Asset);

	//注册Tab
	virtual void RegisterTabSpawners(const TSharedRef<class FTabManager>& TabManager) override;

	//生成TestTab时调用
	TSharedRef<SDockTab> SpawnTestTab(const FSpawnTabArgs& Args);

private:

	static const FName TestTabId;//Tab的ID
};

YaksueTestAssetToolkit.cpp

#include"YaksueTestAssetToolkit.h"

#include"Widgets/Docking/SDockTab.h"
#include"Widgets/Input/SButton.h"

#define LOCTEXT_NAMESPACE "YaksueTest"

const FName FYaksueTestAssetToolkit::TestTabId(TEXT("YaksueTestAssetEditor_TestTab"));

FName FYaksueTestAssetToolkit::GetToolkitFName() const
{
	return FName("YaksueTestAssetName");
}

FText FYaksueTestAssetToolkit::GetBaseToolkitName() const
{
	return LOCTEXT("YaksueTestAssetLabel", "YaksueTestAssetBase");
}

FString FYaksueTestAssetToolkit::GetWorldCentricTabPrefix() const
{
	return LOCTEXT("YaksueTestAssetTabPrefix", "YaksueTestAssetPrefix ").ToString();
}

FLinearColor FYaksueTestAssetToolkit::GetWorldCentricTabColorScale() const
{
	return FLinearColor(0,0.5f,0.6f,0.5f);
}


TSharedRef<SDockTab> FYaksueTestAssetToolkit::SpawnTestTab(const FSpawnTabArgs& Args)
{
	return
		SNew(SDockTab)
		[
			SNew(SVerticalBox)
			+SVerticalBox::Slot().AutoHeight()
			[
				SNew(SButton)
				.Text(FText::FromString("TestJoJo!"))
			]
		];
}

void FYaksueTestAssetToolkit::RegisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
	
	FAssetEditorToolkit::RegisterTabSpawners(InTabManager);

	InTabManager->RegisterTabSpawner(TestTabId, FOnSpawnTab::CreateSP(this, &FYaksueTestAssetToolkit::SpawnTestTab))
		.SetDisplayName(LOCTEXT("YaksueTestAssetTestTab", "YaksueTestAssetTestTab"));
}

void FYaksueTestAssetToolkit::Initialize(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UYaksueTestAsset* Asset)
{
	TSharedRef<FTabManager::FLayout> Layout = FTabManager::NewLayout("YaksueTestAssetEditor_Layout")
		->AddArea
		(
			FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical)
			->Split
			(
				FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)->SetSizeCoefficient(0.9f)
				->Split
				(
					FTabManager::NewStack()
					->SetSizeCoefficient(0.7f)
					->AddTab(TestTabId, ETabState::OpenedTab)
				)
			)
		);

	const bool bCreateDefaultStandaloneMenu = true;
	const bool bCreateDefaultToolbar = true;
	const FName AppIdentifier = TEXT("YaksueTestAssetEditor");
	FAssetEditorToolkit::InitAssetEditor(Mode, InitToolkitHost, AppIdentifier, Layout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, Asset);
}

#undef LOCTEXT_NAMESPACE

这里的内容看起来比较复杂,但他主要的逻辑实际上是指定这个Asset编辑器的布局,而其中有一个tab

  • 编辑器中的这个Tab的ID(其实是FName)为TestTabId
  • SpawnTestTab中指定了TestTabId这个Tab生成的UI是什么,这里就是一个按钮。
  • RegisterTabSpawners中注册了这个Tab,即将TestTabIdSpawnTestTab联系起来。这个函数是个虚函数。
  • Initialize中指定了编辑器的布局,就是添加了这个Tab。然后调用FAssetEditorToolkit的函数InitAssetEditor来指定这个Asset的编辑器。
2.2 在AssetTypeActions的OpenAssetEditor中指定编辑器

YaksueTestAssetToolkit.h中重载OpenAssetEditor虚函数:

virtual void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor = TSharedPtr<IToolkitHost>()) override;

然后在YaksueTestAssetToolkit.cpp中定义这个函数的内容

void FYaksueTestAssetTypeActions::OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<IToolkitHost> EditWithinLevelEditor)
{
	EToolkitMode::Type Mode = EToolkitMode::Standalone;

	for (auto ObjIt = InObjects.CreateConstIterator(); ObjIt; ++ObjIt)
	{
		auto Asset = Cast<UYaksueTestAsset>(*ObjIt);
		if (Asset != NULL)
		{
			TSharedRef< FYaksueTestAssetToolkit > NewToolkit(new FYaksueTestAssetToolkit());
			NewToolkit->Initialize(Mode, EditWithinLevelEditor, Asset);
		}
	}
}

可以看到效果实现了
在这里插入图片描述

3*. 测试编辑Asset

下面尝试用某种方式“编辑”一下这个Asset

首先在Toolkit中存放一个要编辑Asset的指针:

class UYaksueTestAsset* EditingAsset;//正在编辑的Asset

Initialize中赋值:

EditingAsset = Asset;

SpawnTestTab中指定界面的逻辑(这里就是给TestData递增):

TSharedRef<SDockTab> FYaksueTestAssetToolkit::SpawnTestTab(const FSpawnTabArgs& Args)
{
	return
		SNew(SDockTab)
		[
			SNew(SVerticalBox)
			+SVerticalBox::Slot().AutoHeight()
			[
				SNew(STextBlock)
				.Text(FText::FromString(FString::FromInt(EditingAsset->TestData)))
			]
			+SVerticalBox::Slot().AutoHeight()
			[
				SNew(SButton)
				.Text(FText::FromString("TestJoJo!"))
				.OnClicked_Lambda([this]() {EditingAsset->TestData++; return FReply::Handled(); })
			]
		];
}

效果:
在这里插入图片描述

注意,由于没有指定让界面刷新,所以再次打开时才可以看到变化

©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页