在藍圖中我們可以使用一些非同步節點,典型的就是“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
();
}