cocos2d-x + Box2d PhysicsEditorで作ったデータを読み込む

cocos2d-xでBox2dを使う - おかひろの雑記 で、Cocos2d-xでBox2dを使う設定をしましたが、
実際にゲームで使いたい物理オブジェクトの形は複雑なことが多いので、
物理体の座標を個別に設定するのは相当難しいと思います。

なので、PhysicsEditor(https://www.codeandweb.com/physicseditor)を使って物理体の定義を行い、そのデータをCocos2d-xで読み込むようにしてみます。

Cocos2d-x側でPhysicsEditorのデータを読み込むには、GitHub - tks2shimizu/GB2ShapeCache-x-for-cocos2d-x-3.x: GB2ShapeCache-x for cocos2d-x 3.xを使うと良いと思います。

今回使用したバージョン

  • Cocos2d-x 3.9
  • PhysicsEditor 1.5.2

動作するサンプルはこちら。 https://github.com/okahiro/Cocos2dxBox2d

準備
GB2ShapeCache-xをダウンロードし、プロジェクトに追加します。

そのままではコンパイルが通らないので、includeの部分を修正します。

修正箇所

#include "cocoa/CCNS.h"#include "CCNS.h"

また、addShapesWithFileメソッドでメモリリークしてるっぽいので、最後にdelete dict;を追加します。
(間違っていたらすみません)

     ....

            } else if (fixtureType == "CIRCLE") {
                auto fix = new FixtureDef();
                fix->fixture = basicData; // copy basic data
                fix->callbackData = callbackData;
                
                auto circleData = (Dictionary *)fixtureData->objectForKey("circle");
                
                auto circleShape = new b2CircleShape();
                
                circleShape->m_radius = static_cast<String *>(circleData->objectForKey("radius"))->floatValue() / ptmRatio;
                auto p = PointFromString(static_cast<String *>(circleData->objectForKey("position"))->getCString());
                circleShape->m_p = b2Vec2(p.x / ptmRatio, p.y / ptmRatio);
                fix->fixture.shape = circleShape;
                
                // create a list
                *nextFixtureDef = fix;
                nextFixtureDef = &(fix->next);
                
            } else {
                CCAssert(0, "Unknown fixtureType");
            }
            
            // add the body element to the hash
            shapeObjects[bodyName->getCString()] = bodyDef;
            
        }
        
    }
	
    delete dict;  // これを追加
}

plistファイルの作成
PhysicsEditorを起動し、物理体を定義します。

サンプルで4つの物理体を作成しました。
"circle"、"cross"、"square"、"triangle"などの名前は後で使うので、きちんとした名前をつけておく必要があります。

Publishでplistを作成します。

plistファイルの読み込み
initメソッドでplistの読み込みを行います。

// 初期化
bool Box2dAndPEScene::init()
{
	if ( !Layer::init() )
	{
		return false;
	}
	
	Size winSize = Director::getInstance()->getWinSize();
	
	// 物理設定
	b2Vec2 gravity;
	gravity.Set(0.0f, -25.0f);	// 重力の値は動きを見ながら調整
	
	// World作成
	_world = new b2World(gravity);
	_world->SetAllowSleeping(true);
	_world->SetContinuousPhysics(true);
	
	// PhysicsEditorから出力したplistを読み込み
	gbox2d::GB2ShapeCache::getInstance()->addShapesWithFile("res/shapes.plist");
	
	// 画面をタップしたら
	auto listenerForSprite = EventListenerTouchOneByOne::create();
	listenerForSprite->setSwallowTouches(true);
	listenerForSprite->onTouchBegan = [=](Touch* touch, Event* event)
	{
		Vec2 pos = touch->getLocation();
		// ブロックを作成
		this->createBlock(pos.x, pos.y);
		
		return true;
	};
	Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listenerForSprite, this);
	
	// 地面を作成
	this->createGround();
	
	// updateメソッドを開始
	this->scheduleUpdate();
	
	return true;
}

物理体作成
大分シンプルになりました。
PhysicsEditor上でつけた名前を指定して物理体を作成します。

// ブロックを作成する
void Box2dAndPEScene::createBlock(int x,int y)
{
	_blockNo++;
	
	// ブロックの名前(PhysicsEditer上でつけた名前)
	std::string blockName = "";
	// Noによってブロックを変える
	switch(_blockNo % 4)
	{
		case 0:
			blockName = "circle";
			break;
		case 1:
			blockName = "cross";
			break;
		case 2:
			blockName = "square";
			break;
		case 3:
			blockName = "triangle";
			break;
		default:
			blockName = "circle";
			break;
	}
	
	// Sprite
	Sprite *block = Sprite::create(StringUtils::format("res/%s.png",blockName.c_str()));
	block->setTag(_blockNo);		// ブロックに番号をつける
	this->addChild(block);
	
	b2BodyDef bodyDef;
	bodyDef.type = b2_dynamicBody;
	bodyDef.position.Set(x / PTM_RATIO,y / PTM_RATIO);
	
	b2Body *body = this->_world->CreateBody(&bodyDef);
	gbox2d::GB2ShapeCache::getInstance()->addFixturesToBody(body, blockName);	// bodyに、plist上の指定の物理体情報でFixture作成
	body->SetUserData(block);
	
	// AnchorPointを取得して設定
	block->setAnchorPoint(gbox2d::GB2ShapeCache::getInstance()->anchorPointForShape(blockName));
	
	
	CCLOG("■ブロック%dを作成しました。",_blockNo);
}

これで、画面をタップすると十字、正方形、三角形、円のオブジェクトが順番にでてくるようになりました。