[Unreal_C++_DarkSoul]#22_HUD
HUD(Head Up Display) 디자인 설계 및 구현
Overview
- 개요 및 설계
- 코드
- 영상
#1. 개요 및 설계
1. 개요
- [개요] : 사용자 캐릭터의 현재 상태 정보를 HUD로 표현합니다. 캐릭터의 HP, SP, MP, 그리고 스킬 쿨타임을 "게이지" 형식으로 표현하고, 6가지 직업의 무기, 무기 속성, 그리고 스킬 정보를 이미지 형식으로 표현합니다.
- [목표] : 게임 플레이 과정에서 사용자 캐릭터의 현재 상태에 대한 정보를 알리기 위한 HUD를 설계합니다. 보다 직관적이고, 변경 내용을 실시간으로 업데이트할 수 있도록 설계합니다. 더불어, 기반 정보의 변경이 별도의 코드 수정 없이 이루어지도록 합니다.
2. 설계
- [UI] : Unreal Engine이 제공하는 UMG를 통해 UI를 디자인합니다.
- [HUD 액터 컴포넌트] : 사용자의 상태 정보를 관리하는 HUD 액터 컴포넌트를 정의합니다.
#2. 코드
1. HUD
- [정의] : HUD는 게임 플레이 도중 사용자 화면 위에 겹쳐 놓이는 UI의 한 종류를 의미합니다. HUD의 목적은 플레이어에게 점수, 생명력, 남은 시간 등 게임의 현재 상태를 알리는 것입니다. HUD 는 보통 플레이어와 직접적으로 상호작용 하지 않습니다. 따라서, HUD는 일반적으로 게임의 현재 상태를 사용자에게 알리는 일방적 상호작용의 매개입니다.
- [특징] : HUD는 시각적 위계를 통해 UI의 중요도에 우선순위를 정하고, 해당 UI를 어느정도 강조할 것인지 결정하는 작업이 중요합니다. "2018 GDC"에서 게임 개발자 Zach Gage는 "The three reads" 의견을 제시했고, 1단계는 사용자가 게임 화면에서 직관적으로 발견하는 상태 정보, 2단계는 게임의 가장 중요한 룰, 그리고 3단계는 플레이 과정에서 준수할 세부적인 룰입니다. 이를 통해, HUD는 사용자의 Cognitive Load(인지 부하)를 최소화하여, 게임에 몰입할 수 있도록 해야 합니다.
- [종류] : HUD는 크게 두 가지로 나누어집니다. 첫 번째는 "게이지(Gauge)", 그리고 두 번째는 "프리뷰(Preview)"입니다.
- 게이지 : 게임 플레이 과정에서 사용자에게 중요한 정보를 화면에 표시하는 UI입니다. 이를 통해, 사용자는 게임 세계의 현재와 미래 정보를 이해하고, 의도를 갖고 계획과 행동을 실천할 수 있습니다.
- 프리뷰 : 게임 플레이 과정에서 사용자 입력에 따른 캐릭터의 미래 행동에 대한 예상 결과를 보여주는 UI입니다. 이를 통해, 사용자는 자신의 의도에 확신을 갖고 행동을 실천할 수 있습니다.
2. UMG(언리얼 모션 그래픽)
- [정의] : UMG(Unreal Motion Graphic)은 게임 내 HUD, 메뉴, 기타 인터페이스 관련 그래픽 요소로 사용자에게 나타낼 UI를 만드는데 사용되는 비주얼 UI 저작 툴입니다. UMG는 Widget이라는 주요 요소를 활용하며, Widget는 미리 만들어진 함수 시리즈로, 이들을 조합해 UI를 만듭니다.
3. HUD 위젯
Details
- HP, MP, SP : HUD는 사용자 캐릭터의 현재 체력, 마나, 그리고 스태미나를 게이지(Progress Bar)를 통해 화면에 표시합니다.
- Weapon Icon : HUD는 현재 무기의 정보를 해당 이미지를 통해 화면에 표시합니다.
- Skill Icons : HUD는 현재 무기가 사용 가능한 스킬 정보들을 해당 이미지들을 통해 화면에 표시합니다.
- Cool Time : HUD는 현재 무기가 사용 가능한 스킬들의 쿨 타임 정보를 게이지(Progress Bar)를 통해 화면에 표시합니다.
- Radar : 미완.
4. UHUD_Profile.h
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "HUD_Profile.generated.h"
UCLASS()
class DARKSOUL_API UHUD_Profile : public UUserWidget
{
GENERATED_BODY()
// ************************************************************************************
// Method
// ************************************************************************************
protected:
virtual void NativeOnInitialized();
// Progress Bar
public:
// #1. HP Bar
void SetHealthBar(float InFloat1, float InFloat2);
// #2. MP Bar
void SetManaBar(float InFloat1, float InFloat2);
// #3. SP Bar
void SetStaminaBar(float InFloat1, float InFloat2);
// Imgaes
public:
// Weapon
void SetWeaponIcon(UTexture2D* InTexture);
public:
// Skill
void SetSkill01Icon(UTexture2D* InTexture);
void SetSkill02Icon(UTexture2D* InTexture);
void SetSkill03Icon(UTexture2D* InTexture);
public:
// CoolTimes
void SetHSkill01CoolTimeBar(float InFloat1, float InFloat2);
void SetHSkill02CoolTimeBar(float InFloat1, float InFloat2);
void SetHSkill03CoolTimeBar(float InFloat1, float InFloat2);
public:
// [23-11-26] : On Prototyping
void SetRadarVisibility();
FORCEINLINE class UHUD_Radar* GetRadar() { return Radar; }
// ************************************************************************************
// Data Member
// ************************************************************************************
// Progress Bar
// #1. HP, MP, SP
protected:
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UProgressBar* HealthBar;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UProgressBar* ManaBar;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UProgressBar* StaminaBar;
// #2. Skills
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UProgressBar* Skill01CoolTimeBar;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UProgressBar* Skill02CoolTimeBar;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UProgressBar* Skill03CoolTimeBar;
// Image
// #1. Profile
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UImage* ProfileImage;
// #2. Weapon
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UImage* BorderImage00;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UImage* WeaponIcon;
// #3. Skills
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UImage* Skill01Icon;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UImage* BorderImage01;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UImage* Skill02Icon;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UImage* BorderImage02;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UImage* Skill03Icon;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UImage* BorderImage03;
private:
TArray<UImage*> SkillIcons;
// Texture
protected:
UPROPERTY(EditDefaultsOnly)
UTexture2D* BlankImage;
UPROPERTY(EditDefaultsOnly)
UTexture2D* SkillIconBorderImage;
protected:
// [23-11-26] : On Proto
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UHUD_Radar* Radar;
};
Details
- UHUD_Profile 클래스를 통해 HUD 위젯 내 하위 위젯들의 행동 방식을 결정합니다.
- UHUD_Profile은 크게 두 가지 하위 위젯을 가지며, UProgressBar(게이지) 형식의 진행 상태 바 위젯과 UImage(이미지) 혹은 UTexture2 D 형식의 이미지 위젯이 있습니다.
5. HUD의 게이지 설정
void UHUD_Profile::SetHealthBar(float InFloat1, float InFloat2)
{
if (HealthBar)
{
HealthBar->SetPercent(InFloat2 / InFloat1);
}
else
{
PrintLine();
return;
}
}
Details
- [HUD_게이지] : UProgressBar 클래스가 제공하는 SetPercent() 함수를 통해 게이지 상태를 화면에 표시합니다.
5. HUD의 이미지 설정
void UHUD_Profile::SetWeaponIcon(UTexture2D* InTexture)
{
if (WeaponIcon)
{
WeaponIcon->SetBrushFromTexture(InTexture);
}
else
{
PrintLine();
return;
}
}
void UHUD_Profile::SetSkill01Icon(UTexture2D* InTexture)
{
if (Skill01Icon)
{
Skill01Icon->SetBrushFromTexture(InTexture);
}
else
{
PrintLine();
return;
}
}
Details
- [HUD_이미지] : UImage 형식의 위젯은 UTexture2D 형식의 2D 이미지를 현재 Brush로 설정합니다.
6. UC_HUDComponent::BeginPlay()
void UC_HUDComponent::BeginPlay()
{
Super::BeginPlay();
AActor* OwnerActor = GetOwner();
if (OwnerActor)
{
// #1. Add HUD To User's ViewPort
AddProfileHUD();
// #2. Delegates
{
UC_HealthComponent* HealthComp = OwnerActor->FindComponentByClass<UC_HealthComponent>();
UC_ManaComponent* ManaComp = OwnerActor->FindComponentByClass<UC_ManaComponent>();
UC_StaminaComponent* StaminaComp = OwnerActor->FindComponentByClass<UC_StaminaComponent>();
UC_WeaponComponent* WeaponComp = OwnerActor->FindComponentByClass<UC_WeaponComponent>();
// HP
if(HealthComp)
{
HealthComp->OnUpdateHUDProfileHealthBar.BindUFunction(this, "UpdateHUDProfileHealthBar");
}
// MP, CoolTime
if (ManaComp)
{
ManaComp->OnUpdateHUDProfileManaBar.BindUFunction(this, "UpdateHUDProfileManaBar");
ManaComp->OnUpdateHUDProfileSkillCoolTime.AddDynamic(this, &UC_HUDComponent::UpdateHUDProfileSkillCoolTime);
}
// SP
if (StaminaComp)
{
StaminaComp->OnUpdateHUDProfileStaminaBar.BindUFunction(this, "UpdateHUDProfileStaminaBar");
}
// Weapon Image, Skill Images
if (WeaponComp)
{
WeaponComp->OnUpdateHUDWeaponImage.BindUFunction(this, "UpdateHUDProfileWeaponImage");
WeaponComp->OnUpdateHUDSkillIcons.BindUFunction(this, "UpdateHUDProfileSkillIcons");
}
// [23-11-29] : Test, Radar
//OwnerPlayer->OnShowCompassWidget.BindUFunction(this, "UpdateCompassVisibility");
}
}
else
{
PrintLine();
return;
}
}
Details
// Health Component -> HUD Component -> HUD
HealthComp->OnUpdateHUDProfileHealthBar.BindUFunction(this, "UpdateHUDProfileHealthBar");
- [HUD 액터 컴포넌트] : HUD 액터 컴포넌트는 사용자 캐릭터의 멤버 컴포넌트로 해당 객체의 사용자 화면에 UHUD_Profile 객체를 나타냅니다. 따라서, HUD 액터 컴포넌트는 UHUD_Profile 형식의 데이터 멤버를 가집니다.
- [액터 컴포넌트 가져오기] : 먼저, HUD의 하위 Widget들의 상태 정보를 처리하는 액터 컴포넌트들을 가져옵니다. 이때, AAcotr::FindComponentByClass 템플릿 함수를 통해 캐릭터 클래스의 헤더 파일 삽입을 생략할 수 있습니다.
- [Delegate에 동적 바인딩] : HUD의 하위 Widget들의 업데이트 함수는 서로 다른 액터 컴포넌트에서 발생하는 이벤트에 콜백 함수 혹은 Delegate로 바인딩합니다. 예를 들면, 캐릭터의 현재 HP 상태는 캐릭터의 Health 액터 컴포넌트에서 관리하며, 이를 실시간으로 업데이트하기 위해 Health 액터 컴포넌트의 OnUpdateHUDProfileHealthBar 델리게이트와 HUD 액터 컴포넌트의 UpdateHUDProfileHealthBar() 멤버 함수를 동적 바인딩합니다.
#3. 영상
'개인프로젝트' 카테고리의 다른 글
[Unreal_C++_DarkSoul]#21_기능 구현, 데이터 변환 (0) | 2023.11.05 |
---|---|
[Unreal_C++_DarkSoul]#20_기능 구현, Level Sequence (0) | 2023.09.17 |
[Unreal_C++_DarkSoul]#19_기능 구현, Portal (0) | 2023.07.23 |
[Unreal_C++_DarkSoul]#18_기능 구현, Sound (0) | 2023.07.23 |
[Unreal_C++_DarkSoul]#17_기능 구현, Grid 클래스 (0) | 2023.06.03 |