One thing that I often hit my head against when starting C++ and UMG:
I have a bunch of buttons, and I want them to all call the same function when they are clicked. Then from that function, I want to know which button was clicked.
… so how the heck do I do that?
There are a few ways to do this!
Why can't we do this by default?
The standard UMG Button class
OnClicked, a Dynamic Multicast Delegate that is called when a user clicks on the button.
Dynamic Multicast Delegates are great for working with Blueprints, but unfortunately the one in
UButton doesn't provide us with any arguments. It also doesn't allow us to use C++ lambdas to add our own arguments. So we can't tell which button was clicked.
So what can we do instead?
a) Make a Button UserWidget
While this method doesn't technically require us to get our hands dirty in C++, I'm going to show you how to do it in C++ because I like getting dirty.
First we are going to create a new UserWidget subclass that is going to be our generic wrapper around
UButton. It will conceptually represent a Button with some styling, and importantly let us make our own dynamic multicast delegate that provides us a way of knowing which button was clicked.
We do this by defining a new delegate that has our new button class as a parameter, and using this new delegate in our new
UUserWidget button class.
Note on Naming Convention: I like to preface all my classes with
BUI to make it clear that I made them. At Brace Yourself Games we use the preface
BYG for our code. Then
UW is what I use for UserWidget subclasses.
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FBUIOnClickedSignature, class UBUIUWButton*, Button);
Then instead of creating an array of
UButton instances, we can create an array of
b) Bind to the Slate Button Widget
As discussed, the UMG
UButton class dynamic multicast delegate does not provide us with any arguments, and does not allow us to use lambdas.
On the other hand, the Slate
SButton that is contained within
UButton, has a non-dynamic delegate that does allow us to use lambdas.
Taking our earlier example, here's how we could re-write it to bind to the
SButton's delegate instead.
c) Use CommonUI Button
CommonUI is a plugin from Epic released with Unreal Engine 4.27 and 5.0. It is still somewhat experimental but has been in use by Epic for a while.
One of the things it adds is a new button class that addresses some of the issues with
- On-Click delegates pass a pointer to the button that clicked them, useful when binding many button instances to the same function.
- Centralized styling using assets.
- Support for a Selected state, useful for making toggle-able buttons.
- Centralized text styling, using the same text style asset as the Common Text widget.
- Tooltip shows even when the button is disabled.
- Minimum desired width/height properties to ensure a standard size for buttons.
So using CommonUI's
UCommonButtonBase can solve our problem:
d) Write your own Button class
This is the most time-consuming of the solutions but it has its benefits. By writing your own alternative to
UButton, you can add whatever functionality you like, including changing the signatures of any
OnClicked delegates you might add.
Rather than writing entirely new button classes from scratch, for Industries of Titan I duplicated
UButton and their slot classes, renamed a stuff till it compiled and then gradually added the functionality I needed.
Hopefully this should give you at least one solution that works for you!
Thank you to Hash Buoy for asking this question on the benui Discord. If you are interested in joining the discussion, come and say hi!