【UE4】编辑器开发(六)自定义预览视口

下边我们在自定义资源中创建一个预览视窗,并通过设置面板数据修改预览视窗中的显示内容。
如何创建自定义资源请参考前面的自定义资源拓展的文章。

一、创建一个简单的预览视窗

这里我们需要创建两个类,分别继承FEditorViewClient和和SEditorViewport。继承FEditorViewClient负责管理视窗对象,SEditorViewport是一个Slate类,所以继承SEditorViewport的类会将视窗显示在UI界面。
我们先理顺一下他们的构造逻辑,首先当打开自定义资源时,执行自定义资源的Toolkit对象,然后生成一个标签页。标签页包含的Slate对象是SEditorViewport,它负责将我们的视窗显示在UI界面上,所以SEditorViewport在执行Construct方法时需要先创建一个FEditorViewClient对象,然后将其绘制。

FEditorViewClient_CustomAsset

在EditorViewClient的头文件中声明有参构造器和CustomAsset变量。

public:
	FCustAssetViewportClient(UCustomAsset* InCustomAsset, FPreviewScene& InPreviewScene);	//构造器
	
	virtual void Tick(float DeltaSeconds) override;		//复写Tick函数
public:
	class UCustomAsset* CustomAsset;	//自定义资源对象
FEditorViewportClient_CustAsset(UCustAsset* InCustomAsset, FPreviewScene& InPreviewScene)
	: FEditorViewportClient(nullptr, &InPreviewScene)	//PreviewScene是我们要绘制的视窗对象,将其赋值给PreviewScene
	, CustomAsset(InCustomAsset)						//赋值自定义资源对象
{
	((FAssetEditorModeManager*)ModeTools)->SetPreviewScene(PreviewScene);	//将PreviewScene赋值给编辑管理器
	SetRealtime(true);									//实时绘制
	UClass* MyActorBP = LoadClass<AActor>(NULL, TEXT("Blueprint'/Game/NewBlueprint.NewBlueprint_C'"));
	if (MyActorBP)	//在视口中生成一个物体
		PreviewScene->GetWorld()->SpawnActor<AActor>(MyActorBP, FVector(0.f, 0.f, 0.f), FRotator(0.f, 0.f, 0.f));
	SetViewLocation(FVector(0.f, 0.f, 100.f));			//设置视口中用户摄像头的位置
}

void Tick(float DeltaSeconds)
{
	FEditorViewportClient::Tick(DeltaSeconds);		//执行父类的Tick方法
	PreviewScene->GetWorld()->Tick(LEVELTICK_All, DeltaSeconds);	//执行PreviewScene世界中所有组件的Tick
}

((FAssetEditorModeManager*)ModeTools)->SetPreviewScene(PreviewScene);
通过编辑管理器我们可以获取场景中选中的物体或对象。
SetRealTime(true)
将SetRealTime设为true,视口内容将实时绘制,最明显的用户感受就是,当操作视口中摄像机时,不会有卡顿。
SetViewLocation(FVector(0.f, 0.f, 100.f));
设置进入视口后,用户摄像机的位置。
PreviewScene->GetWorld()->SpawnActor
在视口中生成一个物体。
视口中生成actor
PreviewScene->AddComponent
在视口中添加一个组件。

SEditorViewport_CustomAsset

class ASSETEDITOR_API SEditorViewport_CustAsset : public SEditorViewport
{
public:
	SLATE_BEGIN_ARGS(SEditorViewport_CustAsset)
	{}
	SLATE_ATTRIBUTE(class UCustAsset*, CustomAsset)
	SLATE_END_ARGS()

	void Construct(const FArguments& InArgs);
	virtual TSharedRef<FEditorViewportClient> MakeEditorViewportClient() override;	//通过此方法创建并返回一个PreviewClient对象,然后将此对象包含的视窗内容进行绘制。
private:
	class UCustAsset* CustomAsset;		//自定义资源对象
	TSharedPtr<class FEditorViewportClient_CustAsset> CustomAssetPreviewClient;		//PreviewportClient指针
	TSharedPtr<class FPreviewScene> PreviewScene;		//PreviewScene指针,视窗可视化内容
};
void Construct(const FArguments& InArgs)
{
	CustomAsset = InArgs._CustomAsset.Get();		//赋值自定义资源对象
	PreviewScene = MakeShareable(new FPreviewScene());	//创建一个预览视窗对象
	//执行父类构造方法,此处会执行MakeEditorViewportClient方法,会使用到我们上边初始化的两个变量进行构造视窗client
	SEditorViewport::Construct(SEditorViewport::FArguments());
}
//创建并返回一个视窗Client对象,就是上面我们定义的FEditorViewportClient_CustAsset类
TSharedRef<FEditorViewportClient> MakeEditorViewportClient()
{
	if (!CustomAssetPreviewClient.IsValid())		//我们也可以在其他地方创建对象,然后传入参数后,在这里返回
		CustomAssetPreviewClient = MakeShareable(new FEditorViewportClient_CustAsset(CustomAsset, *PreviewScene));
	return CustomAssetPreviewClient.ToSharedRef();
}

注册自定义资源的标签页

在自定义资源的Toolkit类中,注册一个包含SEditorViewport_CustomAsset的Slate标签页,然后在布局中也将此标签页添加进去。

TabManager->RegisterTabSpawner("PreviewViewportSlate", FOnSpawnTab::CreateLambda(
	[&](const FSpawnTabArgs& Args)
	{
		return SNew(SDockTab)
			[
				SNew(SEditorViewport_CustAsset).CustomAsset(CustAsset)
			];
	}
));
const TSharedRef<FTabManager::FLayout> StandaloneEdtiorLayout = FTabManager::NewLayout("CustAssetEditorLayout_Layout")
		->AddArea
		(
			FTabManager::NewPrimaryArea()->Split
			(
				FTabManager::NewStack()->AddTab("PreviewViewportSlate", ETabState::OpenedTab)
			)
		);

最终,我们创建了一个如下所示的预览视窗:
自定义视窗

二、通过属性面板修改场景内容

我们先在资源类中声明一个变量,并将属性面板添加到资源编辑器窗口中。

声明资源类变量

第一步就是在CustAsset类中声明一个变量,就是通过修改这个变量来修改场景中的内容。其次还要声明一个单播委托,当属性值修改时,通知视窗client进行更新。这里的代码只是为了说明,格式需要自己去规范。

DECLARE_DELEGATE(FOnAssetPropertyChanged)

#if WITH_EDITOR
	virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
	{
		OnAssetPropertyChanged.ExecuteIfBound();	//当属性修改时,通知视窗进行内容更新
	}#endif
public:
	UPROPERTY(EditAnywhere)
	TSubclassOf<AActor> ActorClass;				//在场景中生成的蓝图对象
public:
	FOnAssetPropertyChanged OnAssetPropertyChanged;

将资源类属性面板添加到资源编辑器窗口

我们在资源类的Toolkit中,将属性面板注册为一个tab页。当然,还要将其添加到页面布局中,这个代码省略,可以参考上面代码或者编辑器开发(四)

InTabManager->RegisterTabSpawner(FName("CustAsset Property"), FOnSpawnTab::CreateLambda(
		[&](const FSpawnTabArgs& Args)
	{	//加载属性编辑器模块
		FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");	
		FDetailsViewArgs DetailsViewArgs;
		//创建属性编辑器的Slate
		TSharedRef<IDetailsView> AssetProperty = PropertyEditorModule.CreateDetailView(DetailsViewArgs);
		//将对象传入,这样就是自动生成对象的属性面板
		AssetProperty->SetObject(CustAsset);
		return SNew(SDockTab)
			[	//将自定义资源类的属性面板注册到一个Tab容器
				AssetProperty
			];
	}
	));

这样,我们就在资源编辑器窗口中添加了一个属性面板,并将ActorClass属性映射过来。
在这里插入图片描述

修改视窗内容

当构造ViewportClient时,先根据资源对象初始化视窗内容,然后绑定属性修改委托。

if (IsValid(CustAsset) && CustAsset->ActorClass)	//如果资源对象的变量有效,则创建视窗内容
	//获取到预览视窗的World,然后在其中创建一个Actor
	ActorInWorld = PreviewScene->GetWorld()->SpawnActor<AActor>(CustAsset->ActorClass, FVector::ZeroVector, FRotator::ZeroRotator);
else
	ActorInWorld = nullptr;			//如果不创建内容,为防止野指针,将变量置空
//绑定属性修改委托
CustAsset->OnAssetPropertyChanged.BindRaw(this, &FEditorViewportClient_CustAsset::OnAssetChanged);
void FEditorViewportClient_CustAsset::OnAssetChanged()
{
if (IsValid(ActorInWorld))
	ActorInWorld->Destroy();
if (IsValid(CustAsset) && CustAsset->ActorClass)
	ActorInWorld = PreviewScene->GetWorld()->SpawnActor<AActor>(CustAsset->ActorClass, FVector::ZeroVector, FRotator::ZeroRotator);
}

完成以上内容,当修改属性面板时,资源预览视窗的内容就可以进行修改了。
在这里插入图片描述

三、高级预览视窗

在上面显示的预览视窗,我们使用的是FPreviewScene类。但UE4编辑器中大部分的资源预览视窗都是用的是FAdvancedPreviewScene类,这个类同样继承FPreviewScene,所以它是一个封装程度更高的可视视窗类。下边,我们也使用此类来创建带有光照的预览视窗。
在SEditorViewport中重新构造一个FAdvancedPreviewScene对象,然后将其传递给ViewportClient。

void Construct(const FArguments& InArgs)
{
	CustAsset = InArgs._CustAsset.Get();
	
	FAdvancedPreviewScene::ConstructionValues ConstructValues;	//预览场景参数
	ConstructValues.SetCreatePhysicsScene(false);				//关闭物理场景
	ConstructValues.ShouldSimulatePhysics(false);				//关闭物理模拟
	ConstructValues.LightBrightness = 3;						//设置光照强度
	ConstructValues.SkyBrightness = 1;							//设置天空光强度
	ConstructValues.bEditor = true;

	AdvancedPreviewScene = MakeShareable(new FAdvancedPreviewScene(ConstructValues));

	//添加天空光组件
	USkyLightComponent* Skylight = NewObject<USkyLightComponent>();
	AdvancedPreviewScene->AddComponent(Skylight, FTransform::Identity);
	//添加大气雾组件
	UAtmosphericFogComponent* AtmosphericFog = NewObject<UAtmosphericFogComponent>();
	AdvancedPreviewScene->AddComponent(AtmosphericFog, FTransform::Identity);

	//设置地面
	AdvancedPreviewScene->SetFloorVisibility(true);		//显示地面,默认就是显示
	AdvancedPreviewScene->SetFloorOffset(100.f);		//设置地面高度偏移,正数是往下偏移
	//设置方向光
	AdvancedPreviewScene->DirectionalLight->SetMobility(EComponentMobility::Movable);
	AdvancedPreviewScene->DirectionalLight->CastShadows = true;				//启用阴影
	AdvancedPreviewScene->DirectionalLight->CastStaticShadows = true;
	AdvancedPreviewScene->DirectionalLight->CastDynamicShadows = true;
	AdvancedPreviewScene->DirectionalLight->SetIntensity(3);

	SEditorViewport::Construct(SEditorViewport::FArguments());
}

TSharedRef<FEditorViewportClient> MakeEditorViewportClient()
{
	ViewportClient = MakeShareable(new FAdvancedViewportClient_CustAsset(CustAsset, *AdvancedPreviewScene));
	return ViewportClient.ToSharedRef();
}

~SCustomAssetEditorViewport()
{
	if(CustomAssetPreviewClient.IsValid())
	{
		CustomAssetPreviewClient->Viewport = NULL;
	}
}

设置ViewportClient类

FAdvancedViewportClient_CustAsset::FAdvancedViewportClient_CustAsset(UCustAsset* InCustAsset, FAdvancedPreviewScene& InPreivewScene)
	: FEditorViewportClient(nullptr, &InPreivewScene)
	, CustAsset(InCustAsset)
{
	((FAssetEditorModeManager*)ModeTools)->SetPreviewScene(PreviewScene);
	SetRealtime(true);

	DrawHelper.bDrawGrid = false;		//是否绘制坐标网格,也可以使用SetShowGrid控制网格显隐的toggle
	DrawHelper.bDrawPivot = true;		//是否绘制支点

	SetViewLocation(FVector(-100.f, 0.f, 100.f));	//设置默认视角位置
	SetViewRotation(FRotator(-45.f, 0.f, 0.f));		//设置默认视角角度

	if (IsValid(CustAsset) && CustAsset->ActorClass)
		ActorInWorld = PreviewScene->GetWorld()->SpawnActor<AActor>(CustAsset->ActorClass, FVector::ZeroVector, FRotator::ZeroRotator);
	else
		ActorInWorld = nullptr;
	CustAsset->OnAssetPropertyChanged.AddRaw(this, &FAdvancedViewportClient_CustAsset::OnAssetChanged);
}

最后注册Tab标签页,将视窗Slate添加进去。下边左图是第一节创建的PreviewScene,右图就是我们创建的AdvancedPreviewScene。
PreviewScene对比

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值