本节主要讲解游戏的起始页,登录页,注册页的实现。主要讲解的知识点是C++如何使用UMG做界面开发和UE4如何实现HTTP通讯及JSON的序列化和反序列化。

1、起始页开发

1、创建蓝图版UserWidget

首先创建好UI目录,把UI素材导入到目录中。

创建起始页蓝图版的UserWidget,按照上述成品图设计即可。包含的元素如下:

  • 背景图片
  • Logo图片
  • 开始游戏按钮
  • 注册账号按钮
  • 退出游戏按钮
  • 版本信息文本

注意事项:设计UMG界面时特别需要注意锚点的使用以及布局面板的使用,起始页面使用的是
VerticalPanel对右面的Logo到版本信息进行了垂直布局。

2、创建C++版UserWidget

首先我们要在Build.cs里面把UMG和Slate配置上去。

创建蓝图UserWidget的父类,让上面的蓝图继承之。因为我们要获取起始页面具体的按钮的引用来进一步对UMG的Widget进行操作。

根据上面的界面设计,我们在头文件中创建了三个UButton,分别表示开始游戏、注册游戏、退出游戏。我们希望这三个成员变量在子类蓝图UMG创建时自动初始化,因此我们重写UserWidget的Initialize()方法,在蓝图UMG初始化时自动初始化父类(UStartUserWidget)的三个按钮成员变量即可。

退出游戏按钮的事件非常的简单,因此我们这里在父类实现即可。添加OnClicked事件并实现退出游戏的逻辑。

综上,我们可以看出UMG的基本的开发思路,首先要设计好要展示的蓝图版的UMG,然后创建C++版的UserWidget,其成员变量/方法应是关于蓝图版的UMG的数据,以方便来使用蓝图版的UMG里面定义的控件。然后回到蓝图版的UMG让其父类继承之。

起始页头文件(StartUserWidget.h)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 游戏开始界面
*/
UCLASS()
class INFINITYBLADE_API UStartUserWidget : public UUserWidget
{
GENERATED_BODY()

public:
/** 开始游戏按钮 */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "UI")
UButton* StartBtn;
/** 注册游戏按钮 */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "UI")
UButton* RegisterBtn;
/** 退出游戏按钮 */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "UI")
UButton* ExitBtn;
public:
/** 创建UserWidget对象后执行的生命周期方法 */
virtual bool Initialize() override;
/** 初始化控件引用 */
void Init();
/** 退出游戏按钮点击事件 */
UFUNCTION()
void ExitBtnClickedEvent();
};

起始页cpp文件(StartUserWidget.cpp)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include "StartUserWidget.h"

/** 创建UserWidget对象后执行的生命周期方法 */
bool UStartUserWidget::Initialize()
{
if (!Super::Initialize())
{
return false;
}

/** 初始化控件的引用 */
Init();

return true;
}

/** 初始化控件引用 */
void UStartUserWidget::Init()
{
/** 初始化游戏开始按钮 */
StartBtn = Cast<UButton>(GetWidgetFromName(TEXT("Button_Start")));
/** 初始化游戏注册按钮 */
RegisterBtn = Cast<UButton>(GetWidgetFromName(TEXT("Button_Register")));
/** 初始化游戏退出按钮 */
ExitBtn = Cast<UButton>(GetWidgetFromName(TEXT("Button_Exit")));
/** 设置退出游戏按钮的点击事件 */
ExitBtn->OnClicked.AddDynamic(this, &UStartUserWidget::ExitBtnClickedEvent);
}

/** 退出游戏按钮点击事件 */
void UStartUserWidget::ExitBtnClickedEvent()
{
/** 退出游戏 */
UKismetSystemLibrary::QuitGame(GetWorld(), nullptr, EQuitPreference::Quit);
}

3、事件的监听

上面我们已经把退出游戏按钮的点击事件实现了,还有开始游戏注册账号两个按钮的事件没有实现,此时,我们考虑一下MVC的设计模式以及高内聚低耦合的模式,我们应该把那两个按钮的事件放在哪里呢?

首先肯定是不能放到自己写的C++父类里面的,如果放在里面的话,我们首先要获取开始游戏页面和注册游戏页面的控件引用,这样就不符合低耦合的概念了。所以这里考虑UE4提供的Gameplay框架,我们首先是考虑放到了GameMode这个类里面。下面是游戏首页关卡的游戏模式类,该类里面拥有我们对于UI的处理逻辑。我们看到还有两个成员变量分别是登录页面和注册页面的UI,我们现在先暂时不考虑,下面会讲解这两个页面的开发。

我们首先看一下起始页面UI的初始化、开始游戏按钮的回调事件和注册游戏按钮的回调事件。

  • 初始化UStartUserWidget对象这里采用的静态加载LoadClass()方法,我们也可以创建游戏模式的蓝图,使用TSubclassOf的方式来加载,这里技术细节不具体赘述了。初始化完成后把该UMG添加到Viewport即可。
  • 开始游戏按钮和注册游戏按钮只需要添加OnClicked事件即可。在这两个游戏按钮的点击事件上,我们动态的切换Viewport展示的UMG即可。

至此,我们已经把游戏起始页的基本功能完成了,这里只是粗略的赘述了一下,如果想更了解细节可以看源代码来自己手动实现。

首页游戏关卡的游戏模式类的头文件:EmptyGameMode.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* 初始化关卡游戏模式
*/
UCLASS()
class INFINITYBLADE_API AEmptyGameMode : public AGameMode
{
GENERATED_BODY()
public:
/** 起始页面UI */
UPROPERTY()
UStartUserWidget* StartUI;
/** 登录页面UI */
UPROPERTY()
ULoginUserWidget* LoginUI;
/** 注册页面UI */
UPROPERTY()
URegisterUserWidget* RegisterUI;
public:
/** 游戏开始调用的方法 */
virtual void BeginPlay() override;
/** 开始游戏按钮回调事件 */
UFUNCTION()
void StartBtnClickedEvent();
/** 注册按钮回调事件 */
UFUNCTION()
void RegisterBtnClickedEvent();
/** 登录页返回按钮回调事件 */
UFUNCTION()
void LoginBackBtnClickedEvent();
/** 注册页返回按钮回调事件 */
UFUNCTION()
void RegisterBackBtnClickedEvent();
};
```​​​​

首页游戏关卡的游戏模式类cpp文件:EmptyGameMode.cpp

``` C++
#include "EmptyGameMode.h"

/** 游戏开始调用的方法 */
void AEmptyGameMode::BeginPlay()
{
/** 初始化起始页UI */
StartUI = CreateWidget<UStartUserWidget>(GetGameInstance(), LoadClass<UStartUserWidget>(nullptr, TEXT("WidgetBlueprint'/Game/UI/Start/UI_Start.UI_Start_C'")));
/** 设置开始游戏按钮的点击事件 */
StartUI->StartBtn->OnClicked.AddDynamic(this, &AEmptyGameMode::StartBtnClickedEvent);
/** 设置注册按钮的点击事件 */
StartUI->RegisterBtn->OnClicked.AddDynamic(this, &AEmptyGameMode::RegisterBtnClickedEvent);

/**初始化登录页UI */
LoginUI = CreateWidget<ULoginUserWidget>(GetGameInstance(), LoadClass<ULoginUserWidget>(nullptr, TEXT("WidgetBlueprint'/Game/UI/Login/UI_Login.UI_Login_C'")));
/** 设置返回按钮的点击事件 */
LoginUI->BackBtn->OnClicked.AddDynamic(this, &AEmptyGameMode::LoginBackBtnClickedEvent);

/**初始化注册页UI */
RegisterUI = CreateWidget<URegisterUserWidget>(GetGameInstance(), LoadClass<URegisterUserWidget>(nullptr, TEXT("WidgetBlueprint'/Game/UI/Register/UI_Register.UI_Register_C'")));
/** 设置返回按钮的点击事件 */
RegisterUI->BackBtn->OnClicked.AddDynamic(this, &AEmptyGameMode::RegisterBackBtnClickedEvent);

/** 添加起始页到视口 */
StartUI->AddToViewport();

}

/** 开始游戏按钮回调事件 */
void AEmptyGameMode::StartBtnClickedEvent()
{
/** 移除起始页UI */
StartUI->RemoveFromViewport();
/** 添加登录页UI到视口 */
LoginUI->AddToViewport();
}

/** 注册按钮回调事件 */
void AEmptyGameMode::RegisterBtnClickedEvent()
{
/** 移除起始页UI */
StartUI->RemoveFromViewport();
/** 添加注册页UI到视口 */
RegisterUI->AddToViewport();
}

/** 登录页返回按钮回调事件 */
void AEmptyGameMode::LoginBackBtnClickedEvent()
{
/** 移除登录页UI */
LoginUI->RemoveFromViewport();
/** 添加起始页UI到视口 */
StartUI->AddToViewport();
}

/** 注册页返回按钮回调事件 */
void AEmptyGameMode::RegisterBackBtnClickedEvent()
{
/** 移除登录页UI */
RegisterUI->RemoveFromViewport();
/** 添加起始页UI到视口 */
StartUI->AddToViewport();
}

2、注册页开发

1、蓝图的创建及C++的创建

首先我们看一下注册页面的布局,典型的上下结构,因此我们首先根据素材导入UE4后创建蓝图版的UMG,然后进行设计。需要注意的是要学会使用布局面板和锚点来设计,保证页面在伸缩时保持弹性。接着我们看一下非样式的控件有哪些:

  • 左上角的返回按钮
  • 账号输入框
  • 密码输入框
  • 确认密码输入框
  • 注册按钮
  • 加载控件,一个选装的Loading框
  • Toast控件,显示错误信息的自定义面板,后面会介绍

根据上面的描述,我们为该UMG创建父类。注册页面我们要是使用JSON来向服务器发送数据。同时使用HTTP与服务器进行交互,所以,我们要在Build.cs里面配置JSON和HTTP。

Web服务器已经对非进行了数据校验,但是数据加密并没有实现,这里应该对账号和密码进行MD5加密,这里没有实现,但是企业级项目大家要注意。我们在本地端就简单的实现两次密码是否保持一致的校验即可。如果密码错误,我们就显示Toast面板来提示。注意:这里有个问题,提示信息如果是中文在安卓打包时会报错,现在还没去解决,因此使用的英文。

本地端进行简单的数据校验以后,就进行服务器的请求,首先我们会先对输入的用户名和密码进行拼接,形成JSON数据串,然后再提交到服务器上。具体的代码在cpp文件里可以看到,注释也比较详细。这里提交服务器的时候,比较友好的是先把注册按钮禁用掉,然后显示Loading框,当服务器返回结果后再把Loading框去掉,把注册按钮的禁用去掉。

注册页面头文件:RegisterUserWidget.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* 注册界面UI
*/
UCLASS()
class INFINITYBLADE_API URegisterUserWidget : public UUserWidget
{
GENERATED_BODY()

public:
/** 账号输入框 */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "UI")
UEditableTextBox* AccountBox;
/** 密码输入框 */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "UI")
UEditableTextBox* PasswordBox;
/** 密码确认输入框 */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "UI")
UEditableTextBox* RePasswordBox;
/** 注册按钮 */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "UI")
UButton* RegisterBtn;
/** 后退按钮 */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "UI")
UButton* BackBtn;
/** 加载控件 */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "UI")
UCircularThrobber* LoadingCircle;
/** Toast控件 */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "UI")
UToastUserWidget* ToastWidget;
public:
/** 对象创建后调用的方法 */
virtual bool Initialize() override;
public:
/** 初始化控件引用 */
void Init();
public:
/** 注册按钮点击事件 */
UFUNCTION()
void RegisterBtnClickedEvent();
/** 注册方法 */
void RegisterServer(FString& Nickname,FString& Password);
/** 注册方法的服务器回调 */
void ServerRegisterCompleteCallback(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bIsSuccessful);
};

注册页面cpp文件:RegisterUserWidget.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/** 对象创建后调用的方法 */
bool URegisterUserWidget::Initialize()
{
if (!Super::Initialize())
{
return false;
}

/** 初始化控件引用 */
Init();

return true;
}

/** 初始化控件引用 */
void URegisterUserWidget::Init()
{
/** 初始化账号输入框 */
AccountBox = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("EditableTextBox_Account")));
/** 初始化密码输入框 */
PasswordBox = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("EditableTextBox_Password")));
/** 初始化确认密码输入框 */
RePasswordBox = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("EditableTextBox_RePassword")));
/** 初始化注册按钮 */
RegisterBtn = Cast<UButton>(GetWidgetFromName(TEXT("Button_Register")));
/** 设置注册按钮的点击事件 */
RegisterBtn->OnClicked.AddDynamic(this, &URegisterUserWidget::RegisterBtnClickedEvent);
/** 初始化后退按钮 */
BackBtn = Cast<UButton>(GetWidgetFromName(TEXT("Button_Back")));
/** 初始化加载控件 */
LoadingCircle = Cast<UCircularThrobber>(GetWidgetFromName(TEXT("CircularThrobber")));
/** 初始化Toast控件 */
ToastWidget = Cast<UToastUserWidget>(GetWidgetFromName(TEXT("UI_Toast")));
}

/** 注册按钮点击事件 */
void URegisterUserWidget::RegisterBtnClickedEvent()
{
/** 获取注册用户名 */
FString Nickname = AccountBox->GetText().ToString();
/** 获取注册密码 */
FString Password = PasswordBox->GetText().ToString();
/** 获取确认的注册密码 */
FString RePassword = RePasswordBox->GetText().ToString();
/** 判断两次输入的密码是否一致 */
if (!Password.Equals(RePassword))
{
/** 设置提示信息 */
ToastWidget->Msg->SetText(FText::FromString("The Two Input Password Is Not Same!"));
/** 显示Toast */
ToastWidget->SetVisibility(ESlateVisibility::Visible);
return;
}
/** 注册服务器 */
RegisterServer(Nickname, Password);
}

/** 注册方法 */
void URegisterUserWidget::RegisterServer(FString& Nickname, FString& Password)
{
/** 创建要提交的数据 */
FString Data;

/** 创建Json写入器 */
TSharedPtr<TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>> JsonWriter = TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create(&Data);
/** 开始写入Json对象 */
JsonWriter->WriteObjectStart();
/** 写入昵称 */
JsonWriter->WriteValue("nickname", Nickname);
/** 写入密码 */
JsonWriter->WriteValue("password", Password);
/** 结束写入Json对象 */
JsonWriter->WriteObjectEnd();
/** 关闭Json写入器 */
JsonWriter->Close();

/** 创建Http请求 */
TSharedPtr<IHttpRequest> Request = FHttpModule::Get().CreateRequest();
/** 设置请求头 */
Request->SetHeader("Content-Type", "application/json;charset=utf-8");
/** 设置请求方式 */
Request->SetVerb("POST");
/** 设置请求的URL */
Request->SetURL("http://www.ujeat.com:7900/user/register-user");
/** 设置上传的数据 */
Request->SetContentAsString(Data);
/** 设置请求成功后的回调事件 */
Request->OnProcessRequestComplete().BindUObject(this, &URegisterUserWidget::ServerRegisterCompleteCallback);
/** 处理请求 */
Request->ProcessRequest();

/** 显示进度控件 */
LoadingCircle->SetVisibility(ESlateVisibility::Visible);
/** 设置注册按钮不可用 */
RegisterBtn->SetIsEnabled(false);
}
/** 注册方法的服务器回调 */
void URegisterUserWidget::ServerRegisterCompleteCallback(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bIsSuccessful)
{
/** 隐藏进度控件 */
LoadingCircle->SetVisibility(ESlateVisibility::Hidden);
/** 设置注册按钮可用 */
RegisterBtn->SetIsEnabled(true);

/** 判断服务器返回状态 */
if (!EHttpResponseCodes::IsOk(Response->GetResponseCode()))
{
/** 如果服务器未返回成功则返回 */
return;
}

/** 创建服务器返回的内容 */
FString Result = Response->GetContentAsString();
/** 创建要解析的JSON对象 */
TSharedPtr<FJsonObject> JsonObject;
/** 创建Json解析器 */
TSharedRef<TJsonReader<TCHAR>> JsonReader = TJsonReaderFactory<TCHAR>::Create(Result);
/** 解析Json */
bool bIsParseSuccess = FJsonSerializer::Deserialize(JsonReader, JsonObject);
/** 判断是否解析成功 */
if (bIsParseSuccess)
{
/** 获取响应状态 */
FString ResponseStatus = JsonObject->GetStringField("status");
/** 获取响应信息 */
FString ResponseMsg = JsonObject->GetStringField("msg");

/** 设置服务器响应信息 */
ToastWidget->Msg->SetText(FText::FromString(ResponseMsg));
/** 显示Toast */
ToastWidget->SetVisibility(ESlateVisibility::Visible);

}
}

2、Toast面板

这里对Toast面板进行简单的介绍,其实和上面开发UMG的思路一致,创建蓝图版再创建C++版,然后C++里持有引用,这里可以看到,引用的控件只是简单的Text控件,我们只对其SetText()即可,这里使用了BackgroundBlur控件进行了背景的模糊处理,这里就不过多赘述了,大家可以自己实现一下。

3、登录页开发

登录页面和注册页面类似,这里只把代码复制上,大家可以自行Review,实现思路和注册页面类似,甚至更简单。登录成功以后我们切换关卡到主战场的Level,这里因为后期有排行榜,所以用户登录成功以后的信息需要进行保存,这里我存到了GameInstace里面,顾名思义就是游戏实例,其生命周期是伴随游戏开始到游戏退出的,如果想做持久化的话,这里可以选用其它的方式如SQLite,这里以后再去研究,我们先假定每次打开游戏都需要进行登录。

登录页面头文件:LoginUserWidget.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* 登录界面UI
*/
UCLASS()
class INFINITYBLADE_API ULoginUserWidget : public UUserWidget
{
GENERATED_BODY()

public:
/** 账号输入框 */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "UI")
UEditableTextBox* AccountBox;
/** 密码输入框 */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "UI")
UEditableTextBox* PasswordBox;
/** 登录按钮 */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "UI")
UButton* LoginBtn;
/** 后退按钮 */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "UI")
UButton* BackBtn;
/** 加载控件 */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "UI")
UCircularThrobber* LoadingCircle;
/** Toast控件 */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "UI")
UToastUserWidget* ToastWidget;
public:
/** 创建UserWidget对象后触发 */
virtual bool Initialize() override;
public:
/** 初始化控件引用 */
void Init();
public:
/** 登录按钮点击事件 */
UFUNCTION()
void LoginBtnClickedEvent();
/** 用户登录 */
void LoginServer(FString& Nickname,FString& Password);
/** 服务器响应的回调事件 */
void ServerCompleteCallback(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bIsSuccessful);
};

登录页面cpp文件:LoginUserWidget.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#include "LoginUserWidget.h"

/** 创建UserWidget对象后触发 */
bool ULoginUserWidget::Initialize()
{
/** 不加这个判断可能会报错 */
if (!Super::Initialize())
{
return false;
}

/** 初始化对象的引用 */
Init();

return true;
}

/** 初始化控件引用 */
void ULoginUserWidget::Init()
{
/** 初始化账号输入框 */
AccountBox = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("EditableTextBox_Account")));
/** 初始化密码输入框 */
PasswordBox = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("EditableTextBox_Password")));
/** 初始化登录按钮 */
LoginBtn = Cast<UButton>(GetWidgetFromName(TEXT("Button_Login")));
/** 设置登录按钮的点击事件 */
LoginBtn->OnClicked.AddDynamic(this, &ULoginUserWidget::LoginBtnClickedEvent);
/** 初始化后退按钮 */
BackBtn = Cast<UButton>(GetWidgetFromName(TEXT("Button_Back")));
/** 初始化加载控件 */
LoadingCircle = Cast<UCircularThrobber>(GetWidgetFromName(TEXT("CircularThrobber")));
/** 初始化Toast控件 */
ToastWidget = Cast<UToastUserWidget>(GetWidgetFromName(TEXT("UI_Toast")));
}

/** 登录按钮点击事件 */
void ULoginUserWidget::LoginBtnClickedEvent()
{
/** 获取输入的用户名 */
FString Nickname = AccountBox->GetText().ToString();
/** 获取输入的密码 */
FString Password = PasswordBox->GetText().ToString();
/** 登录服务器 */
LoginServer(Nickname, Password);
}

/** 用户登录 */
void ULoginUserWidget::LoginServer(FString& Nickname, FString& Password)
{
/** 创建要发送的数据 */
FString Data;

/** 创建Json写入器 */
TSharedPtr<TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>> JsonWriter = TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create(&Data);
/** 开始写入对象数据 */
JsonWriter->WriteObjectStart();
/** 写入账号昵称 */
JsonWriter->WriteValue("nickname", Nickname);
/** 写入账号密码 */
JsonWriter->WriteValue("password", Password);
/** 结束写入对象数据 */
JsonWriter->WriteObjectEnd();
/** 关闭JsonWriter */
JsonWriter->Close();

/** 创建Http请求对象 */
TSharedPtr<IHttpRequest> HttpRequest = FHttpModule::Get().CreateRequest();
/** 设置请求头 */
HttpRequest->SetHeader("Content-Type", "application/json;charset=utf-8");
/** 设置请求的URL */
HttpRequest->SetURL("http://www.ujeat.com:7900/user/login-user");
/** 设置请求方式为POST */
HttpRequest->SetVerb("POST");
/** 设置上传的JSON数据 */
HttpRequest->SetContentAsString(Data);
/** 设置请求成功后的响应事件 */
HttpRequest->OnProcessRequestComplete().BindUObject(this, &ULoginUserWidget::ServerCompleteCallback);
/** 处理请求 */
HttpRequest->ProcessRequest();

/** 显示进度控件 */
LoadingCircle->SetVisibility(ESlateVisibility::Visible);
/** 设置登录按钮不可用 */
LoginBtn->SetIsEnabled(false);
}
/** 服务器响应的回调事件 */
void ULoginUserWidget::ServerCompleteCallback(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bIsSuccessful)
{

/** 隐藏进度控件 */
LoadingCircle->SetVisibility(ESlateVisibility::Hidden);
/** 设置登录按钮可用 */
LoginBtn->SetIsEnabled(true);

/** 判断服务器返回状态 */
if (!EHttpResponseCodes::IsOk(Response->GetResponseCode()))
{
/** 如果服务器未返回成功则返回 */
return;
}

/** 创建服务器返回的内容 */
FString Result = Response->GetContentAsString();
/** 创建要解析的JSON对象 */
TSharedPtr<FJsonObject> JsonObject;
/** 创建Json解析器 */
TSharedRef<TJsonReader<TCHAR>> JsonReader = TJsonReaderFactory<TCHAR>::Create(Result);
/** 解析Json */
bool bIsParseSuccess = FJsonSerializer::Deserialize(JsonReader, JsonObject);
/** 判断是否解析成功 */
if (bIsParseSuccess)
{
/** 获取响应状态 */
FString ResponseStatus = JsonObject->GetStringField("status");
/** 获取响应信息 */
FString ResponseMsg = JsonObject->GetStringField("msg");
/** 获取响应的数据对象 */
TSharedPtr<FJsonObject> Data = JsonObject->GetObjectField("data");
/** 获取用户的id */
FString Id = Data->GetStringField("id");
/** 获取用户的昵称 */
FString Nickname = Data->GetStringField("nickname");
/** 获取用户的密码 */
FString Password = Data->GetStringField("password");
/** 判断是否登录成功 */
if (!Id.IsEmpty())
{
/** 登录成功则保存用户数据到GameInstance */
UCustomGameInstance* GameInstance = Cast<UCustomGameInstance>(GetWorld()->GetGameInstance());
/** 保存Id */
GameInstance->ContextMap.Add("id", Id);
/** 保存昵称 */
GameInstance->ContextMap.Add("nickname", Nickname);
/** 保存用户密码 */
GameInstance->ContextMap.Add("password", Password);
/** 切换关卡到主战场 */
/** 打开主关卡:注意此处打开关卡的路径 */
UGameplayStatics::OpenLevel(GetWorld(), TEXT("/Game/Maps/Map_War"));
}
else
{
/** 设置服务器响应信息 */
ToastWidget->Msg->SetText(FText::FromString(ResponseMsg));
/** 显示Toast */
ToastWidget->SetVisibility(ESlateVisibility::Visible);
}
}
}