도르래입니다. 정확한 번역은 모르겠네요. pull이 "당기다"의 의미를 가지고 있으니 대강 비슷한 느낌으로 생각하셔도 될듯합니다. 다른 joint type들에 비해서 조금 특수합니다. body들도 많이 필요하고 anchor들도 많이 필요합니다. joint의 정의 또한 좀 더 복잡한 편이구요.

그래도 도르래가 뭔지 알고있다면 나머지는 definition의 속성들을 바꿔가면서 익힐 수 있으니 그리 어렵지 않게 적을 할 수 있습니다. 말 10마디 하는것보다 직접 보는게 낫더군요.
이번엔 pulley joint 뿐만 아니라 revolute joint도 같이 적용해봤습니다. 저울이죠. 저울의 양쪽에 revolute joint로 균등하게 엮여 있습니다. 

마우스로 허공에 클릭하면 일정한 크기의 상자가 생기구요. '추'라고 가정해봅니다. 저울 양쪽에 추를 올려놓고 무게에 따른 길이 변화를 관찰하는 것이 목적인 예제 입니다. 사실 저울과는 거리가 좀 있죠. 양쪽의 길이 변화가 ratio 값에 따라 영향을 받는듯 합니다. 만들면서 아직 제가 감을 덜 잡은 부분이기도 하구요. ratio에 따른 길이 변화를 좀 더 명확하게 이해시켜주실분이 어디 없을지..

function onClick(event:MouseEvent):void {

createBox(mouseX, mouseY, 30, 30, 0, true);

}


마우스 이벤트 핸들러입니다. 클릭된 위치에 가로 세로 30 크기의 dynamic body를 만들어냅니다.

function createBox(px:Number, py:Number, w:Number, h:Number, angle:Number, isDynamic:Boolean):b2Body;


function createCircle(px:Number, py:Number, r:Number, isDynamic:Boolean):b2Body;


언제나 한결같은 box와 circle 생성함수입니다. 언급만 하고 패스.

function createRevoluteJoint(body1:b2Body, body2:b2Body, anchor:b2Vec2):void

{

var jointDef:b2RevoluteJointDef = new b2RevoluteJointDef();

jointDef.Initialize(body1, body2, anchor);

jointDef.collideConnected = true;

jointDef.enableLimit = false;

jointDef.enableMotor = false;

world.CreateJoint(jointDef);

}


지난번에 다뤘던 revolute joint 생성함수 입니다. 이 예제에서는 limit과 motor 모두 사용하지 않는 부분만 보시면 되겠습니다.

function createScale(px:Number, py:Number, w:Number, h:Number):b2Body

{

var center:b2Body = createCircle(px, py, 5, true);

var flate:b2Body = createBox(px, py+h, w, 10, 0, true);

createRevoluteJoint(flate, center, new b2Vec2((px-w/2)/scale, (py+h)/scale));

createRevoluteJoint(flate, center, new b2Vec2((px+w/2)/scale, (py+h)/scale));

return center;

}


저울의 한쪽 부분을 생성하는 함수입니다. 지정된 위치에 중심 body를 동그랗게 만들고 h만큼 아래쪽에 판을 w만큼 길게 만들어 냅니다. 그리고 그 판의 양쪽 끝을 중심 body랑 각각 revolute joint 로 엮어주죠. 당연히 body들은 모두 dynamic 이겠죠. 안그럼 저울의 의미가 없으니까. 마지막으로 모든 단위는 각 스케일에 맞게 적용되어야 합니다. 픽셀과 미터는 전혀 상관없는 단위니까요.

function createPulleyJoint(body1:b2Body, body2:b2Body, ground1:b2Vec2, ground2:b2Vec2, anchor1:b2Vec2, anchor2:b2Vec2):void

{

var jointDef:b2PulleyJointDef = new b2PulleyJointDef();

jointDef.Initialize(body1, body2, ground1, ground2, anchor1, anchor2, 1);

jointDef.collideConnected = true;

jointDef.maxLengthA = 190;

jointDef.maxLengthB = 190;

world.CreateJoint(jointDef);

}


그리고 이번에 새롭게 만들어진 pulley joint 입니다. body1을 ground1이랑 anchor1을 기준으로 엮어주고 body2는 ground2, anchor2 와 엮어놓으면 두 body들이 pulley joint의 영향을 받게 됩니다. 하지만 양쪽에 할당될 수 있는 길이는 한정되어 있기 때문에 그 최대값을 여유있게 잡아 놓았습니다. 이 예제에서는 기본적으로 100 정도의 높이에 각 저울이 위치하고 무개에 맞게 늘어나고 줄어들되 0보다 작을수 없으며 190보다 커질 수 없게 해두었습니다.

또한 두 저울이 서로 간섭을 할 수 있게 했네요. 사실 좀 떨어진 거리여서 간섭을 만들어도 별다른 차이점이 이 예제에서 찾아보기 힘듭니다만. : )

var ground1:b2Body = createCircle(100, 0, 5, false);

var ground2:b2Body = createCircle(400, 0, 5, false);

var scale1:b2Body = createScale(100, 200, 140, 90);

var scale2:b2Body = createScale(400, 200, 140, 90);


createPulleyJoint(scale1, scale2, ground1.GetWorldCenter(), ground2.GetWorldCenter(), scale1.GetWorldCenter(), scale2.GetWorldCenter());


이 부분이 createPulleyJoint 함수를 사용해서 두 저울을 생성하고 엮어내는 부분입니다. 저는 생성자 함수에서 이 부분을 사용하고 있습니다. anchor 들은 각 body들의 중심점이므로 간편하게 .GetWorldCenter 함수를 통해서 이용하고 있습니다.

'programming > box2d docs' 카테고리의 다른 글

b2PulleyJoint 예제  (0) 2011.02.01
b2PrismaticJoint 예제  (0) 2011.02.01
b2Revolute Joint 예제  (0) 2011.01.30
b2DistanceJoint 예제  (0) 2011.01.28
b2Body 예제 - Box2DFlash  (4) 2011.01.17
Box2DFlash v2.1a Update Notes 비공식 한글문서  (0) 2011.01.16

WRITTEN BY
buzzler

트랙백  0 , 댓글  0개가 달렸습니다.
secret
prismatic joint는 여닫이 문이나 피스톤같은 제한적 직선운동을 만들때 필요한 joint type입니다. 슬라이더라고도 하죠. revolute joint 처럼 limit을 설정할 수 있어서 일정 바운더리 안에서의 직선 이동을 하도록 제한할 수 있습니다. 또한 motor를 활성화 할 수도 있구요. 이를 이용해서 아래 침대 스프링 형태의 joint를 만들어봤습니다.
마우스로 클릭하면 클릭한 위치에 원이 생기고 자유낙를 하죠. 떨어지는 힘을 받아서 바닥의 스프링들이 출렁거리게 만들어졌습니다.

각 스프링들은 위로 향하는 motor가 돌고 있구요. 하지만 limit이 제한되어 있기 때문에 계속 올라가진 않고 한계점에 서 있게 됩니다. 원과 부딛힌 스프링은 충돌받은 힘만큼 반동으로 아래로 밀려나게 했습니다만 restitution(반발력)이 0 이기 때문에 서로 밀쳐내서 얌채공처럼 튀어오르는 느낌을 없앴습니다. 얌채공같이 반발력이 좋은 물체를 만들려면 restitution값을 1로 설정하면 될것입니다.

joint나 body의 많은 속성들을 바꿔가면서 속성을 이해하는것이 중요한것 같습니다. 말로는 이해하기 힘들어도 속성값 바꿔가면서 느끼니 훨씬 다루기 쉬워지네요.

아래는 예제를 만들기 위해 기본적으로 필요했던 함수 두개 입니다. 상자를 만드는 함수와 원을 만드는 함수죠. 설명 생략하고 함수 구현은 첨부된 .as 파일을 참조하시길.

function createBox(x:Number, y:Number, w:Number, h:Number, angle:Number, isDynamic:Boolean):b2Body;


function createCircle(x:Number, y:Number, r:Number, isDynamic:Boolean):b2Body;


그리고 덤으로 클릭시 원을 생성해주는 핸들러 입니다. 위의 함수를 이용하구요.

function onClick(event:MouseEvent):void {

createCircle(mouseX, mouseY, Math.random()*20+15, true);

}


이제 prismatic joint를 만드는 부분을 보겠습니다.

function createPrismaticJoint(body1:b2Body, body2:b2Body, anchor:b2Vec2, axis:b2Vec2, thick:Number):void

{

var jointDef:b2PrismaticJointDef = new b2PrismaticJointDef();

jointDef.Initialize(body1, body2, anchor, axis);

jointDef.collideConnected = false;

jointDef.enableLimit = true;

jointDef.enableMotor = true;

jointDef.lowerTranslation = -(thick/2)/scale;

jointDef.upperTranslation = (thick/2)/scale;

jointDef.motorSpeed = 3;

jointDef.maxMotorForce = 2900;

world.CreateJoint(jointDef);

}


이는 이 예제에서 사용하기 위한 전용 함수라고 봐도 되겠네요. 모든 prismatic joint가 항상 위와같은 설정값을 가질 필요는 없으니까요. 함수 인자들은 joint definition의 인자와 거의 동일합니다. 단지 limit을 설정하기 위한 변수가 하나 더 추가 되었습니다.

prismatic joint에서 주의깊게 봐야할 부분은 axis 같네요. body가 움질일 축의 방향 벡터 입니다. 단위벡터일 필요는 없습니다. axis의 값이 x="1.0" y="0.0" 이면 x축의 우측 방향을 양의 방향으로 하는 prismatic joint를 생성한다는 의미이고, x="0.0" y="-1.0" 이면 y축의 위쪽을 양의 방향으로 하는 prismatic joint가 됩니다.

신경써야 할 또하나는, 두 body가 처음 joint로 엮이는 그 시점 두 위치가 prismatic joint의 translation값이 0인 지점으로 설정 된다는 것입니다. 그래서 joint를 생성할 당시의 body 위치가 중요합니다.

function createSpring(px:Number, py:Number, w:Number, h:Number):void

{

var b1:b2Body = createBox(px, py-h/2, 5, h, 0, false);

var b2:b2Body = createBox(px, py-h, w, h-10, 0, true);

var anchor:b2Vec2 = new b2Vec2(px/scale,py/scale);

var axis:b2Vec2 = new b2Vec2(0,-1);

createPrismaticJoint(b1,b2,anchor,axis,h-10);

}


이 함수는 위에서 살펴본 createPrismaticJoint를 이용해서 스프링 형태의 물체를 만들어 내는 함수 입니다.  아래에는 static body를 만들고 그 위에 dynamic body를 올리는 형태입니다. axis는 y축 위 방향으로 설정하였습니다.

이 함수에서 매개변수 w는 스프링의 상단 dynamic body의 가로 길이가 되고, h는 static body의 세로 길이가 됩니다.

그렇게 해서 위의 함수를 이용해서 화면의 바닥을 가득 채우는 스프링을 만들어줍니다. 아래 코드는 생성자 함수의 마지막 부분에 들어가면 되겠죠?

for (var i:int = 0 ; i < 10 ; i++) {

createSpring(25+50*i, 400, 48, 30);

}


스프링들이 딱 맞닿아 있으면 마찰때문에 다같이 영향을 받기 때문에 간격은 50으로 하되, 스프링의 가로 길이는 48로 설정했습니다. 최종 소스를 첨부하겠습니다.

'programming > box2d docs' 카테고리의 다른 글

b2PulleyJoint 예제  (0) 2011.02.01
b2PrismaticJoint 예제  (0) 2011.02.01
b2Revolute Joint 예제  (0) 2011.01.30
b2DistanceJoint 예제  (0) 2011.01.28
b2Body 예제 - Box2DFlash  (4) 2011.01.17
Box2DFlash v2.1a Update Notes 비공식 한글문서  (0) 2011.01.16

WRITTEN BY
buzzler

트랙백  0 , 댓글  0개가 달렸습니다.
secret
경첩이나 진자운동같은 형태를 만들기 위해 필요한 joint type입니다. 즉, 원운동에 필요한 joint. 이전에 했던 distance joint와는 다른점이.. 두 body 사이의 길이가 고정 이라는 점. 즉 무거운 물체가 두 물체 사이에 힘을 가한다고 해도 body 사이의 간격이 달라지지 않죠. 게다가 joint는 motor 역할을 시킬수도 있습니다. 그래서 자동차 바퀴가 돌아가는것이나 선풍기날같은 원운동을 하는 자동화된 모터가 되기도 합니다.

이번 예제해서는 revolute joint를 응용해서 진자 운동을 하는 '추' 와 조금 더 응용된 '밧줄'을 만들어보겠습니다. 만들면서 cut the rope가 생각나더군요.
마우스 액션은 없지만 이전의 예제들보다 한층 더 동적인 모습입니다. 밧줄처럼 보이는 것들은 사실 세로로 길죽한 네모를 여러개 붙인 모양이구요. 밧줄의 두께가 너무 얇으면 밧줄끼리 서로 뚫고 지나가서 엉키더군요. 아마 그런 것들을 방지하기 위해서 tracing을 할 수 있게 제공하긴 하지만, cpu사용량이 엄청나게 늘어나기 때문에 이 옵션을 켜는것은 무모한 생각이 듭니다.

소스를 들여다보기 전에.. DebugDraw나 b2Body를 생성하는 방법에 대한 내용은 다루지 않고 지나갈 예정입니다. 이 부분에 대한 자세한 코드나 활용법이 궁금하신분은 같은 programming 카테고리에 있는 User Manual예제코드를 보시면 됩니다.

function createRevoluteJoint(b1:b2Body, b2:b2Body, a:b2Vec2):b2Joint

{

var jointDef:b2RevoluteJointDef = new b2RevoluteJointDef();

jointDef.Initialize(b1, b2, a);

jointDef.collideConnected = false;

jointDef.enableLimit = false;

jointDef.enableMotor = false;

return world.CreateJoint(jointDef);

}


joint를 만들어 주는 함수입니다. joint 또한 definition 클래스를 먼저 만들고 world 클래스의 factory 함수를 통해서 만들죠. 따라서 개발자가 직접 Joint를 생성할 필요가 없습니다.

모든 joint의 설정값이 다 다를 수 있기 때문에 이같이 함수에 따로 묶어두는것이 바람직한 방법은 아닙니다. revolute joint는 기준점에서 body가 서로 이루는 각도를 제한할 수도 있으며 회전운동을 시킬 수 있는데 이같은 속성들을 하나씩 바꿔가면서 생성하려면 좀더 구체적인 방법이 필요할 것입니다. 이 예제는 revolute joint를 사용하는 모든 body가 같은 설정값을 가진다는 가정으로 제작되었습니다. 즉 회전 각도의 한계값은 적용되지 않고 (enableLimit) 또한 자동적으로 회전하는 motor joint도 없습니다(enableMotor). 그리고 각 body들은 서로 충돌하지 않도록 간섭을 피했습니다(collideConnected).

function createBox(px:Number, py:Number, w:Number, h:Number, angle:Number, isDynamic:Boolean):b2Body;


function createCircle(px:Number, py:Number, r:Number, isDynamic:Boolean):b2Body;


네모 상자와 원형 body를 만들어 주는 함수들입니다. 설명은 생략하겠습니다.

다음의 소스는 '진자'를 만들어주는 함수입니다. 진자는 자명종 시계의 추를 생각하시면 되죠. 마우스 액션이 따로 없는 대신 추의 시작 각도를 정할 수 있게 만들어봤습니다.

function createPendulum(px:Number, py:Number, r:Number, h:Number, angle:Number):void

{

var pivot:b2Body = createCircle(px, py, 1, false);

var _radian:Number = angle/180*Math.PI;

var _x:Number = Math.sin(_radian)*h;

var _y:Number = Math.cos(_radian)*h;

var circle:b2Body = createCircle(px+_x, py+_y, r, true);


createRevoluteJoint(pivot, circle, pivot.GetWorldCenter());

}


추가 고정될 위치를 먼저 잡습니다. joint는 반드시 두 body가 연결되어야하기 때문에 편의상 여기서는 반지름 1의 원을 하나 만들었습니다. 그리고 추는 고정되어야 하므로 static body 로 만들었구요.

그다음은 매개변수로 입력받은 초기 추의 각도를 기반으로 추가 생성될 좌표를 계산하는 코드입니다. 원운동의 좌표는 삼각함수로 미리 계산해 낼 수 있겠죠. cos 함수와 sin 함수 두개의 조합으로 원운동 좌표를 계산했습니다. 물론 radian 단위로 사용하기 때문에 degree->radian 변환도 했구요.

마지막으로 앞서 보여드린 소스중 revolute joint를 사용하는 코드입니다. 위에서 생성된 두 body와 기준 body의 중심점을 넘김으로써, 두 body가 revolute joint에 의해 엮입니다. 이렇게 하면 추는 중력의 영향을 받아서 위치변화량이 운동량으로 바뀌면서 진자 운동을 시작하겠네요.

function createRope(px:Number, py:Number, length:Number, thick:Number, seg:int = 10):void

{

var body1:b2Body = createBox(px, py, 1, 1, 0, false);

var body2:b2Body;

var pos:b2Vec2 = new b2Vec2(px, py);

var anchor:b2Vec2 = new b2Vec2(px/scale, py/scale);

var seg_w:Number = thick;

var seg_h:Number = length / seg;


for (var i:int = 0 ; i < seg ; i++) {

   body2 = createBox(pos.x,pos.y+seg_h/2,seg_w,seg_h,0,true);

   createRevoluteJoint(body1, body2, anchor);

   body1 = body2;

   pos.Set(pos.x, pos.y+seg_h);

   anchor.Set(pos.x/scale, pos.y/scale);

}

}


마지막 함수는 진자를 만드는 함수를 좀 더 응용해서 만들어본 밧줄 만드는 함수입니다. 시작위치와 밧줄의 길이, 두께, 촘촘함을 매개변수로 받아서 작은 box를 연결시켜줍니다.

위의 진자를 만드는 함수와 차이점이라면, 여러개의 body가 서로 꼬리를 물면서 revolute joint로 연결된다는 점입니다. 그리고 연결점은 기준 body의 중심점이 아닌 가장 꼬리부분으로 설정했습니다. 그렇게 해야 여로 엮여 있는 느낌이 나겠죠.

이런 joint를 만들면서 실수하기 쉬운부분은 바로 scale입니다. anchor point들은 화면 좌표계가 아닌 world 의 좌표계 이므로 픽셀 좌표로 보기 편하게 작업하던 방식과 혼돈하면 안됩니다. 그래서 포지션은 픽셀 단위로 사용하고 anchor point는 world 좌표계로 변환해서 사용했습니다. 이는 각 view와 world의 비율에 따라서 얼마든지 달라져야 하므로 개발시 상황에 맞게 튜닝 되야겠네요.

이렇게 만들어진 함수들을 초기화 함수에서 절절하게 호출해주면 이번 예제와 같은 모습이 나옵니다. 이 예제는 500 x 400 기분으로 ..

createPendulum(150, 0, 50, 350, -90);

for (var i:int = 1 ; i <= 4 ; i++) {

createRope(150+50*i, 0, 400, 10, 7);

}


이렇게 초기화 시켰습니다.

전체 소스를 첨부시키고 다음에..

'programming > box2d docs' 카테고리의 다른 글

b2PulleyJoint 예제  (0) 2011.02.01
b2PrismaticJoint 예제  (0) 2011.02.01
b2Revolute Joint 예제  (0) 2011.01.30
b2DistanceJoint 예제  (0) 2011.01.28
b2Body 예제 - Box2DFlash  (4) 2011.01.17
Box2DFlash v2.1a Update Notes 비공식 한글문서  (0) 2011.01.16

WRITTEN BY
buzzler

트랙백  0 , 댓글  0개가 달렸습니다.
secret

Body의 사용에 이어서 Joint를 만들어 보려고 합니다. Joint는 다양한 형태가 있습니다만.. 일단 User Manual 의 첫번째로 나왔던 Distance Joint부터. 이름에서부터 대강 어떤 역할을 하는지 알 수 있는데요, 두 Body를 일직선으로 연결하는 역할을 합니다. 그 연결성에 있어서 몇가지 속성을 적용할 수 있는데요.. 그 수치에 따라서 고무줄로 연결한 느낌이 나기도 하고 용수철같은 느낌이 나기도 합니다. 추가적으로 그 사용 예제 뿐만 아니라 몇가지 다른 형태의 Shape들도 만들겠습니다.


먼저 완성된 예제를 보겠습니다.

딱히 꾸며지진 않았지만 Distance Joint가 뭔지 감은 잡힐 겁니다. 나중에는 DebugDraw가 아닌 직접 만든 무비클립과 맵핑하면 그럴싸한 모습이 되겠네요.(저도 아직 연습해보는 단계라서 무비클립과의 맵핑은 현단계에서 해본적이 없습니다)


마우스 클릭으로 생성될 body는 circle입니다. 그리고 두 static body를 연결하는 다리를 만들었는데요, 다리의 양쪽 끝은 static type이고 중간의 흔들리는 발판은 모두 dynamic입니다. 만약 중간의 다리도 static type이라고 하면 이건 joint로 엮어봤자 전혀 제 역할을 하지 못할 것입니다. 메모리 낭비일 뿐이죠.


이 예제는 이전의 b2Body 예제에서 함수를 몇가지 추가한 형태입니다. 풀소스는 가장 마지막에 춤부할 것이고, 설명은 핵심 부분만 하겠습니다.


private function setBg():void {

var bg:Sprite = new Sprite();

bg.graphics.beginFill(0xDDDDDD);

bg.graphics.drawRect(0,0,500,400);

bg.graphics.endFill();

addChild(bg);

}


스테이지 크기와 동일한 Sprite객체를 만들어서 화면에 붙입니다.

이 부분은 이전 소스에 없던 배경 오브젝트를 생성하는 코드입니다. 단순히 블로그에 예제가 올라갈때 IE에서 wmode가 tranparent인 경우 배경이 없으면 stage의 MouseEvent.CLICK 이벤트가 발생하지 않는 이유 때문에 임의로 집어 넣었습니다. 파이어폭스나 사파리, 크롬의 경우는 아무 지장 없습니다.


다음은 이전에 만들었던 createBox 함수와 동일한 createPolygon 입니다. 중심점을 기준으로 다각형 body를 만들어 줍니다. 대신 shape를 만드는 방법이 조금 다를것입니다.


private function createPolygon(px:Number, py:Number, r:Number, numPoly:int, isDynamic:Boolean):b2Body

{

var _tempBody:b2Body;

var _tempBodyDef:b2BodyDef = new b2BodyDef();

var _tempFixtureDef:b2FixtureDef = new b2FixtureDef();

var _tempPolygonDef:b2PolygonShape = new b2PolygonShape();

var _tempVertices:Array = [];

for (var i:int = 0 ; i < numPoly ; i++) {

var vertex:b2Vec2 = new b2Vec2();

var angle:Number = (360 / numPoly * i) / 180 * Math.PI;

vertex.x = Math.cos(angle) * r;

vertex.y = Math.sin(angle) * r;

_tempVertices.push(vertex);

}

_tempPolygonDef.SetAsArray(_tempVertices);

_tempFixtureDef.shape = _tempPolygonDef;

_tempFixtureDef.friction = 0.5;

_tempFixtureDef.density = 1;

_tempBodyDef.position.Set(px / world_scale, py / world_scale);

_tempBodyDef.type = isDynamic ? b2Body.b2_dynamicBody:b2Body.b2_staticBody;

_tempBody = world.CreateBody(_tempBodyDef);

_tempBody.CreateFixture(_tempFixtureDef);

return _tempBody;

}


기존의 SetAsBox 함수를 통해서 간단하게 shape을 만들어냈던것과 반대로, 어떠한 형태로든 다각형을 만들기 위해서 각 꼭지점의 배열로 그 형태를 만들어 내고 있습니다. 이 경우 아시다시피 다각형의 각 꼭지점의 내각이 180도를 넘어서는 안되구요.

이 함수는 원점을 기준으로 필요한 갯수만큼의 다각형을 만드는데 모두 정 다면체가 됩니다. 삼각함수를 통해서 원점을 기준으로 돌기 때문이죠.

만드는김에 원형 shape를 만드는 함수까지 가볍게 넣어 봤습니다.


private function createCircle(px:Number, py:Number, r:Number, isDynamic:Boolean):b2Body

{

var _tempCircleDef:b2CircleShape = new b2CircleShape(r);

var _tempFixtureDef:b2FixtureDef = new b2FixtureDef();

var _tempBodyDef:b2BodyDef = new b2BodyDef();

var _tempBody:b2Body;

_tempFixtureDef.shape = _tempCircleDef;

_tempFixtureDef.friction = 0.5;

_tempFixtureDef.density = 1;

_tempBodyDef.position.Set(px / world_scale, py / world_scale);

_tempBodyDef.type = isDynamic ? b2Body.b2_dynamicBody:b2Body.b2_staticBody;

_tempBody = world.CreateBody(_tempBodyDef);

_tempBody.CreateFixture(_tempFixtureDef);

return _tempBody;

}


원을 만드는것은 딱히 설명드릴게 없네요. 반지름과 원점만 필요하다는 정도는 누구나 알고 계실테니.

위에 잡설이 길었습니다만, 이제 Distance Joint를 생성하는 함수를 들여다 보겠습니다. 일단 함수 전체 소스는...

private function createBridgeByDistanceJoint(body1:b2Body, body2:b2Body, seg:uint = 0, r:Number = 0.3):void

{

var _anchor1:b2Vec2 = body1.GetWorldCenter();

var _anchor2:b2Vec2 = body2.GetWorldCenter();

var _tempBody1:b2Body;

var _tempBody2:b2Body;

var _tempAnchor1:b2Vec2;

var _tempAnchor2:b2Vec2;

var _tempJoint:b2DistanceJoint;

var _tempJointDef:b2DistanceJointDef = new b2DistanceJointDef();

var _tempBodies:Vector.<b2Body> = new <b2Body>[body1];

var _tempAnchors:Vector.<b2Vec2> = new <b2Vec2>[_anchor1];

var _tempVector:b2Vec2 = new b2Vec2();

var _tempGap:b2Vec2 = new b2Vec2();

_tempGap.x = (_anchor2.x-_anchor1.x) * world_scale / (seg+1);

_tempGap.y = (_anchor2.y-_anchor1.y) * world_scale / (seg+1);

for (var i:uint = 1 ; i <= seg ; i++) {

_tempVector.x = _anchor1.x*world_scale + _tempGap.x*i;

_tempVector.y = _anchor1.y*world_scale + _tempGap.y*i;

_tempBody1 = createPolygon(_tempVector.x, _tempVector.y, r, 3, true);

_tempBodies.push(_tempBody1);

_tempAnchors.push(_tempBody1.GetWorldCenter());

}

_tempBodies.push(body2);

_tempAnchors.push(_anchor2);

for (var j:int = 0 ; j < (_tempBodies.length-1) ; j++){

_tempBody1 = _tempBodies[j];

_tempBody2 = _tempBodies[j + 1];

_tempAnchor1= _tempAnchors[j];

_tempAnchor2= _tempAnchors[j + 1];

_tempJointDef.Initialize(_tempBody1, _tempBody2, _tempAnchor1, _tempAnchor2);

_tempJointDef.collideConnected = false;

_tempJointDef.length = _tempGap.Length() / world_scale;

//_tempJointDef.dampingRatio = 0.5;

//_tempJointDef.frequencyHz = 10;

_tempJoint = world.CreateJoint(_tempJointDef) as b2DistanceJoint;

}

}


변수선언이 너무 많네요. 필요한 부분만 뽑아서 설명해보죠.

Joint또한 JointDef 객체를 통해서 world 클래스의 팩토리 함수를 통해서 Joint가 생성됩니다. 앞으로도 직접 Joint를 만들 일은 없을겁니다.

이 함수에서는 매개변수로 전달받은 두 body의 world 좌표계상의 중심점을 기준으로 joint들을 만들어 냅니다. 사실 중심점을 이용하는것이 아니고 두 body의 가까운 쪽의 점을 연결하는것이 맞을텐데.. 귀찮네요 -_-
아무튼 각 중심점들을 기준으로 필요한 만큼의 물체를 dynamic type으로 만들어서 연결해냅니다.

var _tempBodies:Vector.<b2Body> new <b2Body>[body1];

var _tempAnchors:Vector.<b2Vec2> = new <b2Vec2>[_anchor1];

var _tempVector:b2Vec2 = new b2Vec2();

var _tempGap:b2Vec2 = new b2Vec2();

_tempGap.x = (_anchor2.x-_anchor1.x) * world_scale / (seg+1);

_tempGap.y = (_anchor2.y-_anchor1.y) * world_scale / (seg+1);

for (var i:uint = 1 ; i <= seg ; i++) {

_tempVector.x = _anchor1.x*world_scale + _tempGap.x*i;

_tempVector.y = _anchor1.y*world_scale + _tempGap.y*i;

_tempBody1 = createPolygon(_tempVector.x, _tempVector.y, r, 3, true);

_tempBodies.push(_tempBody1);

_tempAnchors.push(_tempBody1.GetWorldCenter());

}

_tempBodies.push(body2);

_tempAnchors.push(_anchor2);


자 이부분을 보시면.. 연결될 모든 body들과 그 body의 중심을 생성하고 배열에 담아내는 일들을 합니다. 나중에 한꺼면에 각각의 joint를 만들어내기 위해서죠. 굉장히 복잡해 보이지만 하는일은 단지 매개변수로 넘겨받은 body 사이에 필요한 만큼의 body를 추가로 생성하는 것. 대신 그 각각의 좌표들은 균일하게 나눠져 있어야 겠죠. 그 좌표를 알아내기 위해서 기존 world의 스케일 비율을 곱해서 좌표를 뽑아냅니다. createBox 함수는 좌표를 받을때 world_scale을 고려하지 않은 픽셀좌표계 기준의 좌표를 받기 때문이죠. getWorldCenter 함수로 넘겨받는 좌표는 스케일링이 되어있어서 픽셀 좌표계와 전혀 다릅니다. 이렇게 굳이 어렵게 진행된 이유는 물리엔진은 픽셀과 상관없는 미터법을 사용하기 때문.


_tempJointDef.Initialize(_tempBody1, _tempBody2, _tempAnchor1, _tempAnchor2);

_tempJointDef.collideConnected = false;

_tempJointDef.length = _tempGap.Length() / world_scale;

//_tempJointDef.dampingRatio = 0.5;

//_tempJointDef.frequencyHz = 10;

_tempJoint = world.CreateJoint(_tempJointDef) asb2DistanceJoint;


Initialize 함수를 통해서 필요한 초기화를 하나본데요.. 왜 생성자에서 안받고 따로 호출하는 식인지는 모르겠네요. 가능성이야 여럿 있지만. 어쨌든, 여러 다른 옵션들을 설정하기도 합니다. 연결된 두 body가 서로 충돌하게 할것인지, 연결될 길이는 어느정도로 할지, 연결성질이 단단한지 등 이러한 속성들에 따라서 보는사람이 느끼는 연결 성질이 천지차이가 되겠네요.


length, dampingRatio, frequencyHz 등등을 바꾸면서 테스트를 하고 감을 잡아두는게 좋겠습니다.


마지막으로 생성하는것은 앞서 설명했던바와같이 world 객체를 통해서 생성됩니다. joint는 눈에 보이는 물체가 아니고 단지 두 물체간의 관계를 정의하기 때문에 DebugDraw를 통해서 그 연결성이 보이진 않습니다. 물체간의 인터렉션으로만 확인 가능하죠. 지금까지 만든 소스를 첨부하겠습니다.


다 적고 나니 뭔가 대단하지 않은 내용으로 주절주절 말이 많았던것 같네요. 다음부터는 joint들 여럿을 모아서 예제로 만들어보겠습니다. 씨유쑨.


WRITTEN BY
buzzler

트랙백  0 , 댓글  0개가 달렸습니다.
secret
2.1a 버전의 메뉴얼이 없어서 적당한 예제 문서가 없다보니 일단 간단하게나마, 각 항목별 예제 코드들을 모아보려고 합니다. 첫번째 예제는 body를 만드는 예제부터.. 만드는 절차는 필요 항목등 세세한 내용은 이미 User Manual 번역문에서 상세히 다뤘으니 못보신 분들은 그쪽을 먼저 참고 하시면 됩니다. (링크는 생략)

이번에 만들게 될 예제입니다.

마우스를 클릭해보시면 사각형이 만들어지고 중력을 받아 자유낙하하는 모습을 보실 수 있습니다. 핑크색 사각형은 깨어있는 상태의 body이고, 이는 시뮬레이션중인 상태라고 생각하시면 됩니다. 시뮬레이션이 필요없는 경우는 흑색으로 바뀝니다. sleep 상태라고 하죠. sleep 상태에서는 성능향상을 위해서 시뮬레이션 스탭에서 제외됩니다. 물론 다른 body의 영향을 받아 깨어날 수 있습니다. 마지막으로 녹색의 사각형들은 static type의 사각형이라고 보면 됩니다. 이는 움직이지 않고 스스로 어떠한 시뮬레이션도 하지 않는 물체라고 보시면 됩니다.

게임에서 빗대어 말하면 static은 지형이나 레벨일 것이고, dynamic은 캐릭터나 총알같은 움직이고 물리적 영향을 받는 것이라고 생각하면 편하겠네요.

이제 소스코드를 들여다 보겠습니다. 액션스크립트 프로젝트를 만들어 메인 클래스가 있을 것입니다. 사이즈는 500 x 400 에 framerate는 30 이라고 하겠습니다. 소스는 메인 클래스 1개 이외의 커스텀 클래스는 없다고 생각하시면 됩니다.

package {

import flash.display.Sprite;

[SWF(width="500", height="400", framerate="30")]

public class MyFirstBox2D extends Sprite {

public function MyFirstBox2D() {

super();

}

}

}


모든 시뮬레이션은 b2World 클래스 객체 단위로 이루어지므로 멤버 변수로 world 객체를 중력 벡터와 함께 선언해줍니다. 또한, 이번 예제에서는 무비클립같은 것들과 완전히 별개로 시뮬레이션만 느껴보기 위해서 디버그 드로잉을 사용할 것입니다. 이는 shape들을 시스템이 임의대로 생성해서 가시적으로 볼 수 있도록 해주는 기능입니다. 원래의 Box2D는 순수하게 물리 시뮬레이션만을 합니다. 때문에 화면에 그려지는것에 대한 것들은 전부 개발자의 몫입니다. 하지만 이 예제에서는 이 부분을 간편하게 디버그 드로잉에 의존할 뿐입니다.

private var world:b2World = new b2World(new b2Vec2(0, 20), true);

private var world_scale :Number = 30;


이렇게 메인 클래스의 멤버 변수로 두개를 선언 했습니다. 중력은 y축으로 20 인 벡터이고 sleep이 가능하게끔 설정했습니다. 또하나의 변수는 디버그 드로잉에 필요한 스케일값입니다. 드로잉이 아닌 시뮬레이션만 담당하는 라이브러리답게 Box2D는 내부적으로 미터(meter)법을 사용하기 때문에 실제로 필셀단위의 디스플레이와 정확하게 매칭 되지 않습니다. 이 스케일값은 디버그 디로잉에만 필요한 값이라고 생각하면 되겠습니다.

다음은 디버그 드로잉을 설정하는 초기화 함수입니다.

private function init():void {

var debug_draw:b2DebugDraw = new b2DebugDraw();

var debug_sprite:Sprite = new Sprite();

debug_draw.SetSprite(debug_sprite);

debug_draw.SetDrawScale(world_scale);

debug_draw.SetFlags(b2DebugDraw.e_shapeBit);

world.SetDebugDraw(debug_draw);

addChild(debug_sprite);

}


이 소스는 2.1a 소스에 포함되어있던 소스코드의 일부이기도 합니다. 디버그 드로잉을 사용하는데에 여기에서 크게 달라질 것이 없어서 일단 동일하게 사용했습니다.

캔버스 역할을 하는 하나의 Sprite 객체를 화면에 붙이고 그 객체를 디버그 드로잉 클래스에 등록하는 과정을 볼 수 있습니다. 앞서 선언했던 스케일값을 셋팅하는 코드도 포함되어있구요. Sprite 객체를 디버그 드로잉 클래스에 등록하고, 그 클래스 객체를 world 객체에 또 등록하네요. 이는 나중에 b2World.DrawDebugData 함수를 통해서 그려내기 위함입니다.

눈여겨볼만한 코드도 한 줄 또 있죠. b2DebugDraw.SetFlags 함수입니다. 이는 화면에 그려낼 정보를 셋팅하는 함수인데요.. b2DebugDraw 클래스의 레퍼런스 문서를 보시면 static 상수가 몇가지 정의되어 있는것을 보실 수 있습니다. 화면에 그릴 옵션들이죠. AABB를 그린다던지, 질량 중심을 그린다던지 그외 joint 나 컨트롤러같은것들까지 설정 할 수 있습니다. 이 예제에서는 body에 첨부된 shape만 그리는 것으로 셋팅되었습니다.

생성자에서 init 함수를 호출해줘야 합니다만 소스는 생략하겠습니다.

자 여기까지 했으면 일단 화면에 출력할 준비가 되었구요. 실제 출력하는 코드를 추가해 보겠습니다. 메인 클래스의 생성자에서 Event.ENTER_FRAME 이벤트 핸들러로 다음의 함수를 붙여줍니다.

private function onEnter(event:Event):void {

world.Step(1/30, 10, 10);

world.ClearForces();

world.DrawDebugData();

}


게임의 핵심 루프에 포함될 내용이 여기 있습니다. 바로 b2World.Step 함수인데요. 이는 world 내에 선언된 모든 정보를 바탕으로 지정해주는 시간만큼 반복해서 시뮬레이션 시키는 함수입니다. 사용자 메뉴얼에 time step을 보시면 더 상세한 내용을 보실 수 있습니다. 이 예제는 frameRate가 30이고 time step도 1/30 으로 지정했습니다. iteration은 10으로.

b2World.ClearForces 함수는 Step으로 시뮬레이팅 한 이후 호출하는 함수라고만 적혀있는데.. sub-step을 또 진행할 것이 아니라면 이 함수를 호출하라고 지시하는것을 보니 아마도 사용된 찌꺼기들 정리하는 느낌으로 보면 될것 같기도 하네요. 이것저것 뒤져보고 혹시 틀린거면 수정하겠습니다. 후훗. 마지막 함수는 말그대로 위에서 지정한 디버그 드로잉 객체에 그려주는 역할을 합니다. 첨부된 shape들을 그리도록 플래그를 설정했으니 아마도 아직까진 그려질것이 없는 상태겠네요.

여기까지는 아무것도 만들어진것도, 시뮬레이션 할 것도 없는 상황입니다. 단지 있다고 가정하고 화면에 그려주고 시뮬레이팅 할 환경만 만들었네요. 다음은 드디어 world 객체에 body를 만드는 함수입니다. world내에 필요한 포인터들은 모두 담겨 있으니 따로 body들의 포인터나 fixture의 포인터를 관리할 필요는 없겠습니다. 단지 b2World.CreateBody 함수의 호출로 만들어주면 world 객체가 내부적으로 관리하고 그려준다고 생각하면 됩니다.

private function createBox

(px:Number, py:Number, angle:Number,

w:Number, h:Number, isDynamic:Boolean):void

{

var _tempBody:b2Body;

var _tempBodyDef:b2BodyDef = newb2BodyDef();

var _tempFixtureDef:b2FixtureDef = newb2FixtureDef();

var _tempPolygonDef:b2PolygonShape = newb2PolygonShape();

_tempPolygonDef.SetAsBox(w/2/world_scale, h/2/world_scale);

_tempFixtureDef.shape = _tempPolygonDef;

_tempFixtureDef.friction = 0.5;

_tempFixtureDef.density = 1;


_tempBodyDef.position.Set(px / world_scale, py / world_scale);

_tempBodyDef.angle = angle / Math.PI * 2;

_tempBodyDef.type = isDynamic ? b2Body.b2_dynamicBody:b2Body.b2_staticBody;

_tempBody = world.CreateBody(_tempBodyDef);

_tempBody.CreateFixture(_tempFixtureDef);

}


사용자 메뉴얼에서도 나와있듯이, body는 world 객체를 통해서 생성합니다. 단지 그 CreateBody를 호출하기 위해선 definition을 지정해야 하고 그렇게 해서 만들어진 body 객체는 그 형태를 띄기 위해서는 fixture라고 하는 재질이나 속성을 다시 할당 해야 합니다. 그리고 기하적 정보는 fixture 객체의 안에 shape를 설정하는것으로 마무리 됩니다. 굉장히 복잡해 보이네요. 하지만 그나마 복잡한 실제 물체들을 시뮬레이팅 하기 위해서 속성을 추상화하는 구조체 치곤 심플하다고 할 수 있습니다.

나중에 다룰 joint와 constrain같은 것들이 추가되기 위해서 이런 구조를 필요로 한다고 생각하면 되겠죠. body를 말 그대로 신체라고 생각한다면 그 안에서 또 얼굴, 팔, 다리, 몸통 같이 세분화 해야할테구요. 그 각각은 속성이 달라질 수도 있으니까 말이죠.

어쨌든 이 함수에서는 생성하고자 하는 사각형의 좌표와 각도, 크기를 받아서 해당 위치에 원하는 크기로 생성해주고 마지막으로 필요한 경우 type을 dynamic 이나 static으로 설정해주는 역할을 합니다.

크기가 위치를 지정할 때 처음에 선언했던 world_scale 값을 꼬박꼬박 나눠서 사용하고 있는데 예상되다시피 디버그 드로잉을 할때 스캐일값만큼 키운것을 보상하기 위한 조치하고 생각하면 되겠습니다. 그 상관관계가 궁금하신 분들은 숫자를 이리 저리 바꾸면서 찾아보시면 되겠죠.

아! 빼먹을뻔 했네요. 잘 보시면 new 명령을 통햇 직접 생성하는 클래스는 Def들 밖에 없습니다. 실제 body와 fixture 는 world와 body에서 제공하는 함수를 통해서만 생성하신다고 생각하면 되겠네요.

이제 이 함수를 사용해서 지형을 만들고 마우스 클릭에 반응해서 동적으로 box들을 만들어보겠습니다. 마우스 클릭 이벤트를 처리할 함수입니다.

private function onMove(event:MouseEvent):void {

createBox(stage.mouseX,stage.mouseY,Math.random()*90, Math.random() * 35 + 15, Math.random() * 35 + 15,true);

}


플래시에 익숙하시다면 마우스 이벤트와 관련된 이야기는 굳이 말하지 않아도 되겠죠? 마우스 클릭이 있을때마다 마우스 위치에 임의의 크기와 각도록 dynamic body를 생성하는 코드입니다. 

그리고 다음은 지형을 만드는 코드죠. 이 코드는 생성자나 초기화 함수에 넣고 한번만 호출될 수 있도록 해야겠죠. 지형이 동적으로 생성될것이 아니라면.

createBox(250,200,0,100,10,false);

createBox(250,390,0,500,10,false);


보시다시피 마지막 매개변수가 false 입니다. static body로 만들라는거죠. static은 중력이나 다른 간섭을 받지 않습니다. 완전히 고정된 body가 됩니다.

자, 이제 최종 소스입니다.

package {

import Box2D.Collision.*;

import Box2D.Common.Math.b2Vec2;

import Box2D.Dynamics.*;


import flash.display.Sprite;

import flash.events.Event;

import flash.events.MouseEvent;

[SWF(width="500", height="400", framerate="30")]

public class MyFirstBox2D extends Sprite {

private var world:b2World = new b2World(new b2Vec2(0, 20), true);

private var world_scale:Number = 30;


public function MyFirstBox2D() {

super();

init();

stage.addEventListener(MouseEvent.CLICK, onMove);

stage.addEventListener(Event.ENTER_FRAME, onEnter);

}

private function init():void {

// debug draw setting

var debug_draw:b2DebugDraw = new b2DebugDraw();

var debug_sprite:Sprite = new Sprite();

debug_draw.SetSprite(debug_sprite);

debug_draw.SetDrawScale(world_scale);

debug_draw.SetFlags(b2DebugDraw.e_shapeBit);

world.SetDebugDraw(debug_draw);

addChild(debug_sprite);


//ground body (static)

createBox(250,200,0,100,10,false);

createBox(250,390,0,500,10,false);

}

private function onMove(event:MouseEvent):void {

createBox(stage.mouseX,stage.mouseY,Math.random()*90, Math.random() * 35 + 15, Math.random() * 35 + 15,true);

}


private function onEnter(event:Event):void {

world.Step(1/30, 10, 10);

world.ClearForces();

world.DrawDebugData();

}

private function createBox(px:Number, py:Number, angle:Number, w:Number, h:Number, isDynamic:Boolean):void {

var _tempBody:b2Body;

var _tempBodyDef:b2BodyDef = new b2BodyDef();

var _tempFixtureDef:b2FixtureDef = new b2FixtureDef();

var _tempPolygonDef:b2PolygonShape = new b2PolygonShape();

_tempPolygonDef.SetAsBox(w/2/world_scale, h/2/world_scale);

_tempFixtureDef.shape = _tempPolygonDef;

_tempFixtureDef.friction = 0.5;

_tempFixtureDef.density = 1;


_tempBodyDef.position.Set(px / world_scale, py / world_scale);

_tempBodyDef.angle = angle / Math.PI * 2;

_tempBodyDef.type = isDynamic ? b2Body.b2_dynamicBody:b2Body.b2_staticBody;

_tempBody = world.CreateBody(_tempBodyDef);

_tempBody.CreateFixture(_tempFixtureDef);

}

}

}



WRITTEN BY
buzzler

트랙백  0 , 댓글  4개가 달렸습니다.
  1. neonatas 2011.01.18 10:03
    오오.. 브라보. 매일 놀러올테닷.!
  2. 비밀댓글입니다
    • 아시겠지만.. 그 에러는 안정화의 문제와 상관없이.. 사용하는 b2PolygonShape 클래스가 import되지 않았을때 발생하는 에러입니다. import 구문이 있는데 에러가 났다면 프로젝트 속성에서 box2d 의 라이브러리 경로가 잘못되었을때 발생한다고 추측할 수 있겠습니다.
secret
Fixtures
가장 큰 변화들 중 하나는 어떻게 shape이 동작하는가 이다. density(밀도), friction(마찰) 같은 shape의 제질 속성은 기하 속성(radius, vertice..)들과 분리되었다.

body를 만든 이후, material(재질) 속성을 위한 fixture definition을 생성해야 한다. fixture에 shape를 설정하고 b2Body.CreateFixture 를 호출한다.
1 var fd:b2FixtureDef = new b2FixtureDef();
2 fd.density = 1.0;
3 fd.shape = new b2CircleShape(5.0);
4 var fixture:b2Fixture = myBody.CreateFixture(fd);
편의를 위해서, b2Body.CreateFixture2 함수는 shape와 density를 직접 매개변수로정하여 b2FixtureDef를 생성하는것을 생략할 수 있게 해준다. 
1 var fixture:b2Fixture = myBody.CreateFixture2(newb2CircleShape(5.0), 1.0);
b2CircleShape와 b2PolygonShape는 직접적으로 사용됨을 메모하자. 그것들은 b2ShapeDef가 필요없다.

Body types
2.0 버전에서, static, dynamic 두 종류의 body가 있었다. body가 static인지 아닌지에 따라 질량이 결정되어졌다. 이번 버젼에서는 body가 움직이지만 다른 body에 영향받지 않는 kinematic 이라는 새로운 타잎의 body가 추가되었다. 이를 사용하기 위해서, body의 타입을 b2BodyDef.type 과 b2Body.SetType을 사용하여 정확하게 설정해야만 한다.
1 var bd:b2BodyDef = new b2BodyDef();
2 bd.type = b2Body.b2_dynamicBody;

Events
2.0버전의 충돌 이벤트 시스템은 사용하기 매우 어려웠었고, 이는 새로 교체되었다. 이벤트는 contact point별로가 아닌, contact 단위로 발생된다. 그것들은 contact point를 포함한 많은 정보를 담고있다. BeginContact 와 EndContact는 두 물체의 충돌 시작과 끝의 시점을 쉽게 측정할수 있도록 제공되고, sensor에 의새서 발생되는 유일한 이벤트이다. 자세한 내용은 레퍼런스 문서를 참조하라.

Initialization
2.1버전은 전체적으로 완전히 제작성 되었다. world의 사이즈를 지정할 필요가 없어졌으며, 충분히 커질만큼 커질것이다.

Game loop
최종 버전에서는 변할지도 모르지만, b2World.Step이후 즉시 b2World.ClearForces 를 호출할 필요가 없어졌다. 게임로직상의 한 스탭에 여러번의 b2World.Step을 사용하길 원하는 사용자를 위해서 안정성에 기여하기위한 목적을 가진 기능이다. 또한 디버그 드로잉을 사용하고자 한다면 b2World.DrawBedugDate()를 호출할 필요가 있을지도 모른다.

Restrictions
몇몇 제약사항을 걷어냈다.
  • world 의 바운더리가 없어졌다 
  • polygon의 꼭지점 개수 제한이 없다. 원하는 만큼 얇게 만들 수도 있지만 여전히 convex(역자주:모든 꼭지점의 내각이 180도 미만)여야한다. 좋은 모서리 형태의 shape을 만들기 위해 단 두개의 꼭지점도 유효한 다각형으로 만들 수 있다.
  • 몇몇 내부적 제약도 사라졌다는 말은 더 이상 assertion failure 때문에 당황하지 않아도 된다는 의미이다.

WRITTEN BY
buzzler

트랙백  0 , 댓글  0개가 달렸습니다.
secret
8. Joints
8.1. About
joint는 body를 world에서 또는 body끼리 서로 제약하기 위해 사용된다. 게임에서 그 전형적인 예로 ragdoll, teeter, pulley 들이 포함된다. joint는 흥미로운 움직임을 만들기 위해서 다양한 방법으로 합쳐질 수 있다.

어떤 joint는 한계치를 제공하여 움직임의 범위를 제어할 수 있다. 어떤 joint는 정해진 force나 torque가 넘을때까지 정해진 속도로 돌아가도록 하기위해 모터를 제공한다

joint motor는 매우 다양한 방식으로 사용될 수 있다. 포지션을 제어하기 위해서 실좌표와 예정 좌표간의 차이에 비례하는 joint velocity를 지정하여 모터를 사용할 수있다. joint friction을 시뮬레이팅하기 위해서 모터를 사용할 수도 있다. joint velocity를 0으로 설정하고 작지만 매우 큰 최대 모터의 force나 torque를 제공한다. 그러면 모터는 joint를 load가 너무 커지기 전까지의 움직임으로부터 유지하려고 시도할 것이다.

8.2. The Joint Definition
joint 타입은 각각 b2JointDef으로부터 얻은 definition을 가지고 있다. 모든 joint들은 두 body 사이를 연결한다. 하나가 static이 될것이다. 만약 메모리를 낭비하고 싶다면, 두 static body사이에 joint를 생성하면 된다. : )

어떤 joint type에도 사용자 데이터를 설정할 수 있고 첨부된 body들끼리 서로 충돌하는것을 예방하기 위해서 플래그를 제공할 수 있다. 실제 이는 기본 기능이고 당신은 collideConnected boolean값을 연결된 두 body가 서로 충돌하길 허용하도록 설정 해야한다.

많은 joint definition은 당신이 기하 데이터를  제공하기를 필요로 한다. 종종 joint는 anchor point에 의해서 정의되기도 한다.  이것들은 첨부된 body에 고정된 점이다. Box2D는 로컬 좌표계에 명시되어지는 이러한 점들을 요구한다. 이리하여 joint는 현제 body transform이 (게임이 세이브되고 다시 로딩될때 공통적으로 발생하는) joint constraint를 벗어날 때 조차 지정될 수 있다. 추가적으로, 어떤 joint definition은 body들 사이의 기본 사잇각을 알기를 필요로한다. 이는 joint limit 이나 고정된 각도를 통해서 정확하게 회전을 제한하기 위해 필수이다.

기하 데이터를 초기화하는것은 귀찮을 수 있으므로 많은 joint는 현제 body transform을 제거하기 위해 사용되는 초기화 함수를 가지고 있다. 그러나 이러한 초기화 함수들은 일반적으로 프로토타이핑을 위해서만 사용되어야 한다. 제작 코드는 기하 데이터를 직접적으로 정의해야한다. 이것은 joint 기능을 더욱 탄탄하게 만들 것이다.

jooint definition의 나머지 데이타는 다음번에 다뤄질 joint type에 따라서 달라진다.

8.3. Distance Joint
가장 단순한 joint중 하나는 두 body에 있는 두점 사이의 거리를 제한해주는 distance joint이다. 두 body에 distance joint를 지정할때, 두 body는 이미 생성되어있어야한다. 그런다음 world 좌표계에 두 anchor point를 지정한다. 첫번째 anchor point는 1번 body에 연결되어지고, 두번째 anchor point는 2번 body에 연결된다. 이러한 포인트는 제한 거리의 길이를 의미한다.

distance joint definitiondml 예제가 있다. 이러한 경우 우리는 body들이 충돌하는것을 허용할지 결정한다.
1 var jointDef:b2DistanceJointDef = new b2DistanceJointDef();
2 jointDef.Initialize(myBody1, myBody2, worldAnchorOnBody1, worldAnchorOnBody2);
3 jointDef.collideConnected = true;

8.4. Revolute Joint
종종 hinge point(경첩) 라고 불리는 revolute joint는 두 body가 하나의 공동된 anchor point를 공유한다. revolute joint는 두 body의 상대적 회전의 자유로움에 있어서 단일 각도를 가진다. 이를 joint angle이라고 부른다.

revolute를 지정하기 위해서는 world에 존재하는 두개의 body와 하나의 anchor point를 필요로 한다. 초기화 함수는 body들이 정확한 포지션에 있는지 확인한다.

이 예제에서는, 두 body가 하나의 첫번째 body의 질량 중점의 revolute joint에 의해 연결되어있다.
1 var jointDef:b2RevoluteJointDef = new b2RevoluteJointDef();
2 jointDef.Initialize(myBody1, myBody2, myBody1->GetWorldCenter());
body2가 angle point에 대해 반시계방향으로 회전할 때 revolute joint angle은 양수이다. Box2D의 모든 각도들과같이, revolute angle도 라디안값으로 측정한다. joint가 Initialize함수에 의해 생성될 때 관례적으로 현제 두 body의 회전에 상관없이 revolute joint angle은 0 이다.

어떤경우에는 joint angle을 제어하길 원할지도 모른다. 이런 경우, revolute joint는 부가적으로 joint limit 을 시뮬레이션하면서 동시에 motor로 시뮬레이션 할 수 있다.

joint limit은 joint angle을 최저, 최고 한계치 사이를 유지하도록 한다. limit은 이를 발생시키도록 만드는것이 필요로 한 만큼 많은 토크를 적용할 것이다. 그 한계 범위는 0을 포함하는 반면 joint는 시뮬레이션을 시작할 때 요동칠 것이다(lurch).

joint motor는 joint speed를 지정할 수 있게 해준다. 속도는 음수일 수도 있고 양수일 수도 있다. motor는 무한한 힘을 가질 수 있지만 이는 보통 바람직하진 않다. 다음과 같은 표현을 들어본 적 있을 것이다.

주의

"움직이지 않는 물체(immovable object)가 압도적인 힘(irresistible force)을 만나면 어떤 일이 벌어질까?"

이는 그다지 귀엽지 않다. 그래서 최대 토크를 joint motor에 제공할 수 있다. joint motor는 요구되는 토크가 지정된 최대치를 넘어가지 않는한 지정된 속도를 유지할 것이다. 최대 토크가 초과되는 순간, joint는 속도를 줄일 것이고 심지어 거꾸로 돌아갈 것이다.

joint friction을 시뮬레이팅 하기 위해서 joint motor를 사용할 수 있다. joint speed를 0으로 셋팅하고 최대 토크를 작은 값으로 두지만 중요한 값이다. 그 motor는 joint를 회전하지 못하게 시도할 것이지만 중요한 로드에 양보할 것이다.

여기에 위에서 말한 revolute joint 의 정의에 대한 수정안이 있다. 이 순간 joint는 limit과 활성화된 motor를 가지고 있다. 그 motor는 joint friction을 시뮬레이팅하기 위해 설정되었다.
1 var jointDef:b2RevoluteJointDef = new b2RevoluteJointDef();
2 jointDef.Initialize(body1, body2, myBody1.GetWorldCenter());
3 jointDef.lowerAngle     = -0.5 * b2Settings.b2_pi; // -90 degrees
4  
5 jointDef.upperAngle     = 0.25 * b2Settings.b2_pi; // 45 degrees
6 jointDef.enableLimit    = true;
7 jointDef.maxMotorTorque = 10.0;
8 jointDef.motorSpeed     = 0.0;
9 jointDef.enableMotor    = true;

8.5. Prismatic Joint
prismatic joint는 특정 축을 따라서 두 body의 위치이동을 해준다(역자주: 미닫이문을 연상하면 된다). prismatic joint는 회전을 억제한다.

prismatic joint definition은 revolute joint 설명글과 매우 유사하다. 각도 대신 위치이동, 그리고 힘 대신 토크로 바꾸면 된다. 이러한 유사점을 이용하여 joint limit과 friction motor가 있는 prismatic joint definition 예제를 제공한다.
01 var worldAxis:b2Vec2 = new b2Vec2(1.00.0);
02  
03 var jointDef:b2PrismaticJointDef = new b2PrismaticJointDef();
04 jointDef.Initialize(myBody1, myBody2, myBody1.GetWorldCenter(), worldAxis);
05 jointDef.lowerTranslation= -5.0;
06 jointDef.upperTranslation= 2.5;
07 jointDef.enableLimit     = true;
08 jointDef.maxMotorForce   = 1.0;
09 jointDef.motorSpeed      = 0.0;
10 jointDef.enableMotor     = true;
revolute joint는 스크린밖으로 뻗은 내장된 축을 가지고 있다. prismatic joint는 화면과 평행하게 드러난 축이 필요하다. 이 축은 두 body에 고정되고 그들의 움직임을 따른다.
revolute joint 처럼 Initialize() 함수를 사용하여 joint가 생성될때 위치이동값은 0으로 셋팅된다. 그러므로 당신의 최저, 최대 위치이동값 사이에 0이 있어야 함을 명심하라.

8.6. Pulley Joint
pulley는 이상화된 도르래를 만들기 위해서 사용된다. 도르래는 두 body를 ground에서 각각의 body로 연결한다. 한 body가 올라가면 다른 하나는 내려온다. 도르래 줄의 총 길이는 초기 설정에 따라서 보존된다.

length1 + length2 == constant

block과 tackle을 시뮬레이팅하는 비율을 제공할 수 있다. 이는 도르래의 한쪽을 반대쪽보다 빠르게 팽창하는 것을 유발한다. 동시에 통제하는 힘은 첫번째 쪽이 다른곳보다 작아진다. 당신은 기계적인 지렛대를 생성하는것에 사용할 수 있다.

length1 + ratio * length2 == constant

예를들면, 비율이 2라면 length1은 legnth2보다 두배로 달라진다. 또한 body1에 첨부된 로프에 실리는 그 힘은 body2에 첨부된 로프에 실리는 constraint force 절반이 될 것이다.

pulley는 한쪽이 완전히 팽창했을때 골칫거리가 될 수 있다. 반대쪽의 로프는 길이가 0이 될것이다. 이 지점에 constraint 방정식은 단수가 된다. 그러므로 pulley joint는 양측이 나타낼 수 있는 최대 길이를 제한한다. 또한, 게임 플레이상의 이유로 최대 길이를 제어하길 원할지 모른다. 그래서 최대 길이는 안정성을 향상시키고 더 많은 제어권을 당신에게 줄 것이다.

pulley definition의 예제이다.
01 var anchor1:b2Vec2 = myBody1.GetWorldCenter();
02 var anchor2:b2Vec2 = myBody2.GetWorldCenter();
03 var groundAnchor1:b2Vec2 = new b2Vec2(p1.x, p1.y + 10.0);
04  
05 var groundAnchor2:b2Vec2 = new b2Vec2(p2.x, p2.y + 12.0);
06 var ratio:Number 1.0;
07  
08 var jointDef:b2PulleyJointDef = new b2PulleyJointDef();
09 jointDef.Initialize(myBody1, myBody2, groundAnchor1, groundAnchor2, anchor1, anchor2, ratio);
10 jointDef.maxLength1 = 18.0;
11 jointDef.maxLength2 = 20.0;

8.7. Gear Joint
정교한 메카닉 기계를 만들기 원한다면 gear를 사용하길 원할것이다.  Box2D에선 원칙적으로 기어 이빨 모델에 복합적인 shape를 사용함으로써 gear를 만들 수 있다. 이는 그리 효율적이지 않을뿐더러 제작하기 귀찮다. 또한 gear를 가지런히 해야 이빨이 부드럽게 맞물린다. Box2D는 gear(gear joint)를 만들기위한 간편한 함수를 가지고 있다.

gear joint는 경첩(revolute)이나 미닫이(prismatic) 형식의 joint에 의해서 ground body와 연결된 두 body를 필요로 한다. 저런 joint type들의 어떠한 조합이라도 사용할 수 있다. 또한, Box2D는 ground형태의 body1에서 생성된 revolute joint와 prismatic joint를 요구한다.

pulley ratio 와 같이, gear ratio를 지정할 수 있다. 하지만, 이경우 gear ratio는 음수가 될 수 있다. 또한 한 joint가 revolute joint이고 다른쪽은 prismatic joint라면 gear ratio는 단위 길이나 1이상의 길이를 가질 수 있다는것을 명심하라.

coordinate1 + ratio * coordinate2 == contant

gear joint의 예제이다
1 var jointDef:b2GearJointDef = new b2GearJointDef();
2 jointDef.body1 = myBody1;
3 jointDef.body2 = myBody2;
4 jointDef.joint1 = myRevoluteJoint;
5 jointDef.joint2 = myPrismaticJoint;
6 jointDef.ratio = 2.0 * b2Settings.b2_pi / myLength;
gear joint는 두개의 다른 joint에 의존한다는것을 메모하자. 이는 허술한 상황을 만들어낸다. 만약 저런 joint들이 삭제되면 어떤일이 벌어질까?

경고

언제나 gear에 있는 revolute/prismatic joint보다 먼저 gear joint가 삭제하라. 그렇지 않으면 당신의 코드는 gear joint내에 엉뚱한곳을 참조하는 joint 포인터 때문에 나쁜 방향으로 망가져 버릴것이다. 관련된 body들 또한 gear joint보다 먼제 삭제되어서는 안된다.

8.9. Mouse Joint
mouse joint는 테스트베드에서 마우스로 body들을 다루기 위해 사용된다. 상세설명은 testbed와 b2MouseJoint.as 을 읽어보길 권한다.

8.9. Joint Factory
joint는 world factory 함수들을 이용해서 만들어지고 삭제된다. 이는 오래된 이슈를 떠올린다.

주의

body와 joint를 new 문법으로 직접 생성하려고 시도하지 말라. 반드시 b2World 클래스의 함수들을 통해서 생성하고 삭제해야한다.

이것은 revolute joint의 라이프타임 예제이다.
1 var jointDef:b2RevoluteJointDef = new b2RevoluteJointDef();
2 jointDef.body1 = myBody1;
3 jointDef.body2 = myBody2;
4 jointDef.anchorPoint = myBody1.GetCenterPosition();
5  
6 var joint:b2RevoluteJoint = myWorld.CreateJoint(jointDef);
7 ... do stuff ...
8 myWorld.DestroyJoint(joint);
9 joint = null;
그것들을 삭제한 이후에 포인터를 null로 비워두는것이 좋다. 이는 포인터를 제사용하려 시도했을때 통제된 형식의 프로그램 충돌을 만들어낼 것이다.

joint의 라이프타임은 간단하지 않다. 이런 경고를 잘 새겨들어라:

주의

joint는 첨부된 body가 삭제될때 같이 삭제된다.

이 예방조치가 항상 필수인것은 아니다. 당신은 게임 엔진을 조직화하여 body 전에 joint 가 항상 삭제되게끔할지도 모른다. 이런경우 당신은 리스너 클래스를 구현할 필요가 없다. 자세히 알고싶다면 10.2장 Implicit Destruction 을 보라.

8.10. Using Joints
많은 시뮬레이션은 joint들을 만들고 삭제될때까지 다시 접근하지 않는다. 그러나 joint에 포함된, 질좋은 시뮬레이션을 만드는데 사용할 수 있는 유용한 데이타가 많이 있다.

그중 첫 번째로 joint로 부터 body, anchor point, 사용자 데이터들을 얻어올 수 있다.
1 GetBody1():b2Body;
2 GetBody2():b2Body;
3 GetAnchor1():b2Vec2;
4 GetAnchor2():b2Vec2;
5 GetUserData():*;
모든 joint들은 reaction force(반동력)와 reaction torque(반동회전력) 둘다 가지고 있다. anchor point에서 이것들은 body2에 적용되어진다. 반동력을 joint를 깨뜨리는데 사용하거나 다른 게임 이벤트의 트리거로 사용할 수 있다. 이런 함수들은 어느정도의 연산을 해야하므로 결과값이 필요한 경우가 아니라면 호출을 하지 않는것이 좋다.
1 function