목차
- 내적이란?
- 내적을 통해 방향 알아내기
- C++로 구현하기
1. 내적이란
내적에 대해 수학백과사전에서는 다음과 같이 설명되어 있다.
내적은 벡터공간에서 한 쌍의 벡터마다 스칼라를 대응시키는 연산이다.
평면이나 공간에서는 두 벡터 사이의 각이라는 직관적인 개념을 생각할 수 있다. 일반적인 벡터공간에서도 두 벡터 사이의 각을 정의하고 두 벡터가 수직으로 교차한다는 것이 무슨 뜻인지 정의하는 데에 내적을 사용한다. 또한 내적은 길이를 정하는 데에도 사용할 수 있다. 내적을 흔히 중간에 점을 찍어 나타내기 때문에 'dot product'라고도 한다.
[네이버 지식백과] 내적 (수학백과, 2015.5)
이 내적을 통해 피격을 당할 경우 피격된 방향을 알 수 있다.
2. 내적을 통해 방향 알아내기
에너미가 있으면 이 에너미가 바라보는 방향으로 전방백터가 존재할 것이다. 그리고 에너미가 타격을 받는 순간 해당 지점을 향하는 백터를 만들 수 있다.
타격지점과 에너미의 위치를 빼면 타격지점을 향하는 백터를 만들어 낼 수 있다.
전방백터를 Forward,
타격지점을 ImpactPonit,
타격지점을 향하는 백터는 ToHit,
에너미의 위치를 ActorLocation이라고 하면
Tohit = ImpactPoint - ActorLocation
이렇게 된다.
이제 전방백터와 ToHit가 존재하고 이 둘의 각도를 알아내면 타격받는 지점이 어느 방향인지 알아낼 수 있다.
이 그림에서 전방백터와 ToHit사이의 각도가 45 ~ -45도 안에 있으면 앞에서 타격을 받은 것이다.
이런 식으로 둘 사이의 각도를 구하면 어느 방향에서 타격을 받았는지 알 수 있다. 그렇다면 이 각도를 어떻게 구할 수 있을까?
원래라면 수학적 이론에 따라 이 각도를 구해야 하지만 지금 하고 있는 것은 수학공부가 아닌 프로그래밍 공부인 관계로 다 넘어가서 공식만 알아보기로 하자.
삼차원공간에서 0이 아닌 아닌 두 벡터 U와 V가 이루는 각을 세타라고 하면 아래와 같은 공식이 성립한다. (출처: 수학백과)
그리고 이를 통해 아래와 같은 공식을 구할 수 있다.
따라서 세타(각도)는 아래와 같이 구할 수 있다.
양변에 코사인을 나누어서 세타만 남기는 방법인데 이렇게 하려면 코사인의 역이 필요하고 다행히 언리얼 엔진에서는 이것을 제공해 주기 때문에 가져다 쓰기만 하면 된다.
그러면 이러한 것들을 언리얼 엔진이 어떻게 제공해주는 지 살펴보자
3. C++로 구현하기
비주얼 스튜디오로 들아가자.
void AEnemy::GetHit(const FVector& ImpactPoint)
{
}
현재 GetHit 함수가 존재하고 매개변수로 타격지점의 좌표를 받는다.
타격을 받는 액터 즉, 에너미의 위치와 이 임팩트 포인트를 빼면 ToHit 백터를 알아낼 수 있을 것이다.
그럼 이제 전방백터(Forward Vector)와 이 ToHit를 알아내자.
const FVector Forward = GetActorForwardVector();
const FVector ToHit = ImpactPoint - GetActorLocation();
이렇게 하면 전방백터의 경우 정규화 되어 백터의 크기가 1로 되고 방향만 존재하게 된다. 두 백터사이의 각도를 구할때 백터의 크기는 딱히 필요가 없고 방향만 알면 되기 때문에 정규화 하게 되면 계산이 단순해지는 이점이 있다.
그렇기 때문에 ToHit도 정규화 해서 사용하자. GetSafeNormal() 함수를 이용할 것이다.
const FVector ToHit = (ImpactPoint - GetActorLocation()).GetSafeNormal();
이렇게 하면 정규화된 백터를 구할 수 있다. 이제 내적을 통해 이 둘 사이의 각도를 구해보자.
가장 먼저 첫번째 공식을 통해 좌변을 알아볼 것이다.
언리얼 엔진에서 제공해주는 기능을 이용하면 된다.
FVector::DotProduct(Forward, ToHit);
이렇게 하면 반환값으로 U * V 의 값을 얻는다. 그러면 다음은 우변을 구할 필요가 있다. 공식이 복잡해보이지만 여기서 알아야 할 것은 우리가 지금쓰고 있는 백터는 정규화된 백터라는 것이다. 그렇기 때문에 우변의 ||U|| ||V|| cos θ 는 cos θ 만 남게 된다.
즉 다음과 같이 계산이 된다는 것이다.
const double CosTheta = FVector::DotProduct(Forward, ToHit);
위의 구문은 수학 공식으로 봤을때 아래와 동일한 것이다.
cosθ = u * v
이제 여기서 코사인 세타의 코사인을 제거해야 하고 이때 코사인의 역함수를 쓰면 된다. 그리고 이 역함수는 언리얼엔진에서 제공해준다.
double Theta = FMath::Acos(CosTheta);
이렇게 간단하게 세타를 구할 수 있는데 여기서 반환되는 값은 라이단 단위이기 때문에 이를 디그리(도)로 바꿔주어야 한다.
이 또한 전부 엔진에서 제공해준다. (이 기능은 언리얼 엔진뿐 아니라 C++ 라이브러리에도 존재하는 것으로 알고 있다. 다만 그것을 쓰려면 헤더파일을 가져와야하기 때문에 그냥 언리얼 자체 라이브러리를 이용하도록 하자)
Theta = FMath::RadiansToDegrees(Theta);
이렇게 하면 라디안을 디그리로 변환해준다.
이제 이 각도가 제대로 구해졌는지 확인하기 위해 에디터 상에서 나타나게 해보자.
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(
1,
10.f,
FColor::Cyan,
FString::Printf(TEXT("Theta : %f"),Theta)
);
}
전체적인 코드는 아래와 같다.
void AEnemy::GetHit(const FVector& ImpactPoint)
{
// U = Forward
const FVector Forward = GetActorForwardVector();
// V = ToHit
const FVector ToHit = (ImpactPoint - GetActorLocation()).GetSafeNormal();
// U * V = |U||V| cosθ
// |U| = 1, |V| = 1, 따라서 U * V = cosθ
const double CosTheta = FVector::DotProduct(Forward, ToHit);
// cosθ의 역함수 구하기
double Theta = FMath::Acos(CosTheta);
// 라디안 단위를 디그리 단위로 변환
Theta = FMath::RadiansToDegrees(Theta);
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(1, 10.f, FColor::Cyan, FString::Printf(TEXT("Theta : %f"), Theta));
}
}
에디터로 돌아가 잘 작동되는지 확인하자.
전방에서 타격을 받을 시 45 ~ - 45도 사이에 있어야 하며 올바른 값이 나오는 것을 볼 수 있다.
왼쪽에서 타격을 받을시 -45에서 -135 사이에 있어야 하며 비록 부호는 잘못되었지만 각도 자체는 정상적으로 나오는 것을 볼 수 있다.
사진에서 보면 알 수 있듯이 값은 정상적으로 잘 나오지만 부호가 무조건 +로만 나오는 것을 볼 수 있다.
이것을 고쳐야 하는데 그 전에 간단히 고쳐야 할 부분이 있다.
현재 ToHit를 구하는 방식으로는 타격포인트의 Z축 방향과 에너미의 Z축 방향이 다를 수 있기 때문에 Z 축 방향이 존재하게 되는데 현재 Z축의 방향이 딱히 필요하지 않다. 그렇기 때문에 이를 바꾸려고 한다.
// Forward = U
const FVector Forward = GetActorForwardVector();
// ImpactPoint의 Z축을 에너미의 Z축과 동일하게 설정
const FVector ImpactLowered(ImpactPoint.X, ImpactPoint.Y, GetActorLocation().Z);
// ToHit = V
const FVector ToHit = (ImpactLowered - GetActorLocation()).GetSafeNormal();
이제 이 부분을 바꾸었으니 다음에는 디그리에서 +와 -를 구분하려고 한다.
이때 사용할 것은 백터의 외적이다. 다음에 외적에 대해 알아보도록 하자.
'게임프로그래밍 > 실습1' 카테고리의 다른 글
[언리얼 실습] 30. 1회 공격 당 1히트만 하기 (0) | 2024.10.29 |
---|---|
[언리얼 실습] 29. 백터의 외적을 통해 Hit 방향 알아내기 (0) | 2024.10.29 |
[언리얼 실습] 27. 인터페이스 사용하기 (0) | 2024.10.28 |
[언리얼 실습] 26. 에너미 클래스 만들기 (0) | 2024.10.28 |
[언리얼 실습] 25. 언리얼 인터페이스 만들기 (0) | 2024.10.27 |