摘要

:虛幻引擎雖然內建了許多的藍圖節點,能夠用於實現各種各樣的功能,但仍然會遇到內建節點無法滿足需求的情形。K2Node藍圖節點是引擎一種自定義高階藍圖節點的機制,用於擴充套件藍相簿。本教程以K2Node_ArraySort藍圖節點實現過程為例,旨在說明K2Node節點的建立規則和要求,並且介紹如何給藍圖節點的引腳新增下拉列表。

關鍵字

:虛幻引擎;藍圖節點;K2Node;自定義引腳;

Chapter 9 Design and Implementation of Custom K2Node Blueprint Node

Abstract

:Although Unreal Engine has many built-in blueprint nodes that can implement various functions, it will still encounter the situation that these engine nodes cannot meet the needs。 The K2Node is a mechanism for the engine to customize advanced blueprint nodes, which is used to expand the Blueprint system。 This tutorial takes the implementation process of the K2Node_ArraySort node as an example to illustrate the rules and requirements for creating the K2Node node, and introduces how to add a drop-down list to the pins of K2Node node。

1.Overview

本教程以K2NodeArraySort實現過程為例,簡要的說明如何在UE4/5中實現一個深度定製的K2Node藍圖節點,實現對泛型陣列進行快速排序(QuickSort);特別說明:以下程式碼使用UE5 EA版引擎實現。

2. Introduction

眾所周知,虛幻引擎(Unreal Engine)擁有強大的藍圖系統(Blueprint System),利用引擎內建的藍圖節點(Node)可以建立各式各樣的功能;但是藍圖系統也有不足,比如常常會碰到內建的藍圖節點並沒有我們想要功能的情況。這時候就需要用UE C++來自定義藍圖節點,將用C++寫好函式暴露給藍圖系統,藍圖編輯器中各式各樣的藍圖節點也都是來源於此。藍圖編輯器中的絕大多數藍圖節點都是在編寫相應的C++程式碼時,透過UFUNCTION宏暴露出來的,這些藍圖節點的屬性(如:引腳(Pin)、名稱(Title))在藍圖編輯器中基本是不可改變的。K2Node節點是虛幻引擎另一種建立藍圖節點的機制, 與一般使用UFUNCTION宏編寫藍圖節點不同,派生於UK2Node類的藍圖節點可以在藍圖編輯器中完成各種各樣的功能,如:給藍圖節點的引腳新增下拉列表,動態修改藍圖節點引腳型別,增/減引腳個數等。引擎中具有代表性的UK2Node_GetDataTableRow類實現的GetDataTableRow藍圖節點,它不僅能夠根據DataTable引腳的值動態改變輸出引腳OutRow的型別,同時也能夠給輸入引腳RowName新增下拉列表,列舉RowName變數的可選值,以及動態改變節點名稱。曾經在我剛開始接觸虛幻引擎的那段時間,看到這個節點都感到非常的不可思議,感慨藍圖功能的強大,也為實現K2Node藍圖節點摸索良久。

第9期 UE45 K2Node藍圖節點設計與實現

GetDataTableRow藍圖節點

迴歸主題,為什麼要寫K2Node_ArraySort節點?早在去年本人曾在第1期 在藍圖實現任意型別陣列排序,介紹了使用藍圖宏方法,實現選擇排序(Select Sort)和氣泡排序(Bubble Sort)二個版本的泛型陣列排序藍圖宏節點;隨後又在第2期 UE4 C++實現任意型別陣列藍圖排序節點中介紹如何使用UFUNCTION宏建立泛型陣列排序節點,同樣是使用氣泡排序演算法。再後來對其中排序演算法進行改進,利用快速排序(Quick Sort)實現更為高效的泛型陣列排序節點,詳情見個人部落格:UE4 C++實現任意型別陣列藍圖排序節點(Quick Sort)。為了更好的使用這個藍圖節點,打算對該藍圖節點進一步的迭代,仿照GetDataTableRow節點也給這個藍圖節點新增一個下拉列表,列舉待排序陣列成員的型別為結構體和UObject派生類物件時可排序變數名稱。同時也為了深入瞭解引擎自定義K2Node節點的機制。原始碼的較長,難以面面俱到的講解每段程式碼的含義,以下內容將主要介紹其中的緣由,詳細的原始碼見文末連結。

3. Step by step

(1) 建立外掛並手動新增一個編輯器模組

一般來說,自定義K2Node節點實現的功能具有通用性,可複用,因此將K2Node節點程式碼封裝到外掛之中更為合理。首先使用空模板建立一個名為AwesomeBlueprints的外掛,此時這個外掛的Source目錄下包含了一個與外掛同名的預設模組(AwesomeBlueprints模組)。

第9期 UE45 K2Node藍圖節點設計與實現

建立AwesomeBlueprints外掛

建立名為AwesomeBlueprintsEditor的編輯器模組:複製AwesomeBlueprints外掛目錄下Source/AwesomeBlueprints資料夾(Crtl +滑鼠拖拽),修改複製後的

資料夾

build。cs

、 。

h檔案

。cpp

檔案的名稱,把所有

AwesomeBlueprints

全部替換為

AwesomeBlueprintsEditor

(文字工具:查詢->替換),修改的位置包括檔名、模組名,模組類名等。雖然這個手動建立的模組的名稱可以任取,但是一般為了方便表明二個模組之間的關聯性,直接命名對應{Runtime模組名}+Editor。

第9期 UE45 K2Node藍圖節點設計與實現

手動建立模組

隨後得到如下目錄結構:

第9期 UE45 K2Node藍圖節點設計與實現

包含二個模組的外掛AwesomeBlueprints檔案目錄

將新模組

AwesomeBlueprintsEditor

新增到AwesomeBlueprints。uplugin檔案中如下所示位置:

“Modules”

{

“Name”

“AwesomeBlueprints”

“Type”

“Runtime”

“LoadingPhase”

“Default”

},

{

“Name”

“AwesomeBlueprintsEditor”

“Type”

“UncookedOnly”

“LoadingPhase”

“Default”

}

重新執行Generate Visual Studio File,並編譯程式碼,在該外掛中就會有二個模組,一個Runtime模組(AwesomeBlueprints),一個Editor模組(AwesomeBlueprintsEditor);以上準備工作就以完成。特別注意:其中AwesomeBlueprintsEditor模組型別(Type)應當設定為“UncookedOnly”,否則在編譯藍圖時會遇到如下警告。

第9期 UE45 K2Node藍圖節點設計與實現

The node ‘ K2 Array Sort ’ is from an Editor Only module, but is placed in a runtime blueprint! K2 Nodes should only be defined in a Developer or UncookedOnly module。

(2)以UK2Node類為父類,建立類UK2Node_ArraySort子類

在這裡遵循引擎對K2Node派生類的命名規則

K2Node_{FunctionName}

,將其命名為K2Node_ArraySort。在進行下一步之前,先簡單分析這個K2Node_ArraySort節點程式碼的存在的模組位置。UK2Node_ArraySort類定義的是Array_Sort藍圖節點在藍圖編輯器中的行為,故而這個類的程式碼應該屬於編輯器模組(

AwesomeBlueprintsEditor

)中的程式碼。

第9期 UE45 K2Node藍圖節點設計與實現

建立K2Node_ArraySort類

由於打包之後,並不包含編輯模組的程式碼,所以同編寫普通的藍圖節點一樣,這裡我們仍需要把真正完成陣列排序功能的程式碼放到Runtime模組(

AwesomeBlueprints

)。如上一步類似,以UBlueprintFunctionLibrary基類,建立UAwesomeFunctionLibrary類,並選擇

AwesomeBlueprints

模組

4. Source Code Insight

AwesomeBlueprintsEditor。build。cs依賴的模組

PrivateDependencyModuleNames

AddRange

new

string

[]

{

“CoreUObject”

“Engine”

“Slate”

“SlateCore”

“KismetCompiler”

“UnrealEd”

“BlueprintGraph”

“GraphEditor”

“AwesomeBlueprints”

// 。。。 add private dependencies that you statically link with here 。。。

}

);

此處只要知道UE4模組的匯出機制,很容易理解為什麼新增這些模組,不做過多介紹。

重點介紹為藍圖引腳建立下拉列表原理:

首先簡要的闡述下,GetDataTableRow節點中RowName引腳如何產生下拉列表。如下圖呼叫堆疊所示,在藍圖編輯器中,點選GetDataTableRow節點的Datatable引腳時,首先從ContentBrowser中獲得所有DataTable資源;當我們選擇其中一個DataTable時,呼叫

UK2Node_GetDataTableRow::PinDefaultValueChanged()

函式,其中

UK2Node_GetDataTableRow::RefreshOutputPinType()

更新OutRow引腳的型別,

UK2Node_GetDataTableRow::RefreshRowNameOptions()

函式呼叫

Graph->NotifyGraphChanged()

,通知藍圖系統該藍圖圖表Graph發生改變,需重新繪製。

第9期 UE45 K2Node藍圖節點設計與實現

選擇DataTable資源後執行過程

當重新繪製藍圖圖表時,依次建立 節點(SGraphNode),引腳(SGraphPin),節點和引腳均為一個個Slate元素,如下圖所示

第9期 UE45 K2Node藍圖節點設計與實現

繪製藍圖節點執行過程

若當前正在建立的引腳為GetDataTableRow節點的RowName引腳,並且DataTable值非空,則返回

SGraphPinDataTableRowName

,其中

SGraphPinDataTableRowName

的父類之一就是SGraphPin類。

第9期 UE45 K2Node藍圖節點設計與實現

建立SGraphPinDataTableRowName的過程

而上圖中的

FBlueprintGraphPanelPinFactory

類在FBlueprintEditor啟動模組時註冊到FEdGraphUtilities類中

第9期 UE45 K2Node藍圖節點設計與實現

BlueprintEditor模組StartupModule函式

綜上分析:我們在AwesomeBlueprintsEditor模組中增加了

SGraphPinArraySortPropertyName

類和

FK2BlueprintGraphPanelPinFactory

類,並在AwesomeBlueprintsEditor模組啟動時,將

FK2BlueprintGraphPanelPinFactory

註冊到FEdGraphUtilities中。

void

FAwesomeBlueprintsEditorModule

::

StartupModule

()

{

// This code will execute after your module is loaded into memory; the exact timing is specified in the 。uplugin file per-module

TSharedPtr

<

FK2BlueprintGraphPanelPinFactory

>

BlueprintGraphPanelPinFactory

=

MakeShareable

new

FK2BlueprintGraphPanelPinFactory

());

FEdGraphUtilities

::

RegisterVisualPinFactory

BlueprintGraphPanelPinFactory

);

}

在完成

UK2Node_ArraySort

SGraphPinArraySortPropertyName

FK2BlueprintGraphPanelPinFactory

三個類的程式碼時,即可到K2Node_ArraySort藍圖節點的所有功能。

K2Node_ArraySort的PropertyName引腳下拉列表資料來源:當TargetArray引腳連線到結構體UStruct陣列或者UObject派生陣列時,迭代結構體UStruct或者UObjectClass的所有屬性,將其中可用於排序的屬性名新增到數組裡。

TArray

<

FString

>

UK2Node_ArraySort

::

GetPropertyNames

()

{

TArray

<

FString

>

PropertyNames

UEdGraphPin

*

TargetArrayPin

=

GetTargetArrayPin

();

if

TargetArrayPin

&&

TargetArrayPin

->

LinkedTo

Num

()

>

0

{

UEdGraphPin

*

LinkArrayPin

=

TargetArrayPin

->

LinkedTo

0

];

// The type of inner property of TargetArrayPin is structure

if

UScriptStruct

*

InnerStruct

=

Cast

<

UScriptStruct

>

LinkArrayPin

->

PinType

PinSubCategoryObject

Get

()))

{

for

TFieldIterator

<

const

FProperty

>

It

InnerStruct

);

It

++

It

{

const

FProperty

*

BaseProp

=

*

It

if

const

FEnumProperty

*

EnumProp

=

CastField

<

const

FEnumProperty

>

BaseProp

))

{

PropertyNames

Add

BaseProp

->

GetAuthoredName

());

}

else

if

const

FNumericProperty

*

NumProp

=

CastField

<

const

FNumericProperty

>

BaseProp

))

{

PropertyNames

Add

BaseProp

->

GetAuthoredName

());

}

}

}

// The type of inner property of TargetArrayPin is Blueprint & C++ class

else

if

UClass

*

InnerObjClass

=

Cast

<

UClass

>

LinkArrayPin

->

PinType

PinSubCategoryObject

Get

()))

{

for

TFieldIterator

<

const

FProperty

>

It

InnerObjClass

);

It

++

It

{

const

FProperty

*

BaseProp

=

*

It

if

const

FEnumProperty

*

EnumProp

=

CastField

<

const

FEnumProperty

>

BaseProp

))

{

PropertyNames

Add

BaseProp

->

GetAuthoredName

());

}

else

if

const

FNumericProperty

*

NumProp

=

CastField

<

const

FNumericProperty

>

BaseProp

))

{

PropertyNames

Add

BaseProp

->

GetAuthoredName

());

}

}

}

}

return

PropertyNames

}

5. Test & Usage

第9期 UE45 K2Node藍圖節點設計與實現

ArraySort在藍圖編輯下測試

此外外掛中還將第3期 ue4 泛型藍圖節點的實現及應用例項 的不同型別(基礎型別,FArrayProperty, FMapProperty, FSetProperty)的萬用字元引腳合併在一起,寫成二個節點

Get Property Value By Name

Set Property Value By Name

,使其同時Array,Map,Set的變數。

第9期 UE45 K2Node藍圖節點設計與實現

Get/Set Object Property Value By Name 泛型節點

6. Conclusion

本文以K2Node_ArraySort節點實現過程為例,著重介紹了建立模組,以及K2Node節點的引腳新增下拉列表的過程。不足之處:K2Node_ArraySort節點的PropertyName引腳下拉列表生成過程重複呼叫了

GetPropertyNames()

迭代陣列元素型別,應當新增快取機制。一家之言:將K2Node_ArraySort節點與UFUNCTION版實現的ArraySort節點對比,不難發現K2Node版節點雖然增加了一些功能,但這些功能都屬於Editor狀態下的功能,並沒有改變Runtime狀態時的程式碼執行過程,也就不會提升執行效率。但是對於

GetProperyValueByName

SetProperyValueByName

二個節點來說,能夠將不同型別的萬用字元引腳合併,使用K2Node方式實現該功能藍圖節點,大大減少了功能重複的節點,確有其價值。

原始碼:

7.Addition

這一期加上前幾期,分別介紹了CustomThunk泛型藍圖節點、多分支執行流非同步藍圖節點、K2Node藍圖節點的實現過程,基本上涵蓋了自定義藍圖節點的絕大數內容,估計以後不會再像之前那樣介紹藍圖節點了。距離上一次更新已經過了很久,因為這一年接手的是純UE4 C++專案,所以幾乎沒有用藍圖寫什麼程式碼。目前自己更傾向於用UE4 C++寫外掛/模組,方便程式碼管理和引擎升級,也在考慮該使用UE做些什麼樣的東西。ps。 隨緣更新。