Dev./UE 언리얼 엔진

[TIL_250224] 언리얼 엔진 탄약 UI 연동, 크로스헤어(조준점) 줌인 구현

raindrovvv 2025. 2. 24. 21:21

💭회고

오늘은 언리얼 엔진을 활용한 무기 시스템 구현해보았다. 총기 탄약 UI 연동, 실시간 탄약 정보 업데이트, 총알 아이콘의 연동, 그리고 줌인 시 크로스헤어 표시 기능을 단계별로 구현해보았다...!

이를 통해 HUD와 게임 로직을 분리하여 델리게이트를 통한 통신 방식을 적극 활용하면, 유지보수와 확장성을 높일 수 있다는 것을 배웠다.

🗺️마인드맵

📒학습 내용

1. 무기 탄약 UI 연동 및 기본 초안

1.1 초안 구성 및 CWeapon.h

  • 무기 클래스 확장: 기존 ACWeapon 클래스를 기반으로 탄약 관련 멤버와 HUD 연동용 getter 함수를 추가했다.
  • 핵심 코드:
    • GetMaxMagazineCount(), GetCurrentMagazineCount()를 통해 탄약 정보를 반환하며, HUD 연동을 위해(편의를 위해) 별칭 함수인 GetMaxAmmo()와 GetCurrentAmmo()를 사용했다.

1.2 CWeaponComponent.h / .cpp

  • 무기 타입 및 탄약 정보 관리:
    • 무기 종류(라이플, 피스톨, 칼 등)를 구분하고, 해당 무기별로 탄약 정보와 UI 업데이트 델리게이트를 정의!
    • 델리게이트(FAmmoChanged)를 통해 탄약 정보가 변경될 때마다 HUD에 내용을 전달했다.
  • 코드 활용:
    • 무기 장착, 발사, 재장전 시 각각 최신 탄약 정보를 HUD에 즉시 전달했다.
  • 핵심 요약:
    • 무기 컴포넌트는 각 무기의 상태를 관리하며, 탄약 업데이트를 위한 델리게이트를 활용하여 UI와의 연동을 실시간으로 수행한다.
더보기
.h
class ACWeapon;
class ACCharacter;

UENUM(meta = (BlueprintSpawnableComponent))
enum class EWeaponType : uint8
{
	Rifle, Pistol, Knife, Max	
};

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FWeaponTypeChanged, EWeaponType, InPrevType, EWeaponType, InNewType);

// 탄약 정보 변경 델리게이트 선언 (현재 탄약, 최대 탄약)
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAmmoChanged, int32, CurrentAmmo, int32, MaxAmmo);


UCLASS (ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class START_API UCWeaponComponent : public UActorComponent
{
	GENERATED_BODY()
private:
	UPROPERTY(EditAnywhere, Category ="Settings")
	TArray<TSubclassOf<class ACWeapon>> WeaponClasses;
public:
	FORCEINLINE bool IsUnarmedModeMode(){return Type == EWeaponType::Max;}
	FORCEINLINE bool IsRifleMode(){return Type == EWeaponType::Rifle;}
	FORCEINLINE bool IsPistolMode(){return Type == EWeaponType::Pistol;}
	FORCEINLINE bool IsKnifeMode(){return Type == EWeaponType::Knife;}
    
// 생략 ~


public:
	FWeaponTypeChanged OnWeaponTypeChanged; // 무기 타입 변경 델리게이트
	FAmmoChanged OnAmmoChanged; // 무기의 탄약 정보 변경 시 전달할 델리게이트 추가
.cpp
.cpp
void UCWeaponComponent::SetMode(EWeaponType InType)
{
	if (Type == InType)
	{
		SetUnarmedMode();
		
		return;
	}
	else if(IsUnarmedModeMode() == false)
	{
		//무기를 장착하고 있는 상태라면 현재 무기를 장착해제할 수 있는지 체크한뒤 무기 장착 해제
		if(GetCurrentWeapon()->CanUnequip() == false)
			return;

		GetCurrentWeapon()->Unequip();
	}

	if (Weapons[(int32)InType] == nullptr)
		return;
	if (Weapons[(int32)InType]->CanEquip() == false)
		return;
	Weapons[(int32)InType]->Equip();
	ChangeType(InType);
}


// ~

void UCWeaponComponent::Begin_Fire() // 무기 발사 시작
{
	if (GetCurrentWeapon() == nullptr)
		return;
	if (GetCurrentWeapon()->CanFire() == false)
		return;

	GetCurrentWeapon()->BeginFire();

	// 발사 후 최신 탄약 정보 전달 (HUD 업데이트용)
	int32 CurrentAmmo = GetCurrentWeapon()->GetCurrentAmmo();
	int32 MaxAmmo = GetCurrentWeapon()->GetMaxAmmo(); 
	OnAmmoChanged.Broadcast(CurrentAmmo, MaxAmmo);
}

void UCWeaponComponent::End_Fire()
{
	if (GetCurrentWeapon() == nullptr)
		return;;

	GetCurrentWeapon()->EndFire();
}

// ~

void UCWeaponComponent::Reload()
{
	if(GetCurrentWeapon() == nullptr)
		return;
	GetCurrentWeapon()->Reload();

	// 재장전 후 최신 탄약 정보 전달 (HUD 업데이트용)
	int32 CurrentAmmo = GetCurrentWeapon()->GetCurrentAmmo();
	int32 MaxAmmo = GetCurrentWeapon()->GetMaxAmmo();
	OnAmmoChanged.Broadcast(CurrentAmmo, MaxAmmo);
}

2. 실시간 탄약 업데이트

2.1 기능 추가 배경

  • 개선 사항:
🐞키패드 1번을 누르면 총을 들게 되는데 현재 문제점은 마우스 클릭을 한 번 눌러줘야 탄약 정보가 업데이트가 된다는 것이다. 그리고 또 개선하고 싶은 부분은 계속 탄약 정보가 보여지는 것이 아니라 총을 들 때 보이도록 하고 싶었다...!
무기를 장착할 때 즉시 라이플의 탄약이 업데이트되어 보여지도록 하고, 마우스 오른쪽 클릭 전까지 기다리지 않고 바로 반영하도록 개선한다.

2.2 적용 내용 및 수정 코드

  • CWeaponComponent.cpp
    • 무기 장착 시, 탄약 정보를 바로 HUD에 전달하도록 OnAmmoChanged.Broadcast() 호출을 추가했다.
  • CHUDWidget.h
    • 새로운 HUD 업데이트 함수를 추가했다 ➡️ UpdateWeaponType(), SetAmmoVisibility()
  • CPlayerController.cpp
    • HUD 위젯 생성 후, 무기 컴포넌트의 델리게이트에 HUD의 UpdateAmmo()와 함께 UpdateWeaponType() 함수를 바인딩했다.
  • 핵심 요약:
    • 무기 장착과 발사, 재장전 시 탄약 정보를 실시간으로 HUD에 업데이트하여 플레이어에게 즉각적인 피드백을 제공한다.

더보기
CWeaponComponent.cpp
CHUDWidget.h
CPlayerController.cpp

3. 총알 아이콘과 HUD 연동 방법

3.1 아이콘 표시 조건

  • 목적:
    • 무기를 들었을 때만 총알 아이콘(AmmoIcon)이 보이도록 하며, 탄약이 0인 경우에는 아이콘을 숨겨서 탄약이 없다는 것을 표현하고 싶었다✨
  • 구현 방식:
    • HUD 위젯 내의 UpdateAmmo() 함수에서 탄약 수에 따라 AmmoIcon의 Visibility를 제어했다.
    • SetAmmoVisibility() 함수에서 탄약 텍스트와 아이콘의 표시 여부를 동시에 설정했다.
    • 중요한 점 ! ➡️ 위젯블루프린트에서 아이콘의 설정을 Hidden으로 해줘야 한다. (처음에는 보이지 않도록!)

WBP_CHUDWidget


3.2 코드

void UCHUDWidget::UpdateAmmo(int32 iCurrentAmmo, int32 iMaxAmmo)
{
    if (AmmoText)
    {
        FString AmmoString = FString::Printf(TEXT("%d / %d"), iCurrentAmmo, iMaxAmmo);
        AmmoText->SetText(FText::FromString(AmmoString));
    }
    // 탄약이 0 이상이면 아이콘 표시, 아니면 숨김 처리한다.
    if (AmmoIcon)
    {
        AmmoIcon->SetVisibility(iCurrentAmmo > 0 ? ESlateVisibility::Visible : ESlateVisibility::Hidden);
    }
}

3.3 실무 팁⚙️

  • 코드 관리:
    • UI 업데이트 관련 코드는 한 곳에 모아두어 변경 사항이 있을 때 쉽게 유지보수할 수 있도록 관리한다.
  • 디버깅:
    • HUD 업데이트 시 로그를 활용하여 상태를 확인하고, 예외 상황(ex. null 포인터)이 발생하지 않도록 주의한다.

4. 줌인 시 크로스헤어(조준점) 표시 구현

4.1 기능 필요성 및 개념 설명

  • 기능 설명:
    • 🐞플레이어가 무기를 장착한 후, 줌인(aim) 상태로 전환할 때 크로스헤어(조준점)를 화면에 표시하고 싶었다. 왜냐하면 FPS가 아닌 TPS 시점이라 플레이어 캐릭터의 머리가 크로스헤어와 중첩됐기 때문이다.

4.2 구현 방법 및 코드 적용

  • 무기 컴포넌트:
    • FAimChanged 델리게이트를 선언하여 Aim 상태 변경을 알리고, BeginAim()과 EndAim() 함수에서 이를 Broadcast 했다.
  • HUD 위젯:
    • SetCrosshairVisibility(bool bVisible) 함수를 통해 크로스헤어 아이콘의 Visibility를 제어했다.
  • CPlayerController:
    • HUD 생성 후, 무기 컴포넌트의 OnAimChanged 델리게이트를 HUD의 SetCrosshairVisibility()에 바인딩하여 Aim 상태가 변경될 때마다 크로스헤어 표시를 즉시 업데이트했다.

4.3 코드

// UCWeaponComponent.cpp - BeginAim() 함수 예시
void UCWeaponComponent::BeginAim()
{
    if (GetCurrentWeapon() == nullptr)
        return;
    
    GetCurrentWeapon()->BeginAim();
    OnAimChanged.Broadcast(true); // Aim 상태로 변경됨을 알림한다.
}

// CHUDWidget.cpp - SetCrosshairVisibility() 함수 예시
void UCHUDWidget::SetCrosshairVisibility(bool bVisible)
{
    if (CrosshairIcon)
    {
        CrosshairIcon->SetVisibility(bVisible ? ESlateVisibility::Visible : ESlateVisibility::Hidden);
    }
}
// HUD 생성 후, 무기 컴포넌트의 델리게이트 바인딩 (Ammo, WeaponType, Aim)
		if (APawn* MyPawn = GetPawn())
		{
			// 캐스팅하여 UCHUDWidget로 사용
			if (UCHUDWidget* HUD = Cast<UCHUDWidget>(CurrentWidget))
			{
				if (UCWeaponComponent* WeaponComp = MyPawn->FindComponentByClass<UCWeaponComponent>())
				{
					WeaponComp->OnAmmoChanged.AddDynamic(HUD, &UCHUDWidget::UpdateAmmo);
					WeaponComp->OnWeaponTypeChanged.AddDynamic(HUD, &UCHUDWidget::UpdateWeaponType);
					WeaponComp->OnAimChanged.AddDynamic(HUD, &UCHUDWidget::SetCrosshairVisibility);
				}
			}
		}

 


🔗참고 링크

https://designerd.tistory.com/entry/Unreal-55강

 

[UE] 총알 연사, HUD & CrossHair

전반적으로 언리얼 엔진에서 TPS 총알 발사를 구현하려면 블루프린트, 애니메이션 및 물리 시뮬레이션의 조합이 필요하다. 올바른 설정을 통해 플레이어가 게임에 계속 몰입할 

designerd.tistory.com

 


🟣오늘의 옵시디언 현황