UE4 Asset Editor Preview

UE4 Asset Editor Preview

1 Overview


If the custom asset has some properties that are needed to be previewed (ex. animation, mesh…), then you should create a viewport to present these properties.

To create a viewport, there are serveral key classes:

  • FPreviewScene : The class to maintain the preview world, actor, light and so on.

  • FEditorViewportClient : The class to control the logic of FPreviewScene, such as actors’ location.

  • SViewportToolBar : The viewport toolbar widget that is placed in a viewport

  • SEditorViewport : The class to maintain the widget layout, which will create FEditorViewportClient and SViewportToolBar.

In this case, if you want to create a preview tab in the asset editor, you only need to create a tab body widget class to maintain your SEditorViewport class. The flow to do this is given blew.


2 FPreviewScene Class

Create your own preview scene class, derived form 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);

    ...
	
}

In this preview scene class, you can setup a default preview world, required in your own asset editor.

Preview


3 FEditorViewportClient


Create your own viewport client class, derived form FEditorViewportClient.

This class will allow you to access Draw and Tick function.

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);
	}
	...
}

This class can be used to control the objects inside the preview world.


4 SViewportToolBar Class

The class, derived from SViewportToolBar, is used to customize your viewport toolbar.

You can create a sub class of SCommonEditorViewportToolbarBase to customize from build-in tool bar.

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

Notice that if you use SCommonEditorViewportToolbarBase class, your SEditorViewport class should override the interface class ICommonEditorViewportToolbarInfoProvider, which will be shown in next section.


5 SEditorViewport Class


Create your own editor viewport class, derived form SEditorViewport.

This class is the key class to show viewport and maintain layout. FEditorViewportClient and SViewportToolBar class are created by this class as well.

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 Tab Factory


As mentioned in the previous article, each tab should have a tab factory class to generate tab body.


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 Spawn Tab Body


We need to create tab widget body for preview tab.

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()
				]
			]
		];
}

Finally, we create the tab body in the asset editor.


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

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

Notice that you can create a manager class to maintain the preview scene and actors inside.


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

Follow the above steps, we can create a preview tab as follow.

PreviewTab





Tags: UE4 Editor Vistied:
Share: Twitter Facebook LinkedIn