남은 총알의 개수를 UI로 띄워보자.

 

테스트로 만들어볼 것이기 때문에 비율 같은것은 따로 계산하지 않고 Text를 하나 적당히 배치한다.

 

// MyHUD.h
class FIRSTPLAYER_API UMyHUD : public UUserWidget
{
public:
    UPROPERTY(meta = (BindWidget))
    class UTextBlock* AmmoText;
};

 

그리고 UserWidget을 상속받는 C++ 클래스를 하나 만들어주고 블루프린트 위젯의 텍스트와 직접 바인딩 시키기 위해 속성도 지정한다. 블루프린트 위젯의 부모 클래스도 MyHUD로 바꿔준다.

 

 

// FirstPlayerGameMode.h
public:
    UPROPERTY()
    TSubclassOf<UUserWidget> HUD_Class; // UI 리소스

    UPROPERTY()
    UUserWidget* CurrentWidget; // 현재 띄우고 있는 UI
    
// FirstPlayerGameMode.cpp
AFirstPlayerGameMode::AFirstPlayerGameMode() : Super()
{
    static ConstructorHelpers::FClassFinder<UMyHUD> UI_HUD(TEXT("WidgetBlueprint'/Game/WBP_HUD.WBP_HUD_C'"));
    if (UI_HUD.Succeeded())
    {
        HUD_Class = UI_HUD.Class;

        CurrentWidget = CreateWidget(GetWorld(), HUD_Class);
        if (CurrentWidget)
        {
            CurrentWidget->AddToViewport(); // Z-order를 정하고 뷰포트에 추가함
            // CurrentWidget->RemoveFromViewport(); // 뷰포트에서 제거
        }
    }
}

 

블루프린트로 만든 WBP_HUD를 게임모드 C++ 클래스에서 생성해서 멤버 변수(HUD_Class)로써 가지고 있고 현재 월드에 멤버 변수에 저장한 WBP_HUD를 생성한다.

CurrentWidget은 현재 띄워진 UI를 가지게 되는데 실습에서는 하나의 UI만 사용하므로 별다른 차이는 없다.

 

다만 생성만 하고 뷰포트에 띄운것이 아니므로 AddToViewport 함수를 통해 실제로 출력한다.

반대로 RemoveFromViewport 함수를 통해 뷰포트에서 제거할 수도 있다.

 

 

현재까지 WBP_HUD에서 만들어둔 텍스트를 MyHUD 클래스에서 바인딩 하고, 게임모드 클래스의 생성자에서 UI 리소스(WBP_HUD)를 로드하고 뷰포트에 출력하는 것 까지 구현했다.

 

이제 실제로 총알 개수가 변해야 한다.

어떻게 구현할지 간단하게 생각해보면 해당 UI에서 멤버 변수를 만들고 데이터를 관리해서 직접 UI에 반영시키는 방법이 있을 것이다.

하지만 UI와 실제 데이터와 뒤섞으면 관리하기가 굉장히 어려워진다. 구조가 복잡해지면 해당 데이터가 UI 용도였는지 인게임 로직 용도였는지 알기가 어려워진다.

그렇기 때문에 UI에서 실제 데이터를 관리하지 않도록 주의해야한다.

 

지금은 총알 개수 하나만 테스트를 해보는 것이기 때문에 따로 데이터를 관리하지 않고 캐릭터 클래스에서 총알 관리를 해 볼 것이다.

 

// FirstPlayerCharacter.h
public:
    void RefreshUI();
    
public:
    int32 AmmoCount = 5;
    int32 MaxAmmoCount = 5;
    
// FirstPlayerCharacter.cpp
void AFirstPlayerCharacter::RefreshUI()
{
	// 월드의 게임모드를 가져와서 캐스팅 한다
    AFirstPlayerGameMode* GameMode = Cast<AFirstPlayerGameMode>(UGameplayStatics::GetGameMode(GetWorld()));
    if (GameMode)
    {
        UMyHUD* MyHUD = Cast<UMyHUD>(GameMode->CurrentWidget);
        if (MyHUD)
        {
            const FString AmmoStr = FString::Printf(TEXT("Ammo %01d/%01d"), AmmoCount, MaxAmmoCount);
            MyHUD->AmmoText->SetText(FText::FromString(AmmoStr));
        }
    }
}

void AFirstPlayerCharacter::OnFire()
{
    if (AmmoCount <= 0)
        return;

    AmmoCount -= 1;

    RefreshUI();
    
    // ..생략
}

 

BeginPlay에서도 UIRefresh를 추가해주면 시작 즉시 5/5로 초기화 된다.

위에서 설명했듯이 실제 캐릭터의 데이터는 다른 클래스나 구조체에서 관리하는 것이 좋을 것이다.

 

 

총알 하나만 관리하는데도 생각할 것들이 생긴다. 구조가 더 복잡해질때를 고려해야한다.

게임 컨텐츠와 UI가 맞물리다 보면 어디서 데이터를 관리해야 하는지가 생각보다 복잡해 질 수 있다.

따로 UI매니저를 만들어서 공통으로 접근할 수 있는것을 만들어서 관리 한다던가 하는 방법을 사용할 수 있다.

+ Recent posts