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);