게임 수학
19편
원점에서 멀어지면 떨리는 이유
오픈월드에서 원점에서 수십 km 떨어지면 캐릭터와 카메라가 덜덜 떨립니다. float 는 큰 수일수록 표현할 수 있는 값 사이가 벌어져서, 작은 움직임이 반올림으로 사라지거나 부풀기 때문입니다. 세상을 원점 쪽으로 다시 옮겨서 막습니다.
빠른 총알이 벽을 뚫고 지나가는 이유
물리는 0.02초마다 그 순간의 자리만 보고 겹쳤는지 검사합니다. 한 걸음에 가는 거리가 벽 두께보다 크면, 벽 앞과 벽 뒤만 보고 부딪힌 적이 없다고 판단합니다. 연속 충돌 검사와 레이캐스트로 그 사이를 메웁니다.
리지드바디가 떨려 보이는 이유
물리는 1초에 50번 움직이고 화면은 그보다 자주 그리니, Rigidbody 는 몇 프레임 제자리에 있다가 한 번에 뜁니다. deltaTime 으로는 안 고쳐지고, Interpolate 가 직전 두 물리 위치 사이를 메워 그려서 고칩니다.
FixedUpdate 가 따로 도는 이유
유니티 물리는 화면 프레임과 상관없이 0.02초마다 한 번씩 계산합니다. 프레임마다 지난 시간을 모아 두었다가 0.02초씩 덜어 내며 FixedUpdate 를 부르기 때문에, 한 프레임에 0번 불리기도 하고 두 번 불리기도 합니다. 간격을 고정하는 이유와 그 때문에 생기는 입력 씹힘을 숫자로 따라갑니다.
점프가 포물선이 되는 이유
중력은 위치가 아니라 속도를 바꿉니다. 매 프레임 속도에 중력을, 위치에 속도를 더하면 포물선이 나옵니다. 속도 · 가속도 · 포물선을 작은 숫자로 처음부터 따라가고, 계산 간격이 크면 점프 높이가 달라지는 이유와 원하는 높이로 뛰게 하는 처음 속도 √(2gh) 까지 봅니다.
매 프레임 Lerp(a, b, 0.1f) 를 쓰면 생기는 일
카메라를 부드럽게 따라가게 하려고 매 프레임 Lerp 에 0.1 을 넣으면, fps 가 높은 기계에서 더 빨리 따라갑니다. deltaTime 을 곱해도 딱 맞지 않습니다. 남은 거리가 곱셈으로 줄어들기 때문이고, 비율을 1 − Exp(−k × deltaTime) 로 주면 fps 와 상관없어집니다. 거듭제곱부터 숫자로 따라갑니다.
Time.deltaTime 을 곱하는 이유
내 컴퓨터에서는 알맞던 캐릭터가 다른 컴퓨터에서는 두 배로 빨라집니다. Update 가 1초에 몇 번 불릴지 모르기 때문이고, deltaTime 은 "한 번에 얼마" 를 "1초에 얼마" 로 바꿔 줍니다.
쿼터니언을 몰라도 쓰는 법
쿼터니언은 "축 하나와 그 축으로 돈 각도" 를 네 숫자에 담은 회전입니다. 네 숫자를 직접 건드리지 않고, 함수로 만들고, 곱해서 합치고, q * v 로 벡터를 돌립니다. 곱하는 순서가 월드 기준과 자기 기준을 가릅니다.
오일러 각: eulerAngles 가 넣은 값과 다르게 나오고 짐벌 락이 생기는 이유
x 를 100 으로 넣은 회전을 읽으면 (80, 180, 180) 이 나옵니다. 오일러 각은 세 축으로 차례로 세 번 도는 것이라 같은 자세를 여러 숫자로 쓸 수 있고, x 가 90 이 되면 두 축이 같은 일을 해서 회전 방향 하나를 잃는 짐벌 락이 생깁니다. 축과 회전부터 따라갑니다.
로컬 좌표와 월드 좌표
같은 (2, 1) 도 월드 기준이면 한 자리, 캐릭터 기준이면 다른 자리입니다. 좌표계와 기저가 무엇인지부터, TransformPoint 가 축을 섞어 월드 자리로 옮기고 InverseTransformPoint 가 내적으로 되돌리는 과정을 작은 숫자로 따라갑니다.
행렬: 크기 · 회전 · 이동을 한 번에 담는 표
부모를 돌리면 자식도 같이 도는 것은 엔진이 꼭짓점마다 행렬을 곱하기 때문입니다. 행렬이 무엇인지부터, 행렬 × 벡터가 열을 섞는 계산이라는 것, 크기 · 회전 · 이동을 TRS 순서로 합치는 이유까지 작은 숫자로 따라갑니다.
투영: 벽에 막힌 이동을 벽을 따라 미끄러지게 바꾸기
벽에 비스듬히 부딪힌 캐릭터가 멈추지 않고 벽을 따라 미끄러지게 하려면, 이동 벡터에서 벽으로 파고드는 조각만 빼면 됩니다. 그 조각을 내적으로 구하는 법과 ProjectOnPlane · Reflect 가 하는 일을 작은 숫자로 따라갑니다.
외적: 왼쪽인가 오른쪽인가
내적은 앞뒤만 알려 주고 왼쪽과 오른쪽은 가리지 못합니다. 외적은 두 벡터에 모두 수직인 벡터를 만들고, 바닥 위의 두 방향이면 그 y 부호가 왼쪽과 오른쪽을 알려 줍니다. 수직 · 법선 · 왼손 좌표계부터 작은 숫자로 따라갑니다.
내적: 적이 앞에 있나 뒤에 있나
백어택 판정과 시야각 판정은 Vector3.Dot 한 줄입니다. 내적은 같은 자리끼리 곱해서 더한 숫자이고, 부호는 앞과 뒤를, 값은 사이각의 cos 를 알려 줍니다.
방향 (x, y) 로 각도 구하기: 총구를 마우스로 돌리는 Atan2
2D 게임에서 총구를 마우스 쪽으로 돌리려면 방향 (x, y) 에서 각도를 구해야 합니다. 세로 ÷ 가로 가 tan 이고, 그 비율에서 각도를 거꾸로 찾는 것이 Atan 입니다. Atan 이 정반대 방향을 가리지 못하는 이유와 Atan2 가 그것을 고치는 과정을 숫자로 따라갑니다.
sin 과 cos 는 무엇인가: 비스듬한 화살표를 가로와 세로로 나누기
대포를 30도로 쏠 때 앞으로 얼마, 위로 얼마 가는지 구하는 도구가 cos 와 sin 입니다. 직각삼각형에서 가로 ÷ 빗변 이 cos, 세로 ÷ 빗변 이 sin 이고, 길이 1 인 원 위의 점이 (cos, sin) 입니다. 유니티의 Mathf.Sin 이 도가 아닌 라디안을 받는 이유까지 수학을 처음부터 따라갑니다.
대각선으로 가면 빨라지는 이유
W 와 D 를 같이 누르면 캐릭터가 41% 빨라집니다. 입력 (1, 1) 의 길이가 1 이 아니라 √2 라서입니다. 벡터의 길이를 재는 법과, 길이를 1 로 맞추는 정규화를 언제 쓰고 언제 쓰지 않는지 봅니다.
벡터 더하기는 화살표를 이어 붙이는 것이다
걷기, 바람, 넉백처럼 여러 움직임을 한 번에 합치는 방법은 벡터 더하기입니다. 같은 자리끼리 더하면 화살표가 이어 붙고, 숫자를 곱하면 방향은 두고 길이만 바뀝니다.
벡터는 위치가 아니라 화살표다
Vector3 하나에 위치도 방향도 담기지만 둘은 다른 것입니다. 벡터는 시작점 없이 방향과 길이만 가진 화살표이고, 두 위치 사이의 방향은 도착 − 출발로 구합니다.