캐릭터 체력 및 점수 관리 시스템 구현하기
캐릭터 체력 시스템 구현하기
1️⃣ 캐릭터 클래스에 체력 변수 및 함수 선언
- PlayerState를 쓰지 않는 이유
- PlayerState: 각 플레이어마다의 어떤 정보를 관리해주는 클래스
- 언리얼 엔진에서 PlayerState는 주로 멀티플레이 환경에서 각 플레이어 간 데이터 동기화를 위해 사용합니다.
- 예를 들어, 점수나 킬/데스 카운트처럼 서버와 클라이언트 모두가 공통으로 확인해야 하는 정보를 저장하죠.
- 하지만 싱글 플레이 게임에서는 이런 동기화가 필요 없으므로, 캐릭터 클래스자체에 체력이나 스코어 변수를 넣어 관리해도 충분합니다.
- 그럼에도 추후 확장성 생각하면 PlayerState 가 더 좋을 수 있지만 우선은 이렇게 합니다.
- 캐릭터 클래스에 체력 관리 로직 추가
- 싱글 플레이 환경을 가정하여, 플레이어 캐릭터를 담당하는 클래스에 체력 관리용 변수를 선언합니다.
- MaxHealth: 캐릭터의 최대 체력을 나타냅니다.
- Health: 캐릭터의 현재 체력을 나타냅니다.
- TakeDamage(): 데미지를 받았을 때 호출되는 함수로, 내부에서 체력을 감소시키는 로직을 처리합니다.
- AddHealth(): 아이템 등을 통해 체력을 회복할 때 호출하는 함수로, 내부에서 체력을 회복시킵니다.
- OnDeath(): 체력이 0 이하가 되었을 때 호출되는 사망 처리 함수입니다.
(BlueprintPure: Get함수 사용시 주로 사용됩니다)
2️⃣ 데미지 및 회복 처리
- 언리얼 엔진에는 데미지 시스템을 간편히 사용할 수 있는 UGameplayStatics::ApplyDamage(데미지 발생)와 AActor::TakeDamage(데미지 처리) 함수가 제공됩니다.
- 데미지 처리 흐름 (ApplyDamage → TakeDamage)
- UGameplayStatics::ApplyDamage
- 공격자 (또는 폭발물 등)가 데미지를 줄 대상 액터와 데미지 양, 데미지를 유발한 주체 등을 인자로 넘겨 호출합니다.
- 내부적으로 대상 액터의 TakeDamage() 함수를 호출하려 시도합니다.
- AActor::TakeDamage
- Actor의 가상 함수입니다. 모든 액터가 기본적으로 이 함수를 가지고 있으며, 필요하다면 자식 클래스(캐릭터 등)에서 오버라이드할 수 있습니다.
- 실제로 체력 감소 또는 특수한 데미지 처리 로직을 이 안에서 구현하게 됩니다.
- UGameplayStatics::ApplyDamage
- AddHealth(float Amount)
- 체력을 일정 양만큼 회복합니다.
- FMath::Clamp를 통해 최대 체력을 초과하지 않도록 제한합니다.
- TakeDamage(...)
- 언리얼 엔진의 기본 데미지 시스템을 사용하는 대표적인 함수입니다.
- DamageAmount: 데미지 값
- EventInstigator: 데미지를 유발한 주체(Controller)
- DamageCauser: 데미지를 직접 발생시킨 오브젝트(총알, 폭발물 등)
- 반환값: 실제 적용된 데미지(기본 로직에서는 DamageAmount와 동일한 경우가 많지만, 게임 상황에 따라 감소 또는 증폭 등을 처리할 수도 있습니다.)
- OnDeath()
- 체력이 0 이하로 떨어졌을 때 사망 로직을 처리하는 함수입니다.
- 흔히 입력 비활성화, Ragdoll 적용, 사망 애니메이션 재생 등을 수행합니다.
MainCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MainCharacter.generated.h"
// 미리 선언
// 전방 선언(Forward Declaration)
class USpringArmComponent; // 스프링 암 관련 클래스 헤더
class UCameraComponent; // 카메라 관련 클래스 전방 선언
// Enhanced Input에서 액션 값을 받을 때 사용하는 구조체
struct FInputActionValue;
UCLASS()
class BC_CH3_ASSIGNMENT_5_API AMainCharacter : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AMainCharacter();
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
USpringArmComponent* SpringArmComp = nullptr;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
UCameraComponent* CameraComp = nullptr;
// 현재 체력을 가져오는 함수
UFUNCTION(BlueprintPure, Category = "Health")
float GetHealth() const;
// 체력을 회복시키는 함수
UFUNCTION(BlueprintCallable, Category = "Health")
void AddHealth(float Amount);
protected:
// === Variables ===
// 최대 체력
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Health")
float MaxHealth;
// 현재 체력
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Health")
float Health;
// = Movements
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
float NormalSpeed; // 기본 걷기 속도
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
float SprintSpeedMultiplier; // "기본 속도" 대비 몇 배로 빠르게 달릴지
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Movement")
float SprintSpeed; // 실제 스프린트 속도 SprintSpeed= NormalSpeed * SprintSpeedMultiplier
// =====
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UFUNCTION()
void Move(const FInputActionValue& value);
UFUNCTION()
void Look(const FInputActionValue& value);
// on off 의 형태의 것들은 그냥 나눠 주는 것이 좋다
// 왜냐하면 이전에는 세세하게 처리하기가 상당이 까다로웠는데
// EnhancedInputSystem은 그것들을 매우 편하게 변경해주었기 때문에 왠만하면 나눠 주는 것이 좋다
UFUNCTION()
void StartJump(const FInputActionValue& value);
UFUNCTION()
void StopJump(const FInputActionValue& value);
UFUNCTION()
void StartSprint(const FInputActionValue& value);
UFUNCTION()
void StopSprint(const FInputActionValue& value);
// 사망 처리 함수 (체력이 0 이하가 되었을 때 호출)
UFUNCTION(BlueprintCallable, Category = "Health")
virtual void OnDeath();
// 데미지 처리 함수 - 외부로부터 데미지를 받을 때 호출됨
// 또는 AActor의 TakeDamage()를 오버라이드
virtual float TakeDamage(
float DamageAmount,
struct FDamageEvent const& DamageEvent, // Damage 관련 추가 정보 (예: Skill 속성에 따른 반응)
AController* EventInstigator, //데미지를 발생 시킨 주체
AActor* DamageCauser // 데미지를 일으킨 오브젝트
) override;
};
MainCharacter.cpp
#include "MainCharacter.h"
#include "EnhancedInputComponent.h"
//#include "InputActionValue.h"
#include "MainPlayerController.h"
// 카메라, 스프링 암 실제 구현이 필요한 경우라서 include
// 전방 선언(Forward Declaration) 한것 여기서 (실질적으로 사용하는곳) 포함
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
//
#include "GameFramework/CharacterMovementComponent.h" // GetCharacterMovement() 사용을 위해
#include "GameFramework/Actor.h"
#include "Kismet/GameplayStatics.h"
AMainCharacter::AMainCharacter()
{
PrimaryActorTick.bCanEverTick = false;
// (1) 스프링 암 생성
SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
// 스프링 암을 루트 컴포넌트 (CapsuleComponent)에 부착
SpringArmComp->SetupAttachment(RootComponent);
// 캐릭터와 카메라 사이의 거리 기본값 300으로 설정
SpringArmComp->TargetArmLength = 300.0f;
// 컨트롤러 회전에 따라 스프링 암도 회전하도록 설정
SpringArmComp->bUsePawnControlRotation = true;
//
// (2) 카메라 컴포넌트 생성
CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
// 스프링 암의 소켓 위치에 카메라를 부착
CameraComp->SetupAttachment(SpringArmComp, USpringArmComponent::SocketName);
// 카메라는 스프링 암의 회전을 따르므로 PawnControlRotation은 꺼둠
CameraComp->bUsePawnControlRotation = false;
// Movements & Sprint Speed
NormalSpeed = 600.0f;
SprintSpeedMultiplier = 1.75f;
SprintSpeed = NormalSpeed * SprintSpeedMultiplier;
// #include "GameFramework/CharacterMovementComponent.h" 추가해야함
// MaxWalkSpeed 변경시 캐릭터 이동속도가 즉시 변경
GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
// 초기 체력 설정
MaxHealth = 100.0f;
Health = MaxHealth;
}
// Called to bind functionality to input
void AMainCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// Enhanced InputComponent로 캐스팅
if (UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(PlayerInputComponent))
{
// IA를 가져오기 위해 현재 소유 중인 Controller를 AMainPlayerController로 캐스팅
if (AMainPlayerController* PlayerController = Cast<AMainPlayerController>(GetController()))
{
// null check
if (PlayerController->MoveAction)
{
// IA_Move 액션 키를 "키를 누르고 있는 동안" Move() 호출
EnhancedInput->BindAction(
PlayerController->MoveAction,
ETriggerEvent::Triggered,
this,
&AMainCharacter::Move
);
}
if (PlayerController->JumpAction)
{
// IA_Jump 액션 키를 "키를 누르고 있는 동안" StartJump() 호출
EnhancedInput->BindAction(
PlayerController->JumpAction,
ETriggerEvent::Triggered,
this,
&AMainCharacter::StartJump
);
// IA_Jump 액션 키에서 "손을 뗀 순간" StopJump() 호출
EnhancedInput->BindAction(
PlayerController->JumpAction,
ETriggerEvent::Completed,
this,
&AMainCharacter::StopJump
);
}
if (PlayerController->LookAction)
{
// IA_Look 액션 마우스가 "움직일 때" Look() 호출
EnhancedInput->BindAction(
PlayerController->LookAction,
ETriggerEvent::Triggered,
this,
&AMainCharacter::Look
);
}
if (PlayerController->SprintAction)
{
// IA_Sprint 액션 키를 "누르고 있는 동안" StartSprint() 호출
EnhancedInput->BindAction(
PlayerController->SprintAction,
ETriggerEvent::Triggered,
this,
&AMainCharacter::StartSprint
);
// IA_Sprint 액션 키에서 "손을 뗀 순간" StopSprint() 호출
EnhancedInput->BindAction(
PlayerController->SprintAction,
ETriggerEvent::Completed,
this,
&AMainCharacter::StopSprint
);
}
}
}
}
void AMainCharacter::Move(const FInputActionValue& value)
{
// 컨트롤러가 있어야 방향 계산이 가능
if (!Controller) return;
// Value는 Axis2D로 설정된 IA_Move의 입력값 (WASD)을 담고 있음
// 예) (X=1, Y=0) → 전진 / (X=-1, Y=0) → 후진 / (X=0, Y=1) → 오른쪽 / (X=0, Y=-1) → 왼쪽
const FVector2D MoveInput = value.Get<FVector2D>();
// IsNearlyZero
// 부동소수점들은 딱 0으로 안 떨어 질 수도 있기 때문에 작은 오차들은 0으로 처리 하기 위한 함수
if (!FMath::IsNearlyZero(MoveInput.X))
{
// 캐릭터가 바라보는 방향(정면)으로 X축 이동
AddMovementInput(GetActorForwardVector(), MoveInput.X);
}
if (!FMath::IsNearlyZero(MoveInput.Y))
{
// 캐릭터의 오른쪽 방향으로 Y축 이동
AddMovementInput(GetActorRightVector(), MoveInput.Y);
}
}
void AMainCharacter::Look(const FInputActionValue& value)
{
// 마우스의 X, Y 움직임을 2D 축으로 가져옴
FVector2D LookInput = value.Get<FVector2D>();
// X는 좌우 회전 (Yaw), Y는 상하 회전 (Pitch)
// 좌우 회전
AddControllerYawInput(LookInput.X);
// 상하 회전
// IA에서 반전해둔 상태
// 여기엔 추후 변경 가능한 옵션 추가해주자
AddControllerPitchInput(LookInput.Y);
}
void AMainCharacter::StartJump(const FInputActionValue& value)
{
// Jump 함수는 Character가 기본 제공
if (value.Get<bool>())
{
Jump();
}
}
void AMainCharacter::StopJump(const FInputActionValue& value)
{
// StopJumping 함수도 Character가 기본 제공
if (!value.Get<bool>())
{
StopJumping();
}
}
void AMainCharacter::StartSprint(const FInputActionValue& value)
{
// Shift 키를 누른 순간 이 함수가 호출된다고 가정
// 스프린트 속도를 적용
if (GetCharacterMovement())
{
// 중간에 변경시
SprintSpeed = NormalSpeed * SprintSpeedMultiplier;
GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
}
}
void AMainCharacter::StopSprint(const FInputActionValue& value)
{
// Shift 키를 뗀 순간 이 함수가 호출
// 평상시 속도로 복귀
if (GetCharacterMovement())
{
GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
}
}
float AMainCharacter::GetHealth() const
{
return Health;
}
// 체력 회복 함수
void AMainCharacter::AddHealth(float Amount)
{
// 체력을 회복시킴. 최대 체력을 초과하지 않도록 제한함
Health = FMath::Clamp(Health + Amount, 0.0f, MaxHealth);
//UE_LOG(LogTemp, Log, TEXT("Health increased to: %f"), Health);
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(
-1, 2.f, FColor::Green,
TEXT("Health Increased to: %f"), Health);
}
}
// 데미지 처리 함수
float AMainCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
// 기본 데미지 처리 로직 호출 (필수는 아님)
int32 ActualDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
// 체력을 데미지만큼 감소시키고, 0 이하로 떨어지지 않도록 Clamp
Health = FMath::Clamp(Health - DamageAmount, 0.0f, MaxHealth);
//UE_LOG(LogTemp, Warning, TEXT("Health decreased to: %f"), Health);
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(
-1, 2.f, FColor::Yellow,
TEXT("Health decreased to: %f"), Health);
}
// 체력이 0 이하가 되면 사망 처리
if (Health <= 0.0f)
{
OnDeath();
}
// 실제 적용된 데미지를 반환
return ActualDamage;
}
// 사망 처리 함수
void AMainCharacter::OnDeath()
{
//UE_LOG(LogTemp, Error, TEXT("Character is Dead!"));
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(
-1, 2.f, FColor::Red,
TEXT("Character is Dead!"));
}
// 사망 후 로직
}
3️⃣ 지뢰 아이템 데미지 함수 수정
지뢰 아이템 (MineItem)이 폭발할 때, 주변 액터에게 데미지를 주려면 UGameplayStatics::ApplyDamage 함수를 호출해 해당 액터의 TakeDamage()가 실행되도록 하면 됩니다.
- UGamePlayStatics::ApplyDamage() 라는 UE Static 제공 함수.
- #include "Kismet/GameplayStatics.h" 해줘야 합니다.
- UGameplayStatics::ApplyDamage는 언리얼 엔진에서 액터에게 손쉽게 데미지를 가하고 대상의 TakeDamage() 함수를 트리거하는 핵심 함수입니다
UGameplayStatics::ApplyDamage(
AActor* DamagedActor, // 데미지를 받을 대상 액터
float BaseDamage, // 기본 데미지 양
AController* EventInstigator, // 데미지를 입힌 주체의 컨트롤러 (데스 카운트 등에 사용)
AActor* DamageCauser, // 데미지를 실제로 발생시킨 액터 (무기, 투사체 등)
TSubclassOf<UDamageType> DamageTypeClass // 데미지 타입 클래스
);
- ApplyDamage()
- 대상 액터(Actor)가 존재하는지 확인.
- 대상 액터의 TakeDamage() 함수를 호출합니다.
- DamageType은 여러 가지 파생 클래스를 만들어 물리/화염/독 등 다양한 데미지 유형을 정의할 수 있습니다 (지금은 기본값 사용).
- 지뢰는 독립적으로 스폰된 뒤 폭발하므로 EventInstigator를 nullptr로 둡니다.
- 멀티플레이에서 “누가 지뢰를 설치했느냐”를 추적하려면, 생성 시점에 Instigator나 Controller 정보를 넣어줄 수도 있습니다.
#include "MineItem.h"
#include "Components/SphereComponent.h"
#include "Kismet/GameplayStatics.h"
AMineItem::AMineItem()
{
ExplosionDelay = 5.0f;
ExplosionRadius = 300.0f;
ExplosionDamage = 30.0f;
ItemType = "Mine";
ExplosionCollision = CreateDefaultSubobject<USphereComponent>(TEXT("ExplosionCollision"));
ExplosionCollision->InitSphereRadius(ExplosionRadius);
//ExplosionCollision->SetCollisionProfileName(TEXT("OverlapAllDynamic"));
ExplosionCollision->SetCollisionProfileName(CollisionProfile_OverlapAllDynamic);
ExplosionCollision->SetupAttachment(Scene);
}
void AMineItem::ActivateItem(AActor* Activator)
{
// 5초 후 폭발 실행
GetWorld()->GetTimerManager().SetTimer(ExplosionTimerHandle, this, &AMineItem::Explode, ExplosionDelay);
}
void AMineItem::Explode()
{
TArray<AActor*> OverlappingActors;
ExplosionCollision->GetOverlappingActors(OverlappingActors);
for (AActor* Actor : OverlappingActors)
{
if (Actor && Actor->ActorHasTag("Player"))
{
//GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, FString::Printf(TEXT("Player damaged %d by MineItem"), ExplosionDamage));
// 데미지를 발생시켜 Actor->TakeDamage()가 실행되도록 함
UGameplayStatics::ApplyDamage(
Actor, // 데미지를 받을 액터
ExplosionDamage, // 데미지 양
nullptr, // 데미지를 유발한 주체 (지뢰를 설치한 캐릭터가 없으므로 nullptr)
this, // 데미지를 유발한 오브젝트(지뢰)
UDamageType::StaticClass() // 기본 데미지 유형
);
}
}
// 지뢰 제거
DestroyItem();
}
4️⃣ 힐링 아이템 체력 회복 함수 수정
- 아래 코드는 힐링 아이템(HealingItem)이 플레이어를 회복시킵니다. AddHealth() 함수를 직접 호출해 체력을 증가시킵니다.
- ActorHasTag("Player")로 플레이어인지 판별하는 단순 로직입니다.
- 캐릭터를 구분하는 더 안전한 방법(예: Cast<AMainCharacter>(Activator))을 쓰거나, Collision 채널을 이용하는 식으로도 바꿀 수 있습니다.
- AddHealth()에서 FMath::Clamp를 사용해 최대 체력 이상으로 올라가지 않도록 제한합니다.
점수 관리 시스템 구현하기
1️⃣ GameMode와 GameState의 연계 이해하기
- 언리얼 엔진에서 GameMode와 GameState는 게임의 전역 정보를 유지하고, 필요할 경우 멀티플레이어 환경에서 해당 정보를 서버와 클라이언트 간에 동기화하는 역할을 합니다.
- GameMode
- “게임의 규칙(룰)”을 정의하고 관리합니다.
- 어떤 캐릭터를 스폰할지, 플레이어가 사망했을 때 어떻게 처리할지를 결정합니다.
- 멀티플레이에서는 서버 전용으로 동작합니다(클라이언트에는 존재하지 않음).
- GameState
- 게임 플레이 전반에서 “공유되어야 하는 전역 상태”를 저장합니다. GameState는 기본적으로 “레벨당 1개” 존재하며, 엔진 내부에서 데이터 동기화를 고려해 설계되었기에 전역 데이터 관리용으로 적합합니다.
- 대표적으로 점수, 남은 시간, 현재 게임 단계(Phase), 스폰된 오브젝트의 총 개수 등을 저장합니다.
- 멀티플레이에서는 서버가 관리하고, 클라이언트는 이를 자동으로 동기화 받아볼 수 있습니다.
- GameMode
- 싱글 플레이에서도 GameState가 굳이 필요 없을 것 같지만, “전역적으로 공유해야 할 정보”를 한 군데서 관리하면 유지보수가 더 편해집니다. “몇 개의 아이템이 스폰되었는지”, “현재 게임 진행도는 어느 정도인지” 같은 데이터를 GameState에서 일괄 관리할 수 있기 때문입니다.
- 예시: 전역적인 스폰 제한 로직
- “현재 게임에 스폰된 아이템이 30개를 넘으면, 더 이상 아이템을 스폰하지 않는다.”
- 이 로직을 GameState에 두고 관리한다면:
- GameState 내부에서 “스폰된 아이템 개수” 변수를 관리
- 스폰이 일어날 때마다 이 변수를 증가시키고, 30개를 초과하는지 체크
- 초과 시 GameMode(또는 스폰 시스템)에서 “더 이상 스폰 금지” 로직 적용
- 이처럼 전역적으로 공유되어야 할 데이터(점수, 스폰 개수, 타이머 등)는 GameState에 저장하고, 게임 규칙이나 흐름을 제어하는 로직은 GameMode 또는 별도의 매니저 클래스로 분리하는 것이 일반적입니다.
2️⃣ GameState에 점수 데이터 및 함수 추가
- 이번에는 전역 점수를 GameState에 저장하고, 누군가 점수를 획득할 때마다 AddScore()를 호출해 점수가 증가하도록 구현해봅니다.
- 언리얼 에디터에서 C++ Class → GameStateBase를 선택하고, 클래스 이름을 MainGameStateBase 라고 짓습니다.
- Main GameStateBase.h와 MainGameStateBase.cpp에 아래와 같이 코드를 작성합니다.

#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameState.h"
#include "MainGameState.generated.h"
UCLASS()
class BC_CH3_ASSIGNMENT_5_API AMainGameState : public AGameState
{
GENERATED_BODY()
public:
AMainGameState();
// === Variables ===
// 전역 점수를 저장하는 변수
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Score")
int32 Score;
// === Func ===
// 현재 점수를 읽는 함수
UFUNCTION(BlueprintPure, Category="Score")
int32 GetScore() const;
// 점수를 추가해주는 함수
UFUNCTION(BlueprintCallable, Category="Score")
void AddScore(int32 Amount);
};
#include "MainGameState.h"
AMainGameState::AMainGameState()
{
}
int32 AMainGameState::GetScore() const
{
return Score;
}
void AMainGameState::AddScore(int32 Amount)
{
Score += Amount;
}
3️⃣ GameMode와 GameState 연동
다음으로, 기존에 만들었던 GameMode 클래스에서 GameStateClass 멤버를 우리가 만든 MainGameState로 설정해줍니다.
- 방금 만든 MainGameState 에 대한 C++ 코드가 컴파일되면, 언리얼 에디터에서 해당 클래스를 인식하게 됩니다.
- MainGameState 를 더 확장하거나 블루프린트 비주얼 스크립팅을 사용하기 위해, C++ 클래스를 부모로 하는 블루프린트(BP_ MainGameState)를 만들어서 사용합니다.
- Project Settings 적용
- Edit → Project Settings → Maps & Modes로 들어가서 Default GameMode를 우리 커스텀 게임 모드( MainGameState 또는 그 블루프린트 클래스)로 설정합니다.
- 그 뒤 Game State Class도 BP_MainGameState (또는 MainGameState )로 맞춰주면, 전역적으로 이 설정이 적용됩니다.
- World Settings에서 설정 (레벨별로 오버라이드 가능)

#include "MainGameMode.h"
#include "MainCharacter.h"
#include "MainPlayerController.h"
#include "MainGameState.h"
AMainGameMode::AMainGameMode()
{
// StaticClass(): 클래스 이름을 통해서 호출해주는것
DefaultPawnClass = AMainCharacter::StaticClass();
//
PlayerControllerClass = AMainPlayerController::StaticClass();
GameStateClass = AMainGameState::StaticClass();
}
4️⃣ 코인 아이템 점수 획득 함수 수정
- GetWorld()->GetGameState<AMainGameState>()로 게임 스테이트를 가져오고, AddScore(PointValue) 함수를 호출해 점수를 올립니다.
- PointValue(int32)는 이 코인 아이템이 제공하는 점수량이며, ACoinItem의 멤버 변수로 관리하고 있습니다.
- 이 과정을 통해 플레이어가 코인을 획득하면, 전역적으로 관리되는 GameState에서 점수가 증가하고, 코인은 사라져서 한 번만 획득되도록 처리됩니다.
#include "CoinItem.h"
#include "Engine/World.h"
#include "MainGameState.h"
ACoinItem::ACoinItem()
{
// 부모클래스라 안해도 되지만
// 점수 기본값을 0으로 설정
PointValue = 0;
ItemType = "DefaultCoin";
}
void ACoinItem::ActivateItem(AActor* Activator)
{
// 플레이어 태그 확인
if (Activator && Activator->ActorHasTag("Player"))
{
// 점수 획득 디버그 메시지
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Green,
FString::Printf(TEXT("Player Gained %i Points!"), PointValue));
if (UWorld* World = GetWorld())
{
if (AMainGameState* GameState = World->GetGameState<AMainGameState>())
{
GameState->AddScore(PointValue);
}
}
// 부모 클래스 (BaseItem)에 정의된 아이템 파괴 함수 호출
DestroyItem();
}
}
추천
[Unreal Engine/UE 기초] - 인터페이스 기반 아이템 클래스 설계하기 | [언리얼 엔진 C++ (Unreal Engine C++)]
인터페이스 기반 아이템 클래스 설계하기 | [언리얼 엔진 C++ (Unreal Engine C++)]
인터페이스 기반 아이템 클래스 설계하기 인터페이스 이해하기1️⃣ 인터페이스란?인터페이스 (Interface)란 클래스 (또는 오브젝트)가 반드시 구현해야 할 함수 목록만을 미리 정의해 두고, 실제
devcol.tistory.com
[Unreal Engine/UE 기초] - 충돌 이벤트로 획득되는 아이템 구현하기 | [언리얼 엔진 C++ (Unreal Engine C++)]
충돌 이벤트로 획득되는 아이템 구현하기 | [언리얼 엔진 C++ (Unreal Engine C++)]
충돌 이벤트로 획득되는 아이템 구현하기 이전 포스팅에서는 클래스 구조만 짜고 로직은 비여있던 상태.이전 포스팅: [Unreal Engine/UE 기초] - 인터페이스 기반 아이템 클래스 설계하기 | [언리얼
devcol.tistory.com
[Unreal Engine/UE 기초] - 아이템 스폰 및 레벨 데이터 관리하기 | [언리얼 엔진 C++ (Unreal Engine C++)]
아이템 스폰 및 레벨 데이터 관리하기 | [언리얼 엔진 C++ (Unreal Engine C++)]
아이템 스폰 및 레벨 데이터 관리하기 랜덤 위치에 아이템 스폰하기 1️⃣ 레벨 셋팅하기Resources 폴더에 Maps 폴더에는 3개의 레벨이 이미 존재합니다. 각각 난이도에 따라서 크기가 다른 맵이고,
devcol.tistory.com
[페이지] Unreal Engine | 언리얼 엔진