I have a class inheriting from UBlueprintAsyncActionBase which works in Blueprint, but I haven’t managed to spawn an instance of the task in C++ and get it to activate. How can I do that?
I looked at how the Blueprint K2Node_BaseAsyncTask activated the task. In UK2Node_BaseAsyncTask::ExpandNode after all node connections has been made it just calls the Activate function by name which in K2Node_AsyncTask is overridden to “Activate”.
K2Node_BaseAsyncTask.cpp line 450:
// Create a call to activate the proxy object if necessary
if (ProxyActivateFunctionName != NAME_None)
{
UK2Node_CallFunction* const CallActivateProxyObjectNode = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph);
CallActivateProxyObjectNode->FunctionReference.SetExternalMember(ProxyActivateFunctionName, ProxyClass);
CallActivateProxyObjectNode->AllocateDefaultPins();
// Hook up the self connection
UEdGraphPin* ActivateCallSelfPin = Schema->FindSelfPin(*CallActivateProxyObjectNode, EGPD_Input);
check(ActivateCallSelfPin);
bIsErrorFree &= Schema->TryCreateConnection(ProxyObjectPin, ActivateCallSelfPin);
// Hook the activate node up in the exec chain
UEdGraphPin* ActivateExecPin = CallActivateProxyObjectNode->FindPinChecked(UEdGraphSchema_K2::PN_Execute);
UEdGraphPin* ActivateThenPin = CallActivateProxyObjectNode->FindPinChecked(UEdGraphSchema_K2::PN_Then);
bIsErrorFree &= Schema->TryCreateConnection(LastThenPin, ActivateExecPin);
LastThenPin = ActivateThenPin;
}
I thought it would be a more sophisticated system where actions registered with the GameInstance would get activated automatically after a few frames, but I guess it fulfills its purpose in a much safer and simpler way. This means that in C++, you have to make the overridden UBlueprintAsyncActionBase::Activate public and call it yourself after binding all delegates, which themselves have to be public.
Example UBlueprintAsyncActionBase:
.h
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "PrintInSeconds.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FPrintInSecondsCompleteDelegate, bool, bSuccess);
/**
*
*/
UCLASS()
class MYPROJECT_API UPrintInSeconds : public UBlueprintAsyncActionBase
{
GENERATED_BODY()
public:
/** Broadcasted when task has finished, public so C++ can access */
UPROPERTY(BlueprintAssignable)
FPrintInSecondsCompleteDelegate Complete;
protected:
/** Message to be printed */
FString Message;
/** Time to wait before print */
float TimeSeconds;
/** TimerHandle instance */
FTimerHandle TimerHandle;
public:
UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly="true", WorldContext="WorldContextObject"))
static UPrintInSeconds* PrintInSeconds(UObject* WorldContextObject, const FString& Message, float TimeSeconds);
//~ Begin UBlueprintAsyncActionBase Interface
virtual void Activate() override;
//~ End UBlueprintAsyncActionBase Interface
protected:
void PrintMessage();
};
.cpp
#include "PrintInSeconds.h"
UPrintInSeconds* UPrintInSeconds::PrintInSeconds(UObject* WorldContextObject, const FString& Message, float TimeSeconds)
{
UPrintInSeconds* Task = NewObject<UPrintInSeconds>(WorldContextObject);
Task->RegisterWithGameInstance(WorldContextObject);
if (Task && Task->RegisteredWithGameInstance.IsValid())
{
Task->Message = Message;
Task->TimeSeconds = TimeSeconds;
}
else
{
Task->Complete.Broadcast(false);
Task->SetReadyToDestroy();
}
return Task;
}
void UPrintInSeconds::Activate()
{
if (GetWorld() != nullptr)
{
GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &UPrintInSeconds::PrintMessage, TimeSeconds, false);
return;
}
Complete.Broadcast(false);
SetReadyToDestroy();
}
void UPrintInSeconds::PrintMessage()
{
UE_LOG(LogTemp, Warning, TEXT("PRINTING MESSAGE: '%s'"), *Message);
Complete.Broadcast(true);
SetReadyToDestroy();
}
Example spawn & activate async task Blueprint:
Example spawn & activate async task C++:
.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "MyProjectGameModeBase.generated.h"
/**
*
*/
UCLASS()
class MYPROJECT_API AMyProjectGameModeBase : public AGameModeBase
{
GENERATED_BODY()
protected:
/** Demonstration for registering callback to task */
UFUNCTION()
void TaskComplete(bool bSuccess);
//~ Begin AActor Interface
virtual void BeginPlay() override;
//~ End AActor Interface
};
.cpp
#include "MyProjectGameModeBase.h"
#include "PrintInSeconds.h"
void AMyProjectGameModeBase::BeginPlay()
{
UPrintInSeconds* Task = UPrintInSeconds::PrintInSeconds(this, TEXT("Hello :3"), 3);
Task->Complete.AddDynamic(this, &AMyProjectGameModeBase::TaskComplete);
Task->Activate();
}
void AMyProjectGameModeBase::TaskComplete(bool bSuccess)
{
UE_LOG(LogTemp, Warning, TEXT("Print in Seconds: bSuccess=%s"), bSuccess ? TEXT("true") : TEXT("false"));
}
I do feel pretty stupid after this one.
MyProjectGameModeBase.cpp (567 Bytes)
MyProjectGameModeBase.h (553 Bytes)
PrintInSeconds.cpp (1.1 KB)
PrintInSeconds.h (1.2 KB)
nice code.
just one comment. i think this broadcast won’t work the way it might look as.
since the object is created inside PrintInSeconds
and nothing can bind after the function finishes, the delegate is almost warrantied to not have any binding.
also just for completion here’ s a link to the old ue wiki Creating Asynchronous Blueprint Nodes - Old UE4 Wiki
(personally i prefer using simple objects with delegates instead of async bp nodes, since the latter can’ t be used inside functions, and in the end they work the same but the prior are simpler and similar to use on cpp and bps)