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.
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.
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.
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);
}
Notice that if you use SCommonEditorViewportToolbarBase class, your SEditorViewport class should override the interface class ICommonEditorViewportToolbarInfoProvider, which will be shown in next section.
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)
;
}
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");
}
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.