Displaying a quote from each NPC dialog box

To display a dialog box, we need a custom (heads-up display) HUD. In the UE4 editor, go to File | Add Code To Project... and choose the HUD class from which the subclass is created. Name your subclass as you wish; I've named mine MyHUD.

After you have created the MyHUD class, let Visual Studio reload. We will make some code edits.

Displaying messages on the HUD

Inside the AMyHUD class, we need to implement the DrawHUD() function in order to draw our messages to the HUD and to initialize a font to draw to the HUD with, as shown in the following code:

UCLASS()
class GOLDENEGG_API AMyHUD : public AHUD
{
  GENERATED_UCLASS_BODY()
  // The font used to render the text in the HUD.
  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = HUDFont)
  UFont* hudFont;
  // Add this function to be able to draw to the HUD!
  virtual void DrawHUD() override;
};

The HUD font will be set in a blueprinted version of the AMyHUD class. The DrawHUD() function runs once per frame. In order to draw within the frame, add a function to the AMyHUD.cpp file:

void AMyHUD::DrawHUD()
{
  // call superclass DrawHUD() function first
  Super::DrawHUD();
  // then proceed to draw your stuff.
  // we can draw lines..
  DrawLine( 200, 300, 400, 500, FLinearColor::Blue );
  // and we can draw text!
  DrawText( "Greetings from Unreal!", FVector2D( 0, 0 ), hudFont,  FVector2D( 1, 1 ), FColor::White );
}

Wait! We haven't initialized our font yet. To do this, we need to set it up in blueprints. Compile and run your Visual Studio project. Once you are in the editor, go to the Blueprints menu at the top and navigate to GameMode | HUD | + Create | MyHUD.

Displaying messages on the HUD

Creating a blueprint of the MyHUD class

I called mine BP_MyHUD. Edit BP_MyHUD and select a font from the drop-down menu under HUDFont:

Displaying messages on the HUD

I selected RobotoDistanceField for my HUD font

Next, edit your Game Mode blueprint (BP_GameModeGoldenEgg) and select your new BP_MyHUD (not MyHUD class) for the HUD Class panel:

Displaying messages on the HUD

Test your program by running it! You should see text printed on the screen.

Displaying messages on the HUD

Using TArray<Message>

Each message we want to display for the player will have a few properties:

  • An FString variable for the message
  • A float variable for the time to display it
  • An FColor variable for the color of the message

So it makes sense for us to write a little struct function to contain all this information.

At the top of MyHUD.h, insert the following struct declaration:

struct Message
{
  FString message;
  float time;
  FColor color;
  Message()
  {
    // Set the default time.
    time = 5.f;
    color = FColor::White;
  }
  Message( FString iMessage, float iTime, FColor iColor )
  {
    message = iMessage;
    time = iTime;
    color = iColor;
  }
};

Note

An enhanced version of the Message structure (with a background color) is in the code package for this chapter. We used simpler code here so that it'd be easier to understand the chapter.

Now, inside the AMyHUD class, we want to add a TArray of these messages. A TArray is a UE4-defined special type of dynamically growable C++ array. We will cover the detailed use of TArray in the next chapter, but this simple usage of TArray should be a nice introduction to garner your interest in the usefulness of arrays in games. This will be declared as TArray<Message>:

UCLASS()
class GOLDENEGG_API AMyHUD : public AHUD
{
  GENERATED_UCLASS_BODY()
  // The font used to render the text in the HUD.
  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = HUDFont)
  UFont* hudFont;
  // New! An array of messages for display
  TArray<Message> messages;
  virtual void DrawHUD() override;
  // New! A function to be able to add a message to display
  void addMessage( Message msg );
};

Now, whenever the NPC has a message to display, we're just need to call AMyHud::addMessage() with our message. The message will be added to TArray of the messages to be displayed. When a message expires (after a certain amount of time), it will be removed from the HUD.

Inside the AMyHUD.cpp file, add the following code:

void AMyHUD::DrawHUD()
{
  Super::DrawHUD();
  // iterate from back to front thru the list, so if we remove
  // an item while iterating, there won't be any problems
  for( int c = messages.Num() - 1; c >= 0; c-- )
  {
    // draw the background box the right size
    // for the message
    float outputWidth, outputHeight, pad=10.f;
    GetTextSize( messages[c].message, outputWidth, outputHeight,  hudFont, 1.f );

    float messageH = outputHeight + 2.f*pad;
    float x = 0.f, y = c*messageH;

    // black backing
    DrawRect( FLinearColor::Black, x, y, Canvas->SizeX, messageH  );
    // draw our message using the hudFont
    DrawText( messages[c].message, messages[c].color, x + pad, y +  pad, hudFont );

    // reduce lifetime by the time that passed since last 
    // frame.
    messages[c].time -= GetWorld()->GetDeltaSeconds();

    // if the message's time is up, remove it
    if( messages[c].time < 0 )
    {
      messages.RemoveAt( c );
    }
  }
}

void AMyHUD::addMessage( Message msg )
{
  messages.Add( msg );
}

The AMyHUD::DrawHUD() function now draws all the messages in the messages array, and arranges each message in the messages array by the amount of time that passed since the last frame. Expired messages are removed from the messages collection once their time value drops below 0.

Exercise

Refactor the DrawHUD() function so that the code that draws the messages to the screen is in a separate function called DrawMessages().

The Canvas variable is only available in DrawHUD(), so you will have to save Canvas->SizeX and Canvas->SizeY in class-level variables.

Note

Refactoring means to change the way code works internally so that it is more organized or easier to read but still has the same apparent result to the user running the program. Refactoring often is a good practice. The reason why refactoring occurs is because nobody knows exactly what the final code should look like once they start writing it.

Solution

See the AMyHUD::DrawMessages() function in the code package for this chapter.

Triggering an event when it is near an NPC

To trigger an event near the NPC, we need to set an additional collision detection volume that is a bit wider than the default capsule shape. The additional collision detection volume will be a sphere around each NPC. When the player steps into the NPC sphere, the NPC reacts and displays a message.

Triggering an event when it is near an NPC

We're going to add the dark red sphere to the NPC so that he can tell when the player is nearby

Inside your NPC.h class file, add the following code in order to declare ProxSphere and UFUNCTION called Prox:

UCLASS()
class GOLDENEGG_API ANPC : public ACharacter
{
  GENERATED_UCLASS_BODY()
  // This is the NPC's message that he has to tell us.
  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category =  NPCMessage)
  FString NpcMessage;
  // The sphere that the player can collide with to get item
  UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category =  Collision)
  TSubobjectPtr<class USphereComponent> ProxSphere;
  // The corresponding body of this function is 
  // ANPC::Prox_Implementation, __not__ ANPC::Prox()!
  // This is a bit weird and not what you'd expect,
  // but it happens because this is a BlueprintNativeEvent
  UFUNCTION(BlueprintNativeEvent, Category = "Collision")
  void Prox( AActor* OtherActor, UPrimitiveComponent* OtherComp,  int32 OtherBodyIndex, bool bFromSweep, const FHitResult &  SweepResult );
};

This looks a bit messy, but it is actually not that complicated. Here, we declare an extra bounding sphere volume called ProxSphere, which detects when the player is near the NPC.

In the NPC.cpp file, we need to add the following code in order to complete the proximity detection:

ANPC::ANPC(const class FPostConstructInitializeProperties& PCIP) : Super(PCIP)
{
  ProxSphere = PCIP.CreateDefaultSubobject<USphereComponent>(this,  TEXT("Proximity Sphere"));
  ProxSphere->AttachTo( RootComponent );
  ProxSphere->SetSphereRadius( 32.f );
  // Code to make ANPC::Prox() run when this proximity sphere
  // overlaps another actor.
  ProxSphere->OnComponentBeginOverlap.AddDynamic( this,  &ANPC::Prox );
  NpcMessage = "Hi, I'm Owen";//default message, can be edited
  // in blueprints
}
// Note! Although this was declared ANPC::Prox() in the header,
// it is now ANPC::Prox_Implementation here.
void ANPC::Prox_Implementation( AActor* OtherActor,  UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool  bFromSweep, const FHitResult & SweepResult )
{
  // This is where our code will go for what happens
  // when there is an intersection
}

Make the NPC display something to the HUD when something is nearby

When the player is near the NPC sphere collision volume, display a message to the HUD that alerts the player about what the NPC is saying.

This is the complete implementation of ANPC::Prox_Implementation:

void ANPC::Prox_Implementation( AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult )
{
  // if the overlapped actor is not the player,
  // you should just simply return from the function
  if( Cast<AAvatar>( OtherActor ) == nullptr )
  {
    return;
  }
  APlayerController* PController = GetWorld()- >GetFirstPlayerController();
  if( PController )
  {
    AMyHUD * hud = Cast<AMyHUD>( PController->GetHUD() );
    hud->addMessage( Message( NpcMessage, 5.f, FColor::White ) );
  }
}

The first thing we do in this function is to cast OtherActor (the thing that came near the NPC) to AAvatar. The cast succeeds (and is not nullptr) when OtherActor is an AAvatar object. We get the HUD object (which happens to be attached to the player controller) and pass a message from the NPC to the HUD. The message is displayed whenever the player is within the red bounding sphere surrounding the NPC.

Make the NPC display something to the HUD when something is nearby

Owen's greeting

Exercises

  1. Add a UPROPERTY function name for the NPC's name so that the name of the NPC is editable in blueprints, similar to the message that the NPC has for the player. Show the NPC's name in the output.
  2. Add a UPROPERTY function (type UTexture2D*) for the NPC's face texture. Draw the NPC's face beside its message in the output.
  3. Render the player's HP as a bar (filled rectangle).

Solutions

Add this property to the ANPC class:

// This is the NPC's name
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = NPCMessage)
FString name;

Then, in ANPC::Prox_Implementation, change the string passed to the HUD to:

name + FString(": ") + message

This way, the NPC's name will be attached to the message.

Add this property to the ANPC class:

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = NPCMessage)
UTexture2D* Face;

Then you can select face icons to be attached to the NPC's face in blueprints.

Attach a texture to your struct Message:

UTexture2D* tex;

To render these icons, you need to add a call to DrawTexture() with the right texture passed in to it:

DrawTexture( messages[c].tex, x, y, messageH, messageH, 0, 0, 1, 1  );

Be sure to check whether the texture is valid before you render it. The icons should look similar to what is shown here, at the top of the screen:

Solutions

This is how a function to draw the player's remaining health in a bar will look:

void AMyHUD::DrawHealthbar()
{
  // Draw the healthbar.
  AAvatar *avatar = Cast<AAvatar>(  UGameplayStatics::GetPlayerPawn(GetWorld(), 0) );
  float barWidth=200, barHeight=50, barPad=12, barMargin=50;
  float percHp = avatar->Hp / avatar->MaxHp;
  DrawRect( FLinearColor( 0, 0, 0, 1 ), Canvas->SizeX - barWidth -  barPad - barMargin, Canvas->SizeY - barHeight - barPad -  barMargin, barWidth + 2*barPad, barHeight + 2*barPad );
  DrawRect( FLinearColor( 1-percHp, percHp, 0, 1 ), Canvas->SizeX  - barWidth - barMargin, Canvas->SizeY - barHeight - barMargin,  barWidth*percHp, barHeight );
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.68.18