2026-04-16 벡터 연산과 게임 수학 기초
목차
y-up vs y-down 좌표계
좌표계란?
공간에서 위치를 숫자로 표현하는 기준 체계. 같은 좌표값이라도 좌표계에 따라 화면에서의 위치가 달라진다.
y-up 좌표계 (수학/게임 월드)
1
2
3
4
5
6
7
8
| y
↑
|
| (3, 4) ← 오른쪽 위
|
───────┼──────→ x
|
| (3, -2) ← 오른쪽 아래
|
- y값이 클수록 위 (수학 그래프와 동일)
- 사용처: 언리얼 엔진 월드 좌표, Unity 월드 좌표, 물리 엔진, 수학
y-down 좌표계 (화면/스크린 좌표계)
1
2
3
4
5
6
7
| ───────┼──────→ x
|
| (3, 2) ← 오른쪽 아래
|
| (3, 6) ← 더 아래
↓
y
|
- y값이 클수록 아래 (모니터 스캔 방향과 동일)
- 사용처: UI 좌표 (UMG, HTML Canvas), 텍스처 UV, DirectX 텍스처 좌표, 이미지 픽셀
같은 좌표, 다른 위치
| 좌표 (3, 4) | y-up | y-down |
|---|
| 화면상 위치 | 오른쪽 위 | 오른쪽 아래 |
| y값 증가 시 | 위로 이동 ↑ | 아래로 이동 ↓ |
언리얼 엔진에서의 좌표계
1
2
3
4
5
6
| // 언리얼은 왼손 좌표계 (Left-Handed)
// X = 앞(Forward), Y = 오른쪽(Right), Z = 위(Up)
// → 월드 공간은 Z-up (y-up의 3D 버전)
// UI (UMG)는 화면 좌표계 사용
// → 왼쪽 상단이 (0, 0), 아래로 갈수록 y 증가 (y-down)
|
y-down → y-up 변환
1
2
3
4
5
6
| // 화면 좌표를 월드 좌표로 변환할 때
// y_world = ScreenHeight - y_screen
// 예시: 화면 해상도 1080, 화면 좌표 (300, 200)
int ScreenY = 200;
int WorldY = 1080 - ScreenY; // 880 (위쪽)
|
복기
- y-up: y가 클수록 위 → 게임 월드, 물리, 수학
- y-down: y가 클수록 아래 → 화면 UI, 텍스처, 이미지
- 언리얼: 월드 = Z-up (y-up 계열), UI = y-down
- 변환:
y_world = ScreenHeight - y_screen
문제 1 — 좌표계와 벡터의 이해
조건: 2D 게임에서 캐릭터 A가 (1, 5)에, 적 B가 (4, 1)에 있다.
Q1. A에서 B를 향하는 벡터의 방향
1
2
3
| // 방향 벡터 = B - A = (4-1, 1-5) = (3, -4)
// x가 양수(우), y가 음수(하)
// 답: 우하향
|
Q2. y-up 좌표계에서 B는 A보다 위? 아래?
1
2
3
4
| // y-up 좌표계: y값이 클수록 위
// A의 y = 5, B의 y = 1
// B의 y < A의 y → B는 A보다 아래
// 답: 아래
|
Q3. y-down 좌표계(화면 좌표계)에서 B는 A보다 위? 아래?
1
2
3
4
| // y-down 좌표계(화면 좌표계): y값이 클수록 아래
// A의 y = 5, B의 y = 1
// B의 y < A의 y → B는 A보다 위
// 답: 위
|
| 좌표계 | y값 증가 방향 | 사용처 |
|---|
| y-up (수학/게임 월드) | 위로 ↑ | 언리얼, Unity 월드 좌표 |
| y-down (화면 좌표계) | 아래로 ↓ | UI, 스크린 좌표, DirectX 텍스처 |
문제 2 — 벡터 연산과 정규화
조건: 플레이어 위치 P = (2, 3), 목표 위치 T = (6, 6)
Q1. P에서 T를 향하는 방향 벡터 d
1
2
3
4
| // 방향 벡터 d = T - P = (6-2, 6-3) = (4, 3)
FVector2D P(2, 3);
FVector2D T(6, 6);
FVector2D d = T - P; // (4, 3)
|
Q2. 방향 벡터 d의 크기(길이)
1
2
| // |d| = √(x² + y²) = √(4² + 3²) = √(16 + 9) = √25 = 5
float Length = d.Size(); // 5.0
|
피타고라스 정리: 벡터의 크기 = √(x² + y²)
Q3. 정규화한 단위 벡터
1
2
3
| // 단위 벡터 = d / |d| = (4/5, 3/5) = (0.8, 0.6)
// 단위 벡터의 크기는 항상 1
FVector2D UnitDir = d.GetSafeNormal(); // (0.8, 0.6)
|
정규화(Normalize): 방향은 유지하면서 크기를 1로 만드는 연산
왜 필요? → 방향과 속도를 분리하기 위해. 단위벡터 × 원하는 속도 = 이동량
Q4. 단위 벡터 방향으로 이동 후 새 위치
1
2
3
4
5
6
7
8
9
| // 조건: 속도 10, 시간 간격 dt = 0.5
// 새 위치 = P + 단위벡터 × speed × dt
// = (2, 3) + (0.8, 0.6) × 10 × 0.5
// = (2, 3) + (4, 3)
// = (6, 6)
float Speed = 10.0f;
float dt = 0.5f;
FVector2D NewPos = P + UnitDir * Speed * dt; // (6, 6)
|
이동 공식: NewPosition = CurrentPosition + Direction × Speed × DeltaTime
이 공식이 게임 루프에서 매 프레임 캐릭터를 이동시키는 핵심 공식
문제 3 — 타워 디펜스 사거리 판정
조건: 타워 위치 (0, 0), 사거리 = 10 / 적 3마리: A(6, 8), B(3, 4), C(8, 7)
Q1. 사거리 안에 들어온 적 판별
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // 각 적까지의 거리 = √(x² + y²)
// 적 A: √(6² + 8²) = √(36 + 64) = √100 = 10 → 사거리 이내 ✅
// 적 B: √(3² + 4²) = √(9 + 16) = √25 = 5 → 사거리 이내 ✅
// 적 C: √(8² + 7²) = √(64 + 49) = √113 ≈ 10.63 → 사거리 밖 ❌
float Range = 10.0f;
for (auto& Enemy : Enemies)
{
float Dist = FVector2D::Distance(TowerPos, Enemy.Pos);
if (Dist <= Range)
{
// 공격 가능 대상
}
}
|
거리 판정 최적화: 실무에서는 √ 연산 비용을 줄이기 위해 제곱 비교를 사용한다.
1
2
3
4
5
6
7
| // 최적화된 사거리 판정 (√ 연산 생략)
float RangeSq = Range * Range; // 100
// 적 A: 6² + 8² = 100 <= 100 ✅
// 적 B: 3² + 4² = 25 <= 100 ✅
// 적 C: 8² + 7² = 113 > 100 ❌
if (Enemy.Pos.SizeSquared() <= RangeSq) { ... }
|
Q2. 가장 가까운 적 → 공격 대상
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // 사거리 안의 적: A(거리 10), B(거리 5)
// 가장 가까운 적: B (거리 5)
float MinDist = MAX_FLT;
AEnemy* Target = nullptr;
for (auto& Enemy : InRangeEnemies)
{
float Dist = FVector2D::Distance(TowerPos, Enemy->Pos);
if (Dist < MinDist)
{
MinDist = Dist;
Target = Enemy;
}
}
|
Q3. 타워에서 공격 대상까지의 정규화된 방향 벡터
1
2
3
4
5
6
7
| // 타워(0,0) → B(3,4)
// 방향 벡터 = (3, 4)
// 크기 = √(9 + 16) = √25 = 5
// 단위 벡터 = (3/5, 4/5) = (0.6, 0.8)
FVector2D Dir = (Target->Pos - TowerPos).GetSafeNormal(); // (0.6, 0.8)
// 이 방향으로 투사체 발사!
|
핵심 공식 요약
| 연산 | 공식 | 게임 활용 |
|---|
| 방향 벡터 | d = 목표 - 현재 | 적을 향한 방향, AI 추적 |
| 벡터 크기 | |d| = √(x² + y²) | 거리 계산, 사거리 판정 |
| 정규화 | û = d / |d| | 방향과 속도 분리 |
| 이동 | P’ = P + û × speed × dt | 매 프레임 캐릭터/투사체 이동 |
| 거리 판정 (최적화) | x² + y² ≤ R² | 사거리/충돌 판정 (√ 생략) |
ch3 팀프로젝트 — 04/16 회의 결과
게임 컨셉 확정
| 항목 | 결정 사항 |
|---|
| 장르 | 좀비 슈터 + 뱀파이어 서바이벌 융합 |
| 한 줄 설명 | Space Marine 2 느낌에 뱀서 장르를 섞은 TPS 생존 게임 |
| 핵심 메카닉 | 근접+원거리 전투 / 웨이브 클리어 후 업그레이드 카드 선택 |
| 레퍼런스 | Space Marine 2, Vampire Survivors, Left 4 Dead 2 |
| 카메라 시점 | TPS (3인칭) |
코어 루프
- 순간 루프 (30초): 이동 + 근접/원거리로 좀비 처치 → 생존
- 단기 루프 (5-15분): 웨이브 클리어 → 업그레이드 카드 선택 → 다음 웨이브
- 장기 루프: 강해지는 빌드로 보스까지 클리어 / 다른 업그레이드 조합 시도
게임 필러
- 압도: 수백 마리 좀비 대군에 둘러싸이는 스케일감
- 성장: 웨이브마다 강해지는 업그레이드 빌딩의 재미
- 타격감: 근접+원거리 전투의 묵직한 피드백
MVP 범위
넣는 것 ✅
- 플레이어 이동 + 원거리 사격 / 근접 전투 (기본 콤보 1~2개)
- 좀비 웨이브 5라운드 / 좀비 타입 3종 (일반 / 돌진형 / 탱커형)
- 웨이브 클리어 후 업그레이드 카드 선택 (3장 중 1개)
- 보스 1마리 / 아레나 맵 1개 / 메인 메뉴 + 기본 HUD
절대 안 넣는 것 ❌
- 멀티플레이 / 랜덤 맵 생성 / 스토리 컷씬 / 세이브 시스템