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.
This recipe shows you how to attach functions to these events so that we can run custom code when they occur.
GameMode
subclass called AClickEventGameMode
.private
members to the class:private: TSharedPtr<SVerticalBox> Widget; TSharedPtr<STextBlock> ButtonLabel;
public
functions, noting the override for BeginPlay()
:public: virtual void BeginPlay() override; FReplyButtonClicked();
.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)); }
ButtonClicked()
:FReplyAClickEventGameMode::ButtonClicked() { ButtonLabel->SetText(FString(TEXT("Clicked!"))); returnFReply::Handled(); }
AClickEventGameMode
: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.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.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.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.BeginPlay
, the first thing we do is call the implementation we are overriding to ensure that the class is initialized appropriately.SNew
function to create VerticalBox
, and we add a slot to it which is centered.Button
inside that slot, and we add a value to the OnClicked
attribute that the button contains.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).CreateUObject
, CreateStatic
, or CreateLambda
. Any of those will work—we can bind UObject
member functions, static functions, lambdas, and other functions.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.
CreateUObject
expects a pointer to a class instance, and a pointer to the member function defined in that class to call./** The delegate to execute when the button is clicked */ FOnClickedOnClicked;
OnClicked
delegate type is FOnClicked
—this is why the ButtonClicked
function that we declared has the same signature as FOnClicked
.Content()
function, which returns a reference to the single slot that the button has for any content that it should contain.SAssignNew
to create our button's label, using the TextBlock
widget.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.SAssignNew
first argument is the variable that we want to store the widget in, and the second argument is the type of that widget.ButtonLabel
now pointing at our button's TextBlock
, we can set its Text
attribute to a static string.AddViewportWidgetForPlayer
, which expects, as parameters, LocalPlayer
to add the widget to, the widget itself, and a depth value (higher values to the front).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.ShowMouseCursor
variable to true
. This will cause the cursor to be rendered on screen.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.FInputMode
object as its only parameter, which we can construct with the specific elements that we wish to include by using the builder
pattern.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.builder
pattern allows us to chain the method calls to customize our object instance before it is sent into the function as the parameter.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.ButtonClicked
, our event handler.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.FReply::Handled()
returns FReply
set up to indicate this to the framework.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.18.225.149.32