UE4异步蓝图节点设计与实现(单线程)
在UE4的蓝图中,有许多节点的执行是独立于主线程的,例如定时器或者一些延迟操作,我们不能在主线程中执行,这样会导致游戏画面出现暂停等情况。
在蓝图中,最常见的一个异步节点就是delay节点,在异步节点中我们也可以分为使用单线程还是多线程实现的异步节点。
在蓝图中也存在许多异步节点,但是在实际开发中,根据实际需要,我们可能需要对场景中多个对象进行异步操作,这个时候就需要我们自定义的实现UE4中蓝图异步节点。
其中,最主要的就是使用C++中的BlueprintAsyncActionBase类来自定义蓝图异步节点,注意异步节点的输出需要绑定多播委托。
下面我们设计了一个基于异步的定时器节点,其目的非常简单,即通过计时器不断访问TickActor中的计数,当计数值大于等于目标值时,则Success多播相应,反之则Fail响应。
MyBluprintAsyncActionBase.h
#pragma once
#include "CoreMinimal.h"
#include "TickTestActor.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "MyBlueprintAsyncActionBase.generated.h"
//异步节点的输出需要各自绑定相应的委托
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAsyncNodeResult, int32, Result);
/**
*
*/
UCLASS()
class TESTTHREAD_API UMyBlueprintAsyncActionBase : public UBlueprintAsyncActionBase
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable)
FAsyncNodeResult Success;
UPROPERTY(BlueprintAssignable)
FAsyncNodeResult Failed;
FTimerHandle TimerHandle;
//这个声明为static不知道是不是必须的
//meta=(WorldContext = "WorldContestObject"))可以自动获取调用actor的world,用于间接获取世界上下文
UFUNCTION(BlueprintCallable, meta=(WorldContext = "WorldContestObject"))
static UMyBlueprintAsyncActionBase* WaitTickCounter(UObject* WorldContextObject, ATickTestActor* TickTestActor, int32 TargetCounter);
};
MyBluprintAsyncActionBase.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyBlueprintAsyncActionBase.h"
UMyBlueprintAsyncActionBase* UMyBlueprintAsyncActionBase::WaitTickCounter(UObject* WorldContextObject,
ATickTestActor* TickTestActor, int32 TargetCounter)
{
//基类是一个Uobject,因此不需要使用智能指针来管理
UMyBlueprintAsyncActionBase* Node = NewObject<UMyBlueprintAsyncActionBase>();
auto Function = [Node, WorldContextObject, TickTestActor, TargetCounter]()
{
if (IsValid(TickTestActor))
{
if (TickTestActor->Counter >= TargetCounter)
{
UE_LOG(LogTemp, Log, TEXT(__FUNCTION__"@%u wait TargetCounter(%d) success. Current Counter=[%d]."), __LINE__, TargetCounter, TickTestActor->Counter);
Node->Success.Broadcast(TickTestActor->Counter);
WorldContextObject->GetWorld()->GetTimerManager().ClearTimer(Node->TimerHandle);
}
else
{
UE_LOG(LogTemp, Log, TEXT(__FUNCTION__"@%u waiting TargetCounter(%d)... Current Counter=[%d]."), __LINE__, TargetCounter, TickTestActor->Counter);
}
}
else
{
UE_LOG(LogTemp, Log, TEXT(__FUNCTION__"@%u wait fail. Actor is invalid."), __LINE__);
Node->Failed.Broadcast(-1);
WorldContextObject->GetWorld()->GetTimerManager().ClearTimer(Node->TimerHandle);
}
};
WorldContextObject->GetWorld()->GetTimerManager().SetTimer(Node->TimerHandle, FTimerDelegate::CreateLambda(Function), 0.1f, true);
UE_LOG(LogTemp, Log, TEXT(__FUNCTION__"@%u wait TargetCounter(%d) begin."), __LINE__, TargetCounter);
return Node;
}