본문 바로가기

게임프로그래밍/실습1

[언리얼 실습] 49. 데미지 시스템 사용하기


목차

  1. 언리얼 데미지 시스템
  2. 데미지 받기 (TakeDamage)
  3. 데미지 주기 (ApplyDamage)
  4. 버그 수정하기

1. 언리얼 데미지 시스템

 

언리얼 엔진에는 자체적으로 가지고 있는 데미지 시스템이 있다. 액터라면 종류에 상관없이 데미지를 받을 수 있다.

 

현재 에너미에게는 체력이 있고 이를 보여주는 체력바도 있다. 이제는 실제로 데미지를 줘서 이 체력에 영향을 주고 싶다.

 

이제 비주얼 스튜디오에서 이를 구현해보자.


2. 데미지 주기 (ApplyDamage)

 

데미지를 주는 법을 알아보겠다. 무기.cpp 파일로 들어가자.

 

무기 클래스에는 OnBoxOverlap() 함수가 있고 이곳에서 오버랩되는 액터가 무엇인지 알 수 있다.

 

UGameplayStatics 라이브러리에 있는 함수 중 ApplyDamage() 함수를 사용할 것인데 매개변수는 다음과 같다.

 

1. 데미지를 받는 액터

2. 데미지

3. 이벤트 인스티게이터

4. 데미지를 발생시키는 실제 액터

5. 데미지 타입 클래스

 

여기서 주목해야할 것은 3번 매개변수이다. 인스티게이터를 어떻게 알 수 있을까? 무기에 인스티게이터가 누구인지 설정을 해주어야 한다.

 

무기의 주인은 무기를 가지고 있는 액터가 될것이다. 무기를 소지할 수 있는 액터는 플레이어와 에너미이지만 에너미는 현재 무기를 가질 수 있는 능력이 없으므로 플레이어 먼저 설정을 해주도록 하자.

 

캐릭터.cpp 파일로 들어간다.

 

void AKnight::EKeyPressed()
{
	AWeapons* OverlappingWeapon = Cast<AWeapons>(OverlappingItem);
	if (OverlappingWeapon)
	{
		OverlappingWeapon->Equip(GetMesh(), FName("RightHandSocket"));
        
        // 생략

 

무기를 장착하는 부분이 있는데 OverlappingWeapon 에 인스티게이터를 설정해주면 된다.

 

OverlappingWeapon->SetOwner(this);
OverlappingWeapon->SetInstigator(this);

 

OverlappingWeapon 주인과 인스티게이터 모두 캐릭터 클래스이다. 그렇기 때문에 둘다 this로 설정해주면 된다.

현재는 Instigator만 설정해도 되겠지만 나중에는 Owner가 누구인지도 중요하게 되기 때문에 둘 다 설정하였다.

 

함수를 보다보면 조금 어색한 부분이 있다. 둘다 무기 클래스에 필요해서 사용하는 함수인데 캐릭터 클래스에서 사용하고 있다. 모듈화에 조금 맞지 않아 보인다.

 

캐릭터 클래스에서는 액터 정보만 넘기고 실제 세팅은 무기 클래스에서 하는 게 맞아 보인다.

 

그렇기 때문에 무기 클래스로 가 함수를 정의하도록 하자.

 

// void Equip(USceneComponent* InParent, FName InSocketName); 아래로 변경
void Equip(USceneComponent* InParent, FName InSocketName, AActor* NewOwner, APawn* NewInsigator);

 

이제 새롭게 오너랑 인스티게이터를 매개변수로 받는다. 대게 오너랑 인스티게이터가 갖지만 간혹 다른 경우도 있어서 따로 받는다고 한다.

 

void AWeapons::Equip(USceneComponent* InParent, FName InSocketName, AActor* NewOwner, APawn* NewInsigator)
{
	SetOwner(NewOwner);
	SetInstigator(NewInsigator);
    
    // 생략

 

이렇게 오너랑 인스티게이터를 설정해주었고, 마지막으로 캐릭터 클래스에서 함수를 호출하면 된다.

void AKnight::EKeyPressed()
{
	AWeapons* OverlappingWeapon = Cast<AWeapons>(OverlappingItem);
	if (OverlappingWeapon)
	{
		OverlappingWeapon->Equip(GetMesh(), FName("RightHandSocket"), this, this);
        
        // 생략

 

이제 인스티게이터를 설정하였으니 ApplyDamage() 함수를 사용할 수 있다.

 

UGameplayStatics::ApplyDamage(HitResult.GetActor(), Damage, GetInstigator()->GetController(), this, UDamageType::StaticClass());

 

데미지 float의 경우 웨폰의 헤더파일에서 기본값을 주어 정의하였다.


3. 데미지 받기 (TakeDamage)

 

이제 피해를 줄 수 있으니 피해를 받을 수도 있어야 한다. 이를 구현해보자.

UCLASS()
class PRACTICE_API AEnemy : public ACharacter, public IHitInterface
{
	GENERATED_BODY()

public:

//...

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

// ...
}

 

 

TakeDamage 함수를 오버라이드 하여 사용할 것이다. 함수가 호출되면 행동할 동작을 정의해보자.

 

전에 Attribute 클래스를 만들었고 이곳에서 체력을 정의하였다. 이 값을 가져와 사용할 수 있을 것이다. 현재 체력이 private 영역에 있으므로 getter와 setter를 만들어 이를 조절할 수 있게 하자.

 

먼저 setter를 설정할 것이다. setter의 경우 단순히 빼는 연산만 하는 것이 아니라 약간의 연산을 더할 것이라 인라인 함수가 아닌 일반 함수로 만들 것이다.

class PRACTICE_API UAttributeComponent : public UActorComponent
{
// 생략
public:
	void ReceiveDamage(float Damage);
    
}
void UAttributeComponent::ReceiveDamage(float Damage)
{
	Health = FMath::Clamp(Health - Damage, 0.f, MaxHealth);
}

 

Clap 함수는 X(첫번째 매개변수의 값)을 Health에 전달하는데 이때 이 값은 이 두번째와 세번째 매개변수 사이에서만 존재하도록 하는 함수이다.

 

즉 체력을 넘어서는 데미지를 줄 경우 마이너스 체력이 아니라 0의 체력으로 설정한다. 이렇게 하면 체력이 마이너스로 넘어갈 일이 없으며 이 함수를 통해 체력을 회복하는 일이 있어도 (일반적으로 이렇게 구현하지는 않겠지만) 체력이 최대체력을 넘어 회복되는 일도 없을 것이다.

 

이제 세터를 만들었으니 이걸 에너미 클래스에서 쓰도록 하자.

 

float AEnemy::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	if (Attributes)
	{
		Attributes->ReceiveDamage(DamageAmount);
	}
	return 0.0f;
}

 

이렇게 하면 체력이 줄어든다. 체력이 줄어들면 체력바도 업데이트 해야한다. 

 

if (HealthBarWidget) { HealthBarWidget->SetHealthPercent(.1f); }

 

BeginPlay() 함수에 있던 이 구문을 지우고 TakeDamge에서 쓰도록 하자.

 

최대체력과 현재 체력을 가져와 비율을 표시해야하는데 이 비율을 가져오기 위해서 Getter가 필요하다.

 

Attribute 컴포넌트로 다시 가자

 

class PRACTICE_API UAttributeComponent : public UActorComponent
{
	GENERATED_BODY()
    
// 생략

public:
float GetHealthPercent();

}

 

float UAttributeComponent::GetHealthPercent()
{
	return Health / MaxHealth;
}

 

이제 Getter를 얻었으니 헬스바를 업데이트 하자.

 

float AEnemy::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	if (Attributes && HealthBarWidget)
	{
		Attributes->ReceiveDamage(DamageAmount);
		HealthBarWidget->SetHealthPercent(Attributes->GetHealthPercent());
	}
	return DamageAmount;
}

 

이제 컴파일하고 잘 되는지 확인하자.

 

 

피해를 입었지만 아무일도 생기지 않는다. 이를 고쳐보도록 하겠다.

 


4. 버그 수정하기

 

 

캐릭터를 선택하고 Attribute 컴포넌트를 보면 최대체력과 체력이 0인 것을 볼 수 있다. 이렇게 되면 체력의 변화가 생기지 않고 비율또한 그대로 이다.

 

이를 바꿔주자

 

 

이제 체력바가 줄어드는 것을 볼 수 있다.

 

 

추가로 c++에서도 기본값을 설정해주었다.