관리 메뉴

도드넷

JAVASCRIPT#73 - 자바스크립트 타일내 충돌 검출! 본문

창고/JS KING 포니 [중단]

JAVASCRIPT#73 - 자바스크립트 타일내 충돌 검출!

도드! 2016. 7. 25. 12:56
반응형



God, please let me marry Applejack :3


JAVASCRIPT#73 - 자바스크립트 충돌 검출 V2


충돌검사 재대로 만들어보자.


gf.intersect = function(a1,a2,b1,b2){
    var i1 = Math.min(Math.max(a1, b1), a2);
    var i2 = Math.max(Math.min(a2, b2), a1);
    return [i1, i2];
}


첫번째 함수는 인터섹트 함수인데 Math.min 메소드와 Math.max 메소드가 주로 쓰였다. 둘은 괄호안의 값들중 최대값 또는 최솟값을 반환하는 메소드이다.


intersect 함수가 하는 일은 두 선분의 겹치는 위치 (충돌 겹침 포인트)를 반환한다.


리턴값 형태가 좀 특이한데 이건, 배열행태로 i1 과 i2를 차례로 반환하고 있는게 된다.

만약 WTF = intersect(a1,a2,b1,b2); 식으로 쓰이면 WTF은 배열형태로 WTF[0] 값은 i1, WTF[1] 값은 i2 을 가지게 될것이다.



gf.tilemapBox = function(tilemapOptions, boxOptions){
    var tmX  = tilemapOptions.x;
    var tmXW = tilemapOptions.x + tilemapOptions.width * tilemapOptions.tileWidth;
    var tmY  = tilemapOptions.y;
    var tmYH = tilemapOptions.y + tilemapOptions.height * tilemapOptions.tileHeight;
   
    var bX  = boxOptions.x;
    var bXW = boxOptions.x + boxOptions.width;
    var bY  = boxOptions.y;
    var bYH = boxOptions.y + boxOptions.height;
   
    var x = gf.intersect(tmX,tmXW, bX, bXW);
    var y = gf.intersect(tmY, tmYH, bY, bYH);
   
    return {
        x1: Math.floor((x[0] - tilemapOptions.x) / tilemapOptions.tileWidth),
        y1: Math.floor((y[0] - tilemapOptions.y) / tilemapOptions.tileHeight),
        x2: Math.ceil((x[1] - tilemapOptions.x) / tilemapOptions.tileWidth),
        y2: Math.ceil((y[1] - tilemapOptions.y) / tilemapOptions.tileHeight)
    }
}


두번째 함수는 tilemapBox로 타일 데이터(tilemapOptions)와 오브젝트 데이터(boxOptions)을 받아서 두 물체가 겹치는 부분을 찾아내서 특정수(x1,x2,y1,y2)로 가공한뒤 반환하는 타일-오브젝트 충돌함수의 중간단계에 해당하는

함수이다.


이 함수는 타일전체(맵전체)와 어떤 특정 오브젝트가 겹치는 부분을 찾아내서 특정수로 가공해서 반환하는게 목적인 함수이다.


var tmX  = tilemapOptions.x;
var tmXW = tilemapOptions.x + tilemapOptions.width * tilemapOptions.tileWidth;
var tmY  = tilemapOptions.y;
var tmYH = tilemapOptions.y + tilemapOptions.height * tilemapOptions.tileHeight;


은 타일전체 가로 세로를 지정하고 있고


var bX  = boxOptions.x;
var bXW = boxOptions.x + boxOptions.width;
var bY  = boxOptions.y;
var bYH = boxOptions.y + boxOptions.height;


오브젝트의 현재위치에서의 가로 세로를 지정하고있다.


var x = gf.intersect(tmX,tmXW, bX, bXW);
var y = gf.intersect(tmY, tmYH, bY, bYH);


이후 타일전체와 오브젝트가 겹치는 위치를 구해낸다.



    return {
        x1: Math.floor((x[0] - tilemapOptions.x) / tilemapOptions.tileWidth),
        y1: Math.floor((y[0] - tilemapOptions.y) / tilemapOptions.tileHeight),
        x2: Math.ceil((x[1] - tilemapOptions.x) / tilemapOptions.tileWidth),
        y2: Math.ceil((y[1] - tilemapOptions.y) / tilemapOptions.tileHeight)

}


리턴부분이 굉장히 특이한데, 타일의 위치값을 빼서 상대위치를 구하고 타일 넓이 높이 단위로 나눠주고 있다. 이후 floor나 ceil을 해주면 결과값은 충돌한, 겹쳐있는 타일의 인덱스값이 나오게 된다.


(여기서 빼주는 tilemapOptions.x 와 y는 타일맵 시작위치로 만약 0 이라면 지워도 상관없다.)


Math.floor : 버림 (2.63 = 2)

Math.ceil : 올림 (2.3 = 3)


ceil 즉 "올림"된 값들은 실제로 충돌하지 않은 인덱스인데 충돌한 이후의 인덱스로 나중에 반복문돌릴때 걸쳐있음을 구현하기 위해 ceil을 해준것이다. 만약 캐릭터가 타일(1,0)과 타일(2,0)을 밟고 서있다면 x1은 1, x2는 3이 될것이다. (2.0~2.999...) 사이값에 있으므로 3을 반환!


gf.tilemapCollide = function(tilemap, box){
    var options = tilemap.data("gf");
    var collisionBox = gf.tilemapBox(options, box);
    var divs = [];
   
    for (var i = collisionBox.y1; i < collisionBox.y2; i++){
        for (var j = collisionBox.x1; j < collisionBox.x2; j++){
       
            var index = options.map[i][j];
            if( index > 0){
                divs.push(tilemap.find(".gf_line_"+i+".gf_column_"+j));
            }
        }
    }
    return divs;
}


세번째 함수는 tilemapCollide로 얘는 충돌한 타일을 모아서 반환하는 역할을 한다.


push 부분이 좀 특이한데, find 메소드를 통해서 해당 class를 가진 요소를 divs에 넣어주고 있다.

divs는 충돌한 타일맵의 배열이다. 불려질때마다 매번 새로 초기화되서 정의되므로 빼거나 그럴필요는 없다.


            var collisions = gf.tilemapCollide(tilemap, {x: newX, y: newY, width: newW, height: newH});
            var i = 0;
            while (i < collisions.length > 0) {
                var collision = collisions[i];
                i++;
                var collisionBox = {
                    x1: gf.x(collision),
                    y1: gf.y(collision),
                    x2: gf.x(collision) + gf.width(collision),
                    y2: gf.y(collision) + gf.height(collision)
                };

                var x = gf.intersect(newX, newX + newW, collisionBox.x1,collisionBox.x2);
                var y = gf.intersect(newY, newY + newH, collisionBox.y1,collisionBox.y2);
               
                var diffx = (x[0] === newX)? x[0]-x[1] : x[1]-x[0];
                var diffy = (y[0] === newY)? y[0]-y[1] : y[1]-y[0];
                if (Math.abs(diffx) > Math.abs(diffy)){
                    // displace along the y axis
                     newY -= diffy;
                     speed = 0;
                     if(status=="jump" && diffy > 0){
                         status="stand";
                         gf.setAnimation(this.div, playerAnim.stand);
                     }
                } else {
                    // displace along the x axis
                    newX -= diffx;
                }
                //collisions = gf.tilemapCollide(tilemap, {x: newX, y: newY, width: newW, height: newH});
            }


네번째는 그냥 실사용예다. (위 함수 전체는 무한 반복문안에 속한다.)


일단 collisions라는 변수는 오브젝트와 겹친, 충돌한 타일 데이터들이 담긴 배열이 된다.

그리고 이 배열안의 요소 하나하나는 반복문에 의해 차례로 collision에 순서대로 저장되고 이를 이용해서 다시 오브젝트와 겹친부분을 구하고 만약 정확히 일치하면 멈추도록 만드는 함수이다. 즉 이로써 벽, 땅이 구현된다고 보면 된다.


특이한 부분이 눈에띄는데


                var diffx = (x[0] === newX)? x[0]-x[1] : x[1]-x[0];
                var diffy = (y[0] === newY)? y[0]-y[1] : y[1]-y[0];


이 두놈이다.


이건 정의문 + if 구문이랑 합쳐놓은 형태로 만약, x[0] === newX 가 성립되면 x[0]-x[1] 을 diffx 값으로 지정하고 아니면 x[1]-x[0] 을 diffx값으로 지정하게 된다.


x[0] < x[1] 그리고 y[0] < y[1] 이므로 x[1] - x[0], y[1] - y[0] 은 음수형태의 차잇값을 가지고 이는 아래에

newY -= diffy, newX -= diffy 로인해 덧셈이되서 우측으로 밀어내거나 아랫쪽으로 밀어내는 역할을 하게된다.


diffx와 diffy는 침범 범위가되겠고 (x[0] === newX)는 어느방향으로 들어왔는지 알아보는 조건문이 된다.

만약 성립하면 타일에 오른쪽으로 들어왔다는것이고 (y[0] === newY)이 성립할경우 타일의 아랫쪽에서 들어온게 된다. 왜냐하면 x[0]은 충돌 상자의 첫번째 x값이고 이것이 오브젝트의 X값인 newX과 일치한다는 것은 타일에 좌측진입 했다는 말이고 똑같이 y[0]은 충돌상자의 첫번째 y값이고 이것이 오브젝트의 Y값인 newY와 일치한다는것은 아랫쪽 진입을 의미한다. (아래 설명참조)




그 다음 조건문   if (Math.abs(diffx) > Math.abs(diffy)을 보면 충돌(침범) 범위, 차잇값 너비(diffx)가 높이(diffy)보다 크면 y이동, 높이가 너비보다 크면 x이동 시키는데, 충돌에서 벗어나기위해 "가장 가까운 곳"으로 이동시키는 개념이다.


(회색 상자를 파랑상자에서 나가게 하는 가장 빠른 방법은 왼쪽이나 오른쪽으로 이동시키는게 아닌

바로 위로 빼는것이다.)


gf.spriteCollide = function(sprite1, sprite2){
    var option1 = sprite1.data("gf");
    var option2 = sprite2.data("gf");
   
    var x = gf.intersect(
        option1.x,
        option1.x + option1.width,
        option2.x,
        option2.x + option2.width);
    var y = gf.intersect(
        option1.y,
        option1.y + option1.height,
        option2.y,
        option2.y + option2.height);
   
    if (x[0] == x[1] || y[0] == y[1]){
        return false;
    } else {
        return true;
    }
}


보너스로 오브젝트간의 충돌검출하는 것도 살짝 보고가자.


간단히 두 오브젝트의 위치값 너비높이값을 가져온후 충돌 포인트를 구하고 구한 충돌 포린트중 x[0] == x[1] 또는 y[0] == y[1]가 성립하면 충돌하지 않은것이라고 리턴한다.


충돌, 겹치는 두 포인트가 x[0] == x[1] 인 경우는 한 경우 뿐이다.


바로 충돌하지 않아서 둘다 0일 경우이다.


즉, 두 오브젝트가 충돌하는 경우는 x축 y축 모두 충돌했을 경우이다. (실제로 두 오브젝트가 충돌할경우 두 축모두 겹쳐져야한다.)






반응형
Comments