1. 反射
什麼是反射?或者說反射能做什麼,簡單來說,反射可以提供一種能力,能夠在運行時動態獲取對象的成員信息,如成員函數,成員變量。
UE 在其反射系統上支持了許多功能,如:
- 編輯器中可供編輯的屬性
- GC
- 序列化
- 網絡同步
1.1 使用反射的準備工作
UE 中應用反射需要與它定義的宏相結合,主要有 3 種類型,如下所示:
- 類註冊
#include "Weapon.generated.h" // 包含自動生成的頭文件信息
UCLASS() // 註冊類信息
class AWeapon : public AActor {
GENERATED_BODY() // 生成類輔助代碼
public:
UPROPERTY() // 註冊類屬性
FName WeaponName;
UFUNCTION() // 註冊類成員函數
void Fire();
}
- 結構體註冊(需要注意的是,
UFUNCTION
只能在 Class 中使用)
#include "Weapon.generated.h" // 包含自動生成的頭文件信息
USTRUCT() // 註冊結構體
struct FWeapon {
UPROPERTY() // 註冊結構體屬性
FName WeaponName;
}
- 枚舉註冊
#include "Weapon.generated.h" // 包含自動生成的頭文件信息
UENUM() // 註冊枚舉信息
enum WeaponType {
Short,
Middle,
Far,
}
1.2 反射的簡單應用
前面註冊完畢反射後,就能簡單的使用反射了,如下:
#include "human.generated.h" // 包含自動生成的頭文件信息
/** UHuman.h **/
class UHuman {
public:
UPROPERTY()
FString Name = "Hello, Reflection!!!";
UPROPERTY()
UHuman* Child;
}
UHuman* Human = NewObject<UHuman>();
UClass* UCHuman = UHuman::StaticClass();
// 轉爲對應的Property
if (FStrProperty* StrProperty = CastField<FStrProperty>(Property))
{
// 取Property地址(因爲屬性系統知道屬性在類內存中的偏移值)
void* PropertyAddr = StrProperty->ContainerPtrToValuePtr<void>(Human);
// 通過地址取值(其實就是類型轉換,畢竟我們都拿到內存地址了)
FString PropertyValue = StrProperty->GetPropertyValue(PropertyAddr);
UE_LOG(LogTemp, Warning, TEXT("Property's Value is %s"), *PropertyValue);
}
但是這種使用只是最粗淺的使用,更多時候反射的應用對我們來說是無感知的,如網絡同步,編輯器的屬性編輯等,都是建立在反射系統之上的,反射系統更多是一個基層系統,輔助構建其他高層次的系統。
2. 反射整體結構
UE 的反射系統其整體的結構如下:
總體來說,其各種結構對應收集不同類型的反射信息:
- UClass :收集類數據,描述一個類的成員變量,函數,父類等信息
- UEnum:收集枚舉數據
- UScriptStruct :收集結構體數據
- UFunction:收集函數信息
以 UClass 爲例,其採用FProperty
來儲存所有的簡單屬性信息(如Bool
,Int
),而一些複合類型數據則使用UField
存儲(如AActor
,TArray
)。這裏需要認識到:UClass 等反射結構其本質上只是描述一個類的結構,本身與業務類無實際耦合關係,每個標記了UCLASS(...)
宏的 class 都會有一個UClass* Object
儲存其反射信息。
3. 構建流程
從寫代碼的角度來說,我們只需要對變量,類等定義標註一個 宏,再 include 一個頭文件就完事了,具體構建的過程則是由 UE 的編譯工具去完成的。也就是 Unreal Build Tool(UBT) 和 Unreal Header Tool(UHT)。
接下來以前面的 class AWeapon
爲例,展示其自動生成的內容和如何初始化其反射信息。
[!note]
UHT 是一個用於預處理源代碼文件的工具,它可以識別UCLASS
、UFUNCTION
等宏,並通過生成額外的 C++ 代碼來擴展類的功能。UHT 還可以用於生成反射信息,例如類的元數據和屬性信息,以便在運行時進行藍圖交互等操作。UBT 是一個用於編譯和鏈接 UE4 項目的構建系統。它可以自動管理項目中的依賴項,並生成可執行文件和動態鏈接庫等二進制文件。UBT 還可以執行諸如打包、部署和測試等其他任務。
兩個工具在 UE4 開發中密切相關,因爲 UHT 生成的反射信息需要在 UBT 中使用,以便生成最終的可執行文件和動態鏈接庫。因此,在構建 UE4 項目時,UBT 將首先調用 UHT 來處理源代碼文件,然後使用生成的代碼來編譯和鏈接項目。
3.1 自動生成文件
在 [[原理#^644683|1.1 使用反射的準備工作]] 中,主要工作分爲兩步:
- 標註宏信息(如
UCLASS
,UFUNCTION
,UPROPERTY
) - 包含頭文件
#include ${filename}.generated.h
這裏頭文件是利用 UHT 工具掃描生成的,其附帶還會生成一個${filename}.gen.cpp
的源文件。這兩個文件主要負責兩件事情:
- 定義一個或多個輔助類(根據
UCLASS
和USTRUCT
等標註的結構數量),收集標註了宏信息的結構,該輔助類構造函數會返回一個構造好的UClass
- 定義一個
FCompileDeferInfo
靜態變量,其構造函數會在啓動時將輔助類的信息導入到一個全局的容器中,啓動時會遍歷這個容器,構建好 UClass 等反射信息。
其大致流程如下:
3.2 預生成代碼
接下來分析預先生成的 generated.h 和 gen.cpp 都做了什麼事情
一個 Class 需要註冊反射信息時,其使用方式如下(有一個必要的前提條件爲該 Class 的繼承鏈中需要有 UObject
):
#include "Weapon.generated.h" // 包含自動生成的頭文件信息
UCLASS() // 註冊類信息
class AWeapon : public AActor {
GENERATED_BODY() // 生成類輔助代碼
public:
UPROPERTY() // 註冊類屬性
FName WeaponName;
UFUNCTION() // 註冊類成員函數
void Fire();
}
可以看到其相關的宏主要有如下幾個:
- UCLASS
- GENERATED_BODY
- UPROPERTY
- UFUNCTION
這裏首先需要了解這些宏背後都做了什麼
3.2.1 宏展開
關鍵的宏定義如下:
/* 將 ABCD 4 個名稱鏈接起來*/
#define BODY_MACRO_COMBINE_INNER(A,B,C,D) A##B##C##D
#define BODY_MACRO_COMBINE(A,B,C,D) BODY_MACRO_COMBINE_INNER(A,B,C,D)
/* 拼接成另一個宏 */
#define UCLASS(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_PROLOG)
#define GENERATED_BODY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY);
/* 純標記,用給 UHT 掃描 */
#define UPROPERTY(...)
#define UFUNCTION(...)
以 3.2 的示例爲例,展開後內容大致如下:
UdemyProject_Source_UdemyProject_AWeapon_h_3_PROLOG // 註冊類信息
class AWeapon : public AActor {
UdemyProject_Source_UdemyProject_AWeapon_h_5_GENERATED_BODY // 生成類輔助代碼
public:
UPROPERTY() // 由於是標記,這裏展開之後是沒有特殊信息的
FName WeaponName;
UFUNCTION() // 由於是標記,這裏展開之後是沒有特殊信息的
void Fire();
}
可以看到展開後是一個個神祕的符號,其實這都是宏的名稱,其定義在自動生成的 generated.h 文件中。
這裏展示了一個特點,儘管不同的類都使用的相同的宏,但是 UHT 還是能保證掃描生成的文件信息唯一性。
這裏主要關注兩個宏:
- GENERATED_BODY_LEGACY
- GENERATED_BODY
接着展示一下兩個宏其對應的文件信息。
#define UdemyProject_Source_UdemyProject_AWeapon_h_3_PROLOG
#define UdemyProject_Source_UdemyProject_AWeapon_h_5_GENERATED_BODY_LEGACY \
PRAGMA_DISABLE_DEPRECATION_WARNINGS \
public: \
UdemyProject_Source_UdemyProject_AWeapon_h_5_PRIVATE_PROPERTY_OFFSET \
UdemyProject_Source_UdemyProject_AWeapon_h_5_SPARSE_DATA \
UdemyProject_Source_UdemyProject_AWeapon_h_5_RPC_WRAPPERS \
UdemyProject_Source_UdemyProject_AWeapon_h_5_INCLASS \
UdemyProject_Source_UdemyProject_AWeapon_h_5_STANDARD_CONSTRUCTORS \
public: \
#define UdemyProject_Source_UdemyProject_AWeapon_h_5_GENERATED_BODY \
PRAGMA_DISABLE_DEPRECATION_WARNINGS \
public: \
UdemyProject_Source_UdemyProject_AWeapon_h_5_PRIVATE_PROPERTY_OFFSET \
UdemyProject_Source_UdemyProject_AWeapon_h_5_SPARSE_DATA \
UdemyProject_Source_UdemyProject_AWeapon_h_5_RPC_WRAPPERS_NO_PURE_DECLS \
UdemyProject_Source_UdemyProject_AWeapon_h_5_INCLASS_NO_PURE_DECLS \
UdemyProject_Source_UdemyProject_AWeapon_h_5_ENHANCED_CONSTRUCTORS \
private: \
可以看到 GENERATED_BODY_LEGACY
和 GENERATED_BODY
的內容基本一致,查閱資料發現這主要是爲了前向兼容。因此可以先忽略 GENERATED_BODY_LEGACY
內容,關注 GENERATED_BODY
的內容。
可以看到 GENERATED_BODY
又嵌套了一堆宏(宏的定義在自動生成的 generated.h 頭文件),其展開之後纔是真正的代碼,比如
/* UFUNCTION Wrapper 函數 */
#define UdemyProject_Source_UdemyProject_AWeapon_h_5_RPC_WRAPPERS_NO_PURE_DECLS \
DECLARE_FUNCTION(execFire);
可以對其完整展開,還原其最終的樣貌
/* 該宏可以忽略 */
UdemyProject_Source_UdemyProject_AWeapon_h_5_GENERATED_BODY_LEGACY
class AWeapon : public AActor {
public:
/*
UdemyProject_Source_UdemyProject_AWeapon_h_5_RPC_WRAPPERS_NO_PURE_DECLS
UFunction 的 Wrapper Function 集合
*/
static void execFire( UObject* Context, FFrame& Stack, RESULT_DECL );
private:
static void StaticRegisterNativesAWeapon();
friend struct Z_Construct_UClass_AWeapon_Statics;
public:
/*
DECLARE_CLASS(AWeapon, AActor, COMPILED_IN_FLAGS(0 | CLASS_Config), CASTCLASS_None, TEXT("/Script/UdemyProject"), NO_API)
類輔助定義相關
*/
private: \
AWeapon& operator=(AWeapon&&); \
AWeapon& operator=(const AWeapon&); \
TRequiredAPI static UClass* GetPrivateStaticClass(); \
public: \
/** Bitwise union of #EClassFlags pertaining to this class.*/ \
enum {StaticClassFlags=COMPILED_IN_FLAGS(0 | CLASS_Config}; \
/** Typedef for the base class ({{ typedef-type }}) */ \
typedef AActor Super;\
/** Typedef for {{ typedef-type }}. */ \
typedef AWeapon ThisClass;\
/** Returns a UClass object representing this class at runtime */ \
inline static UClass* StaticClass() \
{ \
return GetPrivateStaticClass(); \
} \
/** Returns the package this class belongs in */ \
inline static const TCHAR* StaticPackage() \
{ \
return TEXT("/Script/UdemyProject"); \
} \
/** Returns the static cast flags for this class */ \
inline static EClassCastFlags StaticClassCastFlags() \
{ \
return CASTCLASS_None;
}
/** For internal use only; use StaticConstructObject() to create new objects. */
inline void* operator new(const size_t InSize, EInternal InInternalOnly, UObject* InOuter = (UObject*)GetTransientPackage(), FName InName = NAME_None, EObjectFlags InSetFlags = RF_NoFlags)
{
return StaticAllocateObject(StaticClass(), InOuter, InName, InSetFlags);
}
/** For internal use only; use StaticConstructObject() to create new objects. */
inline void* operator new( const size_t InSize, EInternal* InMem ) \
{
return (void*)InMem;
}
/* 序列化相關 */
friend FArchive &operator<<( FArchive& Ar, AWeapon*& Res )
{
return Ar << (UObject*&)Res;
}
friend void operator<<(FStructuredArchive::FSlot InSlot, AWeapon*& Res) \
{
InSlot << (UObject*&)Res;
}
/* 構造函數相關 */
/** Standard constructor, called after all reflected properties have been initialized */
NO_API AWeapon(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { };
private:
/** Private move- and copy-constructors, should never be used */
NO_API AWeapon(AWeapon&&);
NO_API AWeapon(const AWeapon&);
public:
/* 默認構造函數 */
NO_API AWeapon(FVTableHelper& Helper);
static UObject* __VTableCtorCaller(FVTableHelper& Helper)
{
return new (EC_InternalUseOnlyConstructor, (UObject*)GetTransientPackage(), NAME_None, RF_NeedLoad | RF_ClassDefaultObject | RF_TagGarbageTemp) AWeapon(Helper);
}
static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())AWeapon(X); }
public:
UPROPERTY() // 註冊類屬性
FName WeaponName;
UFUNCTION() // 註冊類成員函數
void Fire();
}
可以看到 GENERATED_BODY
宏爲 AWeapon
擴展了很多功能,包括但不限於:
- 增加了構造函數
- 增加了序列化功能
- UFunction 增加 Wrapper Function 以供調用
- 增加獲取當前父類以及當前類的 UClass 功能
3.2.2 gen.cpp 內容分析
gen.cpp 的內容主要爲構建好描述 AWeapon
反射信息的 UClass
UFUNCTION 相關代碼
首先以 AWeapon::Fire
爲例,對其標記 UFUNCTION
後檢查其生成的相關內容大致如下:
- 實現 Wrapper Function 的內容,這個接口主要供 藍圖 或者 RPC 使用。
DEFINE_FUNCTION(AWeapon::execFire)
{
P_FINISH;
P_NATIVE_BEGIN;
P_THIS->Fire(); // 實際上就是調用了下 Navtive 的 Fire 函數
P_NATIVE_END;
}
- 生成一個結構體
FFunctionParams
,儲存構建UFunction
所需的參數,並提供構建UFunction
的方法,參數內容主要分爲:
- 函數的標記(比如標記爲 Server 或者 Client 等)
- 函數的名稱
- 函數的參數和返回值(其統一用一個 List 存儲,每個元素會有一個 Flag 標記其是引用還是返回值還是普通參數)
- 參數的數量
/* 定義一個結構體,參數爲構建一個 UFunction 所需要的參數 */
struct Z_Construct_UFunction_AWeapon_Fire_Statics
{
static const UE4CodeGen_Private::FFunctionParams FuncParams;
};
/* 初始化一個結構體 */
const UE4CodeGen_Private::FFunctionParams Z_Construct_UFunction_AWeapon_Fire_Statics::FuncParams =
{ (UObject*(*)())Z_Construct_UClass_AWeapon,
nullptr,
"Fire",
nullptr,
nullptr,
0,
nullptr,
0,
RF_Public|RF_Transient|RF_MarkAsNative,
(EFunctionFlags)0x00020401,
0,
0, METADATA_PARAMS(Z_Construct_UFunction_AWeapon_Fire_Statics::Function_MetaDataParams, UE_ARRAY_COUNT(Z_Construct_UFunction_AWeapon_Fire_Statics::Function_MetaDataParams)) };
/* 生成一個構造方法,用來構造 AWeapon::Fire 的 UFunction 信息 */
UFunction* Z_Construct_UFunction_AWeapon_Fire()
{
static UFunction* ReturnFunction = nullptr;
if (!ReturnFunction)
{ UE4CodeGen_Private::ConstructUFunction(ReturnFunction, Z_Construct_UFunction_AWeapon_Fire_Statics::FuncParams);
} return ReturnFunction;
}
UPROPERTY 相關代碼
類似生成 UFunction
,此處由於 WeaponName
是基礎類型,所以直接初始化一個 FNamePropertyParams
的結構體。
這裏面就包含了:
- 變量的名稱
- 變量的 Flag(比如標記爲 Replicated)
- 變量的偏移(方便從類指針從偏移獲取該變量)
const UE4CodeGen_Private::FNamePropertyParams Z_Construct_UClass_AWeapon_Statics::NewProp_WeaponName = {
"WeaponName",
nullptr,
(EPropertyFlags)0x0010000000000000,
UE4CodeGen_Private::EPropertyGenFlags::Name,
RF_Public|RF_Transient|RF_MarkAsNative,
1,
STRUCT_OFFSET(AWeapon, WeaponName),
METADATA_PARAMS(Z_Construct_UClass_AWeapon_Statics::NewProp_WeaponName_MetaData,
UE_ARRAY_COUNT(Z_Construct_UClass_AWeapon_Statics::NewProp_WeaponName_MetaData)) };
UCLASS 相關代碼
前面定義的函數和成員變量的代碼都已經生成完畢了,接下來看具體是如何將其結合到 Class 中的。
首先 gen.cpp 中會生成代碼將 Function 和 Property 分開存儲,定義如下:
/** 成員變量 **/
const UE4CodeGen_Private::FPropertyParamsBase* const Z_Construct_UClass_AWeapon_Statics::PropPointers[] = {
(const UE4CodeGen_Private::FPropertyParamsBase*)&Z_Construct_UClass_AWeapon_Statics::NewProp_WeaponName,
};
/** 成員函數 **/
const FClassFunctionLinkInfo Z_Construct_UClass_AWeapon_Statics::FuncInfo[] = {
{ &Z_Construct_UFunction_AWeapon_Fire, "Fire" }, // 2996945510
};
接着提供構建 AWeapon
的 UClass
信息,類似構建 UFunction
一般,其填充了一個 FClassParams
的結構體,主要內容包括但不限於:
- 成員變量列表
- 函數列表
- 類標記(即 UCLASS 宏中標記)
const UE4CodeGen_Private::FClassParams Z_Construct_UClass_AWeapon_Statics::ClassParams = {
&AWeapon::StaticClass,
"Engine",
&StaticCppClassTypeInfo,
DependentSingletons,
FuncInfo,
Z_Construct_UClass_AWeapon_Statics::PropPointers,
nullptr,
UE_ARRAY_COUNT(DependentSingletons),
UE_ARRAY_COUNT(FuncInfo),
UE_ARRAY_COUNT(Z_Construct_UClass_AWeapon_Statics::PropPointers),
0,
0x008000A4u,
METADATA_PARAMS(Z_Construct_UClass_AWeapon_Statics::Class_MetaDataParams, UE_ARRAY_COUNT(Z_Construct_UClass_AWeapon_Statics::Class_MetaDataParams))
};
然後提供一個構建 UClass
的接口
UClass* Z_Construct_UClass_AWeapon()
{
static UClass* OuterClass = nullptr;
if (!OuterClass)
{ UE4CodeGen_Private::ConstructUClass(OuterClass, Z_Construct_UClass_AWeapon_Statics::ClassParams);
} return OuterClass;
}
至此,整個類的自動生成的反射代碼基本描述完了。
3.2.3 小結
3.2 主要闡述自動生成的代碼內容大致是什麼東西,個人認爲主要分爲如下幾點:
- 爲
AWeapon
增加輔助接口(比如 Super,StaticClass,構造函數等) - 生成
AWeapon
中所有標記了UPROPERTY
和UFUNCTION
的反射代碼和構建接口 - 生成
AWeapon
這個 Class 的反射代碼和構建接口
最後將接口暴露出去給引擎初始化調用即可。
3.3 初始化反射信息
3.2 中預生成的代碼已經封裝好所有反射結構的接口了,接下來只要調用就可以生成 AWeapon
的反射信息了。
3.3.1 入口調用
UE 中反射信息主要是在引擎啓動時初始化的,主要利用 gen.cpp 中自動生成的一個靜態變量
static FCompiledInDefer Z_CompiledInDefer_UClass_AWeapon(Z_Construct_UClass_AWeapon, &AWeapon::StaticClass, TEXT("/Script/UdemyProject"), TEXT("AWeapon"), false, nullptr, nullptr, nullptr);
其構造函數會將 構造 AWeapon
的 反射接口傳入到一個全局容器,啓動時會調用 UObjectLoadAllCompiledInDefaultProperties
遍歷構造好 UClass。
大致僞代碼如下:
// DeferredCompiledInRegistration 存儲了 Z_Construct_UClass_AWeapon
static void UObjectLoadAllCompiledInDefaultProperties(){
TArray<UClass* (*)()> PendingRegistrants = MoveTemp(DeferredCompiledInRegistration);
for (UClass* (*Registrant)() : PendingRegistrants)
{
// 此處調用 Registrant,也就會調用 Z_Construct_UClass_AWeapon
UClass* Class = Registrant();
/* 省略一些代碼 */
NewClasses.Add(Class);
}
}
3.3.2 構建反射信息
AWeapon 反射信息的構建入口如下:
UClass* Z_Construct_UClass_AWeapon()
{
static UClass* OuterClass = nullptr;
if (!OuterClass)
{
UE4CodeGen_Private::ConstructUClass(OuterClass, Z_Construct_UClass_AWeapon_Statics::ClassParams);
}
return OuterClass;
}
即使有多個 AWeapon 對象也是共用一個 UClass 來描述反射信息。其具體的調用鏈如下(下面的 AWeapon 可替換爲任意自定義的 Class):
4. QA
4.1 如何利用 UClass 構建一個對象
以 SpawnActor 爲例,其接口格式如下:
AActor* UWorld::SpawnActor( UClass* Class, FVector const* Location, FRotator const* Rotation, const FActorSpawnParameters& SpawnParameters )
UClass*
參數可以通過如 AWeapon::StaticClass()
或者 TSubClassOf<AWeapon>()
獲取,核心調用鏈如下:
- 準備構建參數,檢查
SpawnParameters.template
,如果不存在則使用 CDO (每個 UClass 創建時會有對應描述的 Class 的 Default Object,可以認爲是調用了 Class 的默認構造函數構建出來的) - 調用 NewObject
- StaticConstructObject_Internal
- StaticAllocateObject
- 檢查對象是否已經存在
- 不存在則調用 AllocateUObject 分配一個 UObject
- 調用
UClass->ClassConstructor
在 UObject 上構建對應類
- 返回 Actor
4.2 UClass 如何獲取描述類的構造函數
4.1 中說到,UClass
是利用 ClassConstructor
來構建對應描述的 Class 對象的,ClassConstructor
初始化的時機在於構建 UClass
。UClass
的構建通過調用 TClass::StaticClass
,具體執行流程參考 [[Pasted image 20230329232659.png|3.3.2]] 中第二步初始化 UClass。
其具體初始化方式便是通過宏 DECLARE_CLASS
和 IMPLEMENT_CLASS
來生成相應代碼並將其傳入到構建 UClass 的一環中。
4.3 UFunction 如何存儲參數及返回值
回顧類圖。
UFunction 的所有參數和返回值都存儲在父類 UStruct::PropertyLink
,這是一個鏈表結構,元素類型爲 FProperty
,通過遍歷並且做標記比對來判斷 Property 是參數還是返回值,以獲取返回值爲例,其操作如下:
/** 獲取 UFunction 返回值 **/
FProperty* UFunction::GetReturnProperty() const
{
for( TFieldIterator<FProperty> It(this); It && (It->PropertyFlags & CPF_Parm); ++It )
{
if( It->PropertyFlags & CPF_ReturnParm )
{
return *It;
}
}
return NULL;
}
4.4 UFunction 的執行
首先在 UE 中,粗分下來有兩種函數:
- 藍圖函數
- C++ 函數
UE 中用了一個FUNC_Native
標記來區分,Native 函數是 C++ 函數,非 Native 函數則是藍圖函數。當執行 UFunction 時,需要調用UFunction::Invoke
接口。接口會調用UFunction::Func
函數指針。當 UFunction 類型爲 Native 時,Func 指向實際調用的函數,反之 Func 則指向UObject::ProcessInternal
。
藍圖函數的調用原理涉及到藍圖虛擬機,在[[藍圖與 CPP 之間相互調用|藍圖篇]]做補充。
4.5 RPC 函數如何執行的
這裏以純 C++ 實現武器開火爲例,開火顯然是一個需要服務器認證的 Function,爲了能夠在客戶端上調用,服務器上執行,需要加上 Server 標記
#include "Weapon.generated.h" // 包含自動生成的頭文件信息
UCLASS() // 註冊類信息
class AWeapon : public AActor {
GENERATED_BODY()
public:
UFUNCTION(Server) /* client 調用,Server 執行 */
void Fire(); /* 定義時只需要定義 Fire_Implementation */
}
接着需要在 Weapon.cpp 中定義 void Fire_Implementation()
接口,此接口爲服務器收到請求後執行的接口。 在調用開火時,只需要如下操作,就可以從 client 調用到 server 的 fire 函數:
AWeapon* Weapon = GetWeapon();
Weapon->Fire();
這裏的原理是 UHT 在對 RPC 函數會在 gen.cpp 中額外生成一個新的函數定義,格式如下:
/* gen.cpp */
void AWeapon::Fire()
{
ProcessEvent(FindFunctionChecked(NAME_AWeapon_Fire),NULL);
}
UObject::ProcessEvent
接口會調用 UObject::CallRemoteFuntion
將請求發送到服務器,服務器接受到請求後再利用反射查詢要執行的函數名稱和對象,再對其進行執行。
/* gen.cpp */
// 函數名稱及執行函數關聯起來
static const FNameNativePtrPair Funcs[] = {
{"Fire", &AWeapon::execFire},
}
// 服務器執行的函數定義
DEFINE_FUNCTION(AWeapon::execFire)
{
P_FINISH;
P_NATIVE_BEGIN;
P_THIS->SpawnHero13();
P_NATIVE_END;
}
其執行流程大致如下: