藍圖跟C++交互
概述
藍圖可以繼承C++類,從而使得程序員可以在代碼中創建新的游戲性類,而關卡設計人員可以使用藍圖來繼承該類並對其進行修改。 有很多種修飾符可以改變C++類和藍圖系統間交互方式,其中某些修飾符會在本示例中突出介紹。
可以通過查看以下內容來快速了解:
- 虛幻引擎快速入門視頻教程第五章,見引用[1]
- 官方文檔
類設置
在類設置的第一部分中,使用C++類向導創建一個名稱為LightSwitchBoth 的類。
LightSwitchBoth類中的大部分代碼設置都和 僅使用C++的LightSwitch示例 類似。盡管您可以讓一個藍圖繼承LightSwitchCodeOnly類,但藍圖圖表並不能訪問該類中創建的組件、屬性及函數。該示例將使用 UPROPERTY() 和 UFUNCTION() 修飾符,這兩個修飾符使得LightSwitchBoth作為繼承它的藍圖的模板。
這個頭文件是從 僅使用C++的LightSwitch示例 改編而來,添加了以下功能:
- PointLightComponent和SphereComponent是BlueprintReadOnly(僅藍圖可讀的),並且將顯示在 我的藍圖 選卡中的 Switch Components(切換組件) 類目中。
- OnOverlap現在是一個BlueprintNativeEvent,將會顯示在 我的藍圖 選卡中的 Switch Functions(切換函數) 類目中。
- DesiredBrightness是BlueprintReadWrite(藍圖可讀寫的),將顯示在 我的藍圖 選卡中的 Switch Properties(切換屬性) 類目中。
- DesiredBrightness現在是EditAnywhere(隨處可編輯的),而不是VisibleAnywhere(隨處可見的)。
在LightSwitchBoth的源文件中,構造器仍然是一樣的。但是,需要對 OnOverlap 函數做一點修改。這個函數現在是一個BlueprintNativeEvent。這意味着 在繼承這個類的藍圖中,可以放置一個覆蓋 OnOverlap 的事件,當正常調用該函數時會執行此事件。如果該事件不存在,那么則是執行那個函數的 C++實現。要想使這個設置正常工作,該C++函數需要重命名為 OnOverlap_Implementation 。稍后在本示例中將介紹這個藍圖設置。對 OnOverlap 函數 進行了修改后,LightSwitchBoth 的源文件如下所示:
當創建類時,新的 UCLASS() 、UFUNCTION() 或 UPROPERTY() 宏意味着該代碼必須在Visual Studio 或Xcode中進行編譯,然后使用虛幻編輯器重新加載它們。 關閉虛幻編輯器,在Visual Studio 或Xcode中編譯該項目,然后打開編輯器並重新加載該項目,以確保正確地重新加載該游戲模塊。
當重新打開虛幻編輯器並重新打開您的項目后,便可以創建一個新的類藍圖了。 在本示例中,選擇LightSwitchBoth作為該藍圖的父類,藍圖名稱為 LightSwitchBoth_BP 。
在 C++代碼中添加的PointLightComponent和SphereComponent 也會顯示在 Blueprint Editor(藍圖編輯器) 的 組件模式中的 Components(組件) 選卡內。 它們的圖標是深藍色,表示它們是從父類LightSwitchBoth類繼承而來的原生組件。而剛剛添加到 LightSwitchBoth_BP 藍圖中的新組件的圖標 是淺藍色的
在 Graph Mode(圖標模式) 中, My Blueprint(我的藍圖) 選卡顯示了在C++中添加到LightSwitchBoth類中的 PointLightComponent 和SphereComponent 。這是因為 BlueprintReadOnly 修飾符存在的緣故。 通過在 我的藍圖 選卡中點擊並拖拽這些組件的名稱到圖表中,可以將這些組件的節點添加到圖表中。然后,您可以把這些節點連接到改變像可見性 或光源顏色這樣屬性的節點上。DesiredBrightness 屬性也會出現在 我的藍圖 選卡中。因為它是一個屬性,而不是一個組件,所以可以使用 BlueprintReadWrite 修飾符。這意味着在藍圖圖表中可以創建節點來獲取及設置 DesiredBrightness 的值。
有兩個圖表用於設置 LightSwitchBoth_BP 類的行為。第一個是構造腳本圖表,它包含了一個專用的 Construction Script(構建腳本)事件。如果沒有該 Construction Script 設置,那么新的 LightSwitchBoth_BP Actor 將僅使用LightSwitchBoth的構造函數。然而,當Actor在關卡中移動時,及當 Desired Brightness 發生改變時,都會執行 Construction Script 。使用 Construction Script 意味着,可以輕松地改變暴露給藍圖的Actor屬性, 並且可以快速地看到這些修改的效果。
在 LightSwitchBoth_BP 類中, Construction Script 事件連接到了 Set Brightness 節點上,以便當在關卡中添加或移動Actor時或者 Desired Brightness 發生改變時,將 Point Light 1 (PointLightComponent) 的亮度設置為 Desired Brightness 的值。
在 LightSwitchBoth_BP 類中, Construction Script 事件連接到了 Set Brightness 節點上,以便當在關卡中添加或移動Actor時或者 Desired Brightness 發生改變時,將 Point Light 1 (PointLightComponent) 的亮度設置為 Desired Brightness 的值。
LightSwitch_BPOnly 類中設置的另一個圖表是 事件圖表 。EventGraph的執行是由事件觸發的。在這個示例中, 任何時候當調用C++函數 OnOverlap 時, OnOverlap 就會執行。在LightSwitchBoth的源文件中,設置了代理,以便當一個Actor進入或離開SphereComponent時會執行 OnOverlap 。
在變量的設置中, DesiredBrightness 變量設置為 EditAnywhere (隨處可編輯) ,所以在 藍圖編輯器 的默認模式中它是可見的,並且可以進行編輯。 這意味着對於類的每個實例,這個變量是可以變化的,所以每個Actor可以有其自己的 DesiredBrightness 。因為 DesiredBrightness 也是 BlueprintReadWrite(藍圖可讀寫) 的,且 Construction Script 中使用了它,所以更新它還會導致再次執行 Construction Script 。
其他Class Blueprints(類藍圖)可以繼承由藍圖創建的類,通過以下兩種方式實現:使用 Class Viewer(類別查看器) 中的類附近的下拉列表按鈕來創建一個新藍圖, 或者通過右擊該藍圖並選擇 Create New Blueprint Based on This(基於此藍圖創建一個新藍圖) 。
藍圖函數庫
有時候有一些幫助函數,我們需要在藍圖中使用,這時候我們可以創建一個藍圖函數庫。它是一些靜態函數的集合。
創建藍圖庫跟使用UFUNCITON()暴露一些函數給藍圖很相似。只需要繼承UBlueprintFunctionLibrary即可。它們也應該包含靜態函數。
下面是StartSession的實現
編譯后,就可以在藍圖內里面調用這些方法了。
屬性修飾符
此處只列舉了一些常用的屬性,具體參照官方文檔
- AdvancedDisplay
- AssetRegistrySearchable
- BlueprintAssignable僅能用於Multicast代理。應顯示該屬性,以供在藍圖中分配。
- BlueprintCallable僅能用於Multicast代理。應顯示該屬性,以在藍圖代碼中調用。
- BlueprintReadOnly 只讀
- BlueprintReadWrite 讀寫
- Category定義屬性的分類
- Config
- Const
- DuplicateTransient
- EditAnywhere表示該屬性可從編輯器內的屬性窗口編輯。
- EditDefaultsOnly表示該屬性可通過屬性窗口來編輯,但僅能對原型編輯。
- EditFixedSize
- EditInline
- EditInstanceOnly
- …
函數修飾符
函數聲明
UFUNCTION([specifier, specifier, ...], [meta(key=value, key=value, ...)])
ReturnType FunctionName([Parameter, Parameter, ...])
函數修飾符
在聲明函數時,聲明上可添加修飾符以控制引擎和編輯器的不同方面的屬性表現。
- BlueprintAuthorityOnly
- BlueprintCallable 該函數可在藍圖或關卡藍圖圖表內執行。
- BlueprintCosmetic 此函數為修飾函數而且無法運行在專屬服務器上
- BlueprintImplementableEvent此函數可以在藍圖或關卡藍圖圖表內進行重載。
- BlueprintNativeEvent 此函數將由藍圖進行重載,但同時也包含native類的執行。提供一個名稱為[FunctionName]_Implementation的函數本體而非[FunctionName];自動生成的代碼將包含轉換程序,此程序在需要時會調用實施方式。
- BlueprintPure 此函數不會以任何方式影響其從屬對象,並且可在藍圖或關卡藍圖圖表中執行。
- Category 當在藍圖編輯工具中顯示時,定義函數的分類。
- Client此函數僅在該函數從屬對象所從屬的客戶端上執行。提供一個名稱為[FunctionName]_Implementation的函數主體,而不是[FunctionName]; 自動生成的代碼將包含一個轉換程序來在需要時調用實現方法。
- CustomThunk UnrealHeaderTool(虛幻頭文件工具)的代碼生成器將不會為此函數生成execFoo轉換程序; 可由用戶來提供
- Exec此函數可從游戲中的控制台中執行。Exec命令僅在特定類中聲明時才產生作用。
- NetMulticast無論Actor的NetOwner為何值,此函數都會在服務器上被本地執行且將被復制到所有的客戶端
- Reliable此函數在網絡間進行復制,並會忽略帶寬或網絡錯誤而被確保送達。僅在與客戶端或服務器共同使用時可用
- Server此函數僅在服務器上執行。提供一個名稱為[FunctionName]_Implementation的函數主體,而不是[FunctionName]; 自動生成的代碼將包含一個轉換程序來在需要時調用實現方法。
- Unreliable此函數在網絡間復制,但可能會由於帶寬限制或網絡錯誤而傳送失敗。僅在與客戶端或服務器一起使用時有效。
類修飾符
在聲明類時,聲明上可添加修飾符以控制引擎和編輯器的不同方面的類表現。
有太多修飾符,此處僅列舉一些常用的,其它請參考官方文檔。
- Blueprintable指定該類為創建藍圖的可接受基類。除非被繼承,否則默認值為NotBlueprintable。它由子類繼承。
- BlueprintType此類可作為藍圖中的一種變量類型使用
- Abstract Abstract 類修飾符將類聲明為"抽象基類",這樣會阻止用戶在虛幻編輯器中向這個世界中添加這個類的Actor,或者在游戲過程中創建這個類的實例。
- Deprecated該類已被廢棄,並且該類的對象在序列化時將不會被保存。該標識由子類繼承。
- …
元數據修飾符
在聲明類、函數和接口時,聲明上可添加元數據修飾符以控制其在引擎和編輯器的不同方面的表現。
對元數據修飾符的使用按常規類、函數和接口修飾符而不同。
類元數據修飾符
- BlueprintSpawnableComponent
函數元數據修飾符
- BlueprintInternalUseOnly
- BlueprintProtected
- DeprecatedFunction
- DeprecationMessage
- UnsafeDuringActorConstruction
接口元數據修飾符
- CannotImplementInterfaceInBlueprint
藍圖技術指南
藍圖編程指南
當決定使用C++或者藍圖時,有兩個主要考慮的因素
- 速度
- 表達式復雜度
除了這兩個因素外,其它的來自游戲的復雜程序以及團隊的組成。如果你有比程序員更多的設計師,那么藍圖代碼將會比C++代碼多很多。相反,如果你有更多的程序員,那么他們可能更傾向於使用C++。我們(Epic)期望在這中間有一個平衡點。在Epic,大多工作流程是這樣的,內容創建者會做一個非常復雜的藍圖,程序員通過C++編寫一個新的藍圖節點來把前面所創建的藍圖的大部分轉成C++代碼,他把那部分功能轉成了C++函數。一個好的經驗是大量使用藍圖,然后把它轉換成C++代碼,當它們達到一定復雜度的時候,它需要一個關於該功能一個更精確的表達式(或者對於一個非程序員太復雜時),或者執行速度過慢需要轉換成C++時。
速度
就速度來說,藍圖肯定會比C++慢(8~10倍見上方引用部分),並不是說性能就非常差,但是如果你做的事情需要很多的計算,或者執行頻率非常高,那么最多是使用C++而不是藍圖。然而,很好地結合兩者來為你們團隊很好的工作和取得一個不錯的性能是很有可能的。如果一個藍圖類有很多功能,那么你可以把一些功能轉換成C++代碼來加速,但是保留其它的部分來保證靈活性。如果你的性能分析發現藍圖中的某個操作很耗時,那么可以把那部分轉移到C++,其它的保留在藍圖中。
假如要使用藍圖來控制幾千個Actor,那么這種情況下就最好用C++來處理決策、尋路等,然后把一個可調節的屬性和控制函數暴露給藍圖。
復雜度
就表達式復雜來說,有些東西用C++來做確實比藍圖容易。藍圖在很多方面做的很好,但是有些東西在藍圖結點內卻並不好表達。比如操作大量數據,字符串操作,大量數據的數學操作等都很復雜,且在一個可視化系統里面並不容易雲做。這些東西最好用C++來實現,因為它們容易看出來到底在做什么。
參考
- https://docs.unrealengine.com/latest/CHN/Gameplay/ClassCreation/CodeAndBlueprints/index.html
- https://docs.unrealengine.com/latest/CHN/Programming/UnrealArchitecture/Reference/Classes/Specifiers/index.html
- https://docs.unrealengine.com/latest/INT/Engine/Blueprints/TechnicalGuide/Guidelines/index.html