Displaying and hiding a sheet of UMG elements in-game

So we have already discussed how to add a widget to the viewport, which means that it will be rendered on the player's screen.

However, what if we want to have UI elements that are toggled based on other factors, such as proximity to certain Actors, or a player holding a key down, or if we want a UI that disappears after a specified time?

How to do it...

  1. Create a new GameMode class called ToggleHUDGameMode.
  2. Override BeginPlay and EndPlay.
  3. Add the following UPROPERTY:
    UPROPERTY()
    FTimerHandle HUDToggleTimer;
  4. Lastly add this member variable:
    TSharedPtr<SVerticalBox> widget;
  5. Implement BeginPlay with the following code in the method body:
    void AToggleHUDGameMode::BeginPlay()
    {
      Super::BeginPlay();
      widget = SNew(SVerticalBox)
      + SVerticalBox::Slot()
      .HAlign(HAlign_Center)
      .VAlign(VAlign_Center)
      [
        SNew(SButton)
        .Content()
        [
          SNew(STextBlock)
          .Text(FText::FromString(TEXT("Test button")))
        ]
      ];
      GEngine->GameViewport->AddViewportWidgetForPlayer(GetWorld()->GetFirstLocalPlayerFromController(), widget.ToSharedRef(), 1);
    
      GetWorld()->GetTimerManager().SetTimer(HUDToggleTimer, FTimerDelegate::CreateLambda
      ([this] 
      {
        if (this->widget->GetVisibility().IsVisible())
        {
          this->widget->SetVisibility(EVisibility::Hidden);
        }
        else
        {
          this->widget->SetVisibility(EVisibility::Visible);
        }
      }), 5, true);
    }
  6. Implement EndPlay:
    void AToggleHUDGameMode::EndPlay(constEEndPlayReason::Type EndPlayReason)
    {
      Super::EndPlay(EndPlayReason);
      GetWorld->GetTimerManager().ClearTimer(HUDToggleTimer);
    }
  7. Compile your code, and start the editor.
  8. Within the Editor, open World Settings from the toolbar:
    How to do it...
  9. Inside World Settings, override the level's Game Mode to be our AToggleHUDGameMode:
    How to do it...
  10. Play the level, and verify that the UI toggles its visibility every 5 seconds.

How it works...

As with most of the other recipes in this chapter, we are using a custom GameMode class to display our single-player UI on the player's viewport for convenience:

  1. We override BeginPlay and EndPlay so that we can correctly handle the timer that will be toggling our UI on and off for us.
  2. To make that possible, we need to store a reference to the timer as a UPROPERTY to ensure it won't be garbage collected.
  3. Within BeginPlay, we create a new VerticalBox using the SNew macro, and place a button in its first slot.
  4. Buttons have Content, which can be some other widget to host inside them, such as SImage or STextBlock.
  5. In this instance, we place a STextBlock into the Content slot. The contents of the text block are irrelevant as long as they are long enough for us to be able to see our button properly.
  6. Having initialized our widget hierarchy, we add the root widget to the player's viewport so that it can be seen by them.
  7. Now we set up a timer to toggle the visibility of our widget. We are using a timer to simplify this recipe rather than having to implement user input and input bindings, but the principle is the same.
  8. To do this, we get a reference to the game world, and its associated timer manager.
  9. With the Timer manager in hand, we can create a new timer.
  10. However, we need to actually specify the code to run when the timer expires. One simple way to do this is to use a lambda function for our toggle the hud function.
  11. Lambdas are anonymous functions. Think of them as literal functions.
  12. To link a lambda function to the timer, we need to create a timer delegate.
  13. The FTimerDelegate::CreateLambda function is designed to convert a lambda function into a delegate, which the timer can call at the specified interval.
  14. The lambda needs to access the this pointer from its containing object, our GameMode, so that it can change properties on the widget instance that we have created.
  15. To give it the access it needs, we begin our lambda declaration with the [] operators, which enclose variables that should be captured into the lambda, and accessible inside it.
  16. The curly braces then enclose the function body in the same way they would with a normal function declaration.
  17. Inside the function, we check if our widget is visible. If it is visible, then we hide it using SWidget::SetVisibility.
  18. If the widget isn't visible, then we turn it on using the same function call.
  19. In the rest of the call to SetTimer, we specify the interval (in seconds) to call the timer, and set the timer to loop.
  20. One thing we need to be careful of, though, is the possibility of our object being destroyed between two timer invocations, potentially leading to a crash if a reference to our object is left dangling.
  21. In order to fix this, we need to remove the timer.
  22. Given that we set the timer during BeginPlay, it makes sense to clear the timer during EndPlay.
  23. EndPlay will be called whenever GameMode either ends play or is destroyed, so we can safely cancel the timer during its implementation.
  24. With GameMode set as the default game mode, the UI is created when the game begins to play, and the timer delegate executes every 5 seconds to switch the visibility of the widgets between true and false.
  25. When you close the game, EndPlay clears the timer reference, avoiding any problems.
..................Content has been hidden....................

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