---
title: "대각선으로 가면 빨라지는 이유"
description: "W 와 D 를 같이 누르면 캐릭터가 41% 빨라집니다. 입력 (1, 1) 의 길이가 1 이 아니라 √2 라서입니다. 벡터의 길이를 재는 법과, 길이를 1 로 맞추는 정규화를 언제 쓰고 언제 쓰지 않는지 봅니다."
author: "유니얼"
pubDate: 2026-11-05
category: "game-dev/math"
tags: ["Unity", "기초"]
canonical: https://blog.unialgames.com/posts/math-normalize/
---
키보드로 캐릭터를 움직여 보면, W 만 누를 때보다 W 와 D 를 같이 누를 때 캐릭터가 눈에 띄게 빨리 갑니다.
대각선으로 달리는 쪽이 41% 빠릅니다. 대각선 이동만 쓰는 사람이 이기는 게임이 됩니다.

원인은 입력 벡터의 **길이**입니다. **벡터**는 방향과 길이를 가진 화살표이고, (3, 4) 는 "오른쪽으로 3, 위로 4" 입니다.
앞만 누르면 입력이 (0, 1) 이고 길이가 1 입니다.
앞과 오른쪽을 같이 누르면 (1, 1) 인데, 이 화살표의 길이는 1 이 아니라 √2 ≈ 1.41 입니다. √2 는 두 번 곱해서 2 가 되는 수입니다.
같은 빠르기를 곱하면 대각선만 1.41배로 갑니다.

> **벡터의 길이는 피타고라스로 √(x² + y²) 입니다.
> 길이로 각 자리를 나누면 방향은 그대로이고 길이만 1 인 벡터가 됩니다. 이것을 정규화라고 부릅니다.
> 이동 방향은 정규화한 뒤에 빠르기를 곱해야 어느 쪽으로 가든 빠르기가 같습니다.**

## 벡터의 길이는 피타고라스로 잰다

(3, 4) 를 그리면 가로로 3, 세로로 4 를 간 화살표입니다. 화살표 끝까지 곧장 잰 길이가 벡터의 길이입니다.
가로와 세로는 직각(90°)으로 만나니, 화살표는 **직각삼각형**(한 각이 90° 인 삼각형)의 **빗변**이 됩니다. 빗변은 직각 맞은편의 비스듬한 변입니다.

빗변의 길이는 **피타고라스의 정리**로 구합니다. 각 변 위에 정사각형을 세우면, **빗변 위 정사각형의 넓이가 나머지 두 정사각형 넓이의 합과 같다**는 정리입니다.
정사각형의 넓이는 한 변 × 한 변 입니다. 같은 수를 두 번 곱하는 것을 **제곱**이라 하고 3² = 3 × 3 = 9 처럼 씁니다. 그래서 식으로는 빗변² = 가로² + 세로² 입니다.
빗변² 에서 빗변으로 돌아갈 때는 **제곱근**(√, 루트)을 씁니다. 두 번 곱해서 그 수가 되는 수이고, 5 × 5 = 25 라서 √25 = 5 입니다.

<x-vec>
<script type="application/json">
{
  "label": "(3, 4) 의 길이 재기",
  "view": [-5, -4, 8, 8],
  "steps": [
    {
      "caption": "벡터 (3, 4).",
      "items": [ { "id": "v", "from": [0, 0], "to": [3, 4], "label": "(3, 4)", "labelAt": [3.6, 4.5] } ]
    },
    {
      "caption": "가로로 3, 세로로 4 를 갔습니다. 둘이 직각으로 만나서 화살표는 직각삼각형의 빗변입니다.",
      "items": [
        { "id": "v", "from": [0, 0], "to": [3, 4], "label": "(3, 4)", "labelAt": [3.6, 4.5] },
        { "id": "h", "type": "line", "from": [0, 0], "to": [3, 0], "color": "muted", "label": "3", "labelAt": [1.5, 0.4] },
        { "id": "u", "type": "line", "from": [3, 0], "to": [3, 4], "color": "muted", "label": "4", "labelAt": [2.6, 1.2] }
      ]
    },
    {
      "caption": "가로 위에 한 변 3 인 정사각형, 세로 옆에 한 변 4 인 정사각형을 세웁니다. 넓이는 3 × 3 = 9 와 4 × 4 = 16 입니다.",
      "items": [
        { "id": "v", "from": [0, 0], "to": [3, 4], "label": "(3, 4)", "labelAt": [3.6, 4.5] },
        { "id": "h", "type": "line", "from": [0, 0], "to": [3, 0], "color": "muted", "label": "3", "labelAt": [1.5, 0.4] },
        { "id": "u", "type": "line", "from": [3, 0], "to": [3, 4], "color": "muted", "label": "4", "labelAt": [2.6, 1.2] },
        { "id": "a1", "type": "line", "from": [0, 0], "to": [0, -3], "color": "accent2" },
        { "id": "a2", "type": "line", "from": [0, -3], "to": [3, -3], "color": "accent2" },
        { "id": "a3", "type": "line", "from": [3, -3], "to": [3, 0], "color": "accent2" },
        { "id": "at", "type": "text", "at": [1.5, -1.5], "color": "text", "label": "9" },
        { "id": "b1", "type": "line", "from": [3, 0], "to": [7, 0], "color": "accent2" },
        { "id": "b2", "type": "line", "from": [7, 0], "to": [7, 4], "color": "accent2" },
        { "id": "b3", "type": "line", "from": [7, 4], "to": [3, 4], "color": "accent2" },
        { "id": "bt", "type": "text", "at": [5, 2], "color": "text", "label": "16" }
      ]
    },
    {
      "caption": "빗변 위에도 정사각형을 세웁니다. 이 넓이가 9 + 16 = 25 와 같다는 것이 피타고라스의 정리입니다.",
      "items": [
        { "id": "v", "from": [0, 0], "to": [3, 4], "label": "(3, 4)", "labelAt": [3.6, 4.5] },
        { "id": "h", "type": "line", "from": [0, 0], "to": [3, 0], "color": "muted", "label": "3", "labelAt": [1.5, 0.4] },
        { "id": "u", "type": "line", "from": [3, 0], "to": [3, 4], "color": "muted", "label": "4", "labelAt": [2.6, 1.2] },
        { "id": "a1", "type": "line", "from": [0, 0], "to": [0, -3], "color": "accent2" },
        { "id": "a2", "type": "line", "from": [0, -3], "to": [3, -3], "color": "accent2" },
        { "id": "a3", "type": "line", "from": [3, -3], "to": [3, 0], "color": "accent2" },
        { "id": "at", "type": "text", "at": [1.5, -1.5], "color": "text", "label": "9" },
        { "id": "b1", "type": "line", "from": [3, 0], "to": [7, 0], "color": "accent2" },
        { "id": "b2", "type": "line", "from": [7, 0], "to": [7, 4], "color": "accent2" },
        { "id": "b3", "type": "line", "from": [7, 4], "to": [3, 4], "color": "accent2" },
        { "id": "bt", "type": "text", "at": [5, 2], "color": "text", "label": "16" },
        { "id": "c1", "type": "line", "from": [3, 4], "to": [-1, 7], "color": "warn" },
        { "id": "c2", "type": "line", "from": [-1, 7], "to": [-4, 3], "color": "warn" },
        { "id": "c3", "type": "line", "from": [-4, 3], "to": [0, 0], "color": "warn" },
        { "id": "ct", "type": "text", "at": [-0.5, 3.5], "color": "text", "label": "25" }
      ]
    },
    {
      "caption": "빗변² = 25 이니 빗변은 두 번 곱해 25 가 되는 수, √25 = 5 입니다. 유니티의 magnitude 가 이 계산을 합니다.",
      "code": "new Vector2(3, 4).magnitude      // 5\nnew Vector2(3, 4).sqrMagnitude   // 25 (루트를 씌우기 전)",
      "items": [
        { "id": "v", "from": [0, 0], "to": [3, 4], "color": "warn", "label": "√(9 + 16) = 5", "labelAt": [-0.2, 2.6] },
        { "id": "h", "type": "line", "from": [0, 0], "to": [3, 0], "color": "muted", "label": "3", "labelAt": [1.5, 0.4] },
        { "id": "u", "type": "line", "from": [3, 0], "to": [3, 4], "color": "muted", "label": "4", "labelAt": [2.6, 1.2] }
      ]
    }
  ]
}
</script>
</x-vec>

3차원이면 z 하나가 더 붙을 뿐입니다. (x, y, z) 의 길이는 √(x² + y² + z²) 이고, (1, 2, 2) 는 √(1 + 4 + 4) = √9 = 3 입니다.

| 유니티 | 값 | (3, 4) 일 때 |
| --- | --- | --- |
| `v.magnitude` | √(x² + y²) | 5 |
| `v.sqrMagnitude` | x² + y² (루트를 안 씌운 값) | 25 |

## 대각선 입력은 길이가 1 이 아니다

키보드 입력을 방향 벡터로 바꾸면 이렇게 됩니다.

| 누른 키 | 입력 | 길이 | 빠르기 5 를 곱하면 1초에 |
| --- | --- | --- | --- |
| W | (0, 1) | √(0 + 1) = **1** | 5 |
| D | (1, 0) | √(1 + 0) = **1** | 5 |
| W + D | (1, 1) | √(1 + 1) = √2 ≈ **1.41** | 7.07 |

여덟 방향 입력을 원점에서 그리면 대각선 넷만 길이 1 인 점선 원 밖으로 나갑니다.

<x-vec>
<script type="application/json">
{
  "label": "여덟 방향 입력의 길이",
  "view": [-2, -2, 2, 2],
  "steps": [
    {
      "caption": "위아래, 좌우만 누르면 화살표 끝이 길이 1 인 원 위에 있습니다.",
      "items": [
        { "id": "c", "type": "circle", "at": [0, 0], "r": 1, "color": "muted" },
        { "id": "e", "from": [0, 0], "to": [1, 0] },
        { "id": "n", "from": [0, 0], "to": [0, 1] },
        { "id": "w", "from": [0, 0], "to": [-1, 0] },
        { "id": "s", "from": [0, 0], "to": [0, -1] }
      ]
    },
    {
      "caption": "두 키를 같이 누른 대각선 넷은 (±1, ±1) 이라 원 밖으로 나갑니다. 끝이 원이 아니라 네모를 그립니다. 길이는 √2 ≈ 1.41 입니다.",
      "items": [
        { "id": "c", "type": "circle", "at": [0, 0], "r": 1, "color": "muted" },
        { "id": "e", "from": [0, 0], "to": [1, 0] },
        { "id": "n", "from": [0, 0], "to": [0, 1] },
        { "id": "w", "from": [0, 0], "to": [-1, 0] },
        { "id": "s", "from": [0, 0], "to": [0, -1] },
        { "id": "ne", "from": [0, 0], "to": [1, 1], "color": "warn", "label": "1.41", "labelAt": [1.35, 1.35] },
        { "id": "nw", "from": [0, 0], "to": [-1, 1], "color": "warn" },
        { "id": "sw", "from": [0, 0], "to": [-1, -1], "color": "warn" },
        { "id": "se", "from": [0, 0], "to": [1, -1], "color": "warn" }
      ]
    },
    {
      "caption": "대각선을 길이 1.41 로 나누면 (0.71, 0.71) 이 되어 원 위로 들어옵니다. 방향은 그대로이고 길이만 1 이 됐습니다. 이제 여덟 방향 모두 같은 빠르기로 갑니다.",
      "code": "new Vector2(1, 1).normalized   // (0.71, 0.71)",
      "items": [
        { "id": "c", "type": "circle", "at": [0, 0], "r": 1, "color": "muted" },
        { "id": "e", "from": [0, 0], "to": [1, 0] },
        { "id": "n", "from": [0, 0], "to": [0, 1] },
        { "id": "w", "from": [0, 0], "to": [-1, 0] },
        { "id": "s", "from": [0, 0], "to": [0, -1] },
        { "id": "ne", "from": [0, 0], "to": [0.71, 0.71], "color": "accent2", "label": "1", "labelAt": [0.95, 0.95] },
        { "id": "nw", "from": [0, 0], "to": [-0.71, 0.71], "color": "accent2" },
        { "id": "sw", "from": [0, 0], "to": [-0.71, -0.71], "color": "accent2" },
        { "id": "se", "from": [0, 0], "to": [0.71, -0.71], "color": "accent2" }
      ]
    }
  ]
}
</script>
</x-vec>

빠르기는 **방향 벡터의 길이 × 빠르기** 로 정해집니다. 방향 벡터의 길이가 방향마다 다르면 빠르기도 방향마다 달라집니다.
그래서 방향을 나타내는 벡터는 길이를 1 로 맞춰 두고 쓰는 것이 약속입니다. 길이가 1 인 벡터를 **단위 벡터**라고 부릅니다.
길이가 1 이면 빠르기 5 를 곱한 결과의 길이가 정확히 5 라서, 곱한 숫자가 곧 빠르기가 됩니다.
`transform.forward`, `Vector3.right` 같은 값은 처음부터 단위 벡터입니다.

## 정규화: 길이로 나눠서 1 로 만든다

길이를 1 로 만들려면 **각 자리를 그 벡터의 길이로 나누면** 됩니다.
x 와 y 를 같은 수로 나누니 비율이 그대로라서 방향은 바뀌지 않습니다.

(3, 4) 는 길이가 5 입니다.

| 단계 | 계산 | 결과 |
| --- | --- | --- |
| 1. 길이 재기 | √(3² + 4²) | 5 |
| 2. 길이로 나누기 | (3 ÷ 5, 4 ÷ 5) | (0.6, 0.8) |
| 3. 길이 확인 | √(0.6² + 0.8²) = √(0.36 + 0.64) | **1** |
| 4. 빠르기 2 곱하기 | (0.6 × 2, 0.8 × 2) | (1.2, 1.6), 길이 2 |

<x-vec>
<script type="application/json">
{
  "label": "(3, 4) 를 정규화하고 빠르기를 곱하기",
  "view": [-1, -1, 5, 5],
  "steps": [
    {
      "caption": "(3, 4). 길이 5 인 화살표입니다.",
      "items": [
        { "id": "c", "type": "circle", "at": [0, 0], "r": 1, "color": "muted" },
        { "id": "v", "from": [0, 0], "to": [3, 4], "label": "(3, 4) 길이 5", "labelAt": [3.6, 4.5] }
      ]
    },
    {
      "caption": "x 와 y 를 둘 다 5 로 나눕니다. (0.6, 0.8). 같은 방향 그대로 줄어들어 길이 1 인 원에 닿습니다.",
      "code": "Vector2 dir = new Vector2(3, 4).normalized;   // (0.6, 0.8)",
      "items": [
        { "id": "c", "type": "circle", "at": [0, 0], "r": 1, "color": "muted" },
        { "id": "v", "from": [0, 0], "to": [0.6, 0.8], "color": "accent2", "label": "(0.6, 0.8) 길이 1", "labelAt": [2.2, 1.2] }
      ]
    },
    {
      "caption": "길이가 1 이니 빠르기를 곱하면 그 숫자가 그대로 길이가 됩니다. 2 를 곱해 (1.2, 1.6), 길이 2.",
      "code": "Vector2 velocity = dir * 2f;                  // (1.2, 1.6)",
      "items": [
        { "id": "c", "type": "circle", "at": [0, 0], "r": 1, "color": "muted" },
        { "id": "v", "from": [0, 0], "to": [1.2, 1.6], "color": "warn", "label": "(1.2, 1.6) 길이 2", "labelAt": [2.8, 1.9] }
      ]
    }
  ]
}
</script>
</x-vec>

이동 코드에 넣으면 이렇습니다.

```csharp
// GetAxisRaw 는 키를 누르면 -1, 0, 1 중 하나를 그대로 돌려준다
// 그래서 W 와 D 를 같이 누르면 (1, 0, 1) 이 된다
Vector3 input = new Vector3(
    Input.GetAxisRaw("Horizontal"),
    0f,
    Input.GetAxisRaw("Vertical"));

// 대각선이어도 길이 1
Vector3 direction = input.normalized;

// Time.deltaTime 은 지난 프레임이 걸린 초. 곱하면 1초에 speed 만큼 간다
transform.position += direction * speed * Time.deltaTime;
```

유니티에는 정규화가 두 가지 있습니다.

| 코드 | 하는 일 |
| --- | --- |
| `v.normalized` | 길이 1 인 **새 벡터**를 돌려줍니다. v 는 그대로입니다 |
| `v.Normalize()` | v **자기 자신**을 길이 1 로 바꿉니다 |

## 스틱 입력은 정규화하면 안 된다

게임패드 스틱은 조금 기울이면 입력이 작게 들어옵니다. 살짝 기울여 (0.3, 0.4) 가 들어왔다면 길이는 0.5 입니다.
"천천히 걷고 싶다" 는 뜻인데, 이것을 정규화하면 (0.6, 0.8) 로 길이가 1 이 되어 **늘 전속력**이 됩니다.

이때는 길이가 1 을 넘을 때만 1 로 줄이는 `Vector3.ClampMagnitude` 를 씁니다.

| 입력 | 길이 | `normalized` | `ClampMagnitude(v, 1)` |
| --- | --- | --- | --- |
| (0.3, 0.4) 살짝 | 0.5 | (0.6, 0.8), 길이 1 | 그대로, 길이 0.5 |
| (1, 1) 키보드 대각선 | 1.41 | (0.71, 0.71), 길이 1 | (0.71, 0.71), 길이 1 |
| (0, 1) 앞 | 1 | 그대로 | 그대로 |

정리하면 **방향만 필요하면 `normalized`, 세기도 살려야 하면 `ClampMagnitude`** 입니다.
적을 향해 쏘는 총알처럼 길이가 거리라서 의미 없는 경우는 정규화하고, 스틱처럼 길이가 세기인 경우는 길이를 남깁니다.

정규화로 얻는 것과 치르는 것을 나란히 두면 이렇습니다.

| | 내용 |
| --- | --- |
| 얻는 것 | 어느 방향이든 길이가 1 이라 빠르기가 방향마다 같아집니다 |
| 잃는 것 | 원래 길이. 스틱의 세기, 적까지의 거리가 사라집니다 |
| 비용 | 루트 한 번과 자리(x, y, z) 수만큼의 나눗셈. 매 프레임 수천 번이 아니면 신경 쓸 정도는 아닙니다 |
| 안 되는 경우 | 길이가 0 인 벡터 |

## 영벡터는 정규화할 수 없다

아무 키도 안 누르면 입력은 (0, 0) 입니다. 길이가 0 이라서 0 으로 나눠야 하는데, 0 으로는 나눌 수 없습니다.
(0, 0) 에는 처음부터 방향이 없으니 "길이 1 인 같은 방향" 도 없습니다.

유니티는 이때 오류를 내지 않고 **(0, 0, 0) 을 그대로 돌려줍니다.** 길이가 아주 작은 벡터도 마찬가지입니다.
움직일 때는 문제가 없지만, 이동 방향으로 캐릭터를 돌리는 코드는 입력이 없을 때 이상한 방향으로 돌 수 있습니다.
방향이 필요한 코드는 길이가 있는지 먼저 확인합니다.

```csharp
// 입력이 거의 없으면 방향을 바꾸지 않는다
if (input.sqrMagnitude > 0.0001f)
{
    transform.forward = input.normalized;
}
```

## 거리를 비교할 때는 sqrMagnitude

공격 범위 안에 적이 있는지 볼 때는 거리를 재서 범위와 비교합니다.
이때 실제 거리 값이 필요한 게 아니라 **어느 쪽이 큰지만** 알면 됩니다.

거리가 둘 다 0 보다 크면, 제곱해도 크고 작은 순서가 바뀌지 않습니다. 거리 4 와 5 를 비교하는 것과 16 과 25 를 비교하는 것은 답이 같습니다.
그래서 루트를 씌우지 않은 `sqrMagnitude` 를 범위의 제곱과 비교하면 루트 계산을 건너뛸 수 있습니다.
루트는 곱하기나 더하기보다 오래 걸리는 계산이라, 적 수백 마리와의 거리를 매 프레임 재는 코드라면 이 차이가 쌓입니다. 유니티 문서도 거리 비교에는 `sqrMagnitude` 를 쓰라고 권합니다.

```csharp
float range = 5f;
Vector3 toEnemy = enemy.position - transform.position;

// toEnemy.magnitude < range 와 같은 판정
if (toEnemy.sqrMagnitude < range * range)
{
    Attack();
}
```

적이 (3, 4) 만큼 떨어져 있으면 `sqrMagnitude` 는 25, 범위 5 의 제곱도 25 라서 딱 경계입니다.
**범위 쪽도 제곱해야 한다**는 것을 빠뜨리기 쉽습니다. `sqrMagnitude < 5` 로 쓰면 실제로는 거리 √5 ≈ 2.2 안쪽만 잡힙니다.

## 정리

- 벡터의 길이는 피타고라스로 √(x² + y²) 입니다. 3차원이면 z² 가 더 붙습니다
- 키보드 대각선 입력 (1, 1) 의 길이는 √2 ≈ 1.41 이라 같은 빠르기를 곱하면 41% 빨라집니다
- 각 자리를 길이로 나누는 것이 정규화입니다. 방향은 그대로, 길이는 1 이 됩니다
- 방향 벡터는 길이 1 로 맞춰 두고 빠르기를 곱합니다. 그래야 빠르기가 곧 길이가 됩니다
- 스틱처럼 길이가 세기인 입력은 정규화하지 말고 `ClampMagnitude(v, 1)` 로 1 만 넘지 않게 합니다
- 영벡터는 정규화할 수 없습니다. 유니티는 (0, 0, 0) 을 돌려주니 방향이 필요한 코드는 길이부터 확인합니다
- 거리 비교는 `sqrMagnitude < 범위 * 범위` 로 루트를 건너뜁니다