在Plugins页面中点击Add,新建一个插件。
在新建插件中选择Blueprint Library。
这里填入的信息会相应的在.uplugin中的信息对应。
在蓝图插件中的函数可以进行同步对应。
UCLASS()
class USTHLeariningLoginPluginBPLibrary : public UBlueprintFunctionLibrary
{
GENERATED_UCLASS_BODY()
/**DisplayName是用来指明在蓝图中可调用的函数名称**/
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Execute Sample function", Keywords = "STHLeariningLoginPlugin sample test testing"), Category = "STHLeariningLoginPluginTesting")
static float STHLeariningLoginPluginSampleFunction(float Param);
};
注意:创建Blueprint Library插件,所有的函数都是static的。相当于一个函数库。
我们创建一个Log文件夹,并新建.h文件,文件编码一定使用UTF-8。
#pragma once
#include "CoreMinimal.h"
DEFINE_LOG_CATEGORY_STATIC(LogSTHLogin, Log, All)
/**
* DeBug信息的几种方式:
* 1. 打印到屏幕窗口
* 2. 打印到日志
* 3. 右下角显示一个对话框
* 4. 屏幕中央弹出一个msg
*/
我们就可以使用UE_Log()中,使用我们创建的这个文件,用来打印日志。
UE_LOG(LogSTHLogin, Display, TEXT("Display"));
UE_LOG(LogSTHLogin, Warning, TEXT("Display"));
UE_LOG(LogSTHLogin, Error, TEXT("Display"));
日志打印的内容是在/Saved/Logs中,在文件中输入上面的名称,如LogSTHLogin打印内容进行查找。
注意:如果是程序崩溃,也是/Saved文件夹中进行查找。
我们创建一个管理HTTP的子系统,用来实现关于HTTP的相关操作。
子系统应该继承UGameInstanceSubsystem类和FTickableGameObject类,并重写相关方法。
STHLoginSubsystem.h文件代码如下:
#pragma once
#include "CoreMinimal.h"
#include "../STHLeariningLoginPluginBPLibrary.h"
#include "STHLoginSubsystem.generated.h"
UCLASS()
class USTHLoginSubsystem : public UGameInstanceSubsystem, public FTickableGameObject
{
GENERATED_BODY()
public:
#pragma region GameInstanceSubsystemOverrideFuction // 重写方法
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
#pragma endregion
public:
#pragma region TickableGameObjectOverrideFuction // 重写方法
virtual void Tick( float DeltaTime ) override;
virtual bool IsTickable() const override;
virtual TStatId GetStatId() const override;
#pragma endregion
public:
// 注册
bool SubsystemRegister(FString UserName, FString Password, FString PhoneNum, FString EMail, FSTHDelegate RegisterDelegate);
// 登录
bool SubsystemLogin(FString UserName, FString Password, FSTHDelegate LoginDelegate);
protected:
void HttpRegisterIn();
void HttpSignIn();
};
子系统的方法通过BPLibrary调用,BPLibrary.h文件代码如下:
#pragma once
#include "Kismet/BlueprintFunctionLibrary.h"
#include "STHLeariningLoginPluginBPLibrary.generated.h"
// 单播 多播 动态单波 动态单播
DECLARE_DYNAMIC_DELEGATE_TwoParams(FSTHDelegate, bool, str, FString, Message);
UCLASS()
class USTHLeariningLoginPluginBPLibrary : public UBlueprintFunctionLibrary
{
GENERATED_UCLASS_BODY()
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Execute Register", Keywords = "STHLeariningLoginPlugin Register", WorldContext = "WorldlContextObject"), Category = "STHLeariningLoginPlugin")
static bool STHRegister(FString UserName, FString Password, FString PhoneNum, FString EMail, const UObject* WorldlContextObject, FSTHDelegate RegisterDelegate);
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Execute Login", Keywords = "STHLeariningLoginPlugin Login", WorldContext = "WorldlContextObject"), Category = "STHLeariningLoginPlugin")
static bool STHLogin(FString UserName, FString Password, const UObject* WorldlContextObject, FSTHDelegate LoginDelegate);
};
注意:
(1)静态方法一定要加上const UObject* WorldlContextObject,这需要在meta中设置WorldContext = “WorldlContextObject”。这是一个用来保存上下文的东西,我们能通过这个拿到它本身。
(2)因为HTTP是异步的,我们不希望运行等待HTTP回调就一直卡在那里,因此我们需要设置动态多播来保证异步,下图是设置委托之后的蓝图。
BPLibrary.cpp文件代码如下:
#include "STHLeariningLoginPluginBPLibrary.h"
#include "STHLeariningLoginPlugin.h"
#include "Kismet/GameplayStatics.h"
#include "Log/LogSTHLogin.h"
#include "LoginCore/STHLoginSubsystem.h"
USTHLeariningLoginPluginBPLibrary::USTHLeariningLoginPluginBPLibrary(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
bool USTHLeariningLoginPluginBPLibrary::STHRegister(FString UserName, FString Password, FString PhoneNum, FString EMail, const UObject* WorldlContextObject, FSTHDelegate RegisterDelegate)
{
if(!WorldlContextObject)
{
return false;
}
/**注意:UObject对象都是通过指针来管理生命周期的*/
UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(WorldlContextObject); // 从GEngine中拿到UWorld,再返回UWorld的GetGameInstance
USTHLoginSubsystem* STHLoginSubsystem = GameInstance->GetSubsystem<USTHLoginSubsystem>(); // 再从GameInstance拿到子系统USTHLoginSubsystem
return STHLoginSubsystem->SubsystemRegister(UserName, Password, PhoneNum, EMail, RegisterDelegate);
}
bool USTHLeariningLoginPluginBPLibrary::STHLogin(FString UserName, FString Password, const UObject* WorldlContextObject, FSTHDelegate LoginDelegate)
{
if(!WorldlContextObject)
{
return false;
}
UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(WorldlContextObject);
USTHLoginSubsystem* STHLoginSubsystem = GameInstance->GetSubsystem<USTHLoginSubsystem>();
return STHLoginSubsystem->SubsystemLogin(UserName, Password, LoginDelegate);
}
我们使用Project Setting来对自己的配置文件进行创建并且通过读取配置文件来获取到想要的值。
1.创建Project Setting配置项
首先创建配置文件
LoginSettings.h文件代码如下:
#pragma once
#include "CoreMinimal.h"
#include "STHLoginSettings.generated.h"
UCLASS(Config = STHDataBase, DefaultConfig)
class USTHLoginSettings : public UDeveloperSettings
{
GENERATED_BODY()
/** Gets the settings container name for the settings, either Project or Editor */
virtual FName GetContainerName() const override
{
return TEXT("Project"); // 对应引擎中的 Project Setting
};
/** Gets the category for the settings, some high level grouping like, Editor, Engine, Game...etc. */
virtual FName GetCategoryName() const override
{
return TEXT("STHSetting"); // 对应引擎中 Project Setting 的大模块
};
/** The unique name for your section of settings, uses the class's FName. */
virtual FName GetSectionName() const override
{
return TEXT("STHDataBaseSetting"); // 对应引擎中的设置项名称
};
public:
UPROPERTY(Config, EditAnywhere, BlueprintReadWrite)
FString ServerIP = TEXT("127.0.0.1"); // 服务器IP
UPROPERTY(Config, EditAnywhere, BlueprintReadWrite)
FString ServerPort = TEXT("7000"); // 服务器端口号
};
继承自UDeveloperSettings类并且重写自定义所在的位置,下图我们能在UE的Project Settings中找到我们设置出来的值。
注意
(1)因为DeverSettings.h在DeverSettings模块中,所以需要在配置文件中加入该模块。
(2)当在Project Settings中修改了值,/Config/文件路径中会自动生成并修改对应的配置文件.ini,这样的修改是动态的。
2.读取配置文件
Subsystem.h文件新增函数
protected:
bool LoadServerIPAndPort(FString& ServerIP, FString& ServerPort);
对应实现
bool USTHLoginSubsystem::LoadServerIPAndPort(FString& ServerIP, FString& ServerPort)
{
FConfigFile DatabaseConfigFile;
// ProjectConfigDir()映射到"/Config"路径,读取对应的配置文件
FString Path = FPaths::ProjectConfigDir() / TEXT("DefaultSTHDataBase.ini");
DatabaseConfigFile.Read(Path);
bool bServerIP = DatabaseConfigFile.GetString(TEXT("/Script/STHLeariningLoginPlugin.STHLoginSettings"), TEXT("ServerIP"), ServerIP);
bool bServerPort = DatabaseConfigFile.GetString(TEXT("/Script/STHLeariningLoginPlugin.STHLoginSettings"), TEXT("ServerPort"), ServerPort);
if(bServerIP && bServerPort)
{
UE_LOG(LogSTHLogin, Display, TEXT("ServerIP: %s"), *ServerIP);
UE_LOG(LogSTHLogin, Display, TEXT("ServerPort: %s"), *ServerPort);
return true;
}
UE_LOG(LogSTHLogin, Warning, TEXT("There is no right ServerIP and ServerPort"));
return false;
}
在SubsystemRegister()和SubsystemLogin()发送HTTP请求时,都用这个函数判断一下IP和端口号是否配置,下图为从配置文件中读取到有效值。
发送HTTP请求代码如下
void USTHLoginSubsystem::HttpRegisterIn(FString ServerIP, FString ServerPort, FString UserName, FString Password, FString PhoneNum, FString EMail)
{
UE_LOG(LogSTHLogin, Display, TEXT("STHLoginSubsystem::HttpRegisterIn"));
// 1. 创建一个内容体,它是一个字符串
// 1.1 创建一个json对象
TSharedRef<FJsonObject> RequestObj = MakeShared<FJsonObject>();
// 1.2 往json对象中输入各种属性
FString InterviewType = "RegisterIn";
RequestObj->SetStringField("InterviewType", InterviewType);
RequestObj->SetStringField("UserName", UserName);
RequestObj->SetStringField("Password", Password);
RequestObj->SetStringField("PhoneName", PhoneNum);
RequestObj->SetStringField("Email", EMail);
// 1.3 把这个json对象序列化成字符串
FString RequestBody;
// 1.3.1 创建一个输入流
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&RequestBody);
UE_LOG(LogSTHLogin, Display, TEXT("STHLoginSubsystem::%s"), *RequestBody);
// 1.3.2 把json对象输入到流里面
FJsonSerializer::Serialize(RequestObj, Writer);
/**
* 如何反序列化?
* 1. 创建一个json的obj对象
* TSharedRef ResponseObj;
* 2. 创建一个json的读取流
* TSharedRef> Reader = TJsonReaderFactory<>::Create(RequestBody);
* 3. 反序列化
* FJsonSerializer::Deserialize(Reader, ResponseObj);
*/
// 2. 创建HTTP请求
/**
*FHttpRequestRef 为根据不同平台的类型来创建不同的 HTTP
*FHttpModule 为处理Http的一个模块,且全局单例
***/
FHttpRequestRef Request = FHttpModule::Get().CreateRequest();
// 请求完成后,绑定回调函数
Request->OnProcessRequestComplete().BindUObject(this, &USTHLoginSubsystem::OnResponseReceived);
// 拼接 http://127.0.0.1:80/Login
FString URL = TEXT("");
URL += TEXT("http://");
URL += ServerIP;
URL += TEXT(":");
URL += ServerPort;
URL += "/Login"; // 由服务器和客户端通信协议确定的
// 设置请求URL
Request->SetURL(URL);
// 设置请求模式 Get 或 Post
Request->SetVerb("Post");
// 设置请求头
Request->SetHeader("Content-Type", "application/json");
// 设置请求内容
Request->SetContentAsString(RequestBody);
// 3. 发送请求
Request->ProcessRequest();
}
在配置中加入新的模块
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"DeveloperSettings",
"HTTP",
"JsonUtilities",
"Json",
// ... add private dependencies that you statically link with here ...
}
);
第三方库以后有时间再研究…
操作MySQL数据库的函数如下:
mysql_init
:初始化mysql
MYSQL mysql; // mysql上下文;
mysql_init(&mysql); // 初始化 mysql 上下文
const int outTime = 3;
mysql_options(&mysql, MYSQL_OPT_CONNECT_TIMEOUT, &outTime); // 连接超时设定
const int recon = 1;
mysql_options(&mysql, MYSQL_OPT_RECONNECT, &recon); // 自动重连
mysql_real_connect(&mysql, 172.0.0.1, root, 12345678, uedatabase, 3306, 0, 0) // 连接数据库
mysql_query
:执行一条mysql语句
mysql_query(&mysql, "SET NAMES GB2312"); //解决中文乱码问题
mysql_real_query
:运行sql语句。注意:此处的失败是sql语句执行问题,而不是返回NULL。
// 编写mysql语句
Query = "select * from usertable where `username` = " + UserName + " and `password`= " + UserPassword;
sql = GetCharfromFString(Query); // 将Char值转化为FString值
mysql_real_query(&mysql, sql, strlen(sql)) // 执行SQL语句,若执行成功返回0,若执行失败返回非0值
mysql_error
:获取错误信息
ErrorMassage = mysql_error(&mysql); // 获取错误并打印
mysql_store_result
:获取mysql的返回结果
mysql_free_result
:清理结果值
mysql_num_rows
:获取结果中的行数
mysql_num_fields
:获取结果中的列数
res = mysql_store_result(&mysql); // 储存结果的信息
mysql_free_result(res); // 清理查询结果值
if (mysql_num_rows(res) < 1) // 获取结果的行数
if (mysql_num_fields(res) < 3) // 获取结果的列数
mysql_fetch_row
:获取结果中的行信息,并把行结构返回
struct FMySQLDataRow
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SQLDataRow")
TArray<FString> RowData;
};
FMySQLDataRow m_row;
TArray<FMySQLDataRow>& Rows
while ((row = mysql_fetch_row(res)) != NULL) // 遍历所有的行结构数据
{
m_row.RowData.Empty(); // 每次遍历清理RowData的值
for (int i = 0; i < mysql_num_fields(res); i++)
{
m_row.RowData.Add(GetWCharfromChar(row[i]));
}
Rows.Add(m_row); // 将m_row值存入Rows中
}
mysql_close
:关闭数据库
mysql_close(&mysql);
因此这里的主要方式是通过第三方库的链接,执行SQL语句,只需要根据这里的函数使用C++和SQL语句来完成逻辑即可。
UE中自带正则表达式的执行,包含在internationalization\Regex.h
头文件当中,模块需要包含Core。
FRegularRet ULoginSubsystem::Regular(const FString str, const FString Reg)
{
FRegexPattern Pattern(Reg); // 判断规则
FRegexMatcher regMatcher(Pattern, str); // 进行匹配
//regMatcher.SetLimits(0,str.Len());
FRegularRet ret;
ret.RetMatcherBool = regMatcher.FindNext(); // 是否找到匹配
ret.RetLength = regMatcher.GetEndLimit(); // 获取匹配串的总长度
RetMatcherLength regMatcher.GetMatchEnding(); // 获取匹配成功串的总长度
return ret;
}
所有功能在LoginSubsystem
层面上实现,蓝图库通过WorldlContextObject → GameInstance → GetSubsystem
获取子系统调用相应的函数。
/** 数据库实现登录功能 */
bool UmySQLPluginBPLibrary::MySQL_UserLogin(FString UserName, FString UserPassword, FString & ErrorMassage)
{
mysql_query(&mysql, "SET NAMES GB2312"); //解决中文乱码问题
Query = "select * from usertable where `username` = " + UserName + " and `password`= " + UserPassword;
sql = GetCharfromFString(Query); // 将FString转化为Char
if (mysql_real_query(&mysql, sql, strlen(sql)) != 0) //执行SQL语句
{
ErrorMassage = mysql_error(&mysql);
return false;
}
res = mysql_store_result(&mysql);
if (mysql_num_rows(res) < 1) //获取结果行
{
return false;
}
else
{
return true;
}
}
/** 蓝图库-登录 */
bool UmySQLPluginBPLibrary::Login(FString UserName, FString Password, const UObject* WorldlContextObject, FSTHDelegate LoginDelegate)
{
if(!WorldlContextObject)
{
return false;
}
UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(WorldlContextObject);
ULoginSubsystem* LoginSubsystem = GameInstance->GetSubsystem<ULoginSubsystem>();
return LoginSubsystem->SubsystemLoginByUerName(UserName, Password, LoginDelegate);
}
/** 子系统-登录 */
bool ULoginSubsystem::SubsystemLoginByUerName(FString UserName, FString Password, FSTHDelegate LoginDelegate)
{
FString ServerIP;
FString ServerPort;
FString ServerUserName;
FString ServerUserPassword;
if(!LoadServerIPAndPort(ServerIP, ServerPort, ServerUserName, ServerUserPassword))
{
return false;
}
FString ErrorStr;
if(UmySQLPluginBPLibrary::MySQL_UserLogin(UserName, Password, ErrorStr))
{
UE_LOG(STHMySQLLog, Display, TEXT("Login Successful."));
return true;
}
UE_LOG(STHMySQLLog, Error, TEXT("Login Error : %s"), *ErrorStr);
return false;
}
/** 数据库实现注册功能 */
FBoolMessages UmySQLPluginBPLibrary::MySQL_UserRegister(FString UserName, FString Password, FString PhoneNum, FString EMail, FString& ErrorMassage)
{
FBoolMessages boolRet;
// 判断各字段是否存在
if(MySQL_JudgmentField("username", UserName, ErrorMassage))
{
boolRet.RetBool = false;
boolRet.RetMassage = "UserName Already Exists";
ErrorMassage = "UserName Already Exists";
return boolRet;
}
if(MySQL_JudgmentField("email", EMail, ErrorMassage))
{
boolRet.RetBool = false;
boolRet.RetMassage = "Email Already Exists";
ErrorMassage = "Email Already Exists";
return boolRet;
}
if(MySQL_JudgmentField("phone", PhoneNum, ErrorMassage))
{
boolRet.RetBool = false;
boolRet.RetMassage = "Phone Already Exists";
ErrorMassage = "Phone Already Exists";
return boolRet;
}
// 注册insert语句
mysql_query(&mysql, "SET NAMES GB2312");
Query = "insert into usertable SET `username` = \"" + UserName + "\"" +", `password` = \"" + Password + "\"" + ", `email` = \"" + EMail + "\"" +", `phone` = \"" + PhoneNum + "\"";
sql = GetCharfromFString(Query);
if (mysql_real_query(&mysql, sql, strlen(sql)) != 0)
{
ErrorMassage = mysql_error(&mysql);
boolRet.RetBool = false;
boolRet.RetMassage = ErrorMassage;
return boolRet;
}
else
{
boolRet.RetBool = true;
boolRet.RetMassage = "Register Is Successful";
return boolRet;
}
}
/** 蓝图库-注册 */
FBoolMessages UmySQLPluginBPLibrary::Register(FString UserName, FString Password, FString PhoneNum, FString EMail, const UObject* WorldlContextObject, FSTHDelegate RegisterDelegate)
{
FBoolMessages ret;
if(!WorldlContextObject)
{
ret.RetBool = false;
return ret;
}
// 注意:UObject对象都是通过指针来管理生命周期的
UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(WorldlContextObject); // 从GEngine中拿到UWorld,再返回UWorld的GetGameInstance
ULoginSubsystem* LoginSubsystem = GameInstance->GetSubsystem<ULoginSubsystem>(); // 再从GameInstance拿到子系统USTHLoginSubsystem
return LoginSubsystem->SubsystemRegister(UserName, Password, PhoneNum, EMail, RegisterDelegate);
}
/** 子系统-注册 */
FBoolMessages ULoginSubsystem::SubsystemRegister(FString UserName, FString Password, FString PhoneNum, FString EMail, FSTHDelegate RegisterDelegate)
{
FBoolMessages ret;
FString ServerIP;
FString ServerPort;
FString ServerUserName;
FString ServerUserPassword;
if(!LoadServerIPAndPort(ServerIP, ServerPort, ServerUserName, ServerUserPassword))
{
ret.RetBool = false;
return ret;
}
FString ErrorStr;
// 通过正则表达式判断各字段的格式
if(!(RegularByType(Password, ERegexType::Password).RetBool))
{
return RegularByType(Password, ERegexType::Password);
}
if(!(RegularByType(EMail, ERegexType::Email).RetBool))
{
return RegularByType(EMail, ERegexType::Email);
}
if(!(RegularByType(PhoneNum, ERegexType::Phone).RetBool))
{
return RegularByType(PhoneNum, ERegexType::Phone);
}
// 调用用MySQL函数
if(UmySQLPluginBPLibrary::MySQL_UserRegister(UserName, Password, PhoneNum, EMail, ErrorStr).RetBool)
{
UE_LOG(STHMySQLLog, Display, TEXT("Register Successful."));
ret.RetBool = true;
ret.RetMassage = "Register Successful.";
return ret;
}
UE_LOG(STHMySQLLog, Error, TEXT("Register Error : %s"), *ErrorStr);
ret.RetBool = false;
ret.RetMassage = ErrorStr;
return ret;
}