{"id":3247,"date":"2021-01-02T19:28:53","date_gmt":"2021-01-02T11:28:53","guid":{"rendered":"http:\/\/blog.coolcoding.cn\/?p=3247"},"modified":"2021-01-02T19:40:24","modified_gmt":"2021-01-02T11:40:24","slug":"displaying-and-hiding-a-sheet-of-umg-elements-in-game","status":"publish","type":"post","link":"https:\/\/blog.coolcoding.cn\/?p=3247","title":{"rendered":"Displaying and hiding a sheet of UMG elements in-game"},"content":{"rendered":"\n<p>We have already discussed how to add a widget to the viewport, which means that it will be rendered on the player&#8217;s screen.<\/p>\n\n\n\n<p>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?<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">How to do it&#8230;<\/h1>\n\n\n\n<ol><li>Create a new&nbsp;GameModeBase&nbsp;class called&nbsp;ToggleHUDGameMode:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/ebab40ac-26b7-46a2-906b-b5eead0b1736.png\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>Add the following&nbsp;UPROPERTY&nbsp;and function definitions to the&nbsp;ToggleHUDGameMode.h&nbsp;file:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">#pragma once<br><br>#include \"CoreMinimal.h\"<br>#include \"GameFramework\/GameModeBase.h\"<br><strong>#include \"SlateBasics.h\"<\/strong><br>#include \"ToggleHUDGameMode.generated.h\"<br><br>UCLASS()<br>class CHAPTER_14_API AToggleHUDGameMode : public AGameModeBase<br>{<br> GENERATED_BODY()<br> <br><strong>public:<\/strong><br><strong> UPROPERTY()<\/strong><br><strong> FTimerHandle HUDToggleTimer;<\/strong><br><br><strong> TSharedPtr&lt;SVerticalBox&gt; widget;<\/strong><br><br><strong> virtual void BeginPlay() override;<\/strong><br><strong> virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;<\/strong><br>};<\/pre>\n\n\n\n<ol><li>Implement&nbsp;BeginPlay&nbsp;with the following code in the method body:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">void AToggleHUDGameMode::BeginPlay()<br>{<br>    Super::BeginPlay();<br>    widget = SNew(SVerticalBox)<br>        + SVerticalBox::Slot()<br>        .HAlign(HAlign_Center)<br>        .VAlign(VAlign_Center)<br>        [<br>            SNew(SButton)<br>            .Content()<br>        [<br>            SNew(STextBlock)<br>            .Text(FText::FromString(TEXT(\"Test button\")))<br>        ]<br>        ];<br><br>    auto player = GetWorld()-&gt;GetFirstLocalPlayerFromController();<br><br>    GEngine-&gt;GameViewport-&gt;AddViewportWidgetForPlayer(player, widget.ToSharedRef(), 1);<br><br>    auto lambda = FTimerDelegate::CreateLambda<br>    ([this]<br>    {<br>        if (this-&gt;widget-&gt;GetVisibility().IsVisible())<br>        {<br>            this-&gt;widget-&gt;SetVisibility(EVisibility::Hidden);<br><br>        }<br>        else<br>        {<br>            this-&gt;widget-&gt;SetVisibility(EVisibility::Visible);<br>        }<br>    });<br><br>    GetWorld()-&gt;GetTimerManager().SetTimer(HUDToggleTimer, lambda, 5, true);<br>}<\/pre>\n\n\n\n<ol><li>Implement&nbsp;EndPlay:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">void AToggleHUDGameMode::EndPlay(const EEndPlayReason::Type EndPlayReason)<br>{<br>    Super::EndPlay(EndPlayReason);<br>    GetWorld()-&gt;GetTimerManager().ClearTimer(HUDToggleTimer);<br>}<\/pre>\n\n\n\n<ol><li>Compile your code and start the editor.<\/li><li>Within the Editor, open&nbsp;World Settings&nbsp;from the toolbar:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/e117fa02-d7d4-447a-a504-6350baad3827.jpg\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>Inside&nbsp;World Settings, override the level&#8217;s&nbsp;Game Mode&nbsp;to be our&nbsp;AToggleHUDGameMode:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/3eb58755-c6a1-4830-973d-c23df59487c3.jpg\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>Play the level and verify that the UI toggles its visibility every five seconds.<\/li><\/ol>\n\n\n\n<h1 class=\"wp-block-heading\">How it works&#8230;<\/h1>\n\n\n\n<p>As with most of the other recipes in this chapter, we are using a custom&nbsp;GameMode&nbsp;class to display our single-player UI on the player&#8217;s viewport for convenience.<\/p>\n\n\n\n<p>We override&nbsp;BeginPlay&nbsp;and&nbsp;EndPlay&nbsp;so that we can correctly handle the timer that will be toggling our UI on and off for us. To make that possible, we need to store a reference to the timer as a&nbsp;UPROPERTY&nbsp;to ensure it won&#8217;t be garbage collected.<\/p>\n\n\n\n<p>Within&nbsp;BeginPlay, we create a new&nbsp;VerticalBox&nbsp;using the&nbsp;SNew&nbsp;macro, and place a button in its first slot. Buttons have&nbsp;Content, which can be some other widget to host inside them, such as&nbsp;SImage&nbsp;or&nbsp;STextBlock.<\/p>\n\n\n\n<p>In this instance, we place an&nbsp;STextBlock&nbsp;into the&nbsp;Content&nbsp;slot. The contents of the text block are irrelevant, that is, as long as they are long enough for us to be able to see our button properly.<\/p>\n\n\n\n<p>Having initialized our widget hierarchy, we add the root widget to the player&#8217;s viewport so that it can be seen by them.<\/p>\n\n\n\n<p>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. To do this, we get a reference to the game world and its associated timer manager.<\/p>\n\n\n\n<p>With the timer manager in hand, we can create a new timer. However, we need to actually specify the code to run when the timer expires. One simple way to do this is to use a&nbsp;lambda&nbsp;function for our toggle the hud function.<\/p>\n\n\n\n<p>Lambdas are anonymous functions. Think of them as literal functions. To link a&nbsp;lambda&nbsp;function to the timer, we need to create a&nbsp;timer&nbsp;delegate.<\/p>\n\n\n\n<p>The&nbsp;FTimerDelegate::CreateLambda&nbsp;function is designed to convert a&nbsp;lambda&nbsp;function into a delegate, which the timer can call at the specified interval.<\/p>\n\n\n\n<p>The&nbsp;lambda&nbsp;needs to access the&nbsp;this&nbsp;pointer from its containing object, our&nbsp;GameMode, so that it can change properties on the widget instance that we have created. To give it the access it needs, we begin our&nbsp;lambda&nbsp;declaration with the&nbsp;[]&nbsp;operators, which enclose variables that should be captured into the&nbsp;lambda,&nbsp;and are accessible inside it. The curly braces then enclose the function body in the same way they would with a normal function declaration.<\/p>\n\n\n\n<p>Inside the function, we check if our widget is visible. If it is visible, then we hide it using&nbsp;SWidget::SetVisibility. If the widget isn&#8217;t visible, then we turn it on using the same function call.<\/p>\n\n\n\n<p>In the rest of the call to&nbsp;SetTimer, we specify the interval (in seconds) to call the timer, and set the timer to loop.<\/p>\n\n\n\n<p>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. To fix this, we need to remove the timer.<\/p>\n\n\n\n<p>Given that we set the timer during&nbsp;BeginPlay, it makes sense to clear the timer during&nbsp;EndPlay.&nbsp;EndPlay&nbsp;will be called whenever&nbsp;GameMode&nbsp;either ends play or is destroyed, so we can safely cancel the timer during its implementation.<\/p>\n\n\n\n<p>With&nbsp;GameMode&nbsp;set as the default game mode, the UI is created when the game begins to play, and the timer delegate executes every five seconds to switch the visibility of the widgets between&nbsp;true&nbsp;and&nbsp;false.<\/p>\n\n\n\n<p>When you close the game,&nbsp;EndPlay&nbsp;clears the timer reference, avoiding any problems.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Attaching function calls to Slate events<\/h1>\n\n\n\n<p>While creating buttons is all well and good, at the moment, any UI element you add to the player&#8217;s screen just sits there without anything happening, even if a user clicks on it. We don&#8217;t have any event handlers attached to the Slate elements at the moment, so events such as mouse clicks don&#8217;t actually cause anything to happen.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Getting ready<\/h1>\n\n\n\n<p>This recipe shows you how to attach functions to these events so that we can run custom code when they occur.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">How to do it&#8230;<\/h1>\n\n\n\n<ol><li>Create a new&nbsp;GameModeBase&nbsp;subclass called&nbsp;ClickEventGameMode:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/ee48dd37-65bb-47a8-aab4-41abb8f4e351.png\" alt=\"\"\/><\/figure>\n\n\n\n<ol><li>From the&nbsp;ClickEventGameMode.h&nbsp;file, add the following functions and&nbsp;private&nbsp;members to the class:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">#pragma once<br><br>#include \"CoreMinimal.h\"<br>#include \"GameFramework\/GameModeBase.h\"<br><strong>#include \"SlateBasics.h\"<\/strong><br>#include \"ClickEventGameMode.generated.h\"<br><br>UCLASS()<br>class CHAPTER_14_API AClickEventGameMode : public AGameModeBase<br>{<br>    GENERATED_BODY()<br>    <br><strong>private:<\/strong><br><strong>    TSharedPtr&lt;SVerticalBox&gt; Widget;<\/strong><br><strong>    TSharedPtr&lt;STextBlock&gt; ButtonLabel;<\/strong><br><br><strong>public:<\/strong><br><strong>    virtual void BeginPlay() override;<\/strong><br><strong>    FReply ButtonClicked();<\/strong><br>};<\/pre>\n\n\n\n<ol><li>Within the&nbsp;.cpp&nbsp;file, add the implementation for&nbsp;BeginPlay:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">void AClickEventGameMode::BeginPlay()<br>{<br>    Super::BeginPlay();<br><br>    Widget = SNew(SVerticalBox)<br>        + SVerticalBox::Slot()<br>        .HAlign(HAlign_Center)<br>        .VAlign(VAlign_Center)<br>        [<br>            SNew(SButton)<br>            .OnClicked(FOnClicked::CreateUObject(this, &amp;AClickEventGameMode::ButtonClicked))<br>        .Content()<br>        [<br>            SAssignNew(ButtonLabel, STextBlock)<br>            .Text(FText::FromString(TEXT(\"Click me!\")))<br>        ]<br>        ];<br><br>    auto player = GetWorld()-&gt;GetFirstLocalPlayerFromController();<br><br>    GEngine-&gt;GameViewport-&gt;AddViewportWidgetForPlayer(player, Widget.ToSharedRef(), 1);<br><br>    GetWorld()-&gt;GetFirstPlayerController()-&gt;bShowMouseCursor = true;<br><br>    auto pc = GEngine-&gt;GetFirstLocalPlayerController(GetWorld());<br><br>    EMouseLockMode lockMode = EMouseLockMode::DoNotLock;<br><br>    auto inputMode = FInputModeUIOnly().SetLockMouseToViewportBehavior(lockMode).SetWidgetToFocus(Widget);<br><br>    pc-&gt;SetInputMode(inputMode);<br><br>}<\/pre>\n\n\n\n<ol><li>Also, add an implementation for&nbsp;ButtonClicked():<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">FReply AClickEventGameMode::ButtonClicked()<br>{<br>    ButtonLabel-&gt;SetText(FString(TEXT(\"Clicked!\")));<br>    return FReply::Handled();<br>}<\/pre>\n\n\n\n<ol><li>Compile&nbsp;your code and launch the editor.<\/li><li>Override the game mode in&nbsp;World Settings&nbsp;to be&nbsp;ClickEventGameMode.<\/li><li>Preview this in the editor and verify that the UI shows a button that changes from&nbsp;Click Me!&nbsp;to&nbsp;Clicked!&nbsp;when you use the mouse cursor to click on it:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/learning.oreilly.com\/library\/view\/unreal-engine-4x\/9781789809503\/assets\/72e77d50-3b49-4973-b4ac-bddae6bbe8cc.png\" alt=\"\"\/><\/figure>\n\n\n\n<p>Button displays Clicked! after being clicked\n\n<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">How it works&#8230;<\/h1>\n\n\n\n<p>As with most of the recipes in this chapter, we use&nbsp;GameMode&nbsp;to create and display our UI to minimize the number of classes that are extraneous to the point of the recipe that you need to create.<\/p>\n\n\n\n<p>Within our new game mode, we need to retain references to the Slate Widgets that we create so that we can interact with them after their creation.<\/p>\n\n\n\n<p>As a result, we create two shared pointers as member data within our&nbsp;GameMode&nbsp;\u2013&nbsp;one to the overall parent or root widget of our UI, and the other to the label on our button, because we&#8217;re going to be changing the label text at runtime later.<\/p>\n\n\n\n<p>We override&nbsp;BeginPlay, as it is a convenient place to create our UI after the game has started, and we will be able to get valid references to our player controller.<\/p>\n\n\n\n<p>We also create a function called&nbsp;ButtonClicked. It returns&nbsp;FReply, a&nbsp;struct&nbsp;indicating if an event was handled. The function signature for&nbsp;ButtonClicked&nbsp;is determined by the signature of&nbsp;FOnClicked, a delegate that we will be using in a moment.<\/p>\n\n\n\n<p>Inside our implementation of&nbsp;BeginPlay, the first thing we do is call the implementation we are overriding to ensure that the class is initialized appropriately.<\/p>\n\n\n\n<p>Then, as usual, we use our&nbsp;SNew&nbsp;function to create&nbsp;VerticalBox, and we add a slot to it, which is centered.<\/p>\n\n\n\n<p>We create a new&nbsp;Button&nbsp;inside that slot, and we add a value to the&nbsp;OnClicked&nbsp;attribute that the button contains.<\/p>\n\n\n\n<p>OnClicked&nbsp;is a delegate property. This means that the&nbsp;Button&nbsp;will broadcast the&nbsp;OnClicked&nbsp;delegate any time a certain event happens (as the name implies in this instance, when the button is clicked).<\/p>\n\n\n\n<p>To subscribe or listen to the delegate, and be notified of the event that it refers to, we need to assign a delegate instance to the property.<\/p>\n\n\n\n<p>We do that using the standard delegate functions such as&nbsp;CreateUObject,&nbsp;CreateStatic, or&nbsp;CreateLambda. Any of those will work&nbsp;\u2013&nbsp;we can bind&nbsp;UObject&nbsp;member functions, static functions, lambdas, and other functions.<br>Check out&nbsp;<a href=\"https:\/\/learning.oreilly.com\/library\/view\/unreal-engine-4x\/9781789809503\/cf821ef4-8a19-440c-805d-573748b76d5f.xhtml\">Chapter 5<\/a>,&nbsp;<em>Handling Events and Delegates,<\/em>&nbsp;to learn more about delegates and look at the other types of functions that we can bind to delegates.<\/p>\n\n\n\n<p>CreateUObject&nbsp;expects a pointer to a class instance, and a pointer to the member function that&#8217;s defined in that class to call. The function has to have a signature that can be converted into the signature of the delegate:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">\/** The delegate to execute when the button is clicked *\/<br> FOnClickedOnClicked;<\/pre>\n\n\n\n<p>As we can see, the&nbsp;OnClicked&nbsp;delegate type is&nbsp;FOnClicked&nbsp;\u2013&nbsp;this is why the&nbsp;ButtonClicked&nbsp;function that we declared has the same signature as&nbsp;FOnClicked.<\/p>\n\n\n\n<p>By passing in a pointer to this, and the pointer to the function to invoke, the engine will call that function on this specific object instance when the button is clicked.<\/p>\n\n\n\n<p>After setting up the delegate, we use the&nbsp;Content()&nbsp;function, which returns a reference to the single slot that the button has for any content that it should contain.<\/p>\n\n\n\n<p>We then use&nbsp;SAssignNew&nbsp;to create our button&#8217;s label by using the&nbsp;TextBlock&nbsp;widget.&nbsp;SAssignNew&nbsp;is important because it allows us to use Slate&#8217;s declarative syntax, and yet assigns variables to point to specific child widgets in the hierarchy.&nbsp;SAssignNew&#8217;s first argument is the variable that we want to store the widget in, and the second argument is the type of that widget.<\/p>\n\n\n\n<p>With&nbsp;ButtonLabel&nbsp;now pointing at our button&#8217;s&nbsp;TextBlock, we can set its&nbsp;Text&nbsp;attribute to a static string.<\/p>\n\n\n\n<p>Finally, we add the widget to the player&#8217;s viewport using&nbsp;AddViewportWidgetForPlayer, which expects, as parameters,&nbsp;LocalPlayer&nbsp;to add the widget to, the widget itself, and a depth value (higher values to the front).<\/p>\n\n\n\n<p>To get the&nbsp;LocalPlayer&nbsp;instance, we assume we are running without split screen, and so the first player controller will be the only one, that is, the player&#8217;s controller. The&nbsp;GetFirstLocalPlayerFromController&nbsp;function is a convenience function that simply fetches the first player&#8217;s controller and returns its local player object.<\/p>\n\n\n\n<p>We also need to focus the widget so that the player can click on it and display a cursor so that the player knows where their mouse is on the screen.<\/p>\n\n\n\n<p>We know from the previous step that we can assume the first local player controller is the one we&#8217;re interested in, so we can access it and change its&nbsp;ShowMouseCursor&nbsp;variable to&nbsp;true. This will cause the cursor to be rendered on screen.<\/p>\n\n\n\n<p>SetInputMode&nbsp;allows us to focus on a widget so that the player can interact with it among other UI-related functionality, such as locking the mouse to the game&#8217;s viewport. It uses an&nbsp;FInputMode&nbsp;object as its only parameter, which we can construct with the specific elements that we wish to include by using the&nbsp;builder&nbsp;pattern.<\/p>\n\n\n\n<p>The&nbsp;FInputModeUIOnly&nbsp;class is an&nbsp;FInputMode&nbsp;subclass that specifies that we want all input events to be redirected to the UI layer rather than the player controller and other input handling.<\/p>\n\n\n\n<p>The&nbsp;builder&nbsp;pattern allows us to chain the method calls to customize our object instance before it is sent into the function as the parameter.<\/p>\n\n\n\n<p>We chain&nbsp;SetLockMouseToViewport(false)&nbsp;to specify that the player&#8217;s mouse can leave the boundary of the game screen with&nbsp;SetWidgetToFocus(Widget), which specifies our top-level widget as the one that the game should direct player input to.<\/p>\n\n\n\n<p>Finally, we have our actual implementation for&nbsp;ButtonClicked, which is our event handler. When the function is run due to our button being clicked, we change our button&#8217;s label to indicate it has been clicked. We then need to return an instance of&nbsp;FReply&nbsp;to the caller to let the UI framework know that the event has been handled, and to not continue propagating the event back up the widget hierarchy.<\/p>\n\n\n\n<p>FReply::Handled()&nbsp;returns&nbsp;FReply&nbsp;set up to indicate this to the framework. We could have used&nbsp;FReply::Unhandled(), but this would have told the framework that the click event wasn&#8217;t actually the one we were interested in, and it should look for other objects that might be interested in the event instead.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We have already discussed how to add a widget to the vi [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[27,1],"tags":[26],"_links":{"self":[{"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts\/3247"}],"collection":[{"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3247"}],"version-history":[{"count":1,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts\/3247\/revisions"}],"predecessor-version":[{"id":3253,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts\/3247\/revisions\/3253"}],"wp:attachment":[{"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3247"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3247"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3247"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}