[CH4-20] 캐릭터 산소(Breath) 시스템 구현

김여울·2025년 10월 7일
0

내일배움캠프

목록 보기
92/111

💭 원하는 것

  • 캐릭터가 물에 들어가면 1초에 1씩 산소가 줄고, 산소가 0이 되면 체력이 2씩 줄어듦

  • 물에서 나오면 산소가 점점 회복됨

  • 구성 :

    • 캐릭터 클래스에 산소 관리 변수, 타이머, 함수 구현
    • 산소 수치 변화 시 UI를 HUD에 실시간으로 반영

🖥️ 코드

1️⃣ 캐릭터 변수

// 최대 산소량
UPROPERTY(EditAnywhere, Replicated, Category = "Stats")
float MaxBreath = 100.f;

// 현재 산소량 (네트워크 복제, 변경 감지 함수 지정)
UPROPERTY(ReplicatedUsing=OnRep_CurretnBreath)
float CurrentBreath = MaxBreath;

// 산소 부족 시 체력에 입는 데미지량
UPROPERTY(EditAnywhere, Replicated, Category = "Stats")
float NotEnoughBreathDmg = 2.f;

2️⃣ 산소 감소/회복 타이머 및 함수

// 1초 간격 호출됨
void DecreaseBreath()
{
    if(CurrentBreath > 0)
    {
        CurrentBreath = FMath::Max(0.f, CurrentBreath - 1.f);
        OnRep_CurretnBreath(); // UI 갱신용 함수 호출
    }
    else
    {
        ApplyDamage(NotEnoughBreathDmg); // 산소 0 이면 체력 깎기
    }
}

void IncreaseBreath()
{
    if(CurrentBreath < MaxBreath)
    {
        CurrentBreath = FMath::Min(MaxBreath, CurrentBreath + 1.f);
        OnRep_CurretnBreath();
    }
    else
    {
        GetWorldTimerManager().ClearTimer(BreathTimer);
    }
}

3️⃣ CurrentBreath 상태 변경 감지 및 UI 갱신 호출

void OnRep_CurretnBreath()
{
    if (APlayerController* PC = Cast<APlayerController>(Controller))
    {
        if (ADCPlayerController* MyPC = Cast<ADCPlayerController>(PC))
        {
            if (UInGameHUDWidget* HUD = MyPC->GetInGameHUD())
            {
                HUD->SetOxygen(CurrentBreath, MaxBreath);
            }
        }
    }
}

4️⃣ 체력 감소 함수 분리

void ApplyDamage(float DamageAmount)
{
    CurrentHP = FMath::Clamp(CurrentHP - DamageAmount, 0.f, MaxHP);
    // 사망 판정 등 추가 구현 가능
}
  • 체력과 산소는 별도 변수
  • 산소가 0이 되었을 때 ApplyDamage 가 호출돼 체력을 깎음
  • ApplyDamage 함수는 체력만 다룸

5️⃣ UI 초기화 및 동기화

InGameHUDWidget.cpp (NativeConstruct 초기화 부분)

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

    if (APlayerController* PC = GetOwningPlayer())
    {
        if (AWaitingPlayerState* PS = PC->GetPlayerState<AWaitingPlayerState>())
        {
            if (!PS->PlayerNickname.IsEmpty())
            {
                SetNicknameText(PS->PlayerNickname);
            }
            else
            {
                SetNicknameText(PS->GetPlayerName());
            }
            PS->OnNicknameChanged.AddDynamic(this, &UInGameHUDWidget::SetNicknameText);
        }
    }

    // 초기 체력 UI 설정 - 임시 100/100
    UpdateHealthDisplayWithValues(100.0f, 100.0f);

    // 초기 산소 UI 설정 - 임시 100/100
    SetOxygen(100.0f, 100.0f);
}

DCCharacter.cpp (생성자 및 BeginPlay)

ADCCharacter::ADCCharacter()
{
    PrimaryActorTick.bCanEverTick = true;

    MaxHP = 100.f;
    CurrentHP = MaxHP;

    MaxBreath = 100.f;
    CurrentBreath = MaxBreath;

    NotEnoughBreathDmg = 2.f;

    // 기타 변수 초기화
}

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

    // 체력 및 산소 초기화 (필요에 따라 HUD/PlayerState 등 초기화 호출 포함 가능)
    CurrentHP = MaxHP;
    CurrentBreath = MaxBreath;

    // 예시: 물에 들어가면 BrewerTimer 타이머 시작 등 추가 설정 가능
}
  • 위젯(NativeConstruct)에서는 임의로 100/100 등 초기값 세팅
  • 캐릭터 생성자에서 MaxHP, MaxBreath와 CurrentHP, CurrentBreath 초기값 선언 및 세팅
  • 게임 시작(BeginPlay)에서 초기값 대입 및 이후 플레이 상태에 따라 값 갱신

🎨 UI (HP/Oxygen)

1️⃣ 프로그레스바 색상 동적 변경

void UInGameHUDWidget::SetOxygen(float CurrentOxygen, float MaxOxygen)
{
    // 텍스트 출력 (예: "80 / 100")
    if (OxygenText)
    {
        FString OxygenString = FString::Printf(TEXT("%.0f / %.0f"), CurrentOxygen, MaxOxygen);
        OxygenText->SetText(FText::FromString(OxygenString));
    }

    // 게이지 채우기 및 색상 변경
    if (OxygenBar)
    {
        float Percent = MaxOxygen > 0 ? CurrentOxygen / MaxOxygen : 0.f;
        OxygenBar->SetPercent(Percent);

        FLinearColor BarColor;
        if (Percent < 0.3f) // 30% 미만: 분홍색
        {
            BarColor = FLinearColor(FColor::FromHex(TEXT("FF9DFB")));
        }
        else // 기본(30% 이상): 하늘색
        {
            BarColor = FLinearColor(FColor::FromHex(TEXT("6BD9FF")));
        }
        OxygenBar->SetFillColorAndOpacity(BarColor);
    }
}

2️⃣ 숫자를 바 내부에 겹치기 (텍스트 오버레이)

  • Overlay 자식 순서: ProgressBar 위, TextBlock 아래(=텍스트가 위에 뜸)

  • TextBlock

    • Justification = Center, Vertical Alignment = Center
    • Padding 살짝(예: 0, -2) 줘서 시각적으로 딱 가운데
    • Hit Test Invisible로 바꾸면 클릭 막지 않음.
  • ProgressBar

    • 원하는 크기 안 나오면 SizeBox로 감싸서 Width/Height 지정,
      또는 Anchors 좌우 스트레치 + SizeToContent 해제
    • Style → Background/Fill 모두 Draw As = Image/Box 맞는지 확인

3️⃣ ‘관 안에 액체’ 느낌(틈 있는 HP 바)

ProgressBar의 Style에서 이미지 교체:

  • Background Image: 둥근 관(외곽) PNG (투명+하얀 외곽/그림자)

    • Draw As = Box 또는 Image (9-slice 있으면 Box, 없으면 Image)
  • Fill Image: 안쪽 채워지는 캡슐 PNG (테두리와 간격이 살짝 보이게 여백 포함)

    • Draw As = Box 또는 Image

➡ 이렇게 하면 배경과 채움 사이에 띠가 남아 ‘튜브 속 액체’처럼 보임!

➡ 진행률은 SetPercent() 로 갱신, 색은 위 1) 방식으로 변경

4️⃣ ZOrder & 클릭 처리

  • Canvas Panel 안에서는 자식 목록의 위/아래가 Z 순서

    • 필요한 경우 ZOrder 숫자 노출되는 슬롯(캔버스 슬롯 등)에서 조정
  • UI 배경(블러/이미지)이 버튼을 가리지 않게:

    • 배경 위젯 Visibility를 Not Hit-Testable로(필요 시 Self Only / Self & All Children 선택)

💡 기억해!

  • Replication 및 타이머를 이용해 동기화 및 자동 업데이트 구현

  • 캐릭터와 UI 간 원활한 상태 전달 및 실시간 갱신 중요

  • HUD 위젯 생성시 임의값 100/100으로 초기화 가능

🧠 정리

  1. 캐릭터가 물에 들어가면 타이머에 의해 DecreaseBreath() 가 1초마다 호출

  2. CurrentBreath가 줄고 0이 되면 ApplyDamage로 체력이 줄어듦

  3. 물 밖에서는 IncreaseBreath() 로 다시 산소를 회복

  4. OnRep_CurretnBreath()가 호출되어 HUD UI의 산소 ProgressBar를 갱신

  5. HUD에서는 SetOxygen(CurrentBreath, MaxBreath) 함수를 통해 UI가 각각의 값을 표시

0개의 댓글