이번에는 비행기와 소행성이 서로 부디치는 지를 검사해서 충돌한 경우에는 양쪽 모두 사라지도록 해보겠습니다.  우선 여기서는 바로 객체들이 사라지도록 하고, 추후 소행성이 폭발하는 애니메이션 동작을 추가하도록 하겠습니다.



충돌 테스트의 알고리즘 연구

우선 충돌 테스트를 어떻게 하면 좋을까요?  다양한 방법이 있을 수 있겠지만, 쉽게 사용 할 수 있는 두 가지 방법을 소개합니다.
게임 컨트롤과 똑같거나 비슷한 크기의 네모 상자를 이용하는 방법
게임 컨트롤의 중심 간의 거리를 계산하는 방법

중심 간의 거리를 이용하는 방법은 [그림 1]과 같이 상당히 쉽게 구현 할 수가 있습니다.

pic1.png
[그림 1] 객체의 중심 간이 거리를 계산해서 충돌을 테스트 하는 경우

우선 중심 간의 거리 distance를 구하면 아래와 같습니다.  실행코드가 아니고, 의사코드 입니다.

distance = Math.sqrt(Math.pow(Ship.X-Asteroid.X) + Math.pow(Ship.Y-Asteroid.Y));

그리고, 이렇게 구해진 distance의 값이 두 객체에 지정된 원의 지름 값의 합보다 작으면 충돌 된 것으로 간주하게 됩니다.

그리고, 지금 우리가 사용하는 게임 엔진(게쪽)에서 사용하는 방법은 [그림 2]와 같이 네모 상자를 이용하는 방법입니다.

pic2.png
[그림 2] 네모 상자를 이용하는 방법

네모 상자를 이용하는 방법은 중심 간의 거리를 이용하는 방법에 비해서 다소 복잡해 보입니다.
   1 :  // 직선 (S1, E1), (S2, E2)가 서로 교차하는 지 검사한다.
   2 :  private boolean check_LineCollision(int S1, int E1, int S2, int E2) {
   3 :  boolean A = (S1 <= S2) && (S2 <= E1);
   4 :  boolean B = (S1 <= E2) && (E2 <= E1);
   5 :  boolean C = (S2 <= S1) && (S1 <= E2);
   6 :  boolean D = (S2 <= E1) && (E1 <= E2);
   7 : 
   8 :  return (A || B || C || D);
   9 :  }
  10 : 
  11 :  public boolean CheckCollision(Boundary ADest) {
  12 :  return 
  13 :  check_LineCollision(_Left, _Right, ADest.getLeft(), ADest.getRight()) && 
  14 :  check_LineCollision(_Top, _Bottom, ADest.getTop(), ADest.getBottom());
  15 :  }

가로와 세로의 경우를 따로 떼어서 양쪽 모두 겹치는 경우 충돌이라고 판정합니다.

자 이렇게 두 가지 방법을 보여드린 것은, 양쪽 모두 장단점이 있다는 것입니다.  우선 객체의 모양에 따라서, 결정 할 수가 있습니다.  원에 가까운 객체라면 중심 간의 거리를 통해서 계산하는 것이 자연스러울 테고, 네모 모양에 가깝다면 역시 네모 상자를 이용하는 것이 더 자연스러운 결과를 얻을 수가 있습니다.

또한, 네모 상자를 이용하면 테트리스와 같이 다소 복잡하게 생긴 모양들 간의 충돌 테스트도 자연스럽게 할 수가 있습니다.  이 부분은 다른 게임을 통해서 설명하도록 하겠습니다.  네모 상자는 [그림 2]처럼 각 객체의 모양이 서로 다른 경우에도 비교적 자연스런 접근을 할 수가 있습니다.  

물론 원도 타원을 이용하거나 하는 방법을 생각해 볼 수 있겠지만, 계산하는 속도가 현저히 떨어지게 될 것 입니다.

우리는 앞으로 네모 상자를 이용한 충돌 테스트를 이용 할 것 입니다.

제곱근의 경우에도 계산 속도가 상당히 느린 편입니다.  "덧셈 --> 곱셈  --> 제곱근 및 삼각함수"과 같은 순서로 계산 속도가 점점 느려집니다.  

게임에서는 계산 속도가 상당히 중요하기 때문에, "미리 계산해 두기"를 사용하는 경우가 많습니다.  예를 들어 삼각함수는 0.5도 또는 1도 단위로 미리 계산하여 배열 속에 넣어두고, 원하는 각도에 가장 가까운 각도의 값을 배열에서 읽어서 사용하는 것 입니다.  효과는 상당합니다.  

제곱근의 경우에는 배열로 바로 처리하기는 어렵고, 필자의 경우에는 해시를 이용한 계산 결과 캐시를 사용합니다.  이런 부분은 추후 여유가 있을 때 다시 정리하도록 하겠습니다.



게쪽에서 충돌 테스트 하는 방법

우선 게쪽에서는 충돌을 테스트 하기위해서 ryulib.graphic.Boundary라는 클래스를 사용합니다.  Rect와 마찬가지로 네모 상자의 영역을 표시하는 역할을 합니다.  그리고, 이것을 리스트 형태로 묶어서 사용합니다.  현재 만들고 있는 JetBoy의 경우에는 네모 상자 하나면 충분하지만, 테트리스 등을 구현 할 때는 상자가 여러 개 있어야 하기 때문에, 엔진 쪽에서는 네모 상자를 리스트로 묶어서 그 중 하나라도 충돌하면 전체가 충돌 한 것으로 간주 합니다.

Boundary(네모 상자)의 리스트를 관리하는 클래스는 ryulib.game.HitArea 입니다.

충돌 테스트를 위해서는 [소스 1]과 같은 코드를 Ship.java에 추가 합니다.

[소스 1] Ship 클래스에 충돌 테스트 코드 추가 #1
   1 :  // Boundary는 Rect와 유사한 기능을 한다.  
   2 :  private Boundary _HitBoundary = new Boundary(0, 0, 0, 0);
   3 : 
   4 :  @Override
   5 :  protected HitArea getHitArea() {
   6 :  // 비행기의 가상자리에서는 충돌이 일어나지 않도록 마진을 주고 있다.
   7 :  _HitBoundary.setBoundary(
   8 :  _JoyStick.getX() + _HORIZONTAL_MARGINE, 
   9 :  _JoyStick.getY() + _VERTICAL_MARGINE, 
  10 :  _JoyStick.getX() + ResourceShip.WIDTH - _HORIZONTAL_MARGINE*2, 
  11 :  _JoyStick.getY() + ResourceShip.HEIGHT - _VERTICAL_MARGINE*2
  12 :  );
  13 : 
  14 :  return _HitArea;
  15 :  }

2: 네모 상자 영역을 표시 할 객체를 만들어 냅니다.  초기에는 영역의 좌표가 (0, 0) - (0, 0) 으로 되어 있어서, 면적이 0인 객체가 되었습니다.  또한, 이미 언급한 대로 우리는 네모 상자 하나만으로 충돌 영역을 검사하기 때문에, Boundary 객체가 하나면 됩니다.

5-15: GameControlBase에 선언된 getHitArea() 메소드를 재정의 하고 있습니다.  이를 통해서 충돌 테스트를 하려면 지금 내가 HitArea를 알려 줄테니까, 이 정보를 토대로 검사해봐 하고, 충돌 테스트 로직에게 알려주게 됩니다.

8-11: _HitBoundary의 영역을 현재 우주선의 크기에 맞춰서 입력합니다.  [그림 2]에서처럼 우주선보다 약간 작은 영역으로 지정합니다.  우주선이 네모 반듯하지 않기 때문에 우주선 이미지 크기대로 지정하게 되면 빈 공간도 충돌 영역에 포함되기 때문입니다.  따라서, _HORIZONTAL_MARGINE과 _VERTICAL_MARGINE를 통해서 사방에 마진을 주고 있습니다.

우주선이 충돌했는 지를 실제 검사하는 방법은 [소스 2]와 같습니다..

[소스 2] Ship 클래스에 충돌 테스트 코드 추가 #1
   1 :  @Override
   2 :  protected void onDraw(GamePlatformInfo platformInfo) {
   3 :  _Canvas.drawBitmap(
   4 :  ResourceShip.getInstance().getBitmap(_AnimationCounter.getIndex()), 
   5 :  _JoyStick.getX(), _JoyStick.getY(), 
   6 :  _Paint
   7 :  );
   8 : 
   9 :  GameControl GameControl = this.CheckCollision(this);
  10 :  if ((GameControl != null) && (GameControl instanceof Asteroid)) {
  11 :  GameControl.Delete();
  12 :  this.Delete();
  13 :  }
  14 :  }

9: GameControl 클래스에 선언된 this.CheckCollision(this); 메소드를 통해서, 자기 자신과 충돌하는 다른 객체를 찾게됩니다.  첫 번째로 찾은 객체를 리턴하게 되는데, null이 리턴되면 충돌된 객체가 전혀 없다는 뜻 입니다.

11-12: 충돌된 경우 충돌 된 소행성과 우주선을 삭제합니다.


이 상태에서 프로그램을 테스트하면, 충돌은 제대로 검사가 되지 않습니다.  왜 그럴까요?  네!  그렇습니다.  소행성은 충돌 영역에 대해서 아무런 정보를 알려주지 않고 있기 때문에 충돌 테스트가 제대로 이뤄질 수가 없습니다.  따라서, 이제 소행성 클래스에 충돌에 관한 코드를 추가 합니다.

[소스 3] Asteroid 클래스에 충돌 테스트 코드 추가
   1 :  // 충돌 검사 할 때 자신이 차지하고 있는 영역을 알려준다.  
   2 :  private HitArea _HitArea = new HitArea();
   3 : 
   4 :  // Boundary는 Rect와 유사한 기능을 한다.  
   5 :  private Boundary _HitBoundary = new Boundary(0, 0, 0, 0);
   6 : 
   7 :  @Override
   8 :  protected HitArea getHitArea() {
   9 :  _HitBoundary.setBoundary(
  10 :  _X + _HORIZONTAL_MARGINE, 
  11 :  _Y + _VERTICAL_MARGINE, 
  12 :  _X + ResourceAsteroid.SIZE - _HORIZONTAL_MARGINE*2, 
  13 :  _Y + ResourceAsteroid.SIZE - _VERTICAL_MARGINE*2
  14 :  );
  15 : 
  16 :  return _HitArea;
  17 :  }

Ship 클래스와 동일하기 때문에 설명은 생략하도록 하겠습니다.



정리

여기까지는 충돌 테스트에 대한 설명이었습니다.  게임 엔진 내부의 코드는 강좌를 마무리 한 다음에 살펴보록하고, 앞으로도 계속 사용하는 방법에 집중하여 강좌를 이어 갈 예정입니다.  다음에는 레이저를 발사하는 과정에 대해서 설명하도록 하겠습니다.



소스