- Published on
创建异步节点
- Authors

- Name
- 东哥
前言

如图所示,我们经常在UE4内看到如此的异步节点,简单说此类节点的输出并非像函数一样瞬间完成,而是拥有自己的生命周期,此类节点一般在右上角有一个时钟标志
本文讲解如何制作类似
AI_MoveTo的异步节点另一篇AI_MoveTo简单分析和扩展介绍
AI_MoveTo的简单运行机制和其他扩展方式
- 2020.11.24更新:
更新各类移动扩展函数库以及K2Node
- 2021.2.25更新:
基于
UBlueprintAsyncActionBase的异步节点 跳转
UK2Node_AIMoveTo
我们先看一下这个节点的所有申明
UCLASS()
class AIGRAPH_API UK2Node_AIMoveTo : public UK2Node_BaseAsyncTask//继承类
{
GENERATED_UCLASS_BODY() //加入UCLASS生成默认构造函数
virtual FText GetTooltipText() const override; //鼠标移动到节点的说明
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;//节点的名字
virtual FText GetMenuCategory() const override;//分类
};
短短没几行代码,主要内容在构造函数的定义
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
代理类
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();
//~ Begin UObject Interface
virtual void BeginDestroy() override;
//~ End UObject Interface
`TWeakObjectPtr<AAIController>` AIController;
FAIRequestID MoveRequestId;
`TWeakObjectPtr<UWorld>` MyWorld;
FTimerHandle TimerHandle_OnInstantFinish;
};
代理类的代码也不多,实际上最关键的就是几个多播代理,异步节点的扩展节点就是由这几个多播代理决定的,代理类里什么时候广播就决定异步节点的运行了;
大概看明白了以后我们自己来扩展一个高级版的AIMoveTo
UK2Node
代理类

参考
AI_MoveTo,我们制作一个输入一个路径点数组,完成逐步沿着各个点移动的节点
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMoveResult, EPathFollowingResult::Type, MovementResult);
先定义一个多播代理,用来申明我们自己的代理变量
UPROPERTY(BlueprintAssignable,BlueprintCallable)
FMoveResult OnFail;
UPROPERTY(BlueprintAssignable, BlueprintCallable)
FMoveResult OnFinished;
UPROPERTY(BlueprintAssignable, BlueprintCallable)
FMoveResult OnOneStep;
直接来3个代理变量,意味着我们这个异步节点会有3个扩展节点
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个函数,第一个函数让函数库调用,后两个用来内部控制移动的逻辑
UPROPERTY()
`TArray<FVector>` CurPath;
UPROPERTY()
APawn* CurPawn;
UPROPERTY()
float fAcceptanceRadius;
UPROPERTY()
bool bStopOnOverlap;
bool bIsInit = false;
主要用CurPath这个数组,记录实时路径点
直接贴代码
void UAIMoveToByPathProxy::MoveTo(APawn* Pawn, const `TArray<FVector>`& Path, float AcceptanceRadius /*= 5.0*/, bool StopOnOverlap /*= false*/)
{
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);
}
void UAIMoveToByPathProxy::MoveEnd(EPathFollowingResult::Type result)
{
//UKismetSystemLibrary::PrintString(CurPawn, TEXT("End!"));
OnFail.Broadcast(result);
}
void UAIMoveToByPathProxy::MoveSuccess(EPathFollowingResult::Type result)
{
if (CurPath.Num()<=0)
{
OnOneStep.Broadcast(result);
OnFinished.Broadcast(result);
//UKismetSystemLibrary::PrintString(CurPawn, TEXT("Finish!"));
}
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);
}
//UKismetSystemLibrary::PrintString(CurPawn, TEXT("OneSTEP!"));
}
}
此类移动一次成功后就会派发OneStep,移动完成和失败分别广播对应事件
库
UAIMoveToByPathProxy* UBPLib_MoveTo::V_AI_MoveToByPath(APawn* Pawn, const `TArray<FVector>`& Path, float AcceptanceRadius /*= 5.0*/, bool StopOnOverlap /*= false*/)
{
UAIMoveToByPathProxy* p = `NewObject<UAIMoveToByPathProxy>`(Pawn);
p->MoveTo(Pawn, Path, AcceptanceRadius, StopOnOverlap);
return p;
}
K2Node
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的改变
//.h
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);
};
//.cpp
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);
}
然后我们被监听的类内需要实现如下内容
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnTagChanged, const FGameplayTag&, bool);
virtual FOnTagChanged& GetStatChangedDelegate()
{
return OnStatChanged;
};
FOnTagChanged OnStatChanged;
主要是这个代理需要此类在合适的时机去广播, 比如下面方法内
void AInteractableActorBase::AddStat_Implementation(FGameplayTag StatTag)
{
StatTags.AddTag(StatTag);
OnStatChanged.Broadcast(StatTag, true);
}
