参考文献:
- 《大象无形》
- ue4中c++的网络同步、RPC_zhangxiaofan666的博客-CSDN博客_rpc ue4
虚幻C++编程
调试技巧
- 打开控制台
- 调整窗口大小:Execute Console Command r.setres 600x600
UObject
UObbject提供的功能
- Garbage collection垃圾收集
- 继承自UObject类,自动地进行对象的生命周期管理。
- 使用智能指针,只有非UObject类型,才能使用智能指针进行内存释放。
- Reference updating引用自动更新
- Reflectio反射
- Serialization序列化
- 你也可以通过给自己的纯C++类手动实现序列化所需要的函数
- Automatic updating of default property changes自动检测默认变量的更
改 - Automatic property initialization自动变量初始化
- Automatic editor integration和虚幻引擎编辑器的自动交互
- Type information available at runtime运行时类型识别
- 虚幻引擎打开了/GR-编译器参数。意味着你无法使用C++标准的RTTI机制:dynamic_cast,需要继承UObject,使用Cast<>函数进行
- Network replication网络复制
UObject构造函数
注意:UObject类会在引擎加载阶段,创建一个Default Object默认对象。
- 因此构造函数并不在游戏运行时调用,如果只有一个UObject对象存在于场景中,构造函数会被调用两次。
- 构造函数被调用时,UWorld不一定存在!
AActor
AActor提供的功能
- 它能够被挂载组件。
- 坐标和旋转量,是SceneComponent,如果Actor不需要一个固定位置,可以不挂载该组件。
Pawn、Character 、Controller
Pawn是棋子、Controller是棋手。
创建C++类
从C++到蓝图
UPROPERTY
- BlueprintReadWrite
- VisibleAnywhere
UFUNCTION
- BlueprintCallable
- BlueprintImplementEvent:由蓝图子类实现
- BlueprintNativeEvent:提供了C++默认实现,你需要提供一个“函数名_Implement”为名字的函数实现
RPC
同步
属性同步
-
actor中的变量要有复制属性,加上Replicated UPROPERTY(Replicated,BlueprintReadOnly,Category="Gameplay")
bool bIsCarryingObjective;
如果变量设置复制的时候,还要在.cpp中加上复制的规则,在DOREPLIFETIME的第一个参数是该actor,第二个参数是要复制的变量名,DOREPLIFETIME是一般属性复制,第二个 DOREPLIFETIME_CONDITION 是条件属性复制 void MyActor::GetLifetimeReplicatedProps(TArray<FLiftimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(MyActor, mMyProp);
}
为了加强对属性复制的控制,您可以使用一个专门的宏来添加附加条件, COND_SimulatedOnly 表示在复制属性前执行一次额外检查。这时,它只会复制到拥有此 actor 模拟复本的客户端
void AActor::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const
{
DOREPLIFETIME_CONDITION( AActor, ReplicatedMovement, COND_SimulatedOnly );
}
还有一个名叫 DOREPLIFETIME_ACTIVE_OVERRIDE 的宏,利用您想要的任何定制条件来决定何时复制/不复制某个属性 void AActor::PreReplication( IRepChangedPropertyTracker & ChangedPropertyTracker )
{
DOREPLIFETIME_ACTIVE_OVERRIDE( AActor, ReplicatedMovement, bReplicateMovement );
}
现在 ReplicatedMovement 属性只会在 bReplicateMovement 为 true 时复制。
函数同步
//WithValidation表示这个函数在调用远端相应函数时会进行有效性检查,有一种防止作弊的味道
//而reliable表示这个函数在本地调用之后,无论网络传输是否丢包,一定会在远端得到调用,是一个可靠传输
UFUNCTION(Server,Reliable,WithValidation)
void ServerFunc();
而且注意,如果是在服务端运行的函数,函数实现的名字必须后面加上_Implementation,把在服务端运行的逻辑写到下面函数里
同时,如果加了WithValidation关键字,除了实现ServerFire_Implementation这个函数,还需要实现函数名_Validate,只不过返回值为bool,返回为true,它会在服务端进行完整性检查时用到,如果客户端return false这一反馈时,服务端会取消连接,因为出现了问题,比如某处使用外挂,或出现了严重问题,它会强硬的断开
bool AFPSCharacter::ServerFunc_Validate()
{
//增加判断
return true;
}
WithValidation 关键字是RPC 增加验证函数的功能,比如如下,如果加的血大于一定血量,就强行断开
最后在该调用的时候调用下面的函数即可
ServerFunc();
多播函数
游戏性框架概述
行为树
- 行为树:行为树包含流程控制、装饰器、行为结点
- 流程控制分为Selector和Sequence
网络架构
- UE3时期,服务器不断发送状态给客户端,因为延迟原因,往往状态已经过时。
- UE4,客户端不再同步服务器状态,而是模仿服务端,既根据服务端的之前的状态和时间差“猜测”现在的状态。
- 一般网络延迟不太大时,游戏运行会比较流畅,但如果出现网络频繁波动太大的,就会出现在地上来回滑动的现象。
系统引擎相关类
-
容器
- UE4常用的容器有TArray、TMap
- 不能依赖于容器内部对象的地址,因为容器在扩容的时候,会重新分配内存,并将旧的对象移动到新的内存中,过去的内存就会失效。
-
正则表达式
- FRegexPattern
- FRegexMatcher
-
FPath
- FPaths::GameDir():游戏根目录
- FPaths::FileExists():文件是否存在
- FPaths::ConvertRelativePathToFull():相对路径转绝对路径
-
XML和JSON
- FXmlFile
- FXmlNode
- TJsonReaderFactory
-
文件读写访问
- FPlatformFileManager::Get()->GetPlatformFile();
- CopyDirectoryTree
- CopyFile
- OpenRead、OpenWrite
- LoadFileToArray、LoadFileToString
-
GConfi
-
UE_LOG
- DEFINE_LOG_CATEGORY_STATIC(LogMyCategory,Warning,All);
-
字符串处理
- FName:大小写不敏感,无法修改
- FText:本地化支持,无法修改
- FString:字符串,支持修改
-
平台支持
-
Image
-
ImagerWrapper,压缩数据是CompressedData,原始数据是RawData -
例如:转换PNG图片到JPG
- 从文件中读取为TArray的二进制数据;
- 用SetCompressData填充为压缩数据;
- 使用GetRawData即可获取RGB数据;
- 将RGB数据填充到JPG类型的ImageWrapper中;
- 使用GetCompressData,即可获得压缩后的JPG数据;
- 使用FFileHelper写入到文件中。
-
UTexture2D贴图数据 PNG转JPG bool FSystemToolsPublic::CovertPNG2JPG(const FString &SourceName, const FString &TargetName)
{
check(SourceName.EndsWith(TEXT(".png")) && TargetName.EndsWith(TEXT(".jpg")));
IImageWrapperModule &ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
IImageWrapperPtr SourceImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
IImageWrapperPtr TargetImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::JPEG);
TArray<uint8> SourceImageData;
TArray<uint8> TargetImageData;
int32 Width, Height;
const TArray<uint8> *UncompressedRGBA = nullptr;
if (!FPlatformFileManager::Get().GetPlatformFile().FileExists(*SourceName))
;
{
//文件不存在
return false;
}
if (!FFileHelper::LoadFileToArray(SourceImageData, *SourceName))
{
//文件读取失败
return false;
}
if (SourceImageWrapper.IsValid() && SourceImageWrapper->SetCompressed(SourceImageData.GetData(), SourceImageData.Num()))
{
if (SourceImageWrapper->GetRaw(ERGBFormat::RGBA, 8, UncompressedRGBA))
{
Height = SourceImageWrapper->GetHeight();
Width = SourceImageWrapper->GetWidth();
if (TargetImageWrapper->SetRaw(UncompressedRGBA->GetData(), UncompressedRGBA->Num(), Width, Height, ERGBFormat::RGBA, 8))
{
TargetImageData = TargetImageWrapper->GetCompressed();
if (!FFileManagerGeneric::Get().DirectoryExists(*TargetName))
{
FFileManagerGeneric::Get().MakeDirectory(*FPaths::GetPath(TargetName), true);
}
return FFileHelper::SaveArrayToFile(TargetImageData, *TargetName);
}
}
}
return false;
}
从文件加载贴图 UTexture2D* FSystemToolsPublic::LoadTexture2DFromBytesAndExtension(
const FString& ImagePath,
uint8* InCompressedData,
int32 InCompressedSize,
int32& OutWidth,
int32& OutHeight
)
{
UTexture2D* Texture = nullptr;
IImageWrapperPtr ImageWrapper = GetImageWrapperByExtention(ImagePath);
if (ImageWrapper.IsValid() && ImageWrapper->SetCompressed(InCompressedData, InCompressedSize))//读取压缩后的图片数据
{
const TArray <uint8>* UncompressedRGBA = nullptr;
if (ImageWrapper->GetRaw(ERGBFormat::RGBA, 8,
UncompressedRGBA))//获取原始图片数据
{
Texture = UTexture2D::CreateTransient(ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), PF_R8G8B8A8);
if (Texture != nullptr)
{
//通过内存复制,填充原始RGB数据到贴图的数据中
OutWidth = ImageWrapper->GetWidth();
OutHeight = ImageWrapper->GetHeight()
;
void* TextureData = Texture->
PlatformData->Mips[0].BulkData.
Lock(LOCK_READ_WRITE);
FMemory::Memcpy(TextureData,
UncompressedRGBA->GetData(),
UncompressedRGBA->Num());
Texture->PlatformData->Mips[0].
BulkData.Unlock();
Texture->UpdateResource();
}
}
}
return Texture;
}
UTexture2D* FSystemToolsPublic::LoadTexture2DFromFilePath(
FString& ImagePath,
int32& OutWidth,
int32& OutHeight
)
{
//文件是否存在
if (!FPlatformFileManager::Get().GetPlatformFile().FileExists(*ImagePath))
{
return nullptr;
}
//读取文件资源
TArray <uint8 > CompressedData;
if (!FFileHelper::LoadFileToArray(CompressedData, *ImagePath))
{
return nullptr;
}
return LoadTexture2DFromBytesAndExtension(ImagePath, CompressedData.GetData(), CompressedData.Num(), OutWidth, OutHeight);
}
虚幻引擎解析
模块机制
-
Runtime、Development、Editor、Plugin -
Public、Private、.build.cs -
/模块文件夹
-
模块名.Build.cs -
/Public
/Private
-
cs文件 using UnrealBuildTool;
public class pluginDev : ModuleRules
{
public pluginDev(TargetInfo Target)
{
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject"} ); PrivateDependencyModuleNames.AddRange(new string[] {
});
}
}
-
头文件 #pragma once
//#include "Engine.h"
#include "ModuleManager.h"
class FPluginDevModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};
-
源文件 //#include "pluginDev.h"//包含你刚刚创建的头文件
//IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl , pluginDev, "pluginDev")\
#include "PluginDevPrivatePCH.h"
//包含接下来要定义的模块预编译头文件名
void FPluginDevModule::StartupModule()
{
//...这里书写模块启动时需要执行的内容
}
void FPluginDevModule::ShutdownModule()
{
//...这里书写模块卸载时需要执行的内容
}
IMPLEMENT_MODULE(FPluginDevModule, pluginDev)
-
引入当前模块 工程名.Target.cs文件 public override void SetupBinaries(TargetInfo Target, ref List<UEBuildBinaryConfiguration > OutBuildBinaryConfigurations , ref List<string> OutExtraModuleNames
)
{
OutExtraModuleNames.AddRange( new string[] { "pluginDev" } );
}
UBT
- UBT项目会输出可执行文件,在win64平台上,输出.exe文件
- UBT输出Editor.exe
UHT
- UHT提供反射机制,在win64平台上,输出.exe文件
- UHT编译出是.generated.cpp和.generated.h
- 在.generated.cpp文件中,声明了的UClassCompiledInDefer<当前类名>静态全局变量实例,并在Main函数之前调用了该类的GetPrivateStaticClass函数,将该类的注册的UPROPERTY和UFUNCTION函数地址和变量地址收集出来,并提供给UE4作为反射的依据。
内存分配
- TBB内存分配相比于标准内存分配,解决以下两个问题:
- 标准内存分配要求同一时间只能有一个线程分配内存,TBB不需要
- CPU存在高速缓存,当出现内存不对齐的时候,会让缓存假命中,TBB提供了内存对齐的方案。
- 虚幻采用了TBB方案分配内存。
初始化
- UE4包含预初始化PreInit和初始化Init
- 预初始化初始化了一些引擎必须要设置的内容、加载模块,之后才会进入正式初始化
主循环
- 主循环是独立while循环,渲染线程并不是主循环。
- 主循环主要更新遍历状态、请求渲染线程刷新、更新GEngine->Tick、收集下一帧需要清理的UObject对象。
- 并不是所有的任务都在一次Tick中进行,例如加载地图不会要求一次加载完成而出现卡死的情况。
线程
-
FRunnable
- UE4提供了FRunnable类,包含Init、Run、Exit方法
- 通过FRunnableThread::Create创建
-
TaskGraph
- TGraphTask采用模板编程,不需要继承特定的类,但是需要提供对应的函数。
-
std::function:可以采用C++11的方法创建使用 -
UE4提供了一些类似C++11的线程同步方案
-
FCriticalSection 临界区 -
TFuture、TPromise:C++11也提供了std::future和std::promise -
UE4 提供了Async模板 auto Result = Async()EAsyncExecution::Thread, {
return 123;
}
UObject对象
- 构造:虚幻引擎采用了两步构造的机制,先分配内存,做出一些额外处理后再模塑对象。
- 销毁:一般从Root开始基于图算法遍历,然后标记出那些变量是不可达的,之后再进行删除。
- 序列化:UE4会默认保存类的默认变量,没有被标记成UPROPERTY不会被序列化,没有在默认值做修改的变量也不会被序列化。
SWidget
例如有如下的内容
ChildSlot
[
SNew(SOverlay)
+ SOverlay::Slot()
.HAlign(HAlign_Center)
.VAlign(VAlign_Top)
[
SNew(STextBlock)
.ColorAndOpacity(FLinearColor::White)
.ShadowColorAndOpacity(FLinearColor::Black)
.ShadowOffset(FIntPoint(-1, 1))
.Font(FSlateFontInfo("Arial", 26))
.Text(FText::FromString("Main Menu"))
]
+ SOverlay::Slot()
.HAlign(HAlign_Right)
.VAlign(VAlign_Bottom)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
[
SNew(SButton)
.Text(FText::FromString("Play Game!"))
.OnClicked(this, &SMainMenuUI::PlayGameClicked)
]
+ SVerticalBox::Slot()
[
SNew(SButton)
.Text(FText::FromString("Quit Game"))
.OnClicked(this, &SMainMenuUI::QuitGameClicked)
]
]
];
- 创建控件 SNew(WidgetClass)
- 增加子控件 + WidgetClass::Slot() ,使用[]为该插槽指定项,注意 SLeafWidget 不能包含子控件
- 组合控件 SVerticalBox 是垂直框
|