在工程中有一些异步操作的需求,比如下载文件的行为,一个下载命令等待下载完成之后回调,而且要尽量避免太多的事件绑定和解绑操作。 在蓝图的节点中有一些异步的操作,比如 PlayMontage
/DownloadImage
等,都是异步操作中有多个输出节点的: 我们能不能自己写一个这样的异步操作的节点呢?那必然是可以的。 可以在 Engine\Source\Runtime\UMG\Public\Blueprint\AsyncTaskDownloadImage.h
中查看 DownloadImage 节点的实现 (C++API )。 可以自己仿照这自己写一个出来:
关键就是要继承UBlueprintAsyncActionBase
,这个是必须的。
然后写一个 static
的函数,返回类的指针,并用 UFUNCTION
的mate
标记为BlueprintInternalUseOnly="true"
声明并定义几个派发器成员,这些派发器成员就是异步的节点,也就是蓝图节点右侧的 exec 节点。
我们先来看一下 DownloadImage
的声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class UTexture2DDynamic ;DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam (FDownloadImageDelegate, UTexture2DDynamic*, Texture);UCLASS ()class UMG_API UAsyncTaskDownloadImage : public UBlueprintAsyncActionBase{ GENERATED_UCLASS_BODY () public : UFUNCTION (BlueprintCallable, meta=( BlueprintInternalUseOnly="true" )) static UAsyncTaskDownloadImage* DownloadImage (FString URL) ; public : UPROPERTY (BlueprintAssignable) FDownloadImageDelegate OnSuccess; UPROPERTY (BlueprintAssignable) FDownloadImageDelegate OnFail; public : void Start (FString URL) ; private : void HandleImageRequest (FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded) ; };
对比上面的 DownloadImage 的节点,可以看到: 0. 首先声明了一个动态多播代理FDownloadImageDelegate
,并且需要传入一个参数
UAsyncTaskDownloadImage
类中声明了两个事件派发器 OnSuccess
和OnFail
,这也是蓝图节点右侧的 Exec 和参数Texture
,本质都是派发器(动态多播代理)
UAsyncTaskDownloadImage
的 static
函数 DownloadImage
接收一个 FString 的参数,返回一个UAsyncTaskDownloadImage*
,这个返回就是把派发器的执行节点在蓝图中显示出来
即:声明的动态多播的成员和该多播的参数都会显示在蓝图节点的右侧。
我自己实现了一个异步行为的操作,先在蓝图中看操作: 行为就是,先创建一个 AsyncActionObject
的对象作为后期触发异步操作的对象,然后执行 CreateAsyncTask
里面对上一步创建的 AsyncActionObject
进行事件绑定。 然后我们就可以在那个 ActionObj 调用 OnActionStart
之类的操作就可以调用 CreateAsyncTask
右侧的相关节点。 注意:我做的限制是,一个对象对应一个 Task, 如果当前传入的 AsyncActionObject
正在被其他的 task 绑定,则创建 task 会失败。 然后就是代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #pragma once #include "AsyncActionObject.h" #include "Array.h" #include "Kismet/KismetSystemLibrary.h" #include "CoreMinimal.h" #include "UObject/ObjectMacros.h" #include "Kismet/BlueprintAsyncActionBase.h" #include "AsyncTask.generated.h" #ifndef PRINT_LOG #define PRINT_LOG(lOG_TEXT) UKismetSystemLibrary::PrintString(this,lOG_TEXT,true,true); #endif #ifndef PRINT_ASYNC_ACTION_TOOLS_DEBUG_INFO #define PRINT_ASYNC_ACTION_TOOLS_DEBUG_INFO 0 #endif DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam (FAsyncTaskDelegate,UAsyncActionObject*,ActionObj);UCLASS (BlueprintType)class UAsyncTask : public UBlueprintAsyncActionBase{ GENERATED_UCLASS_BODY () public : UFUNCTION (BlueprintCallable, meta=( BlueprintInternalUseOnly="true" )) static UAsyncTask* CreateAsyncTask (UAsyncActionObject* ActionObj) ; public : UPROPERTY (BlueprintAssignable) FAsyncTaskDelegate OnStart; UPROPERTY (BlueprintAssignable) FAsyncTaskDelegate OnAbort; UPROPERTY (BlueprintAssignable) FAsyncTaskDelegate OnUpdate; UPROPERTY (BlueprintAssignable) FAsyncTaskDelegate OnFinishd; protected : void StartTask (UAsyncActionObject* ActionObj) ; virtual void OnActionStart (UAsyncActionObject* ActionObj) ; virtual void OnActionAbort (UAsyncActionObject* ActionObj) ; virtual void OnActionUpdate (UAsyncActionObject* ActionObj) ; virtual void OnActionFinishd (UAsyncActionObject* ActionObj) ; };
然后是实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include "AsyncTask.h" #include "Modules/ModuleManager.h" UAsyncTask::UAsyncTask (const FObjectInitializer& ObjectInitializer) : Super (ObjectInitializer) { if (HasAnyFlags (RF_ClassDefaultObject) == false ) { AddToRoot (); } } UAsyncTask* UAsyncTask::CreateAsyncTask (UAsyncActionObject* ActionObj) { UAsyncTask* AsyncActionTask = NewObject<UAsyncTask>(); AsyncActionTask->StartTask (ActionObj); return AsyncActionTask; } void UAsyncTask::StartTask (UAsyncActionObject* ActionObj) { if (ActionObj && !ActionObj->Action_IsRunning ()) { #if PRINT_ASYNC_ACTION_TOOLS_DEBUG_INFO PRINT_LOG (TEXT ("UAsyncTask::StartTask Bind Event" )); #endif (ActionObj->OnStart).BindUObject (this ,&UAsyncTask::OnActionStart); (ActionObj->OnAbort).BindUObject (this ,&UAsyncTask::OnActionAbort); (ActionObj->OnUpdate).BindUObject (this ,&UAsyncTask::OnActionUpdate); (ActionObj->OnFinishd).BindUObject (this ,&UAsyncTask::OnActionFinishd); } } void UAsyncTask::OnActionStart (UAsyncActionObject* ActionObj) {#if PRINT_ASYNC_ACTION_TOOLS_DEBUG_INFO PRINT_LOG (TEXT ("UAsyncTask::OnStart" )); #endif OnStart.Broadcast (ActionObj); } void UAsyncTask::OnActionAbort (UAsyncActionObject* ActionObj) {#if PRINT_ASYNC_ACTION_TOOLS_DEBUG_INFO PRINT_LOG (TEXT ("UAsyncTask::OnActionAbort" )); #endif OnAbort.Broadcast (ActionObj); } void UAsyncTask::OnActionUpdate (UAsyncActionObject* ActionObj) {#if PRINT_ASYNC_ACTION_TOOLS_DEBUG_INFO PRINT_LOG (TEXT ("UAsyncTask::OnActionUpdate" )); #endif OnUpdate.Broadcast (ActionObj); } void UAsyncTask::OnActionFinishd (UAsyncActionObject* ActionObj) {#if PRINT_ASYNC_ACTION_TOOLS_DEBUG_INFO PRINT_LOG (TEXT ("UAsyncTask::OnActionFinishd" )); #endif OnFinishd.Broadcast (ActionObj); }
AsyncActionObject 类的声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #pragma once #include "Array.h" #include "Kismet/KismetSystemLibrary.h" #include "CoreMinimal.h" #include "UObject/NoExportTypes.h" #include "AsyncActionObject.generated.h" #ifndef PRINT_LOG #define PRINT_LOG(lOG_TEXT) UKismetSystemLibrary::PrintString(this,lOG_TEXT,true,true); #endif #ifndef PRINT_ASYNC_ACTION_OBJ_DEBUG_INFO #define PRINT_ASYNC_ACTION_OBJ_DEBUG_INFO 0 #endif class UAsyncActionObject ;DECLARE_DELEGATE_OneParam (FAsyncActionDelegate,UAsyncActionObject*);UCLASS (BlueprintType,Blueprintable)class UAsyncActionObject : public UObject{ GENERATED_BODY () public : UAsyncActionObject (const FObjectInitializer& objectInitializer); FAsyncActionDelegate OnStart; FAsyncActionDelegate OnAbort; FAsyncActionDelegate OnUpdate; FAsyncActionDelegate OnFinishd; public : UFUNCTION (BlueprintCallable) virtual bool OnActionStart (FString& rReason) ; UFUNCTION (BlueprintCallable) virtual void OnActionAbort () ; UFUNCTION (BlueprintCallable) virtual void OnActionUpdate () ; UFUNCTION (BlueprintCallable) virtual void OnActionFinishd () ; public : UFUNCTION (BlueprintCallable) virtual bool Action_IsRunning () const ; UFUNCTION (BlueprintCallable) virtual bool Action_ExecutableStart () const ; protected : virtual void EndAction () ; virtual void UnBindAll () ; virtual void InitDelegateList () ; virtual bool Action_IsStarted () const ; virtual bool Action_EventIsBinded () const ; protected : bool mActionStarted=false ; TArray<FAsyncActionDelegate*> DelegateList; };
以及它的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 #include "AsyncActionObject.h" UAsyncActionObject::UAsyncActionObject (const FObjectInitializer& objectInitializer) { #if PRINT_ASYNC_ACTION_OBJ_DEBUG_INFO PRINT_LOG (TEXT ("UAsyncActionObject::UAsyncActionObject" )); #endif InitDelegateList (); } bool UAsyncActionObject::OnActionStart (FString& rReason) { rReason.Reset (); bool local_bResault=false ; if (Action_ExecutableStart ()) { #if PRINT_ASYNC_ACTION_OBJ_DEBUG_INFO PRINT_LOG (TEXT ("UAsyncActionObject::OnActionStart" )); #endif mActionStarted=true ; OnStart.ExecuteIfBound (this ); local_bResault=true ; }else { #if PRINT_ASYNC_ACTION_OBJ_DEBUG_INFO PRINT_LOG (TEXT ("call UAsyncActionObject::OnActionStart Faild." )); #endif local_bResault=false ; if (Action_IsStarted ()) { #if PRINT_ASYNC_ACTION_OBJ_DEBUG_INFO PRINT_LOG (TEXT ("StartFaild: Action is Started." )); #endif rReason.Append (FString (TEXT ("Action is Started.\n" ))); } if (!Action_EventIsBinded ()) { #if PRINT_ASYNC_ACTION_OBJ_DEBUG_INFO PRINT_LOG (TEXT ("StartFaild: Action is not bind anything event." )); #endif rReason.Append (FString (TEXT ("Action is not bind to anything Task" ))); } } return local_bResault; } void UAsyncActionObject::OnActionAbort () { if (Action_IsRunning ()) { #if PRINT_ASYNC_ACTION_OBJ_DEBUG_INFO PRINT_LOG (TEXT ("UAsyncActionObject::OnActionAbort" )); #endif OnAbort.ExecuteIfBound (this ); EndAction (); } } void UAsyncActionObject::OnActionUpdate () { if (Action_IsRunning ()) { #if PRINT_ASYNC_ACTION_OBJ_DEBUG_INFO PRINT_LOG (TEXT ("UAsyncActionObject::OnActionUpdate" )); #endif OnUpdate.ExecuteIfBound (this ); } } void UAsyncActionObject::OnActionFinishd () { if (Action_IsRunning ()) { #if PRINT_ASYNC_ACTION_OBJ_DEBUG_INFO PRINT_LOG (TEXT ("UAsyncActionObject::OnActionFinishd" )); #endif OnFinishd.ExecuteIfBound (this ); EndAction (); } } void UAsyncActionObject::EndAction () { if (Action_IsRunning ()) { #if PRINT_ASYNC_ACTION_OBJ_DEBUG_INFO PRINT_LOG (TEXT ("UAsyncActionObject::EndAction" )); #endif UnBindAll (); mActionStarted=false ; } } void UAsyncActionObject::UnBindAll () {#if PRINT_ASYNC_ACTION_OBJ_DEBUG_INFO PRINT_LOG (TEXT ("UAsyncActionObject::UnBindAll" )); #endif for (auto & DeleIndex:DelegateList) { if (DeleIndex->IsBound ()) { DeleIndex->Unbind (); } } } void UAsyncActionObject::InitDelegateList () { if (!Action_IsStarted ()) { DelegateList.AddUnique (&OnStart); DelegateList.AddUnique (&OnAbort); DelegateList.AddUnique (&OnUpdate); DelegateList.AddUnique (&OnFinishd); } } bool UAsyncActionObject::Action_EventIsBinded () const { bool EventIsBinded=true ; for (auto & DeleIndex:DelegateList) { if (!DeleIndex->IsBound ()) EventIsBinded=false ; } return EventIsBinded; } bool UAsyncActionObject::Action_IsStarted () const { return mActionStarted; } bool UAsyncActionObject::Action_IsRunning () const { return Action_IsStarted () && Action_EventIsBinded (); } bool UAsyncActionObject::Action_ExecutableStart () const { return !Action_IsStarted () && Action_EventIsBinded (); }
其实就是本质上裹了两层派发器而已… 举个例子的用途:可以在行为树里监听某个动画被终止或者结束了之后然后再执行其他的行为,可以解决不同模块之间之间的耦合。