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提供的线程安全,默认情况下是线程不安全的,线程安全会消耗额外的资源;
