---
title: "벡터 더하기는 화살표를 이어 붙이는 것이다"
description: "걷기, 바람, 넉백처럼 여러 움직임을 한 번에 합치는 방법은 벡터 더하기입니다. 같은 자리끼리 더하면 화살표가 이어 붙고, 숫자를 곱하면 방향은 두고 길이만 바뀝니다."
author: "유니얼"
pubDate: 2026-11-05
category: "game-dev/math"
tags: ["Unity", "기초"]
canonical: https://blog.unialgames.com/posts/math-vector-ops/
---
캐릭터가 오른쪽으로 걷고 있는데 위쪽에서 바람이 불고, 그 순간 적에게 맞아 뒤로 밀려나기까지 합니다.
움직임이 셋인데 캐릭터의 위치는 하나입니다. 셋을 어떻게 합쳐야 할까요.

각 움직임을 벡터 하나로 두고 **더하면** 됩니다.
벡터 더하기는 x 는 x 끼리, y 는 y 끼리 더하는 것이고, 그림으로는 **화살표를 끝에 이어 붙이는 것**입니다.
움직임을 빠르게 하거나 느리게 하는 것은 벡터에 **숫자를 곱해서** 합니다. 방향은 그대로 두고 길이만 바뀝니다.

> **벡터 + 벡터는 같은 자리끼리 더하기이고, 화살표를 이어 붙인 끝이 결과입니다.
> 벡터 × 숫자는 같은 방향으로 길이만 그 배가 됩니다. 음수를 곱하면 방향이 뒤집힙니다.
> 유니티의 이동 코드는 거의 전부 이 두 가지로 되어 있습니다.**

## 더하기는 같은 자리끼리

**벡터**는 방향과 길이만 가진 화살표입니다. (3, 1) 은 "오른쪽으로 3, 위로 1" 입니다.
괄호 안의 숫자 하나하나를 **성분**이라고 부릅니다. (3, 1) 의 x 성분은 3, y 성분은 1 입니다.
두 벡터 a = (3, 1), b = (1, 2) 를 더하면 이렇게 됩니다. x 성분은 x 성분끼리, y 성분은 y 성분끼리 더합니다.

| | x | y |
| --- | --- | --- |
| a | 3 | 1 |
| b | 1 | 2 |
| a + b | 3 + 1 = **4** | 1 + 2 = **3** |

그림으로 보면 a 를 먼저 가고, **a 가 끝난 자리에서** b 를 이어서 간 것입니다.

<x-vec>
<script type="application/json">
{
  "label": "a + b 를 화살표로",
  "view": [-1, -1, 6, 5],
  "steps": [
    {
      "caption": "a = (3, 1). 원점에서 오른쪽으로 3, 위로 1 을 갑니다.",
      "items": [
        { "id": "a", "from": [0, 0], "to": [3, 1], "label": "a (3, 1)", "labelAt": [2, 0.2] }
      ]
    },
    {
      "caption": "a 가 끝난 자리 (3, 1) 에서 b = (1, 2) 를 이어서 갑니다. 오른쪽으로 1, 위로 2.",
      "items": [
        { "id": "a", "from": [0, 0], "to": [3, 1], "label": "a (3, 1)", "labelAt": [2, 0.2] },
        { "id": "b", "from": [3, 1], "to": [4, 3], "color": "accent2", "label": "b (1, 2)", "labelAt": [4.8, 1.8] }
      ]
    },
    {
      "caption": "원점에서 마지막 끝까지 곧장 그은 화살표가 a + b = (4, 3) 입니다. 가로로 3 + 1, 세로로 1 + 2.",
      "items": [
        { "id": "a", "from": [0, 0], "to": [3, 1], "label": "a (3, 1)", "labelAt": [2, 0.2] },
        { "id": "b", "from": [3, 1], "to": [4, 3], "color": "accent2", "label": "b (1, 2)", "labelAt": [4.8, 1.8] },
        { "id": "s", "from": [0, 0], "to": [4, 3], "color": "warn", "label": "a + b (4, 3)", "labelAt": [1.6, 2.6] }
      ]
    },
    {
      "caption": "이번에는 b 를 먼저 가고 a 를 이어 붙였습니다. 끝나는 자리가 똑같이 (4, 3) 입니다. 더하기는 순서를 바꿔도 결과가 같습니다.",
      "items": [
        { "id": "a", "from": [1, 2], "to": [4, 3], "label": "a", "labelAt": [2.4, 3.1] },
        { "id": "b", "from": [0, 0], "to": [1, 2], "color": "accent2", "label": "b", "labelAt": [0.2, 1.4] },
        { "id": "s", "from": [0, 0], "to": [4, 3], "color": "warn", "label": "a + b (4, 3)", "labelAt": [2.8, 1.4] }
      ]
    }
  ]
}
</script>
</x-vec>

왜 이어 붙이는 것과 같은 자리끼리 더하는 것이 같은지는 가로와 세로를 따로 보면 보입니다.
a 로 가로 3 을 가고 b 로 가로 1 을 더 갔으니 가로로는 모두 4 를 간 것입니다. 세로도 1 과 2 를 갔으니 3 입니다.
**화살표를 이어 붙이는 것은 가로로 간 양과 세로로 간 양을 각각 모으는 일**입니다.

## 이동은 위치에 벡터를 계속 더하는 것

**위치**는 원점(좌표의 기준 자리 (0, 0))에서 그 자리까지의 화살표로 볼 수 있습니다. 그래서 위치에 벡터를 더하면 화살표만큼 간 새 위치가 나옵니다.
캐릭터를 움직이는 코드는 매 프레임 이 더하기를 합니다.

걷기가 프레임마다 (1, 0), 바람이 프레임마다 (0, 0.5) 이고, (0, 0) 에서 출발해 세 프레임을 더합니다.
(실제 코드는 프레임 시간까지 곱하지만, 여기서는 더하기만 보려고 한 프레임에 한 번씩 그대로 더합니다.)

<x-vec>
<script type="application/json">
{
  "label": "걷기와 바람을 프레임마다 더하기",
  "view": [-1, -1, 5, 3],
  "dwell": 2.4,
  "steps": [
    {
      "caption": "(0, 0) 에서 출발합니다.",
      "code": "position = (0, 0)",
      "items": [
        { "id": "p", "type": "point", "at": [0, 0], "color": "warn", "label": "(0, 0)", "labelAt": [0, -0.6] }
      ]
    },
    {
      "caption": "첫 프레임. 걷기 (1, 0) 을 더하고, 그 끝에서 바람 (0, 0.5) 를 더합니다. 위치는 (1, 0.5).",
      "code": "position += walk;   // (1, 0)\nposition += wind;   // (1, 0.5)",
      "items": [
        { "id": "w1", "from": [0, 0], "to": [1, 0] },
        { "id": "d1", "from": [1, 0], "to": [1, 0.5], "color": "accent2" },
        { "id": "p", "type": "point", "at": [1, 0.5], "color": "warn", "label": "(1, 0.5)", "labelAt": [1, 1.1] }
      ]
    },
    {
      "caption": "둘째 프레임. 같은 두 벡터를 또 이어 붙입니다. 위치는 (2, 1).",
      "code": "position += walk;   // (2, 0.5)\nposition += wind;   // (2, 1)",
      "items": [
        { "id": "w1", "from": [0, 0], "to": [1, 0] },
        { "id": "d1", "from": [1, 0], "to": [1, 0.5], "color": "accent2" },
        { "id": "w2", "from": [1, 0.5], "to": [2, 0.5] },
        { "id": "d2", "from": [2, 0.5], "to": [2, 1], "color": "accent2" },
        { "id": "p", "type": "point", "at": [2, 1], "color": "warn", "label": "(2, 1)", "labelAt": [2, 1.6] }
      ]
    },
    {
      "caption": "셋째 프레임. 위치는 (3, 1.5). 걷기를 세 번, 바람을 세 번 더했습니다.",
      "code": "position += walk;   // (3, 1)\nposition += wind;   // (3, 1.5)",
      "items": [
        { "id": "w1", "from": [0, 0], "to": [1, 0] },
        { "id": "d1", "from": [1, 0], "to": [1, 0.5], "color": "accent2" },
        { "id": "w2", "from": [1, 0.5], "to": [2, 0.5] },
        { "id": "d2", "from": [2, 0.5], "to": [2, 1], "color": "accent2" },
        { "id": "w3", "from": [2, 1], "to": [3, 1] },
        { "id": "d3", "from": [3, 1], "to": [3, 1.5], "color": "accent2" },
        { "id": "p", "type": "point", "at": [3, 1.5], "color": "warn", "label": "(3, 1.5)", "labelAt": [3, 2.1] }
      ]
    },
    {
      "caption": "걷기와 바람을 먼저 더해 (1, 0.5) 하나로 만들고 세 번 더해도 같은 (3, 1.5) 에 닿습니다. 움직임이 몇 개든 먼저 합쳐서 한 번에 더하면 됩니다.",
      "code": "Vector2 move = walk + wind;   // (1, 0.5)\nposition += move;             // 세 번이면 (3, 1.5)",
      "items": [
        { "id": "w1", "from": [0, 0], "to": [1, 0.5], "color": "warn" },
        { "id": "w2", "from": [1, 0.5], "to": [2, 1], "color": "warn" },
        { "id": "w3", "from": [2, 1], "to": [3, 1.5], "color": "warn" },
        { "id": "p", "type": "point", "at": [3, 1.5], "color": "warn", "label": "(3, 1.5)", "labelAt": [3, 2.1] }
      ]
    }
  ]
}
</script>
</x-vec>

**위치 + 벡터 = 새 위치**라는 것과 **더하는 순서는 상관없다**는 것, 이 두 가지가 합쳐진 결과입니다.
넉백이 들어오면 그 벡터를 하나 더 더하면 됩니다. 움직임마다 코드를 따로 고칠 필요가 없습니다.

## 몇 배 하기는 길이만 바꾼다

벡터에 숫자 하나를 곱하면 **x 와 y 에 똑같이** 곱합니다. 이것을 **스칼라 곱**이라고 부릅니다.
스칼라는 방향 없이 크기만 있는 보통 숫자라는 뜻입니다.

| 곱한 수 | (2, 1) × 수 | 화살표 |
| --- | --- | --- |
| 2 | (4, 2) | 같은 방향, 길이 두 배 |
| 0.5 | (1, 0.5) | 같은 방향, 길이 절반 |
| −1 | (−2, −1) | 길이 그대로, 방향 정반대 |
| 0 | (0, 0) | 길이 0. 방향이 사라짐 |

방향이 그대로인 이유는 **x 와 y 의 비율**에 있습니다. (2, 1) 은 가로 2 칸에 세로 1 칸, 곧 가로로 2 갈 때마다 위로 1 오르는 기울기입니다.
(4, 2) 도 가로 4 에 세로 2 라서 기울기가 같습니다. 두 성분에 같은 수를 곱하니 비율이 깨지지 않습니다.
길이가 두 배인 것은 더하기로 확인됩니다. (2, 1) × 2 는 (2, 1) + (2, 1) 이고, 같은 화살표를 두 번 이어 붙인 것이니 길이도 두 배입니다.

<x-vec>
<script type="application/json">
{
  "label": "(2, 1) 에 숫자를 곱하기",
  "view": [-3, -2, 5, 3],
  "dwell": 2.4,
  "steps": [
    {
      "caption": "× 1. 처음 벡터 (2, 1) 입니다.",
      "code": "dir * 1f    // (2, 1)",
      "items": [ { "id": "v", "from": [0, 0], "to": [2, 1], "label": "(2, 1)" } ]
    },
    {
      "caption": "× 2. x 도 y 도 두 배라서 같은 방향으로 길이만 두 배가 됩니다.",
      "code": "dir * 2f    // (4, 2)",
      "items": [ { "id": "v", "from": [0, 0], "to": [4, 2], "label": "(4, 2)" } ]
    },
    {
      "caption": "× 0.5. 같은 방향으로 길이가 절반입니다.",
      "code": "dir * 0.5f  // (1, 0.5)",
      "items": [ { "id": "v", "from": [0, 0], "to": [1, 0.5], "label": "(1, 0.5)" } ]
    },
    {
      "caption": "× (−1). x 와 y 의 부호가 같이 뒤집혀서 정반대를 가리킵니다. 길이는 그대로입니다.",
      "code": "dir * -1f   // (-2, -1)",
      "items": [ { "id": "v", "from": [0, 0], "to": [-2, -1], "color": "accent2", "label": "(−2, −1)", "labelAt": [-2.2, -1.6] } ]
    },
    {
      "caption": "× 0. 길이가 0 인 벡터 (0, 0) 이 됩니다. 이것을 영벡터라고 부릅니다. 방향 정보가 사라집니다.",
      "code": "dir * 0f    // (0, 0)",
      "items": [ { "id": "v", "from": [0, 0], "to": [0, 0], "color": "warn", "label": "(0, 0)", "labelAt": [0.6, 0.5] } ]
    }
  ]
}
</script>
</x-vec>

그래서 이동 코드는 **방향**과 **빠르기**를 나눠서 들고 있다가 곱합니다.

```csharp
Vector3 direction = Vector3.right;   // 어느 쪽으로
float speed = 4f;                     // 얼마나 빨리

Vector3 velocity = direction * speed; // (4, 0, 0)
```

방향을 바꾸고 싶으면 `direction` 만, 빠르기를 바꾸고 싶으면 `speed` 만 고치면 됩니다.
뒤로 밀려나는 넉백은 바라보는 방향에 음수를 곱해 만듭니다. `transform.forward * -3f` 는 뒤쪽으로 3 입니다.

**빼기**는 몇 배 하기와 더하기를 합친 것입니다. a − b 는 b 에 −1 을 곱해 뒤집은 다음 더한 것, 곧 a + (−1)b 입니다.
그래서 더하기와 달리 **빼는 순서를 바꾸면 결과가 정반대**가 됩니다.

a = (3, 1), b = (1, 2) 면 a − b = (3 − 1, 1 − 2) = (2, −1) 입니다. b 의 자리 (1, 2) 에서 (2, −1) 을 이어 붙이면 (3, 1), 곧 a 에 닿습니다.
**a − b 는 b 에서 a 로 가는 화살표**입니다. 두 위치 사이의 방향을 구할 때 "도착 − 출발" 로 빼는 이유가 이것입니다.

## 두 축을 섞어서 방향을 만든다

캐릭터가 돌아 서 있으면 "캐릭터 기준 앞" 과 "캐릭터 기준 오른쪽" 은 월드의 x, z 축(바닥의 가로와 세로)과 다릅니다.
유니티는 캐릭터 기준의 축을 `transform.right`(캐릭터의 오른쪽) 와 `transform.forward`(캐릭터의 앞) 로 줍니다. 둘 다 길이가 1 인 벡터라서, 몇 배를 하면 그 숫자가 그대로 길이가 됩니다.

키 입력이 오른쪽 1, 앞 2 라면 **오른쪽 축을 1배, 앞 축을 2배 해서 더한 것**이 이동 방향입니다.
캐릭터가 돌아 있어서 오른쪽이 (0.8, −0.6), 앞이 (0.6, 0.8) 이라고 합시다.
위에서 내려다본 그림이라 가로가 x, 세로가 z 입니다.

| | x | z |
| --- | --- | --- |
| 1 × right | 1 × 0.8 = 0.8 | 1 × (−0.6) = −0.6 |
| 2 × forward | 2 × 0.6 = 1.2 | 2 × 0.8 = 1.6 |
| 더하면 | 0.8 + 1.2 = **2.0** | −0.6 + 1.6 = **1.0** |

<x-vec>
<script type="application/json">
{
  "label": "1 × right + 2 × forward",
  "view": [-1, -2, 4, 3],
  "steps": [
    {
      "caption": "돌아 선 캐릭터의 오른쪽 (0.8, −0.6) 과 앞 (0.6, 0.8). 둘 다 길이가 1 입니다.",
      "items": [
        { "id": "r", "from": [0, 0], "to": [0.8, -0.6], "color": "accent2", "label": "right", "labelAt": [1.2, -1] },
        { "id": "f", "from": [0, 0], "to": [0.6, 0.8], "label": "forward", "labelAt": [0.3, 1.3] }
      ]
    },
    {
      "caption": "오른쪽 입력이 1 이라 right 를 1배 합니다. (0.8, −0.6) 그대로입니다.",
      "items": [
        { "id": "r", "from": [0, 0], "to": [0.8, -0.6], "color": "accent2", "label": "1 × right", "labelAt": [1.4, -1] }
      ]
    },
    {
      "caption": "앞 입력이 2 라 forward 를 2배 해 (1.2, 1.6) 을 만들고, right 가 끝난 자리에 이어 붙입니다.",
      "items": [
        { "id": "r", "from": [0, 0], "to": [0.8, -0.6], "color": "accent2", "label": "1 × right", "labelAt": [1.4, -1] },
        { "id": "f", "from": [0.8, -0.6], "to": [2, 1], "label": "2 × forward", "labelAt": [2.6, 0] }
      ]
    },
    {
      "caption": "원점에서 끝까지가 이동 방향 (2, 1) 입니다. 축 두 개를 몇 배씩 섞어서 원하는 방향을 만들었습니다.",
      "items": [
        { "id": "r", "from": [0, 0], "to": [0.8, -0.6], "color": "accent2", "label": "1 × right", "labelAt": [1.4, -1] },
        { "id": "f", "from": [0.8, -0.6], "to": [2, 1], "label": "2 × forward", "labelAt": [2.6, 0] },
        { "id": "m", "from": [0, 0], "to": [2, 1], "color": "warn", "label": "(2, 1)", "labelAt": [1.2, 1.2] }
      ]
    }
  ]
}
</script>
</x-vec>

코드로 쓰면 한 줄입니다.

```csharp
float h = Input.GetAxis("Horizontal");   // 오른쪽 입력
float v = Input.GetAxis("Vertical");     // 앞 입력

// 캐릭터 기준 축 두 개를 입력만큼 섞기
Vector3 move = h * transform.right + v * transform.forward;
```

이렇게 **벡터 몇 개에 숫자를 곱해서 더한 것**을 수학에서는 **선형결합**이라고 부릅니다. 1 × right + 2 × forward 가 선형결합 하나입니다.
서로 다른 쪽을 가리키는 축 두 개만 있으면, 곱하는 두 숫자를 바꿔서 평면 위의 어느 방향이든 만들 수 있습니다. 월드의 (2, 1) 도 사실 2 × (1, 0) + 1 × (0, 1), 곧 x 축과 y 축의 선형결합입니다.

## 유니티에서 쓰는 벡터 연산

| 코드 | 결과 | 뜻 |
| --- | --- | --- |
| `a + b` | 벡터 | 같은 자리끼리 더하기. 이어 붙이기 |
| `a - b` | 벡터 | b 에서 a 로 가는 화살표 |
| `a * 2f` 또는 `2f * a` | 벡터 | 길이 두 배 |
| `a / 2f` | 벡터 | 길이 절반 |
| `-a` | 벡터 | 반대 방향 |
| `a + 1f` | 컴파일 에러 | 벡터에 숫자 하나를 더하는 연산은 없습니다 |
| `Vector3` 끼리 `a * b` | 컴파일 에러 | `Vector3` 에는 벡터끼리 곱하는 `*` 가 없습니다 |
| `Vector3.Scale(a, b)` | 벡터 | 같은 자리끼리 곱하기. (2, 3) 과 (4, 5) 면 (8, 15) |
| `Vector2` 끼리 `a * b` | 벡터 | `Vector2` 는 `*` 가 같은 자리끼리 곱하기입니다. `Scale` 과 같습니다 |

`Vector3` 에 벡터끼리 `*` 가 없는 이유는 "벡터끼리 곱한다" 가 한 가지로 정해지지 않기 때문입니다.
같은 자리끼리 곱하는 것(`Scale`), 곱해서 숫자 하나를 만드는 것(내적, `Vector3.Dot`), 수직인 벡터를 만드는 것(외적, `Vector3.Cross`)이 모두 쓰입니다.
그래서 `Vector3` 는 연산자 하나로 정하지 않고 이름을 붙인 함수로 나눠 두었습니다. `Vector2` 의 `*` 는 그중 같은 자리끼리 곱하기를 고른 것이라, 헷갈리지 않게 `Scale` 을 쓰는 편이 읽기 쉽습니다.

## 조심할 것

- **섞으면 길이가 변합니다.** 앞 입력 1, 오른쪽 입력 1 을 섞으면 `right + forward` 의 길이는 1 이 아니라 √2 ≈ 1.41 입니다.
  right 와 forward 는 서로 수직이고 길이가 1 이라, 이어 붙이면 두 변이 1 과 1 인 직각삼각형(한 각이 90° 인 삼각형)이 됩니다.
  직각삼각형의 비스듬한 변(빗변) 길이는 피타고라스의 정리로 √(가로² + 세로²) 입니다. ² 는 같은 수를 두 번 곱하기, √ 는 두 번 곱해 그 수가 되는 수입니다.
  그래서 √(1 × 1 + 1 × 1) = √2 ≈ 1.41 입니다(1.41 × 1.41 ≈ 2).
  대각선으로 갈 때만 41% 빨라지는 이유가 이것이고, 길이를 1 로 되돌리는 정규화(`normalized`)로 막습니다
- **0 을 곱하면 방향이 사라집니다.** 영벡터 (0, 0) 에서는 "어느 쪽" 을 되찾을 수 없습니다.
  입력이 없을 때 이동 방향으로 캐릭터를 돌리는 코드는 영벡터인지 먼저 확인해야 합니다
- **위치 + 위치는 뜻이 없습니다.** 더해서 의미가 있는 것은 위치 + 벡터, 벡터 + 벡터입니다

## 정리

- 벡터 더하기는 x 는 x 끼리, y 는 y 끼리 더하는 것이고, 그림으로는 화살표를 끝에 이어 붙이는 것입니다
- 더하는 순서는 상관없습니다. 빼는 순서는 결과를 뒤집습니다
- 이동은 위치에 벡터를 계속 더하는 것입니다. 움직임이 여러 개면 먼저 더해서 한 번에 더해도 같습니다
- 숫자를 곱하면 방향은 두고 길이만 그 배가 됩니다. 음수는 방향을 뒤집고, 0 은 영벡터를 만듭니다
- 이동 코드는 방향(길이 1 인 벡터)과 빠르기(숫자)를 따로 들고 곱합니다
- `h * transform.right + v * transform.forward` 처럼 축을 몇 배씩 섞는 것을 선형결합이라고 부릅니다
- `Vector3` 끼리는 `*` 가 없습니다. 같은 자리끼리 곱하기는 `Vector3.Scale` 입니다