2024/02/07 TIL

양우창·2025년 2월 7일

내일배움캠프 TIL

목록 보기
22/22

개요

오늘은 언리얼 엔진 5에서 캐릭터 체력 시스템과 점수 관리 시스템을 구현하는 방법을 정리해봤다.
싱글 플레이 환경을 기준으로, 체력과 점수를 어떻게 관리하고, 아이템과 상호작용하도록 설계하는지 살펴봤다.

배운 내용

  1. 캐릭터 체력 시스템 구현

왜 PlayerState를 쓰지 않고, 캐릭터 클래스에 체력을 넣을까?

  • PlayerState는 멀티플레이 환경에서 서버-클라이언트 동기화를 위해 존재한다.
    • 예) 점수, 킬/데스 카운트 등 모든 플레이어가 공유해야 하는 정보 저장
  • 하지만 싱글 플레이에서는 동기화가 필요 없으므로, 캐릭터 클래스(SpartaCharacter)에 체력 변수를 넣어 관리하는 것이 효율적이다.

캐릭터 클래스에 체력 관련 로직 추가

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Health")
float MaxHealth;

UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Health")
float Health;

UFUNCTION(BlueprintPure, Category = "Health")
int32 GetHealth() const;

UFUNCTION(BlueprintCallable, Category = "Health")
void AddHealth(float Amount);

virtual float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;

UFUNCTION(BlueprintCallable, Category = "Health")
virtual void OnDeath();
  • 주요 기능

GetHealth() : 현재 체력을 반환
AddHealth(float Amount) : 체력을 회복 (최대 체력 초과 방지)
TakeDamage(float DamageAmount, ...) : 데미지 처리 (체력 감소 & 사망 체크)
OnDeath() : 체력이 0이 되면 실행되는 사망 처리 함수

  • 데미지 & 체력 회복 구현
    • 언리얼 엔진에서는 ApplyDamage()TakeDamage()를 활용하면 쉽게 데미지 처리가 가능하다.
    • 체력 회복은 AddHealth()를 이용해 회복 후 최대 체력을 초과하지 않도록 FMath::Clamp()를 사용했다.
void ASpartaCharacter::AddHealth(float Amount)
{
    Health = FMath::Clamp(Health + Amount, 0.0f, MaxHealth);
    UE_LOG(LogTemp, Log, TEXT("Health increased to: %f"), Health);
}

float ASpartaCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
    float ActualDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
    Health = FMath::Clamp(Health - DamageAmount, 0.0f, MaxHealth);
    UE_LOG(LogTemp, Warning, TEXT("Health decreased to: %f"), Health);

    if (Health <= 0.0f) { OnDeath(); }
    return ActualDamage;
}
  • 사망 처리 (OnDeath())
void ASpartaCharacter::OnDeath()
{
    UE_LOG(LogTemp, Error, TEXT("Character is Dead!"));
    // 사망 후 추가 로직 (예: 애니메이션, 리스폰 등)
}
  • 지뢰 아이템 (MineItem) 데미지 처리
    • ApplyDamage()를 호출해 주변 플레이어에게 폭발 데미지를 준다.
void AMineItem::Explode()
{
    TArray<AActor*> OverlappingActors;
    ExplosionCollision->GetOverlappingActors(OverlappingActors);

    for (AActor* Actor : OverlappingActors)
    {
        if (Actor && Actor->ActorHasTag("Player"))
        {
            UGameplayStatics::ApplyDamage(Actor, ExplosionDamage, nullptr, this, UDamageType::StaticClass());
        }
    }

    DestroyItem();
}
  • 힐링 아이템 (HealingItem) 체력 회복
    • AddHealth() 함수를 호출해 플레이어의 체력을 증가시킨다.
void AHealingItem::ActivateItem(AActor* Activator)
{
    if (Activator && Activator->ActorHasTag("Player"))
    {
        if (ASpartaCharacter* PlayerCharacter = Cast<ASpartaCharacter>(Activator))
        {
            PlayerCharacter->AddHealth(HealAmount);
        }
        DestroyItem();
    }
}
  1. 점수 관리 시스템 구현

- 왜 GameMode가 아닌 GameState에서 점수를 관리할까?

  • GameMode는 게임 규칙을 관리하는 역할 (오직 서버에서만 존재)
  • GameState는 게임 전역 데이터를 저장하는 역할 (서버와 클라이언트가 공유)
  • 따라서 점수와 같은 전역적인 게임 데이터는 GameState에서 관리하는 것이 적절하다.

GameState에 점수 데이터 추가

UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Score")
int32 Score;

UFUNCTION(BlueprintPure, Category="Score")
int32 GetScore() const;

UFUNCTION(BlueprintCallable, Category="Score")
void AddScore(int32 Amount);
void ASpartaGameStateBase::AddScore(int32 Amount)
{
    Score += Amount;
}
  • 싱글플레이에서도 GameState를 활용하면 관리가 편해진다!
  • ex) “현재 맵에 있는 아이템 개수” 같은 전역적인 데이터를 관리할 때 사용 가능.

GameMode와 GameState 연동하기

  • GameMode에서 GameState를 SpartaGameStateBase로 설정
ASpartaGameMode::ASpartaGameMode()
{
    GameStateClass = ASpartaGameStateBase::StaticClass();
}
  • 언리얼 에디터에서 Default GameMode 설정
    • Edit → Project Settings → Maps & Modes
    • Game State Class → BP_SpartaGameStateBase

코인 아이템 (CoinItem) 점수 획득 구현

  • GetWorld()->GetGameState<ASpartaGameStateBase>() 를 사용해 GameState에 접근
  • AddScore(PointValue) 호출해 점수 증가
void ACoinItem::ActivateItem(AActor* Activator)
{
    if (Activator && Activator->ActorHasTag("Player"))
    {
        if (UWorld* World = GetWorld())
        {
            if (ASpartaGameStateBase* GameState = World->GetGameState<ASpartaGameStateBase>())
            {
                GameState->AddScore(PointValue);
            }
        }

        DestroyItem();
    }
}

내용 정리

  • 체력 시스템

    • TakeDamage()ApplyDamage()를 활용해 데미지 처리
    • AddHealth()로 체력 회복, OnDeath()로 사망 처리
    • 지뢰 아이템(MineItem)과 힐링 아이템(HealingItem)이 체력에 영향을 주도록 구현
  • 점수 시스템

    • GameMode가 아닌 GameState에서 점수를 관리
    • GameStateClassSpartaGameStateBase로 설정
    • CoinItem에서 AddScore() 호출하여 점수 증가
profile
내배캠 언리얼 엔진 1기

0개의 댓글