Dev./UE 언리얼 엔진

[TIL_250213_2] 캐릭터 체력 시스템 구현

raindrovvv 2025. 2. 13. 23:15

📒학습 내용

🛡️ 캐릭터 체력 시스템

1. 핵심 구조

  • 캐릭터 클래스 내에 Health (현재 체력)와 MaxHealth (최대 체력)를 선언한다.
  • PlayerState 대신 Character 클래스 내부에서 체력을 관리한다.
  • 변수 및 함수는 리플렉션 변수 → 가상함수 → 리플렉션 함수 → 일반 함수 순으로 정리한다.

구조화 팁: 변수와 함수 순서를 구분하여 정리함으로써 코드 가독성을 높인다.


2. 주요 기능 구현

  • 체력 관련 변수와 함수가 정의된다.
  • TakeDamage() (데미지 처리 함수)는 데미지 입력 후 체력을 감소시킨다.
  • AddHealth() (체력 회복 함수)와 OnDeath() (캐릭터 사망 처리 함수)가 각각 체력 회복과 사망 처리를 수행한다.

2.1 체력 변수 및 함수 구현 - [기본 기능]

  1. 체력 관련 변수를 구현한다.
    • MaxHealth: 캐릭터의 최대 체력을 저장한다.
    • Health: 캐릭터의 현재 체력을 저장한다.
  2. 핵심 함수를 구현한다.
    • TakeDamage(): 데미지 입력 후 체력을 감소시키며, 0 이하 시 OnDeath()를 호출한다.
      • DamageAmount (입력된 데미지)와 실제 적용된 ActualDamage를 구분하여 처리한다.
    • AddHealth(): 체력 회복 값을 추가하며, Clamp (값 제한 함수)를 사용하여 범위를 제한한다.
    • OnDeath(): 체력이 0 이하일 때 캐릭터의 사망 처리를 수행한다.
더보기
// SP_Pawn.h
#pragma once
  
#include "CoreMinimal.h"  
#include "InputActionValue.h"  
#include "Animation/AnimInstance.h"  
#include "GameFramework/Pawn.h"  
#include "SP_Pawn.generated.h"  
  
UCLASS()  
class START_API ASP_Pawn : public APawn  
{  
    GENERATED_BODY()  
  
public:  
    ASP_Pawn();  
    virtual void Tick(float DeltaTime) override;  
  
    // 루트 충돌 컴포넌트  
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")  
    class UBoxComponent* CollisionComp;  
    // 스켈레탈 메쉬 컴포넌트
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")  
    class USkeletalMeshComponent* SkeletalMesh;  
  
    // 스프링 암 컴포넌트
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")  
    class USpringArmComponent* SpringArm;  
  
    // 카메라 컴포넌트
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")  
    class UCameraComponent* Camera;  
    UPROPERTY(BlueprintReadOnly, Category = "Input")  
    float MoveRightValue = 0.0f; // MoveRight 입력 값 저장  
  
    // 현재 체력을 가져오는 함수 
    UFUNCTION(BlueprintPure, Category = "Health")  
    float GetHealth() const;  
  
    // 체력 회복을 위한 함수  
    UFUNCTION(BlueprintCallable, Category = "Health")  
    void AddHealth(float Amount);  
  
protected:  
    // 캐릭터 최대 체력 정의
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Health")  
    float MaxHealth = 100.0f;  
    // 캐릭터 현재 체력 정의
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Health")  
    float Health = 0.0f;  
  
    // 부스터 효과를 위한 컴포넌트
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Effects")  
    class UParticleSystemComponent* BoosterEffect;  
    // 부스터 사운드를 위한 컴포넌트
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Effects")  
    class UAudioComponent* BoosterSound;  
    virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

    // 데미지 처리 함수 오버라이드 
    virtual float TakeDamage(  
       float DamageAmount, // 데미지 양  
       struct FDamageEvent const& DamageEvent, // 데미지 유형 및 추가 정보 전달  
       class AController* EventInstigator, // 데미지 발생 주체 전달
       AActor* DamageCauser // 데미지 유발 액터 전달
       ) override;
         
    // 이동 및 기타 동작 함수 정의  
    void Move(const FVector& Direction, float AxisValue);  
    void MoveForward(const FInputActionValue& Value);  
    void MoveRight(const FInputActionValue& Value);  
    void Look(const FInputActionValue& Value);  
    void MoveUp(const FInputActionValue& Value);  
    void ActivateBooster(const FInputActionValue& Value);  
    void DeactivateBooster(const FInputActionValue& Value);  
    void RotateRoll(const FInputActionValue& Value);  
    void ToggleFlightHold(const FInputActionValue& Value);  
    void ApplyGravity(float DeltaTime);  
    void CheckGround();  
    void ApplyTiltEffect(float DeltaTime);  
  
    // 데미지 시스템 관련 함수 정의
    void OnDeath();
    
private:  
    // 이동 속도, 회전 속도, 중력 등 물리 관련  
    const float MoveSpeed = 950.0f;  
    const float RotationSpeed = 90.0f;  
    const float Gravity = -500.0f;  
    const float Power = 90.0f;  
    const float AirControlFactor = 0.9f;  
    const float GroundCheckDistance = 1.0f;  
    float HoverAltitude = 0.0f;  
    FVector Velocity = FVector::ZeroVector;  
    bool bIsGrounded = false;  
    bool bIsFlightHold = false;  
    bool bIsBoosting = false;  
    float BoostSpeed = 3000.0f;  
};
#include "SP_Pawn.h"
#include "SP_PlayerController.h"
#include "EnhancedInputComponent.h"
#include "Components/BoxComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "Particles/ParticleSystemComponent.h"
#include "Components/AudioComponent.h"

// Sets default values
ASP_Pawn::ASP_Pawn()
{
	PrimaryActorTick.bCanEverTick = true;

	// 충돌 컴포넌트 (루트)
	CollisionComp = CreateDefaultSubobject<UBoxComponent>(TEXT("CollisionComp"));
	CollisionComp->SetBoxExtent(FVector(50.f, 50.f, 50.f));
	CollisionComp->SetSimulatePhysics(false);
	RootComponent = CollisionComp;

	// 스켈레탈 메쉬 컴포넌트
	SkeletalMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("SkeletalMesh"));
	SkeletalMesh->SetupAttachment(CollisionComp);
	SkeletalMesh->SetSimulatePhysics(false);

	// 스프링 암 컴포넌트
	SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
	SpringArm->SetupAttachment(SkeletalMesh);
	SpringArm->TargetArmLength = 300.0f;
	// Pawn회전과 독립적으로 동작, 왜? 이동 방향을 카메라의 Forward/Right 벡터를 기준으로 계산하여 회전해야 하기 때문에.
	SpringArm->bUsePawnControlRotation = false; 

	// 카메라 컴포넌트
	Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
	Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);
	Camera->bUsePawnControlRotation = false; // 카메라는 스프링 암의 회전만 따르도록 설정

	// 부스터 효과 (파티클 및 사운드)
	BoosterEffect = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("BoosterEffect"));
	BoosterEffect->SetupAttachment(SkeletalMesh);
	BoosterEffect->bAutoActivate = false;

	BoosterSound = CreateDefaultSubobject<UAudioComponent>(TEXT("BoosterSound"));
	BoosterSound->SetupAttachment(SkeletalMesh);
	BoosterSound->bAutoActivate = false;
	
	Health = MaxHealth;
}

void ASP_Pawn::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	ApplyGravity(DeltaTime);
	CheckGround();
	ApplyTiltEffect(DeltaTime);

	// 부스터 상태에 따른 파티클 효과 업데이트
	if (bIsBoosting)
	{
		if (BoosterEffect && !BoosterEffect->IsActive())
		{
			BoosterEffect->Activate();
		}

		if (BoosterSound && !BoosterSound->IsPlaying())
		{
			BoosterSound->Play();
		}
	}
	else
	{
		if (BoosterEffect && BoosterEffect->IsActive())
		{
			BoosterEffect->Deactivate();
		}

		if (BoosterSound && BoosterSound->IsPlaying())
		{
			BoosterSound->Stop();
		}
	}
}

// 입력 바인딩
void ASP_Pawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	if (UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(PlayerInputComponent))
	{
		if (ASP_PlayerController* PlayerController = Cast<ASP_PlayerController>(GetController()))
		{
			if (PlayerController->MoveForwardAction)
			EnhancedInput->BindAction(PlayerController->MoveForwardAction, ETriggerEvent::Triggered, this, &ASP_Pawn::MoveForward);

			if (PlayerController->MoveRightAction)
			EnhancedInput->BindAction(PlayerController->MoveRightAction, ETriggerEvent::Triggered, this, &ASP_Pawn::MoveRight);

			if (PlayerController->LookAction)
			EnhancedInput->BindAction(PlayerController->LookAction, ETriggerEvent::Triggered, this, &ASP_Pawn::Look);

			if (PlayerController->MoveUpAction)
			EnhancedInput->BindAction(PlayerController->MoveUpAction, ETriggerEvent::Triggered, this, &ASP_Pawn::MoveUp);

			if (PlayerController->SprintAction)
			{
				EnhancedInput->BindAction(PlayerController->SprintAction, ETriggerEvent::Started, this, &ASP_Pawn::ActivateBooster);
				EnhancedInput->BindAction(PlayerController->SprintAction, ETriggerEvent::Completed, this, &ASP_Pawn::DeactivateBooster);
			}

			if (PlayerController->RotateRollAction)
			EnhancedInput->BindAction(PlayerController->RotateRollAction, ETriggerEvent::Triggered, this, &ASP_Pawn::RotateRoll);

			if (PlayerController->FlightHoldAction)
			EnhancedInput->BindAction(PlayerController->FlightHoldAction, ETriggerEvent::Triggered, this, &ASP_Pawn::ToggleFlightHold);
		}
	}
}

void ASP_Pawn::Move(const FVector& Direction, float AxisValue)
{
	if (FMath::IsNearlyZero(AxisValue))
	{
		Velocity = FMath::VInterpTo(Velocity, FVector::ZeroVector, GetWorld()->GetDeltaSeconds(), 2.0f);
		return;
	}

	float CurrentSpeed = bIsBoosting ? BoostSpeed : MoveSpeed;
	float SpeedMultiplier = (bIsGrounded && !bIsFlightHold) ? 1.0f : AirControlFactor;
	FVector TargetVelocity = Direction * (AxisValue * CurrentSpeed * SpeedMultiplier);

	// 부드러운 속도 보간
	Velocity = FMath::VInterpTo(Velocity, TargetVelocity, GetWorld()->GetDeltaSeconds(), 6.0f);
}

void ASP_Pawn::MoveForward(const FInputActionValue& Value)
{
	Move(GetActorForwardVector(), Value.Get<float>());
}

void ASP_Pawn::MoveRight(const FInputActionValue& Value)
{
	float AxisValue = Value.Get<float>();
	
	// A/D 입력값 전달 (왼쪽: -1, 오른쪽: 1, 중립: 0)
	MoveRightValue = AxisValue;
	Move(GetActorRightVector(), Value.Get<float>());
}

void ASP_Pawn::MoveUp(const FInputActionValue& Value)
{
	if (bIsFlightHold) return; // 호버링 상태에서는 수직 이동 입력을 무시하여 높이 고정
	Move(FVector::UpVector, Value.Get<float>());
}

void ASP_Pawn::ActivateBooster(const FInputActionValue& Value)
{
	bIsBoosting = true;
}

void ASP_Pawn::DeactivateBooster(const FInputActionValue& Value)
{
	bIsBoosting = false;
}

void ASP_Pawn::Look(const FInputActionValue& Value)
{
	FVector2D LookInput = Value.Get<FVector2D>();
	float DeltaTime = GetWorld()->GetDeltaSeconds();

	// Yaw 회전 (좌우 회전)
	AddActorLocalRotation(FRotator(0.0f, LookInput.X * RotationSpeed * DeltaTime, 0.0f));

	// Pitch 회전 (카메라 상하 회전)
	FRotator NewSpringArmRot = SpringArm->GetRelativeRotation();
	NewSpringArmRot.Pitch = FMath::ClampAngle(NewSpringArmRot.Pitch + LookInput.Y * RotationSpeed * DeltaTime, -60.0f, 60.0f);
	SpringArm->SetRelativeRotation(NewSpringArmRot);
}

void ASP_Pawn::RotateRoll(const FInputActionValue& Value)
{
	float AxisValue = Value.Get<float>();
	
	if (FMath::IsNearlyZero(AxisValue)) return;
	
	FRotator CurrentRotation  = GetActorRotation();
	CurrentRotation .Roll += AxisValue * RotationSpeed * GetWorld()->GetDeltaSeconds();

	// Roll 보간 적용
	SetActorRotation(CurrentRotation);
}

// ============ //
// 중력 기능 추가 //
// ============ //
void ASP_Pawn::ApplyGravity(float DeltaTime)
{
	if (bIsFlightHold)
	{
		// 비행 홀드 시 중력 제거, BUT 이동 가능하도록 속도 유지
		Velocity.Z = FMath::FInterpTo(Velocity.Z, 0.0f, DeltaTime, 2.0f);
		//return; : 활성화 하면 호버링 상태로 고정돼버림
	}
    
	if (!bIsGrounded)
	{
		Velocity.Z += Gravity * DeltaTime;
	}
	FVector NewLocation = GetActorLocation() + Velocity * DeltaTime;

	// 호버링 상태이면 수직 위치를 기록된 HoverAltitude로 고정
	if (bIsFlightHold)
	{
		NewLocation.Z = HoverAltitude;
		
	}
	
	// 호버링 상태이면 수직 위치를 고정
	if (bIsFlightHold)
	{
		NewLocation.Z = HoverAltitude;
	}
	
	FHitResult Hit;
	// Sweep 기능으로 이동 시 충돌이 발생하면 Hit에 반영되어 지면에서 멈춥니다.
	SetActorLocation(NewLocation, true, &Hit);

	if (Hit.IsValidBlockingHit())
	{
		bIsGrounded = true;
		Velocity.Z = 1.0f;
	}
}


void ASP_Pawn::CheckGround()
{
	FVector Start = GetActorLocation();
	FVector End = Start + FVector(0, 0, -GroundCheckDistance);

	FHitResult Hit;
	FCollisionQueryParams Params;
	Params.AddIgnoredActor(this);

	bool bHit = GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Visibility, Params);

	if (bHit)
	{
		// 땅에 너무 가까우면 HoverAltitude로 높이를 조정
		if (Hit.Distance < GroundCheckDistance + 10.0f)
		{
			HoverAltitude = Hit.Location.Z + 50.0f; // 땅에서 50 유닛 높이 유지
		}
		bIsGrounded = true;
	}
	else
	{
		bIsGrounded = false;
	}
}

// 틸트 기능!!
void ASP_Pawn::ApplyTiltEffect(float DeltaTime)
{
	FRotator CurrentRotation = GetActorRotation();
	const float MaxTiltAngle = 15.0f; 
	const float TiltSpeed = 5.0f; // 틸트 반응 속도
	const float RecoverySpeed = 5.5f; // 회복 속도
	const float SineFactor = 0.8f; // 사인 그래프를 조절하는 팩터

	// 비행 홀드 상태면 기울기를 원래 상태로 복구
	if (bIsFlightHold)
	{
		FRotator TargetRotation = CurrentRotation;
		TargetRotation.Pitch = 0.0f;
		TargetRotation.Roll = 0.0f;

		// 사인 그래프 보간을 적용하여 부드럽게 복구
		float Alpha = FMath::Sin(RecoverySpeed * DeltaTime);  
		SetActorRotation(FMath::Lerp(CurrentRotation, TargetRotation, Alpha));
		return;
	}

	// 이동 방향 벡터 정규화
	FVector LocalVelocity = GetActorRotation().UnrotateVector(Velocity).GetSafeNormal();

	// 목표 틸트 각도 계산 (속도가 작을수록 기울기도 작아지도록 보정)
	float TargetPitch = -LocalVelocity.X * MaxTiltAngle;
	float TargetRoll = LocalVelocity.Y * MaxTiltAngle;

	// 이동이 멈추면 원래 자세로 복귀
	if (Velocity.IsNearlyZero())
	{
		TargetPitch = 0.0f;
		TargetRoll = 0.0f;
	}
	
	float Alpha = FMath::Sin(TiltSpeed * DeltaTime * SineFactor);  

	FRotator TargetRotation = CurrentRotation;
	TargetRotation.Pitch = FMath::Lerp(CurrentRotation.Pitch, TargetPitch, Alpha);
	TargetRotation.Roll = FMath::Lerp(CurrentRotation.Roll, TargetRoll, Alpha);

	SetActorRotation(TargetRotation);
}

// 호버링(비행 홀드) 기능
void ASP_Pawn::ToggleFlightHold(const FInputActionValue& Value)
{
	bIsFlightHold = !bIsFlightHold;
	// 호버링 전환 시 수직 속도를 제거, 현재 높이를 기록하여 고정
	if (bIsFlightHold)
	{
		HoverAltitude = GetActorLocation().Z;
		Velocity.Z = 0.0f;
	}
}

//======================
// 캐릭터 HP, Damage 관련
//======================

float ASP_Pawn::GetHealth() const
{
	return Health;
}

void ASP_Pawn::AddHealth(float Amount)
{
	Health = FMath::Clamp(Health + Amount, 0.0f, MaxHealth);
	UE_LOG(LogTemp, Warning, TEXT("Health increased to : %f"), Health);
}

float ASP_Pawn::TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent,
                           class 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;
}

void ASP_Pawn::OnDeath()
{
	// 게임 종료 로직 호출
}
체력 관련 연산 시 Clamp (값 범위 제한 함수)를 사용하여 오류를 방지한다.

🎯 데미지 처리 시스템

  • 데미지 처리는 AActor::TakeDamage() 함수를 통해 수행된다.
  • 체력 감소 후 0 이하이면 OnDeath()를 호출하여 캐릭터 사망 처리를 수행한다.
  • UGameplayStatics::ApplyDamage()로 공격자가 데미지를 적용한다.

1. 데미지 처리 흐름 구현

  1. AActor::TakeDamage()를 오버라이드하여 체력을 감소시킨다.
    • 입력된 DamageAmount를 사용하여 체력을 감소시키며, Clamp 함수를 통해 최소 0과 최대 체력을 제한한다.
    • 체력이 0 이하가 되면 OnDeath() 함수를 호출하여 캐릭터 사망 처리를 수행한다.
  2. UGameplayStatics::ApplyDamage()를 호출하여 데미지 처리를 진행한다.
    • 해당 함수는 내부적으로 대상 액터의 TakeDamage() 함수를 실행한다.
    • 데미지 발생 주체와 원인을 전달하여 정확한 데미지 처리가 이루어진다.
// ASP_Pawn.cpp
float ASP_Pawn::TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent,  
    class AController* EventInstigator, AActor* DamageCauser)  
{  
    float ActualDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);  
    Health = FMath::Clamp(Health - DamageAmount, 0.0f, MaxHealth);  
    if (Health <= 0.0f)  
    {       
        OnDeath();  
    }    
    return ActualDamage;  
}
// ASP_Pawn.cpp
void ASP_Pawn::AddHealth(float Amount)  
{  
    Health = FMath::Clamp(Health + Amount, 0.0f, MaxHealth);  
    UE_LOG(LogTemp, Warning, TEXT("Health added to : %f"), Health);  
}
  • 체력 회복 함수는 입력 값을 Clamp하여 체력 범위를 유지한다.

데미지 처리 팁: 플레이어의 방어력 또는 저항 시스템 도입 시, DamageAmount와 실제 적용된 ActualDamage를 구분하여 처리해야 한다.


💣 지뢰 및 힐링 아이템 연동

  • 지뢰 아이템은 범위 내 캐릭터에 데미지를 적용한다.
  • 힐링 아이템은 캐릭터의 체력을 회복시킨다.
  • 액터 태그와 캐스팅을 이용하여 대상 액터를 확인한다.

1. 지뢰 아이템 연동

  1. 지뢰 아이템은 범위 내 겹친 액터를 검출한다.
    • GetOverlappingActors()를 사용하여 겹친 액터 목록을 가져온다.
    • ActorHasTag("Player")를 통해 플레이어 여부를 확인한다.
  2. UGameplayStatics::ApplyDamage()를 호출하여 대상 액터에 데미지를 적용한다.
  3. 아이템 사용 후 제거한다.
// MineItem.cpp
void AMineItem::Explode()  
{  
    TArray<AActor*> OverlappingActors;  
    MineCollision->GetOverlappingActors(OverlappingActors);  
    for (AActor* Actor : OverlappingActors)  
    {       
        if (Actor && Actor->ActorHasTag("Player"))  
        {          
            UGameplayStatics::ApplyDamage(  
                Actor,             // 데미지 대상  
                ExplosiveDamage,   // 데미지 양  
                nullptr,           // 발생 주체 (게임 구현상 없음)  
                this,              // 데미지 원인 객체  
                UDamageType::StaticClass());  // 기본 데미지 유형을 사용한다.  
        }  
    }    
    DestroyItem();  
}
  • UGameplayStatics 사용 시 헤더 파일을 포함한다.

2. 힐링 아이템 연동 

  1. 힐링 아이템은 액터 태그를 확인하여 플레이어 캐릭터를 식별한다.
    • ActorHasTag("Player")로 플레이어 여부를 확인한다.
  2. 캐릭터를 ASP_Pawn으로 캐스팅한 후, AddHealth() 함수를 호출하여 체력을 회복시킨다.
  3. 아이템 사용 후 제거한다.
// HealingItem.cpp
void AHealingItem::ActivateItem(AActor* Activator)  
{  
    if (Activator && Activator->ActorHasTag("Player"))  
    {       
        if (ASP_Pawn* PlayerCharacter = Cast<ASP_Pawn>(Activator))  
        {          
            PlayerCharacter->AddHealth(HealAmount);  
        }       
        DestroyItem();  
    }
}

🎮 점수 관리 시스템

  • GameModeGameState를 활용하여 게임 전역의 점수 및 규칙을 관리한다.
  • GameState는 모든 클라이언트에 동기화되어 전역 데이터를 제공한다.
  • 코인 아이템과 연동하여 점수 증가 로직이 구현된다.

1. GameMode와 GameState 활용 - [전역 데이터 관리]

  1. GameMode는 게임 규칙, 캐릭터 스폰 및 흐름을 관리하며 서버 전용으로 동작한다.
  2. GameState는 점수, 남은 시간 등 전역 데이터를 관리하며 모든 클라이언트에 공유된다.
  3. 싱글 플레이에서도 유지보수를 위해 GameState를 활용한다.
GameMode 게임 규칙 및 캐릭터 스폰 로직을 관리하며 서버 전용으로 동작한다.
GameState 전역 데이터를 관리하며 모든 클라이언트에 동기화되어 제공된다.

2. GameState에 점수 데이터 및 함수 추가 - [점수 관리 기능]

  1. MyGameStateBase 클래스에서 전역 점수를 관리한다.
    • Score 변수를 선언하여 점수 데이터를 저장한다.
  2. GetScore() 함수는 현재 점수를 반환한다.
  3. AddScore() 함수는 입력된 점수만큼 증가시킨다.
// MyGameStateBase.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameStateBase.h"
#include "MyGameStateBase.generated.h"

UCLASS()
class START_API AMyGameStateBase : public AGameStateBase
{
	GENERATED_BODY()

public:
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Score")
	int32 Score;
	
	// 점수를 가져오는 함수를 선언한다.
	UFUNCTION(BlueprintPure, Category = "Score")
	int32 GetScore() const;
	// 점수를 증가시키는 함수를 선언한다.
	UFUNCTION(BlueprintCallable, Category = "Score")
	void AddScore(int32 Amount);
};
// MyGameStateBase.cpp
#include "MyGameStateBase.h"  
  
AMyGameStateBase::AMyGameStateBase()  
{  
    Score = 0;  
}  
  
int32 AMyGameStateBase::GetScore() const  
{  
    return Score;  
}  
  
void AMyGameStateBase::AddScore(int32 Amount)  
{  
    Score += Amount;  
}

🎮GameMode와 GameState 연동

  1. SP_GameMode 클래스에서 GameStateClassAMyGameStateBase로 설정한다.
  2. 이를 통해 게임 내 전역 데이터가 서버와 클라이언트 간 자동 동기화된다.
// SP_GameMode.cpp (일부 발췌)
#include "SP_GameMode.h"  
#include "SP_Pawn.h"  
#include "SP_PlayerController.h"  
#include "MyGameStateBase.h"  
  
ASP_GameMode::ASP_GameMode()  
{  
    DefaultPawnClass = ASP_Pawn::StaticClass();  
    PlayerControllerClass = ASP_PlayerController::StaticClass();  
    GameStateClass = AMyGameStateBase::StaticClass();  
}
  • 위 설정은 싱글 플레이와 멀티 플레이 환경 모두에서 전역 데이터의 일관된 관리를 보장한다.

🪙코인 아이템 점수 획득 적용

  1. 플레이어가 코인 아이템을 획득하면, 현재 게임 월드에서 GameState를 가져온다.
    • Engine/World.h 헤더를 포함하여 게임 월드를 참조한다.
  2. MyGameStateBaseAddScore() 함수를 호출하여 점수를 증가시킨다.
  3. 아이템 사용 후 해당 아이템을 제거한다.
// CoinItem.cpp
#include "CoinItem.h"
#include "Engine/World.h"
#include "MyGameStateBase.h"

ACoinItem::ACoinItem()
{
	PointValue = 0;
	ItemType = "DefaultCoinItem";
}

void ACoinItem::ActivateItem(AActor* Activator)
{
	if (Activator && Activator->ActorHasTag("Player"))
	{
		if (UWorld* World = GetWorld())
		{
			if (AMyGameStateBase* GameState = World->GetGameState<AMyGameStateBase>())
			{
				GameState->AddScore(PointValue);
			}
		}
		DestroyItem();
	}
}
  • UI 연동 시 BlueprintPure 함수를 활용하여 실시간 점수 표시가 용이하도록 구성한다.