How should I implement HP Bar for ai and player?

Hi everyone. I’m implementing HP Bar for ai and player right now. At first, I only made player’s Hp Bar visible.


I made the widget pop on top of my head as shown in the picture above.
image
The player made it appear on the screen as shown in the picture above.

Now ai and player are inheriting the base class. Base has set up the deligate. Let me show you the code.

HpWidget code.

void UOLHpBarWidget::NativeConstruct()
{
	Super::NativeConstruct();

	HpBarProgress = Cast<UProgressBar>(GetWidgetFromName(TEXT("HpBar")));
	ensure(HpBarProgress);

	APawn* Player = GetOwningPlayerPawn();
	ensure(Player);

	IOLHpInterface* CharacterInterface = Cast<IOLHpInterface>(Player);
	ensure(CharacterInterface);

	CharacterInterface->SettingHpBar(this);
}

void UOLHpBarWidget::UpdateHpBar(float CurrentHp)
{
	UE_LOG(LogTemp, Error, TEXT("Update Hpabar?"));

	if (HpBarProgress)
	{
		HpBarProgress->SetPercent(CurrentHp); 
		UE_LOG(LogTemp, Error, TEXT("Update Hp"));
	}
}

Base code inherited by ai and Player.

// .h
protected:
	virtual void SettingHpBar(class UOLHpBarWidget* HpWidget) override;	
	virtual void ChangeHp() override;
	class UOLHpBarWidget* HpBarWidget;

// .cpp
void AOLCharacterBase::SettingHpBar(UOLHpBarWidget* HpWidget)
{
	HpBarWidget = HpWidget;
}

void AOLCharacterBase::ChangeHp()
{
}

Player Code

// .h
DECLARE_MULTICAST_DELEGATE_OneParam(FOnHpChangeDelegate, float /*CurrenHp*/);
public:
	FOnHpChangeDelegate OnHpChange;

// HP
protected:
	virtual void SettingHpBar(class UOLHpBarWidget* HpWidget) override; // A function for setting up a deligate.
	virtual void ChangeHp() override;	// Function to reflect hp in widget

// .cpp
void AOLCharacterPlayer::SettingHpBar(UOLHpBarWidget* HpWidget)
{
	//UOLHpBarWidget* HpBarWidget = Cast<UOLHpBarWidget>(HpBar);	
	OnHpChange.AddUObject(HpBarWidget, &UOLHpBarWidget::UpdateHpBar);
}

void AOLCharacterPlayer::ChangeHp()
{
	OnHpChange.Broadcast(Hp / MaxHp);
	UE_LOG(LogTemp, Warning, TEXT("Player Change Hp"));
}

ai Code

// .h
DECLARE_MULTICAST_DELEGATE_OneParam(FOnHpChangeDelegate, float /*CurrenHp*/);
public:
	FOnHpChangeDelegate OnHpChange;

protected:
	virtual void SettingHpBar(class UOLHpBarWidget* HpWidget) override; // A function for setting up a deligate.
	virtual void ChangeHp() override;	// Function to reflect hp in widget

// .cpp
void AOLNPCBasic::SettingHpBar(UOLHpBarWidget* HpWidget)
{
	//UOLHpBarWidget* HpBarWidget = Cast<UOLHpBarWidget>(HpBar);
	OnHpChange.AddUObject(HpBarWidget, &UOLHpBarWidget::UpdateHpBar);
}

void AOLNPCBasic::ChangeHp()
{
	OnHpChange.Broadcast(Hp / MaxHp);	
	UE_LOG(LogTemp, Warning, TEXT("NPC Change Hp"));
}

The code is too long. Sorry, but ChangeHp functions are called when being attacked or recovering from physical strength. Logs are visible for both ai and player. When ChangeHp is called, the UpdateHpBar function is called. But the log doesn’t show up at this time. I think there’s a problem with widget settings, but I don’t know how to fix it…
For reference, the reason why ai and player have the same function and the same content was that when changeHp was written in Base, ai referred to the player’s widget as it was, and when the player was attacked, the widget in ai moved the same way as the player.

This is getting too long. Thank you for reading. I really appreciate your help.
(The language may not be natural because of the use of a translator.)

I could be wrong, but I don’t think GetOwningPlayerPawn() works inside a widget inside a widget component. There’s no need to do this anyhow. Just use WidgetComponent->GetUserWidgetObject() and cast it to your widget class. You can do this on BeginPlay. At least, that’s what I’m doing and it works fine.

Then you have an IOLHpInterface. Is that an interface that can be used in Blueprints? If so, you’re not supposed to call methods directly unless your interface has this UINTERFACE tag:

meta = (CannotImplementInterfaceInBlueprint)

Otherwise, you need to use something like this:

TScriptInterface<IOLHpInterface> CharacterInterface(ParentActor);
CharacterInterface->Execute_SettingHpBar(CharacterInterface->_getUObject(), this);

OR

IOLHpInterface::Execute_SettingHpBar(ParentActor, this);

As for the logs, it wasn’t clear from your description what is working and what isn’t. You said this:

But immediately after, you say this:

Which is it? And what logs work and what logs don’t work?

Anyhow, from what I’m seeing, if GetOwningPlayerPawn() does work, it’ll always return the same value so you’re just setting the widget on the player actor. The NPC’s widget pointer is always null. I would be very surprised if the NPC’s HpBarWidget member is not null.

Thank you so much for your response!

It’s not an interface that can be used in Blueprint!

I changed the code as I understood it. For now, Deligate has set up player and ai separately.

player code

// HP
protected:
	virtual void SettingHpBar(class UOLHpBarWidget* HpWidget) override;	
	virtual void DrinkHpPotion(float HpAmount) override;	
	virtual void ChangeHp() override;	

void AOLCharacterPlayer::SettingHpBar(UOLHpBarWidget* HpBar)
{
	if (IsPlayerControlled())
	{
		UOLHpBarWidget* HpBarWidget = Cast<UOLHpBarWidget>(HpBar);	// 초기화
		OnHpChange.AddUObject(HpBarWidget, &UOLHpBarWidget::UpdateHpBar);
		UE_LOG(LogTemp, Warning, TEXT("Player Setting HpBar"));
	}
}

void AOLCharacterPlayer::DrinkHpPotion(float HpAmount)
{
	Hp = FMath::Clamp(Hp + HpAmount, 0.0f, MaxHp);	
	ChangeHp();	
}

void AOLCharacterPlayer::ChangeHp()
{
	OnHpChange.Broadcast(Hp / MaxHp);	
	UE_LOG(LogTemp, Warning, TEXT("Player Change Hp"));
}

First of all, the important thing in the code above is that there are a total of 4 npc in the map, UE_LOG (LogTemp, Warning, TEXT (“Player Setting HpBar”) including player; that log appears 5 times. I don’t know why. And the important thing is that when a player is attacked, ai seems to share that widget as well. There are fewer widgets together.

ai code

AOLNPCBasic::AOLNPCBasic()
{
	// HP Bar
	HpBar = CreateDefaultSubobject< UWidgetComponent>(TEXT("HpBar"));
	HpBar->SetupAttachment(GetMesh());	// 메시에 부착
	HpBar->SetRelativeLocation(FVector(0.0f, 0.0f, 202.0f));	// 머리 위에 위치하게(부착된 메시를 기준으로 움직임)
	// 월드공간 기준으로 배치될지(3D UI), 스크린 좌표 기준으로 배치될지(2D UI) 결정
	// Screen은 절대로 화면 밖으로 짤리지 않는다
	HpBar->SetWidgetSpace(EWidgetSpace::Screen);
	HpBar->SetDrawSize(FVector2D(150.0f, 20.0f));	// 위젯 크기
	HpBar->SetCollisionEnabled(ECollisionEnabled::NoCollision);	// 충돌 없음

void AOLNPCBasic::BeginPlay()
{
	Super::BeginPlay();

	// HP Bar
	UUserWidget* Widget = HpBar->GetUserWidgetObject();
	UOLHpBarWidget* HpWidget = Cast<UOLHpBarWidget>(Widget);
	OnHpChange.AddUObject(HpWidget, &UOLHpBarWidget::UpdateHpBar);

}

void AOLNPCBasic::ChangeHp()
{
	OnHpChange.Broadcast(Hp / MaxHp);	// 체력이 바뀌면 신호 보내기, 해당 캐릭터
	UE_LOG(LogTemp, Warning, TEXT("NPC Change Hp"));
}

Base code


float AOLCharacterBase::TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);

	Hp = FMath::Clamp(Hp - DamageAmount, 0.0f, MaxHp);	// 체력  업데이트

	if (AOLCharacterPlayer* Player = Cast<AOLCharacterPlayer>(HpUpdateTarget))
	{
		IOLUpdateHpInterface* HpInterface = Cast<IOLUpdateHpInterface>(Player);
		HpInterface->ChangeHp();
	}
	else if (AOLNPCBasic* NPC = Cast<AOLNPCBasic>(HpUpdateTarget))
	{
		IOLUpdateHpInterface* HpInterface = Cast<IOLUpdateHpInterface>(NPC);
		HpInterface->ChangeHp();
	}
	

	if (Hp <= 0.0f && !IsDead)
	{
		DeadCheck();
	}

	return DamageAmount;
}

The reason I wrote the if statement in the code above is because if a player is attacked, the widget in the ai keeps moving the same way as the player’s widget. But it didn’t work. I think I’m doing something wrong. There are so many things I don’t know.. (sorry)

If there’s a change in fitness, I’ve made it to log in withget. If a player is attacked, only one player has a change in fitness, so with with without, you’ll only get one log. But a total of 5 logs. Including ai’s.
And importantly, if the ai is actually attacked, the widgets in all the ai will change…

For your AOLCharacterPlayer, you should really get rid of the SettingBar function. Also remove the call from the widget.

Your widget should initialize like this:

void UOLHpBarWidget::NativeConstruct()
{
	Super::NativeConstruct();

	HpBarProgress = Cast<UProgressBar>(GetWidgetFromName(TEXT("HpBar")));
	ensure(HpBarProgress);
}

Your AOLNPCBasic constructor should call the base class constructor.

AOLNPCBasic::AOLNPCBasic()
  : Super()
{
...
}

I like how you’re setting up the widget in the NPC class and how you set up the delegate in BeginPlay(). You should do the same thing with your player character. This way, you don’t need the SettingBar function at all and you can safely remove it as mentioned above.

I’m not sure I understand what the issue is, but the fact that you said the log is being called 5 times is likely because you’re creating 5 widgets and this code:

APawn* Player = GetOwningPlayerPawn();

always returns the player character even if it’s on an NPC. So it will call SettingHpBar() 5 times on your player character. This is why you see the same log 5 times. Now, shouldn’t IsPlayerControlled() return false for the NPCs? You would think so. So I’m not sure what’s going on there. Did you correctly set the AIController on your NPCs?

Anyhow, try removing the SettingHpBar function and set up your character widget the same way you did with your NPC with the BeginPlay function. After that, your widgets should start working a lot better.

BTW, you don’t need an interface to call ChangeHp(). It’s virtual. You can just call it directly in the TakeDamage function.

this->ChangeHp();

Not sure what HpUpdateTarget is or how it was initialized, but you don’t need it for calling ChangeHp();