게임 수학 벡터를 직접 구현해봤더니 움직임이 보였다

profile_image
작성자 벡터입문자 채운
댓글 0건 조회 10회

캐릭터를 오른쪽으로 움직이는 코드는 만들었는데, 대각선에서는 갑자기 빨라지고 적은 엉뚱한 방향을 바라보나요? 저도 게임 프로그래밍을 처음 배울 때 좌표와 벡터를 같은 숫자 묶음으로 취급했다가 이동, 추적, 조준 코드를 여러 번 고쳤습니다. 작은 벡터 클래스를 직접 구현해보니 외우기만 했던 공식이 게임 속 움직임을 설명하는 언어로 보이기 시작했습니다.

이 글은 수학에 자신 없는 초보자도 따라올 수 있도록 2차원 벡터의 의미부터 정규화, 내적, 실제 이동 코드까지 차례대로 연결합니다. 특정 게임 엔진의 함수 이름보다 원리를 중심으로 설명하므로 Unity, Unreal Engine, Godot 또는 자체 엔진을 공부할 때 모두 활용할 수 있습니다.

좌표 두 개를 벡터로 바라본 첫날

위치와 방향은 모양이 같아도 역할이 다릅니다

2차원 게임에서 위치는 흔히 (x, y)로 표현합니다. 플레이어가 (3, 2)에 있다는 말은 원점에서 오른쪽으로 3, 위로 2만큼 떨어진 지점에 있다는 뜻입니다. 벡터도 (3, 2)처럼 적지만, 여기서는 특정 장소보다 오른쪽으로 3, 위로 2만큼 이동하라는 변화량으로 해석할 수 있습니다.

처음에는 이 차이가 말장난처럼 느껴졌습니다. 하지만 플레이어 위치에서 적 위치를 빼자 즉시 의미가 드러났습니다. 플레이어가 (2, 1), 적이 (7, 4)에 있다면 적을 향하는 벡터는 (7-2, 4-1), 즉 (5, 3)입니다. 출발점과 도착점만 알면 방향을 얻을 수 있으니 추적 AI와 조준선의 출발점이 됩니다.

  • 위치 벡터: 월드 원점을 기준으로 객체가 있는 지점을 나타냅니다.
  • 방향 벡터: 어느 쪽으로 얼마나 변화하는지 나타냅니다.
  • 속도 벡터: 방향에 초당 이동량을 함께 담습니다.
  • 두 점의 차: 도착 위치에서 시작 위치를 빼면 시작점에서 도착점으로 향하는 벡터가 됩니다.

덧셈과 뺄셈을 화면 움직임으로 확인합니다

벡터 덧셈은 현재 위치에 이동량을 적용하는 과정입니다. 위치가 (10, 5)이고 이번 프레임의 이동 벡터가 (2, -1)이면 새 위치는 (12, 4)가 됩니다. 반대로 두 위치를 빼면 둘 사이의 상대적인 방향을 얻습니다. 종이에 공식을 반복해서 쓰는 것보다 작은 점 하나를 화면에 띄우고 값을 바꿔보는 편이 훨씬 빠르게 이해됐습니다.

  1. 화면 중앙에 플레이어 점을 하나 표시합니다.
  2. 방향키 입력을 x축과 y축 값으로 저장합니다.
  3. 입력 벡터를 현재 위치에 더합니다.
  4. 화면에 현재 위치와 입력 벡터를 숫자로 출력합니다.
벡터를 배울 때는 계산 결과만 보지 말고 선의 시작점과 화살표 끝을 함께 그려보세요. 디버그 선 한 줄이 공식 열 줄보다 오래 기억됩니다.

길이와 정규화를 코드로 옮겨본 일주일

대각선 이동이 더 빠른 이유부터 찾았습니다

키보드에서 오른쪽 키만 누르면 입력 벡터는 (1, 0)이고 길이는 1입니다. 오른쪽과 위쪽을 동시에 누르면 (1, 1)이 되는데, 피타고라스 정리에 따라 길이는 √2, 약 1.414가 됩니다. 같은 이동 속도를 곱했는데도 대각선 캐릭터가 약 41% 빠르게 달리는 이유가 바로 여기에 있습니다.

해결 방법은 입력 벡터의 방향은 유지하면서 길이를 1로 만드는 정규화입니다. 벡터 (x, y)의 길이는 √(x²+y²)로 구하고, 각 성분을 그 길이로 나눕니다. (1, 1)을 정규화하면 대략 (0.707, 0.707)이 되며 전체 길이는 1이 됩니다. 여기에 초당 속도와 프레임 시간을 곱하면 방향과 관계없이 일정한 이동을 만들 수 있습니다.

  • 길이: sqrt(x*x + y*y)로 계산합니다.
  • 정규화: 길이가 0이 아닐 때 x와 y를 각각 길이로 나눕니다.
  • 이동량: 정규화한 방향 × 초당 속도 × deltaTime으로 계산합니다.
  • 입력 제한: 아날로그 스틱 입력은 길이가 1을 넘을 때만 정규화하면 약한 입력의 세기를 보존할 수 있습니다.

0 벡터는 반드시 별도로 다룹니다

초보자가 직접 구현할 때 가장 자주 만나는 함정은 (0, 0)을 정규화하는 것입니다. 길이가 0인 벡터를 0으로 나누면 NaN 같은 유효하지 않은 값이 생길 수 있고, 이 값이 위치나 물리 계산으로 전파되면 캐릭터가 사라지거나 전체 시뮬레이션이 망가집니다. 입력이 없거나 목표와 현재 위치가 정확히 같을 때 0 벡터가 자연스럽게 만들어집니다.

실전에서는 길이가 아주 작은지 먼저 확인한 뒤, 작다면 (0, 0)을 반환하는 방식이 안전합니다. 제곱근 계산을 피하고 싶다면 길이 자체 대신 길이의 제곱을 작은 임계값과 비교할 수도 있습니다. 다만 입문 단계에서는 미세한 최적화보다 올바른 예외 처리를 눈으로 확인하는 편이 중요합니다.

  1. lengthSquared = x*x + y*y를 계산합니다.
  2. 값이 작은 임계값 이하라면 0 벡터를 반환합니다.
  3. 그보다 크다면 sqrt(lengthSquared)로 실제 길이를 구합니다.
  4. 각 성분을 길이로 나눈 뒤 결과 길이가 약 1인지 테스트합니다.

처음부터 거대한 수학 라이브러리를 만들 필요도 없습니다. x와 y, 덧셈, 뺄셈, 스칼라 곱, 길이, 정규화 정도만 구현한 뒤 게임 장면에서 사용하는 것으로 충분합니다. 기능을 추가할 때마다 입력과 예상 결과를 나란히 적으면 게임 수학 라이브러리가 단순 공식 모음이 아니라 검증 가능한 개발 도구가 됩니다.

내적을 적용하자 시야 판정이 이해됐습니다

두 방향이 얼마나 비슷한지 숫자로 바꿉니다

내적은 이름만 보면 어렵지만, 게임에서는 두 방향이 같은 쪽을 보는지 판단하는 도구로 생각하면 편합니다. 두 벡터 A와 B의 내적은 A.x×B.x + A.y×B.y입니다. 두 벡터를 먼저 정규화했다면 결과는 -1에서 1 사이가 됩니다. 1에 가까우면 같은 방향, 0이면 직각, -1이면 반대 방향입니다.

예를 들어 경비병의 정면 방향과 경비병에서 플레이어로 향하는 방향을 각각 정규화한 뒤 내적합니다. 결과가 0.7보다 크다면 플레이어가 정면 부근에 있다고 판단할 수 있습니다. 여기에 경비병과 플레이어 사이의 거리 조건, 벽을 확인하는 광선 검사까지 더하면 기초적인 적 시야 시스템이 완성됩니다.

정규화 벡터의 내적방향 관계활용 예
1에 가까움거의 같은 방향정면 조준 보정
0 부근서로 수직옆에 있는 대상 판정
-1에 가까움거의 반대 방향등 뒤 공격 판정

각도 계산을 매번 할 필요는 없습니다

시야각이 90도인 적을 만들고 싶다고 해서 매 프레임 역삼각함수로 각도를 구할 필요는 없습니다. 시야의 절반인 45도의 코사인 값을 임계값으로 미리 준비하고, 정규화한 두 방향의 내적이 그 값보다 큰지만 검사할 수 있습니다. 비교만으로 판정하므로 코드 의도도 분명해집니다.

다만 내적만으로는 벽 뒤의 대상을 걸러낼 수 없습니다. 방향 판정 → 거리 판정 → 가시선 판정 순서로 싼 계산부터 수행하면 불필요한 광선 검사를 줄일 수 있습니다. 여러 적이 동시에 플레이어를 찾는 장면이라면 이런 순서 차이가 실제 성능과 디버깅 난이도에 영향을 줍니다.

  1. 적의 정면 벡터와 목표 방향을 정규화합니다.
  2. 두 방향의 내적을 시야 임계값과 비교합니다.
  3. 두 위치 사이 거리의 제곱을 감지 범위의 제곱과 비교합니다.
  4. 조건을 통과한 대상에만 벽 충돌 검사를 수행합니다.
  5. 판정 결과와 디버그 부채꼴을 화면에 표시합니다.

이런 작은 기술 데모는 개발자의 실력을 보여주는 포트폴리오 소재로도 좋습니다. 게임 개발자가 기술과 결과를 공유하는 대표적인 장인 GDC의 의미와 역할을 살펴보면, 완성품만큼 문제 해결 과정과 지식 공유가 중요하다는 점을 이해할 수 있습니다. 데모 영상에 내적 값과 시야선을 함께 표시하면 결과만 보여줄 때보다 구현 의도가 선명해집니다.

첫 벡터 데모에서 반복한 실수와 FAQ

초보자가 궁금해한 구현 기준

Q. 엔진에 벡터 클래스가 있는데 직접 만들 필요가 있나요?
실제 프로젝트에서는 검증된 엔진 기능을 사용하는 편이 안전합니다. 다만 학습용으로 Vector2의 핵심 연산을 한 번 구현하면 API 이름을 암기하지 않고도 결과를 예측할 수 있습니다. 직접 만든 코드는 작은 콘솔 프로그램이나 별도 테스트 프로젝트에 두고, 배포 게임에서는 엔진 구현을 활용해도 좋습니다.

Q. 2D를 배운 뒤 3D는 처음부터 다시 공부해야 하나요?
그렇지 않습니다. 3D 벡터에는 z 성분이 하나 추가되지만 덧셈, 뺄셈, 길이, 정규화, 내적의 핵심 의미는 같습니다. 이후 회전축과 법선을 다룰 때 외적을 추가로 배우면 됩니다. 먼저 2D 화면에서 화살표를 그려 직관을 만든 뒤 3D로 확장하는 경로가 부담이 적습니다.

Q. 어느 정도까지 만들어야 포트폴리오에 넣을 수 있나요?
캐릭터 이동만 보여주기보다 대각선 속도 보정, 적 시야, 디버그 표시, 간단한 단위 테스트를 한 장면에 묶어보세요. 기능의 목적과 실패 사례, 수정 전후를 짧게 기록하면 developer portfolio에서 사고 과정을 보여줄 수 있습니다. 다른 직군과 협업하는 프로젝트라면 게임 기획자의 역할도 참고해 판정 규칙을 비개발자에게 설명할 수 있는 문서로 남겨두는 편이 좋습니다.

  • 벡터 연산마다 대표 입력, 0 입력, 음수 입력 테스트를 작성합니다.
  • 위치와 방향 변수의 이름을 position, direction처럼 역할에 맞게 구분합니다.
  • 디버그 화살표의 색을 이동, 정면, 목표 방향별로 다르게 지정합니다.
  • 프레임 속도를 바꿔도 같은 시간 동안 이동 거리가 같은지 확인합니다.

값 하나를 믿고 넘기면 버그가 숨어듭니다

제가 첫 데모에서 반복한 실수는 모든 벡터를 무조건 정규화한 것입니다. 속도 벡터에는 방향뿐 아니라 속력도 담겨 있는데 이를 정규화하면 원래 크기 정보가 사라집니다. 정규화가 필요한 대상은 주로 순수한 방향이며, 위치나 최종 속도를 습관적으로 정규화해서는 안 됩니다.

두 번째 실수는 정확한 실수 비교였습니다. 계산 결과가 이론상 1이어도 부동소수점 오차로 0.999999처럼 나올 수 있습니다. 따라서 length == 1 같은 비교보다는 허용 오차 범위 안에 있는지 확인해야 합니다. 세 번째는 deltaTime을 두 번 곱하거나 아예 빼먹는 일이었습니다. 속도가 이미 프레임 이동량인지 초당 이동량인지 변수 이름과 주석으로 구분하면 예방하기 쉽습니다.

  1. 무조건 정규화: 크기에 의미가 있는 속도와 힘까지 단위 벡터로 만들지 않습니다.
  2. 정확한 실수 비교: 두 값의 차이가 작은 허용 오차 이내인지 검사합니다.
  3. 시간 단위 혼동: 초당 속도와 프레임당 이동량을 분리하고 deltaTime 적용 지점을 하나로 정합니다.
화면에서 움직인다는 이유만으로 계산이 맞다고 판단하지 마세요. 입력 벡터, 정규화 결과, 최종 이동량을 함께 표시하면 눈에 잘 띄지 않던 단위 오류까지 찾을 수 있습니다.

연습 장면을 닫기 전에는 입력 없음, 대각선 입력, 목표와 같은 위치, 프레임 속도 변화라는 네 상황을 차례로 재현해보세요. 이 작은 검증 습관이 쌓이면 벡터는 두려운 수학 공식이 아니라 이동과 AI, 카메라, 물리를 연결하는 가장 익숙한 game programming 도구가 됩니다.

게임 수학 벡터를 직접 구현해봤더니 움직임이 보였다

댓글목록

등록된 댓글이 없습니다.