Custom latent – delayed Blueprint function in C++

I wanted to make two functions with delays to assist BP scripting of logic. One for dialog and one for item interaction. The one I’ll cover here handles dialog.

My goal: Make a function that accepts dialog parameters, waits for a user response and returns the player’s selection. (Additionally, make a breakout which flows execution based on the player’s selection.)

I couldn’t find any help online when writing this function. So I crept through engine code. Unreal has a LatentActionManager held by UWorld. You can get its reference from GetWorld->GetLatentActionManager()

Using this I made a c++ class derived from FPendingLatentAction and put our own logic in it.

Here is the BP functions header:

1 UFUNCTION(BlueprintCallable, Meta = (ExpandEnumAsExecs = "PlayerSelection"), Category = "Dialog", meta = (Latent, WorldContext = "WorldContextObject", LatentInfo = "LatentInfo"))
2 void CharacterDialog(UObject* WorldContextObject,
3                                FString CharactersDialog,
4                                FString ResponseOptionA,
5                                bool ShowResponseB,
6                                FString ResponseOptionB,
7                                bool ShowResponseC,
8                                FString ResponseOptionC,
9                                bool ShowResponseD,
10                               FString ResponseOptionD,
11                               struct FLatentActionInfo LatentInfo,
12                               EActorDialogResults& PlayerSelection);

Here is the function’s definition:

1 //
2 //*launch dialog action, which will wait for player response*/
3 void ABaseCharacter::CharacterDialog(UObject* WorldContextObject,
4                                     FString CharactersDialog,
5                                     FString ResponseOptionA,
6                                     bool ShowResponseB,
7                                     FString ResponseOptionB,
8                                     bool ShowResponseC,
9                                     FString ResponseOptionC,
10                                    bool ShowResponseD,
11                                    FString ResponseOptionD,
12                                    struct FLatentActionInfo LatentInfo,
13                                    EActorDialogResults& PlayerSelection)
14 {
15    FDialogStruct Dialog = FDialogStruct(DialogPortrait, CharactersDialog, ResponseOptionA, ShowResponseB, ResponseOptionB, ShowResponseC, ResponseOptionC, ShowResponseD, ResponseOptionD);
    
16    /**launch dialog delay action, which will wait for player response to dialog*/
17    if (UWorld* World = GEngine->GetWorldFromContextObjectChecked(WorldContextObject))
18    {
19        FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
20        if (LatentActionManager.FindExistingAction<FDialogDelayAction>(LatentInfo.CallbackTarget, LatentInfo.UUID) == NULL)
21        {
22            LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, new FDialogDelayAction(Dialog, TempReferenceIComponentRequestingDialog, NULL, LatentInfo, PlayerSelection));
23        }
24    }
25 }

What happens above, we grab the LatentActionManager and make sure it currently isn’t running a task for this object and pass it our custom latent task, along with our custom latent tasks construction params.  InteractionInfo is a custom struct with the players choices, and PlayerSelection is an enum of the possible responses the player can have.

Let’s look at my custom FPendingLatentAction derived class:

1 #include "UObject/WeakObjectPtr.h"
2 #include "Engine/LatentActionManager.h"
3 #include "LatentActions.h"
4 #include "DialogContainer.h"
5 #include "ActorDialogComponent.h"
6 
7 // Based off FDelayAction
8 // Sends dialog to passed in ActorDialogComponent classes delegate to begin dialog
9 //waits for a response
10 class FDialogDelayAction : public FPendingLatentAction
11 {
12 public:
13 FDialogStruct DialogToProcess;
14 FName ExecutionFunction;
15 int32 OutputLink;
16 FWeakObjectPtr CallbackTarget;
17 UActorDialogComponent* DialogComponentCallback;
18 class UBusterInteractionComponent* IComponentCallback;
19 int32 Count = 0;
20 EActorDialogResults* DialogResults = nullptr;
21 
22 /**
23        * @param DialogComponent - Pointer to DialogComponent that called this function.
24 * @param LatentInfo - latent info for callback
25 */
26 FDialogDelayAction(const FDialogStruct& Dialog, UBusterInteractionComponent* 27 IComponentCalling, UActorDialogComponent* DialogComponent, const FLatentActionInfo& LatentInfo, EActorDialogResults& PlayerSelection);
27 
28 virtual void UpdateOperation(FLatentResponse& Response) override;
29 
30 void ReceivePlayerSelection(EActorDialogResults PlayerSelection);
31 };

Constructor:

1  #include "DialogDelayAction.h"
2  #include "BusterInteractionComponent.h"
3 
4  FDialogDelayAction::FDialogDelayAction(const FDialogStruct& Dialog, UBusterInteractionComponent* IComponentCalling, UActorDialogComponent* DialogComponent, const FLatentActionInfo& LatentInfo, EActorDialogResults& PlayerSelection)
5  {
6  DialogToProcess = Dialog;
7  ExecutionFunction = LatentInfo.ExecutionFunction;
8  OutputLink = LatentInfo.Linkage;
9  CallbackTarget = LatentInfo.CallbackTarget;
10 DialogComponentCallback = DialogComponent;
11 DialogResults = &PlayerSelection;
12 IComponentCallback = IComponentCalling;
13 
14 //initiate dialog UI b
15 if (IComponentCallback)
16 {
17 *DialogResults = EActorDialogResults::NoSelectionYet;
18 //Send dialog to BusterInteractionComponent that started process, and wait for response
19 IComponentCallback->ReceiveRequestedDialog(Dialog, this);
20 }
21 else
22 {
23 UE_LOG(LogTemp, Warning, TEXT("ERROR W/ I COMPONENT COMPONENT POINTER IN DIALOG DELAY ACTION "));
24 }
25 }
26 
27 void FDialogDelayAction::UpdateOperation(FLatentResponse& Response)
28 {
29 Response.FinishAndTriggerIf(*DialogResults != EActorDialogResults::NoSelectionYet, ExecutionFunction, OutputLink, CallbackTarget);
30 }
31 
32 void FDialogDelayAction::ReceivePlayerSelection(EActorDialogResults PlayerSelection)
33 {
34 *DialogResults = PlayerSelection;
35 }

I create a callback (I send a self reference to the actor who called this function.) Once the player has selected input, I push a response to this delayed action.

1 void FInteractionDelayAction::ReceivePlayerSelection(EInteractionResults PlayerSelection)
2 {
3 *InteractionResults = PlayerSelection;
4 }

One could also poll from the UpdateOperation. The tick/update function notes a value has been set and completes the task.

1 void FInteractionDelayAction::UpdateOperation(FLatentResponse& Response)
2 {
3 Response.FinishAndTriggerIf(*InteractionResults != EInteractionResults::NoSelectionYet, ExecutionFunction, OutputLink, CallbackTarget);
4 }

If you want to break out the response use the ExpandEnumAsExecs UFunction specifier:

1 /*EXAMPLE BLUEPRINT INTERACTION SELECTION BREAKOUT*/
2 UFUNCTION(BlueprintCallable, Category = "Interactable Object", Meta = (ExpandEnumAsExecs = "OutPaths"))
3 void BreakoutInteractionSelection(const EInteractionResults& PlayerSelection, EInteractionResults& OutPaths);