前言
Slate是构成UE引擎整套UI框架底层的基石,而我们平常蓝图开发的虽然是UMG一套,也就是继承自UWidget,可能感觉是与继承自SWidget的Slate开发流程有些区别,但是本质上UWidget也是采用的是SWidget这套流程,甚至我们再其中还能看到许多存储了SWidget的变量,如下图所示。区别是UWidget是面向用户层的,因此其中有很多方法都是反射到蓝图上使用的,在开发过程中,许多繁琐的UI用UMG实现起来实在是很困难,列如各自表图(类似于EChart中的图表数据),而且我们在开发编辑器插件过程中如果想使用一些UI来进行简单的交互,那Slate的学习不得不提早进入学习档期。虽然有很多文章,包括我之前也简单的对Slate进行了梳理,但是总觉得缺少了一些东西。
首先先打开WidgetReflector
常用的,就是获取绘制的UI(Hit-Testable)与获取可交互的UI(Painted)俩种模式
点击之后我们发现鼠标样式变了
按ECS键结束状态并选中当前鼠标所执行的UI,双击我们就能跟踪到UI定义的位置,包括UMG与源码中的SWidget内容,而且他的结构与我们常见的UMG结构看着很类似,在其中也能充分体现出UI层级结构
为什么要编译源码?Slate学习必须要用到源码吗?
想必很多小伙伴有很多疑问,在此我一一解答,编译源码的原因是为了我们之间从官方SlateViewer开始学习,什么是SlateViewer那就是接下来讲解的内容了,Slate的学习根据自己的能力,也不一定用源码,比如可以创建用常见的窗口插件,在其中练习自己的Slate,源码的编译过程,也能加大大家对于UE的熟悉度,增大对于看源码的过程。
这个就直接附着链接了
UE的源码引擎下载编译
Slate是UE的一套GUI框架,也是构成UE的UI基元
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
GEngine->GameViewport
GEngine->GameViewport->AddViewportWidgetContent(
SNew(MyWidgetPtr.ToSharedRef())
);
//删除对应Widget
GEngine->GameViewport->RemoveViewportWidgetContent(
SNew(MyWidgetPtr.ToSharedRef())
);
//删除所有的Widget
GEngine->GameViewport->RemoveAllViewportWidgets();
以ColorPick举例,使用控件反射器去跟踪Slate结构
参考下图与代码,我们可以看到Slate是很具有结构性的,其实参考代码不难看出它俩之间的联系
SmallTrash = SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SColorTrash)
.UsesSmallIcon(true)
];
this->ChildSlot
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SGridPanel)
.FillColumn(0, 1.0f)
+ SGridPanel::Slot(0, 0)
.Padding(0.0f, 1.0f, 20.0f, 1.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.Padding(0.0f, 1.0f)
[
SNew(SOverlay)
+ SOverlay::Slot()
[
// color theme bar
SAssignNew(CurrentThemeBar, SThemeColorBlocksBar)
.ColorTheme(this, &SColorPicker::HandleThemeBarColorTheme)
.EmptyText(LOCTEXT("EmptyBarHint", "Drag & drop colors here to save"))
.HideTrashCallback(this, &SColorPicker::HideSmallTrash)
.ShowTrashCallback(this, &SColorPicker::ShowSmallTrash)
.ToolTipText(LOCTEXT("CurrentThemeBarToolTip", "Current Color Theme"))
.UseAlpha(SharedThis(this), &SColorPicker::HandleThemeBarUseAlpha)
.UseSRGB(SharedThis(this), &SColorPicker::HandleColorPickerUseSRGB)
.OnSelectColor(this, &SColorPicker::HandleThemeBarColorSelected)
]
// hack: need to fix SThemeColorBlocksBar::EmptyText to render properly
+ SOverlay::Slot()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("EmptyBarHint", "Drag & drop colors here to save"))
.Visibility(this, &SColorPicker::HandleThemeBarHintVisibility)
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
// color theme selector
SAssignNew(ColorThemeButtonOrSmallTrash, SBorder)
.BorderImage(FStyleDefaults::GetNoBrush())
.Padding(0.0f)
]
]
+ SGridPanel::Slot(1, 0)
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
[
// sRGB check box
SNew(SCheckBox)
.ToolTipText(LOCTEXT("SRGBCheckboxToolTip", "Toggle gamma corrected sRGB previewing"))
.IsChecked(this, &SColorPicker::HandleSRGBCheckBoxIsChecked)
.OnCheckStateChanged(this, &SColorPicker::HandleSRGBCheckBoxCheckStateChanged)
[
SNew(STextBlock)
.Text(LOCTEXT("SRGBCheckboxLabel", "sRGB Preview"))
]
]
+ SGridPanel::Slot(0, 1)
.Padding(0.0f, 8.0f, 20.0f, 0.0f)
[
SNew(SBorder)
.BorderImage(FCoreStyle::Get().GetBrush("NoBorder"))
.Padding(0.0f)
.OnMouseButtonDown(this, &SColorPicker::HandleColorAreaMouseDown)
[
SNew(SOverlay)
// color wheel
+ SOverlay::Slot()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.HAlign(HAlign_Center)
[
SNew(SColorWheel)
.SelectedColor(this, &SColorPicker::GetCurrentColor)
.Visibility(this, &SColorPicker::HandleColorPickerModeVisibility, EColorPickerModes::Wheel)
.OnValueChanged(this, &SColorPicker::HandleColorSpectrumValueChanged)
.OnMouseCaptureBegin(this, &SColorPicker::HandleInteractiveChangeBegin)
.OnMouseCaptureEnd(this, &SColorPicker::HandleInteractiveChangeEnd)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(4.0f, 0.0f)
[
// saturation slider
MakeColorSlider(EColorPickerChannels::Saturation)
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
// value slider
MakeColorSlider(EColorPickerChannels::Value)
]
]
// color spectrum
+ SOverlay::Slot()
[
SNew(SBox)
.HeightOverride(200.0f)
.WidthOverride(292.0f)
[
SNew(SColorSpectrum)
.SelectedColor(this, &SColorPicker::GetCurrentColor)
.Visibility(this, &SColorPicker::HandleColorPickerModeVisibility, EColorPickerModes::Spectrum)
.OnValueChanged(this, &SColorPicker::HandleColorSpectrumValueChanged)
.OnMouseCaptureBegin(this, &SColorPicker::HandleInteractiveChangeBegin)
.OnMouseCaptureEnd(this, &SColorPicker::HandleInteractiveChangeEnd)
]
]
]
]
+ SGridPanel::Slot(1, 1)
.Padding(0.0f, 8.0f, 0.0f, 0.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SBox)
.HeightOverride(100.0f)
.WidthOverride(70.0f)
[
// color preview
MakeColorPreviewBox()
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0.0f, 16.0f, 0.0f, 0.0f)
.VAlign(VAlign_Top)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.HAlign(HAlign_Left)
[
// mode selector
SNew(SButton)
.OnClicked(this, &SColorPicker::HandleColorPickerModeButtonClicked)
.Content()
[
SNew(SImage)
.Image(FCoreStyle::Get().GetBrush("ColorPicker.Mode"))
.ToolTipText(LOCTEXT("ColorPickerModeEToolTip", "Toggle between color wheel and color spectrum."))
]
]
+ SHorizontalBox::Slot()
.HAlign(HAlign_Right)
[
// eye dropper
SNew(SEyeDropperButton)
.OnValueChanged(this, &SColorPicker::HandleRGBColorChanged)
.OnBegin(this, &SColorPicker::HandleInteractiveChangeBegin)
.OnComplete(this, &SColorPicker::HandleEyeDropperButtonComplete)
.DisplayGamma(DisplayGamma)
.Visibility(bValidCreationOverrideExists ? EVisibility::Collapsed : EVisibility::Visible)
]
]
]
]
// advanced settings
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0.0f, 4.0f, 0.0f, 0.0f)
[
SNew(SExpandableArea)
.AreaTitle(LOCTEXT("AdvancedAreaTitle", "Advanced"))
.BorderBackgroundColor(FLinearColor::Transparent)
.InitiallyCollapsed(!bAdvancedSectionExpanded)
.OnAreaExpansionChanged(this, &SColorPicker::HandleAdvancedAreaExpansionChanged)
.Padding(FMargin(0.0f, 1.0f, 0.0f, 8.0f))
.BodyContent()
[
SNew(SHorizontalBox)
// RGBA inputs
+ SHorizontalBox::Slot()
.Padding(0.0f, 0.0f, 4.0f, 0.0f)
[
SNew(SVerticalBox)
// Red
+ SVerticalBox::Slot()
[
MakeColorSpinBox(EColorPickerChannels::Red)
]
// Green
+ SVerticalBox::Slot()
.Padding(0.0f, 8.0f, 0.0f, 0.0f)
[
MakeColorSpinBox(EColorPickerChannels::Green)
]
// Blue
+ SVerticalBox::Slot()
.Padding(0.0f, 8.0f, 0.0f, 0.0f)
[
MakeColorSpinBox(EColorPickerChannels::Blue)
]
// Alpha
+ SVerticalBox::Slot()
.Padding(0.0f, 8.0f, 0.0f, 0.0f)
[
MakeColorSpinBox(EColorPickerChannels::Alpha)
]
]
// HSV & Hex inputs
+ SHorizontalBox::Slot()
.Padding(4.0f, 0.0f, 0.0f, 0.0f)
[
SNew(SVerticalBox)
// Hue
+ SVerticalBox::Slot()
[
MakeColorSpinBox(EColorPickerChannels::Hue)
]
// Saturation
+ SVerticalBox::Slot()
.Padding(0.0f, 8.0f, 0.0f, 0.0f)
[
MakeColorSpinBox(EColorPickerChannels::Saturation)
]
// Value
+ SVerticalBox::Slot()
.Padding(0.0f, 8.0f, 0.0f, 0.0f)
[
MakeColorSpinBox(EColorPickerChannels::Value)
]
// Hex linear
+ SVerticalBox::Slot()
.HAlign(HAlign_Right)
.VAlign(VAlign_Top)
.Padding(0.0f, 12.0f, 0.0f, 0.0f)
[
SNew(SHorizontalBox)
.ToolTipText(LOCTEXT("HexLinearSliderToolTip", "Hexadecimal Linear Value"))
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0.0f, 0.0f, 4.0f, 0.0f)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("HexLinearInputLabel", "Hex Linear"))
]
+ SHorizontalBox::Slot()
.AutoWidth()
.MaxWidth(72.0f)
[
SNew(SEditableTextBox)
.MinDesiredWidth(72.0f)
.Text(this, &SColorPicker::HandleHexLinearBoxText)
.OnTextCommitted(this, &SColorPicker::HandleHexLinearInputTextCommitted)
]
]
// Hex sRGB
+ SVerticalBox::Slot()
.HAlign(HAlign_Right)
.VAlign(VAlign_Top)
.Padding(0.0f, 8.0f, 0.0f, 0.0f)
[
SNew(SHorizontalBox)
.ToolTipText(LOCTEXT("HexSRGBSliderToolTip", "Hexadecimal sRGB Value"))
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0.0f, 0.0f, 4.0f, 0.0f)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("HexSRGBInputLabel", "Hex sRGB"))
]
+ SHorizontalBox::Slot()
.AutoWidth()
.MaxWidth(72.0f)
[
SNew(SEditableTextBox)
.MinDesiredWidth(72.0f)
.Text(this, &SColorPicker::HandleHexSRGBBoxText)
.OnTextCommitted(this, &SColorPicker::HandleHexSRGBInputTextCommitted)
]
]
]
]
]
// dialog buttons
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(0.0f, 12.0f, 0.0f, 0.0f)
[
SNew(SUniformGridPanel)
.MinDesiredSlotHeight(FCoreStyle::Get().GetFloat("StandardDialog.MinDesiredSlotHeight"))
.MinDesiredSlotWidth(FCoreStyle::Get().GetFloat("StandardDialog.MinDesiredSlotWidth"))
.SlotPadding(FCoreStyle::Get().GetMargin("StandardDialog.SlotPadding"))
.Visibility((ParentWindowPtr.IsValid() || bValidCreationOverrideExists) ? EVisibility::Visible : EVisibility::Collapsed)
+ SUniformGridPanel::Slot(0, 0)
[
// ok button
SNew(SButton)
.ContentPadding( FCoreStyle::Get().GetMargin("StandardDialog.ContentPadding") )
.HAlign(HAlign_Center)
.Text(LOCTEXT("OKButton", "OK"))
.OnClicked(this, &SColorPicker::HandleOkButtonClicked)
]
+ SUniformGridPanel::Slot(1, 0)
[
// cancel button
SNew(SButton)
.ContentPadding( FCoreStyle::Get().GetMargin("StandardDialog.ContentPadding") )
.HAlign(HAlign_Center)
.Text(LOCTEXT("CancelButton", "Cancel"))
.OnClicked(this, &SColorPicker::HandleCancelButtonClicked)
]
]
];
对比代码与Slate层级之后,我们发现Slate编程有些奇怪,但很具有结构,而Slate为什么会能这样连写呢,第一反应有人觉得这是C++语法吗,请接着往下看。
注:宏定义#表示链接,\用于作换行
可连续用点是因为返回值类型全是自身,就一直调用自己的方法,再将自己返回出去
它也对+号等进行了重载
在这类建立时候,用SLATE_BEGIN_ARGS宏进行了这些基础方法的生成与实现,因此,这是导致Slate语法奇怪的原因,当你知道了这些重载之后再来看Slate语法将会恍然大悟。
列如下图:创窗口设置大小为(541,632),绑定了窗口名字的Lamda表达式,设置其名字为ColorPicker,接下来的操作大家多去看看大概就明白什么意思了。
SlateViewer是属于UE源码引擎的一套官方提供给大众学习观看Slate的最佳途径
首先打开UE源码,将SlateViewer设置为启动项
下面则是点击运行之后得到的,其中几乎将UE整套UI模块,包括部分渲染模块都包含了,因此我们用它去对Slate学习更轻松
在此我们看到了熟悉的c++Win32程序的函数入口,WinMain函数,其中调用了我们Slate启动起来的方法
这个方法也相较于比较简单,便于我们轻松的去查看Slate实现的基本原理
int RunSlateViewer( const TCHAR* CommandLine )
{
// start up the main loop
GEngineLoop.PreInit(CommandLine);
// Make sure all UObject classes are registered and default properties have been initialized
ProcessNewlyLoadedUObjects();
// Tell the module manager it may now process newly-loaded UObjects when new C++ modules are loaded
FModuleManager::Get().StartProcessingNewlyLoadedObjects();
// crank up a normal Slate application using the platform's standalone renderer
FSlateApplication::InitializeAsStandaloneApplication(GetStandardStandaloneRenderer());
// Load the source code access module
ISourceCodeAccessModule& SourceCodeAccessModule = FModuleManager::LoadModuleChecked<ISourceCodeAccessModule>( FName( "SourceCodeAccess" ) );
// Manually load in the source code access plugins, as standalone programs don't currently support plugins.
#if PLATFORM_MAC
IModuleInterface& XCodeSourceCodeAccessModule = FModuleManager::LoadModuleChecked<IModuleInterface>( FName( "XCodeSourceCodeAccess" ) );
SourceCodeAccessModule.SetAccessor(FName("XCodeSourceCodeAccess"));
#elif PLATFORM_WINDOWS
IModuleInterface& VisualStudioSourceCodeAccessModule = FModuleManager::LoadModuleChecked<IModuleInterface>( FName( "VisualStudioSourceCodeAccess" ) );
SourceCodeAccessModule.SetAccessor(FName("VisualStudioSourceCodeAccess"));
#endif
// set the application name
FGlobalTabmanager::Get()->SetApplicationTitle(LOCTEXT("AppTitle", "Slate Viewer"));
FModuleManager::LoadModuleChecked<ISlateReflectorModule>("SlateReflector").RegisterTabSpawner(WorkspaceMenu::DeveloperMenu);
FGlobalTabmanager::Get()->RegisterNomadTabSpawner("WebBrowserTab", FOnSpawnTab::CreateStatic(&SpawnWebBrowserTab))
.SetDisplayName(LOCTEXT("WebBrowserTab", "Web Browser"));
if (FParse::Param(FCommandLine::Get(), TEXT("perftest")))
{
// Bring up perf test
SummonPerfTestSuite();
}
else
{
// Bring up the test suite.
RestoreSlateTestSuite();
}
#if WITH_SHARED_POINTER_TESTS
SharedPointerTesting::TestSharedPointer<ESPMode::Fast>();
SharedPointerTesting::TestSharedPointer<ESPMode::ThreadSafe>();
#endif
// loop while the server does the rest
while (!IsEngineExitRequested())
{
FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread);
FStats::AdvanceFrame(false);
FTicker::GetCoreTicker().Tick(FApp::GetDeltaTime());
FSlateApplication::Get().PumpMessages();
FSlateApplication::Get().Tick();
FPlatformProcess::Sleep(0);
}
FCoreDelegates::OnExit.Broadcast();
FSlateApplication::Shutdown();
FModuleManager::Get().UnloadModulesAtShutdown();
return 0;
}
有些的定义在其他模块或者宏里面,大家查找时候一定要仔细,例如GEngineLoop
FSlateApplication::InitializeAsStandaloneApplication(GetStandardStandaloneRenderer());启用了渲染模块,对于渲染感兴趣的,看这个模块也不错,SlateViewer渲染模块
比如生成Web浏览器选项卡内容
这里加载了SlateReflector模块
这里创建了我们的测试窗口
切入可以看到其中如何创建的我们看见的UI页面,对于这块感兴趣的小伙伴可以自己切入源码看看,在这就不附图了
这个类中也可以看到我们刚熟悉的Slate语法
到此感谢各位小伙伴的观看大家可以对于自己喜欢的模块进行专研