로컬 좌표와 월드 좌표
캐릭터 오른쪽 앞에 무기를 붙이려고 weapon.position = new Vector3(1, 0, 1) 이라고 썼더니, 무기가 캐릭터 옆이 아니라 맵 원점 근처에 생깁니다. 원점은 좌표를 재기 시작하는 자리로, 월드에서는 (0, 0, 0) 입니다.
어느 축으로 잰 숫자인가의 문제입니다. 축은 숫자를 재는 방향으로, 오른쪽 · 위 · 앞을 가리키는 길이 1 인 화살표 세 개입니다. 월드 원점과 월드 축으로 잰 숫자가 월드 좌표이고, 오브젝트의 위치를 원점으로, 오브젝트의 오른쪽 · 위 · 앞을 축으로 잰 숫자가 로컬 좌표입니다. 같은 (1, 0, 1) 이라도 읽는 기준에 따라 다른 자리입니다.
로컬 (a, b) 의 월드 자리는
오브젝트 위치 + a × 오른쪽 축 + b × 위 축입니다.TransformPoint가 이 계산입니다. 거꾸로 월드 자리의 로컬 숫자는, 위치를 뺀 뒤 각 축과 내적해서 구합니다.InverseTransformPoint가 이 계산입니다.
좌표 숫자는 축을 몇 개씩 섞을지다
벡터 (2, 1) 은 “x 축 방향으로 2, y 축 방향으로 1” 입니다. 곧 2 × x 축 + 1 × y 축 입니다.
월드 축은 Vector3.right (1, 0, 0), Vector3.up (0, 1, 0), Vector3.forward (0, 0, 1) 로 늘 같습니다.
오브젝트도 자기 축을 들고 있습니다. transform.right, transform.up, transform.forward 입니다.
오브젝트가 돌면 이 세 축도 같이 돕니다. 셋 다 길이가 1 이고 서로 수직, 곧 직각(90°)으로 만납니다. 원점 하나와 이렇게 만나는 축 세트를 묶은 것을 좌표계, 그 축 세트를 기저라고 부릅니다. 이 세 축이 오브젝트의 기저입니다.
| 원점 | 축 | 오브젝트가 돌면 | |
|---|---|---|---|
| 월드 | (0, 0, 0) | Vector3.right · up · forward |
그대로 |
| 로컬 | 오브젝트의 위치 | transform.right · up · forward |
같이 돈다 |
로컬에서 월드로: 축을 섞고 위치를 더한다
2D 로 줄인 예입니다. 오브젝트가 (3, 1) 에 있고, 조금 돌아 서 있어서 오른쪽 축이 (0.8, 0.6), 위 축이 (−0.6, 0.8) 입니다.
길이는 가로와 세로를 각각 제곱해 더한 뒤 √ 를 씌워 잽니다(피타고라스). (0.8, 0.6) 은 √(0.64 + 0.36) = √1 = 1 이라 길이 1 이고, (−0.6, 0.8) 도 같습니다. 두 축이 수직인지는 내적, 곧 x 끼리, y 끼리 곱해서 더한 숫자로 확인합니다. 수직인 두 방향은 내적이 0 입니다. 0.8 × (−0.6) + 0.6 × 0.8 = 0 이라 서로 수직입니다.
이 오브젝트 기준의 로컬 점 (2, 1) 은 월드의 어디일까요.
| 단계 | 계산 | 결과 |
|---|---|---|
| 1. 오른쪽 축 2 개 | 2 × (0.8, 0.6) | (1.6, 1.2) |
| 2. 위 축 1 개 | 1 × (−0.6, 0.8) | (−0.6, 0.8) |
| 3. 둘을 더함 | (1.6 − 0.6, 1.2 + 0.8) | (1.0, 2.0) |
| 4. 위치를 더함 | (3 + 1.0, 1 + 2.0) | (4, 3) |
이 계산은 로컬 숫자만큼 오브젝트의 축을 섞고, 오브젝트 위치를 더한 것입니다.
transform.TransformPoint(local) 이 그대로 이 일을 합니다. 오브젝트의 Scale 이 1 이 아니면 로컬 숫자에 Scale 을 먼저 곱합니다. Scale 이 (2, 2) 면 로컬 (2, 1) 은 오른쪽 축 4 칸, 위 축 2 칸입니다.
무기를 캐릭터 오른쪽 앞에 붙이려던 코드는 이렇게 고칩니다.
// 캐릭터 기준 오른쪽 1, 앞 1
Vector3 local = new Vector3(1f, 0f, 1f);
weapon.position = character.TransformPoint(local);
월드에서 로컬로: 위치를 빼고 축과 내적한다
반대 방향도 자주 필요합니다. “이 적이 내 기준으로 앞에 있나, 오른쪽에 있나” 를 숫자로 알고 싶을 때입니다. 앞의 월드 (4, 3) 을 오브젝트 기준 숫자로 되돌리면 다시 (2, 1) 이 나와야 합니다.
길이 1 인 축과의 내적은 그 축 위에 내린 그림자의 길이입니다. 축이 서로 수직이라서, 각 축과의 내적이 곧 그 축으로 몇 칸인지가 됩니다. (1, 2) 는 2 × right + 1 × up 입니다. 이것을 right 와 내적하면 2 × (right · right) + 1 × (up · right) = 2 × 1 + 1 × 0 = 2 입니다. 길이 1 인 축은 자기와의 내적이 1 이고, 서로 수직인 축끼리의 내적은 0 이라 다른 축의 몫이 지워지고 그 축의 몫만 남습니다.
| 단계 | 계산 | 결과 |
|---|---|---|
| 1. 위치를 뺌 | (4, 3) − (3, 1) | (1, 2) |
| 2. 오른쪽 축과 내적 | 1 × 0.8 + 2 × 0.6 | 2 |
| 3. 위 축과 내적 | 1 × (−0.6) + 2 × 0.8 | 1 |
transform.InverseTransformPoint(world) 가 이 계산입니다. Scale 이 1 이 아니면 내적한 값을 마지막에 Scale 로 나눕니다.
Vector3 local = transform.InverseTransformPoint(enemy.position);
// local.x > 0 이면 내 오른쪽, local.z > 0 이면 내 앞
bool isFrontRight = local.x > 0f && local.z > 0f;
로컬 좌표로 바꾸면 앞뒤(z), 좌우(x), 위아래(y)가 부호로 바로 보입니다. 축 하나하나와 따로 내적해서 판정하던 것을 한 번에 하는 셈입니다.
점 · 방향 · 벡터를 옮기는 함수가 셋인 이유
로컬에서 월드로 바꾸는 함수는 셋입니다. 위치를 더하는지, 크기를 곱하는지가 다릅니다.
| 함수 | 위치를 더함 | 크기를 곱함 | 넣는 것 |
|---|---|---|---|
TransformPoint |
예 | 예 | 자리. 무기를 붙일 곳 |
TransformVector |
아니요 | 예 | 크기를 따라야 하는 길이 |
TransformDirection |
아니요 | 아니요 | 방향. 총알이 날아갈 쪽 |
방향에는 시작점이 없으니 위치를 더하지 않고, 길이 1 인 방향이 커지면 안 되니 크기도 곱하지 않습니다. 셋 다 회전은 적용합니다.
(1, 1) 에 있고 돌지 않았으며 Scale 이 2 인 오브젝트에 로컬 (1, 1) 을 넣으면 셋의 결과가 다릅니다. TransformPoint 는 (3, 3), TransformVector 는 (2, 2), TransformDirection 은 (1, 1) 입니다.
되돌리는 쪽도 InverseTransformPoint, InverseTransformVector, InverseTransformDirection 셋이 짝을 이룹니다.
position 과 localPosition, Translate 와 +=
같은 생각이 Transform 의 다른 곳에도 그대로 있습니다. 아래에 나오는 부모와 자식은 Hierarchy 관계입니다. 다른 오브젝트 아래에 놓인 오브젝트가 자식, 그 위의 것이 부모이고, 부모가 움직이면 자식이 따라 움직입니다.
| 코드 | 기준 |
|---|---|
transform.position |
월드 |
transform.localPosition |
부모 기준 로컬. 부모가 없으면 position 과 같음 |
transform.forward |
월드로 나타낸 내 앞 방향 |
Vector3.forward |
월드의 앞 (0, 0, 1). 내가 어디를 보든 같음 |
transform.Translate(v) |
기본이 나 기준(Space.Self). v 를 내 축으로 섞어서 감 |
transform.Translate(v, Space.World) |
월드 기준 |
transform.position += v |
월드 기준 |
그래서 transform.Translate(Vector3.forward) 는 내가 보는 쪽으로 갑니다.
transform.position += Vector3.forward 는 내가 어디를 보든 늘 월드 앞쪽으로 갑니다.
transform.position += transform.forward 라고 쓰면 Translate 와 같은 결과입니다. transform.forward 가 이미 내 앞을 월드 숫자로 바꿔 둔 값이기 때문입니다.
localPosition 의 기준은 자기 자신이 아니라 부모라는 점도 헷갈리기 쉽습니다.
자식의 localPosition (1, 0, 0) 은 “부모의 오른쪽 축으로 1” 이고, 부모의 Scale 이 2 면 월드로는 2 칸 떨어집니다.
정리
- 좌표 숫자는 축을 몇 개씩 섞을지입니다. 월드는 월드 축으로, 로컬은 오브젝트의 축(기저)으로 잽니다
- 로컬 → 월드는
위치 + x × right + y × up + z × forward입니다(크기가 있으면 먼저 곱함).TransformPoint - 월드 → 로컬은 위치를 빼고 각 축과 내적합니다. 축이 서로 수직이고 길이 1 이라 내적이 곧 칸 수입니다.
InverseTransformPoint - 로컬 좌표로 바꾸면 앞뒤 · 좌우 · 위아래가 z · x · y 의 부호로 보입니다
- 점은
TransformPoint, 크기를 따르는 길이는TransformVector, 방향은TransformDirection으로 옮깁니다 Translate는 기본이 나 기준,position +=는 월드 기준입니다.localPosition은 부모 기준입니다