모바일/HTML5

[HTML5 Game] Collision Detection

박종명 2013. 9. 23. 21:30
728x90

거의 대부분의 게임은 충돌감지를 필요로 한다.

 

플레이어를 향해 달려오는 적(enemy), 적을 향해 쏘는 총알 등 게임 요소들간 충돌을 구현하기 위해서는 개별 요소들의 시각적 겹침을 처리해야 한다.

 

2D 게임에서의 충돌 감지는, 충돌 감지 대상이 되는 게임 요소의 형태에 따라 충돌박스, 충돌구, 충돌점 등 상황에 맞는 감지 로직을 구현해야 한다.

 

여기서는 가장 기본이 되는 충돌 박스에 대해 알아 보겠다.

 

* 간단한 개념

앞서 언급했듯이, 충돌 감지는 두 그래픽 요소의 시각적 겹침을 처리하면 된다. 겹침은 그래픽 요소의 X, Y 좌표와 요소의 크기(너비/높이) 값이 기준이 되는데 다음과 같이 크게 두 영역의 겹첨을 감지하면 된다.

 

1) X 좌표 겹침

player 와 enemy 라는 두 그래픽 요소가 있다고 가정하자.

player를 기준으로 보면, enemy가 player의 X좌표 영역 안에 있을 경우 충돌되었다고 할 수 있다.

 

아래 그림을 보면 이해가 쉬울 것이다.

먼저 enemy의 우측 끝 X좌표(enemy.x + enemy.width)가 player의 좌측 끝 X좌표(player.x)보다 크고,

enemy의 좌측 끝 X좌표(enemy.x)가 player의 우측 끝 X좌표(player.x + player.width)보다 작으면 서로 겹침 즉, 충돌되었다고 할 수 있다.

 

2) Y 좌표 겹침

같은 개념으로 Y 좌표 겹침을 감지할 수 있다.

먼저 enemy의 하단 끝 Y좌표(enemy.y + enemy.height)가 player의 상단 끝 Y좌표(player.y)보다 크고,

enemy의 상단 끝 Y좌표(enemy.y)가 player의 하단 끝 Y 좌표(player.y + player.height)보다 작으면 서로 겹침 즉, 충돌되었다고 할 수 있다.

 

결론적으로 두 요소의 X 좌표 겹침과 Y 좌표 겹침이 둘다 만족한다면 서로 충돌되었다고 감지하면 되는 것이다.이 개념을 코드화 하면 다음과 같다.

function IsCollision(enemy, player) {
  return enemy.x < player.x + player.width &&
           enemy.x + enemy.width > player.x &&
           enemy.y < player.y + player.height &&
           enemy.y + enemy.height > player.y;

}

 

 

* HTML

충돌 박스에 근거한 간단한 예제를 만들어 보겠다. 먼저 다음과 같이 HTML 파일을 구성한다. 캔버스와 마우스 좌표를 표시하기 위한 span 요소를 정의한다.

<!DOCTYPE html>
<html lang="en">
 <head>  
  <script type="text/javascript" src="js/collisionBox.js"></script>
 </head>
 <body>
   <canvas id="GameCanvas" width="400" height="300" style="border: 1px solid #000;">
      HTML5 Canvas를 지원하지 않습니다. 크롬 또는 사파리와 같은 HTML5 지원 브라우저를 이용해 주세요
   </canvas>
   <br />
   <span id="mousePositionDisplay"></span>
 </body>
</html>

 

 

* Javascript

먼저 Player 객체를 위한 Box 생성자 함수를 정의하는데, Player가 위치할 X,Y 좌표와 크기(너비/높이)를 매개변수로 전달받도록 한다.

function Box(x, y, width, height){
 this.x = x;
 this.y = y;
 this.width = width;
 this.height = height; 
}

 

다음으로 문서의 로딩 완료와 마우스 움직임을 처리하기 위한 이벤트 리스너를 등록하고 필요한 전역 변수를 정의한다. 예제의 단순함을 위해 enemy 요소는 따로 정의하지 않고 마우스 포인트를 사용할 것이다. 즉 Player 박스와 마우스 포인터의 겹침(충돌)을 감지하게 되므로 마우스 이벤트가 필요하다.

window.addEventListener("load", init, false);
window.addEventListener("mousemove", onMousemove, false);

 

var canvasClientRect;  //Canvas Client Bounding Rect
var gameContext;      //Canvas Context
var box;                   //Player Box

 

그리고 초기화 함수와 Player 박스를 그리는 함수를 정의한다. 여기서 주의깊게 봐야 할 것은 Canvas의 getBoundingClientRect() 함수이다. Canvas가 문서에 표시될 때 div와 같은 부모요소에 포함될 수도 있고 문서 자체의 margin, padding 값에 의해 문서의 좌측 상단에서 떨어져 있을 수 있다.

 

다시말해, Canvas의 좌측 상단의 좌표가(0,0)이 아닐 수 있다는 것이다. Canvas에 포함된 게임 요소들의 충돌 감지를 위해서는 Canvas의 좌측 상단의 좌표가 (0,0)이어야 하므로 문서와 Canvas의 간격을 좌표계산에서 제거해야 한다. 이를 위해 문서 자체의 margin값을 0으로 설정하거나 Canvas의 offSet값을 계산에 포함할 수도 있지만 좀더 범용적으로 적용하기 위해서 getBoundingClientRect()함수를 사용하는 것이 좋다. 이 함수를 이용하면 Canvas가 문서로부터 얼마나 떨어져 있는지 알 수 있으므로 쉽게 계산할 수 있게 된다.

function init(){ 
  var canvasElement = document.getElementById("GameCanvas");
  //Get Canvas Bounding ClientRect
  canvasClientRect = canvasElement.getBoundingClientRect();  
  gameContext = canvasElement.getContext('2d');  

  //Draw Player Box
  drawBox();
}

 

function drawBox(){
  //Create Player Box Instance
  box = new Box(150,100,100,100); 
  gameContext.fillStyle = '#000000';
  //Draw Player Box
  gameContext.fillRect(box.x,box.y,box.width,box.height);
}

 

이제 남은건 마우스 무브 이벤트와 충돌감지 로직이다. 이 코드가 핵심인데 다음과 같이 구현한다.

앞서 설명한대로 Player 요소와 마우스 포인터의 겹침을 감지하고 충돌 시 이를 표시해 준다.

function onMousemove(e){
 //Clear Canvas  
 gameContext.clearRect(0, 0, canvasClientRect.width, canvasClientRect.height);
  
 //Draw Player Box
 drawBox();
   
 //Calculate Mouse Position based on Canvas
 mouseXinCanvas = e.clientX - canvasClientRect.left;
 mouseYinCanvas = e.clientY- canvasClientRect.top;
     
 //Collision Detection 
 if(IsCollision(box,mouseXinCanvas,mouseYinCanvas)){
    gameContext.fillStyle = '#ffffff';  
    gameContext.font = '18pt Arial';        
    gameContext.textBaseline = 'top';
    gameContext.fillText("collision!", box.x, box.y);
 }
 
 //Display Mouse Position
 document.getElementById("mousePositionDisplay").innerText =
                                                                   "x:" + mouseXinCanvas + ", y:" + mouseYinCanvas;
}

function IsCollision(box, x, y){    
   return  x > box.x  && x < box.x + box.width &&
              y > box.y  && y < box.y + box.height      
}

 

 

* 실행 화면

예제를 브라우저로 실행해 보면 마우스 포인터가 Player 박스 안에 들어가면 충돌되었다고 표시하는 것을 확인할 수 있을 것이다. 물론 이 예제에서는 마우스 포인트가 enemy를 대신하기 때문에 enemy의 박스 계산(너비/높이 계산)이 필요없게 되지만 개념은 동일하다.