정신과 시간의 방
작성일
2023. 2. 23. 22:09
작성자
risehyun
컴퓨터 게임에서는 화면 속 플레이어를 움직이게 된다. 이 화면은 평면, 즉 2차원으로 구성되어 있다.
이번 장에서는 이러한 2차원을 다루기 위한 최소한의 개념들을 배웠다.

keyword : 차원, 좌표, 좌표축, 벡터, 스칼라, 피타고라스 정리, 정규화

 

4.1 차원

 

차원이란?
 - 공간을 표현하는 한 가지 지표
 - 예를 들어 3차원은 차원(=정보)이 3개라는 의미이며 가로, 세로, 높이 총 3개의 정보를 가짐

 

TV 화면과 컴퓨터 모니터의 경우 둘 다 평면이므로 가로와 세로 2가지 정보로 구성된 2차원임을 알 수 있다.

그러나 우리는 이런 2D 디스플레이로 3D 게임을 즐길 수 있다. 즉, 2D 안에서 3D 공간을 표현할 수 있는 방법이 있다.

이를 이해하기 전에 먼저 2D 게임을 기준으로 어떻게 차원을 다루는지 알아보자.

 

4.2 좌표

 

게임 환경에는 플레이어, 적, 아이템 등 임의의 위치에 다양한 오브젝트가 존재한다.

이렇듯 게임에서는 위치가 중요하다.

 

좌표란?
 - 위치를 명확한 정보로 나타낸 것
 - 좌표는 숫자로 나타냄
 - 좌표를 보면 오브젝트가 정확히 어디에 위치하는 지 알 수 있음

 

2차원 공간을 구성하는데는 세로를 나타내는 좌표와 가로 방향을 나타내는 좌표가 각각 1개씩, 총 2개가 필요하다.

이처럼 각 차원에 숫자를 붙이고 차원을 나타내는 화살표를 좌표축이라고 한다.

이 좌표축 위의 숫자 2개를 이용해 공간 안의 특정 위치를 명확하게 알 수 있다.

 

좌표축에서 모든 축이 0이 되는 교차점을 원점 이라고 한다.

이때 값의 단위는 꼭 가로는 X, 세로는 Y로 고정된 것이 아니라 그때 그때 정하고 변경할 수 있다.

예를 들어 지도의 위도와 경도는 X, Y로 이뤄지진 않았지만 궁극적으로 2차원 좌표축을 가진다.

 

 

수학의 좌표축과 TV 좌표축의 비교

 

일반적으로 수학에서의 좌표축은 가로를 X, 세로를 Y로 지정한다.

그러나 디스플레이 평면을 X축과 Y축으로 끼워 맞추면 Y축에 대응하는 + 좌표가 서로 반대가 된다.

 

+가 되는 좌표가 위쪽 방향을 가지는 수학 좌표계와 달리

디스플레이 좌표계는 Y축이 +가 되는 방향이 아래 방향이다.

반면 X축은 둘 다 오른쪽이 양수 방향이다.

 

앞서 배웠듯 좌표축의 방향에는 고정된 법칙이 없으므로 이렇게 좌표축의 양의 방향이 다른 것은 전혀 문제가 되지 않는다.

하지만 이해를 돕기 위해 이 책에서는 수학 좌표계를 기준으로 설명 되어있다.

 

왜 디스플레이에서는 Y축 양의 방향을 굳이 다르게 설정했을까?

디스플레이에 그래픽을 표시할 때 점(픽셀) 단위로 그리게 되는데,
컴퓨터는 왼쪽 위에서 오른쪽 아래 방향으로 픽셀을 처리한다.
따라서 아래쪽으로 값을 늘려 가는 것이 컴퓨터가 처리하기 더 쉽기 때문에 방향이 반대가 되었다.

 

 

좌표의 표기

 

위치 정보는 좌표를 나타낸다. 그리고 그 좌표를 수치화하기 위해 지표가 되는 좌표축을 사용한다.

 

이번에는 이것을 실제로 수치화해보자. 각 축에 숫자를 붙여서 좌표를 생성할 수 있다.

x축이 5, y축이 2인 위치에 있다면 (x,y) = (5, 2)라고 쓰며 (x, y)부분을 생략하고 (5, 2)라고 표기하는 것이 일반적이다.

 

 

프로그램에서 좌표 다루기

 

2차원을 프로그램에서 다루기 위해서는 좌표 2개가 필요하다.

 

float x = 5.0f; // x 좌표
float y = 2.0f; // y 좌표

 

그러나 현재로서는 x, y가 따로 떨어져 있어 번거롭다.

따라서 구조체를 사용해서 다음과 같이 표시한다.

 

// 좌표 정보 구조체
struct Point
{
	float x, y; // X좌표와 Y좌표
}

// 좌표에 정보 설정
Point p;
p.x = 5.0f; // x 좌표
p.y = 2.0f; // y 좌표

 

 

좌표의 이동

 

가상 공간에서 캐릭터는 단순히 위치하기만 하는 것이 아니라 명령에 따라 이동을 할 수 있어야 한다.

이 때 '좌표가 얼만큼 움직이는가?'라는 정보가 필요한데, 좌표축별로 나눠서 생각하는 것이 쉽다.

 

만약 (5, 2)를 (1, 4)로 이동시키려면 어떻게 될까?

x축 방향은 5 -> 1이므로 -4 만큼 움직이게 되고, y축은 2->4로 움직이기 때문에 2만큼 움직이게 된다.

결론적으로 (-4, 2)만큼 움직이게 되는 것이다.

이 때 이 이동하는 정보, 즉 이동량을 벡터(vector)라고 한다.

 

 

4.3 좌표와 벡터

 

앞서 위치를 나타내는 것은 좌표라고 하고, 이동량을 나타내는 것은 벡터라고 배웠다.

 

좌표와 벡터는 숫자 2개를 사용한다는 점에서 동일하다.

또한 둘 다 (x, y) 형태로 표기한다.

 

하지만 그 안의 내용은 다르다.

좌표는 어느 지점을 가리키는 정보로서 위치를 나타내고, 벡터는 이동량을 나타내는 형태가 없는 정보이다.

 

그렇기 때문에 반드시 올바른 위치에 그려야만 하는 위치와 달리 벡터는 아무 데나 그려도 상관이 없다.

다만 이동량만 가리킬 때는 보통 직관적으로 값을 알 수 있도록 원점에 나오는 화살표로 표시하게 된다.

 

 

좌표와 벡터의 연산

 

좌표와 벡터는 둘 다 숫자이므로 서로 연산할 수 있다.

따라서 문제 없이 사칙연산 등을 수행할 수 있지만, 좌표와 벡터는 각각 표현하는 정보가 다르므로 이를 염두해야 한다.

 

 

1. 좌표와 벡터의 더하기

 

(좌표 A) + (벡터 B) = (좌표 C)

 

좌표에 벡터를 더하면 좌표가 되고, 결과로 나온 좌표 (좌표 C)는 원래 좌표(좌표 A)를 어떤 벡터(벡터 B)만큼 움직인 좌표가 된다. 즉, 현재 플레이어가 있는 위치가 A이고 그 A의 위치에서 B만큼을 더해주면, 최종적으로 A 위치에서 B만큼 이동한 플레이어의 새로운 좌표 C가 만들어진다.

 

 

2. 좌표와 벡터의 빼기

 

이번에는 어떤 좌표에 대해서 특정 벡터를 빼는 연산을 해보자.

 

(좌표 A) - (벡터 B) = (좌표 D)

 

이 때는 더했을 때와 정반대 방향으로 이동한다.

즉, 원래의 위치 A에서  뒤집어진 방향으로 B만큼 이동한 새로운 좌표 D를 가지게 된다.

 

 

3. 좌표와 좌표의 빼기

 

(좌표 A) - (좌표 B) = (벡터 C)

 

좌표와 좌표의 빼기 결과값은 벡터가 된다.

특정 좌표에서 다른 좌표 B를 빼면 B에서 A까지 도달하는데 필요한 이동량인 새로운 벡터 C가 만들어진다.

 

즉, 결과 벡터(C)는 빼는 좌표(B)에서 빼지는 좌표(A)로 이동하는 벡터가 된다.

이를 활용하면 몬스터가 플레이어를 추적하는 기능을 만들 수 있다.

 

예를 들어서 현재 플레이어가 있는 위치가 A이고 몬스터가 있는 위치가 B라면,

A의 위치에서 B를 빼서 B에 위치한 몬스터가 A에 있는 플레이어가 있는 곳까지 이동하는데

필요한 이동량 C를 구할 수 있다.

 

이 값을 가지고 몬스터가 한 프레임당 C만큼, N만큼의 스피드로 움직이도록 코드를 작성하면

몬스터는 자연스럽게 플레이어를 향해 매 프레임마다 N의 스피드로 점점 다가가게 된다.

 

좌표끼리 계산한 결과가 벡터가 된다는 것이 이해하기 어렵다면

현재 수식을 항 이동으로 변형시켜 보면 된다.

 

(좌표 A) - (좌표 B) = (벡터 C)

(좌표 A) = (벡터 C) + 좌표 (B)

 

(좌표 B)를 등호 왼쪽에서 오른쪽으로 이동하면 (벡터 C) + (좌표 B)를 한 값의 결과는 좌표 (A) 가 된다.

이렇게 되면 좌표 + 벡터로 설명했던 '좌표를 원하는 벡터 만큼 이동시킨다가 되므로,

결국 앞서 살표보았던 좌표 + 벡터와 동일한 의미가 된다.

 

이와 같은 논리로 좌표 + 좌표의 결과값도 좌표끼리의 빼기와 동일하게 벡터가 된다.

 

 

4. 벡터와 벡터의 더하기

 

(벡터 A) + (벡터 B) = (벡터 C)

 

벡터끼리 더한 결과는 벡터가 된다.

 

결과 벡터인 C는 단순히 두 벡터 (A, B)의 이동량을 더한 것이 된다.

즉, (벡터 A) 만큼 이동한 후 (벡터 B)만큼 이동한 벡터가 되는 것이다.

선분으로 그려보면 A의 길이와 B의 길이의 총합 만큼의 길이를 가진 C가 되는 것을 확인할 수 있는데,

이를 그림으로 나타내면 그 모습은 세 벡터로 삼각형을 만드는 형태가 된다.

 

 

5. 벡터와 벡터의 빼기

 

(벡터 A) - (벡터 B) = (벡터 C)

 

벡터끼리의 빼기의 결과는 덧셈과 마찬가지로 벡터가 된다.

 

이를 그림으로 나타내면 벡터끼리의 덧셈 결과와 동일하게 삼각형의 형태가 되는데,

다른 점은 결과 벡터의 진행 방향이 덧셈일 때와 반대로 바뀌기 때문에 결과 벡터 C는 뒤집어진 모양으로 나타낸다.

 

따라서 덧셈일 때의 결과로 나오는 삼각형과 정확히 좌/우 대칭이 바뀐 모양새를 가지게 된다.

 

 

좌표와 벡터의 곱하기와 나누기

 

좌표와 벡터는 두 숫자가 모인 것이므로 일반 수와 동일하게 덧셈과 뺄셈 외에도 곱하기와 나누기까지 계산할 수 있다.

이 때 생각해볼 문제는 '무엇을 곱할까?' 와 '무엇을 나눌까?' 이다.

 

더하기와 빼기에서는 좌표와 벡터를 더하거나 뺀다는 의미였다.

그렇기 때문에 계산 결과를 쉽게 예측할 수 있었다.

 

하지만 곱하기와 나누기는 좌표와 벡터를 곱하거나 나눈다는 개념이 생소하기 때문에 이해하기 어려울 수가 있다.

곱하기와 나누기에서는 일반적으로 좌표와 벡터가 아니라 스칼라(Scalar)를 사용한다.

스칼라는 숫자 하나, 즉 일반적인 숫자를 의미한다.

 

일반적인 숫자에 불과하기 때문에 스칼라는 당연히 좌표나 벡터에 쉽게 곱하거나 나눌 수가 있다.

이를 바탕으로 먼저 좌표에 스칼라를 곱하고 나누는 방법에 대해서 알아보자.

 

 

6. 좌표에 스칼라 곱하기

 

(좌표 A) * (스칼라 B) = (좌표 C)

 

좌표에 스칼라를 곱하면 결과는 좌표가 된다.

앞서 살펴보았듯이 스칼라 자체는 단위도. 특별한 의미도 없는 단순한 수에 불과하므로 연산해서 얻는 결과의 종류가 변하지 않는다.

 

따라서 좌표에 스칼라를 곱한 결과는 좌표가 되고, 벡터에 스칼라를 곱한 결과는 벡터가 된다.

좌표에 스칼라를 연산하면 '원점(0, 0)에서 더 확대된 좌표로 바뀐다'는 의미가 된다.

 

예를 들어 (좌표 A)가 (4, 2)고 (스칼라 B)가 2일 때,

이를 곱한 값은 말그대로 단순 곱이므로 (4 * 2, 2 * 2)가 되어 결과적으로 (8, 4)가 된다.

 

 

7. 좌표에 스칼라 나누기

 

이번에는 좌표에 스칼라를 나눠보자.

앞에서 사용한 (좌표 A)인 (4, 2)를 2로 나누면 (2, 1)이 되는데, '2로 나눈다'는 것은

결과적으로 절반 값인 '0.5를 곱한다'와 같은 의미가 되므로 '원점에서 바뀐다'라는 의미에서 보면 곱하기와 같아진다.

 

 

8. 벡터에 대한 스칼라 곱하기와 나누기

 

(벡터 A) * (스칼라 B) = (벡터 C)

 

벡터에 스칼라를 곱하거나 나눠서 나오는 결과는 '벡터 길이를 바꾼 벡터'가 된다.

예를 들어 2를 곱하면 길이가 두 배로 늘어나고, 2로 나누면 절반인 1/2배로 줄어들게 된다.

 

이처럼 좌표와 벡터로 곱하기와 나누기를 할 때는 일반적으로 스칼라를 사용하며,

연산 결과가 벡터 길이를 바꾼 벡터가 되기 때문에 원래의 결과를 바꾸려는 의도를 가지고 있을 때도 스칼라를 사용한다.

 

 

구조체로 벡터 표현하기

// 벡터 구조체
struct Vector
{
	float x, y; // x성분과 y성분
};

// 벡터에 정보 설정
Vector v;
v.x = 2.0f; // x 성분
v.y = 4.0f; // y 성분

 

좌표와 벡터의 각종 계산

더보기
// 좌표 구조체
struct Point
{
	float x, y;
};

// 벡터 구조체
struct Vector
{
	float x, y;
}

// (좌표) + (벡터)
Point Add_Point_Vector(Point p, Vector v)
{
	Point r;
    r.x = p.x + v.x;
    r.y = p.y + v.y;
    
   return (r);
}

// (좌표) - (벡터)
Point Sub_Point_Vector(Point p, Vector v)
{
	Point r;
    r.x = p.x - v.y;
    r.y = p.y - v.y;
    return (r);
}

// (좌표) - (좌표)
Vector Sub_Point_Point(Point p0, Point p1)
{
	Vector r;
    
    r.x = p0.x - p1.x;
    r.y = p0.y - p1.y;'
    return (r);
}