Attaching function calls to Slate events

While creating buttons is all well and fine, at the moment, any UI element you add to the player's screen just sits there without anything happening even if a user clicks on it. We don't have any event handlers attached to the Slate elements at the moment, so events such as mouse clicks don't actually cause anything to happen.

Getting ready

This recipe shows you how to attach functions to these events so that we can run custom code when they occur.

How to do it...

  1. Create a new GameMode subclass called AClickEventGameMode.
  2. Add the following private members to the class:
    private:
    TSharedPtr<SVerticalBox> Widget;
    TSharedPtr<STextBlock> ButtonLabel;
  3. Add the following public functions, noting the override for BeginPlay():
    public:
    virtual void BeginPlay() override;
    FReplyButtonClicked();
  4. Within the .cpp file, add the implementation for BeginPlay:
    void AClickEventGameMode::BeginPlay()
    {
      Super::BeginPlay();
      Widget = SNew(SVerticalBox)
      + SVerticalBox::Slot()
      .HAlign(HAlign_Center)
      .VAlign(VAlign_Center)
      [
        SNew(SButton)
        .OnClicked(FOnClicked::CreateUObject(this, &AClickEventGameMode::ButtonClicked))
        .Content()
        [
          SAssignNew(ButtonLabel, STextBlock)
          .Text(FText::FromString(TEXT("Click me!")))
        ]
      ];
      GEngine->GameViewport->AddViewportWidgetForPlayer(GetWorld()->GetFirstLocalPlayerFromController(), Widget.ToSharedRef(), 1);
      GetWorld()->GetFirstPlayerController()->bShowMouseCursor = true;
      GEngine->GetFirstLocalPlayerController(GetWorld())->
      SetInputMode(FInputModeUIOnly().SetLockMouseToViewport(false).SetWidgetToFocus(Widget));
    }
  5. Also add an implementation for ButtonClicked():
    FReplyAClickEventGameMode::ButtonClicked()
    {
      ButtonLabel->SetText(FString(TEXT("Clicked!")));
      returnFReply::Handled();
    }
  6. Compile your code, and launch the editor.
  7. Override the game mode in World Settings to be AClickEventGameMode:
  8. Preview in the editor, and verify that the UI shows a button that changes from Click Me! to Clicked! when you use the mouse cursor to click on it.

How it works...

  1. As with most of the recipes in this chapter, we use GameMode to create and display our UI to minimize the number of classes extraneous to the point of the recipe that you need to create.
  2. 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.
  3. As a result, we create two shared pointers as member data within our GameMode—one to the overall parent or root widget of our UI, and the other to the label on our button, because we're going to be changing the label text at runtime later.
  4. We override 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.
  5. We also create a function called ButtonClicked. It returns FReply, a struct indicating if an event was handled. The function signature for ButtonClicked is determined by the signature of FOnClicked, a delegate which we will be using in a moment.
  6. Inside our implementation of BeginPlay, the first thing we do is call the implementation we are overriding to ensure that the class is initialized appropriately.
  7. Then, as usual, we use our SNew function to create VerticalBox, and we add a slot to it which is centered.
  8. We create a new Button inside that slot, and we add a value to the OnClicked attribute that the button contains.
  9. OnClicked is a delegate property. This means that the Button will broadcast the OnClicked delegate any time a certain event happens (as the name implies in this instance, when the button is clicked).
  10. 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.
  11. We do that using the standard delegate functions such as CreateUObject, CreateStatic, or CreateLambda. Any of those will work—we can bind UObject member functions, static functions, lambdas, and other functions.

    Note

    Check Chapter 5, Handling Events and Delegates, to learn more on delegates to see about the other types of function that we can bind to delegates.

  12. CreateUObject expects a pointer to a class instance, and a pointer to the member function defined in that class to call.
  13. The function has to have a signature that is convertible to the signature of the delegate:
    /** The delegate to execute when the button is clicked */
    FOnClickedOnClicked;
  14. As can be seen here, OnClicked delegate type is FOnClicked—this is why the ButtonClicked function that we declared has the same signature as FOnClicked.
  15. 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.
  16. After setting up the delegate, we use the Content() function, which returns a reference to the single slot that the button has for any content that it should contain.
  17. We then use SAssignNew to create our button's label, using the TextBlock widget.
  18. SAssignNew is important, because it allows us to use Slate's declarative syntax, and yet assigns variables to point to specific child widgets in the hierarchy.
  19. SAssignNew first argument is the variable that we want to store the widget in, and the second argument is the type of that widget.
  20. With ButtonLabel now pointing at our button's TextBlock, we can set its Text attribute to a static string.
  21. Finally, we add the widget to the player's viewport using AddViewportWidgetForPlayer, which expects, as parameters, LocalPlayer to add the widget to, the widget itself, and a depth value (higher values to the front).
  22. To get the LocalPlayer instance, we assume we are running without split screen, and so, the first player controller will be the only one, that is, the player's controller. The GetFirstLocalPlayerFromController function is a convenience function that simply fetches the first player controller, and returns its local player object.
  23. We also need to focus the widget so the player can click on it, and display a cursor so that the player knows where their mouse is on the screen.
  24. We know from the previous step that we can assume the first local player controller is the one we're interested in, so we can access it and change its ShowMouseCursor variable to true. This will cause the cursor to be rendered on screen.
  25. SetInputMode allows us to focus on a widget so that the player can interact with it amongst other UI-related functionality, such as locking the mouse to the game's viewport.
  26. It uses an FInputMode object as its only parameter, which we can construct with the specific elements that we wish to include by using the builder pattern.
  27. The FInputModeUIOnly class is a FInputMode 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.
  28. The builder pattern allows us to chain the method calls to customize our object instance before it is sent into the function as the parameter.
  29. We chain SetLockMouseToViewport(false) to specify that the player's mouse can leave the boundary of the game screen with SetWidgetToFocus(Widget), which specifies our top-level widget as the one that the game should direct player input to.
  30. Finally, we have our actual implementation for ButtonClicked, our event handler.
  31. When the function is run due to our button being clicked, we change our button's label to indicate it has been clicked.
  32. We then need to return an instance of FReply 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.
  33. FReply::Handled() returns FReply set up to indicate this to the framework.
  34. We could have used FReply::Unhandled(), but this would have told the framework that the click event wasn't actually the one we were interested in, and it should look for other objects that might be interested in the event instead.
..................Content has been hidden....................

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