1. 벡터 연산
캐릭터의 상대 위치를 판단하는 가장 일반적인 방법은 벡터의 내적(Dot Product)과 외적(Cross Product)을 사용하는 것.
1.1. 기본 개념
- 좌표계(Coordinate System): 3D 공간의 모든 오브젝트는 월드 좌표계(World Space) 상의 위치(Position)와 방향(Rotation) 값을 가집니다. 게임 엔진에 따라 왼손 좌표계(Left-handed, Unity, DirectX) 또는 오른손 좌표계(Right-handed, Unreal, OpenGL)를 사용합니다.
- 벡터(Vector): 크기와 방향을 가진 물리량입니다. 게임에서는 위치(Position Vector), 방향(Direction Vector) 등을 나타내는 데 사용됩니다.
- 위치 벡터: 월드 좌표계의 원점(0,0,0)에서 특정 위치까지의 벡터입니다.
- 방향 벡터: 캐릭터가 바라보는 방향 등을 나타내는 크기가 1인 벡터(단위 벡터, Unit Vector)입니다.
- 벡터 연산:
- 벡터 뺄셈: 두 위치 벡터
A와B가 있을 때,B - A는 A에서 B를 향하는 방향 벡터를 계산합니다. - 정규화(Normalization): 벡터의 크기를 1로 만드는 과정입니다. 방향은 유지하되 크기(거리)에 의한 왜곡을 없애기 위해 반드시 필요합니다.
- 벡터 뺄셈: 두 위치 벡터
1.2. 내적 (Dot Product): 앞/뒤 판단
내적은 두 벡터가 얼마나 "같은 방향을 향하고 있는지"를 나타내는 스칼라 값입니다.
- 공식:
A · B = |A| * |B| * cos(θ)|A|,|B|: 벡터 A와 B의 크기θ: 두 벡터 사이의 각도
두 벡터가 모두 단위 벡터(크기 1)라면, 내적의 결과는 cos(θ)와 같습니다.
cos(θ)값의 특징:θ가 -90° ~ 90° 사이 (예각) →cos(θ)> 0θ가 90° 또는 -90° (직각) →cos(θ)= 0θ가 90° ~ 270° 사이 (둔각) →cos(θ)< 0
[구현 방식]
- 플레이어의 '앞쪽' 방향 벡터(
playerForward)를 구합니다. (단위 벡터여야 함) - 플레이어 위치에서 상대를 향하는 방향 벡터(
vectorToTarget)를 구합니다. (targetPosition - playerPosition) vectorToTarget을 정규화합니다.dotResult = playerForward · vectorToTarget(내적)을 계산합니다.
dotResult > 0: 상대가 플레이어의 앞에 있습니다.dotResult < 0: 상대가 플레이어의 뒤에 있습니다.dotResult = 0: 상대가 플레이어의 정확히 옆(90도)에 있습니다.
1.3. 외적 (Cross Product): 좌/우 판단
외적은 두 벡터에 동시에 수직인 새로운 벡터를 반환하는 연산입니다. 이 새로운 벡터의 방향을 통해 좌/우를 구분할 수 있습니다.
- 공식:
C = A x B- 벡터 C는 벡터 A와 B가 이루는 평면에 수직입니다.
- C의 방향은 좌표계(왼손/오른손)와 벡터의 순서에 따라 결정됩니다. (오른손 법칙, 왼손 법칙)
[구현 방식] (Y축을 위(Up)로 사용하는 오른손 좌표계 기준)
- 플레이어의 '앞쪽' 방향 벡터(
playerForward)와 플레이어에서 상대를 향하는 정규화된 방향 벡터(vectorToTarget)를 준비합니다. crossResult = playerForward x vectorToTarget(외적)을 계산합니다.crossResult는playerForward와vectorToTarget가 이루는 평면(보통 XZ 평면)에 수직인 벡터이므로, Y축 방향 성분을 가집니다.crossResult의 Y축 성분(crossResult.y)의 부호를 확인합니다.
crossResult.y > 0: 상대가 플레이어의 오른쪽에 있습니다. (오른손 법칙에 따라,playerForward에서vectorToTarget으로 감아쥘 때 엄지손가락이 위를 향함)crossResult.y < 0: 상대가 플레이어의 왼쪽에 있습니다.crossResult.y = 0: 상대가 플레이어의 정면 또는 정후면에 있습니다.
2D 환경에서의 좌/우 판단:
2D(XY 평면)에서는 외적의 결과가 Z축 성분만 가지는 스칼라 값처럼 계산됩니다 (A.x * B.y - A.y * B.x). 이 값의 부호가 바로 좌/우를 결정합니다.
1.4. 로컬 좌표계 변환을 이용한 방법
더 직관적인 방법으로, 상대의 월드 좌표를 플레이어의 로컬 좌표계(Local Space)로 변환하는 방법이 있습니다.
- 플레이어의 월드 변환 행렬(World Transform Matrix)의 역행렬(Inverse Matrix)을 구합니다. 이 역행렬이 월드 좌표를 로컬 좌표로 변환하는 행렬이 됩니다.
- 상대의 월드 위치(
targetWorldPosition)를 이 역행렬과 곱합니다. - 결과로 나온
targetLocalPosition은 플레이어를 원점(0,0,0)으로, 플레이어의 앞쪽을 Z+ 방향(또는 엔진에 따라 다름)으로 했을 때의 상대 좌표입니다.
targetLocalPosition.x > 0: 상대가 오른쪽에 있습니다.targetLocalPosition.x < 0: 상대가 왼쪽에 있습니다.targetLocalPosition.z > 0: 상대가 앞에 있습니다.targetLocalPosition.z < 0: 상대가 뒤에 있습니다.
1.5. 방법 간 비교
| 방법 | 장점 | 단점 | 사용 사례 |
|---|---|---|---|
| 내적 + 외적 | - 계산 비용이 비교적 저렴하다. - 직관적으로 앞/뒤, 좌/우를 분리해서 계산할 수 있다. |
- 두 번의 벡터 연산이 필요하다. - 좌표계에 따라 외적 결과의 해석이 달라질 수 있다. |
간단한 위치 확인, AI의 방향 전환 결정 등 대부분의 경우에 적합하다. |
| 로컬 좌표계 변환 | - 변환 후에는 좌표의 부호만 확인하면 되므로 매우 직관적이다. - 위치뿐만 아니라 거리, 상대 각도 등 추가 정보 계산이 용이하다. |
- 역행렬 계산은 벡터 연산보다 비용이 높다. - 이미 다른 목적으로 역행렬(View Matrix)을 계산하고 있는 경우가 아니라면 비효율적일 수 있다. |
렌더링 파이프라인, 물리 엔진 등에서 이미 로컬 좌표 변환이 필요할 때 부가적으로 사용하면 효율적이다. |
#include <iostream>
#include <string>
#include <cmath>
#include <vector>
// 3D 벡터를 표현하는 간단한 구조체
struct Vector3 {
float x, y, z;
// 벡터 뺄셈 연산자 오버로딩
Vector3 operator-(const Vector3& other) const {
return {x - other.x, y - other.y, z - other.z};
}
// 벡터의 크기(길이)를 계산
float magnitude() const {
return std::sqrt(x * x + y * y + z * z);
}
// 벡터를 정규화 (크기를 1로 만듦)
void normalize() {
float mag = magnitude();
if (mag > 0) {
x /= mag;
y /= mag;
z /= mag;
}
}
};
// 두 벡터의 내적(Dot Product)을 계산하는 함수
float dot(const Vector3& a, const Vector3& b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
// 두 벡터의 외적(Cross Product)을 계산하는 함수
Vector3 cross(const Vector3& a, const Vector3& b) {
return {
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
};
}
// 게임 캐릭터를 표현하는 클래스
class Character {
public:
std::string name;
Vector3 position; // 캐릭터의 월드 위치
Vector3 forward; // 캐릭터가 바라보는 방향 (단위 벡터)
Character(std::string name, Vector3 position, Vector3 forward)
: name(name), position(position), forward(forward) {
// 생성 시 forward 벡터를 정규화하여 항상 단위 벡터임을 보장
this->forward.normalize();
}
};
// 상대 캐릭터의 위치 관계를 분석하는 함수
void checkRelativePosition(const Character& player, const Character& target) {
std::cout << "--- [" << player.name << "]가 [" << target.name << "]의 위치를 분석합니다. ---" << std::endl;
// 1. 플레이어에서 타겟을 향하는 벡터 계산
Vector3 vectorToTarget = target.position - player.position;
// Q1: 아래 코드는 vectorToTarget을 정규화합니다. 왜 여기서 벡터를 정규화해야 할까요?
// 만약 정규화하지 않고 내적과 외적을 계산하면 어떤 문제가 발생할 수 있을까요?
vectorToTarget.normalize();
// 2. 내적을 이용해 앞/뒤 판단
// Q2: 내적(dot product)의 결과가 양수, 음수, 0일 때 각각 무엇을 의미하나요?
// 이 값이 두 벡터 사이의 각도와 어떤 관계가 있는지 설명해보세요.
float dotResult = dot(player.forward, vectorToTarget);
std::string frontOrBack;
if (dotResult > 0.0f) {
frontOrBack = "앞";
} else if (dotResult < 0.0f) {
frontOrBack = "뒤";
} else {
frontOrBack = "정확히 옆";
}
// 3. 외적을 이용해 좌/우 판단
// Q3: 외적(cross product)을 사용하여 좌우를 판단하는 원리는 무엇인가요?
// 이 코드에서는 Y축 값을 사용했는데(crossResult.y), 그 이유는 무엇이며,
// 만약 게임 엔진이 Z축을 'Up' 벡터로 사용한다면 이 코드는 어떻게 바뀌어야 할까요?
Vector3 crossResult = cross(player.forward, vectorToTarget);
std::string leftOrRight;
// 부동소수점 오차를 고려하여 작은 임계값(epsilon)을 사용
const float epsilon = 1e-4;
if (crossResult.y > epsilon) {
// 오른손 좌표계 기준: player.forward에서 vectorToTarget 방향으로 감아쥘 때
// 오른손 엄지가 위(Y+)를 향하므로 '오른쪽'
leftOrRight = "오른쪽";
} else if (crossResult.y < -epsilon) {
leftOrRight = "왼쪽";
} else {
// crossResult.y가 0에 가깝다는 것은 두 벡터가 거의 평행하다는 의미
leftOrRight = "정면 또는 정후방";
}
// 최종 결과 출력
std::cout << "> 결과: [" << target.name << "] (은)는 [" << player.name << "]의 ["
<< frontOrBack << "]에 있으며, [" << leftOrRight << "]에 위치합니다." << std::endl << std::endl;
}
int main() {
// C++ 입출력 속도 향상
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
// 플레이어 캐릭터 설정 (원점에 위치, Z+ 방향을 바라봄)
Character player("Player", {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f});
// 테스트할 다른 캐릭터들 설정
std::vector<Character> others;
others.push_back(Character("Enemy_Front_Right", {10.0f, 0.0f, 20.0f}, {0.0f, 0.0f, -1.0f})); // 앞, 오른쪽
others.push_back(Character("Enemy_Front_Left", {-10.0f, 0.0f, 20.0f}, {0.0f, 0.0f, -1.0f})); // 앞, 왼쪽
others.push_back(Character("Enemy_Back_Right", {10.0f, 0.0f, -20.0f}, {0.0f, 0.0f, 1.0f})); // 뒤, 오른쪽
others.push_back(Character("Enemy_Back_Left", {-10.0f, 0.0f, -20.0f}, {0.0f, 0.0f, 1.0f})); // 뒤, 왼쪽
others.push_back(Character("Enemy_Directly_Behind", {0.0f, 0.0f, -10.0f}, {0.0f, 0.0f, 1.0f})); // 정후방
// 모든 캐릭터에 대해 위치 관계 분석 수행
for (const auto& other : others) {
checkRelativePosition(player, other);
}
return 0;
}'Unreal' 카테고리의 다른 글
| 레이 트레이싱 Ray Tracing (1) | 2025.12.18 |
|---|---|
| 온라인 서브시스템과 세션 인터페이스 (0) | 2025.12.17 |
| bUseControllerRotation과 bUseControllerDesiredRotation의 차이 (0) | 2025.11.26 |
| 언리얼 엔진 가비지 컬렉션 (0) | 2025.11.17 |
| NetMode, NetConnection, NetDriver, NetRole (0) | 2025.11.14 |
