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序列化 - 知乎
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了