[22] 虚幻引擎知识拓展 智能指针、JSON解析、蓝图解析JSON插件、模块、动态库静态库
大纲
虚幻智能指针
共享指针
共享引用
JSON解析 对象型、数组型、解析Json文件、书写Json、读取场景Actor保存到Json
任务 : 封装高德地图天气系统插件给蓝图使用
蓝图解析JSON插件
自定义模块
有限状态机
多线程
GET、Post、Socket链接
UDP
简历
静态库动态库
UE静态库动态库
Salte
垃圾回收机制
内容
虚幻智能指针
创建共享指针
// //创建共享指针
//TSharedPtr<FMyClass> pMyClass = MakeShareable(new FMyClass);
TSharedPtr<FMyClass> pMyClass(new FMyClass);
//检查有效性
if (pMyClass)
{
}
TSharedPtr<FMyClass> p2;
//比较
if (pMyClass == p2)
{
}
//获取引用计数(当前共享指针管理的内存有多少人在用)
UE_LOG(LogTemp, Log, TEXT("%d"), pMyClass.GetSharedReferenceCount());
p2 = pMyClass;
UE_LOG(LogTemp, Log, TEXT(" == %d"), pMyClass.GetSharedReferenceCount());
//释放
pMyClass = nullptr;
UE_LOG(LogTemp, Log, TEXT(" == %d"), p2.GetSharedReferenceCount());
共享引用
// //创建共享指针
//TSharedPtr<FMyClass> pMyClass = MakeShareable(new FMyClass);
TSharedPtr<FMyClass> pMyClass(new FMyClass);
//检查有效性
if (pMyClass)
{
}
TSharedPtr<FMyClass> p2;
//比较
if (pMyClass == p2)
{
}
//获取引用计数(当前共享指针管理的内存有多少人在用)
UE_LOG(LogTemp, Log, TEXT("%d"), pMyClass.GetSharedReferenceCount());
p2 = pMyClass;
UE_LOG(LogTemp, Log, TEXT(" == %d"), pMyClass.GetSharedReferenceCount());
//释放
pMyClass = nullptr;
UE_LOG(LogTemp, Log, TEXT(" == %d"), p2.GetSharedReferenceCount());
弱指针
// //创建共享指针
//TSharedPtr<FMyClass> pMyClass = MakeShareable(new FMyClass);
TSharedPtr<FMyClass> pMyClass(new FMyClass);
//检查有效性
if (pMyClass)
{
}
TSharedPtr<FMyClass> p2;
//比较
if (pMyClass == p2)
{
}
//获取引用计数(当前共享指针管理的内存有多少人在用)
UE_LOG(LogTemp, Log, TEXT("%d"), pMyClass.GetSharedReferenceCount());
p2 = pMyClass;
UE_LOG(LogTemp, Log, TEXT(" == %d"), pMyClass.GetSharedReferenceCount());
//释放
pMyClass = nullptr;
UE_LOG(LogTemp, Log, TEXT(" == %d"), p2.GetSharedReferenceCount());
循环引用 : 智能指针引用计数为0才会释放
弱指针
JSON解析
对象型JSON
//解析对象型JSON
FString JsonStr = TEXT("{\"name\":\"二狗\", \"age\" : 39}");
//创建接收Json对象的数据
TSharedPtr<FJsonObject> JsonObject;
//借助解析工厂,解析Json文本串到阅读器
TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(JsonStr);
//借助解析器,完成解析
//反向序列化:从其它格式数据到内存对象格式数据
if (FJsonSerializer::Deserialize(JsonReader, JsonObject))
{
//读取数据?
UE_LOG(LogTemp, Log, TEXT("%s"), *JsonObject->GetStringField(TEXT("name")));
//检查是否存在某个字段
float age = 0;
if (JsonObject->TryGetNumberField(TEXT("age"), age))
{
UE_LOG(LogTemp, Log, TEXT("== %f"), age);
}
}
数组型JSON
//数组型JSon
FString JsonStr =TEXT("[\"二狗\", 34, true, {\"name\":\"二狗\", \"age\" : 39}, [49]]");
TArray<TSharedPtr<FJsonValue>> JsonValues;
TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(JsonStr);
if (FJsonSerializer::Deserialize(JsonReader, JsonValues))
{
//读取元素
UE_LOG(LogTemp, Log, TEXT("%s"), *JsonValues[0]->AsString());
//
float Num = 0;
if (JsonValues[1]->TryGetNumber(Num))
{
UE_LOG(LogTemp, Log, TEXT("%f"), Num);
}
TSharedPtr<FJsonObject> JsonObject = JsonValues[3]->AsObject();
UE_LOG(LogTemp, Log, TEXT("%s"), *JsonObject->GetStringField(TEXT("name")));
//解析数组
UE_LOG(LogTemp, Log, TEXT("%f"), JsonValues[4]->AsArray()[0]->AsNumber());
}
从磁盘解析JSON
//从磁盘解析json
FString JsonStr;
if (FFileHelper::LoadFileToString(JsonStr, TEXT("d:\\a.json")))
{
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(JsonStr);
if (FJsonSerializer::Deserialize(JsonReader, JsonObject))
{
TArray<TSharedPtr<FJsonValue>> person = JsonObject->GetArrayField(TEXT("person"));
TArray<TSharedPtr<FJsonValue>> teachers = person[0]->AsObject()->GetArrayField(TEXT("teachers"));
TSharedPtr<FJsonObject> liu = teachers[1]->AsObject()->GetObjectField(TEXT("liu"));
TArray<TSharedPtr<FJsonValue>> hobby = liu->GetArrayField(TEXT("hobby"));
for (auto hb : hobby)
{
UE_LOG(LogTemp, Log, TEXT("%s"), *hb->AsString());
}
}
}
序列化Json 对象型
//序列化json 对象型
TSharedPtr<FJsonObject> JsonObject(new FJsonObject);
//设置字段
JsonObject->SetStringField(TEXT("name"), TEXT("二狗"));
JsonObject->SetNumberField(TEXT("age"), 40);
//构建书写器
FString JsonStr;
TSharedRef<TJsonWriter<>> JsonWriter = TJsonWriterFactory<>::Create(&JsonStr);
//序列化
if (FJsonSerializer::Serialize(JsonObject.ToSharedRef(), JsonWriter))
{
UE_LOG(LogTemp, Log, TEXT("%s"), *JsonStr);
}
序列化Json 数组型
//数组型
TArray<TSharedPtr<FJsonValue>> JsonValues;
JsonValues.Add(MakeShareable(new FJsonValueString(TEXT("二狗"))));
JsonValues.Add(MakeShareable(new FJsonValueNumber(49)));
JsonValues.Add(MakeShareable(new FJsonValueBoolean(true)));
FString JsonStr;
TSharedRef<TJsonWriter<>> JsonWriter = TJsonWriterFactory<>::Create(&JsonStr);
if (FJsonSerializer::Serialize(JsonValues, JsonWriter))
{
UE_LOG(LogTemp, Log, TEXT("%s"), *JsonStr);
}
案例 : 读取场景中的Actor的坐标、缩放、旋转保存到JSON文件
TArray<TSharedPtr<FJsonValue>> JsonValues;
TArray<AActor*> Actors;
//获取场景所有Actor
UGameplayStatics::GetAllActorsOfClass(this, AActor::StaticClass(), Actors);
for (auto ac : Actors)
{
//新建一个对象型 Json
TSharedPtr<FJsonObject> JsonObject(new FJsonObject);
JsonObject->SetStringField(TEXT("Name"), ac->GetName());
TSharedPtr<FJsonObject> Position(new FJsonObject);
Position->SetNumberField(TEXT("X"), ac->GetActorLocation().X);
Position->SetNumberField(TEXT("Y"), ac->GetActorLocation().Y);
Position->SetNumberField(TEXT("Z"), ac->GetActorLocation().Z);
JsonObject->SetObjectField(TEXT("Position"), Position);
TSharedPtr<FJsonObject> Rotation(new FJsonObject);
Rotation->SetNumberField(TEXT("Pitch"), ac->GetActorRotation().Pitch);
Rotation->SetNumberField(TEXT("Yaw"), ac->GetActorRotation().Yaw);
Rotation->SetNumberField(TEXT("Roll"), ac->GetActorRotation().Roll);
JsonObject->SetObjectField(TEXT("Rotation"), Rotation);
TSharedPtr<FJsonObject> Scale(new FJsonObject);
Scale->SetNumberField(TEXT("X"), ac->GetActorScale().X);
Scale->SetNumberField(TEXT("Y"), ac->GetActorScale().Y);
Scale->SetNumberField(TEXT("Z"), ac->GetActorScale().Z);
JsonObject->SetObjectField(TEXT("Scale"), Scale);
JsonValues.Add(MakeShareable(new FJsonValueObject(JsonObject)));
}
FString JsonStr;
TSharedRef<TJsonWriter<>> JsonWriter = TJsonWriterFactory<>::Create(&JsonStr);
if (FJsonSerializer::Serialize(JsonValues, JsonWriter))
{
FFileHelper::SaveStringToFile(JsonStr, TEXT("d:\\b.json"));//保存到文件
}
插件
蓝图无法直接操作C++的东西 , 使用C++包裹一层在蓝图里面调用
纯标记 : 每次调用都会执行一次引脚
侵入式指针
蓝图解析JSON插件
插件安装
引擎安装 : 运行时插件 plugins下 > Runutime
项目安装 : 项目下的 > plugins 下
Public和Private的区别
导出标记宏 : 有了他能在外面使用
Private也能通通过增加宏标签导入
有限状态机
工厂的方法 : 统一逻辑统一管理
函数指针 : 单独剥离逻辑
网络通信
GET
void FHttpHelper::RequestHttpGet(FString Url)
{
//创建请求对象HTTP
HttpRequest = FHttpModule::Get().CreateRequest();
//设置请求方式
HttpRequest->SetVerb(TEXT("Get"));
//设置请求地址
HttpRequest->SetURL(Url);
//设置请求头
HttpRequest->SetHeader(TEXT("User-Agent"), TEXT("UnrealEngine"));
HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
//绑定回调函数(消息请求发送后,用来响应结果)
HttpRequest->OnProcessRequestComplete().BindRaw(this, &FHttpHelper::OnRequestComplete);
//执行请求
HttpRequest->ProcessRequest();
}
POST
void FHttpHelper::RequestHttpPost(const FString& Message)
{
//创建请求对象
HttpRequest = FHttpModule::Get().CreateRequest();
//设置请求方式
HttpRequest->SetVerb(TEXT("Post"));
//设置请求头
HttpRequest->SetHeader(TEXT("User-Agent"), TEXT("UnrealEngine"));
HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
//设置地址
HttpRequest->SetURL(TEXT("127.0.0.1:8888"));
//设置Post信息
HttpRequest->SetContentAsString(Message);
//绑定回调函数(消息请求发送后,用来响应结果)
HttpRequest->OnProcessRequestComplete().BindRaw(this, &FHttpHelper::OnRequestComplete);
//执行请求
HttpRequest->ProcessRequest();
}
Socket
Socket
//-- .h
#pragma once
#include "CoreMinimal.h"
class FSocketHelper : public FRunnable
{
public:
FSocketHelper();
virtual ~FSocketHelper() override;
bool Connect(const FString& IP,int32 Port);
void SendMessage(const FString& Message);
void StartReceive();
void CloseSocket();
protected:
virtual uint32 Run() override;
private:
FSocket* MySocket;
bool bRunning;
};
//-- .cpp
#include "FSocketHelper.h"
#include "Sockets.h"
#include "SocketSubsystem.h"
FSocketHelper::FSocketHelper() : MySocket(nullptr), bRunning(false)
{
}
FSocketHelper::~FSocketHelper()
{
if (MySocket)
{
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(MySocket);
}
CloseSocket();
}
bool FSocketHelper::Connect(const FString& IP, int32 Port)
{
//构建链接地址
TSharedRef<FInternetAddr> InternetAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
//解析文本地址信息
bool bParse = true;
InternetAddr->SetIp(*IP, bParse);
if (!bParse)
{
return false;
}
//设置端口号
InternetAddr->SetPort(Port);
//创建套接字
MySocket = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateSocket(NAME_Stream, TEXT("MySocket"));
//启动链接 返回真和假,真则代表链接服务器成功了,假,则代表没有成功
return MySocket->Connect(*InternetAddr);
}
void FSocketHelper::SendMessage(const FString& Message)
{
if (MySocket)
{
//将文本转换为字节流数据
FTCHARToUTF8 ctu(*Message, Message.Len());
int32 SendSize = 0;
//发送
MySocket->Send(reinterpret_cast<const uint8*>(ctu.Get()), ctu.Length(), SendSize);
}
}
void FSocketHelper::StartReceive()
{
bRunning = true;
//启动线程
FRunnableThread::Create(this, TEXT("MySocketThread"));
}
void FSocketHelper::CloseSocket()
{
if (!bRunning)
{
return;
}
bRunning = false;
if (MySocket)
{
MySocket->Close();
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(MySocket);
MySocket = nullptr;
}
}
uint32 FSocketHelper::Run()
{
//创建消息缓冲区
const int32 BufferSize = 1024;
uint8* Buffer = new uint8[BufferSize];
//设置接收数据大小变量
int32 ReceiveSize = 0;
while (bRunning)
{
//接收消息
MySocket->Recv(Buffer, BufferSize, ReceiveSize);
if (ReceiveSize > 0)//说明你收到了消息
{
//解析消息
FUTF8ToTCHAR utt(reinterpret_cast<const ANSICHAR*>(Buffer), ReceiveSize);
//转换成FString
FString Msg(utt.Length(), utt.Get());
UE_LOG(LogTemp, Log, TEXT("%s"), *Msg);
}
else//服务器可能关闭了,或是链接超时了
{
CloseSocket();
}
}
return 0;
}
//-- mode.cpp 使用
void AUECppGameModeBase::SocketConnect()
{
if (!SocketHelper)
{
SocketHelper = MakeShareable(new FSocketHelper);
SocketHelper->Connect(TEXT("127.0.0.1"), 8060);
if (SocketHelper->Connect(TEXT("127.0.0.1"), 8060))
{
SocketHelper->StartReceive();
}
}
}
void AUECppGameModeBase::SendSocketMessage(const FString& Message)
{
if (SocketHelper)
{
SocketHelper->SendMessage(Message);
}
}
多线程
游戏引擎很依赖前后顺序 ,
以前 : 整个场景的地图加载 , 玩家才可以进入游戏。
现在 : 场景和玩家可以同时加载。
析构函数需要写虚函数 , 可以调用父类的
跨进程通信麻烦 , 跨线程通讯容易
进程和线程
进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基
础。
线程:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中
一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
简单来说,进程是最小的资源分配单位,线程是最小的运算调度单位(CPU调度的最小单位)。进程中可以启动多个线程。
线程函数
创建线程
void AUECppGameModeBase::StartThread()
{
if (!MyRunnable)
{
MyRunnable = MakeShareable(new FMyRunnable);
}
//启动线程
MyThread = FRunnableThread::Create(MyRunnable.Get(), TEXT("MyTestThread"));
}
线程休眠
终止线程
MyThread->Kill(false);//true等待线程结束,如果线程不结束,则一直阻塞当前调用线程,直到Mythread线程结束
线程锁 : 互斥锁 效率低 , 休眠结束在让别人使用对象
先先调用 : 谁的代码块先执行完
FScopeLock lock(&m_CriticalSection);
UDP
模块引入
发消息
//声明创建
//-- .h
#pragma once
class FUdpHelper
{
public:
FUdpHelper();
void SendMessage(const FString& Message);
private:
FSocket* MyUdpSocket;
};
//--.cpp
#include "FUdpHelper.h"
#include "Common/UdpSocketBuilder.h"
FUdpHelper::FUdpHelper() : MyUdpSocket(nullptr), bRunning(false)
{
}
FUdpHelper::~FUdpHelper()
{
CloseSocket();
}
void FUdpHelper::SendMessage(const FString& Message)
{
if (!MyUdpSocket)
{
//创建面向非链接的套接字(UDP)
MyUdpSocket = FUdpSocketBuilder(TEXT("MyUdpSocket")).AsReusable().WithBroadcast().AsBlocking();
//设置消息缓冲区大小
int32 BufferSize = 0;
if (!MyUdpSocket->SetReceiveBufferSize(1024, BufferSize))
{
UE_LOG(LogTemp, Log, TEXT("UDP缓冲区设置失败!"));
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(MyUdpSocket);
MyUdpSocket = nullptr;
return;
}
//创建地址
TSharedRef<FInternetAddr> InternetAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
bool bParse = false;
InternetAddr->SetIp(TEXT("127.0.0.1"), bParse);
if (!bParse)
{
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(MyUdpSocket);
MyUdpSocket = nullptr;
return;
}
//设置端口号
InternetAddr->SetPort(8050);
//将文本转成字节流
FTCHARToUTF8 ctu(*Message, Message.Len());
int32 SendSize = 0;
MyUdpSocket->SendTo(reinterpret_cast<const uint8*>(ctu.Get()), ctu.Length(), SendSize, *InternetAddr);
if (!bRunning)
{
bRunning = true;
FRunnableThread::Create(this, TEXT("MyUDPThread"));
}
}
}
uint32 FUdpHelper::Run()
{
//创建消息缓冲区
const int32 BufferSize = 1024;
uint8* Buffer = new uint8[BufferSize];
//设置接收数据大小变量
int32 ReceiveSize = 0;
while (bRunning)
{
//接收消息
MyUdpSocket->Recv(Buffer, BufferSize, ReceiveSize);
if (ReceiveSize > 0) //说明你收到了消息
{
//解析消息
FUTF8ToTCHAR utt(reinterpret_cast<const ANSICHAR*>(Buffer), ReceiveSize);
//转换成FString
FString Msg(utt.Length(), utt.Get());
UE_LOG(LogTemp, Log, TEXT("%s"), *Msg);
}
else //服务器可能关闭了,或是链接超时了
{
CloseSocket();
}
}
return 0;
}
void FUdpHelper::CloseSocket()
{
if (!bRunning)
{
return;
}
bRunning = false;
if (MyUdpSocket)
{
MyUdpSocket->Close();
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(MyUdpSocket);
MyUdpSocket = nullptr;
}
}
//--调用
void AUECppGameModeBase::SendUdpMessage(const FString& Message)
{
if (!UdpHelper)
{
UdpHelper = MakeShareable(new FUdpHelper);
}
UdpHelper->SendMessage(Message);
}
收消息
内网穿透
UPD聊天
禁止在游戏在主线程之外调用游戏UI
读和写同时发生要考虑线程安全
队列加了线程锁
XML
void AUECppGameModeBase::ParseXml()
{
TSharedPtr<FXmlFile> XmlFile = MakeShareable(new FXmlFile);
if (XmlFile->LoadFile(TEXT("F:\\UnrealProject\\2024-04-The-Fifth-Stage\\Week17\\Teacher\\1.xml")))
{
FXmlNode* RootNode = XmlFile->GetRootNode();
//获取子节点
FXmlNode* Node = RootNode->GetChildrenNodes()[0];
//读取标签名称
UE_LOG(LogTemp, Log, TEXT("%s"), *Node->GetTag());
//读取属性值
UE_LOG(LogTemp, Log, TEXT("%s"), *Node->GetAttribute(TEXT("graduate")));
//读取标签值
UE_LOG(LogTemp, Log, TEXT("%s"), *Node->GetContent());
}
}
void AUECppGameModeBase::WriteXml()
{
TSharedPtr<FXmlFile> XmlFile = MakeShareable(new FXmlFile(TEXT("<person></person>"), EConstructMethod::ConstructFromBuffer));
FXmlNode* RootNode = XmlFile->GetRootNode();
TArray<FXmlAttribute> Attributes;
FXmlAttribute Age(TEXT("Age"), TEXT("20"));
Attributes.Add(Age);
RootNode->AppendChildNode(TEXT("hero"), TEXT("二狗"), Attributes);
XmlFile->Save(TEXT("d:\\2.xml"));
}
动态库静态库
逻辑的合集
编译阶段 , 静态阶段 , 运行起来是动态阶段
C++静态库
常规项目改成库项目
引入静态库
配置相对路径目录 静态库
不在一个解决方案引入静态库
配置目录
配置输入目录
C++动态库
项目不用编译就可以在新项目里面用 , 只修改动态库内容
函数不可以导出 , 类可以
直接引入dll并使用
显示调用
隐式调用
通过定义默认宏 , 动态确定是到处还是到处_declspec(dllexport) || _declspec(dllimport)
虚幻静态库
Salte
void AUECppGameModeBase::RegisterTab()//注册
{
//注册
FGlobalTabmanager::Get()->RegisterNomadTabSpawner(
TabName, FOnSpawnTab::CreateUObject(this, &AUECppGameModeBase::OnSpawnTab))
.SetDisplayName(NSLOCTEXT("mytab", "kd3", "自定义停留Tab"));
}
void AUECppGameModeBase::ShowTab()//显示
{
FGlobalTabmanager::Get()->TryInvokeTab(TabName);
}
void AUECppGameModeBase::UnRegisterTab()//卸载
{
FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(TabName);
}
TSharedRef<SDockTab> AUECppGameModeBase::OnSpawnTab(const FSpawnTabArgs& Args)
{
//加载UMG页面
TSubclassOf<UUserWidget> WigetClass = LoadClass<UUserWidget>(
nullptr, TEXT("/Script/UMGEditor.WidgetBlueprint'/Game/UMG/UMG_ChatUI.UMG_ChatUI_C'"));
UUserWidget* Widget = CreateWidget<UUserWidget>(UGameplayStatics::GetPlayerController(this, 0), WigetClass);
//加载Widget
return SNew(SDockTab).TabRole(ETabRole::NumRoles)
[
Widget->TakeWidget()
];
//手写控件
return SNew(SDockTab).TabRole(ETabRole::NomadTab)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
[
SNew(SButton).HAlign(HAlign_Center).VAlign(VAlign_Center).OnClicked(
FOnClicked::CreateUObject(this, &AUECppGameModeBase::OnButtonClicked))
[
SAssignNew(ButtonTextblock, STextBlock).Text(NSLOCTEXT("mytab", "adsksd2", "按钮文字"))
]
]
+ SVerticalBox::Slot()
[
SNew(SCheckBox)
[
SNew(STextBlock).Text(NSLOCTEXT("Mytab", "asddfdgf", "选项)"))
]
]
];
}
FReply AUECppGameModeBase::OnButtonClicked()
{
if (ButtonTextblock)
{
Number++;
ButtonTextblock->SetText(FText::AsNumber(Number));
return FReply::Handled();
}
return FReply::Unhandled();
}
//--windows窗口
void AUECppGameModeBase::BuildWindow()
{
if (!MyWindow)
{
SAssignNew(MyWindow, SWindow)
.Title(NSLOCTEXT("mywindow", "asdk2", "自定义窗口"))
.ClientSize(FVector2D(800, 600))
.ScreenPosition(FVector2D(100, 100))
[
SNew(SButton)
[
SNew(STextBlock).Text(NSLOCTEXT("mywindow", "asdkfd2", "按钮文本"))
]
];
}
}
void AUECppGameModeBase::ShowWindow()
{
if (MyWindow)
{
FSlateApplication::Get().AddWindow(MyWindow.ToSharedRef());
}
}
垃圾回收机制
GC
addtoRoot慎用
自定义类持有U类指针 , 阻止垃圾回收机制
TIPS
面试 : 循环引用
Json和虚幻数组的区别 , 不是同质类型
多思考这个游戏我来做这个功能怎么做
时间膨胀值 : 局部和全局
时空穿梭 :
前面const是值不能改 , 后面的const的地址不能改
面试 :
插件和模块有什么区别 : 插件是可以打包在其他项目里面用 , 模块的话是内部的
模块里面只会放相关功能 , 一个插件可以有多个模块
没有白色执行引脚的函数 , 如果输出引脚被2个地方调用了 , 函数会调用2次
Source
面试
多态应用在哪里 : 有限状态机
进程和线程 : 进程是一个计算机最小的一个调度单位 , 线程是CPU最小的调度单位 , 进行中可以启动多个线程。
5.2和5.3请求有差异会踢除HTTP
API & 节点
面试技巧课
彩色简历
提前10~20分钟
信息准备:
个人职业规划 : 公司有没有管理方面的发展
怎么看待加班 :
在自己工作的时间内完成自己的工作 , 如果工作的项目比较感我也愿意配合加班 。
那咋们公司加班的话是补休还是有加班费的。