UE4 中实现简单的本地登录注册功能 (UMG C++ 入门向)

本文简单介绍了如何在 UE4 中实现简单的本地登录注册功能。

前言

之前在 minigame 中开发游戏的登录注册功能时,发现网上关于 UE4 的介绍资料寥寥,对于登录注册功能的介绍更少,且大多要求通过 HTTP 等网络协议与服务器或者数据库进行通信交互,提高了开发难度,且不符合我们只是想做一个休闲单机小游戏的预期。

在翻阅了相关资料后,找到了一种通过本地文件交互实现登录注册功能的方法,考虑到相关资料介绍并不多,将实现方式总结如下,仅是一篇抛砖引玉的文章,希望有兴趣的人多多交流学习。

一、前期准备

Unreal Engine 4.25.4

Visual Studio 2019

1. 新建空白项目,命名为 LoginRegister

新建空白项目

2. 新建空白关卡和对应的用户控件

空白关卡和对应的用户控件

如何新建关卡可以参考以下视频
新关卡一片黑?初学者必知的UE4新建关卡时的一些设置【虚幻引擎】

3. 修改关卡 Login 的关卡蓝图,将 UMG 添加到视口

修改关卡

二、修改控件

首先修改控件 LoginUI,添加两个文本框和两个按钮。

控件 UI

需要注意的地方

  1. 按钮和可编辑文本框需要暴露为变量,而且为了方便后续编码需要起合适的变量名,比如 UsernameTextBoxPasswordTextBoxLoginButtonRegisterButton
  2. 密码文本框需要在外观中设置成 为密码
  3. 可编辑文本框的提示文本可以在外观中进行设置,比如上图中的 请输入用户名请输入密码
  4. 按钮里的文字需要使用文本控件,并用外层按钮进行包裹作为子控件。

这样就完成了基本的 UI 设计,修改完后别忘了保存和编译。

执行当前 Login 关卡,显示以下界面。

登录界面

三、UMG C++ 添加控件逻辑

新建 C++ 类,注意需要打开 显示所有类,继承自 UserWidget 父类,并将生成的类命名为 LoginWidget,如下图所示。

选择父类

控件类命名

生成源文件后,编辑源码 LoginWidget.hLoginWidget.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
// LoginWidget.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Components/Button.h" // 使用按钮控件 api
#include "Components/EditableTextBox.h" // 使用可编辑文本框 api

#include "LoginWidget.generated.h"

/**
*
*/
UCLASS()
class LOGINREGISTER_API ULoginWidget : public UUserWidget
{
GENERATED_BODY()

public:
// 构造函数
ULoginWidget(const FObjectInitializer& ObjectInitializer);

// 点击登录按钮的响应事件
UFUNCTION()
void PlayerLogin();
// 点击注册按钮的响应事件
UFUNCTION()
void PlayerRegister();

protected:
virtual void NativeConstruct();

// 工具函数: 检查数据文件是否存在
bool CheckTextFileExists(FString FileName);
// 工具函数: 将数据存入文本文件
bool SaveToTextFile(FString FileName);
// 工具函数: 从文本文件读取数据
bool LoadFromTextFile(FString FileName);

private:

UButton* LoginButton;
UButton* RegisterButton;

UEditableTextBox* UsernameTextBox;
UEditableTextBox* PasswordTextBox;

FString Username;
FString Password;
};

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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// LoginWidget.cpp
// Fill out your copyright notice in the Description page of Project Settings.


#include "LoginWidget.h"
#include "HAL/PlatformFileManager.h"

ULoginWidget::ULoginWidget(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
LoginButton = nullptr;
RegisterButton = nullptr;
UsernameTextBox = nullptr;
PasswordTextBox = nullptr;
}

void ULoginWidget::NativeConstruct()
{
Super::NativeConstruct();

UE_LOG(LogTemp, Warning, TEXT("ULoginWidget::NativeConstruct()"));

if (UButton* SBtn = Cast<UButton>(GetWidgetFromName("LoginButton")))
{
LoginButton = SBtn;

FScriptDelegate sgbDelegate;
sgbDelegate.BindUFunction(this, "PlayerLogin");
LoginButton->OnClicked.Add(sgbDelegate);
}
if (UButton* SBtn = Cast<UButton>(GetWidgetFromName("RegisterButton")))
{
RegisterButton = SBtn;

FScriptDelegate sgbDelegate;
sgbDelegate.BindUFunction(this, "PlayerRegister");
RegisterButton->OnClicked.Add(sgbDelegate);
}
if (UEditableTextBox* SText = Cast<UEditableTextBox>(GetWidgetFromName("UsernameTextBox")))
{
UsernameTextBox = SText;
}
if (UEditableTextBox* SText = Cast<UEditableTextBox>(GetWidgetFromName("PasswordTextBox")))
{
PasswordTextBox = SText;
}
}

void ULoginWidget::PlayerLogin()
{
if (UsernameTextBox == nullptr || PasswordTextBox == nullptr)
{
UE_LOG(LogTemp, Warning, TEXT("Widget can't get"));
return;
}

// 获取当前编辑框内的文本内容
FText UsernameText = UsernameTextBox->GetText();
FText PasswordText = PasswordTextBox->GetText();
FString UsernameStr = UsernameText.ToString();
FString PasswordStr = PasswordText.ToString();
if (UsernameStr.Len() <= 0 || PasswordStr.Len() <= 0)
{
UE_LOG(LogTemp, Warning, TEXT("Username or Password must not be empty"));
return;
}

// 数据文件路径
FString FileName = TEXT("Player_") + UsernameStr + TEXT(".data");

// 找不到数据文件,用户未注册
if (!this->CheckTextFileExists(FileName))
{
UE_LOG(LogTemp, Warning, TEXT("Player-[%s] Not Registered"), *UsernameStr);
return;
}

// 读取数据文件
if (!this->LoadFromTextFile(FileName))
{
UE_LOG(LogTemp, Warning, TEXT("Player-[%s] Login Failed. DataFile Wrong Occured"), *UsernameStr);
return;
}

// 检查密码正确性
if (this->Password != PasswordStr)
{
UE_LOG(LogTemp, Warning, TEXT("Player-[%s] Login Failed. Password Not Correct"), *UsernameStr);
return;
}

// Login Successfully. Do Something !
GEngine->AddOnScreenDebugMessage(0, 5.0f, FColor::Red, TEXT("Login Successfully"), false, FVector2D(2.0, 2.0));
}

void ULoginWidget::PlayerRegister()
{
if (UsernameTextBox == nullptr || PasswordTextBox == nullptr)
{
UE_LOG(LogTemp, Warning, TEXT("Widget can't get"));
return;
}

// 获取当前编辑框内的文本内容
FText UsernameText = UsernameTextBox->GetText();
FText PasswordText = PasswordTextBox->GetText();
FString UsernameStr = UsernameText.ToString();
FString PasswordStr = PasswordText.ToString();
if (UsernameStr.Len() <= 0 || PasswordStr.Len() <= 0)
{
UE_LOG(LogTemp, Warning, TEXT("Username or Password must not be empty"));
return;
}

// 数据文件路径
FString FileName = TEXT("Player_") + UsernameStr + TEXT(".data");

// 存在数据文件,用户已注册
if (this->CheckTextFileExists(FileName))
{
UE_LOG(LogTemp, Warning, TEXT("Player-[%s] Already Registered"), *UsernameStr);
return;
}

// 准备注册信息
this->Username = UsernameStr;
this->Password = PasswordStr;

// 存储数据文件
if (!this->SaveToTextFile(FileName))
{
UE_LOG(LogTemp, Warning, TEXT("Player-[%s] Register Failed. DataFile Wrong Occured"), *UsernameStr);
return;
}

// Register Successfully. Do Something !
GEngine->AddOnScreenDebugMessage(0, 5.0f, FColor::Red, TEXT("Register Successfully"), false, FVector2D(2.0, 2.0));
}

bool ULoginWidget::CheckTextFileExists(FString FileName)
{
// 获取文件操作接口
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
return PlatformFile.FileExists(*FileName);
}

bool ULoginWidget::SaveToTextFile(FString FileName)
{
TArray<FString> SaveArray; // 构造字符串数组 保存需要存储的数据
SaveArray.Add(this->Username);
SaveArray.Add(this->Password);
if (!FFileHelper::SaveStringArrayToFile(SaveArray, *FileName, FFileHelper::EEncodingOptions::ForceUTF8))
{
UE_LOG(LogTemp, Warning, TEXT("Save Text File-[%s] Failed"), *FileName);
return false;
}
return true;
}

bool ULoginWidget::LoadFromTextFile(FString FileName)
{
TArray<FString> SaveArray;
if (!FFileHelper::LoadFileToStringArray(SaveArray, *FileName))
{
UE_LOG(LogTemp, Warning, TEXT("Load Text File-[%s] Failed"), *FileName);
return false;
}
if (SaveArray.Num() < 2)
{
UE_LOG(LogTemp, Warning, TEXT("Text File-[%s] Broken"), *FileName);
return false;
}

this->Username = SaveArray[0];
this->Password = SaveArray[1];
return true;
}

编写好源文件后,需要将 LoginUI 空间中的类设置改为继承 LoginWidget

修改UI继承

重新运行 Login 关卡,用户名输入 test,密码输入 test,先点击注册按钮,屏幕上会打印出红色日志,如下图所示。

成功注册

然后再点击登录按钮。

成功登录

存档数据在 $UE_INSTALL_DIR/Engine/Binaries/Win64/Player_test.data,其中 $UE_INSTALL_DIR 为 ue4 的安装路径,比如在我的电脑上为 C:\Program Files\Epic Games\UE_4.25

使用记事本或其他软件打开存档文件,可以发现数据是分行进行存储,第一行是用户名,第二行是密码,密码是明文存储,实际应用中可以再通过单向加密算法等转为密文存储。

以上,成功通过本地文件存储的方式实现了简单的登录注册功能,读者可以在此基础上更进一步扩展开发,比如加入登录成功的关卡跳转功能、对用户或密码的合法性检查等,此处不再赘述。

参考文献

  1. UE4/CPP C++绑定UMG中的按钮事件_I_itaiit的博客-CSDN博客
  2. UE4 UMG中C++成员变量绑定蓝图Widget - 知乎
  3. UE4 开发之实现按钮事件响应 - 腾讯云开发者社区-腾讯云
  4. [UE4]UMG widget构造初始化函数中获取其内部组件_玄冬Wong的博客-CSDN博客
  5. 虚幻4(Unreal Engine4)中的代理委托Delegate的使用方法 - 知乎
  6. UE4-文件操作 - 知乎
  7. 【UE4 C++】读写Text文件 FFileHelper - 砥才人 - 博客园
  8. 【UE4】UE4文件系统_Goulandis的博客-CSDN博客_ue4game是什么文件夹
  9. 浅谈UE4序列化系列(1) 结合用例浅谈 UE4序列化 - 知乎