UE4 中實現簡單的本地登錄注冊功能 (UMG C++ 入門向)
前言
之前在 minigame 中開發游戲的登錄注冊功能時,發現網上關于 UE4 的介紹資料寥寥,對于登錄注冊功能的介紹更少,且大多要求通過 HTTP 等網絡協議與服務器或者數據庫進行通信交互,提高了開發難度,且不符合我們只是想做一個休閑單機小游戲的預期。
在翻閱了相關資料后,找到了一種通過本地文件交互實現登錄注冊功能的方法,考慮到相關資料介紹并不多,將實現方式總結如下,僅是一篇拋磚引玉的文章,希望有興趣的人多多交流學習。
一、前期準備
Unreal Engine 4.25.4
Visual Studio 2019
1. 新建空白項目,命名為 LoginRegister

2. 新建空白關卡和對應的用戶控件

如何新建關卡可以參考以下視頻
新關卡一片黑?初學者必知的UE4新建關卡時的一些設置【虛幻引擎】。
3. 修改關卡 Login 的關卡藍圖,將 UMG 添加到視口

二、修改控件
首先修改控件 LoginUI,添加兩個文本框和兩個按鈕。

需要注意的地方
- 按鈕和可編輯文本框需要暴露為變量,而且為了方便后續編碼需要起合適的變量名,比如
UsernameTextBox、PasswordTextBox、LoginButton和RegisterButton。 - 密碼文本框需要在外觀中設置成
為密碼 - 可編輯文本框的提示文本可以在外觀中進行設置,比如上圖中的
請輸入用戶名和請輸入密碼。 - 按鈕里的文字需要使用文本控件,并用外層按鈕進行包裹作為子控件。
這樣就完成了基本的 UI 設計,修改完后別忘了保存和編譯。
執行當前 Login 關卡,顯示以下界面。

三、UMG C++ 添加控件邏輯
新建 C++ 類,注意需要打開 顯示所有類,繼承自 UserWidget 父類,并將生成的類命名為 LoginWidget,如下圖所示。


生成源文件后,編輯源碼 LoginWidget.h 和 LoginWidget.cpp。
// 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;
};
// 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。

重新運行 Login 關卡,用戶名輸入 test,密碼輸入 test,先點擊注冊按鈕,屏幕上會打印出紅色日志,如下圖所示。

然后再點擊登錄按鈕。

存檔數據在 $UE_INSTALL_DIR/Engine/Binaries/Win64/Player_test.data,其中 $UE_INSTALL_DIR 為 ue4 的安裝路徑,比如在我的電腦上為 C:\Program Files\Epic Games\UE_4.25。
使用記事本或其他軟件打開存檔文件,可以發現數據是分行進行存儲,第一行是用戶名,第二行是密碼,密碼是明文存儲,實際應用中可以再通過單向加密算法等轉為密文存儲。
以上,成功通過本地文件存儲的方式實現了簡單的登錄注冊功能,讀者可以在此基礎上更進一步擴展開發,比如加入登錄成功的關卡跳轉功能、對用戶或密碼的合法性檢查等,此處不再贅述。
參考文獻
- UE4/CPP C++綁定UMG中的按鈕事件_I_itaiit的博客-CSDN博客
- UE4 UMG中C++成員變量綁定藍圖Widget - 知乎
- UE4 開發之實現按鈕事件響應 - 騰訊云開發者社區-騰訊云
- [UE4]UMG widget構造初始化函數中獲取其內部組件_玄冬Wong的博客-CSDN博客
- 虛幻4(Unreal Engine4)中的代理委托Delegate的使用方法 - 知乎
- UE4-文件操作 - 知乎
- 【UE4 C++】讀寫Text文件 FFileHelper - 砥才人 - 博客園
- 【UE4】UE4文件系統_Goulandis的博客-CSDN博客_ue4game是什么文件夾
- 淺談UE4序列化系列(1) 結合用例淺談 UE4序列化 - 知乎
浙公網安備 33010602011771號