I second (third) using a delegate and binding it in your UMG blueprint. I prefer never to have my base classes talking directly to my hud classes at all. I just fire events based on state updates and my UMG blueprints can do whatever they like on that event with the data passed with the event. This creates a nice loosely-coupled system where changes to the UI don’t propagate downwards into your code. The event also does not have to be on your player controller, it can be on some central object like the game mode or a event manager custom component.
For example, you could add a delegate and property to MyGameMode.h. I’ll pass the player controller here, but maybe you want the player’s id, name, or pawn instead? Just pass whatever makes sense to your game. You can also use TwoParams or more if you need.
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FPlayerDeathDelegate, AMyPlayerController*, PlayerController);
UCLASS()
class MYGAME_API AMyGameMode : public AGameMode
{
UPROPERTY(BlueprintAssignable)
FPlayerDeathDelegate OnPlayerDeath;
}
When your player dies, you can broadcast the event on the game mode from MyPlayerController.cpp, passing in this to represent the player controller that died, but again you could pass whatever data you need.
void MyPlayerController::IamDeadDeadDead()
{
AMyGameMode* game_mode = (AMyGameMode*)GetWorld()->GetAuthGameMode();
if (game_mode)
{
game_mode.OnPlayerDeath.Broadcast(this);
}
}
Then in your UMG blueprint, just bind from your game mode, because you set BlueprintAssignable, to your OnPlayerDeath event and you’re good to go. This way you never actually have to know beforehand which player controller is going to raise the event, it’s all routed through a central event handler. Combine this with inheritance and it gets even more powerful and flexible and the sky is the limit.