UE4自定义资源编辑器开发-预览窗口

UE4自定义资源编辑器开发-预览窗口

1 概述


对于某些自定义资源,可能需要预览功能,如动画,Mesh资源等,则需要创建一个编辑器视口来显示这些内容。对于预览视窗的创建,有几个关键类:

  • FPreviewScene : 用于维护预览世界里的对象,光照等等。

  • FEditorViewportClient : 用于控制FPreviewScene中的逻辑,如Actor的位置等。

  • SViewportToolBar : 预览窗口上的工具栏,如缩放、相机移动速度等等。

  • SEditorViewport : 用于维护布局,并控制FEditorViewportClient和SViewportToolBar的创建。

创建流程如下。


2 FPreviewScene

派生FPreviewScene,创建自己的预览场景类。

class  FTestPreviewScene : public FPreviewScene
{
public:
	FTestPreviewScene(ConstructionValues CVS);
    ...
};

FTestPreviewScene::FTestPreviewScene(ConstructionValues CVS):FPreviewScene(CVS)
{
    // world setting
	GetWorld()->GetWorldSettings()->NotifyBeginPlay();
	GetWorld()->GetWorldSettings()->NotifyMatchStarted();
	GetWorld()->GetWorldSettings()->SetActorHiddenInGame(false);
	GetWorld()->bBegunPlay = true;
	// set light options 
	DirectionalLight->SetRelativeLocation(FVector(-1024.f, 1024.f, 2048.f));
	DirectionalLight->SetRelativeScale3D(FVector(15.f));
    ...
	SetLightBrightness(4.f);
	DirectionalLight->InvalidateLightingCache();
	DirectionalLight->RecreateRenderState_Concurrent();

	// creae a sky sphere
	UStaticMeshComponent* SkyComp = NewObject<UStaticMeshComponent>();
	UStaticMesh * StaticMesh = LoadObject<UStaticMesh>(NULL, TEXT("/Engine/MapTemplates/Sky/SM_SkySphere.SM_SkySphere"), NULL, LOAD_None, NULL);
	SkyComp->SetStaticMesh(StaticMesh);
	UMaterial* SkyMaterial = LoadObject<UMaterial>(NULL, TEXT("/Engine/EditorMaterials/PersonaSky.PersonaSky"), NULL, LOAD_None, NULL);
	SkyComp->SetMaterial(0, SkyMaterial);
	const float SkySphereScale = 1000.f;
	const FTransform SkyTransform(FRotator(0, 0, 0), FVector(0, 0, 0), FVector(SkySphereScale));
	AddComponent(SkyComp, SkyTransform);

	....

	// now add floor
	UStaticMesh* FloorMesh = LoadObject<UStaticMesh>(NULL, TEXT("/Engine/EditorMeshes/EditorCube.EditorCube"), NULL, LOAD_None, NULL);
	UStaticMeshComponent* FloorComp = NewObject<UStaticMeshComponent>();
	FloorComp->SetStaticMesh(FloorMesh);
	AddComponent(FloorComp, FTransform::Identity);
	FloorComp->SetRelativeScale3D(FVector(3.f, 3.f, 1.f));
	UMaterial* Material = LoadObject<UMaterial>(NULL, TEXT("/Engine/EditorMaterials/PersonaFloorMat.PersonaFloorMat"), NULL, LOAD_None, NULL);
	FloorComp->SetMaterial(0, Material);
	FloorComp->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
	FloorComp->SetCollisionObjectType(ECC_WorldStatic);

    ...
	
}

预览场景类中可以按需创建默认对象,光照设置等等。

Preview


3 FEditorViewportClient


派生 FEditorViewportClient类,该类可以重载 DrawTick 函数。

class  FTestViewportClient : public FEditorViewportClient
{
public:
	...

	virtual void Draw(FViewport* InViewport, FCanvas* Canvas) override;

	virtual void Tick(float DeltaSeconds) override;

    ...
};

void FTestViewportClient::Tick(float DeltaSeconds)
{
    ...
	if (WorldDelta > 0.f)
	{
		PreviewScene->GetWorld()->Tick(LEVELTICK_All, DeltaSeconds);
	}
	...
}

该类用于控制预览场景中的对象,可在Tick或Draw函数添加不同更新操作。


4 SViewportToolBar

通过派生SViewportToolBar,可以定制预览窗口中的工具栏,如果想使用引擎默认的工具栏,那直接派生 SCommonEditorViewportToolbarBase类即可。

class STestEditorViewportToolBar : public SCommonEditorViewportToolbarBase //SViewportToolBar
{
public:
	SLATE_BEGIN_ARGS(STestEditorViewportToolBar) { }

	SLATE_END_ARGS()

	void Construct(const FArguments& InArgs, TSharedPtr<class STestPreviewViewport> InRealViewport);

    ...
};

void STestEditorViewportToolBar::Construct(const FArguments& InArgs, TSharedPtr<class STestPreviewViewport> InRealViewport)
{
	SCommonEditorViewportToolbarBase::Construct(SCommonEditorViewportToolbarBase::FArguments(), InViewport);
}

ViewportToolbar

若从SCommonEditorViewportToolbarBase类派生,则SEditorViewport派生类需要实现接口 ICommonEditorViewportToolbarInfoProvider,下一节中会提到。


5 SEditorViewport


派生SEditorViewport类来创建窗口类,该类主要用于显示窗口及维护布局。FEditorViewportClient及SViewportToolBar类都会由该类创建。

class  STestPreviewViewport : public SEditorViewport, public ICommonEditorViewportToolbarInfoProvider
{
public:
	SLATE_BEGIN_ARGS(STestPreviewViewport) {}
	SLATE_END_ARGS()

	//Toolbar interface
	virtual TSharedRef<class SEditorViewport> GetViewportWidget() override;
	virtual TSharedPtr<FExtender> GetExtenders() const override;
	virtual void OnFloatingButtonClicked() override;
    ...

protected:
    // Create viewport client and toolbar
	// SEditorViewport interface
	virtual TSharedRef<FEditorViewportClient> MakeEditorViewportClient() override;
	virtual TSharedPtr<SWidget> MakeViewportToolbar() override;	
	// End of SEditorViewport interface
    ...

private:
	TSharedPtr<FEditorViewportClient> LevelViewportClient;
};

TSharedRef<class SEditorViewport> STestPreviewViewport::GetViewportWidget()
{
	return SharedThis(this);
}

TSharedPtr<FExtender> STestPreviewViewport::GetExtenders() const
{
	TSharedPtr<FExtender> Result(MakeShareable(new FExtender));
	return Result;
}

void STestPreviewViewport::OnFloatingButtonClicked()
{
}

TSharedRef<FEditorViewportClient> STestPreviewViewport::MakeEditorViewportClient()
{
	LevelViewportClient = MakeShareable(new FTestViewportClient(*scene, context, SharedThis(this)));
	LevelViewportClient->ViewportType = LVT_Perspective;
	LevelViewportClient->bSetListenerPosition = false;
	...
	return LevelViewportClient.ToSharedRef();
}

TSharedPtr<SWidget> STestPreviewViewport::MakeViewportToolbar()
{
	return SNew(STestEditorViewportToolBar, SharedThis(this))
		.Cursor(EMouseCursor::Default)
		;
}


6 页签工厂


同样,我们需要增加一个页签工厂,来生成页签体,如下:


struct FTestPreviewSummoner : public FWorkflowTabFactory
{
public:
	FTestPreviewSummoner(TSharedPtr<class FTestEditor> InTestEditorPtr);

	virtual TSharedRef<SWidget> CreateTabBody(const FWorkflowTabSpawnInfo& Info) const override;
	virtual FText GetTabToolTipText(const FWorkflowTabSpawnInfo& Info) const override;

protected:
	TWeakPtr<class FTestEditor> TestEditorPtr;
};


FTestPreviewSummoner::FTestPreviewSummoner(TSharedPtr<class FTestEditor> InTestEditorPtr)
	: FWorkflowTabFactory(FTestEditorTabs::PreviewID, InTestEditorPtr)
	, TestEditorPtr(InTestEditorPtr)
{
	TabLabel = LOCTEXT("TestPreviewLabel", "Preview");
	TabIcon = FSlateIcon(FEditorStyle::GetStyleSetName(), "Kismet.Tabs.Components");
	bIsSingleton = true;
	ViewMenuDescription = LOCTEXT("TestPreview", "Priview");
	ViewMenuTooltip = LOCTEXT("TestPreview_ToolTip", "Show the preview tab");
}

TSharedRef<SWidget> FTestPreviewSummoner::CreateTabBody(const FWorkflowTabSpawnInfo& Info) const
{
	return TestEditorPtr.Pin()->SpawnPreview();
}

FText FTestPreviewSummoner::GetTabToolTipText(const FWorkflowTabSpawnInfo& Info) const
{
	return LOCTEXT("TestPreviewTabTooltip", "The preview tab allows displaying models");
}


7 创建页签体


引擎内置并没有现成的方法,因此需要定制一个页签实体,如下

class STestViewportTabBody : public SCompoundWidget
{
	SLATE_BEGIN_ARGS(STestViewportTabBody) {}

	SLATE_END_ARGS()
public:

	void Construct(const FArguments& InArgs,FTestPreviewScene& scene);

private:
	TSharedPtr<FEditorViewportClient> m_LevelViewportClient;
	TSharedPtr<STestPreviewViewport> m_ViewportWidget;
};

void STestViewportTabBody::Construct(const FArguments& InArgs, FTestPreviewScene& scene)
{
	m_ViewportWidget = SNew(STestPreviewViewport,  SharedThis(this), context,&scene);
	m_LevelViewportClient = m_ViewportWidget->GetViewportClient();
	TSharedPtr<SVerticalBox> ViewportContainer = nullptr;
	this->ChildSlot
		[
			SAssignNew(ViewportContainer, SVerticalBox)

			+ SVerticalBox::Slot()
			.VAlign(EVerticalAlignment::VAlign_Fill)
			.HAlign(EHorizontalAlignment::HAlign_Fill)
			.FillHeight(1)
			[
				SNew(SOverlay)
				// The viewport
				+ SOverlay::Slot()
				[
					m_ViewportWidget.ToSharedRef()
				]
			]
		];
}

最后,页签工厂里会调用下面方法,创建页签体。


//	TSharedPtr<class STestViewportTabBody> tabBody;
//	FTestPreviewScene PreviewScene;

TSharedRef<SWidget> FTestEditor::SpawnPreview()
{
	SAssignNew(tabBody, STestViewportTabBody, PreviewScene);
	return tabBody.ToSharedRef();;
}

由于预览窗口里的逻辑比较复杂,可以创建一个管理器来控制。


TSharedRef<SWidget> FTestEditor::SpawnPreview()
{
	return PreviewMgr.MakeViewportWidget();
}

通过以上方式,我们便可以创建出如下的预览窗口,预览窗口里的内容,可以按需求定制。

PreviewTab


链接




Tags: UE4 Editor Vistied:
Share: Twitter Facebook LinkedIn