UE4开发C++沙盒游戏教程笔记(四)(对应教程 12 ~ 14)
11. 写入存档 Json 文件
上节课讲了如何进行 Json 文件的读取,这节课讲如何对 Json 文件进行写入操作。
既然要写入,那就要能够将数据转化为 Json 数据的格式。首先声明 3 个方法用于执行写操作
SlAiJsonHandle.h
#pragma once
#include "CoreMinimal.h"
#include "SlAiTypes.h"
#include "Json.h"
class SLAICOURSE_API SlAiJsonHandle
{
public:
SlAiJsonHandle();
void RecordDataJsonRead(FString& Culture, float& MusicVolume, float& SoundVolume, TArray<FString>& RecordDataList);
// 修改存档
void UpdateRecordData(FString Culture, float MusicVolume, float SoundVolume, TArray<FString>* RecordDataList);
private:
bool LoadStringFromFile(const FString& FileName, const FString& RelaPath, FString& ResultString);
// FJsonObject 转换为 Json 格式的字符串
bool GetFStringInJsonData(const TSharedPtr<FJsonObject>& JsonObj, FString& JsonStr);
// 保存字符串到文件
bool WriteFileWithJsonData(const FString& JsonStr, const FString& RelaPath, const FString& FileName);
private:
FString RecordDataFileName;
FString RelativePath;
};
SlAiJsonHandle.cpp
#include "Data/SlAiJsonHandle.h"
#include "SlAiHelper.h"
SlAiJsonHandle::SlAiJsonHandle()
{
RecordDataFileName = FString("RecordData.json");
RelativePath = FString("Res/ConfigData/");
}
void SlAiJsonHandle::RecordDataJsonRead(FString& Culture, float& MusicVolume, float& SoundVolume, TArray<FString>& RecordDataList)
{
FString JsonValue;
LoadStringFromFile(RecordDataFileName, RelativePath, JsonValue);
TArray<TSharedPtr<FJsonValue>> JsonParsed;
TSharedRef<TJsonReader<TCHAR>> JsonReader = TJsonReaderFactory<TCHAR>::Create(JsonValue);
if (FJsonSerializer::Deserialize(JsonReader, JsonParsed)) {
Culture = JsonParsed[0]->AsObject()->GetStringField(FString("Culture"));
MusicVolume = JsonParsed[1]->AsObject()->GetNumberField(FString("MusicVolume"));
SoundVolume = JsonParsed[2]->AsObject()->GetNumberField(FString("SoundVolume"));
TArray<TSharedPtr<FJsonValue>> RecordDataArray = JsonParsed[3]->AsObject()->GetArrayField(FString("RecordData"));
for (int i = 0; i < RecordDataArray.Num(); ++i) {
FString RecordDataName = RecordDataArray[i]->AsObject()->GetStringField(FString::FromInt(i));
RecordDataList.Add(RecordDataName);
}
}
else {
SlAiHelper::Debug(FString("Deserialize Failed"));
}
}
void SlAiJsonHandle::UpdateRecordData(FString Culture, float MusicVolume, float SoundVolume, TArray<FString>* RecordDataList)
{
// 用于存放完整的、要存入的 Json 对象的指针
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
// 用于存放内部多个 Json 对象指针的数组(Culture、MusicVolume、SoundVolume...)
TArray<TSharedPtr<FJsonValue>> BaseDataArray;
TSharedPtr<FJsonObject> CultureObject = MakeShareable(new FJsonObject);
// 定义语言 Json 对象的键值对
CultureObject->SetStringField("Culture", Culture);
// 组合好的语言 Json 对象
TSharedPtr<FJsonValueObject> CultureValue = MakeShareable(new FJsonValueObject(CultureObject));
// 下面两个大同小异,就不多说了
TSharedPtr<FJsonObject> MusicVolumeObject = MakeShareable(new FJsonObject);
MusicVolumeObject->SetNumberField("MusicVolume", MusicVolume);
TSharedPtr<FJsonValueObject> MusicVolumeValue = MakeShareable(new FJsonValueObject(MusicVolumeObject));
TSharedPtr<FJsonObject> SoundVolumeObject = MakeShareable(new FJsonObject);
SoundVolumeObject->SetNumberField("SoundVolume", SoundVolume);
TSharedPtr<FJsonValueObject> SoundVolumeValue = MakeShareable(new FJsonValueObject(SoundVolumeObject));
// 存放存档数据的 Json 对象指针的数组
TArray<TSharedPtr<FJsonValue>> RecordDataArray;
for(int i = 0; i < RecordDataList->Num(); ++i) {
TSharedPtr<FJsonObject> RecordItem = MakeShareable(new FJsonObject);
RecordItem->SetStringField(FString::FromInt(i), (*RecordDataList)[i]); // 存档的键值对
TSharedPtr<FJsonValueObject> RecordDataValue = MakeShareable(new FJsonValueObject(RecordItem));
RecordDataArray.Add(RecordDataValue); // 添加进数组
}
// 定义存档数组对象
TSharedPtr<FJsonObject> RecordDataObject = MakeShareable(new FJsonObject);
RecordDataObject->SetArrayField("RecordData", RecordDataArray);
TSharedPtr<FJsonValueObject> RecordDataValue = MakeShareable(new FJsonValueObject(RecordDataObject));
// 将上面众多 Json 对象添加到数组内
BaseDataArray.Add(CultureValue);
BaseDataArray.Add(MusicVolumeValue);
BaseDataArray.Add(SoundVolumeValue);
BaseDataArray.Add(RecordDataValue);
// 给完整的 Json 对象赋予键值对,值就是上面的数组
JsonObject->SetArrayField("T", BaseDataArray);
FString JsonStr; // 用于存放 Json 格式的 FString 字符串
GetFStringInJsonData(JsonObject, JsonStr);
SlAiHelper::Debug(FString("Origin Str: " + JsonStr), 60.f);
// 去掉多余字符
JsonStr.RemoveAt(0, 8);
JsonStr.RemoveFromEnd(FString("}"));
SlAiHelper::Debug(FString("Final Str: " + JsonStr), 60.f);
// 写入文件
WriteFileWithJsonData(JsonStr, RelativePath, RecordDataFileName);
}
bool SlAiJsonHandle::LoadStringFromFile(const FString& FileName, const FString& RelaPath, FString& ResultString)
{
if (!FileName.IsEmpty()) {
FString AbsoPath = FPaths::GameContentDir() + RelaPath + FileName;
if (FPaths::FileExists(AbsoPath)) {
if (FFileHelper::LoadFileToString(ResultString, *AbsoPath)) {
return true;
}
else {
SlAiHelper::Debug(FString("Load Error") + AbsoPath);
}
}
else {
SlAiHelper::Debug(FString("File Not Exist") + AbsoPath);
}
}
return false;
}
bool SlAiJsonHandle::GetFStringInJsonData(const TSharedPtr<FJsonObject>& JsonObj, FString& JsonStr)
{
if (JsonObj.IsValid() && JsonObj->Values.Num() > 0)
{
TSharedRef<TJsonWriter<TCHAR>> JsonWriter = TJsonWriterFactory<TCHAR>::Create(&JsonStr);
FJsonSerializer::Serialize(JsonObj.ToSharedRef(), JsonWriter); // 要保存的数据都放在 JsonObj 里了
return true;
}
return false;
}
bool SlAiJsonHandle::WriteFileWithJsonData(const FString& JsonStr, const FString& RelaPath, const FString& FileName)
{
if (!JsonStr.IsEmpty()) {
if (!FileName.IsEmpty()) {
FString AbsoPath = FPaths::ProjectContentDir() + RelaPath + FileName;
// 保存, 如果已存在的话会直接覆盖
if (FFileHelper::SaveStringToFile(JsonStr, *AbsoPath)) {
return true;
}
else {
SlAiHelper::Debug(FString("Save") + AbsoPath + FString("-->Failed"), 10.f);
}
}
}
return false;
}
SlAiDataHandle.cpp
// ... 省略
void SlAiDataHandle::ChangeLocalizationCulture(ECultureTeam Culture)
{
switch (Culture)
{
case ECultureTeam::EN:
FInternationalization::Get().SetCurrentCulture(TEXT("en"));
break;
case ECultureTeam::ZH:
FInternationalization::Get().SetCurrentCulture(TEXT("zh"));
break;
}
CurrentCulture = Culture;
// 更新存档数据
SlAiSingleton<SlAiJsonHandle>::Get()->UpdateRecordData(GetEnumValueAsString<ECultureTeam>
(FString("ECultureTeam"), CurrentCulture), MusicVolume, SoundVolume, &RecordDataList);
}
void SlAiDataHandle::ResetMenuVolume(float MusicVol, float SoundVol)
{
if (MusicVol > 0)
{
MusicVolume = MusicVol;
}
if (SoundVol > 0)
{
SoundVolume = SoundVol;
}
// 更新存档数据
SlAiSingleton<SlAiJsonHandle>::Get()->UpdateRecordData(GetEnumValueAsString<ECultureTeam>
(FString("ECultureTeam"), CurrentCulture), MusicVolume, SoundVolume, &RecordDataList);
}
启动游戏,若左上角显示数据跟随设置界面变化、Json 文件内容同步更改,则说明写入成功。
测试无误后记得把 SlAiJsonHandle.cpp 里面那两句 Debug 语句注释掉。
12. 进入游戏控件
开始新游戏按钮
这节课开始补充菜单栏 Widget 里的其他按钮。
新建两个 Slate Widget 类,分别取名为 SlAiNewGameWidget 和 SlAiChooseRecordWidget,路径为 /Public/UI/Widget/。一个是 “新游戏” (新建存档)界面,一个是 “选择存档” 界面。
首先要在菜单栏 Widget 里把新建的 Widget 添加进去
SSlAiMenuWidget.cpp
// ...其他头文件省略
#include "SSlAiNewGameWidget.h" // 新游戏
#include "SSlAiChooseRecordWidget.h" // 选择存档
#include "SlAiHelper.h"
#include "SlAiDataHandle.h"
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiMenuWidget::Construct(const FArguments& InArgs)
{
MenuStyle = &SlAiStyle::Get().GetWidgetStyle<FSlAiMenuStyle>("BPSlAiMenuStyle");
ChildSlot
[
// ...结构省略
];
RootSizeBox->SetWidthOverride(600.f);
RootSizeBox->SetHeightOverride(510.f);
ContentBox->AddSlot()
[
// 换成新游戏 Widget
SNew(SSlAiNewGameWidget)
];
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
// ... 省略
接下来是完善新建游戏页面。
新建游戏需要输入存档名,所以为了保存存档名,要在数据控制类里添加一个变量用于存储
SlAiDataHandle.h
// ... 省略
public:
ECultureTeam CurrentCulture;
float MusicVolume;
float SoundVolume;
TArray<FString> RecordDataList;
// 存档名
FString RecordName;
// ... 省略
SlAiDataHandle.cpp
// ...省略
void SlAiDataHandle::InitRecordData()
{
// 默认存档名为空
RecordName = FString("");
FString Culture;
SlAiSingleton<SlAiJsonHandle>::Get()->RecordDataJsonRead()->(Culture, MusicVolume, SoundVolume, RecordDataList);
ChangeLocalizationCulture(GetEnumValueFromString<ECultureTeam>(FString("ECultureTeam"), Culture));
// 下面这里的 Debug 代码如果没用就删除或注释掉
}
SSlAiNewGameWidget.h
#pragma once
#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"
class SEditableTextBox; // 提前声明
class SLAICOURSE_API SSlAiNewGameWidget : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SSlAiNewGameWidget)
{}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs);
// 是否可以进入游戏
bool AllowEnterGame();
private:
// 获取样式类
const struct FSlAiMenuStyle* MenuStyle;
// 输入框指针
TSharedPtr<SEditableTextBox> EditTextBox;
};
SSlAiNewGameWidget.cpp
#include "UI/Widget/SSlAiNewGameWidget.h"
#include "SlateOptMacros.h"
// 补充头文件
#include "SlAiStyle.h"
#include "SlAiMenuWidgetStyle.h"
#include "SBox.h"
#include "STextBlock.h"
#include "SOverlay.h"
#include "SImage.h"
#include "SEditableTextBox.h"
#include "SlAiDataHandle.h" // 别忘了
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiNewGameWidget::Construct(const FArguments& InArgs)
{
// 获取样式类
MenuStyle = &SlAiStyle::Get().GetWidgetStyle<FSlAiMenuStyle>("BPSlAiMenuStyle");
// 新建存档的结构
ChildSlot
[
SNew(SBox)
.WidthOverride(500.f)
.HeightOverride(100.f)
[
SNew(SOverlay)
+SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SNew(SImage)
.Image(&MenuStyle->MenuItemBrush)
]
+SOverlay::Slot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(FMargin(20.f, 0.f, 0.f, 0.f))
[
SNew(STextBlock)
.Font(MenuStyle->Font_40)
.Text(NSLOCTEXT("SlAiMenu", "NewGame", "NewGame"))
]
+SOverlay::Slot()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(FMargin(0.f, 0.f, 20.f, 0.f))
[
SNew(SBox)
.WidthOverride(300.f)
.HeightOverride(60.f)
[
SAssignNew(EditTextBox, SEditableTextBox)
// 输入框为空时显示的提示输入内容
.HintText(NSLOCTEXT("SlAiMenu", "RecordNameHint", "Input Record Name!"))
.Font(MenuStyle->Font_30)
]
]
]
];
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
bool SSlAiNewGameWidget::AllowEnterGame()
{
// 获取输入的新存档名
FText InputText = EditTextBox->GetText();
// 文字是否为空
if (InputText.ToString().IsEmpty()) return false;
// 查询是否已经存在存档
for (TArray<FString>::TIterator It(SlAiDataHandle::Get()->RecordDataList); It; ++It) {
if ((*It).Equals(InputText.ToString())) {
// 设置 TextBox 为空
EditTextBox->SetText(FText::FromString(""));
// 修改 Hint 为存档名重复
EditTextBox->SetHintText(NSLOCTEXT("SlAiMenu", "NameRepeatedHint", "Record Name Repeated!"));
// 返回 false
return false;
}
}
// 保存新的存档名
SlAiDataHandle::Get()->RecordName = InputText.ToString();
// 返回 true
return true;
}
完成后效果如下图。
选择存档按钮
替换菜单栏界面的 Widget 为选择存档 Widget
SSlAiMenuWidget.cpp
// ... 一顿省略
ContentBox->AddSlot()
[
// 换成新的游戏 Widget
SNew(SSlAiChooseRecordWidget)
];
// ... 省略
SSlAiChooseRecordWidget.h
#pragma once
#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"
class STextComboBox; // 提前声明
class SLAICOURSE_API SSlAiChooseRecordWidget : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SSlAiChooseRecordWidget)
{}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs);
// 更新存档名
void UpdateRecordName();
private:
// 获取样式类
const struct FSlAiMenuStyle* MenuStyle;
// 下拉菜单的指针
TSharedPtr<STextComboBox> RecordComboBox;
// 保存下拉菜单条目的字符指针数组
TArray<TSharedPtr<FString>> OptionsSource;
};
SSlAiChooseRecordWidget.cpp
#include "UI/Widget/SSlAiNewGameWidget.h"
#include "SlateOptMacros.h"
// 补充头文件
#include "SlAiStyle.h"
#include "SlAiMenuWidgetStyle.h"
#include "SBox.h"
#include "STextBlock.h"
#include "SOverlay.h"
#include "SImage.h"
#include "STextComboBox.h" // 下拉框的头文件
#include "SlAiDataHandle.h" // 别忘了
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiChooseRecordWidget::Construct(const FArguments& InArgs)
{
// 获取样式类
MenuStyle = &SlAiStyle::Get().GetWidgetStyle<FSlAiMenuStyle>("BPSlAiMenuStyle");
// 初始化下拉菜单数据
for (TArray<FString>::TIterator It(SlAiDataHandle::Get()->RecordDataList); It; ++It) {
OptionsSource.Add(MakeShareable(new FString(*It)));
}
ChildSlot
[
SNew(SBox)
.WidthOverride(500.f)
.HeightOverride(100.f)
[
SNew(SOverlay)
+SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SNew(SImage)
.Image(&MenuStyle->MenuItemBrush)
]
+SOverlay::Slot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(FMargin(20.f, 0.f, 0.f, 0.f))
[
SNew(STextBlock)
.Font(MenuStyle->Font_40)
.Text(NSLOCTEXT("SlAiMenu", "ChooseRecord", "ChooseRecord")) // 这里老师写错了,用了 NewGame
]
+SOverlay::Slot()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(FMargin(0.f, 0.f, 20.f, 0.f))
[
SNew(SBox)
.WidthOverride(300.f)
.HeightOverride(60.f)
[
SAssignNew(RecordComboBox, STextComboBox)
.Font(MenuStyle->Font_30)
.OptionsSource(&OptionsSource)
]
]
]
];
// 设置默认的选项
RecordComboBox->SetSelectedItem(OptionsSource[0]);
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiChooseRecordWidget::UpdateRecordName()
{
// 修改存档名
SlAiDataHandle::Get()->RecordName = *RecordComboBox->GetSelectedItem().Get();
}
上面的所有代码写好后,我们就得到了带有输入框的新游戏界面和带有下拉框的选择存档界面。选择存档界面如图(选择存档的文本是我后面发现了才改正的,这张图大家凑合着看吧,重点是下拉框 = = u )
13. 菜单控件初始化
这集课程继续完善整个菜单界面的内容。
首先在数据结构类添加界面类型的枚举。
SlAiTypes.h
// ... 省略
// Menu 界面类型
namespace EMenuType
{
enum Type
{
None,
MainMenu,
StartGame,
GameOption,
NewGame,
ChooseRecord
};
}
然后在菜单栏界面进一步完善动态配置界面的逻辑。
SSlAiMenuWidget.h
#pragma once
#include "CoreMinimal.h"
#include "SlAiTypes.h"
#include "Widgets/SCompoundWidget.h"
class SBox;
class STextBlock;
class SVerticalBox;
// 添加 4 个声明
struct MenuGroup;
class SSlAiGameOptionWidget;
class SSlAiNewGameWidget;
class SSlAiChooseRecordWidget;
class SLAICOURSE_API SSlAiMenuWidget : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SSlAiMenuWidget)
{}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs);
private:
void MenuItemOnClicked(EMenuItem::Type ItemType);
void ChangeCulture(ECultureTeam Culture);
void ChangeVolume(const float MusicVolume, const float SoundVolume);
// 初始化所有的控件
void InitializeMenuList();
// 选择显示的界面
void ChooseWidget(EMenuType::Type WidgetType);
// 修改菜单的大小(这里第一个参数名,老师写成了 NewWidget,此处看读者喜好是否更改)
void ResetWidgetSize(float NewWidth, float NewHeight);
private:
TSharedPtr<SBox> RootSizeBox;
const struct FSlAiMenuStyle* MenuStyle;
TSharedPtr<STextBlock> TitleText;
TSharedPtr<SVerticalBox> ContentBox;
// 保存菜单组
TMap<EMenuType::Type, TSharedPtr<MenuGroup>> MenuMap;
// 游戏设置 Widget 的指引
TSharedPtr<SSlAiGameOptionWidget> GameOptionWidget;
// 新游戏控件指针
TSharedPtr<SSlAiNewGameWidget> NewGameWidget;
// 选择存档控件指针
TSharedPtr<SSlAiChooseRecordWidget> ChooseRecordWidget;
};
SSlAiMenuWidget.cpp
// ... 头文件省略
// 新增结构体,用来存储每个界面的具体内容
struct MenuGroup
{
// 菜单标题
FText MenuName;
// 菜单高度
float MenuHeight;
// 下属组件
TArray<TSharedPtr<SCompoundWidget>> ChildWidget;
// 构造函数
MenuGroup(const FText Name, const float Height, TArray<TSharedPtr<SCompoundWidget>>* Children)
{
MenuName = Name;
MenuHeight = Height;
for (TArray<TSharedPtr<SCompoundWidget>>::TIterator It(*Children); It; ++It)
{
ChildWidget.Add(*It);
}
}
};
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiMenuWidget::Construct(const FArguments& InArgs)
{
MenuStyle = &SlAiStyle::Get().GetWidgetStyle<FSlAiMenuStyle>("BPSlAiMenuStyle");
ChildSlot
[
// ...结构省略
];
// 用初始化方法替代原来的设置尺寸和添加 Widget
InitializeMenuList();
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
// ... 省略此处三个方法的实现
void SSlAiMenuWidget::InitializeMenuList()
{
// 实例化主界面
TArray<TSharedPtr<SCompoundWidget>> MainMenuList;
MainMenuList.Add(
SNew(SSlAiMenuItemWidget)
.ItemText(NSLOCTEXT("SlAiMenu", "StartGame", "StartGame"))
.ItemType(EMenuItem::StartGame)
.OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
MainMenuList.Add(
SNew(SSlAiMenuItemWidget)
.ItemText(NSLOCTEXT("SlAiMenu", "GameOption", "GameOption"))
.ItemType(EMenuItem::GameOption)
.OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
MainMenuList.Add(
SNew(SSlAiMenuItemWidget)
.ItemText(NSLOCTEXT("SlAiMenu", "QuitGame", "QuitGame"))
.ItemType(EMenuItem::QuitGame)
.OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
MenuMap.Add(EMenuType::MainMenu, MakeShareable(new MenuGroup(NSLOCTEXT("SlAiMenu", "Menu", "Menu"), 510.f, &MainMenuList)));
// 开始游戏界面
TArray<TSharedPtr<SCompoundWidget>> StartGameList;
StartGameList.Add(
SNew(SSlAiMenuItemWidget)
.ItemText(NSLOCTEXT("SlAiMenu", "NewGame", "NewGame"))
.ItemType(EMenuItem::NewGame)
.OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
StartGameList.Add(
SNew(SSlAiMenuItemWidget)
.ItemText(NSLOCTEXT("SlAiMenu", "LoadRecord", "LoadRecord"))
.ItemType(EMenuItem::LoadRecord)
.OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
StartGameList.Add(
SNew(SSlAiMenuItemWidget)
.ItemText(NSLOCTEXT("SlAiMenu", "GoBack", "GoBack"))
.ItemType(EMenuItem::StartGameGoBack)
.OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
MenuMap.Add(EMenuType::StartGame, MakeShareable(new MenuGroup(NSLOCTEXT("SlAiMenu", "StartGame", "StartGame"), 510.f, &StartGameList)));
// 游戏设置界面
TArray<TSharedPtr<SCompoundWidget>> GameOptionList;
// 实例化游戏设置的 Widget
SAssignNew(GameOptionWidget, SSlAiGameOptionWidget)
.ChangeCulture(this, &SSlAiMenuWidget::ChangeCulture)
.ChangeVolume(this, &SSlAiMenuWidget::ChangeVolume);
// 添加控件到数组
GameOptionList.Add(GameOptionWidget);
GameOptionList.Add(
SNew(SSlAiMenuItemWidget)
.ItemText(NSLOCTEXT("SlAiMenu", "GoBack", "GoBack"))
.ItemType(EMenuItem::GameOptionGoBack)
.OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
MenuMap.Add(EMenuType::GameOption, MakeShareable(new MenuGroup(NSLOCTEXT("SlAiMenu", "GameOption", "GameOption"), 610.f, &GameOptionList)));
// 开始新游戏界面
TArray<TSharedPtr<SCompoundWidget>> NewGameList;
SAssignNew(NewGameWidget, SSlAiNewGameWidget);
NewGameList.Add(NewGameWidget);
NewGameList.Add(
SNew(SSlAiMenuItemWidget)
.ItemText(NSLOCTEXT("SlAiMenu", "EnterGame", "EnterGame"))
.ItemType(EMenuItem::EnterGame)
.OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
NewGameList.Add(
SNew(SSlAiMenuItemWidget)
.ItemText(NSLOCTEXT("SlAiMenu", "GoBack", "GoBack"))
.ItemType(EMenuItem::NewGameGoBack)
.OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
MenuMap.Add(EMenuType::NewGame, MakeShareable(new MenuGroup(NSLOCTEXT("SlAiMenu", "NewGame", "NewGame"), 510.f, &NewGameList)));
// 选择存档界面
TArray<TSharedPtr<SCompoundWidget>> ChooseRecordList;
SAssignNew(ChooseRecordWidget, SSlAiChooseRecordWidget);
ChooseRecordList.Add(ChooseRecordWidget); // 下一集会补充
ChooseRecordList.Add(
SNew(SSlAiMenuItemWidget)
.ItemText(NSLOCTEXT("SlAiMenu", "EnterRecord", "EnterRecord"))
.ItemType(EMenuItem::EnterRecord)
.OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
ChooseRecordList.Add(
SNew(SSlAiMenuItemWidget)
.ItemText(NSLOCTEXT("SlAiMenu", "GoBack", "GoBack"))
.ItemType(EMenuItem::ChooseRecordGoBack)
.OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
MenuMap.Add(EMenuType::ChooseRecord, MakeShareable(new MenuGroup(NSLOCTEXT("SlAiMenu", "LoadRecord", "LoadRecord"), 510.f, &ChooseRecordList)));
ChooseWidget(EMenuType::MainMenu);
}
void SSlAiMenuWidget::ChooseWidget(EMenuType::Type WidgetType)
{
// 移除所有组件
ContentBox->ClearChildren();
// 如果 MenuType 是 None
if (WidgetType == EMenuType::None) return;
// 循环添加组件
for (TArray<TSharedPtr<SCompoundWidget>>::TIterator It((*MenuMap.Find(WidgetType))->ChildWidget); It; ++It) {
ContentBox->AddSlot().AutoHeight()[(*It)->AsShared()];
}
// 更改标题
TitleText->SetText((*MenuMap.Find(WidgetType))->MenuName);
// 修改 Size
ResetWidgetSize(600.f, (*MenuMap.Find(WidgetType))->MenuHeight);
}
// 如果不修改高度,NewHeight 转入-1
void SSlAiMenuWidget::ResetWidgetSize(float NewWidth, float NewHeight)
{
RootSizeBox->SetWidthOverride(NewWidth);
if (NewHeight < 0) return;
RootSizeBox->SetHeightOverride(NewHeight);
}
到这里,菜单栏界面的初步布局就已经写好了。