You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
563 lines
20 KiB
Plaintext
563 lines
20 KiB
Plaintext
INTSourceChangelist:3238697
|
||
Title:Slate游戏内UI快速入门
|
||
Crumbs:
|
||
Description:
|
||
Availability:Docs
|
||
Version:4.9
|
||
|
||
## 项目设置
|
||
|
||
首先,创建一个新的基础代码项目:
|
||
|
||
[REGION:fullwidth]
|
||

|
||
[/REGION]
|
||
|
||
默认情况下,这样会提供以下类:
|
||
|
||

|
||
|
||
## 在屏幕上显示内容
|
||
|
||
1. 除了默认类,我还添加了一个新类“MainMenu”来封装我将会创建的菜单。为了在屏幕上显示一些内容,首先创建一些简单的函数来构造菜单,并创建一些用于打开和关闭(隐藏和显示)菜单的函数。我还添加了一个成员变量“SWidget共享指针”来保持对菜单层级中的最上方Slate控件的引用。
|
||
|
||
#pragma once
|
||
|
||
#include "Slate.h"
|
||
|
||
class MainMenu : public TSharedFromThis<MainMenu>
|
||
{
|
||
public:
|
||
/** 构造菜单所需的控件等 */
|
||
void ConstructMenu();
|
||
|
||
/** 通过向GameViewport控件内容添加根控件来显示菜单 */
|
||
void OpenMenu();
|
||
|
||
/** 通过从GameViewport控件内容移除根控件来关闭菜单 */
|
||
void CloseMenu();
|
||
|
||
private:
|
||
|
||
/** 这是菜单的最根部Slate控件。其他都是它的子代。*/
|
||
TSharedPtr<SWidget> MenuRoot;
|
||
};
|
||
|
||
MainMenu类派生自TSharedFromThis模板,因为这有助于传递对“this”的安全引用,以用于“Slate菜单”事件的代理函数,我们稍后再讨论这一点。
|
||
|
||
1. 为菜单类函数添加基本实现:
|
||
|
||
#include "SlateGameMenuExample.h"
|
||
#include "MainMenu.h"
|
||
|
||
|
||
#define LOCTEXT_NAMESPACE "MainMenu"
|
||
|
||
|
||
void MainMenu::ConstructMenu()
|
||
{
|
||
MenuRoot = SNew(SSearchBox)
|
||
.HintText(LOCTEXT("FilterSearch", "Search..."))
|
||
.ToolTipText(LOCTEXT("FilterSearchHint", "Type here to search").ToString());
|
||
}
|
||
|
||
void MainMenu::OpenMenu()
|
||
{
|
||
if (GEngine && GEngine->GameViewport)
|
||
{
|
||
GEngine->GameViewport->AddViewportWidgetContent(MenuRoot.ToSharedRef());
|
||
FSlateApplication::Get().SetKeyboardFocus(MenuRoot.ToSharedRef());
|
||
}
|
||
}
|
||
|
||
void MainMenu::CloseMenu()
|
||
{
|
||
if (GEngine && GEngine->GameViewport)
|
||
{
|
||
GEngine->GameViewport->RemoveViewportWidgetContent(MenuRoot.ToSharedRef());
|
||
FSlateApplication::Get().ClearKeyboardFocus(EFocusCause::Cleared);
|
||
}
|
||
}
|
||
|
||
#undef LOCTEXT_NAMESPACE
|
||
|
||
为了在屏幕上显示内容,我从代码中的任意地方选择了一个随机Slate控件作为临时MenuRoot。然后,它会被添加到GameViewport的ViewportWidgetContent,这样就会显示出来。此外,我们确保SlateMenu获得键盘焦点,以便它能够响应输入。注意LOCTEXT宏的使用,务必始终使用它来表示必须本地化为其他语言的任意文本。本博文将详细讨论这一点:
|
||
|
||
[在UE4中创建可本地化的游戏:第1部分——文本](https://www.unrealengine.com/blog/creating-a-localization-ready-game-in-ue4-part-1-text)
|
||
|
||
接下来,我添加了一个新的GameMode来表示位于主菜单“MainMenuGameMode”的玩家的状态。这只是定义我们用于这种游戏模式的PlayerController类型,使用BeginPlay事件打开主菜单,并在共享指针中保持对MainMenu的引用。
|
||
|
||
#pragma once
|
||
|
||
#include "GameFramework/GameModeBase.h"
|
||
#include "MainMenuGameMode.generated.h"
|
||
|
||
class MainMenu;
|
||
|
||
/**
|
||
*
|
||
*/
|
||
UCLASS()
|
||
class AMainMenuGameMode : public AGameModeBase
|
||
{
|
||
GENERATED_UCLASS_BODY()
|
||
|
||
protected:
|
||
|
||
virtual void BeginPlay() override;
|
||
|
||
public:
|
||
|
||
TSharedPtr<MainMenu> MainMenuPtr;
|
||
|
||
};
|
||
|
||
这里需要注意,GameMode是UClass,因此需要标准的#include用于生成的头,UCLASS()宏放在类定义前面,GENERATED_UCLASS_BODY()放在类定义开头。
|
||
|
||
#include "SlateGameMenuExample.h"
|
||
#include "MainMenuGameMode.h"
|
||
#include "SlateGameMenuExamplePlayerController.h"
|
||
|
||
#include "MainMenu.h"
|
||
|
||
AMainMenuGameMode::AMainMenuGameMode(const FObjectInitializer& ObjectInitializer)
|
||
:Super(ObjectInitializer)
|
||
{
|
||
PlayerControllerClass = ASlateGameMenuExamplePlayerController::StaticClass();
|
||
}
|
||
|
||
void AMainMenuGameMode::BeginPlay()
|
||
{
|
||
MainMenuPtr = MakeShareable(new MainMenu());
|
||
MainMenuPtr->ConstructMenu();
|
||
MainMenuPtr->OpenMenu();
|
||
}
|
||
|
||
现在,我们需要将这个新的GameMode指定为要使用的模式。这可以逐个关卡地在场景设置中指定:
|
||
|
||

|
||
|
||
或者通过“项目设置(Project Settings)>贴图和模式(Maps & Modes)”中的默认GameMode设置:
|
||
|
||
[REGION:fullwidth]
|
||

|
||
[/REGION]
|
||
|
||
这样我们就能够在屏幕上显示一些内容:
|
||
|
||
[REGION:fullwidth]
|
||

|
||
[/REGION]
|
||
|
||
我添加的这个搜索框控件占据了整个视口,但这是一个好的起点。
|
||
|
||
添加一些按钮
|
||
|
||
现在,让我们来处理一下这个菜单,让它看起来更像是一个常见的“主菜单”。或许类似于下面这个简单的布局:
|
||
|
||

|
||
|
||
这里在一个更大的区域中包含了三个按钮。“播放”按钮用于启动游戏,“退出”按钮用于退出应用程序,“选项”按钮可以转至另一个菜单。为此,我修改了ConstructMenu函数,如下:
|
||
|
||
#include "StyleDefaults.h"
|
||
|
||
void MainMenu::ConstructMenu()
|
||
{
|
||
const FSlateBrush* NoBorderBrush = FStyleDefaults::GetNoBrush();
|
||
|
||
MenuRoot =
|
||
SNew(SBorder)
|
||
[
|
||
SNew(SVerticalBox)
|
||
+ SVerticalBox::Slot()
|
||
.FillHeight(1.f)
|
||
.HAlign(HAlign_Center)
|
||
[
|
||
SNew(SBorder)
|
||
.HAlign(HAlign_Center)
|
||
.VAlign(VAlign_Center)
|
||
.BorderImage(NoBorderBrush)
|
||
[
|
||
SNew(STextBlock)
|
||
.Text(LOCTEXT("SlateGameMenuTitle", "Slate Game Menu!"))
|
||
]
|
||
]
|
||
+ SVerticalBox::Slot()
|
||
.FillHeight(1.f)
|
||
.HAlign(HAlign_Center)
|
||
[
|
||
SNew(SBorder)
|
||
.HAlign(HAlign_Center)
|
||
.VAlign(VAlign_Center)
|
||
.BorderImage(NoBorderBrush)
|
||
[
|
||
SNew(SButton)
|
||
.OnClicked(FOnClicked::CreateSP(this, &MainMenu::StartGame))
|
||
.HAlign(HAlign_Center)
|
||
.VAlign(VAlign_Center)
|
||
.Text(LOCTEXT("StartGameButtonText", "Start Game"))
|
||
]
|
||
]
|
||
+ SVerticalBox::Slot()
|
||
.FillHeight(1.f)
|
||
.HAlign(HAlign_Center)
|
||
[
|
||
SNew(SBorder)
|
||
.HAlign(HAlign_Center)
|
||
.VAlign(VAlign_Center)
|
||
.BorderImage(NoBorderBrush)
|
||
[
|
||
SNew(SButton)
|
||
.HAlign(HAlign_Center)
|
||
.VAlign(VAlign_Center)
|
||
//.OnClicked(FOnClicked::CreateSP(this, &MainMenu::OpenOptionsMenu))
|
||
.Text(LOCTEXT("OptionsButtonText", "Options..."))
|
||
]
|
||
]
|
||
+ SVerticalBox::Slot()
|
||
.FillHeight(1.f)
|
||
.HAlign(HAlign_Center)
|
||
[
|
||
SNew(SBorder)
|
||
.HAlign(HAlign_Center)
|
||
.VAlign(VAlign_Center)
|
||
.BorderImage(NoBorderBrush)
|
||
[
|
||
SNew(SButton)
|
||
.HAlign(HAlign_Center)
|
||
.VAlign(VAlign_Center)
|
||
.OnClicked(FOnClicked::CreateSP(this, &MainMenu::QuitGame))
|
||
.Text(LOCTEXT("QuitButtonText", "Quit Game"))
|
||
]
|
||
]
|
||
]
|
||
;
|
||
}
|
||
|
||
如您所见,Slate有一些特殊语法,让您可以使用各种运算符来修改属性(这些链接提供了一些有关Slate声明性语法和结构的信息)。您可以在使用SNew(SWidget)后,通过“.”运算符来设置Slate属性,并可以用括号运算符在其他控件内部嵌套子控件。
|
||
|
||
上述示例在所有内容外部使用了SBorder,内部使用了一个SVerticalBox,它有4个插槽,用于包含菜单标题文本和三个按钮。使用了多种对齐方式和大小来得到所需的外观。该链接提供了有关Slate中的“布局”工作方式的更多信息。这些添加最终会产生类似于以下示例的菜单:
|
||
|
||

|
||
|
||
这更接近于我们想要的效果。请注意,上述代码中已经设置了SButtons的OnClicked属性。这是一个代理,在您单击该按钮时将调用您选择的函数。“开始游戏”按钮的OnClicked声明类似于:
|
||
|
||
.OnClicked(FOnClicked::CreateSP(this, &MainMenu::StartGame))
|
||
|
||
FOnClicked是控件想要通知用户已经单击过这些控件时调用的一类代理,旨在供按钮和其他类似于按钮的控件使用。要关联代理,您需要指定要对其调用函数的对象、要调用的函数和要传递给函数的可选参数。您可以使用不同的函数来指定代理,我在这里使用的是CreateSP,这是基于共享指针的成员函数代理(这也是MainMenu派生自TSharedFromThis的原因,所以我使用更安全的共享指针选项)。您还可以结合使用原始C++指针和CreateRaw,或者可以结合使用静态函数和CreateStatic(这样就无需指定要对其调用函数的对象)。
|
||
|
||
现在,已经指定了“开始游戏”和“退出游戏”按钮的代理,接下来需要定义它们调用的函数。这些是非常简单的函数。
|
||
|
||
FReply MainMenu::StartGame()
|
||
{
|
||
if (Controller.IsValid())
|
||
{
|
||
Controller->GetWorld()->ServerTravel("/Game/GameLevel");
|
||
}
|
||
CloseMenu();
|
||
|
||
return FReply::Handled();
|
||
}
|
||
|
||
此函数使用ServerTravel来告诉引擎切换到另一个地图来开始游戏。然后,我们关闭菜单。您或许注意到了,该函数正在返回FReply。Reply是指Slate事件返回给系统以通知其事件处理方式的特定方面的信息。例如,控件处理OnMouseDown事件的方式可能是告诉系统对特定控件给予鼠标捕获(为此,return FReply::CaptureMouse( NewMouseCapture )。)
|
||
|
||
FReply MainMenu::QuitGame()
|
||
{
|
||
CloseMenu();
|
||
if (MyPlayerController.IsValid())
|
||
{
|
||
MyPlayerController->ConsoleCommand("quit");
|
||
}
|
||
|
||
return FReply::Handled();
|
||
}
|
||
|
||
QuitGame函数仅使用控制台命令“quit”在关闭菜单后退出游戏。
|
||
|
||
这两个函数都使用对玩家控制器的引用来调用函数,这意味着我必须修改MainMenu.cpp以保持对PlayerController的WeakObjectPtr,并通过构造函数来进行这项设置,如:
|
||
|
||
MainMenu::MainMenu(TWeakObjectPtr<class APlayerController> InController)
|
||
: MyPlayerController(InController)
|
||
{
|
||
}
|
||
|
||
MainMenuGameMode仅抓取第一个PlayerController并将抓取的信息传递给MainMenu类,如:
|
||
|
||
#include "../../../../../../Engine/Source/Runtime/Engine/Classes/Kismet/GameplayStatics.h"
|
||
void AMainMenuGameMode::BeginPlay()
|
||
{
|
||
APlayerController* FirstPC = NULL;
|
||
if (GetWorld() != NULL)
|
||
{
|
||
// 玩家0获得UI
|
||
FirstPC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
|
||
}
|
||
MainMenuPtr = MakeShareable(new MainMenu(TWeakObjectPtr<APlayerController>(FirstPC)));
|
||
MainMenuPtr->ConstructMenu();
|
||
MainMenuPtr->OpenMenu();
|
||
}
|
||
|
||
这里的另外两个问题是,在默认情况下,没有绘制鼠标光标,因此玩家很难单击我们刚刚创建的按钮。此外,PlayerController仍在使用鼠标和键盘输入来控制场景中的角色和摄像机,因此移动鼠标来单击按钮也会移动菜单背后的场景摄像机。要解决这两个问题,可以启用CinematicMode来暂时禁用玩家输入,并告诉PlayerController显示鼠标光标,如:
|
||
|
||
void MainMenu::OpenMenu()
|
||
{
|
||
if (GEngine && GEngine->GameViewport)
|
||
{
|
||
// 将菜单控件内容添加到游戏视口以便能显示它
|
||
UGameViewportClient* const GVC = GEngine->GameViewport;
|
||
GVC->AddViewportWidgetContent(MenuRoot.ToSharedRef());
|
||
if (MyPlayerController.IsValid())
|
||
{
|
||
// 启用鼠标光标并禁用其他输入(移动鼠标不会旋转摄像机等)。
|
||
MyPlayerController->SetCinematicMode(true, false, false, true, true);
|
||
MyPlayerController->bShowMouseCursor = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
关闭菜单起到相反作用:
|
||
|
||
void MainMenu::CloseMenu()
|
||
{
|
||
if (GEngine && GEngine->GameViewport)
|
||
{
|
||
// 从游戏视口移除菜单控件内容,以便不再显示它
|
||
UGameViewportClient* const GVC = GEngine->GameViewport;
|
||
GVC->RemoveViewportWidgetContent(MenuRoot.ToSharedRef());
|
||
FSlateApplication::Get().ClearKeyboardFocus(EFocusCause::Cleared);
|
||
if (MyPlayerController.IsValid())
|
||
{
|
||
// 重新启用其他输入并移除鼠标光标
|
||
MyPlayerController->SetCinematicMode(false, false, false, true, true);
|
||
MyPlayerController->bShowMouseCursor = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
定义和使用Slate样式
|
||
|
||
接下来,我们想要定义和使用一些Slate样式,便于我们快速并轻松地更改菜单样式。样式只包含一些文本字符串,用来引用笔刷(即包含如何绘制Slate元素的相关信息的容器)、控件样式、Slate字体信息等。
|
||
|
||
例如,您可以为工具栏按钮定义样式,如:
|
||
|
||
// 普通按钮
|
||
FButtonStyle Button = FButtonStyle()
|
||
.SetNormal(BOX_BRUSH("Button", FVector2D(32, 32), 8.0f / 32.0f))
|
||
.SetHovered(BOX_BRUSH("Button_Hovered", FVector2D(32, 32), 8.0f / 32.0f))
|
||
.SetPressed(BOX_BRUSH("Button_Pressed", FVector2D(32, 32), 8.0f / 32.0f))
|
||
.SetDisabled(BOX_BRUSH("Button_Disabled", 8.0f / 32.0f))
|
||
.SetNormalPadding(FMargin(2, 2, 2, 2))
|
||
.SetPressedPadding(FMargin(2, 3, 2, 1));
|
||
Style->Set("MyButtonStyle ", Button);
|
||
|
||
或文本样式,如:
|
||
|
||
const FTextBlockStyle NormalText = FTextBlockStyle()
|
||
.SetFont(TTF_FONT("Fonts/Roboto-Regular", 9))
|
||
.SetColorAndOpacity(FSlateColor::UseForeground())
|
||
.SetShadowOffset(FVector2D::ZeroVector)
|
||
.SetShadowColorAndOpacity(FLinearColor::Black)
|
||
.SetHighlightColor(FLinearColor(0.02f, 0.3f, 0.0f))
|
||
.SetHighlightShape(BOX_BRUSH("TextBlockHighlightShape", FMargin(3.f / 8.f)));
|
||
Style->Set("NormalText", NormalText);
|
||
|
||
为了完成代码,上述BRUSH和FONT宏按如下所示定义以简化定义:
|
||
|
||
#define IMAGE_BRUSH( RelativePath, ...) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
|
||
#define BOX_BRUSH( RelativePath, ...) FSlateBoxBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
|
||
#define BORDER_BRUSH( RelativePath, ...) FSlateBorderBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
|
||
#define TTF_FONT( RelativePath, ...) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ )
|
||
#define OTF_FONT( RelativePath, ...) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ )
|
||
|
||
这些样式可以按如下所示用于按钮:
|
||
|
||
SNew(SButton) // Start Game button
|
||
.ButtonStyle(&FMySlateStyle::Get().GetWidgetStyle<FButtonStyle>("MyButtonStyle"))
|
||
.TextStyle(&FMySlateStyle::Get().GetWidgetStyle<FTextBlockStyle>("NormalText"))
|
||
.OnClicked(FOnClicked::CreateSP(this, &MainMenu::StartGame))
|
||
.HAlign(HAlign_Center)
|
||
.VAlign(VAlign_Center)
|
||
.Text(LOCTEXT("StartGameButtonText", "Start Game"))
|
||
|
||
对于示例Slate菜单,我创建了一个单独的类,叫做MySlateStyle,它创建了两个FSlateStyleSets(样式组),它们定义相同的样式,但定义不同的视觉外观。这样我就能够轻松地切换两个样式集。上述创建的FLateStyleSet中的“Style”变量包含一组按照以上方式设置的样式,然后按如下所示注册到Slate样式注册表中:
|
||
|
||
FSlateStyleRegistry::RegisterSlateStyle(NewStyle);
|
||
|
||
最初我将一些随机样式输入进去并得出这样的结果:
|
||
|
||

|
||
|
||
当然不美观,但现在可以轻松地迭代来加以美化。
|
||
|
||
再添加一个菜单
|
||
|
||
上文我解释了如何让“开始游戏”和“退出游戏”按钮工作,但“选项”按钮仍无法工作。该按钮应该是让玩家单击按钮后前往另一个菜单。毫无疑问可以使用ServerTravel移到另一个关卡,打开另一个菜单,然后单击“后退”时返回到MainMenu关卡,但我采用了另一个方法。
|
||
|
||
我想在主菜单上弹出选项菜单,所以将主菜单包含在SOverlay中,这样就可以定义不同的可以重叠的内容插槽。这个重叠成为菜单的新根控件。
|
||
|
||
在构造函数中,我构造了两个菜单:
|
||
|
||
void MainMenu::ConstructMenu()
|
||
{
|
||
// ...
|
||
|
||
HeaderFontInfo = FMySlateStyle::Get().GetFontStyle("RichText.Header");
|
||
|
||
// 设置主菜单控件内容
|
||
MenuRoot =
|
||
SNew(SOverlay) // 用于允许“选项”菜单被“覆盖”或显示在该菜单上方的重叠
|
||
+SOverlay::Slot() // New Slot for the overlay.这只包含主菜单内容
|
||
[
|
||
// ...
|
||
// 主菜单控件
|
||
/ ...
|
||
]
|
||
;
|
||
|
||
// ...
|
||
|
||
// 构建选项菜单控件
|
||
OptionsMenuRoot = SNew(SBorder)
|
||
.Cursor(EMouseCursor::Default)
|
||
.HAlign(HAlign_Center)
|
||
.VAlign(VAlign_Center)
|
||
[
|
||
// ...
|
||
// 主菜单控件
|
||
/ ...
|
||
]
|
||
;
|
||
|
||
}
|
||
|
||
当我想要显示选项菜单时,我把选项菜单的根控件放入根SOverlay中的新插槽中:
|
||
|
||
FReply MainMenu::OpenOptionsMenu()
|
||
{
|
||
SOverlay* MenuRootOverlay = (SOverlay*)MenuRoot.Get();
|
||
|
||
if (MenuRootOverlay)
|
||
{
|
||
// 将另一个插槽添加到主菜单的重叠,并在其中放入我们的“选项”菜单内容
|
||
OptionsMenuSlot = &MenuRootOverlay->AddSlot()
|
||
[
|
||
OptionsMenuRoot.ToSharedRef()
|
||
]
|
||
;
|
||
}
|
||
|
||
return FReply::Handled();
|
||
}
|
||
|
||
当我想要隐藏选项菜单时,我会移除该插槽:
|
||
|
||
FReply MainMenu::CloseOptionsMenu()
|
||
{
|
||
SOverlay* MenuRootOverlay = (SOverlay*)MenuRoot.Get();
|
||
|
||
if (MenuRootOverlay != NULL)
|
||
{
|
||
// 移除选项菜单重叠插槽
|
||
MenuRootOverlay->RemoveSlot(OptionsMenuSlot->Widget);
|
||
}
|
||
|
||
return FReply::Handled();
|
||
}
|
||
|
||
为选项菜单创建更多随机样式,如以下示例:
|
||
|
||

|
||
|
||
更改选项
|
||
|
||
在以上截图中,您会看到,有一些用于更改菜单样式和字体样式的选项。
|
||
|
||
这些同样使用一些简单的代理来调用这些函数。
|
||
|
||
void MainMenu::StyleComboBoxSelectionChanged(TSharedPtr<FString> StringItem, ESelectInfo::Type SelectInfo)
|
||
{
|
||
if (StringItem->Equals("Style1"))
|
||
{
|
||
FMySlateStyle::SetStyle1();
|
||
}
|
||
else
|
||
{
|
||
FMySlateStyle::SetStyle2();
|
||
}
|
||
|
||
// 进行这些更改后,关闭菜单,重新构造并再次打开
|
||
CloseOptionsMenu();
|
||
CloseMenu();
|
||
ConstructMenu();
|
||
OpenMenu();
|
||
OpenOptionsMenu();
|
||
}
|
||
|
||
void MainMenu::FontSize_ValueChanged(int32 InValue)
|
||
{
|
||
HeaderFontInfo.Size = InValue;
|
||
MenuHeaderText->SetFont(HeaderFontInfo);
|
||
OptionsMenuHeaderText->SetFont(HeaderFontInfo);
|
||
}
|
||
|
||
对STextBlock对象的引用通过在构造函数中使用SAssignNew来维护,以将产生的控件对象分配给可以在代理函数中引用的成员变量:
|
||
|
||
SAssignNew(OptionsMenuHeaderText, STextBlock) // Header text
|
||
.TextStyle(&FMySlateStyle::Get().GetWidgetStyle<FTextBlockStyle>("RichText.Header"))
|
||
.Text(LOCTEXT("OptionsMenuTitle", "Options!"))
|
||
|
||
由于更改样式复选框选择会导致菜单被重新构建,因此需要一点额外的代码来确保菜单在重新构建后选择正确的样式,这部分代码也在构造函数中编写:
|
||
|
||
// 保存样式列表
|
||
StyleList.Empty();
|
||
StyleList.Add(MakeShareable(new FString("Style1")));
|
||
StyleList.Add(MakeShareable(new FString("Style2")));
|
||
|
||
// 在菜单样式更改时调用构造函数菜单,因此确保菜单样式复选框中的选择在做出新选择时是最新的
|
||
FString CurrentStyleName = FMySlateStyle::Get().GetStyleSetName().ToString();
|
||
TSharedPtr<FString> CurrentlySelectedStyle;
|
||
for (TSharedPtr<FString> StyleString :StyleList)
|
||
{
|
||
if (StyleString->Equals(CurrentStyleName))
|
||
{
|
||
CurrentlySelectedStyle = StyleString;
|
||
}
|
||
}
|
||
if (!CurrentlySelectedStyle.IsValid() && StyleList.Num() > 0)
|
||
{
|
||
CurrentlySelectedStyle = StyleList[0];
|
||
}
|
||
|
||
现在,更改样式下拉菜单会产生另一种样式(虽然样式本身仍需要处理):
|
||
|
||

|
||
|
||
现在,更改字体滑块也会更改字体大小:
|
||
|
||

|
||
|
||
创建一些合适的背景和样式后,最终菜单应该类似于以下示例:
|
||
|
||
[REGION:imagetable]
|
||
|(w:460) | (w:460) |
|
||
| ------ | ------ |
|
||
| 使用样式1的主菜单 | 使用样式2的选项菜单 |
|
||
[/REGION]
|
||
|
||
## 使用控件反射器
|
||
|
||
在设计Slate菜单时特别有用的一个工具是控件反射器,可以从“开发者工具”菜单访问。
|
||
|
||
[REGION:fullwidth]
|
||

|
||

|
||
[/REGION]
|
||
|
||
它按照控件存在的层级显示存在的每一个Slate控件,甚至显示其可见性以及创建它的代码文件和代码行。在调试菜单时,或者调查其他菜单以了解它们的创建方式时,该信息非常有用。此外,“选取控件”按钮允许您从控件层级中查找鼠标所在的下部件:
|
||
|
||
[REGION:fullwidth]
|
||

|
||
[/REGION]
|
||
|
||
您还可以再次按下这个按钮(或按ESC键)来冻结当前选中的控件。
|