소년코딩

2016. 7. 24(일) 게임 엔진 스터디 3주차

 

게임 엔진 스터디 3주차 내용은 다음과 같습니다.

    1. 충돌 감지

    • 충돌 감지 루틴
    • 충돌 감지 알고리즘
    • block

1. 충돌 감지(Collision Detection)

충돌은 상대적으로 운동하는 물체 또는 입자가 근접 또는 접촉해서 상호작용을 미치는 현상입니다.

게임 분야에서 오브젝트간의 다양한 충돌 이벤트는 필수적입니다. 왜나하면 게임에서 발생하는 거의 모든 이벤트가 바로 오브젝트의 충돌에서 시작되는 것이기 때문입니다.

게임을 구성하는 다양한 오브젝트(주인공, 적, 총알, 장애물 등)들이 주어진 조건에 따라 충돌했을 경우, 이를 감지하고 필요한 처리를 해주어야하며 이에 따른 적절한 충돌감지 알고리즘이 필요합니다.

 

1) 충돌감지 루틴

게임에서 아군 캐릭터와 적 캐릭터간의 충돌 여부 감지등과 같이 적의 무기, 총알, 획득해야 할 아이템과의 충돌, 맵에서 장애물과의 충돌까지 충돌체크 루틴이 필요합니다.

충돌감지

// 게임 루프
function update() {
    // more...
    // more...

    // 충돌감지 루틴
    for( var i = 0; i < enemyObject.length; ++i ) {

        // 충돌감지 알고리즘 수행
        if( collsionDetect(myPlayer, enemyObject[i]) ) {

            // 충돌후 사용자 로직 수행

        }
    }
}

 

2) 충돌 감지 알고리즘

 

- 원을 사용한 충돌 감지

원을 사용하는 방법에서는 이들의 반지름을 사용합니다.

두 원의 반지름 합을 상수로 정해 두 원 중심 사이의 거리가 이 상수보다 같거나 작은 경우 충돌로 감지하는 방법입니다.

원충돌


두 원 사이의 거리 d는 다음과 같습니다.

d: 두 원간의 거리

이 경우  이라면 두 원의 충돌이 발생한 경우입니다.

원 사용방법은 접촉, 충돌 감지를 위한 계산이 가장 간단한 장점을 가지고 있으나 원 형태가 아닌 경우(ex 직사각형)엔 적합하지 않으므로 제한적으로 사용되고 있습니다.

function hitTestCircle(circle1, circle2)
{
  // 원의 중심 좌표로부터 벡터 x, y 계산
  var vx = circle1.centerX() - circle2.centerX();
  var vy = circle1.centerY() - circle2.centerY();
  
  // 원 사이의 거리
  var d = Math.sqrt(vx * vx + vy * vy);
  
  // 반지름의 합
  var totalRadii = circle1.halfWidth() + circle2.halfWidth();
  
  // 충돌 검사
  var hit = magnitude <= totalRadii;
  
  return hit;
}

 

- 사각형을 사용한 충돌 감지

사각형은 4개의 특징점 좌표를 가지고 있으며 이들은 동일한 x, y좌표를 가지고 있으므로 이들의 좌표값을 이용하여 충돌 여부를 확인할 수 있습니다.

사각형충돌검사

위의 경우 a4의 좌표 (ax4, ay4), b1의 좌표(bx1, by1)라 하면 (bx1 > ax4 || by1 > ay4),

a1의 좌표(ax1, ay1), b4의 좌표 (bx4, by4) 에서 (ax1 > bx4 || ay1 > by4)중 하나만 만족하면 충돌이 발생하지 않습니다.

이 방법은 충돌체크가 간단한 장점이 있으나 오브젝트의 형상(ex 삼각형)에 따라 정밀한 감지가 어려워 원을 이용한 방식처럼 환경에따라 제한적으로 사용됩니다.

function hitTestRectangle(rect1, rect2) {

    if( rect1.x < rect2.x + rect2.width  &&
        rect1.x + rect1.width > rect2.x  &&
        rect1.y < rect2.y + rect2.height &&
        rect1.height < rect1.y > rect2.y
    ) {
        return true;
    }

    return false;
}

 

원을 이용한 방식에서 거리와 반지름의 합을 사용했듯이 사각형을 이용한 방식에서도 거리반넓이, 반높이의 합을 이용해 충돌감지를 할 수 있습니다.

이 방식은 충돌이 일어난 방향도 구할수 있는 장점이 있습니다.

사각형충돌2

function hitTestRectangle(rect1, rect2) {

    // 충돌 위치
    var collisionSide = "none";

    // 사각형의 중심으로부터 벡터 x, y 계산
    var vx = rect1.centerX() - rect2.centerX();
    var vy = rect1.centerY() - rect2.centerY();

    // 사각형 반넓이, 반높이의 합
    var combinedHalfWidth  = rect1.halfWidth()  + rect2.halfWidth();
    var combinedHalfHeight = rect1.halfHeight() + rect2.halfHeight();

    // 충돌 감지
    if( Math.abs(vx) < combinedHalfWidth || Math.abs(vy) < combinedHalfHeight ) {
        // 충돌 !

        // 충돌위치를 구하는 로직

        // 겹친 넓이, 높이
        var overlapX = combinedHalfWidth - Math.abs(vx);
        var overlapY = combinedHalfHeight - Math.abs(vy);

        if( overlapX >= overlapY ) {
            // x축에서 충돌 발생
            collisionSide = vy > 0 ? 'top' : 'bottom';

        } else {
            // y축에서 충돌 발생
            collisionSide = vx > 0 ? 'left' : 'right';

        }

    }

    return collisionSide;
}

 

3) block

충돌감지후 충돌을 막아줘야하는 로직이 필요한 경우가 있습니다.

- 원을 사용한 방식

원을 사용한 방식에서는 삼각함수 공식을 이용하면 됩니다.

삼각함수

TeX   

TeX     

TeX

cossin의 식을 변형하면 다음처럼 됩니다.

  • TeX
  • TeX
function hitTestCircle(circle1, circle2, block)
{
  var block = block || false;

  // ...more

  if( hit && block ) {

    // 겹쳐진 길이
    var overlap = d - totalRadii;
    
    // 삼각함수의 비를 사용
    var dx = vx / d;  // cosΘ
    var dy = vy / d;  // sinΘ

    circle1.x += overlap * dx;
    circle1.y += overlap * dy;
  }
  
  return hit;
}

 

- 사각형을 사용한 방식

충돌 방향에 따라 겹쳐진정도를 더하거나 빼주면 됩니다.

function hitTestRectangle(rect1, rect2, block) {

  var block = block || false;

  // ..more

  // 충돌 감지
  if( Math.abs(vx) < combinedHalfWidth || Math.abs(vy) < combinedHalfHeight ) {
    // 충돌 !
    
    // 겹친 넓이, 높이
    var overlapX = combinedHalfWidth - Math.abs(vx);
    var overlapY = combinedHalfHeight - Math.abs(vy);

    // ... more


    if( block ) {
      switch( collisionSide ) {
        case "top":
          rect1.y = rect1.y + overlapY;
          break;
        case "bottom":
          rect1.y = rect1.y - overlapY;
          break;
        case "left":
          rect1.x = rect1.x + overlapX;
          break;
        case "right":
          rect1.x = rect1.x - overlapX;
          break;
      }
    }

  }

  return collisionSide;
}

 

game

by 소년코딩

추천은 글쓴이에게 큰 도움이 됩니다.

악플보다 무서운 무플, 댓글은 블로그 운영에 큰 힘이됩니다.

댓글 로드 중…

블로그 정보

소년코딩 - 소년코딩

소년코딩, 자바스크립트, C++, 물리, 게임 코딩 이야기

최근에 게시된 이야기