6.4 충돌검사시 고려할 것들
Last updated
Last updated
당신은 이전 절 끝에서 언급했던, 현재까지 구현한 게임의 그 특정(?) 버그(bug)를 발견했는가? 두 가지 정도 발견했을 것 같다. 두 가지 다 반사판의 양 옆 끝부분(edge)로 공을 받았을 때 발생하는 현상인데 첫번째는 엄밀하게 반사판이 공을 받아낸게 아님에도(공이 반사반에 닿지도 않았는데) 마치 정상 받아낸 것처럼 동작하는 현상이 하나 있다.
원인을 찾기 위해 공의 이미지의 실체에 대해 생각해보자. 그래픽에디터를 통해 직접 이미지를 제작해 본 경험이 있는 사람은 더 잘 이해할 것이다. 공 이미지는 게임상에서 우리 눈에 보이긴 원(circle)으로 보이나 실제 원은 아니고, 원래 맨 처음엔 가로세로 32x32px(픽셀) 크기의 정사각형 형태에 모든 픽셀이 꽉들어차 있고, 사람 눈에 보여야 하는 픽셀은 실제 사람 눈에 보이는 가시적인 색깔을 칠하게 되고, 사람 눈에 보이지 말아야 하는 픽셀(특별히 공의 네 귀퉁이를 둥글게 깎은 부분으로 체스판처럼 보이는 부분)은 투명처리색(transparent)으로 표시(색칠)하게된다. 따라서, 그 실체는 본래 사각형이나, 원처럼 보이게하기 위한 눈속임 정도로 생각할 수 있다.
따라서 우리 게임에 등장하는 배우(Actor)들의 원래 이미지는 사각형(rectangle)였던 것이고, 게임에서 말하는 이 이미지들간에 서로 닿았는가의 충돌(collision)이라는 의미는 아래 예시처럼 이 각각의 이미지들의 사각형 영역이 서로 닿았는가라는 것으로 판단(충돌검사)하는 것이었기 때문에, 아래의 예시처럼 내부적으로 실제 프로그램 상에는 닿은게 맞지만, 우리 눈에는 안닿은 것처럼 보여서 오동작처럼 보였던 현상이다.
이 충돌검사하는 기존 코드를 다시한번 살펴보면 왜 코딩에 사용한 메소드(객체소속 함수)이 이름이 colliderect (collide + rectangle) 였는지를 이제 이해할 수 있을 것이다.
자 그럼 이제 이 부분을 어떻게 처리하면 오동작처럼 보이지 않고 우리 눈에 자연스럽게 보일 수 있을까? 몇 가지 아이디어가 있을 수 있을 것 같다. 제일 첫번째로 생각해 볼 수 있는게 사각형(rect)끼리의 충돌검사가 아닌, 만약 우리 눈에 보이는 이미지가 원형(circle)에 더 가까우면 아래 그림의 우측처럼 충돌검사 영역을 원형으로 만들어 검사(detection)할 수 있으면 더 좋지 않을까? 괜찮은 아이디어이다.
그 다음으로 또 다른 아이디어는 뭐가 있을까? 완벽한 충돌검사를 위해 아예 사람 눈에 보이는 색깔이 칠해진 영역끼리만의 충돌 검사를 허락한다면 이것이 젤 완벽한 충돌검사일 것 같다. 그럴려면 본래 사각형에서 아래와 같이 색깔 칠해진 영역만 따로 한번 더 분리해 내야 하는 작업(그래픽툴에서는 이걸 누끼따기라고 말함)이 추가로 필요하고, 또 이후에 실제 충돌영역 검사에서도 불규칙적인 외형끼리의 픽셀겸칩이라는 더 복잡한 연산이 필요한게 사실이다. 따라서, 이로인한 게임의 속도가 느려지는 현상이 생길 수도 있기에 이렇듯 각각의 솔루션에는 항상 트레이드오프(trade-off)가 있다. 결국 게임사용자에게 큰 불편을 주지 않는 어느정도 선해서 솔루션 선택의 타협을 해야할 것이다.
이제 이 솔루션을 적용해 기존 코드를 바꿔보자. 그런데, 아쉽게도 그런식의 충돌검사 방법을 파이게임제로 라이브러리에서는 제공하지 않고 있다는 것이다. 그러나, 다행인건 지난 5.8절에서 플래피버드 애니메이션을 위해 사용해봤던 파이게임제로를 기능확장하는 추가모듈(pgzhelper)에서는 제공하고 있기 때문에 이를 통해 해결할 수 있다는 것이다. 만약, 당신의 커스텀 뮤 에디터의 사용자가 아니고, 이전에 이 모듈을 설치한 적이 없다면, 최초 1회에 한 해 해당 모듈의 설치가 필요하며 설치에 관한 것은 5.8절에서 이미 충분히 설명하였으므로 해당 부분을 참고하여 설치하자. 다음으로 이제 위에서 언급한 첫번째 솔루션을 적용한 코드를 살펴보자.
두번째 솔루션의 경우도 적용은 쉬운데, 객체 모양을 누끼따낸 픽셀간의 충돌검사를 위한 메소드인 collide_pixel 를 사용하면 되고, 이 메소드는 만약 충돌이 없을 경우엔 None 값을 리턴하기 때문에 그것에 맞춰 다음과 같이 53번라인 코드를 수정하면 끝이다.
이제 두번째 버그를 살펴볼 차례이다. 두번째 버그는 아래의 그림처럼 공이 반사판의 옆구리(edge)에 닿는 순간 반사판을 타고 도는 의도지 않은 기현상(?)이 발생하고 있다. 먼저, 이 버그는 도데체 왜 생겨나는가에 대해 추측해보자.
정확한 원인을 유추하기 위해 타고 돌고 있는 공의 모습을 자세히 살펴보자. 여러분의 이해를 돕기 위해 공의 움직임을 슬로우 모션으로 캡쳐해 보여주고 있다. 첫번째 힌트로 공은 반사판 안에 갇혀서 이동하고 있다. 두번째 힌트로 공은 오르락 내리락을 반복하면서 이동하고 있다. 먼저 첫번째 문제가 공이 반사판 우측끝(edge)에 닿았을 때부터 문제가 생기는데 그 순간 54번 라인의 코드에 의해 공이 반사시키려고 공의 속도(velocity)를 반대방향으로 바꾸는데 바꾸는 것까진 좋았는데 그 결과로 오히려 공이 반사판 내부쪽으로 방향이 바뀌면서 반사판 내부로 침투(?)하는 현상이 생긴 것이다. 이 침투한게 매우 치명적이다. 원래는 공은 침투해서는 안되고, 표면에서 반사되어 반사판을 곧바로 떠나가야 하는데 알다시피 update 함수는 1초 60번이나 호출이 되는데 이는 매우 짧은 순간으로, 공의 아주 조금의 위치변화가 있긴 있었으나, 그래봐야 여전히 현 위치가 계속 반사판 내부를 벗어나지 못한 관계로 매번 계속 반사판과 충돌이라고 판단해 버리게 된다. 그결과로 연이은 매 update 함수 호출마다 속도방향을 반대로 바꾸게 되기 때문에 반사판에 갇혀 있는 공이 오르락 내리락 하면서 이동하게되는 것이다.
그다음으로 이제 원인은 알았으니 어떻게 해결하면 좋을까? 여러분에겐 어떤 아이디어가 있는지 무척이나 궁금하다. 이 문제를 해결하는데는 두 가지 정도의 해법이 있을 것 같다. 두 아이디어 모두 공이 반사판 안쪽으로 침투를 막는 것에 관한 것이다.
첫번 째 아이디어는 공이 반사판 끝쪽에서의 반사의 경우는 반사시 반사판쪽 방향으로 꺽이지 않고, 즉 공은 방향의 꺾임없이 들어온 방향 그대로 같은 방향으로 다시 되돌아 나가게 하는 것이다.
두번 째 아이디어는 반사방향에 대한 것은 건드리지 않되, 반사되어 나갈 때 반사판에 걸려서 그 안으로 침투가 안되도록 하기 위해 일단 공을 수직으로 얼마간(10픽셀) 반사판으로부터 떨어뜨려놓은 후, 그다음에 기존처럼 반사하는 것이다.
우리는 복잡하지 않게 최대한 간단하게 구현하는데 더 우선순위가 있으므로, 둘 중에 후자가 훨씬 간단하므로 해당 솔루션을 적용한 이번 게임의 최종 코드는 다음과 같다.
기존 코드에서 딱 3라인의 변화만 있다. 먼저 맨 첫 라인에서 pgzhelper 모듈을 우선적으로 import 했다. 8-10라인의 게임등장 배우객체들의 생성코드 중, 기존과 다른 우리 목표는 공을 사각형(rect)가 아닌 원형(circle)로 간주되길 원하는거라 9번라인처럼 객체에 반지름(radius)을 사전에 설정해 두는게 필요하다. 가로세로 32픽셀 사각형 안에 꽉차는 원이기 때문에 반지름은 ball 이미지 길이의 절반(16픽셀)으로 ball 객체의 반지름(radius)을 설정해 놓으면 된다. 이제 실제 충돌검사하는 부분인 53번 라인에서는 circle_colliderect 함수를 사용하면 된다. 이 함수로 인해 반사판은 여전히 사각형(rect)일지라도 적어도 공 객체는 원 형태로해서 상호간에 충돌검사가 일어난다.