UE4游戏架构
1)UE4world里,所有的物体大部分都要继承自Actor,world容器同时存在于服务端和客户端;
2)GameInstance:游戏实例,架构上凌驾于最顶端,实例里的数据不会随着关卡的切换而消失;
3)GameMode:联网状态下,只存在于服务端,可以充当一个服务端,客户端不能访问;
4)GameSession:联网状态下的游戏房间;
5)GameState:数据的全局管理,服务端和客户端都存在一份,用来保持数据的同步,也可将其中的部分数据设置为不同步;
6)PlayerController:非常重要的一个类,关联了客户端和服务端,通过该类,客户端可以向服务端发送请求;
7)PlayerState:角色数据,该类需要通过PlayerController来访问;
8)Pawn:角色的外在表现类,同PlayerState,也需要通过PlayerController来访问,服务端和客户端都存在一份,同时保持同步;
9)HUD:只存在于客户端;
10)GameViewportClient:游戏视口,在编辑器中开启多个游戏视口时,会有多个GameViewportClient实例;
11)GameUserSettings:游戏配置,该类里有save和load方法,来方便存储和读取配置信息;
12)WorldSettings:当前world的属性;
Actor的生命周期
1)新建一个C++的Actor类,在Actor上单击右键,创建一个基于该Actor类的蓝图类并放入场景中;
注:新创建的C++Actor类,不能直接放入场景中;
注:1)Actor可以看成是构成level的最基本单位,存在于world里的实例大部都是Actor,除了Actor,还有UObject;
2)gameMode,gameState,gameSession,playController,pawn,HUD等都是继承自AActor;
3)Actor存在于wrold上,将Actor放置在world中时,需要通过SpawnActor实现;
4)Actor中集成了主要类的一些共性,如网络同步,按键响应,角色缩放等;
5)辅助功能,如读取配置,保存等是直接继承自UObject;
UE4初始化-Actor
1)上面同一个文件的同一个方法中,开始创建gameInstance
按F11,可以进入CreatePIEGameInstance函数里面
如果有自定义的gameInstance,就生成自定义的,没有就生成默认的,生成后添加到根目录,以防止被GC垃圾回收
有了gameInstance后,要用gameInstance来初始化编辑器
初始化编辑器里,会先生成一个world
判断是客户端还是服务端,如果是服务端,则复制一个wrold,复制时,会将Actor全部复制到新的world里,该world为编辑器world,是没有图形界面的
复制好了后,会重新设置world,将新的world放入wroldContext中,再进行初始化
2)创建好gameInstance后,同一个文件里同一个方法下面开始执行gameSinstance
2-1)startPlayInEditorGameInstance里,先获取一个引擎的实例,并判断该实例是否为客户端,不是的分支为服务端
服务端的分支里,进行gameMode的初始化
初始化gameMode时,会用URL来创建gameMode
如果有蓝图的gameMode,则载入蓝图的gameMode,载入后,如果为空,则生成一个全局的
2-2)服务端分支里,创建好gameMode后,创建AIS,初始化Actor,并通过URL来判断是哪个world
Actor初始化
如果Actor没有被初始化,则开始Actor的初始化
遍历levels里的所有level,并且初始化每个level里的Actor
正式开始初始化Actor
初始化Actor之前,先判断本Actor是否已经初始化,没有的话,先预初始化Actor的组件
预初始化前,会先调用父类的函数
父类的函数里,会先判断是否支持按键响应,默认是不支持的,只有playController支持
注:Actor的预初始化PreInitializeComponents的主要作用是判断,本Actor是否会输出按键响应,默认是不能的,只有playController是可以输出按键响应的;
预初始化后,会开始正式的Actor初始化,实始化结束后,在PostInitializeComponents判断本组件是否需要销毁,不需要时,更新该组件
初始化组件时,先获取组件,且如果本组件没注册,则不执行初始化
判断本组件是否需要被销毁
组件更新时,还需判断本组件是否支持复制
2-3)服务端分支里,组件初始化完成后,会初始化world
2-4)beginPlay的初始化,PlayWorld就是一个UWorld的变量
BeginPlay里会先获取GameMode,再用GameMode来调用StartPlay
2-4-1)GameMode下的StartPlay
开始调用Actor里面的BeginPlay
BeginPlay里,InitialLifeSpan表示Actor的存在时间,0表示不一直存在,1表示存在1秒;
RegisterAllActorTickFunctions开始注册tick;
如果本Actor里有多个组件时,则会循环所有组件,如果本组件注册了,则注册组件的tick函数,且执行组件里的BeginPlay,在执行组件里的BeginPlay时,还会调用组件蓝图里的BeginPlay;
ReceiveBeginPlay()表示调用蓝图里的BeginPlay,由此可见,C++文件里的BeginPlay是先于蓝图里的BeginPlay执行的;
设置InitialLifeSpan的存在时间的底层原理,也是通过计时器来完成的
断言
1)check():check里的条件,如果为null或者0或者false,就会触发中断,如check((ne = PrintC()) != false)、check(GameBase);
2)verifyf():类似check,条件为null或者0或者为false时,就会触发中断,verifyf除了条件外,还可以输出信息,如verifyf(false , TEXT("pikaqiu %s") , *this->GetName());
3)checkf():类似verifyf,如checkf(false , TEXT("pikaqiu %s") , *this->GetName());
4)checkNoEntry():函数中断,写在函数体里,如果该函数体执行了,就会触发中断;
5)checkNoReentry():函数中断,写在函数体里,如果该函数体执行了两次,就会触发中断;
6)checkNoRecursion():函数中断,写在函数体里,如果该函数体有递归,则会触发中断;
7)ensure():类似check,但ensure触发的是断点,触发点点击继续,程序可以继续执行,check触发程序会直接崩溃,不能继续执行;
8)ensureMsgf():类似ensure,多了一个可以附加的额外信息,类似check和checkf;
智能指针-共享指针
UE4里的UObject是支持GC垃圾回归的,但如果是非继承自UObject的类,就需要用智能指针来管理,以防止出现野指针;
共享指针和UObject都支持GC垃圾回归,但两者不兼容,继承自UObject的类不能使用共享指针;
1)共享指针,共享指针必须使用游戏线程,且共享指针可以为null;共享指针不支持复制,但可利用引用计数,当引用计数为0时,GC就会进行回归,以防止出现野指针;使用共享指针时,不能使用TArray,因为TArray会将类或指针进行复制;
智能指针-共享引用
2)共享引用:和共享指针不同,共享引用没有IsValid方法,共享引用只要存在,就一定是有效的;共享引用必须在定义的时候,使用有效的对象进行初始化;共享指针转换成共享引用是不安全的,转换的过程中有一个断言,如果对角为空,触发后程序会崩溃;共享引用不能设置为null;
共享引用示例代码:
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "PikaTestActor.generated.h" class PikaBase { }; class Pikaqiu : public PikaBase { public: void PikaPinrt() {}; }; //继承自TSharedFromThis<PikaNoObj>后,获取到类实例后,可以直接转换成共享指针 class PikaNoObj :public TSharedFromThis<PikaNoObj> { public: int32 PikaInt; float PikaFloat; //单例模式 static TSharedRef<PikaNoObj> Get() { if (PointPikaNoObj.IsValid() || PointPikaNoObj.Get()) { //获取本实例 PointPikaNoObj = MakeShareable(new PikaNoObj()); } //返回共享指针 return PointPikaNoObj->AsShared(); } private: static TSharedPtr<PikaNoObj> PointPikaNoObj; }; class PikaSettings :public TSharedFromThis<PikaSettings> { public: void PikaPrint() { } }; UCLASS() class PIKAFIRSTDEBUG_API APikaTestActor : public AActor { GENERATED_BODY() public: // Sets default values for this actor's properties APikaTestActor(); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; //共享指针 void PikaTestFun(); //共享引用 void PikaShareRef(); //共享指针和共享引用之间的转换 void PikaShareTurn(); //弱指针 void PikaTweakPtr(); public: // Called every frame virtual void Tick(float DeltaTime) override; //共享指针 TSharedPtr<PikaNoObj> PikaNoObjInstance; //弱指针 TWeakPtr<PikaNoObj> PikaTweakPoint; }; ---------------------------上面为.h文件,下面为.cpp文件------------------------------- // Fill out your copyright notice in the Description page of Project Settings. #include "PikaTestActor.h" TSharedPtr<PikaNoObj>PikaNoObj::PointPikaNoObj; // Sets default values APikaTestActor::APikaTestActor() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; } // Called when the game starts or when spawned void APikaTestActor::BeginPlay() { Super::BeginPlay(); //智能转换 //用派生类获取基类共享指针 TSharedPtr<PikaBase> PikaBasePtr = MakeShareable(new Pikaqiu()); //用基类共享指针转换成派生类共享指针 TSharedPtr<Pikaqiu> PikaqiuPtr = StaticCastSharedPtr<Pikaqiu>(PikaBasePtr); //共享引用的转换类型共享指针,只需将StaticCastSharedPtr换成StaticCastSharedRef if (PikaqiuPtr.IsValid()) { PikaqiuPtr->PikaPinrt(); } //常量指针转换成变量指针 const TSharedPtr<PikaBase> PikaBaseConst = MakeShareable(new PikaBase()); //常量转换时,不能使用派生类 TSharedPtr<PikaBase> PikaBaseVar = ConstCastSharedPtr<PikaBase>(PikaBaseConst); TSharedPtr<Pikaqiu> PikaqiuVar = StaticCastSharedPtr<Pikaqiu>(PikaBaseVar); if (PikaqiuVar.IsValid()) { PikaqiuVar->PikaPinrt(); } } // Called every frame void APikaTestActor::Tick(float DeltaTime) { Super::Tick(DeltaTime); } void APikaTestActor::PikaTestFun() { //共享指针的使用 PikaNoObjInstance = MakeShareable(new PikaNoObj());//PikaNoObjInstance转换成共享指针 if (PikaNoObjInstance.IsValid() || PikaNoObjInstance.Get()) { PikaNoObjInstance.Get()->PikaFloat; //释放共享指针 PikaNoObjInstance.Reset(); } /*PikaNoObjInstance = NULL; //利用引用计数 TSharedPtr<PikaNoObj> PikaNoObjInstance_b; PikaNoObjInstance_b = PikaNoObjInstance;*/ } //共享引用 void APikaTestActor::PikaShareRef() { //共享引用创建 TSharedRef<PikaNoObj> PikaRef(new PikaNoObj()); //共享引用的两种访问方法 PikaRef->PikaFloat; (*PikaRef).PikaFloat; } //共享指针和共享引用之间的转换 void APikaTestActor::PikaShareTurn() { //共享引用创建,PikaRef是局部函数,作用域只是本函数周期 TSharedRef<PikaNoObj> PikaRef(new PikaNoObj()); //共享引用转换成共享指针,隐式转换 PikaNoObjInstance = PikaRef; //普通指针转换成共享指针 PikaNoObj* PikaNew = new PikaNoObj(); PikaNoObjInstance = MakeShareable(PikaNew); //共享指针转换成共享引用 //共享指针转换成共享引用是不安全的,转换的过程中有一个断言,如果对角为空,触发后程序会崩溃 PikaRef = PikaNoObjInstance.ToSharedRef(); //通过单例模式访问属性 PikaNoObj::Get()->PikaFloat; } //弱指针 void APikaTestActor::PikaTweakPtr() { //创建弱指针的两种方式 //1-共享指针创建 TSharedPtr<PikaNoObj> PikaPtr = MakeShareable(new PikaNoObj()); TWeakPtr<PikaNoObj> PikaWeak_a(PikaPtr); PikaTweakPoint = PikaWeak_a; //2-共享引用创建 TSharedRef<PikaNoObj> PikaRef(new PikaNoObj()); TWeakPtr<PikaNoObj> PikaWeak_b(PikaRef); PikaTweakPoint = PikaWeak_b; //置空弱指针,置空后,弱指针不会阻止对象被销毁,置空后,对象依然存在 PikaTweakPoint = nullptr; //弱指针转换成共享指针 TSharedPtr<PikaNoObj> PikaNewPtr(PikaTweakPoint.Pin()); if (PikaNewPtr.IsValid()) { PikaNewPtr->PikaFloat; } } //弱指针的应用示例-树结构 class PikaTreeNode { TArray<TSharedPtr<PikaTreeNode>> Children;//子节点 TWeakPtr<PikaTreeNode> Parent;//父节点 }; void NewMain() { //获取共享指针 TSharedPtr<PikaSettings> PikaSetInstance = MakeShareable(new PikaSettings()); PikaSetInstance->PikaPrint(); //解引用,共享指针转换成普通类指针 PikaSettings * PikaSetNormal = PikaSetInstance.Get(); //类指针再次转换成共享指针 if (PikaSetNormal) { PikaSetNormal->AsShared(); } }
智能指针-弱指针
弱指针就是对对象一个弱引用,与共享指针不同的是,弱指针不会阻止对象被销毁,如果对象被共享指针销毁,弱指针会自动清空;
示例代码如上;
智能指针的公优缺点
使用UE4智能库的原因:
1)标准库std::shared_ptr(and even trl::shared_ptr)不是所有平台上都可用;
2)使得在所有编译器和平台上有更加一致的实现;
3)可以和其他虚幻容器及类型无缝地协作;
4)更好地控制平台特性,包括线程处理和优化;
5)我们想提供线程安全的功能(以获得好的性能);
6)我们想在性能方面有更多的控制权(内联函数、内存、虚函数的应用等);
7)在不需要的时候倾向于不引入新的第三方依赖;
整体优点:
1)您可以像操作常规的c++指针那样来复制、解引用及比较共享指针;
2)当没有共享引用时资源自动销毁;
3)包含了可以通过多个线程安全进行访问的“线程安全”版本;
4)您几乎可以创建到任何类型的对象的共享指针;
5)支持指针“const”、前置声明的不完全类型、类型转换等;
6)所占内存大小是C++指针在64-位系统中所占内存的二倍(外加了一个共享的16字节的引用控制器);
在性能上的优势:
1)所有操作所占时间都是固定的;
2)共享指针解引用的速度和C++指针一样快;
3)复制共享指针永远不会分配内存;
4)线程安全的版本是无锁的;
5)和Boost或STL相比,其实现更快;
劣势:
1)创建及复制指针所带来的性能消耗;
2)引用计数处理;
3)共享指针使用的内存比c++指针多;
4)引用控制器的额外的堆分配;
5)由多个共享指针引用的每个独立对象都有性能消耗;
6)弱指针访问速度比共享指针访问速度略慢;
内存使用情况(在32位操作系统):
1)所有的共享指针(TSharedPtr,TSharedRef,TWeakPtr)都占8个字节(当针对32-位系统编译时);
2)C++指针(无符号32位整型);
3)引用控制器指针(无符号32位整型);
4)TSharedFromThis也占8个字节,因为它内嵌了弱指针;
引用控制器(当针对32-位系统编译时)
1)引用控制器对象占12个字节;
2)C++指针(无符号32位整型);
3)共享引用计数(无符号32位整型);
4)弱引用计数(无符号32位整型)
注:无论有多少个共享指针或弱指针引用一个对象,都仅为每个对象创建一个引用控制器;
共享指针:
1)最常用的指针,用来存储;
共享引用优点:
1)共享引用永远不会为null,且总是可以进行解引用;
2)共享指针的性能消耗最小,所有操作所占时间都是固定的;
弱指针:
1)弱指针允许您安全地检查一个对象是否已经被销毁;
2)使用弱引用来断开引用循环;
缺点:最慢;
智能转换
示例代码如上面的BeginPlay();
强指针和弱指针的应用
#pragma once #include "CoreMinimal.h" //玩家ID接口 class IPikaID { IPikaID() { ID = FMath::RandRange(100, 10000); } FORCEINLINE uint64 GetID() {return ID;} private: uint64 ID; }; //数据类 class PikaData { public: PikaData() { PikaHealth = 100.f; PikaDeath = 0; PikaPlayerName = TEXT("piqiu"); } float PikaHealth; uint8 PikaDeath : 1; FName PikaPlayerName; }; //数据管理类 class PikaDataManage { //单例模式 public: TSharedRef <PikaDataManage> Get() { if (!PikaDataManInstance.IsValid()) { PikaDataManInstance = MakeShareable(new PikaDataManage()); } return PikaDataManInstance.ToSharedRef(); } TSharedRef<PikaData> PikaCreateData() { TSharedPtr<PikaData> PikaTempData = MakeShareable(new PikaData()); PikaMyData.Add(PikaTempData->GetID() , PikaTempData); return PikaTempData.ToSharedRef(); } //析构函数 ~PikaDataManage() { //清空数据 /*for (auto & Tmp: PikaMyData) { delete Tmp.Value; } PikaMyData.Empty();*/ } private: static TSharedPtr<PikaDataManage> PikaDataManInstance; //强引用 TMap<uint64, TSharedPtr<PikaData>> PikaMyData; }; //角色实例(显示层) class PikaCharacter { public: //更新角色数据 void PikaUpdateData(TSharedRef<PikaData> PikaCurData) {PikaCharData = PikaCurData;} //更新角色名字 void PikaUpdateName(FString PikaNewName) { if (IsValid()) { PikaCharData.Pin()->PikaPlayerName = FName(*PikaNewName); } } //返回数据 FORCEINLINE bool IsValid() { return PikaCharData;} private: //弱指针指向角色数据,可以对数据(上面强引用的PikaMyData)进行修改 //好处:当强引用里的数据被析构掉后,该弱指针就自动为空了, //如果此处不是弱指针,数据被强引用析构掉后,该指针就会变成野指针 TWeakPtr<PikaData> PikaCharData; }; //假设的场景函数 void PikaMain() { //注册 PikaCharacter * PikaMyCharacter = new PikaCharacter(); PikaMyCharacter->PikaUpdateData(PikaDataManage->Get()->PikaCreateData()); PikaMyCharacter->PikaUpdateName("pikaqiu"); }
UE4代理
1)代理是指不需要知对象是什么,就可以执行对象里的函数,条件是需要提前进行函数绑定;
2)UE4的代理分为3种,单播代理、多播代理和动态代理;
3)单播代理是指可以支持绑定单个函数,多播代理是指可以支持绑定多个函数,动态代理可以支持序列化(UObject);
4)单播代理可以有返回值,但多播代理是没有返回值的;
5)动态代理要求,被绑定的函数是继承自UObject;
单播,共享指针绑定的执行如下:
单播绑定UObject的执行如下:
动态代理绑定UObject的执行如下:
动态事件代理绑定执行如下:
代理的相关示例代码
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "PikaFun.generated.h" //单播代理 //普通代理 DECLARE_DELEGATE(FPikaTestA); //带一个参数的代理 DECLARE_DELEGATE_OneParam(FPikaTestB , bool); //带返回值的代理 DECLARE_DELEGATE_RetVal(bool , FPikaTestC); //带一个参数和返回值的代理 DECLARE_DELEGATE_RetVal_OneParam(int32 , FPikaTestD , FString &); //多播代理 DECLARE_MULTICAST_DELEGATE_OneParam(FPikaTestE , FString &); //动态代理 DECLARE_DYNAMIC_DELEGATE_OneParam(FPikaTestF , FString& , PikaStr); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FPikaTestG ,FString & , PikaStr); static void PikaPrint(FString PikaStr) { if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, 20, FColor::Red, PikaStr); } } //代理 class PikaA { public: //单播代理 FPikaTestA PikaTestA; FPikaTestB PikaTestB; FPikaTestC PikaTestC; FPikaTestD PikaTestD; //多播代理 FPikaTestE PikaTestE; //动态代理 FPikaTestF PikaTestF; FPikaTestG PikaTestG; //调用执行代理 void init() { bool PikaIsRight = false; //INDEX_NONE常量为UE4定义,值为-1 int32 PikIndex = INDEX_NONE; FString PikaStr = TEXT("pikaqiu"); //代理执行 PikaTestA.ExecuteIfBound(); PikaTestB.ExecuteIfBound(PikaIsRight); if(PikaTestC.IsBound()) { PikaIsRight = PikaTestC.Execute(); } if (PikaTestD.IsBound()) { PikIndex = PikaTestD.Execute(PikaStr); } FString PikaNewStr = TEXT("pikaqiuhuang"); PikaTestE.Broadcast(PikaNewStr); PikaTestF.ExecuteIfBound(PikaNewStr); PikaTestG.Broadcast(PikaNewStr); } }; class PikaB : public TSharedFromThis<PikaB> { public: PikaB() { //绑定UObject PikaAInstance = MakeShareable(new PikaA()); } //共享指针的初始化 /* void init() { PikaAInstance = MakeShareable(new PikaA()); PikaAInstance->PikaTestA.BindSP(this, &PikaB::PikaExecuteA); PikaAInstance->init(); }*/ //原生绑定 /*PikaB(PikaA * PikaNewA) { if (PikaNewA) { PikaAInstance = PikaNewA; //C++的原生绑定 PikaAInstance->PikaTestA.BindRaw(this , &PikaB::PikaExecuteA); PikaAInstance->PikaTestB.BindRaw(this , &PikaB::PikaExecuteB); PikaAInstance->PikaTestC.BindRaw(this , &PikaB::PikaExecuteC); PikaAInstance->PikaTestD.BindRaw(this , &PikaB::PikaExecuteD); //原生多播绑定 PikaAInstance->PikaTestE.AddRaw(this, &PikaB::PikaExecuteE_A); PikaAInstance->PikaTestE.AddRaw(this, &PikaB::PikaExecuteE_B); } }*/ void PikaExecuteA() { PikaPrint(TEXT("PikaExecuteA")); } void PikaExecuteB(bool PikaIsRight) { PikaPrint(TEXT("PikaExecuteB = ture")); } bool PikaExecuteC() { PikaPrint(TEXT("bool PikaIsRight = ExecuteC")); return false; } int32 PikaExecuteD(FString & PikaStr) { PikaPrint(TEXT("bool PikaIsRight = ExecuteD") + PikaStr); return 0; } void PikaExecuteE_A(FString & PikaStr) { PikaPrint(TEXT("PikaExecuteE_A")); } void PikaExecuteE_B(FString & PikaStr) { PikaPrint(TEXT("PikaExecuteE_B")); } ~PikaB() { //原生绑定 //if (PikaAInstance) //共享指针 if(PikaAInstance.Get()) { //解绑定 PikaAInstance->PikaTestA.Unbind(); PikaAInstance->PikaTestB.Unbind(); PikaAInstance->PikaTestC.Unbind(); PikaAInstance->PikaTestD.Unbind(); PikaAInstance->PikaTestE.Clear(); PikaAInstance->init(); //原生,改为共享指针后,会自动释放,不需手动 //delete PikaAInstance; //PikaAInstance = nullptr; } } //强制内敛共享指针 FORCEINLINE TSharedRef<PikaA> PikaGet() {return PikaAInstance.ToSharedRef();}; private: //PikaA * PikaAInstance; //共享指针 TSharedPtr<PikaA> PikaAInstance; }; //增加头文件,编译通过后,生成的反射数据位置为: //\PikaFirstDebug\Intermediate\Build\Win64\UE4Editor\Inc\PikaFirstDebug\PikaFun.generated.h UCLASS() class UPikaDynamicObj : public UObject { GENERATED_BODY() public: void init() { PikaA *PikaNewA = new PikaA(); PikaNewA->PikaTestF.BindDynamic(this , &UPikaDynamicObj::PikaPirntObj_A); PikaNewA->PikaTestG.AddDynamic(this , &UPikaDynamicObj::PikaPirntObj_B); PikaNewA->PikaTestG.AddDynamic(this , &UPikaDynamicObj::PikaPirntObj_C); PikaNewA->init(); //解绑 PikaNewA->PikaTestF.Unbind(); PikaNewA->PikaTestG.RemoveDynamic(this , &UPikaDynamicObj::PikaPirntObj_B); PikaNewA->PikaTestG.RemoveDynamic(this , &UPikaDynamicObj::PikaPirntObj_C); //再次调用PikaA里的init,如果打印一两次,说明解绑失败 PikaNewA->init(); delete PikaNewA; } UFUNCTION() void PikaPirntObj_A(FString& PikaStr) { PikaPrint(PikaStr + TEXT("PikaPirntObj_A")); } UFUNCTION() void PikaPirntObj_B(FString& PikaStr) { PikaPrint(PikaStr + TEXT("PikaPirntObj_B")); } UFUNCTION() void PikaPirntObj_C(FString& PikaStr) { PikaPrint(PikaStr + TEXT("PikaPirntObj_C")); } }; //动态事件代理 class FPikaEvent { public: //绑定一个事件FPikaEventName DECLARE_EVENT_OneParam(FPikaEvent, FPikaEventName, FString &); //必须用on来获取 virtual FPikaEventName &OnPikaEventName() = 0; DECLARE_EVENT_OneParam(FPikaEvent, FPikaqiuEventName, FString &); FPikaqiuEventName &OnPikaqiuEventName() { return PikaqiuEventName; } virtual ~FPikaEvent() {} private: FPikaqiuEventName PikaqiuEventName; }; class FPikaEventA : public FPikaEvent { public: DECLARE_DERIVED_EVENT(FPikaEventA, FPikaEvent::FPikaEventName, FPikaEventB); virtual FPikaEventB &OnPikaEventName() { return PikaEventB; }; void init() { FString PikaqiuNewStr = "pika"; OnPikaEventName().Broadcast(PikaqiuNewStr); PikaqiuNewStr = "pikaqiu"; OnPikaqiuEventName().Broadcast(PikaqiuNewStr); } private: FPikaEventB PikaEventB; }; class APikaTest { public: void init() { FPikaEventA *PikaNew = new FPikaEventA(); PikaNew->OnPikaEventName().AddRaw(this , &APikaTest::PikaExecuteA); PikaNew->OnPikaEventName().AddRaw(this, &APikaTest::PikaExecuteB); PikaNew->OnPikaqiuEventName().AddRaw(this, &APikaTest::PikaExecuteC); PikaNew->OnPikaqiuEventName().AddRaw(this, &APikaTest::PikaExecuteD); PikaNew->init(); delete PikaNew; } void PikaExecuteA(FString & PikaStr) { PikaPrint(TEXT("PikaExecuteA")); } void PikaExecuteB(FString & PikaStr) { PikaPrint(TEXT("PikaExecuteB")); } void PikaExecuteC(FString & PikaStr) { PikaPrint(TEXT("PikaExecuteC")); } void PikaExecuteD(FString & PikaStr) { PikaPrint(TEXT("PikaExecuteD")); } }; 1 1 -------------------------上面为定义的PikaFun.h文件--------------------------- -------------------------下面为执行代码的PikaMainActor文件------------------- // Fill out your copyright notice in the Description page of Project Settings. #include "PikaMainActor.h" #include "PikaFun.h" static void PikaStatic() { PikaPrint(TEXT("PikaStatic")); } // Sets default values APikaMainActor::APikaMainActor() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; } // Called when the game starts or when spawned void APikaMainActor::BeginPlay() { Super::BeginPlay(); //原生 /*PikaA *PikaNewA = new PikaA(); PikaB *PikaNewB = new PikaB(PikaNewA); PikaNewA->init(); delete PikaNewB; //PikaNewA,PikaNewB此时都为野指针 PikaNewA = nullptr; PikaNewB = nullptr;*/ //共享指针 //TSharedPtr<PikaB> PikaNewB = MakeShareable(new PikaB); //PikaNewB->init(); //单播绑定UObject //PikaB *PikaNewB = new PikaB(); //PikaNewB->PikaGet()->PikaTestA.BindUObject(this, &APikaMainActor::PikaqiuPrint); //绑定静态函数 //PikaNewB->PikaGet()->PikaTestA.BindStatic(PikaStatic); //绑定普通函数,函数绑定前一定要在声明时加上UFUNCTION() //PikaNewB->PikaGet()->PikaTestA.BindUFunction(this, TEXT("PikaqiuPrint")); //PikaNewB->PikaGet()->init(); //动态代理调用 /*UPikaDynamicObj *PikaDynObj = NewObject<UPikaDynamicObj>(this); if (PikaDynObj) { PikaDynObj->init(); }*/ //动态事件代理调用 APikaTest *EPikaTest = new APikaTest(); if (EPikaTest) { EPikaTest->init(); } delete EPikaTest; EPikaTest = nullptr; } // Called every frame void APikaMainActor::Tick(float DeltaTime) { Super::Tick(DeltaTime); } void APikaMainActor::PikaqiuPrint() { PikaPrint(TEXT("pipikaqiusou")); }
多线程
进程是一段程序的执行过程,一个进程可以包含多个线程,且最少有一个线程,线程是程序不同的执行路径;一个线程如果崩溃了,进程也会崩溃;
线程不拥有资源,进程才拥有资源;
UE4里在网络通信及加载level时,会用到多线程;
创建线程操作的代码,本身是运行在主线程之中的;
创建线程的示例代码中,运行的主线程及自己创建的Pikaqiu线程;
输出日志中的打印信息
线程切换的运行图
切换线程时,代理里绑定的函数
代理里绑定的函数执行完成后,又变回Pikaqiu线程
创建线程及切换线程的示例代码
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "PikaInterface.generated.h" // This class does not need to be modified. UINTERFACE(MinimalAPI) class UPikaInterface : public UInterface { GENERATED_BODY() }; /** * */ class PIKAFIRSTDEBUG_API IPikaInterface { GENERATED_BODY() // Add interface functions to this class. This is the class that will be inherited to implement this interface. public: void PikaWork() {}; }; ---------------------上面为接口类PikaInterface.h文件--------------------------- ---------------------下面为线程类PikaThread.h文件------------------------------ // Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "PikaInterface.h" DECLARE_DELEGATE(FPikaThread); /** * */ class PIKAFIRSTDEBUG_API PikaThread : public FRunnable { public: PikaThread(); ~PikaThread(); FPikaThread PikaThreadDelegate; virtual uint32 Run(); //创建线程 void PikaCreateThread(IPikaInterface * PikaInterfaceInstancePar); private: //线程实例 FRunnableThread * PikaThreadInstance; //接口实例 IPikaInterface * PikaInterfaceInstance; }; ---------------------上面为线程类PikaThread.h文件------------------------------ ---------------------下面为线程类PikaThread.cpp文件---------------------------- // Fill out your copyright notice in the Description page of Project Settings. #include "PikaThread.h" PikaThread::PikaThread() { PikaInterfaceInstance = nullptr; } PikaThread::~PikaThread() { } //线程创建成功后,会运行Run函数 uint32 PikaThread::Run() { UE_LOG(LogTemp , Log , TEXT("PikaThreadRun")); if (PikaInterfaceInstance) { PikaInterfaceInstance->PikaWork(); //将线程切换到主线程,ENamedThreads::GameThread为切换的线程 FGraphEventRef PikaThreadChange = FFunctionGraphTask::CreateAndDispatchWhenReady([&]() { //执行代理绑定的函数 PikaThreadDelegate.ExecuteIfBound(); } , TStatId() , nullptr , ENamedThreads::GameThread ); //等待线程切换任务完成 FTaskGraphInterface::Get().WaitUntilTaskCompletes(PikaThreadChange); } return 0; } void PikaThread::PikaCreateThread(IPikaInterface * PikaInterfaceInstancePar) { PikaInterfaceInstance = PikaInterfaceInstancePar; //创建线程,第二个参数为线程名称,第三个为堆栈大小,0表示当前堆栈大小 PikaThreadInstance = FRunnableThread::Create(this, TEXT("Pikaqiu") , 0 , TPri_Normal); } ---------------------上面为线程类PikaThread.cpp文件------------------------------ ---------------------下面为GameMode类调用的.h文件和.cpp文件---------------------- // Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/GameModeBase.h" #include "PikaObj.h" #include "PikaInterface.h" #include "RunnableThread.h" #include "PikaThread.h" #include "PikaFirstDebugGameMode.generated.h" UCLASS(minimalapi) class APikaFirstDebugGameMode : public AGameModeBase { GENERATED_BODY() public: APikaFirstDebugGameMode(); virtual void BeginPlay() override; virtual void EndPlay(const EEndPlayReason::Type EndPlayReason); //由主线程执行 void PikaPrint(); private: IPikaInterface * PikaInteface; PikaThread * PikaThreadIns; }; ---------------------上面为.h文件,下面为.cpp文件-------------------------------- // Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. #include "PikaFirstDebugGameMode.h" #include "PikaFirstDebugHUD.h" #include "PikaFirstDebugCharacter.h" #include "PikaObj.h" #include "UObject/ConstructorHelpers.h" #include "PikaGraph.h" APikaFirstDebugGameMode::APikaFirstDebugGameMode() : Super() { // set default pawn class to our Blueprinted character static ConstructorHelpers::FClassFinder<APawn> PlayerPawnClassFinder(TEXT("/Game/FirstPersonCPP/Blueprints/FirstPersonCharacter")); DefaultPawnClass = PlayerPawnClassFinder.Class; // use our custom HUD class HUDClass = APikaFirstDebugHUD::StaticClass(); } void APikaFirstDebugGameMode::BeginPlay() { Super::BeginPlay(); //创建线程 PikaInteface = new IPikaInterface(); PikaThreadIns = new PikaThread(); //绑定PikaPrint PikaThreadIns->PikaThreadDelegate.BindUObject(this, &APikaFirstDebugGameMode::PikaPrint); PikaThreadIns->PikaCreateThread(PikaInteface); //使用闲置线程,ConstructAndDispatchWhenReady为延时构造 TGraphTask<FPikaGraph>::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(4.6f); } void APikaFirstDebugGameMode::EndPlay(const EEndPlayReason::Type EndPlayReason) { Super::EndPlay(EndPlayReason); //删除线程 delete PikaThreadIns; //置空接口实例 PikaInteface = nullptr; } //由主线程执行 void APikaFirstDebugGameMode::PikaPrint() { UE_LOG(LogTemp, Log, TEXT("PikaMainThreadRun")); }
调用闲置线程
使用闲置线程执行图,使用前使用的是主线程
调用闲置线程后为
调用闲置线程的示例代码
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" //使用闲置线程 class FPikaGraph { public: FPikaGraph(float PikaFloatA) :PikaFloatB(PikaFloatA)//初始化列表 { } static ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; } FORCEINLINE TStatId GetStatId() { RETURN_QUICK_DECLARE_CYCLE_STAT(FPikaGraph ,STATGROUP_TaskGraphTasks); } void DoTask(ENamedThreads::Type CurrentThread , FGraphEventRef Subsequents) { UE_LOG(LogTemp , Log , TEXT("Hello Pikaqiu %f") , PikaFloatB); } //AnyThread表示使用任意的闲置线程 static ENamedThreads::Type GetDesiredThread() { return ENamedThreads::AnyThread; } private: float PikaFloatB; }; ------------------------上面为定义的PikaGraph.h文件-------------------------------- ------------------------下面为在GameMode类里调用的代码----------------------------- void APikaFirstDebugGameMode::BeginPlay() { Super::BeginPlay(); //创建线程 //PikaInteface = new IPikaInterface(); //PikaThreadIns = new PikaThread(); //绑定PikaPrint //PikaThreadIns->PikaThreadDelegate.BindUObject(this, &APikaFirstDebugGameMode::PikaPrint); //PikaThreadIns->PikaCreateThread(PikaInteface); //使用闲置线程,ConstructAndDispatchWhenReady为延时构造 TGraphTask<FPikaGraph>::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(4.6f); }
异步调用线程运行图
使用线程池中的PoolThread5来执行代码
异步调用线程的示例代码
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" class PikaAsyncThread : public FNonAbandonableTask { public: friend class FAsyncTask<PikaAsyncThread>; int32 PikaInt; PikaAsyncThread( int32 _PikaInt) :PikaInt(_PikaInt)//初始化变量 { } //异步线程执行逻辑 void DoWork() { UE_LOG(LogTemp , Log , TEXT("PikaWork %d") , PikaInt); } FORCEINLINE TStatId GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(PikaAsyncThread , STATGROUP_ThreadPoolAsyncTasks); } }; --------------------------上面为异步调用线程的PikaAsyncThread.h文件------------------- --------------------------下面为GameMode中调用的代码---------------------------------- //本函数执行在主线程中 void APikaFirstDebugGameMode::BeginPlay() { Super::BeginPlay(); //创建线程 PikaInteface = new IPikaInterface(); PikaThreadIns = new PikaThread(); //绑定PikaPrint PikaThreadIns->PikaThreadDelegate.BindUObject(this, &APikaFirstDebugGameMode::PikaPrint); PikaThreadIns->PikaCreateThread(PikaInteface); //使用闲置线程,ConstructAndDispatchWhenReady为延时构造 TGraphTask<FPikaGraph>::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(4.6f); //异步线程调用 FAsyncTask<PikaAsyncThread> * PikaAsyncThreadInstance = new FAsyncTask<PikaAsyncThread>(3); //从线程池里调用后台线程 PikaAsyncThreadInstance->StartBackgroundTask(); //同步调用线程,同步的对象为程序主线程 //PikaAsyncThreadInstance->StartSynchronousTask(); //判断线程是否执行完毕 if (PikaAsyncThreadInstance->IsDone()) { UE_LOG(LogTemp , Log , TEXT("PikaAsyncThreadInstance is done")); } //固定用法 //确保线程执行完毕后,删除该线程 PikaAsyncThreadInstance->EnsureCompletion(); delete PikaAsyncThreadInstance; }
线程的使用区别
注:SpawnActor、NewObject、Destory Uobject/Actor、DrawDebugline只能由主线程执行,如果是用非主线程执行时,会触发代码里的断言,导致程序崩溃;
线程安全
进程创建多个线程后,多个线程并发对同一块内存进行操作的话,会导致数据不一致的问题;
线程锁的种类:互斥锁、条件锁、自旋锁、读写锁、递归锁、超时锁;
全局变量、静态变量对于多线程来说,都是不安全的,如果将全局资源变成某个线程的资源,那么,对于该线程来说,就是安全的;
UE4提供的线程安全,默认情况下是线程不安全的,线程安全会消耗额外的资源;