虛幻引擎中的反射(譯)

原文鏈接:https://www.unrealengine.com/en-US/blog/unreal-property-system-reflection?sessionInvalidated=true

反射是程序的一種能力斜棚,借助于它可以在運行時查看自身。作為虛幻引擎中的基礎技術兽狭,它相當有用抛猖,增強了眾多的系統(tǒng)比如編輯器中的屬性面板,對象序列化救赐,垃圾回收智政,網(wǎng)絡對象傳輸以及藍圖腳本和C++之間的通信等掏缎。不過C++語言本身并不提供任何形式的反射,因此虛幻引擎實現(xiàn)了一套自己的反射系統(tǒng)衫冻,通過它來收集诀紊,查詢和修改C++中的類,結構隅俘,函數(shù)邻奠,成員變量和枚舉的信息。在本文中我們提到反射通常是指屬性系統(tǒng)为居,而不是圖形學中的概念碌宴。

反射系統(tǒng)是可選的。如果你希望某些類型或者屬性對反射系統(tǒng)可見蒙畴,那么就必須給它們加上修飾宏贰镣,這樣在編譯工程時Unreal Header Tool (UHT) 才會去收集這些信息呜象。

標示
為了標示一個頭文件包含了反數(shù)據(jù)類型,我們需要在文件的頭部包含一個特殊的文件碑隆。UHT會識別出這個文件需要處理恭陡,并且也會為該頭文件加上反射系統(tǒng)的實現(xiàn)代碼(更多的介紹請參見“反射的實現(xiàn)原理”)。示例如下:

#include "FileName.generated.h"

這時你就可以使用UENUM()上煤, UCLASS()休玩, USTRUCT(), UFUNCTION()劫狠, 以及 UPROPERTY()來修飾頭文件中不同的類和類成員了拴疤。這幾個宏必須加在類和成員聲明的前面,另外還可以加上一些額外的特殊關鍵字独泞。讓我們來看看一個來自實際項目例子(來自StrategyGame):

//////////////////////////////////////////////////////////////////////////
// Base class for mobile units (soldiers)

#include "StrategyTypes.h"
#include "StrategyChar.generated.h"

UCLASS(Abstract)

class AStrategyChar : public ACharacter, public IStrategyTeamInterface
{

    GENERATED_UCLASS_BODY()

    /** How many resources this pawn is worth when it dies. */
    UPROPERTY(EditAnywhere, Category=Pawn)
    int32 ResourcesToGather;

    /** set attachment for weapon slot */
    UFUNCTION(BlueprintCallable, Category=Attachment)
    void SetWeaponAttachment(class UStrategyAttachment* Weapon);

    UFUNCTION(BlueprintCallable, Category=Attachment)
    bool IsWeaponAttached();

    protected:

    /** melee anim */
    UPROPERTY(EditDefaultsOnly, Category=Pawn)
    UAnimMontage* MeleeAnim;

    /** Armor attachment slot */
    UPROPERTY()
    UStrategyAttachment* ArmorSlot;

    /** team number */
    uint8 MyTeamNum;

    [more code omitted]
};

這個頭文件聲明了一個繼承自ACharacter的類AStrategyChar遥赚。UCLASS()來指定該類具有反射特性。與UCLASS()對應的阐肤,我們還在類定義內部插入了GENERATED_UCLASS_BODY() 宏。對于想要加入反射的類和結構體中讲坎,GENERATED_UCLASS_BODY() / GENERATED_USTRUCT_BODY()是必須的孕惜。通過這兩個宏,我們給類和結構體注入了實現(xiàn)反射所必須的額外函數(shù)和類型信息晨炕。

在代碼中衫画,第一個反射屬性是ResourcesToGather。它被指定為EditAnywhere 和Category=Pawn瓮栗。這意味著這個屬性可以在任意屬性面板里編輯削罩,并且屬于Pawn這個類別。此外好幾個函數(shù)指定了BlueprintCallable和一個類別费奸,這意味這些函數(shù)都可以在Blueprints里被調用弥激。

如MyTeamNum聲明所示,在同一個類中混雜反射成員和非反射成員是沒有問題的愿阐,只是要注意的是非反射成員對于所有基于反射的系統(tǒng)來說都是不可見的(比如緩存一個非反射的UObject指針通常來說是危險的微服,因為垃圾回收器并不知道你引用了它)。

你可以在ObjectBase.h里找到每一個說明符關鍵字(比如EditAnywhere和BlueprintCallable)的一段簡短注釋和用法說明缨历。如果你不知道某個關鍵字是起做什么用的以蕴,按快捷鍵Alt+G會跳轉到ObjectBase.h中對應的定義上去了(這些不是真的C++關鍵字,但是智能提示和VAX看起來不關心也分不清它們間的區(qū)別)辛孵。

更多的信息可以參見官網(wǎng)上的Gameplay Programming Reference丛肮。

局限
UHT并不是一個真正的C++解析器。它可以識別語言的常見子集魄缚,并且在解析時盡可能多的跳過任何它認為不相關的代碼宝与;同時僅關注反射的類,函數(shù)和屬性。盡管這樣伴鳖,某些情況下它還是會出錯的节值,所以當往一個已有的頭文件里加上反射類型時,你可能要重寫一些代碼榜聂,或者要把已有的代碼包在#if CPP / #endif里搞疗。你應該盡量避免把反射宏修飾過的屬性或者函數(shù)包在 #if/#ifdef (WITH_EDITOR 和WITH_EDITORONLY_DATA除外)里,這是因為在某些構建配置里這些宏定義有可能不是true的须肆,那么在生成的代碼里去引用這些屬性或者函數(shù)時就會報編譯錯誤了匿乃。

對于虛幻引擎的反射來說,C++中絕大多數(shù)的數(shù)據(jù)類型都是支持的豌汇,但是并不是所有(特別是只有少數(shù)模版類型是支持的幢炸,比如TArray和TSubclassOf,并且它們的模版參數(shù)不能是嵌套類型)拒贱。如果你使用反射宏來修飾無法在運行時表示的數(shù)據(jù)類型宛徊,UHT會給你報一個描述性質的錯誤信息。

使用反射信息
雖然大部分的游戲代碼在運行時使用反射系統(tǒng)給予的便利的同時逻澳,可以忽略反射系統(tǒng)闸天,但是當你編寫工具或者玩法系統(tǒng)時會發(fā)現(xiàn)反射還是很有用的。

反射系統(tǒng)的類型層級如下所示:

UField
    UStruct
        UClass (C++ class)
        UScriptStruct (C++ struct)
        UFunction (C++ function)

    UEnum (C++ enumeration)

    UProperty (C++ member variable or function parameter)

(更多不同類型的子類)

UStruct是聚合類結構(任何包含了其他成員的類型斜做,比如C++類苞氮,結構,或者函數(shù))的基礎類型瓤逼,不要把它和C++的結構混淆起來(與它對應的是UScriptStruct)笼吟。UClass可以包含函數(shù)或者屬性作為它的成員,但UFunction和UScriptStruct只能局限于屬性霸旗。

通過UTypeName::StaticClass() 或者 FTypeName::StaticStruct()贷帮,你可以獲取某個反射C++類型的UClass 或UScriptStruct修飾;對于UObject實例來說诱告,你可以通過Instance->GetClass()獲得它的類型(因為結構體是沒有共同的基類也沒有反射機制所需的存儲空間皿桑,所以是無法獲得它的實例類型的)。

為了遍歷一個UStruct所有的成員蔬啡,你可以用一個TFieldIterator實例:

for (TFieldIterator<UProperty> PropIt(GetClass()); PropIt; ++PropIt)
{
    UProperty* Property = *PropIt;
    // Do something with the property
}

TFieldIterator的模版參數(shù)用作過濾器(通過該參數(shù)你可以使用UField來同時查看屬性和函數(shù)诲侮,或者其中任意一個)。迭代器構造函數(shù)的第二個參數(shù)用來指定是否只需訪問該類或者結構體中的屬性/函數(shù)箱蟆,還是也同時訪問基類/結構(默認)沟绪;這個參數(shù)取值不會對函數(shù)有任何的影響。

每個類型都有一組唯一的標志位(EClassFlags + HasAnyClassFlags, etc…)和一個繼承自UField的通用元數(shù)據(jù)存儲系統(tǒng)空猜。反射宏中的說明符關鍵字可以作為標志位存儲也可以作為元數(shù)據(jù)存儲绽慈,取決于它們是被用于游戲運行時恨旱,還是只是在編輯器里。這樣就可以實現(xiàn)在游戲運行時去除僅在編輯器用的元數(shù)據(jù)來達到節(jié)省內存的目的坝疼,而作為標志位存儲的則一直可用搜贤。

你可以通過應用反射數(shù)據(jù)來實現(xiàn)不同的功能(比如枚舉屬性,按數(shù)據(jù)驅動的方式獲取/設置屬性钝凶,調用反射方法仪芒,甚至創(chuàng)建新實例);與其深入了解每個反射特性耕陷,不如瀏覽一下UnrealType.h 和 Class.h掂名,然后跟蹤調試一個和你要做的功能相近的樣例來得更容易一些。

反射的實現(xiàn)原理
如果你僅僅只是想使用反射系統(tǒng)而已哟沫,那么可以毫不猶豫的跳過這一部分饺蔑;但是知道它是怎么工作的可以讓你在使用時更好的做出決定并了解它的局限性。

Unreal Build Tool (UBT) 和 Unreal Header Tool (UHT)在實現(xiàn)運行時的反射功能中扮演著核心的角色嗜诀。UBT的工作就是掃描頭文件猾警,如果一個頭文件包含至少一個反射類型則記錄該頭文件所在的模塊。如果這些頭文件在編譯之后發(fā)生改變隆敢,UHT就會被喚起收集并更新對應的反射數(shù)據(jù)发皿。UHT解析頭文件,創(chuàng)建反射數(shù)據(jù)集合筑公,然后生成包含反射數(shù)據(jù)(包含在每個模塊都有的.generated.inl里)以及各類輔助類和函數(shù)(包含在每個頭文件對應的.generated.h里)的C++代碼。

之所以通過生成的C++代碼來保存反射信息尊浪,一個主要的好處是這樣可以確保這些信息和最終的二進制文件保持同步匣屡。把反射信息和引擎代碼一起編譯,并在啟動時通過C++表達式來計算成員的偏移等信息拇涤,而不是逆向工程某個特定的平臺/編譯器/優(yōu)化選項的組合捣作,這樣你永遠都不會加載到錯誤的反射信息。UHT作為一個獨立的程序鹅士,它不會修改任何生成的頭文件券躁,這樣就避免了在UE3腳本編譯中經常被抱怨的先有蛋還是先有雞這樣的問題。

生成的方法包括像StaticClass() / StaticStruct()掉盅,方便你獲得某個類型反射數(shù)據(jù)的類型也拜,生成的代碼段則方便你在Blueprints或者網(wǎng)絡傳輸中調用。這些東西必須作為類或者結構體的一部分來聲明趾痘,這就是為什么GENERATED_UCLASS_BODY() 或GENERATED_USTRUCT_BODY()宏要包含在反射類型里慢哈,以及頭文件中需加入包含定義這些宏的代碼#include “TypeName.generated.h” 的原因。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末永票,一起剝皮案震驚了整個濱河市卵贱,隨后出現(xiàn)的幾起案子滥沫,更是在濱河造成了極大的恐慌,老刑警劉巖键俱,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兰绣,死亡現(xiàn)場離奇詭異,居然都是意外死亡编振,警方通過查閱死者的電腦和手機缀辩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來党觅,“玉大人雌澄,你說我怎么就攤上這事”埃” “怎么了镐牺?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長魁莉。 經常有香客問我睬涧,道長,這世上最難降的妖魔是什么旗唁? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任畦浓,我火速辦了婚禮,結果婚禮上检疫,老公的妹妹穿的比我還像新娘讶请。我一直安慰自己,他們只是感情好屎媳,可當我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布夺溢。 她就那樣靜靜地躺著,像睡著了一般烛谊。 火紅的嫁衣襯著肌膚如雪风响。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天丹禀,我揣著相機與錄音状勤,去河邊找鬼。 笑死双泪,一個胖子當著我的面吹牛持搜,可吹牛的內容都是我干的。 我是一名探鬼主播焙矛,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼朵诫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了薄扁?” 一聲冷哼從身側響起剪返,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤废累,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后脱盲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體邑滨,經...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年钱反,在試婚紗的時候發(fā)現(xiàn)自己被綠了掖看。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡面哥,死狀恐怖哎壳,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情尚卫,我是刑警寧澤归榕,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站吱涉,受9級特大地震影響刹泄,放射性物質發(fā)生泄漏。R本人自食惡果不足惜怎爵,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一特石、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鳖链,春花似錦姆蘸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至题山,卻和暖如春兰粉,著一層夾襖步出監(jiān)牢的瞬間故痊,已是汗流浹背顶瞳。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留愕秫,地道東北人慨菱。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像戴甩,于是被迫代替她去往敵國和親符喝。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內容

  • 在瑜伽中甜孤,以蓮花姿勢靜坐协饲,氣定凝神畏腕,心中摒除雜念,靜若清池茉稠,不是件容易的事描馅,而更不容易的是以頭倒立的姿勢靜心。 很...
    袁一今閱讀 1,342評論 6 12
  • 在自由操作時間里龍龍取的工作是“植物生長過程”龍龍在操作這份工作時表現(xiàn)的很熟練哦而线,在和卡片配對的時候龍龍能準確的把...
    a81c671c0ae2閱讀 401評論 0 0
  • 1铭污、混淆平臺型商業(yè)模式和組織模式“蚶海互聯(lián)網(wǎng)企業(yè)中絕大部分依然是由“初創(chuàng)期的創(chuàng)業(yè)熱情”和“互聯(lián)網(wǎng)時代造就的紅利”支撐嘹狞,...
    花得一閱讀 296評論 0 0
  • 愛徒 王正華(四.5) 媽媽去年買了一盆蘭花,剛買來時誓竿,我還以為那是一棵韭菜:它葉片細細的磅网,又濃又綠,還有些發(fā)黑烤黍,...
    從源王有鵬閱讀 242評論 0 1