While our game may be running without any issues in the editor or even in…
Implementing a basic Dialog System in C++
In this post we’re going to implement a basic dialog system in C++. Here is a breakdown of our system:
- The player can initiate a conversation with a pawn
- The pawn will answer to our player based on the player’s message
- The player can terminate the conversation
Before we dive into creating our system, here is the end-result:
Disclaimer: I’ve used some Blueprint code on my Character’s blueprint just to avoid classes which fall off the scope of this tutorial. In case you want to create this system using C++ only, you can read this post in order to convert my logic to C++.
You can find all the code for this tutorial on my github repo.
Gathering the necessary assets
For this system we need some audio recordings. I used audacity to record myself and then I imported my recordings inside the engine (don’t record yourself just yet! You will know why in a bit!)
In total I’ve used 5 recordings:
- Players’ hi message (and the Pawns’ answer)
- Players’ small talk message (and the Pawns’ answer)
- Players’ goodbye message
Setting up our project
Create a Third Person C++ Template Project and add the following keybind:
In order to determine when our UI should show the subtitles of our dialogs we’re going to use two structs. The first, will include the excerpt of the players’ choice (this will appear inside the buttons of the UI), the corresponding sfx, an array of subtitles and a bool variable which will be used in order to determine if we want the other pawn to respond.
The second struct is the subtitle itself. Each subtitle includes the associated text that will be shown inside our UI as well as the associated time, in seconds, which is the time that the subtitle will appear on our screen. This is identical to how subtitles work for movies! Note that the time in this case is relative – an associated time of 0 seconds means that the subtitle should appear when our sfx starts. An associated time of 1 second means that the subtitle should appear 1 second after our SFX has started playing.
Since I recorded myself using audacity, I’ve used it’s interface to determine the actual time for each subtitle.
Let’s take a look at the sound wave of the second recording which contains the following subtitles:
- Hey this is a pretty nice place…
- right?
Notice that I have selected the time right before the second subtitle should go off and audacity informs me (see the red arrow of the screenshot) that this sound will be played after 2.7 seconds. This value is the associated time. Now you know the workflow to find the correct associated time without hardcoding or guessing!
I suggest to create your recordings at this point since we’ll be focusing on our code from now on.
Note: As a convention from this point I will refer to the second pawn as an AI. Don’t get confused – this post contains no AI at all!
Having said that, let’s add the following C++ structs (in case you don’t remember how to add a c++ struct, check out this post):
- A struct named Dialog
- and a struct named Subtitle
Here is my code for the Subtitle struct:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
USTRUCT(BlueprintType) struct FSubtitle : public FTableRowBase { GENERATED_USTRUCT_BODY() /*The subtitle that will be displayed for a specific period of time in our UI*/ UPROPERTY(EditAnywhere) FString Subtitle; /*The relative time in seconds, that the subtitle will appear*/ UPROPERTY(EditAnywhere) float AssociatedTime; }; |
Don’t forget to include the “Engine/DataTable.h” library
Here is my code for the Dialog struct:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
USTRUCT(BlueprintType) struct FDialog : public FTableRowBase { GENERATED_USTRUCT_BODY() /*The question's excerpt - this will be shown inside our UI*/ UPROPERTY(EditAnywhere) FString QuestionExcerpt; /*The actual SFX that we're going to play*/ UPROPERTY(EditAnywhere) USoundBase* SFX; /*An array of the associated subtitles*/ UPROPERTY(EditAnywhere) TArray<FSubtitle> Subtitles; /*True if we want to wait for the AI to respond*/ UPROPERTY(EditAnywhere) bool bShouldAIAnswer; }; |
Moreover, in the dialog struct add the following includes:
1 2 |
#include "Engine/DataTable.h" #include "Subtitle.h" |
Compile your code and switch to your editor. Then, add a new Data Table, named PlayerLines, based on the Dialog struct and add the following rows:
When you’re done with that, create a second data table, named AILines, based on the dialog struct and populate it using the same workflow:
Make sure that both the Player Lines and the AI Lines have the same row names. This has to do with the code that we’re going to implement later on.
In the AI table you can omit some fields, like QuestionExcerpt and the ShouldAnswer variable because we won’t use them.
Creating our dummy AI
Create a new C++ class, named AICharacter, which inherits the character class and add the following code inside the header file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
private: UFUNCTION() void OnBoxOverlap(AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherIndex, bool bFromSweep, const FHitResult& SweepResult); UFUNCTION() void OnBoxEndOverlap(AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherIndex); UFUNCTION() void Talk(USoundBase* SFX, TArray<FSubtitle> Subs); public: /*Answers to the character after a specified delay*/ void AnswerToCharacter(FName PlayerLine, TArray<FSubtitle>& SubtitlesToDisplay, float delay); /*Retrieves the corresponding character lines*/ UDataTable* GetPlayerLines() { return PlayerLines; } protected: /*If the player is inside this box component he will be able to initiate a conversation with the pawn*/ UPROPERTY(VisibleAnywhere) UBoxComponent* BoxComp; /*The audio component responsible for playing the dialog coming from this pawn*/ UPROPERTY(VisibleAnywhere) UAudioComponent* AudioComp; /*The player lines - each pawn can offer different dialog options for our character*/ UPROPERTY(EditAnywhere, Category = DialogSystem) UDataTable* PlayerLines; /*The ai lines*/ UPROPERTY(EditAnywhere, Category = DialogSystem) UDataTable* AILines; |
Don’t forget to include the following libraries:
1 2 |
#include "Engine/DataTable.h" #include "Subtitle.h" |
For now, implement all the functions we’ve added by adding no code inside them. We will implement them later on!
Switch to the source file of your AI and add the following initializations inside the constructor:
1 2 3 4 5 6 |
//Init the box and audio comps BoxComp = CreateDefaultSubobject<UBoxComponent>(FName("BoxComp")); BoxComp->AttachTo(GetRootComponent()); AudioComp = CreateDefaultSubobject<UAudioComponent>(FName("AudioComp")); AudioComp->AttachTo(GetRootComponent()); |
Then, modify your begin play functions to match the following code:
1 2 3 4 5 6 7 8 9 10 |
// Called when the game starts or when spawned void AAICharacter::BeginPlay() { Super::BeginPlay(); //Register the begin and end overlap functions BoxComp->OnComponentBeginOverlap.AddDynamic(this, &AAICharacter::OnBoxOverlap); BoxComp->OnComponentEndOverlap.AddDynamic(this, &AAICharacter::OnBoxEndOverlap); } |
Compile and save your code. Switch to your editor and create a blueprint based on the above c++ class. Then:
- Assign the default static mesh and the default animation blueprint
- Increase the box comp extent to a relative large scale (I’ve used 250 x 250 x 100)
Let’s leave the AI for now. We’ll come back later on to implement more of it’s functionality.
Creating our UI
Navigate to your [Your_Project_Name].Build.cs file and add the following line of code:
1 |
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "UMG", "Slate", "SlateCore" }); |
Then, navigate to your [Your_Project_Name].h file and add the following includes:
1 2 3 4 5 |
#include "Runtime/UMG/Public/UMG.h" #include "Runtime/UMG/Public/UMGStyle.h" #include "Runtime/UMG/Public/Blueprint/UserWidget.h" #include "Runtime/UMG/Public/Slate/SObjectWidget.h" #include "Runtime/UMG/Public/IUMGModule.h" |
Then, switch to your editor and add a new C++ class, which inherits from the UserWidget class. I named my class DialogUI. Open up the header file and add the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public: /*This property will be used in order to bind our subtitles Binding will make sure to notify the UI if the content of the following variable change.*/ UPROPERTY(BlueprintReadOnly) FString SubtitleToDisplay; /*Updates the displayed subtitles based on the given array*/ UFUNCTION(BlueprintCallable, Category = DialogSystem) void UpdateSubtitles(TArray<FSubtitle> Subtitles); /*This array will populate our buttons from within the show function*/ UPROPERTY(VisibleAnywhere, BlueprintReadWrite) TArray<FString> Questions; /*Adds the widget to our viewport and populates the buttons with the given questions*/ UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category = DialogSystem) void Show(); |
Moreover, don’t forget to add the Dialog.h file.
Implement an empty logic for the UpdateSubtitles function for now.
Compile and save your code!
Create a blueprint based on the above C++ class and implement the following UI:
Make sure to bind the variable SubtitleToDisplay in the middle text box.
Moreover, mark the Q1,Q2 and Q3 text boxes as variables.
Switch to the Graph tab of the UMG editor and implement the following custom event:
Note: You could bind C++ properties to these text boxes as well. I just used this way instead in this case!
Let’s leave the UMG for now and implement some logic on our character!
Creating the logic for our character
Navigate to your character’s header file and add the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
private: /*True if the player is currently talking with any pawn*/ bool bIsTalking; /*True if the player is inside a valid range to start talking to a pawn*/ bool bIsInTalkRange; /*Initiates or terminates a conversation*/ void ToggleTalking(); /*The pawn that the player is currently talking to*/ AAICharacter* AssociatedPawn; /*A reference to our lines - retrieved from the associated pawn*/ UDataTable* AvailableLines; /*Searches in the given row inside the specified table*/ FDialog* RetrieveDialog(UDataTable* TableToSearch, FName RowName); public: /*Generates the player lines*/ void GeneratePlayerLines(UDataTable& PlayerLines); /*This array is essentially an Array of Excerpts from our dialogs!*/ UPROPERTY(BlueprintReadOnly) TArray<FString> Questions; /*Performs the actual talking - informs the associated pawn if necessary in order to answer The subtitles array contain all the subtitles for this talk - it should be passed to our UI*/ UFUNCTION(BlueprintCallable, Category = DialogSystem) void Talk(FString Excerpt, TArray<FSubtitle>& Subtitles); /*Enables / disables our talk ability. The player can't talk if he's not in a valid range*/ void SetTalkRangeStatus(bool Status) { bIsInTalkRange = Status; } /*Sets a new associated pawn*/ void SetAssociatedPawn(AAICharacter* Pawn) { AssociatedPawn = Pawn; } /*Retrieves the UI reference*/ UDialogUI* GetUI() { return UI; } protected: /*The component responsible for playing our SFX*/ UPROPERTY(VisibleAnywhere) UAudioComponent* AudioComp; /*Opens or closes the conversation UI*/ UFUNCTION(BlueprintImplementableEvent, Category = DialogSystem) void ToggleUI(); /*UI Reference*/ UPROPERTY(VisibleAnywhere, BlueprintReadWrite) UDialogUI* UI; |
Don’t forget to add the following includes before the .generated.h file:
1 2 |
#include "AICharacter.h" #include "DialogUI.h" |
Switch to your character’s source file and inside the constructor add the following code:
1 2 3 4 5 6 |
bIsTalking = false; bIsInTalkRange = false; AssociatedPawn = nullptr; AudioComp = CreateDefaultSubobject<UAudioComponent>(FName("AudioComp")); AudioComp->AttachTo(GetRootComponent()); |
Then, let’s add the bind we’ve added through our editor. Go inside the SetupPlayerInputComponent function and add the following line of code:
1 |
InputComponent->BindAction("Talk", IE_Pressed, this, &ADialogSystemCharacter::ToggleTalking); |
Moreover, I modified my MoveRight and MoveForward functions in order to disable the player movement if he’s talking like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
void ADialogSystemCharacter::MoveForward(float Value) { if ((Controller != NULL) && (Value != 0.0f) && !bIsTalking) { // find out which way is forward const FRotator Rotation = Controller->GetControlRotation(); const FRotator YawRotation(0, Rotation.Yaw, 0); // get forward vector const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X); AddMovementInput(Direction, Value); } } void ADialogSystemCharacter::MoveRight(float Value) { if ( (Controller != NULL) && (Value != 0.0f) && !bIsTalking ) { // find out which way is right const FRotator Rotation = Controller->GetControlRotation(); const FRotator YawRotation(0, Rotation.Yaw, 0); // get right vector const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y); // add movement in that direction AddMovementInput(Direction, Value); } } |
When you’re done with that, implement the Toggle Talking function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void ADialogSystemCharacter::ToggleTalking() { if (bIsInTalkRange) { //If we are in talk range handle the talk status and the UI bIsTalking = !bIsTalking; ToggleUI(); if (bIsTalking && AssociatedPawn) { //The associated pawn is polite enough to face us when we talk to him! FVector Location = AssociatedPawn->GetActorLocation(); FVector TargetLocation = GetActorLocation(); AssociatedPawn->SetActorRotation((TargetLocation - Location).Rotation()); } } } |
Right here, create an empty body for the rest functions and let’s implement the logic for the ToggleUI function:
Then, implement the following logic for the GeneratePlayerLines:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
void ADialogSystemCharacter::GeneratePlayerLines(UDataTable& PlayerLines) { //Get all the row names of the table TArray<FName> PlayerOptions = PlayerLines.GetRowNames(); //For each row name try to retrieve the contents of the table for (auto It : PlayerOptions) { //Retrieve the contents of the table FDialog* Dialog = RetrieveDialog(&PlayerLines, It); if (Dialog) { //We retrieved a valid row - populate the questions array with our excerpts Questions.Add(Dialog->QuestionExcerpt); } } //Make sure to create a reference of the available line for later use AvailableLines = &PlayerLines; } |
In order for the above function to work, we also need to implement the Retrieve Dialog function:
1 2 3 4 5 6 7 8 |
FDialog* ADialogSystemCharacter::RetrieveDialog(UDataTable* TableToSearch, FName RowName) { if(!TableToSearch) return nullptr; //The table is valid - retrieve the given row if possible FString ContextString; return TableToSearch->FindRow<FDialog>(RowName, ContextString); } |
As a last step, implement the following logic for the Talk function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
void ADialogSystemCharacter::Talk(FString Excerpt, TArray<FSubtitle>& Subtitles) { //Get all the row names based on our stored lines TArray<FName> PlayerOptions = AvailableLines->GetRowNames(); for (auto It : PlayerOptions) { //Search inside the available lines table to find the pressed Excerpt from the UI FDialog* Dialog = RetrieveDialog(AvailableLines, It); if (Dialog && Dialog->QuestionExcerpt == Excerpt) { //We've found the pressed excerpt - assign our sfx to the audio comp and play it AudioComp->SetSound(Dialog->SFX); AudioComp->Play(); //Update the corresponding subtitles Subtitles = Dialog->Subtitles; if (UI && AssociatedPawn && Dialog->bShouldAIAnswer) { //Calculate the total displayed time for our subtitles //When the subtitles end - the associated pawn will be able to talk to our character TArray<FSubtitle> SubtitlesToDisplay; float TotalSubsTime = 0.f; for (int32 i = 0; i < Subtitles.Num(); i++) { TotalSubsTime += Subtitles[i].AssociatedTime; } //Just a hardcoded value in order for the AI not to answer right after our subs. //It would be better if we expose that to our editor? Sure! TotalSubsTime += 1.f; //Tell the associated pawn to answer to our character after the specified time! AssociatedPawn->AnswerToCharacter(It, SubtitlesToDisplay, TotalSubsTime); } else if (!Dialog->bShouldAIAnswer) ToggleTalking(); break; } } } |
Now we are ready to implement the logic inside our dummy AI.
Revisiting our dummy AI
Navigate to the source file of your ai and add the following implementations of the overlap functions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
void AAICharacter::OnBoxOverlap(AActor * OtherActor, UPrimitiveComponent * OtherComp, int32 OtherIndex, bool bFromSweep, const FHitResult & SweepResult) { if (OtherActor->IsA<ADialogSystemCharacter>()) { ADialogSystemCharacter* Char = Cast<ADialogSystemCharacter>(OtherActor); Char->SetTalkRangeStatus(true); Char->GeneratePlayerLines(*PlayerLines); Char->SetAssociatedPawn(this); } } void AAICharacter::OnBoxEndOverlap(AActor * OtherActor, UPrimitiveComponent * OtherComp, int32 OtherIndex) { if (OtherActor->IsA<ADialogSystemCharacter>()) { ADialogSystemCharacter* Char = Cast<ADialogSystemCharacter>(OtherActor); Char->SetTalkRangeStatus(false); Char->SetAssociatedPawn(nullptr); } } |
Then, add the following implementation of the AnswerToCharacter and Talk functions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
void AAICharacter::Talk(USoundBase * SFX, TArray<FSubtitle> Subs) { ADialogSystemCharacter* Char = Cast<ADialogSystemCharacter>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0)); //Play the corresponding sfx AudioComp->SetSound(SFX); AudioComp->Play(); //Tell the UI to update with the new subtitles Char->GetUI()->UpdateSubtitles(Subs); } void AAICharacter::AnswerToCharacter(FName PlayerLine, TArray<FSubtitle>& SubtitlesToDisplay, float delay) { if (!AILines) return; //Retrieve the corresponding line FString ContextString; FDialog* Dialog = AILines->FindRow<FDialog>(PlayerLine, ContextString); ADialogSystemCharacter* MainChar = Cast<ADialogSystemCharacter>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0)); if (Dialog && MainChar) { FTimerHandle TimerHandle; FTimerDelegate TimerDel; TimerDel.BindUFunction(this, FName("Talk"), Dialog->SFX, Dialog->Subtitles); //Talk to the player after the delay time has passed GetWorld()->GetTimerManager().SetTimer(TimerHandle, TimerDel, delay, false); } } |
Revisiting our UI
The only thing left is to associate the button click with each question excerpt and show our subtitles! Open up the graph of your UMG and create the following function:
Note that The Associated Text Block is a Text Reference.
Then, implement the following OnClick functionality for each button:
The last piece of code is kinda catchy, so before we type in our code, I will explain why we need this particular approach.
Multithreading in Unreal
As users, we need every software application to be responsible at any given time, even if the application is going into crash we need to be informed so we can take action. This is done by using Multithreading.
In every application (including games) the thread that is responsible for the UI is considered as the main thread. By using multiple cores of our CPU in our application we are able to perform relatively heavy calculations in another thread while our main thread remains responsive.
In this post we’ve said that we’re going to wait for a certain amount of time before we display our subtitles, which is totally fine. But if we “freeze” our UI the whole game will freeze. You definitely don’t want that! That’s why, I’ve used multithreading in order to update the subtitles in time.
I’ve created a low priority thread in order to update my UI subtitles. This thread literally pauses it’s execution until it’s time for the next subtitle to be shown. Since this thread is a different than the main thread our game won’t get paused and the user won’t notice a thing! Luckily, Unreal Engine provides an easy way to achieve multithreading!
Right under the end of the DialogUI class, add the following class (you don’t need to use the editor in order to add this particular class!):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
class UMGAsyncTask : public FNonAbandonableTask { /*The subtitles that we're going to display*/ TArray<FSubtitle> Subs; /*UI Reference*/ UDialogUI* DialogUI; public: //Constructor UMGAsyncTask(TArray<FSubtitle>& Subs, UDialogUI* DialogUI) { this->Subs = Subs; this->DialogUI = DialogUI; } /*Function needed by the UE in order to determine what's the tasks' status*/ FORCEINLINE TStatId GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(UMGAsyncTask, STATGROUP_ThreadPoolAsyncTasks); } /*This function executes each time this thread is active - UE4 searches for a function named DoWord() and executes it*/ void DoWork() { for (int32 i = 0; i < Subs.Num(); i++) { //Sleep means that we pause this thread for the given time FPlatformProcess::Sleep(Subs[i].AssociatedTime); //Update our subtitles after the thread comes back DialogUI->SubtitleToDisplay = Subs[i].Subtitle; } //Sleep 1 second to let the user read the text FPlatformProcess::Sleep(1.f); //Clear the subtitle DialogUI->SubtitleToDisplay = FString(""); } }; |
Just to clarify, here is a screenshot of my code:
When you’ve typed the said code, implement the following logic inside the UpdateSubtitles function:
1 2 3 4 5 6 7 |
void UDialogUI::UpdateSubtitles(TArray<FSubtitle> Subtitles) { if (!Subtitles.IsValidIndex(0)) return; //Start a background task in a low priority thread (new FAutoDeleteAsyncTask<UMGAsyncTask>(Subtitles, this))->StartBackgroundTask(); } |
Compile your code and test your system!
In the screenshot right after the section “Revisiting our UI” the blueprint calls the Talk function, but in the C++ class, the subtitle is a parameter, not a return value (i’m implementing everything in C++ like in the Inventory tutorial), so what’s happening?
The subtitles array is passed by reference. Since we pass a value by reference (which doesn’t include the const keyword) in a function, it means that the function will modify the given value by some way. UE4 translates this function with an output pin in the Blueprint graphs.
-Orfeas
Ok got it 😉 oh btw i’ve been trying to implement drag and drop functionality to your inventory system tutorial in C++, but i’m having kind of a hard time doing so :p do you think you could do a tutorial on that?
I don’t think I’m going to expand old tutorials at this time. However, I’ll see if I can integrate this functionality in a future tutorial.
-Orfeas
Actually no i don’t got it… how do i get the subtitles to pass as a parameter in the talk function?
You can’t do that on the Blueprint side. A workaround would be to create a BlueprintCallable setter function.
-Orfeas
Hey Orfeas,
I implemented this code as you have it, and I double checked everything. I’m not getting any errors, however the game is crashing when I run to the AI and overlap the box. From what I can gather, the issue is a null pointer in GeneratePlayerLines function. It seems like the array is not accessible or isn’t filling in, so nothing can be generated? I’m a complete greenhorn when it comes to C++ and unreal, so this is really throwing me off. Any ideas?
Hello,
Have you verified that the PlayerLines isn’t null and that your type casting works?
-Orfeas
That’s the thing, I think PlayerLines might be null, but I don’t know how to remedy that. It’s like the code isn’t able to reference the data table I created in the editor.
Have you assigned the required player lines in the corresponding character? Moreover, try to restart your editor and re-run your game. Sometimes this might fix your issue. Additionally, I would suggest to download the complete source code from GitHub and compare it with your local files.
-Orfeas
Hey Orfeas!
I got it working! I didn’t know I needed to go into the blueprint for the AI and updated the dialog section with the appropriate lines. Now I’ve got it working, I just need to troubleshoot the timing of it.
Also, just so you know, there was an update with Unreal for the .AddDynamic function, and there is a new parameter needed for the overlap functions. The function calls for another primitive component. Here is how I had to set it up to get OnBoxOverlap/OnBoxEndOverlap to work:
UFUNCTION()
void OnBoxOverlap(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
void OnBoxEndOverlap(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherIndex);
Hi, I have the same issue but could not fix it yet.
Im not sure what you mean by “updating the dialog section in the blueprint”
Would be nice if someone is still there for help.
Hello. Everything worked nice until one point – Creating the logic for our character. I am using default FPS character template, provided by the engine and I really don’t know where to put the code for it’s logic or how/where to create the empty body for the toggle UI.. a hint would be nice.. Thank you
In your Revisiting our UI part, where did you get ‘Play Given Lines’ ? Do I need to declare it myself?
Leaving this here in case anyone else has this issue but play given lines is a function created in blueprints, it’s not actually made in the event graph you have to make the function by creating a function to the left first.
I have follow this tutorial with UE5 and work fine 🙂 , very useful and interesting way to programming.