下边我们在自定义资源中创建一个预览视窗,并通过设置面板数据修改预览视窗中的显示内容。
如何创建自定义资源请参考前面的自定义资源拓展的文章。
这里我们需要创建两个类,分别继承FEditorViewClient和和SEditorViewport。继承FEditorViewClient负责管理视窗对象,SEditorViewport是一个Slate类,所以继承SEditorViewport的类会将视窗显示在UI界面。
我们先理顺一下他们的构造逻辑,首先当打开自定义资源时,执行自定义资源的Toolkit对象,然后生成一个标签页。标签页包含的Slate对象是SEditorViewport,它负责将我们的视窗显示在UI界面上,所以SEditorViewport在执行Construct方法时需要先创建一个FEditorViewClient对象,然后将其绘制。
在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
在视口中生成一个物体。
PreviewScene->AddComponent
在视口中添加一个组件。
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。