UE4C++基础(三)
一.静态库和动态库
静态库
- 将静态库编译的
.lib
文件和.h
头文件放入指定的文件夹
这里我选择在项目目录下新建一个ThirdParty
文件夹,将.lib
放入ThirdParty/Lib
文件夹中,.h放进ThirdParty/Include
文件夹中。
- 打开
YourProjectName.Build.cs
文件编写导入静态库的代码
// Copyright Epic Games, Inc. All Rights Reserved.
using System.IO;
using UnrealBuildTool;
public class YourModuleName : ModuleRules
{
private string ModulePath
{
get
{
return ModuleDirectory;
}
}
private string ThirdPartyPath
{
get
{
return Path.GetFullPath(Path.Combine(ModulePath, "../../ThirdParty"));
}
}
public YourModuleName(ReadOnlyTargetRules Target) : base(Target)
{
...
// 依赖的头文件
PublicIncludePaths.Add(Path.Combine(ThirdPartyPath, "Include"));
// 依赖的静态库或导入库
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyPath, "Lib", "LibTest.lib"));
}
}
- 在需要调用的地方引入头文件,调用静态库函数
#include "../../ThirdParty/Include/MyLib.h"
void YourFunctionName()
{
// 静态库函数
YourStaticLibFunctionName();
}
动态库
- 编写代码,将代码标记上导出动态库的标识符,然后编译成动态库。
//.h
#pragma once
#define DLL_EXPORT __declspec(dllexport)
#ifdef __cplusplus
extern "C"
{
#endif
float DLL_EXPORT GetCircleArea(float radius);
#ifdef __cplusplus
}
#endif
//.cpp
#include "MyDLLTest.h"
float DLL_EXPORT GetCircleArea(float radius)
{
return radius*radius*3.14159;
}
- 导出动态库后,将得到的
.dll
文件放在指定的文件夹,这里我放在ThirdParty/DLL
文件夹中。 - 加载
.dll
文件夹,得到动态库文件句柄后,通过函数指针调用动态库函数。
typedef float(*_DLLExportFunctor)(float radius);
void DynamicLibTest()
{
FString DLLPath = FPaths::Combine(*FPaths::ProjectDir(),TEXT("ThirdParty/"), TEXT("DLL/"), TEXT("DLLTest.dll"));
if (FPaths::FileExists(DLLPath)) {
void* DLLHandle = nullptr;
DLLHandle = FPlatformProcess::GetDllHandle(*DLLPath);
if (DLLHandle) {
_DLLExportFunctor ExportFunctor = nullptr;
FString ProcessName = "GetCircleArea";
ExportFunctor = (_DLLExportFunctor)FPlatformProcess::GetDllExport(DLLHandle, *ProcessName);
if (ExportFunctor) {
UE_LOG(LogTemp, Warning, TEXT("DynamicLib Circle Area:%f"), ExportFunctor(20.f));
}
}
}
}
二.断言
- 简单断言
check(param);
其中param为空指针或者为false,断言就会被执行到。
- 额外信息断言
verifyf(param,TEXT("..."),__VARGS__);
checkf(param,TEXT("..."),__VARAGS__);
在简单断言的基础上多了打印信息的功能。
- 阻塞流程断言
checkNoEntry();
使用这个断言的函数体只要被执行就会被断言。
checkNoReentry();
使用这个断言的函数体被执行了两次才会被断言。
checkNoRecursion();
检测到无限递归的断言。
- 断点式断言
ensure(param);
ensureMesgf(param,TEXT("..."),__VARAGS__);
模拟断点的断言,不会产生崩溃。
三.Json、Xml、ini文件读写
一.Json
项目Build.cs
中添加模块Json、JsonUtilities
。
Json
的数据格式如下:
{
"Name":"张三", // 字符串
"Age":18, // 整数
"体重":62.1, // 浮点数
"结婚":true, // 逻辑值
// 数组用[]表示
// {} 表示对象
"银行卡":[
{
"银行名":"农行",
"数量":1
},
{
"银行名":"工行",
"数量":1
},
{
"银行名":"邮政银行",
"数量":1
},
],
"余额":[
52,13.2,100
]
}
- 设置字段
TSharedPtr<FJsonObject> JsonObjectRoot = MakeShareable(new FJsonObject());
JsonObjectRoot->SetBoolField("bMan", true);
JsonObjectRoot->SetStringField("Id", "522201199811060050");
JsonObjectRoot->SetStringField("Name", "UExplorer");
TSharedPtr<FJsonObject> SubObj = MakeShareable(new FJsonObject());
SubObj->SetStringField("Company", "HongYu");
SubObj->SetNumberField("Salary", 9000);
JsonObjectRoot->SetObjectField("Work", SubObj);
- 设置array
// Set json string array
TArray<TSharedPtr<FJsonValue>> OverageValus;
{
TSharedPtr<FJsonValueString> StringValue1 = MakeShared<FJsonValueString>(TEXT("1000"));
TSharedPtr<FJsonValueString> StringValue2 = MakeShared<FJsonValueString>(TEXT("2000"));
TSharedPtr<FJsonValueString> StringValue3 = MakeShared<FJsonValueString>(TEXT("3000"));
OverageValus.Add(StringValue1);
OverageValus.Add(StringValue2);
OverageValus.Add(StringValue3);
JsonObjectRoot->SetArrayField("Overage", OverageValus);
}
// Set json object array
TArray<TSharedPtr<FJsonValue>> Family;
{
TSharedPtr<FJsonObject> p1 = MakeShareable(new FJsonObject());
p1->SetNumberField("Age", 20);
p1->SetStringField("Name", "Ann");
TSharedPtr<FJsonObject> p2 = MakeShareable(new FJsonObject());
p2->SetNumberField("Age", 10);
p2->SetStringField("Name","WangXianer");
TSharedPtr<FJsonValueObject> n_p1 = MakeShareable(new FJsonValueObject(p1));
TSharedPtr<FJsonValueObject> n_p2 = MakeShareable(new FJsonValueObject(p2));
Family.Add(n_p1);
Family.Add(n_p2);
JsonObjectRoot->SetArrayField("Family", Family);
}
Json
的序列化
// Json serialize
FString OutputStr;
TSharedRef<TJsonWriter<>> JsonWriter = TJsonWriterFactory<>::Create(&OutputStr);
FJsonSerializer::Serialize(JsonObjectRoot.ToSharedRef(), JsonWriter);
// Save json to file
FFileHelper::SaveStringToFile(OutputStr,TEXT("YourSavePath\\Info.Json"));
- 反序列化
TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(OutputStr);
TSharedPtr<FJsonObject> DeserializeObject = MakeShared<FJsonObject>();
FJsonSerializer::Deserialize(JsonReader,DeserializeObject);
- 解析
Json
//使用Json中的get方法来进行解析
if (DeserializeObject.IsValid()) {
bool IsMan = DeserializeObject->GetBoolField("bMan");
FString Id = DeserializeObject->GetStringField("Id");
TArray<TSharedPtr<FJsonValue>> Members = DeserializeObject->GetArrayField("Family");
for (const auto& Member : Members) {
TSharedPtr<FJsonObject> MemberPure = Member->AsObject();
UE_LOG(LogTemp, Warning, TEXT("%f %s"), MemberPure->GetNumberField("Age"), *MemberPure->GetStringField("Name"));
}
}
- 加载
Json
文件和写入Json
文件
// Save json to file
FFileHelper::SaveStringToFile(OutputStr,TEXT("YourSavePath\\Info.Json"));
// Load json file
FString NewjsonStr;
FString JsonPath(TEXT("YourSavePath\\Info.Json"));
FFileHelper::LoadFileToString(NewjsonStr,*JsonPath);
二.Xml
在项目模块中添加XmlParser
,Xml
语法如下:
<root>
<child>
<subchild>....</subchild>
</child>
</root>
- 属性值必须加引号
<note date="12/11/2007"></note>
在UE4中解析XML
在UE4中解析XML
使用FXmlFile
和FXmlNode
这两个类。
例子:
<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="CHILDREN">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>
bool ReadXmlFile(const FString& InXmlFilePath) {
// Check file path is valid
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
if (!PlatformFile.FileExists(*InXmlFilePath)) {
UE_LOG(LogTemp, Warning, TEXT("Does not exists such file path!"));
return false;
}
TSharedPtr<FXmlFile> XmlFile = MakeShareable(new FXmlFile(InXmlFilePath));
if (XmlFile->IsValid()) {
FXmlNode* RootNode = XmlFile->GetRootNode();
for (FXmlNode* ChildNode : RootNode->GetChildrenNodes()) {
UE_LOG(LogTemp, Warning, TEXT("%s"), *ChildNode->GetAttribute("category"));
for (FXmlNode* SubChildNode : ChildNode->GetChildrenNodes()) {
UE_LOG(LogTemp, Warning, TEXT("%s"), *SubChildNode->GetAttribute("lang"));
FString Content = SubChildNode->GetContent();
UE_LOG(LogTemp, Warning, TEXT("Content: %s"), *Content);
}
}
return true;
}
else {
return false;
}
}
三.ini
ini
俗称Config
文件,基本格式如下:
[KeyName]
VarName = Value
在UE4
中通过GConfig
对ini
文件进行读取。
bool ReadConfigFile(FString FilePath){
FString ConfigPath = FilePath;
bool ReadSuccess = false;
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
if(PlatformFile.FileExists(*ConfigPath)){
FString Info;
ReadSuccess = GConfig->GetString(KeyName,VarName,Info,ConfigPath);
}
return ReadSuccess
}
四. 异步节点
一.蓝图调用
创建一个继承自UFunctionLibrary
的类。
// .h
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "LatentLibrary.generated.h"
UENUM(BlueprintType)
enum class EDelayExec :uint8 {
HalfExec,
CompleteExec
};
UCLASS()
class REFLECTLEARNING_API ULatentLibrary :public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable,meta=( ExpandEnumAsExecs = "Exec", Latent,LatentInfo="ActionInfo" ,HidePin="WorldContextObject" , DefaultToSelf="WorldContextObject"))
static void TwiceDelay(UObject* WorldContextObject, EDelayExec& Exec,struct FLatentActionInfo ActionInfo,float Duration);
};
其中的FLatentActionInfo
因为是在蓝图端调用,可以由系统自动传递进来。
//.cpp
#include "LatentActions.h"
class FTwiceDelayLatent :public FPendingLatentAction
{
public:
FTwiceDelayLatent(float Duration,
const FLatentActionInfo& ActionInfo,
EDelayExec& DelayExec) :TotalTime(Duration), TimeRemaining(Duration),
ExecutionFunction(ActionInfo.ExecutionFunction), OutputLink(ActionInfo.Linkage),
CallbackTarget(ActionInfo.CallbackTarget), ExecRef(DelayExec), bTriggerHalf(false) {}
virtual void UpdateOperation(FLatentResponse& Response)
{
TimeRemaining -= Response.ElapsedTime();
if (TimeRemaining < TotalTime / 2.f && !bTriggerHalf) {
ExecRef = EDelayExec::HalfExec;
Response.TriggerLink(ExecutionFunction, OutputLink, CallbackTarget); //回调函数
bTriggerHalf = true;
}
else if (TimeRemaining <= 0.f) {
ExecRef = EDelayExec::CompleteExec;
Response.TriggerLink(ExecutionFunction, OutputLink, CallbackTarget);
Response.DoneIf(true); //终止函数
}
}
private:
float TotalTime;
float TimeRemaining;
FName ExecutionFunction;
int32 OutputLink;
FWeakObjectPtr CallbackTarget;
EDelayExec& ExecRef;
bool bTriggerHalf;
};
void ULatentLibrary::TwiceDelay(UObject* WorldContextObject, EDelayExec& Exec, struct FLatentActionInfo ActionInfo, float Duration)
{
if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject)) {
// 获取LatentActionManager
FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
if (LatentActionManager.FindExistingAction<FTwiceDelayLatent>(ActionInfo.CallbackTarget,ActionInfo.UUID)==NULL) {
LatentActionManager.AddNewAction(ActionInfo.CallbackTarget, ActionInfo.UUID, new FTwiceDelayLatent(Duration,ActionInfo,Exec));
}
}
}
二.CPP调用
CPP
和蓝图调用主要的区别就是,FLetentActionInfo
变量的获取,蓝图中可以自动由系统传递。在CPP
中我们需要自行声明。
int32 LinkAge = 100;
int32 UUID = 9;
FLatentActionInfo ActionInfo(LinkAge,UUID,TEXT("LatentActionCall"),this);
FLatentActionManager& ActionManager = GetWorld()->GetLatentActionManager();
if (ActionManager.FindExistingAction<FLatentCallInCPP>(ActionInfo.CallbackTarget, UUID) == NULL) {
ActionManager.AddNewAction(ActionInfo.CallbackTarget, UUID, new FLatentCallInCPP(ActionInfo,FOnLatentComplete::CreateUObject(this, &AAsyncLoadActor::LatentActionObjectCall)));
其中LinkAge
和UUID
的随意指定一个你不会重复用到的int32
类型值就行。其余关于继承FPendingLatentAction
子类的实现,就大同小异了。
class FLatentCallInCPP :public FPendingLatentAction
{
public:
FLatentCallInCPP(const FLatentActionInfo& ActionInfo, FOnLatentComplete InDelegate)
:CallBackTarget(ActionInfo.CallbackTarget),
ExecuFunction(ActionInfo.ExecutionFunction),
LinkAge(ActionInfo.Linkage),
CompleteDelegate(InDelegate),
TotalTime(3.f),
TimeRemaining(3.f){}
virtual void UpdateOperation(FLatentResponse& Response)
{
TimeRemaining -= Response.ElapsedTime();
if (TimeRemaining < TotalTime / 2.f) {
Response.TriggerLink(ExecuFunction, LinkAge, CallBackTarget);
if (TimeRemaining <= 0.f) {
Response.DoneIf(true);
}
}
else {
// 委托通知
CompleteDelegate.Execute(CallBackTarget.Get());
}
}
private:
float TotalTime;
float TimeRemaining;
FWeakObjectPtr CallBackTarget;
FName ExecuFunction;
int32 LinkAge;
FOnLatentComplete CompleteDelegate;
};
五. 类型转换
一.FString与基本类型之间的转换
// Fstring to int
int32 IntValue = FCString::Atoi(*Str);
// FString to float
float floValue = FCString::Atoi(*Str);
// FString to bool
bool BoValue = Str.ToBool();
// FString to FName
FName NewName = FName(*Str);
// FString to FText
FText NewText = FText::FromString(Str);
// Int to FString
FString NewStr = FString::FromInt(NewInt);
// Float to FString
FString NewStr = FString::SanitizeFloat(NewFloat);
// FText to FString
FString NewStr = NewText.ToString();
// FName to FString
FString NewStr = NewName.ToString();
二. 字符之间的转换
// FString to TCHAR
// 方式1:
TCHAR* NewTchar = *Str;
// 方式2:
TCHAR* NewTchar = Str.GetCharArray().GetData();
// TCHAR to char*
char* Newchar = TCHAR_TO_ANSI(NewTchar);
// 如果是UTF8格式请使用TCHAR_TO_UTF8()
// 同理UTF8_TO_TCHAR()
// Char* to TCHAR
TCHAR* NewTchar = ANSI_TO_TCHAR(Newchar);
三. UEnum的转换
// Enum to FString
template<class TEnum>
FORCEINLINE FString GetEnumValueAsString(const FString& EnumName,TEnum Value){
UEnum* EnumPtr = FindObject<UEnum>((UObject*)ANY_PACkAGE,*EnumName,true);
return EnumPtr?EnumPtr->GetNameStringByIndex((int32)Value):"InValid";
}
// FString to Enum
template<class TEnum>
FORCEINLINE TEnum GetEnumValueFromString(const FString& EnumName,FString value){
UEnum* EnumPtr = FindObject<UEnum>((UObject*)ANY_PACkAGE,*EnumName,true);
return EnumPtr?EnumPtr->(TEnum)GetIndexByName(FName(*Value)):TEnum(0);
}
// Enum to int32
FORCEINLINE int32 GetEnumIndexFromName(const FString& EnumName, FName InValue) {
UEnum* EnumPtr = FindObject<UEnum>(ANY_PACKAGE, *EnumName,true);
return EnumPtr?EnumPtr->GetIndexByName(InValue):-1;
}
六.传递函数的方法
TFunction
对应C++
标准库中的std::function
。
void TFunctionTest(){
TFunction<void(FString,int32)> Functor = [this](FString Str,int32 Val){
//to do...
};
//调用
Functor("Param",10);
}
使用TFunction
定义复用接口
template<class RetType,class ...VarTypes>
void TemplateFunction(TFunction<RetType(VarTypes...)> Functor){
RetType Ret = Functor(VarTypes...);
}
TMemFunPtrType
类似于传递类函数指针。
DECLARE_DELEGATE(FOnMemFuncCalled);
void MemFuncCallBack(FString InStr);
template<class UserClass>
void TMemFuncPtrTypeTest(UserClass* Object, typename TMemFunPtrType<false, UserClass,void(FString)>::Type InFunctor)
{
FOnMemFuncCalled OnMemFuncCalled;
OnMemFuncCalled.BindUObject(Object, InFunctor, FString("Hello MemFunPtrtype"));
OnMemFuncCalled.ExecuteIfBound();
}
// 调用
TMemFuncPtrTypeTest<YourClass>(this,&YourClass::MemFuncCallBack);
FMethodPtr
是TBaseUObjectMethodDelegateInstance
里面的一个成员变量。我们使用的是继承他的子类TUObjectMethodDelegate
template <typename UserClass>
struct TUObjectMethodDelegate:TBaseUObjectMethodDelegateInstance<false, UserClass, FuncType, UserPolicy>
{ typedef TBaseUObjectMethodDelegateInstance<false, UserClass, FuncType, UserPolicy> Super;
TUObjectMethodDelegate(UserClass* InUserObject, typename Super::FMethodPtr InMethodPtr):Super(InUserObject, InMethodPtr){}};
示例:
// 声明委托
DECLARE_DELEGATE(FOnMemFuncCalled)
// 对应的委托参数的函数声明
void CallFunc();
template<class UserClass>
void CommonMethodFunc(UserClass* Object,typename FOnMemFuncCalled::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod){};
// 调用
TestMethodFunc<YourClass>(this, &YourClass::CallFunc);
使用泛型来对绑定的代理、参数和返回值进行定制化。
template<class DelegateType,class UserClass,class ...ParamTypes>
void templateMethodFunc(UserClass* Object,typename DelegateType::template TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, ParamTypes...Params){}
调用的时候需要把自己的委托的类型传递进去。