언리얼에는 내부적으로 데미지를 줄 수 있는 로직이 존재한다. UGameplayStatics::ApplyDamage
함수를 이용하면 쉽게 데미지 이벤트를 발생시킬 수 있다.
이전 글에서도 다루었듯이 데미지를 받고 그에 따라 체력이 감소하고, 체력이 감소함에 따라 특정한 행동을 하는 기능은 별도의 액터 컴포넌트에 구현하고 이를 컴포지션하여 사용하는 것이 좋다.
플레이어 또는 AI에 의해 컨트롤되는 폰 뿐만 아니라 구조물에도 데미지 관련 상호작용을 할 수 있어야 하는 경우가 있기 때문이다.
따라서 HealthComponent 라는 이름의 액터컴포넌트를 만들고 이를 타워, 탱크 블루프린트에 부착해 주었다.
UGameplayStatics::ApplyDamage
함수에서 콜백 함수로 사용할 함수는 다음과 같이 구성한다.
UFUNCTION()
void DamageTaken(AActor* DamagedActor, float Damage, const UDamageType* DamageType, class AController* Instigator, AActor* DamageCauser);
DamagedActor
는 데미지를 입을 액터이다.Damage
는 데미지의 양이다.DamageType
은 데미지의 타입이다.Instigator
는 데미지를 가한 액터(의 컨트롤러) 이다.DamageCauser
는 데미지를 가한 액터이다.UDamageType
은 데미지의 타입, 원인 등을 지정해서 다양한 타입의 데미지에 따라 다르게 대응할 수 있는 기능을 제공한다. 예를 들어 불에 의한 데미지를 받으면 화상 상태이상을 유발한다던지 하는 상호작용이 가능하다.
우리 프로젝트에서 데미지를 가하는 주체는 바로 포탄, 즉 발사체(projectile)이다.
그리고 이전에 이미 hit event를 발생시키는 OnHit
함수를 만든 적이 있다. 발사체가 다른 물체에 부딪히면 -> 데미지를 가한다 라는 자연스러운 플로우에 맞게 OnHit
함수 내부에 ApplyDamage
를 호출하려고 한다.
ApplyDamage
함수 안에 넣을 파라미터 중에서 DamagedActor
와 DamageType
그리고 DamageCauser
는 쉽게 구할 수 있다. DamageType은 별다른 값을 지정해주기보다는 StaticClass
를 사용하기로 한다.
Static Class는 그 클래스를 나타내는 UClass 객체를 리턴해 준다.
그런데 데미지 정보와 Instigator
는 직접 만들어야 한다.
UPROPERTY(EditAnywhere)
float Damage = 50;
데미지 양 정보를 헤더 파일에 선언하고 'Damage' 라는 이름으로 사용하기로 한다.
그리고 데미지를 가한 컨트롤러 정보인 Instigator
가 필요한데, 지금 탱크나 타워가 날려보내는 포탄은 SpawnActor
만 해주므로 Owner 정보가 없다. 따라서 누가 발사한 포탄인지 모른다. 그래서 BasePawn.cpp
에서 포탄을 스폰할 때 Owner 액터 정보를 발사체에 심어서 날리도록 한다.
그리고 발사체에서 GetOwner()
을 통해 쉽게 오너액터에 접근할 수 있고, GetInstigatorController()
함수를 이용해 Instigator 컨트롤러를 구할 수 있다.
AController* MyOwnerInstigator = GetOwner()->GetInstigatorController();
UClass* DamageType = UDamageType::StaticClass();
if (OtherActor != nullptr && OtherActor != this && OtherActor != GetOwner())
{
UGameplayStatics::ApplyDamage(OtherActor, Damage, MyOwnerInstigator, this, DamageType);
Destroy();
}
데미지를 가했으면 포탄은 사라져야 하므로 Destroy()
함수를 추가한다.
따라서 만들어진 함수는 위와 같다. 여기서 GetOwner
가 nullptr일 수 있으니 예외처리를 해 주고 나면 완성이다.
void AProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
if (GetOwner() == nullptr)
return;
AController* MyOwnerInstigator = GetOwner()->GetInstigatorController();
UClass* DamageType = UDamageType::StaticClass();
if (OtherActor != nullptr && OtherActor != this && OtherActor != GetOwner())
{
UGameplayStatics::ApplyDamage(OtherActor, Damage, MyOwnerInstigator, this, DamageType);
Destroy();
}
}
타워가 탱크를 쏴서 충돌할 때마다 DamageTaken
로그가 발생하고 있다.