前言
如图所示,我们经常在UE4内看到如此的异步节点,简单说此类节点的输出并非像函数一样瞬间完成,而是拥有自己的生命周期,此类节点一般在右上角有一个时钟标志
本文讲解如何制作类似AI_MoveTo
的异步节点
另一篇AI_MoveTo简单分析和扩展介绍AI_MoveTo
的简单运行机制和其他扩展方式
更新各类移动扩展函数库以及K2Node
github
基于UBlueprintAsyncActionBase
的异步节点 跳转
UK2Node_AIMoveTo
我们先看一下这个节点的所有申明
1 2 3 4 5 6 7 8 9 10
| UCLASS() class AIGRAPH_API UK2Node_AIMoveTo : public UK2Node_BaseAsyncTask { GENERATED_UCLASS_BODY()
virtual FText GetTooltipText() const override; virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; virtual FText GetMenuCategory() const override;
};
|
短短没几行代码,主要内容在构造函数的定义
1 2 3 4 5 6 7
| UK2Node_AIMoveTo::UK2Node_AIMoveTo(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { ProxyFactoryFunctionName = GET_FUNCTION_NAME_CHECKED(UAIBlueprintHelperLibrary, CreateMoveToProxyObject); ProxyFactoryClass = UAIBlueprintHelperLibrary::StaticClass(); ProxyClass = UAIAsyncTaskBlueprintProxy::StaticClass(); }
|
ProxyFactoryFunctionName = GET_FUNCTION_NAME_CHECKED(UAIBlueprintHelperLibrary, CreateMoveToProxyObject);
- 参数1:调用函数的类
- 参数2:调用的函数
ProxyFactoryClass
:工厂类,一般就是上述参数1的类
ProxyClass
:代理类,用来执行MoveTo
操作并且监听派发的UObject
代理类
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
| class AAIController;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOAISimpleDelegate, EPathFollowingResult::Type, MovementResult);
UCLASS(MinimalAPI) class UAIAsyncTaskBlueprintProxy : public UObject { GENERATED_UCLASS_BODY()
UPROPERTY(BlueprintAssignable) FOAISimpleDelegate OnSuccess;
UPROPERTY(BlueprintAssignable) FOAISimpleDelegate OnFail;
public: UFUNCTION() void OnMoveCompleted(FAIRequestID RequestID, EPathFollowingResult::Type MovementResult);
void OnNoPath(); void OnAtGoal();
virtual void BeginDestroy() override;
TWeakObjectPtr<AAIController> AIController; FAIRequestID MoveRequestId; TWeakObjectPtr<UWorld> MyWorld;
FTimerHandle TimerHandle_OnInstantFinish; };
|
代理类的代码也不多,实际上最关键的就是几个多播代理,异步节点的扩展节点就是由这几个多播代理决定的,代理类里什么时候广播就决定异步节点的运行了;
大概看明白了以后我们自己来扩展一个高级版的AIMoveTo
UK2Node
代理类
参考AI_MoveTo
,我们制作一个输入一个路径点数组,完成逐步沿着各个点移动的节点
1
| DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMoveResult, EPathFollowingResult::Type, MovementResult);
|
先定义一个多播代理,用来申明我们自己的代理变量
1 2 3 4 5 6
| UPROPERTY(BlueprintAssignable,BlueprintCallable) FMoveResult OnFail; UPROPERTY(BlueprintAssignable, BlueprintCallable) FMoveResult OnFinished; UPROPERTY(BlueprintAssignable, BlueprintCallable) FMoveResult OnOneStep;
|
直接来3个代理变量,意味着我们这个异步节点会有3个扩展节点
1 2 3 4 5 6
| UFUNCTION(BlueprintCallable) void MoveTo(APawn* Pawn, const TArray<FVector>& Path, float AcceptanceRadius = 5.0, bool StopOnOverlap = false); UFUNCTION() void MoveEnd(EPathFollowingResult::Type result); UFUNCTION() void MoveSuccess(EPathFollowingResult::Type result);
|
再来3个函数,第一个函数让函数库调用,后两个用来内部控制移动的逻辑
1 2 3 4 5 6 7 8 9 10
| UPROPERTY() TArray<FVector> CurPath; UPROPERTY() APawn* CurPawn; UPROPERTY() float fAcceptanceRadius; UPROPERTY() bool bStopOnOverlap;
bool bIsInit = false;
|
主要用CurPath
这个数组,记录实时路径点
直接贴代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void UAIMoveToByPathProxy::MoveTo(APawn* Pawn, const TArray<FVector>& Path, float AcceptanceRadius , bool StopOnOverlap ) { if (Path.Num()<=0) { return; } if (!bIsInit) { bIsInit = true; CurPath = Path; CurPawn = Pawn; fAcceptanceRadius = AcceptanceRadius; bStopOnOverlap = StopOnOverlap;
} UAIAsyncTaskBlueprintProxy* p=UAIBlueprintHelperLibrary::CreateMoveToProxyObject(Pawn, Pawn, Path[0], nullptr, AcceptanceRadius, StopOnOverlap); p->OnFail.AddDynamic(this, &UAIMoveToByPathProxy::MoveEnd); p->OnSuccess.AddDynamic(this, &UAIMoveToByPathProxy::MoveSuccess); }
|
1 2 3 4 5
| void UAIMoveToByPathProxy::MoveEnd(EPathFollowingResult::Type result) { OnFail.Broadcast(result); }
|
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
| void UAIMoveToByPathProxy::MoveSuccess(EPathFollowingResult::Type result) { if (CurPath.Num()<=0) { OnOneStep.Broadcast(result); OnFinished.Broadcast(result); } else { OnOneStep.Broadcast(result); if (CurPath.Num() > 1) { CurPath.RemoveAt(0); MoveTo(CurPawn, CurPath, fAcceptanceRadius, bStopOnOverlap);
} else { MoveTo(CurPawn, CurPath, fAcceptanceRadius, bStopOnOverlap); CurPath.RemoveAt(0); } } }
|
此类移动一次成功后就会派发OneStep
,移动完成和失败分别广播对应事件
库
1 2 3 4 5 6
| UAIMoveToByPathProxy* UBPLib_MoveTo::V_AI_MoveToByPath(APawn* Pawn, const TArray<FVector>& Path, float AcceptanceRadius , bool StopOnOverlap ) { UAIMoveToByPathProxy* p = NewObject<UAIMoveToByPathProxy>(Pawn); p->MoveTo(Pawn, Path, AcceptanceRadius, StopOnOverlap); return p; }
|
K2Node
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
| UUK2Node_MoveByPath::UUK2Node_MoveByPath(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { ProxyFactoryFunctionName = GET_FUNCTION_NAME_CHECKED(UBPLib_MoveTo, V_AI_MoveToByPath); ProxyFactoryClass = UBPLib_MoveTo::StaticClass(); ProxyClass = UAIMoveToByPathProxy::StaticClass(); }
FText UUK2Node_MoveByPath::GetTooltipText() const {
return FText::FromString(TEXT("move by a path")); }
FText UUK2Node_MoveByPath::GetNodeTitle(ENodeTitleType::Type TitleType) const { return FText::FromString(TEXT("MoveByPath")); }
FText UUK2Node_MoveByPath::GetMenuCategory() const { return FText::FromString(TEXT("MoveByPath")); }
|
最终效果
UBlueprintAsyncActionBase
用法比K2Node的简单一些, 但是一般是建立在能获取多播代理的情况下,如下类是监听tag
的改变
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
| DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnStatChanged, FGameplayTag, NewStatTag, bool, bIsAdd);
UCLASS() class INTERACTIONEXTRA_API UListenForStatChanged : public UBlueprintAsyncActionBase { GENERATED_BODY() public: UPROPERTY(BlueprintAssignable) FOnStatChanged OnStatChanged;
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true")) static UListenForStatChanged* ListenForStatChanged(AInteractableActorBase* Actor);
UFUNCTION(BlueprintCallable) void EndTask();
AInteractableActorBase* ListenActor;
protected: void TagChanged(const FGameplayTag& Tag, bool bIsAdd); };
|
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
| void UListenForStatChanged::EndTask() { if (ListenActor) { ListenActor->GetStatChangedDelegate().RemoveAll(this); } SetReadyToDestroy(); MarkPendingKill(); }
UListenForStatChanged* UListenForStatChanged::ListenForStatChanged(AInteractableActorBase* Actor) { if (!Actor) { return nullptr; } UListenForStatChanged* task = NewObject<UListenForStatChanged>(); task->ListenActor = Actor; Actor->GetStatChangedDelegate().AddUObject(task, &UListenForStatChanged::TagChanged);
return task; }
void UListenForStatChanged::TagChanged(const FGameplayTag& Tag, bool bIsAdd) { OnStatChanged.Broadcast(Tag, bIsAdd); }
|
然后我们被监听的类内需要实现如下内容
1 2 3 4 5 6 7
| DECLARE_MULTICAST_DELEGATE_TwoParams(FOnTagChanged, const FGameplayTag&, bool); virtual FOnTagChanged& GetStatChangedDelegate() { return OnStatChanged; }; FOnTagChanged OnStatChanged;
|
主要是这个代理需要此类在合适的时机去广播, 比如下面方法内
1 2 3 4 5
| void AInteractableActorBase::AddStat_Implementation(FGameplayTag StatTag) { StatTags.AddTag(StatTag); OnStatChanged.Broadcast(StatTag, true); }
|