Unreal - 데미지 오버레이

2025. 12. 1. 06:53·Unreal 프로젝트 다이어리/두번째 프로젝트

구현내용

플레이어의 HP가 일정 이하로 떨어지면, 화면에 붉은색이 번쩍이는

데미지 오버레이 효과를 구현하였습니다

 

사용한 클래스

HpState HP 퍼센트 구간에 따른 단계 를 정의한 Enum 클래스
MainWidget 전달받은 HpState 단계에 따라 화면 데미지 오버레이 연출
StatComponent 현재 HP 변화를 기반으로 HpState를 계산하여 UI에 전달

 

구현

HpState

먼저 상태를 다른 시스템에서 공통으로 사용하기 위해 별도의 EnumClass를 추가해주었습니다

#pragma once

#include "CoreMinimal.h"
#include "HpState.generated.h"

UENUM(BlueprintType)
enum class EHpState : uint8
{
    normal    UMETA(DisplayName = "Normal"),
    danger    UMETA(DisplayName = "Danger"),   
    critical  UMETA(DisplayName = "Critical")
};

 

MainWidget

메인위젯에서 표현될 데미지 오버레이 이미지를 추가하였습니다

 

public:
	UPROPERTY(meta = (BindWidget))
	class UImage* Image_EffectBlink;

	UPROPERTY(meta = (BindWidget))
	class UImage* Image_EffectDamage;

	UPROPERTY(meta = (BindWidget))
	class UImage* Image_EffectHugeDamage;

	UPROPERTY(meta = (BindWidgetAnim), Transient)
	class UWidgetAnimation* BlinkAnim;

	UPROPERTY(meta = (BindWidgetAnim), Transient)
	class UWidgetAnimation* DamageAnim;

	UPROPERTY(meta = (BindWidgetAnim), Transient)
	class UWidgetAnimation* HugeDamageAnim;
public:
	EHpState currentHpState = EHpState::normal;

	TMap<EHpState, UWidgetAnimation*> effectAnimations;

	UFUNCTION()
	void ApplyHpState(EHpState newState);

	UFUNCTION()
	void PlayBlinkEffect();

	UFUNCTION()
	void OnBlinkAnimFinished();

처음에 이미지를 숨기고 effectAnimations Map에 애니메이션을 추가하여주었습니다

void UMainWidget::NativeConstruct()
{
	Super::NativeConstruct();
	Image_EffectBlink->SetVisibility(ESlateVisibility::Collapsed);
	Image_EffectDamage->SetVisibility(ESlateVisibility::Collapsed);
	Image_EffectHugeDamage->SetVisibility(ESlateVisibility::Collapsed);

	effectAnimations.Add(EHpState::danger, DamageAnim);
	effectAnimations.Add(EHpState::critical, HugeDamageAnim);
	effectAnimations.Add(EHpState::normal, nullptr);

}

 

 

 

플레이어의 Hp상태가 바뀔 때 마다 이전 효과를 정리하고 새로운 데미지 오버레이 이미지를 UI에 적용시켜줍니다

void UMainWidget::ApplyHpState(EHpState newState)
{
	if (newState == currentHpState)
		return;

	if (UWidgetAnimation** beforeAnim = effectAnimations.Find(currentHpState))
	{
		if (beforeAnim && *beforeAnim)
			StopAnimation(*beforeAnim);
	}

	switch (currentHpState)
	{
	case EHpState::danger:
		if (Image_EffectDamage) Image_EffectDamage->SetVisibility(ESlateVisibility::Collapsed);
		break;
	case EHpState::critical:
		if (Image_EffectHugeDamage) Image_EffectHugeDamage->SetVisibility(ESlateVisibility::Collapsed);
		break;
	}

	currentHpState = newState;

	switch (newState)
	{
	case EHpState::danger:
		if (Image_EffectDamage) 
			Image_EffectDamage->SetVisibility(ESlateVisibility::Visible);
		if (UWidgetAnimation** afterAnim = effectAnimations.Find(newState))
		{
			if (afterAnim && *afterAnim)
				PlayAnimation(*afterAnim, 0.f, 0);
		}
		break;
	case EHpState::critical:
		if (Image_EffectHugeDamage) Image_EffectHugeDamage->SetVisibility(ESlateVisibility::Visible);
		if (UWidgetAnimation** afterAnim = effectAnimations.Find(newState))
		{
			if (afterAnim && *afterAnim)
				PlayAnimation(*afterAnim, 0.f, 0); 
		}
		break;
	case EHpState::normal:
		if (Image_EffectDamage) 
			Image_EffectDamage->SetVisibility(ESlateVisibility::Collapsed);
		if (Image_EffectHugeDamage) 
			Image_EffectHugeDamage->SetVisibility(ESlateVisibility::Collapsed);
		break;
	}

}

void UMainWidget::PlayBlinkEffect()
{
	Image_EffectBlink->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
	FWidgetAnimationDynamicEvent blinAnimFinishEv;
	blinAnimFinishEv.BindDynamic(this, &UMainWidget::OnBlinkAnimFinished);
	BindToAnimationFinished(BlinkAnim, blinAnimFinishEv);
	PlayAnimation(BlinkAnim, 0.f, 1, EUMGSequencePlayMode::Forward, 1.f);
}

void UMainWidget::OnBlinkAnimFinished()
{
	Image_EffectBlink->SetVisibility(ESlateVisibility::Collapsed);
}

StatComponent

델리게이트를 선언해줍니다

.h
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHpStateChanged, EHpState, newState);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnBlink);

 

public:
	UPROPERTY()
	FOnHpStateChanged onHpStateChanged;

	UPROPERTY()
	FOnBlink onBlink;

	EHpState currentHpState = EHpState::normal;
	TArray<FHpStateRule> hpStateRules;

	EHpState CalculateHpState(float hpPercent) const;

	UFUNCTION()
	void UpdateHpState();

	UFUNCTION()
	void TriggerBlink();

 

Hp 퍼센트를 보고 어떤 State인지 판단하여 반환하며 

UpdateHpState에서는 Hp변화에 따라 상태가 달라졌으면 이벤트를 브로드캐스트합니다

EHpState UStatComponent::CalculateHpState(float hpPercent) const
{
	for (const FHpStateRule& rule : hpStateRules)
	{
		if (hpPercent <= rule.hpThreshold)
			return rule.State;
	}
	return EHpState::normal;
}

void UStatComponent::UpdateHpState()
{
	if (!stats.Contains(EBaseStatType::Hp))
		return;

	float maxHp = stats[EBaseStatType::Hp].EntryTotal();
	if (maxHp <= 0.f) 
		return;

	float hpPercent = currentHp / maxHp;
	EHpState newState = CalculateHpState(hpPercent);

	if (newState != currentHpState)
	{
		currentHpState = newState;
		onHpStateChanged.Broadcast(newState);
	}

}

void UStatComponent::TriggerBlink()
{
	if (onBlink.IsBound())
		onBlink.Broadcast();
}

 

또한 BeginPlay에서 반드시 규칙을 초기화해줍니다

3할일때 Critical

4.5할일때 Danger State를 반환합니다

if (hpStateRules.Num() == 0)
{
	hpStateRules.Add({ 0.3f, EHpState::critical });
	hpStateRules.Add({ 0.45f, EHpState::danger });
}

 

MainWidget - 2

다시 돌아와서 메인위젯에서 델리게이트를 구독해줍니다

필자는 BeginPlay에서 설정해주었습니다

statComp->onHpStateChanged.AddDynamic(this, &UMainWidget::ApplyHpState);
statComp->onBlink.AddDynamic(this, &UMainWidget::PlayBlinkEffect);

 

이렇게 구현한건 UStatComponent가 플레이어의 HP를 관리함으로 상태가 바뀌면 이벤트를 브로드캐스트하고

MainWidget에서 statcomponent의 이벤트를 구독하여

델리게이트로 구독자에게 알려서 동작 흐름이 발견되면 UI를 업데이트시킵니다

이렇게 하면 StatComponent가 UI를 직접 몰라도되며 확장성도 좋고 재사용성도 넓어집니다

(혹시 더 좋은 방법 있으면 댓글로 알려주세요!)

 

결과

4.5할의 Danger 부터 3할의 Critical로 변하는 모습이다

 

Critical 데미지 오버레이

저작자표시 비영리 변경금지 (새창열림)

'Unreal 프로젝트 다이어리 > 두번째 프로젝트' 카테고리의 다른 글

Unreal - 세이브 게임  (0) 2025.12.02
Unreal - 경험치, 레벨업 구현하기  (0) 2025.12.02
Unreal - 인벤토리(5-2) (UI 디테일 추가)  (0) 2025.11.29
Unreal - 인벤토리(5) ( 아이템 정보 )  (0) 2025.11.28
Unreal - 인벤토리(4) ( 정렬, 삭제 )  (0) 2025.11.26
'Unreal 프로젝트 다이어리/두번째 프로젝트' 카테고리의 다른 글
  • Unreal - 세이브 게임
  • Unreal - 경험치, 레벨업 구현하기
  • Unreal - 인벤토리(5-2) (UI 디테일 추가)
  • Unreal - 인벤토리(5) ( 아이템 정보 )
lucodev
lucodev
커피와 노트북 그리고 개발
  • lucodev
    루코 개발테이블
    lucodev
  • 전체
    오늘
    어제
    • 분류 전체보기 (210) N
      • Unreal 프로젝트 다이어리 (107) N
        • 첫번째 프로젝트 (73)
        • 두번째 프로젝트 (34) N
      • Unreal 팁 (8)
      • Unreal 디버깅 (8)
      • C++ 프로그래머스 (52)
        • Stack,Queue (7)
        • Hash (4)
        • Heap (2)
        • Sort (5)
        • Exhaustive search (5)
        • Greedy (2)
        • BFS , DFS (7)
        • Graph (2)
        • Dynamic Programming (1)
        • C++ Math (2)
        • 기타 문제 (14)
      • C++ 백준 (4)
      • C++ 팁 (1)
      • 개인 코테 & 스타디 <비공개> (29)
        • 코드 개인보관함 (9)
        • 코딩테스트+@ (11)
        • 알고리즘 스타디 (6)
        • 알고리즘 스타디 과제 (3)
        • 비공개 (0)
  • 인기 글

  • 최근 글

  • 최근 댓글

  • 링크

  • 공지사항

  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 태그

    unreal 파쿠르
    언리얼 상호작용
    언리얼
    언리얼 인벤토리
    언리얼 parkour
    언리얼 모션매칭
    언리얼 파쿠르
    언리얼 프로그래스바
    언리얼 시퀀스
    언리얼 컷씬
    언리얼 behavior tree
    unreal inventory
    Unreal Parkour
    unreal 모션매칭
    언리얼 motionmatching
    unreal 인벤토리
    언리얼 비헤이비어트리
    언리얼 ui
    unreal 시퀀스
    언리얼 behaviortree
  • hELLO· Designed By정상우.v4.10.3
lucodev
Unreal - 데미지 오버레이
상단으로

티스토리툴바