Player HUD is being Updated on all networks, Need Help Understanding Unreal Engine Events in a Multiplayer Game

Hey everyone,

I’ve been working on a multiplayer game in Unreal Engine and I’m running into an issue with updating the player’s HUD when they take damage.

Currently, I have a function UpdateHealthHud()in my PlayerHUD class, and this function is bound to a public static delegate PlayerHealthUpdated. The strange thing is that when this event is called, the health HUD updates across all clients. I’m not entirely sure why this is happening.

My question is, do the event broadcasts get replicated across all the networks by default, or is there something else I need to set up in the PlayerHUD?

Any insights or suggestions would be greatly appreciated!

PlayerEvent

//----> Player Event.h
DECLARE_MULTICAST_DELEGATE_TwoParams(FHealthUpdated, float, float)

UCLASS()
class CARGO_API UPlayerEvents : public UObject
{
	GENERATED_BODY()
	
public:
	static FHealthUpdated HealthUpdatedEvent;
};

//----> Player Event.cpp
#include "PlayerEvents.h"

FHealthUpdated UPlayerEvents::HealthUpdatedEvent;

PlayerHUD.cpp

void APlayerHUD::PostInitProperties()
{
	Super::PostInitProperties();
	
	UPlayerEvents::HealthUpdatedEvent.AddUObject(this,&APlayerHUD::UpdateHealth);
}

void APlayerHUD::UpdateHealth(float Health, float MaxHealth) const
{
	if(CharacterOverlay && CharacterOverlay->HealthBar && CharacterOverlay->HealthText)
	{
		const float percentage = Health / MaxHealth;
		CharacterOverlay->HealthBar->SetPercent(percentage);
		const FString HealthText = FString::Printf(TEXT("%d/%d"), FMath::CeilToInt(Health),FMath::CeilToInt(MaxHealth));
		CharacterOverlay->HealthText->SetText(FText::FromString(HealthText));
	}
}
FHealthUpdated UPlayerEvents::HealthUpdatedEvent;

BaseCharacter.cpp

// Called when the game starts or when spawned
void ABaseCharacter::BeginPlay()
{
	Super::BeginPlay();

	if(HasAuthority())
	{
		OnTakeAnyDamage.AddDynamic(this, &ABaseCharacter::ReceiveDamage);
	}
}

void ABaseCharacter::ReceiveDamage(AActor* DamagedActor, float Damage, const UDamageType* DamageType,
	AController* InstigatorController, AActor* DamageCauser)
{
	ABaseCharacter* character = Cast<ABaseCharacter>(DamagedActor);
	if(character)
	{
		character->CurrentHealth = FMath::Clamp(CurrentHealth - 10, 0.0f, MaxHealth);
	}
}

void ABaseCharacter::OnRep_Health() const
{
	if(IsLocallyControlled())
	{
		UPlayerEvents::HealthUpdatedEvent.Broadcast(CurrentHealth, MaxHealth);
	}
}

Create a Custom Player State class. In the class add a health var (Float) and a Max Health var (Float).

In your character class create a “Display Health” float. This variable will be used to update widget text health. Set it to Rep_Notify. In the Rep_notify function have it call an event on the Widget to update the health text.

On damage/heal have the Controller on the server reference the Player State → Health. Calc and set new health. Then take the new health and set the characters “Display health”.

done.

1 Like

Your character replicates to everyone, and your Health variable probably also replicates to everyone, calling OnRep on every client.

Since your delegate is static, it is not contextually bound to any specific character. So whenever a character has his health replicated, you are updating your HUD with that character’s health value.

You should probably move the delegate into character class, as a member (non static) property. Then in your HUD you need to grab the current character and bind its HealthUpdated delegate. This comes with the difficulty that you need to unbind/rebind the delegate whenever you change character/viewtarget. A simpler alternative would be to keep it as-is but make your Health variable only replicate to Owner (use COND_OwnerOnly in replication conditions). It will result in Health only replicating to owning player controller and not others. However this approach might bite you in the long run, if you ever want to see health of other players (team mates), or implement a spectator mode.

1 Like