对于某些自定义资源,可能需要预览功能,如动画,Mesh资源等,则需要创建一个编辑器视口来显示这些内容。对于预览视窗的创建,有几个关键类:
FPreviewScene : 用于维护预览世界里的对象,光照等等。
FEditorViewportClient : 用于控制FPreviewScene中的逻辑,如Actor的位置等。
SViewportToolBar : 预览窗口上的工具栏,如缩放、相机移动速度等等。
SEditorViewport : 用于维护布局,并控制FEditorViewportClient和SViewportToolBar的创建。
创建流程如下。
派生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);
...
}
预览场景类中可以按需创建默认对象,光照设置等等。
派生 FEditorViewportClient类,该类可以重载 Draw 和 Tick 函数。
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函数添加不同更新操作。
通过派生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);
}
若从SCommonEditorViewportToolbarBase类派生,则SEditorViewport派生类需要实现接口 ICommonEditorViewportToolbarInfoProvider,下一节中会提到。
派生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)
;
}
同样,我们需要增加一个页签工厂,来生成页签体,如下:
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");
}
引擎内置并没有现成的方法,因此需要定制一个页签实体,如下
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();
}
通过以上方式,我们便可以创建出如下的预览窗口,预览窗口里的内容,可以按需求定制。