Nape, Box2D test with Starling

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

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.