포스트

내적으로 시야각, 외적으로 좌우 판정하기

수식보다 '어떤 질문에 답하는 도구인가'

적이 나를 봤는가, 내가 적의 왼쪽에 있는가 — 두 판정은 각각 내적과 외적 하나로 끝난다. 각도를 방향 벡터로 바꾸는 삼각함수부터 RPG 시야각 판정 적용까지.

내적으로 시야각, 외적으로 좌우 판정하기

적이 나를 봤는지, 내가 적의 왼쪽에 있는지 — 게임에서 자주 나오는 이 두 판정은 각각 내적외적 하나로 끝난다. 문제는 두 연산을 수식으로만 외우면 어느 상황에 뭘 꺼내야 할지 헷갈린다는 것이다. 이 글에서는 둘을 “얼마나 같은 방향인가”와 “어느 쪽에 있는가”라는 질문으로 나눠 정리하고, 각도를 방향 벡터로 바꾸는 삼각함수부터 시작해 RPG 시야각 판정 문제에 직접 적용한 과정을 이야기하려 한다.

삼각함수 — 각도를 방향 벡터로

캐릭터를 각도 θ 방향으로 움직이려면 그 각도를 방향 벡터로 바꿔야 한다. 공식은 하나다.

1
방향 벡터 = (cos θ, sin θ)

단위원 위의 점이 곧 단위 방향 벡터라서, 어떤 각도든 이 식에 넣으면 크기 1짜리 방향 벡터가 나온다.

각도cos θsin θ벡터
10(1, 0) → 오른쪽
90°01(0, 1) → 위
180°-10(-1, 0) → 왼쪽
270°0-1(0, -1) → 아래
1
2
// UE5: 각도로 이동 방향 구하기
FVector dir = FVector(FMath::Cos(AngleRad), FMath::Sin(AngleRad), 0);

내적 — 얼마나 같은 방향?

내적이 답하는 질문은 하나다 — 두 벡터가 얼마나 같은 방향을 가리키는가.

1
a · b = |a||b| cos θ

단위벡터끼리라면 크기가 1이므로 내적값이 곧 cos θ다.

1
â · b̂ = cos θ
결과값의미
1.0완전히 같은 방향 (0°)
0.560° 차이
0.0수직 (90°)
-0.5120° 차이
-1.0완전히 반대 방향 (180°)

내적값에서 각도를 역산할 수도 있다.

1
cos θ = 0.5  →  θ = arccos(0.5) = 60°

계산 방법

실제 계산은 성분곱으로 한다.

1
2
3
a = (ax, ay),  b = (bx, by)

a · b = ax×bx + ay×by

예시: a = (3, 4), b = (5, 0)

1
a · b = (3×5) + (4×0) = 15 + 0 = 15

게임 활용: 시야각 판정

전방 벡터와 적 방향 벡터를 내적하면 cos 값이 나오고, 그걸 시야각의 cos 임계값과 비교하면 시야 안인지 판정할 수 있다.

1
2
3
4
5
6
// 시야각 90° (±45°) 내에 적이 있는지 판정
float dot = FVector::DotProduct(ForwardVec, ToEnemyVec); // 단위벡터 가정
if (dot > FMath::Cos(FMath::DegreesToRadians(45.f))) // cos45° ≈ 0.707
{
    // 적이 시야 안에 있음
}

시야각이 좁을수록 cos 임계값이 1에 가까워진다는 것만 기억하면 된다.

시야각cos 임계값조건
±30°cos30° ≈ 0.866dot > 0.866
±45°cos45° ≈ 0.707dot > 0.707
±60°cos60° = 0.5dot > 0.5

외적 — 어느 쪽?

외적이 답하는 질문은 방향의 유사도가 아니라 위치다 — b는 a의 왼쪽에 있는가, 오른쪽에 있는가.

1
|a × b| = |a||b| sin θ  (크기 = 평행사변형 넓이)

2D에서는 스칼라 부호로 방향을 판단한다 (y-up 기준).

결과의미
양수b가 a의 왼쪽 (반시계)
음수b가 a의 오른쪽 (시계)
절댓값평행사변형 넓이

2D 외적 계산

1
2
3
a = (ax, ay),  b = (bx, by)

a × b = ax×by − ay×bx

예시: a = (3, 4), b = (5, 0)

1
2
3
a × b = (3×0) − (4×5) = 0 − 20 = -20
→ 음수 → b가 a의 오른쪽
→ |−20| = 20 → 평행사변형 넓이

이 공식이 어디서 왔는지는 3D 외적을 보면 된다 — z성분만 꺼낸 것이다.

1
2
3
|  i   j   k  |
| ax  ay   0  |  →  z성분 = ax×by − ay×bx
| bx  by   0  |

게임 활용: 좌우 판정

1
2
3
4
5
float cross = FVector::CrossProduct(ForwardVec, ToEnemyVec).Z;
if (cross > 0)
    TurnLeft();   // 적이 왼쪽
else
    TurnRight();  // 적이 오른쪽

벡터 정규화 (단위벡터)

방향만 남기고 크기를 1로 만드는 게 정규화다.

1
d̂ = 벡터 / |벡터| = (x/|v|, y/|v|)

예시: A = (4, 2)

1
2
3
|A| = √(4²+2²) = √20 = √(4×5) = 2√5 ≈ 4.47

d̂_A = (4/2√5,  2/2√5) = (2/√5,  1/√5) ≈ (0.894, 0.447)

루트 간소화는 루트 안에서 완전제곱수(4, 9, 16…)를 꺼내면 된다.

1
2
3
√20 = √(4×5) = 2√5
√18 = √(9×2) = 3√2
√45 = √(9×5) = 3√5

자주 쓰는 루트 근사값: √2 ≈ 1.414, √3 ≈ 1.732, √5 ≈ 2.236


문제 풀이 — RPG 시야각 판정

조건: 캐릭터 (0,0), 전방 f̂ = (1,0), 시야각 90° (±45°)
적: A=(4,2), B=(3,4), C=(-2,3), D=(5,-1)

Step 1: 정규화

크기d̂ (근사)
A2√5 ≈ 4.47(0.894, 0.447)
B5(0.6, 0.8)
C√13 ≈ 3.61(-0.555, 0.832)
D√26 ≈ 5.10(0.981, -0.196)

Step 2: 내적 f̂·d̂

f̂ = (1,0)이라 x성분만 남는다 (y항은 0이 곱해져서 사라짐).

1
f̂·d̂ = (1×dx) + (0×dy) = dx
f̂·d̂> 0.707?
A0.894O
B0.600X
C-0.555X
D0.981O

시야 내 적: A, D

Step 3: 좌우 판정 (f × d̂의 z성분)

f̂ = (1,0)이라 f×d̂ = dy (x항이 사라짐).

1
2
적 A: f×d̂_A = 0.447  > 0 → 왼쪽
적 D: f×d̂_D = -0.196 < 0 → 오른쪽

시야 내 + 왼쪽: A


정리 — 내적과 외적을 고르는 질문

  1. 삼각함수 = 각도 → 방향 변환 도구. (cos θ, sin θ)으로 어떤 각도든 단위벡터로 변환할 수 있다.
  2. 내적 = 앞/뒤 판단. 단위벡터 내적값이 cos θ와 같으므로, 시야각 임계값을 cos으로 바꿔서 비교하면 된다.
  3. 외적 = 좌/우 판단. 2D 외적 ax×by − ay×bx의 부호만으로 어느 쪽인지 바로 알 수 있다.
  4. f̂ = (1,0)일 때 계산 단순화. 내적은 x성분, 외적은 y성분만 남는다 — 전방이 축과 정렬되면 계산이 극단적으로 간단해진다.
  5. 수식 암기보다 “어떤 도구인가” 먼저. 내적·외적을 구분하는 질문: “방향이 얼마나 같냐(내적)” vs “어느 쪽에 있냐(외적)”.

핵심 요약 — 내적은 “얼마나 같은 방향인가”(시야각 판정), 외적은 “어느 쪽에 있는가”(좌우 판정)에 답하는 도구다. 전방 벡터가 (1,0)처럼 축과 정렬되면 내적은 x성분, 외적은 y성분만 남아서 계산이 크게 단순해진다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.