Engines
Nape 과 Box2D 는 Flash 에서 사용 할 수 있는 물리엔진입니다. 이 들은 Flash의 Display체계와는 독립적으로 동작하므로 Starling 에서도 바로 사용 할 수 있습니다. Nape 은 ActionScript 와 Haxe 에서 사용 할 수 있으며 개발자에게 좀 더 친화적인 API를 제공합니다. Box2D는 유명한 C++ 물리엔진으로 Flash로 포팅 된 버전 입니다. 각 버전은 아래 링크에서 받을 수 있습니다.
Nape : http://napephys.com/downloads.html
Box2D : http://www.box2dflash.org/download
그리고 Starling 과 각 물리엔진등을 모두 모아놓은 Citrus 엔진이 있습니다. Citrus 는 게임개발에 필요한 각종 프레임웍을 모아놓은 종합선물 세트 같은 엔진입니다. Adobe Gaming SDK 에 이 엔진이 포함됐어야 하지 않나 싶을 정도 입니다. ^^ 여튼 제 예제 파일은 이 Citrus 엔진을 사용하였습니다.
Citrus : http://citrusengine.com/download/
Nape
private function addBall(x:Number, y:Number):void { var ball:Body = new Body(BodyType.DYNAMIC, new Vec2(x, y)); ball.shapes.add(new Circle(_ball.width/2, null, new Material(.5, 2, 2, .5))); ball.space = _world; ball.userData.image = new Image(_ballTexture); ball.userData.image.pivotX = ball.userData.image.width/2; ball.userData.image.pivotY = ball.userData.image.height/2; addChild(ball.userData.image); _count.text = 'Nape - ball : '+(++_ballCount); }
Nape 엔진을 이용하여 DynamicBody 를 생성하는 코드 입니다. BodyType.DYNAMIC 을 선언하여 해당 Body 가 물리에 영향을 받도록 합니다. 그리고 실제로 물리계산에 사용되는 collision shape 을 생성합니다. 예제에서는 Circle 클래스로 원형태를 만들었습니다. 그리고 이 Dynamic Body와 연결되어서 실제로 Starling 화면 상에 보일 이미지를 등록합니다. userData를 이용하여 나중에 데이터를 업데이트 합니다.
private function onEnterFrame(e:Event):void { ... // looping though bodies var bodies:BodyList=_world.bodies; for (var i:int = 0; i < bodies.length; i++) { var body:Body=bodies.at(i); if(body.userData.image!=null){ // adjusting graphic asset position body.userData.image.x=body.position.x; body.userData.image.y=body.position.y; body.userData.image.rotation=body.rotation; } } }
userData에 등록된 실제 이미지를 body 의 값으로 업데이트 하여 실제 Starling 의 화면을 갱신합니다. 이 처럼 물리엔진은 화면상에 보이는 디스플레이 객체의 충돌을 판단하는게 아니라 body 에 등록된 collision shape 으로 계산하고 그 데이터로 화면상에 보이는 이미지를 움직입니다.
public function TestNape() { ... if(_debug){ debug = new ShapeDebug(320, 480); debug.thickness = 2; Starling.current.nativeOverlay.addChild(debug.display); } ... }
debug 는 기존 stage 의 DisplayObject 로 만들어져 있으므로 바로 Starling 에 등록 할 수 없습니다. Starling.current.nativeOverlay.addChild(debug.display); 으로 stage 상에 등록합니다.
Box2D
private function createCircle (x:Number, y:Number, radius:Number, dynamicBody:Boolean):void { var bodyDefinition:b2BodyDef = new b2BodyDef (); bodyDefinition.position.Set (x * PHYSICS_SCALE, y * PHYSICS_SCALE); if (dynamicBody) { bodyDefinition.type = b2Body.b2_dynamicBody; } var circle:b2CircleShape = new b2CircleShape (radius * PHYSICS_SCALE); var fixtureDefinition:b2FixtureDef = new b2FixtureDef (); fixtureDefinition.shape = circle; fixtureDefinition.density = .5; fixtureDefinition.friction = .2; fixtureDefinition.restitution = .5; var ball:Image = new Image(_ballTexture); ball.pivotX = ball.width/2; ball.pivotY = ball.height/2; addChild(ball); var body:b2Body = World.CreateBody (bodyDefinition); body.CreateFixture (fixtureDefinition); body.SetUserData( ball ); }
Box2D 를 이용하여 Dynamic body를 생성하는 코드 입니다. 딱 봐도 Nape 보다 코드양도 많고 뭔가 복잡해 보입니다. Box2D 의 경우 C++ 의 ActionScript port 이므로 많은 부분이 기존의 C++ 과 유사해 보이는 부분이 있습니다. b2로 시작하는 클래스명도 그 중 하나입니다. 조금 복잡해 보이기는 하지만 찬찬히 코드를 보면 Nape 과 크게 다르지 않다는 걸 알 수 있습니다. 먼저 body 를 생성하고 타입을 결정합니다. collision shape 이 될 circle 을 b2CircleShape 로 생성 합니다. b2FixtureDef 는 Nape 의 Material 과 유사한 클래스입니다. body 의 탄성이나 마찰계수 등을 설정 할 수 있습니다. 그리고 마지막으로 Nape 과 동일하게 Starling 의 이미지를 userData 에 등록 합니다.
private function this_onEnterFrame (event:Event):void { ... for(var b:b2Body = World.GetBodyList(); b; b=b.GetNext()) { if (b.GetUserData() != null) { var image:Image = b.GetUserData() as Image; image.x = b.GetPosition().x / PHYSICS_SCALE; image.y = b.GetPosition().y / PHYSICS_SCALE; image.rotation = b.GetAngle(); } } }
월드에서 body를 가져오는 방식이 특이합니다. body 의 GetNext() 함수를 이용하여 재귀적으로 다음 body를 가져 옵니다. 이 또한 C++ 스럽네요. body 에서 가져온 x 값에 PHYSICS_SCALE 라는 상수로 나줘 주는데 Box2D의 경우 position의 값이 Flash 좌표체계와 일치 하지 않습니다. 이를 보정해 주는 역활을 합니다.
private function initialize ():void { ... if(_debug){ PhysicsDebug = new flash.display.Sprite (); Starling.current.nativeStage.addChild (PhysicsDebug); PhysicsDebug.alpha = 0.5; var debugDraw:b2DebugDraw = new b2DebugDraw (); debugDraw.SetSprite (PhysicsDebug); debugDraw.SetDrawScale (1 / PHYSICS_SCALE); debugDraw.SetFlags (b2DebugDraw.e_shapeBit); World.SetDebugDraw (debugDraw); } ... }
마지막으로 Box2D 에서 디버그 화면을 설정하는 부분입니다. Nape 과 마찬가지로 Starling.current.nativeStage.addChild (PhysicsDebug); 와 같이 stage 에 디버그를 등록해서 확인 할 수 있습니다.
Collision Shapes
위에서 살펴본것 같이 각 물리엔진은 DisplayObject 로 충동을 판단 하는 것이 아니라 따로 collision shape 을 만들어 이를 이용하여 충돌을 감지합니다. 보통 원, 사각형 과 같이 단순한 형태를 지원 하는데 작업을 하다보면 다각형을이 필요 할 때가 있습니다. 이미지에 맞게 다각형 collision shape 을 만들어 주는 툴로 PhysicsEditor 가 있습니다. PhysicsEditor 를 이용하면 원하는 형태의 collision shape 을 쉽게 만들 수 있지만 유료입니다. ^^;
PhysicsEditor : http://www.codeandweb.com/physicseditor/download
Conclusion
Starling 에서 Nape 과 Box2D를 사용하는 방법을 살펴 보았습니다. 전체코드는 아래 링크에서 다운 받을 수 있습니다. 실제적으로 물리엔진들은 순수하게 CPU 자원을 사용하므로 Starling 을 사용한 GPU 가속의 이득이 거의 없다고 봐야 합니다. 그러므로 너무 많은 객체를 사용할 경우 데모 영상처럼 성능이 현저하게 나빠지는 것을 알수 있습니다. 제가 테스트해본 결과 Box2D의 경우 대략 70개 Nape 의 경우 100개 정도가 한계 인 것 같습니다. 좀더 그럴듯한 뭔가를 만들어 내려면 성능 향상이 불가피 할 것 같네요. 여튼 결론은 Starling 에서 물리엔진을 사용하시려면 Nape을 쓰세요~~! ^^
Download StarlingNapeBox2d.zip
see also
Adobe AIR vs Corona SDK on iPhone 3GS
Box2D Alchemy VS Nape, performance test on iPad3