I will leave the question as to if is the best solution to other people, but if you want a blue print node that will delay a action until the next tick/frame, then it is quite easy to do using c++. As you said you use c++, I’m taking it that you don’t need a solution for a blueprint only project.
If you look at the code for the Delay node then it is quite simple.
class FDelayAction : public FPendingLatentAction
{
public:
float TimeRemaining;
FName ExecutionFunction;
int32 OutputLink;
FWeakObjectPtr CallbackTarget;
FDelayAction(float Duration, const FLatentActionInfo& LatentInfo)
: TimeRemaining(Duration)
, ExecutionFunction(LatentInfo.ExecutionFunction)
, OutputLink(LatentInfo.Linkage)
, CallbackTarget(LatentInfo.CallbackTarget)
{
}
virtual void UpdateOperation(FLatentResponse& Response)
{
TimeRemaining -= Response.ElapsedTime();
Response.FinishAndTriggerIf(TimeRemaining <= 0.0f, ExecutionFunction, OutputLink, CallbackTarget);
}
#if WITH_EDITOR
// Returns a human readable description of the latent operation's current state
virtual FString GetDescription() const OVERRIDE
{
return FString::Printf( *NSLOCTEXT("DelayAction", "DelayActionTime", "Delay (%.3f seconds left)").ToString(), TimeRemaining);
}
#endif
};
The UpdateOperation method is called every frame/tick, and then triggers when the delay time has elapsed. If you wanted it to trigger on the next tick, in principle you would just need to change the
Response.FinishAndTriggerIf(TimeRemaining <= 0.0f, ExecutionFunction, OutputLink, CallbackTarget);
to
Response.FinishAndTriggerIf(true, ExecutionFunction, OutputLink, CallbackTarget);
However that doesn’t always quite work, as it seems that the UpdateOperation is sometimes called (later) in the same frame as the delay was added. For example happens if the delay was called from the “Receive Tick” event, because the LatentActions are ticked after the “Receive Tick” event is called.
Anyway that would be easily fixed by just waiting for two calls to the UpdateOperation method when used in a Received Tick event. Or if you wanted the callback to be called on every tick, then change the Response.FinishAndTriggerIf line to :
Response.TriggerLink(ExecutionFunction, OutputLink, CallbackTarget);
However one problem with is there would be no way to ever stop it from triggering on every tick. Still that might be what you want.
Of course you don’t need to change the actual delay node, but can rather add a new node. The following two files included in a standard project will add a “RepeatEveryTick” node that triggers on every tick, and a “DelayForTicks” node, that allows the number of ticks to wait to be set. If you want it to wait for the next tick, then you would normally set 1, unless you called it from the “Receive Tick” event.
////////LatentBlueprintLibrary.h////////
#include "LatentActions.h"
#include "DelayAction.h"
#include "LatentBlueprintLibrary.generated.h"
#pragma once
/**
*
*/
UCLASS()
class ULatentBlueprintLibrary : public UBlueprintFunctionLibrary
{
GENERATED_UCLASS_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Utilities|FlowControl", meta = (Latent, HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject", LatentInfo = "LatentInfo"))
static void RepeatEveryTick(UObject* WorldContextObject, struct FLatentActionInfo LatentInfo);
UFUNCTION(BlueprintCallable, Category = "Utilities|FlowControl", meta = (Latent, HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject", LatentInfo = "LatentInfo", Ticks = "1"))
static void DelayForTicks(UObject* WorldContextObject, int32 Ticks, struct FLatentActionInfo LatentInfo);
};
class FRepeatTickAction : public FPendingLatentAction
{
public:
FName ExecutionFunction;
int32 OutputLink;
FWeakObjectPtr CallbackTarget;
FRepeatTickAction( const FLatentActionInfo& LatentInfo)
: ExecutionFunction(LatentInfo.ExecutionFunction)
, OutputLink(LatentInfo.Linkage)
, CallbackTarget(LatentInfo.CallbackTarget)
{
}
virtual void UpdateOperation(FLatentResponse& Response)
{
Response.TriggerLink( ExecutionFunction, OutputLink, CallbackTarget);
}
#if WITH_EDITOR
// Returns a human readable description of the latent operation's current state
virtual FString GetDescription() const OVERRIDE
{
return FString::Printf(*NSLOCTEXT("RepeatAction", "RepeatActionTime", "Repeat").ToString(), 0);
}
#endif
};
class FDelayTicksAction : public FPendingLatentAction
{
public:
int32 TimeRemaining;
FName ExecutionFunction;
int32 OutputLink;
FWeakObjectPtr CallbackTarget;
FDelayTicksAction(int32 Duration, const FLatentActionInfo& LatentInfo)
: TimeRemaining(Duration)
, ExecutionFunction(LatentInfo.ExecutionFunction)
, OutputLink(LatentInfo.Linkage)
, CallbackTarget(LatentInfo.CallbackTarget)
{
}
virtual void UpdateOperation(FLatentResponse& Response)
{
TimeRemaining -= 1;
Response.FinishAndTriggerIf(TimeRemaining <= 0, ExecutionFunction, OutputLink, CallbackTarget);
}
#if WITH_EDITOR
// Returns a human readable description of the latent operation's current state
virtual FString GetDescription() const OVERRIDE
{
return FString::Printf(*NSLOCTEXT("DelayTicksAction", "DelayActionTicks", "Delay (%d ticks left)").ToString(), TimeRemaining);
}
#endif
};
////////LatentBlueprintLibrary.cpp////////
#include "Myprojectheader.h" //change to the standard header for your project
#include "LatentBlueprintLibrary.h"
ULatentBlueprintLibrary::ULatentBlueprintLibrary(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{
}
void ULatentBlueprintLibrary::RepeatEveryTick(UObject* WorldContextObject, FLatentActionInfo LatentInfo)
{
if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject))
{
FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
if (LatentActionManager.FindExistingAction<FRepeatTickAction>(LatentInfo.CallbackTarget, LatentInfo.UUID) == NULL)
{
LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, new FRepeatTickAction( LatentInfo));
}
}
}
void ULatentBlueprintLibrary::DelayForTicks(UObject* WorldContextObject, int32 Ticks, FLatentActionInfo LatentInfo)
{
if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject))
{
FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
if (LatentActionManager.FindExistingAction<FDelayTicksAction>(LatentInfo.CallbackTarget, LatentInfo.UUID) == NULL)
{
LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, new FDelayTicksAction(Ticks, LatentInfo));
}
}
}