데미지 시스템 2 - 피격 부분
공격 방향에 따른 피격 애니메이션 설정
피격 컴포넌트
뭐하는 컴포넌트인가
RPG 프로젝트인 만큼 피격 처리를 담당하는 컴포넌트가 있으면 좋겠다고 생각했다. 피격 후 데미지는 스탯 컴포넌트에서, 피격 반응은 HitReactionComponent
에서 처리한다. 주 역할은 다음과 같다.
- 피격 정보, 오너 정보를 사용해 캐릭터 정면 기준 피격 각도 계산
- 피격 방향에 따른 피격 애니메이션 재생
- 오너의 애님 인스턴스, 피격 방향별 반응 몽타주, 피격 리액션 사용 여부 변수와 같은 데이터 관리
피격 각도 계산
-
각도 계산 전체 코드
// HitReactionComponent.cpp void UIVHitReactionComponent::ComputeHitAngle(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) { if (!bIsUsingHitReaction || !GetOwner()) return; // 피격 리액션 사용 여부 및 컴포넌트 연결 확인 AActor* Owner = GetOwner(); // 피격 대상 float Angle = 0.0f; // 공격 방향 계산 if(DamageCauser) { // 이후 연산을 위한 벡터 정규화 FVector CharacterFoward = Owner->GetActorForwardVector().GetSafeNormal(); FVector HitDirection = (DamageCauser->GetActorLocation() - Owner->GetActorLocation()).GetSafeNormal(); // 공격 방향 구하기 Angle = FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(CharacterFoward, HitDirection))); // 공격 방향 좌우 판단 FVector CrossProduct = FVector::CrossProduct(CharacterFoward, HitDirection); Angle *= (CrossProduct.Z > 0.0f) ? 1.0f : -1.0f; } // 방향에 따른 애니메이션 선택 if (AnimInstance) { PlayHitReactionMontage(Angle); } }
이를 설명함에 있어서 피격 방향, 공격 방향 용어를 혼용함을 미리 알린다.
-
벡터 정규화
// 이후 연산을 위한 벡터 정규화 FVector CharacterFoward = Owner->GetActorForwardVector().GetSafeNormal(); FVector HitDirection = (DamageCauser->GetActorLocation() - Owner->GetActorLocation()).GetSafeNormal();
피격 각도 계산을 위해서는 TakeDamage의 데미지 유발자 정보와 컴포넌트 오너 정보가 필요하다. 각각 피격 방향과 캐릭터 정면 방향을 구하기 위함이다. 피격 방향은 데미지 유발자 위치에서 오너의 위치를 빼는 것으로 벡터를 구할 수 있다.
두 벡터는
GetSafeNormal
을 통해 정규화를 거친다. 내적을 위하여 벡터의 크기를 지우고 방향만을 남기는 것이다. -
내적을 사용한 방향 구하기
// 공격 방향 구하기 Angle = FMath::RadiansToDegrees( FMath::Acos( FVector::DotProduct(CharacterFoward, HitDirection)));
두 벡터의 내적은
A dot B = |A||B|cos(theta)
과 같이 나타낼 수 있다. 이때, 벡터 A, B는 정규화 된 상태이므로A dot B = cos(theta)
가 된다. [-1, 1] 범위의 cos(theta) 값을 얻었다.Acos
는 cos의 역함수이다. cos(theta)로부터 theta값, 각도를 알아내는데 사용한다. 얻어낸 각도는 라디안을 사용한다.그렇기에 라디안을 일반적으로 사용하는 각도로 변환할 필요가 있다. 그 과정이
RadiansToDegrees
다.모든 과정을 거친 후, [0, 180] 범위의 각도를 얻을 수 있다.
-
외적을 사용한 좌우 판단
// 공격 방향 좌우 판단 FVector CrossProduct = FVector::CrossProduct(CharacterFoward, HitDirection); Angle *= (CrossProduct.Z > 0.0f) ? 1.0f : -1.0f;
하지만 얻은 각도에는 방향이 없다. 오른쪽, 왼쪽 모두 [0, 180] 범위를 가지기 때문이다. 여기서는 외적을 통해 공격 방향의 좌우 판단을 진행한다.
외적은 연산 순서에 따라 방향성을 가지며, 오른손 법칙을 사용해 방향을 확인할 수 있다. 외적값이 양수라면 공격자가 오른쪽, 음수라면 왼쪽에서 공격을 받았다고 판단한다.
왼쪽 피격 시 각도에 음수를 곱하여 [-180, -0]의 범위를 가지도록 한다. 결과적으로 양쪽 360도 범위로 공격 방향을 나타낼 수 있게 되었다. 이렇게 계산한 각도를 기반으로 몽타주 선택을 진행한다.
피격 리액션 몽타주 선택
// IVHitReactionComponent.cpp
void UIVHitReactionComponent::PlayHitReactionMontage(float Angle)
{
if (!AnimInstance) return;
UAnimMontage* HitReactionMontage = nullptr; // 재생할 몽타주
// 각 90도씩 4방향 범위 판단 후 재생할 몽타주 선택
if (Angle >= -45.0f && Angle < 45.0f)
{
HitReactionMontage = FrontHitMontage; // 정면
}
else if (Angle >= 45.0f && Angle < 135.0f)
{
HitReactionMontage = RightHitMontage; // 오른쪽
}
else if (Angle >= -135.0f && Angle < -45.0f)
{
HitReactionMontage = LeftHitMontage; // 왼쪽
}
else if (Angle >= 135.0f || Angle < -135.0f)
{
HitReactionMontage = BackHitMontage; // 뒤쪽
}
AnimInstance->Montage_Play(HitReactionMontage); // 몽타주 재생
// 피격 애니메이션 종료 시 오너의 피격 리액션 종료
FOnMontageBlendingOutStarted BlendingOutDelegate;
BlendingOutDelegate.BindLambda([this](UAnimMontage* Montage, bool bInterrupted)
{
IIIVHitReactionInterface* HitReactionInterface = Cast<IIIVHitReactionInterface>(GetOwner());
if (HitReactionInterface)
{
HitReactionInterface->EndHitReaction();
}
});
AnimInstance->Montage_SetBlendingOutDelegate(BlendingOutDelegate, HitReactionMontage);
}
피격 방향을 90도씩 분할하여 총 4개의 피격 몽타주를 지정한다. 각 몽타주는 에디터에 노출하여 쉽게 변경할 수 있도록 하였다. 선택된 몽타주 재생 이후, 몽타주가 블랜드 아웃되는 시점에 맞추어 컴포넌트 오너에게 피격 상태가 종료되었음을 알린다.
HitReactionInterface
class IVAN_API IIIVHitReactionInterface
{
GENERATED_BODY()
public:
virtual void StartHitReaction() = 0;
virtual void EndHitReaction() = 0;
};
HitReactionComponent
와 함께 사용되는 인터페이스. 컴포넌트 오너는 소유한 컴포넌트에 직접 접근할 수 있지만, 반대로 컴포넌트가 오너의 정보를 참조하려면 오너가 누구인지 알아야한다. 이러한 결합도를 낮추기 위해 도입한 인터페이스이다.
제공하는 두 함수는 캐릭터 상태 변경과 관련된다. 피격 상태 관리는 스탯 컴포넌트가 담당하지만, 이 상태의 변경 시점은 피격 컴포넌트의 몽타주 재생 시간에 종속되어있다. 따라서 몽타주 종료 시점에 맞추어 오너의 상태 변경을 지시한다.