네!!!
이렇게 7화를 끝내고 싶지만, 더 설명이 필요할 것 같아서 이야기해야겠다. 필자는 게임이 좋아서 프로그래밍 공부를 했고, 수학 공부도 했다.
대부분의 보통 사람들이라면 수학을 싫어하는 것이 지극히 정상이라고 생각한다. 왜냐하면, 정말 재미없게 배우기 때문이며 이걸 배워서 도대체 어디에 써먹어야 할지 항상 의문이 들기 때문이다. 그런데 먼저 한 가지 짚고 넘어가자.
수학이 아닌 다른 학문도 열심히 공부하고 절대로 써먹지 않는 것들이 아주 많다. 너무나 많아서 굳이 예를 들지 않아도 될 것 같다. 물론 알고 있어서 나쁠 건 없지만, 그 시간에 자신에게 도움이 되는 다른 것을 공부하면 더 인생에 도움이 된다고 본다.
그런데... 수학만은 프로그래머가 반드시 알아야 한다는 게 필자의 생각이다.
“왜?”
그것에 대한 필자의 생각을 이야기하는 것이 이번 연재의 내용이다. 이미 필자는 5화에서 벡터를 이용해 충돌 처리를 하는 방법을 설명했었다.
그러면 벡터를 모르면 충돌 처리를 할 수 없는가? 꼭 그렇지만은 않다. 5화에서는 다각형의 충돌 처리를 위해 벡터의 내적을 사용해 플레이어가 다각형의 외부에 있는지 내부에 있는지 검사했다. 하지만 벡터를 사용하지 않고 다른 아이디어를 활용해 이 문제를 해결할 수도 있다.
필드 위에 4각형이 있다.
이것을 흑백으로 분류하면,
충돌 처리를 위한 비트맵 이미지.
그림과 같이 흑과 백으로 표현할 수 있다. 지금까지 연재를 통해서 많이 강조한 것처럼 컴퓨터에서 모든 것은 데이터로 표현된다. 이 흑백으로 분류된 그림을 비트맵 데이터라고 하는데, 이 데이터를 이용해서 충돌 처리를 구현할 수 있다.
만약 검은색으로 되어 있는 지역을 플레이어가 걸을 수 있는 지역, 하얀색으로 되어 있는 지역을 플레이어가 걸을 수 없는 지역이라고 하면, 그 내용을 코드로 표현하면 된다. 컴퓨터에서 색상을 표현하는 방법은 매우 다양하다. 보통 RGB(Red, Green, Blue)로 표현한다. 하지만 여기서는 0과 1, 즉 흑과 백만 존재한다고 생각하자.
노트: 컴퓨터 그래픽스 서적을 참고하면 모니터가 색상을 어떻게 표현하는지 알 수 있다. 관심 있으신 분들은 꼭 참고하시길!
|
이제 0이라는 숫자가 검은색이고, 1이라는 숫자가 흰색이라는 것을 알게 되었다. 이제 다음과 같은 슈도 코드로 충돌 처리를 할 수 있다.
bool CollisionCheck()
{
int color = GetColor(playerX, playerY);
if ( color == 1 )
{
return true;
}
return false;
}
코드를 해석하면 다음과 같다.
1. CollisionCheck라는 함수는 참, 거짓을 반환하는 함수다.
2. GetColor 함수를 호출하는데 여기에 매개변수로 플레이어의 2차원 위치(x, y)를 넘긴다.
3. 결과로 color라는 변수에는 현재 플레이어가 있는 위치의 컬러 값이 할당된다.
4. 만일 color라는 변수값이 1이라면 흰색 부분에 있는 것이므로 충돌했다. 그러므로 참(true)을 리턴한다.
5. 그렇지 않으면 거짓(false)을 리턴하여 함수를 종료한다.
이 함수를 호출해서 그 결과값만 체크하면 플레이어가 현재 위치에 있을 수 있는지 없는지 알 수 있다. 이처럼 어려운 수학을 몰라도 충돌 처리를 할 수 있다. 그런데 5화에서는 굳이 벡터의 내적을 이용해서 다각형 충돌 처리를 했다. 왜 그랬을까?
조금 전에 살펴본 충돌 처리 기법은 실제 상용 게임에서도 사용된 적이 있다. 이 방법이 나쁜 것은 아니지만 확실한 것은 메모리를 많이 사용한다는 점이다. 반면에 수학을 이용하면 더 간단히 문제가 해결되는 경우가 많다.
수학을 쓰는 이유? 간단하다. 수학이라는 학문을 이용해 많은 문제의 정답을 구하는 방법이 이미 존재하기 때문이다. 컴퓨터는 우리의 세상에 나타난 지 100년도 안 됐지만 수학은 매우 오래됐고 수학만큼 완벽을 추구하는 학문도 찾아볼 수 없기에 그 해법들을 믿고 따르기만 해도 우리가 생활하는 데 지장이 없다.
필자는 프로그래머가 수학자만큼이나 수학을 잘해야 한다고 주장하는 것이 아니다. 모든 문제에 대해서 증명을 하는 수준까지 알 필요는 없다. 증명 수준까지는 모르더라도 게임을 만드는 데 큰 지장은 없다. 중요한 것은 수학이라는 도구를 언제 사용하고 어떻게 사용할 줄 아는 정도만 해도 된다는 것이다.
앞서 벡터라는 내용을 설명했을 때 벡터의 내적을 이용해 두 벡터 사이의 각을 알 수 있다고 배웠다. 그것을 증명하는 것은 모르더라도, 두 벡터의 각을 얻고자 할 때 내적이라는 도구를 사용하면 그 값을 얻어낼 수 있다는 점만 알아도 된다. 벡터의 내적 말고 외적(Cross Product)이라는 것도 있는데, 이것을 이용하면 두 벡터에 수직인 벡터를 얻어낼 수 있다.
두 벡터 A와 B 외적의 결과는 N이며, 이는 A 벡터와 B 벡터에 수직이다.
이는 오른손 법칙을 사용했을 때 그렇다. 3D 프로그래밍을 하다 보면 왼손 법칙, 오른손 법칙이라는 용어가 있는데 앞서 외적의 결과는 오른손 법칙을 사용했을 때의 결과이다.
필자가 왼손 법칙, 오른손 법칙을 이해할 때 그림으로 이해하기가 어려웠는데 동영상으로 어떤 의미인지 알아보도록 하자.
왼손, 오른손 좌표계 설명
왼손, 오른손 좌표계 구분
그렇다면 수직인 벡터는 왜 필요할까? 예전에 다각형 충돌 처리에 대해서 설명할 때 벡터에 수직인 벡터와 내적을 한 결과 값을 이용해 어떠한 점(혹은 캐릭터)이 다각형 내부에 있는지, 외부에 있는지 판단했다. 당시에는 수직인 벡터가 있다고 가정을 했을 뿐이고, 실제로 컴퓨터는 우리(프로그래머)가 모든 걸 알려주어야 하므로 수직 벡터를 직접 구해야 한다. 이 때 외적을 이용하면 된다.
그 외에도 3D 게임에서 화면에 나타나는 모든 폴리곤들의 색상을 결정할 때 N이 사용된다. 왜냐하면, 실제로 어떠한 평면의 뒤쪽에 빛(라이트 소스)이 있을 때 우리가 평면의 앞쪽에서 바라보고 있다면 평면의 색상은 아주 어둡거나 검은색으로 나타날 것이기 때문이다.
평면의 뒤쪽에 라이트 소스가 있고 우리가 평면의 앞쪽에서 평면을 바라볼 때, 평면은 검정색으로 보인다.
컴퓨터 게임은 실제 세계를 시뮬레이션한 결과고, 이는 결코 현실 세계와 똑같지 않다. 다만 비슷하게 보이려고 노력하는 것뿐이다. 위와 같은 예는 아주 옛날 게임에서 그랬고 실제로 빛은 모든 물체에 이리저리 튕기면서 우리 눈에 보이기 때문에 완전히 검은색으로 나오지는 않는다.
한 가지 확실한 것은 요새 나오는 게임들의 경우 그래픽 퀄리티가 눈에 띄게 향상되었는데, 이것은 더 사실적인 라이팅 계산이 가능했기 때문이다.
가끔 게임 프로그래머들과 대화를 나누다 보면 3D 게임을 제작하기 때문에 수학적인 능력을 요구하는 경우가 많다고 생각한다. 그런데 사실은 그렇지 않다. 최근 들어 게임 프로그래머에게 수학적인 능력이 요구되는경우가 많아지고 있는 이유는 개발하려는 게임에서 필요한 수준이 높아졌기 때문이다.
예를 들어 2D 게임이라도 캐릭터가 들고 있는 아이템이 실제 철퇴와 같은 움직임을 보인다거나 게임 월드에 존재하는 물의 표현 수준이 단순 컬러가 아니라 반사까지 구현하기라도 하면 3D 게임을 개발할 때와 마찬가지로 높은 수준의 수학적 능력이 필요해진다.
보통 3D 게임을 개발할 때, 현실 세계에서 표현되고 있는 상황을 비슷하게 구현해야만 한다고 생각하기 때문에 구현하기가 어려운 것이지 3D 게임이라서 어려운 게 아니다.
마지막으로!
노트: 연재를 끝내고 실제 간단한 예제를 만들면 더 좋을 것 같다는 생각이 들어서 더 내용을 추가했다.
|
간단한 수학식으로 멋진 효과를 어떻게 구현할 수 있는지 알아보자. 이건 필자가 예전에 캐릭터가 공중에 떠 있는 효과를 구현할 때 사용했던 방법이다. <드래곤볼>을 보았던 독자라면 알겠지만, 캐릭터가 위아래로 움직이면 공중에 떠 있는 느낌을 줄 수 있다.
이것을 단순히 코드로 구현하면 그럴듯한 움직임을 만들어내기가 나름대로(?) 어려운 편에 속한다.
캐릭터의 y좌표가 x-y좌표계에서 수직을 나타낸다면 이 y값을 증가시키거나 감소시켜 캐릭터가 떠 있는 효과를 구현할 수 있다. 예를 들어서 10프레임 동안 위아래로 5번씩 이동한다고 친다면, 다음과 같이 코드를 작성할 수 있다.
void CharacterUpdate()
{
if ( moveUp )
{
y++;
count++;
if ( count >= 5 )
{
moveUp = false;
count = 1;
}
}
else
{
y--;
count++;
if ( count >= 5 )
{
moveUp = true;
count = 1;
}
}
}
count는 초기 값을 1로 설정하고 y 값이 주어진 상태에서 함수를 지속적으로 호출하면 위아래로 5픽셀씩 이동한다. 그 움직임은 다음과 같이 매우 불편해(?) 보인다.
선형적인 움직임
캐릭터가 하늘에 떠 있는 느낌이 들지 않고 그냥 위아래로 움직이기를 반복한다는 느낌만 든다. 이러한 느낌이 드는 이유는 y좌표의 값이 선형적으로 증가하거나 감소하기 때문이다. 하늘에 둥실둥실 떠 있는 효과를 내기 위해서는 속도가 변화해야 한다. sin을 이용하면 이 효과를 매우 쉽게 구현할 수 있다. 이제 sin 함수를 이용해서 하늘에 떠 있는 효과를 구현한 코드를 살펴보자.
void CharacterUpdateSin()
{
value += 0.1f;
ySin = 300 + (float)Math.Sin(value) * 5;
}
y 값은 플레이어의 수직 값이고 300은 화면상의 임의의 y 좌표 값이다. 5이라는 값은 sin 그래프의 진폭을 크게 하려고 사용한 값이다. sin 함수를 이용한 캐릭터의 움직임은 화면에 떠 있는 것과 같이 그럴싸하게 보인다. 이제 앞서 선형적으로 움직일 때와 sin 함수를 이용했을 때의 차이를 알아보기 위해 두 캐릭터를 동시에 화면에 띄워보자.
왼쪽은 선형으로 움직이고 오른쪽은 sin 함수를 이용해서 움직였다. 그 차이를 구분할 수 있겠는가?
최근 게임들은 물리를 이용해 현실 세계와 같은 장면을 만들어내는데, 이 때도 수학의 힘이 필요하다. 수학을 배워야 하는 이유는 명백하다. 우리가 사는 현실 세계와 같은 수준을 게이머들은 원하고 있고, 이것들은 수학이라는 도구를 이용하면 매우 쉽게 구현할 수 있기 때문이다.