
Tutorial: Main Menu with Common UI and C++ (Part 2)
The Goal
In this part of the tutorial we are going to bind functionality to the buttons and enable gamepad navigation.
Delegates in Unreal Engine
In the previous part of the tutorial we already bound the buttons to the corresponding functions of the main menu. Now, let’s take a closes look at how to bind buttons using delegates.
NewGameBtn->OnClicked().AddUObject(this, &UMainMenuWidget::OnNewGame);
The OnClicked function of the NewGameBtn returns a FCommonButtonEvent. Before we try to understand what a event is, we need to understand delegates.
Delegates allow you to call functions on one or multiple objects without knowing the type of the object. This works by binding a function to the delegate. The delegate can then be executed at any time and will call the bound function. Unreal supports multiple types of delegates: (Dynamic) Single Delegates, (Dynamic) Multi-Cast Delegates and Events.
The dynamic version of a delegate is a delegate that can be used in Blueprints. While this is handy, it also comes at a cost. The dynamic delegates are not as flexible and are a bit more of a hassle to declare. If you do not need a delegate inside a Blueprint always use the non-dynamic variant.
A single delegate can only bind one single function. The multi-cast delegate can bind multiple functions. When the delegate is executed all bound functions will be called.
The event is a special form of a multi-cast delegate. They allow multiple functions to be bound, but only the class that declared the event may invoke the event.
The syntax for declaring, binding and executing delegates can be found at the official documentation (here, here, here and here).
Since this tutorial focusses primarily on c++, I am not going to cover the dynamic delegates.
As you can see in the documentation there are multiple ways to bind a function to a delegate. Whenever we want to bind a function of an object that inherits from UObject we can use the AddUObject function. You can also use the Add/BindLambda function to create a lambda expression.
QuitBtn->OnClicked().AddLambda([this]() {
// do something
});
In the brackets you can pass the context for the lambda expression. This determines what functions you can call inside the expression. Without this, you could only call static functions without any context. Inside the parenthesis you can pass the functions parameters. They can be used inside the lambda expression. Lambda expressions are useful if you do not want to create a new functions, but keep in mind that they make your code harder to read and might lead to less organized code.
You can also pass a payload to non-dynamic delegates. Simply pass the payload as a parameter after binding you function. The payload will then be passed to your function when the delegates is executed. Of course, your function’s signature must match the payload.
NewGameBtn->OnClicked().AddUObject(this, &UMainMenuWidget::OnNewGame, 2);
// ========================================================================
void UMainMenuWidget::OnNewGame(int32 SomeNumber) {
// TODO:
}
Cursor and Input Mode
When you now play the main menu map, and click anywhere but a button, the cursor will disappear. That’s not the desired behavior and to fix it we need to set the cursor’s visibility.
void UMainMenuWidget::NativeOnInitialized() {
// ...
GetOwningPlayer()->SetShowMouseCursor(true);
}
The above code will make sure, that the cursor will always be visible, no matter where you click.
In order to use the gamepad, we also need to set the input mode to FInputModeUIOnly.
void UMainMenuWidget::NativeOnInitialized() {
// ...
GetOwningPlayer()->SetInputMode(FInputModeUIOnly());
}
If you start the map now and try to navigate the buttons using the gamepad, you will notice that gamepad navigation is still not working, unless you click on a button first. Why is that? For the gamepad navigation to work we need a focused element as a reference for the navigation system. Without a focused element the navigation will not work. Once a button was clicked, the gamepad navigation works.
So, how can we focus a button from the start? To set focus to a widget is as simple as this:
SomeWidget->SetFocus();
What is much harder is to determine to right moment when to set the focus. Fortunately, CommonUI has a solution for this:
void UMainMenuWidget::NativeOnInitialized() {
//...
GetInputSubsystem()->OnInputMethodChangedNative.AddUObject(this, &UMainMenuWidget::OnInputMethodChanged);
}
void UMainMenuWidget::OnInputMethodChanged(ECommonInputType NewInputType) {
if(NewInputType == ECommonInputType::Gamepad) {
NewGameBtn->SetFocus();
}
}
In every CommonUserWidget we can easily access the input subsystem and bind a function to its OnInputMethodChangedNative event. When the input changes from mouse (which is the default) to gamepad, we can now set the focus the new game button.
Simple Button Functionality
We can now implement loading a level if the “New Game” button is pressed or quit the application, if the “Quit” button is pressed. You can see the tutorial code so far on github.
In the next part we add multiple layers to our main menu.