​ 在藍圖中我們可以使用一些非同步節點,典型的就是“Delay”:它並不會阻塞當前的遊戲邏輯,而是在指定的時間之後,再執行後面的操作。

將非同步操作封裝為藍圖節點

​ “Delay”的實現是在

class UKismetSystemLibrary

中的

static void Delay(UObject* WorldContextObject, float Duration, struct FLatentActionInfo LatentInfo )

函式,詳見:

EngineDir\Source\Runtime\Engine\Classes\Kismet\KismetSystemLibrary。h

。這種實現方式叫做

Latent Function

,這個東西在虛幻3的Unreal Script中就有了。具體實現方式這裡就不細說了。因為這種方式應該屬於歷史遺產啦,現在有另外一種更方便的實現方式:

Blueprint Async Action

在週期很長的大型專案中,會出現這種情況:一個問題有不止一種解決方案或者處理手法,這個很好理解:團隊在發展,技術在發展,一些東西做著做著有了新想法,老程式碼跑的很穩定,也懶得改了。虛幻4就是這樣一個超長週期的專案,所以也不用迷信引擎原始碼,要用歷史的、發展的眼光看得它。

Blueprint Async Action

​ 引擎提供了一個基類:

class UBlueprintAsyncActionBase

,只要從它派生,並按照一定的約定來實現這個派生類,在藍圖編輯器中就可以自動產生相應的非同步節點啦。下面就透過一個最簡單的例子,來看看這個派生類的寫法。

​ 這個例子很簡單,就是傳送一個Http請求,根據結果呼叫“成功”和“失敗”兩個分支。

將非同步操作封裝為藍圖節點

例項:把HTTP請求封裝成一個藍圖的非同步節點

首先,需要建立一個

class UBlueprintAsyncActionBase

的派生類:

UCLASS

()

class

UBlueprintAsyncHttpRequest

public

UBlueprintAsyncActionBase

{

GENERATED_BODY

()

};

然後,要為這個類建立一個工廠方法:

這個方法必須設定為

BlueprintCallable

,並標記

BlueprintInternalUseOnly

UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = “true”))

這個工廠方法的名稱,就會是我們的藍圖節點的名稱了;這個函式的引數會變為節點的輸入引數;

定義一個

Multicast Delegate

型別,作為非同步操作的完成通知;這個類可以有多個完成通知,但是簽名只能有一個;

使用這個 delegate 型別為類新增成員變數,作為完成通知,這個可以有多個;例如,在這個類裡面我定義了

OnSuccess

OnFail

兩個Delegate,單他們的型別都是

FHttpResponseDelegatge

下面就是這個類的核心定義了:

UCLASS

()

class

UBlueprintAsyncHttpRequest

public

UBlueprintAsyncActionBase

{

GENERATED_BODY

()

public

// Factory Method

UFUNCTION

BlueprintCallable

meta

=

BlueprintInternalUseOnly

=

“true”

))

static

UBlueprintAsyncHttpRequest

*

HttpRequest

const

FString

&

URL

);

UPROPERTY

BlueprintAssignable

FHttpResponseDelegatge

OnSuccess

UPROPERTY

BlueprintAssignable

FHttpResponseDelegatge

OnFail

};

這個類實現之後,在藍圖編輯器裡就可以搜到“HttpRequest”這個節點了。不過,它會有右側會有三個Exec針腳,就像下圖這樣:

將非同步操作封裝為藍圖節點

這是因為引擎預設有一個

Then

針腳,可以透過為這個UClass設定meta來關閉它:

UCLASS(meta=(HideThen=true))

。不過,留著它更有用:在等待非同步請求的過程中,可以立即去做其他事情。

稍微挖掘一下

​ 這些又沒有官方文件,我是咋知道的呢? 是這樣的,我稍微挖掘了一下引擎的原始碼,上面說的那些規則是從原始碼中的兩個類來的:

class UK2Node_AsyncAction : public UK2Node_BaseAsyncTask

EngineDir\Source\Editor\Kismet\Public\Nodes\K2Node_AsyncAction。h

class UK2Node_BaseAsyncTask : public UK2Node

EngineDir\Source\Editor\BlueprintGraph\Classes\K2Node_BaseAsyncTask。h

class UK2Node_AsyncAction

class UK2Node_BaseAsyncTask

派生,它們實現的功能大致如下:

class UK2Node_AsyncAction

這個類主要負責繫結

class UBlueprintAsyncActionBase

派生類的工廠方法,也就是上例中的:

UBlueprintAsyncHttpRequest* HttpRequest(const FString& URL);

class UK2Node_BaseAsyncTask

負責建立節點的基本屬性:

使用反射,讀取工廠方法的輸入引數,作為節點的輸入變數

還是透過反射,查詢這個類有哪些

UMulticastDelegateProperty

,來建立右側的Output針腳。前面說的

HideThen

邏輯也是這裡實現的;

至於它們具體是如何實現非同步操作封裝的,有興趣的朋友可以去研究一下

UK2Node_BaseAsyncTask::ExpandNode()

的實現,我先偷個懶吧!

例子完整程式碼

這個例子的完整工程在這裡:

下面是實現這個節點的完整C++程式碼

BlueprintAsyncHttpRequest。h

#include

“CoreMinimal。h”

#include

“Kismet/BlueprintAsyncActionBase。h”

#include

“Http。h” // HTTP

#include

“BlueprintAsyncHttpRequest。generated。h”

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams

FHttpResponseDelegatge

int32

Code

FString

Data

);

UCLASS

meta

=

HideThen

=

true

))

class

BLUEPRINTASYNC_API

UBlueprintAsyncHttpRequest

public

UBlueprintAsyncActionBase

{

GENERATED_BODY

()

public

// Factory Method

UFUNCTION

BlueprintCallable

meta

=

BlueprintInternalUseOnly

=

“true”

))

static

UBlueprintAsyncHttpRequest

*

HttpRequest

const

FString

&

URL

);

UPROPERTY

BlueprintAssignable

FHttpResponseDelegatge

OnSuccess

UPROPERTY

BlueprintAssignable

FHttpResponseDelegatge

OnFail

private

void

OnHttpResponse

FHttpRequestPtr

Request

FHttpResponsePtr

Response

bool

bWasSuccessful

);

void

SendRequest

const

FString

&

URL

);

};

BlueprintAsyncHttpRequest。cpp

#include

“BlueprintAsyncHttpRequest。h”

UBlueprintAsyncHttpRequest

*

UBlueprintAsyncHttpRequest

::

HttpRequest

const

FString

&

URL

{

UBlueprintAsyncHttpRequest

*

NewRequest

=

NewObject

<

UBlueprintAsyncHttpRequest

>

();

NewRequest

->

SendRequest

URL

);

return

NewRequest

}

void

UBlueprintAsyncHttpRequest

::

SendRequest

const

FString

&

URL

{

AddToRoot

();

FHttpModule

&

HttpModule

=

FHttpModule

::

Get

();

TSharedRef

<

IHttpRequest

>

Request

=

HttpModule

CreateRequest

();

Request

->

SetURL

URL

);

Request

->

SetVerb

“GET”

);

Request

->

OnProcessRequestComplete

()。

BindUObject

this

&

UBlueprintAsyncHttpRequest

::

OnHttpResponse

);

Request

->

ProcessRequest

();

}

void

UBlueprintAsyncHttpRequest

::

OnHttpResponse

FHttpRequestPtr

Request

FHttpResponsePtr

Response

bool

bWasSuccessful

{

if

bWasSuccessful

&&

Response

IsValid

())

{

OnSuccess

Broadcast

Response

->

GetResponseCode

(),

Response

->

GetContentAsString

()

);

}

else

{

OnFail

Broadcast

-

1

TEXT

“”

));

}

RemoveFromRoot

();

}