Create Box2D bodies with TMX Objects layer! Cocos2d-x 3.0

Hello there,

I have made a class which allows you to create Box2D bodies for a object layer. I have made this because I have needed this myself and this would be a huge time saver for me and I hope for you also.

This class supports the following objects,

  • Rectangle
  • Circle (You can create it with any shape set “Type” parameter to “Circle” and it creates a Circle fixture based on width as diameter)
  • Polygon (Allowed only per 8 points, I you need more then you have to split it up)
  • Polyline

How it works:

In you tmx editor you have to create a layer called “Collision” and make all the objects in that layer.

Change in TiledBodyCreator.cpp
#define PTMRATIO 64
To your ptmratio

Then in the game code you have to include TiledBodyCreator.h and after you have created Tilemap and Box2d world you can add this code which will create a body with all objects as fixtures in it and add it to world.

TiledBodyCreator::initCollisionMap(map, world);

Here is a small preview of what I have tested
My tiled map in editor

My game with box2d debug

This is the code I have used

this->setAnchorPoint(Point::ZERO);

b2Vec2 gravity;
gravity.Set(0.0f, -10.0f);
auto world = new b2World(gravity);

world->SetAllowSleeping(false);


auto debugLayer = B2DebugDrawLayer::create(world, 64);
this->addChild(debugLayer, 9999);

auto map = TMXTiledMap::create("Test.tmx");
map->setAnchorPoint(Point::ZERO);
map->setPosition(0,0);

this->addChild(map, 3);

TiledBodyCreator::initCollisionMap(map, world);

this->setScale(0.2f);

I hope this helped you a lot. :slight_smile:
The classes are added to attachments of this post.


TiledPreview.PNG (147.8 KB)


GamePreview.PNG (37.7 KB)


TiledBodyCreator.zip (1.8 KB)

you can find the newer version of TiledBodyCreator here

9 Likes

This is very useful. Thank you for sharing.

Great! :slight_smile:

Good Effort mate…

But cant create PolyLine Bodies with it…
Fails at this line in b2ChainSpae.cpp CreateChain method :
b2Assert(m_vertices == NULL && m_count == 0);

Check Screenshot for more Info on error :

@rammehta

Hey, sorry for a late reaction.
Could you mayby check if your tiledmap contains empty polylines or mayby open your tmx file with a text editor and send me collision layer part of it. You can also try to replace createPolyline method in TiledBodyCrator.cpp with this and send me the log:

FixtureDef* TiledBodyCreator::createPolyline(ValueMap object)
{
	ValueVector pointsVector = object["polylinePoints"].asValueVector();
	auto position = Point(object["x"].asFloat() / PTMRATIO, object["y"].asFloat() / PTMRATIO);
	CCLOG("Size of pointsVector: %f", pointsVector.size());

	b2ChainShape *polylineshape = new b2ChainShape();
	float verticesSize = pointsVector.size()+ 1;
	b2Vec2 vertices[30];
	int vindex = 0;

	auto fix = new FixtureDef();

	for(Value point : pointsVector) {
		CCLOG("Initializing vector at index: %d", vindex);
		vertices[vindex].x = (point.asValueMap()["x"].asFloat() / PTMRATIO + position.x);
        vertices[vindex].y = (-point.asValueMap()["y"].asFloat() / PTMRATIO + position.y);
		vindex++;
	}

	polylineshape->CreateChain(vertices, vindex);
	fix->fixture.shape = polylineshape;

	return fix;
}

GOT THE ANSWER…

And verticesSize is never used…

@rammehta
Oh, thats it a actually forgot to remove it. Good job :slight_smile:

Thanks for this thread, it saves me tons of time >:D<

But I suggest you change method createPolyline() as below:

b2Vec2 vertices[30];

–>

int verticesCapacity=32;
b2Vec2* vertices = (b2Vec2*)calloc(verticesCapacity, sizeof(b2Vec2));

and

for(Value point : pointsVector) {
CCLOG(“Initializing vector at index: %d”, vindex);

–>

for(Value point : pointsVector) {
CCLOG(“Initializing vector at index: %d”, vindex);
if(vindex>=verticesCapacity)
{
verticesCapacity+=32;
vertices = (b2Vec2*)realloc(vertices, verticesCapacity*sizeof(b2Vec2));
}

so that you can use polylines which have more than 30 vertices (with origin method app will crash in this case).

Thanks for this tweak. Here is the version with your changes I am sure it will help a lot of people :). I am also glad to hear its useful to you.TileBodyCreator.zip (1.8 KB)

Hello.
if my tiled object with code

 <objectgroup name="Object" width="500" height="20">
  <object x="1976" y="310"/>
  <object x="3495" y="315"/>
  <object x="4716" y="311"/>
.....
 </objectgroup>

In function createRect(ValueMap object) , with and height will have value = 0 and an exception will occur. Please fix and thanks for sharing :smile:

It works like a charm. Kudos!

Where I can find this B2DebugDrawLayer?
I didn’t find it in Box2D library.
Please help

no problem, I found it here.
B2DebugDrawLayer

uhm…
it doesn’t display any debug draw order in 3.5

I added a log and I found it is not working.
It is considered as NULL.

void TiledBodyCreator::initCollisionMap(TMXTiledMap* map, b2World* world)
{
auto collisionGroup = map->getObjectGroup(“coll”);
cocos2d::ValueVector collisionObjects = collisionGroup->getObjects();

b2BodyDef bd;
auto collisionBody = world->CreateBody(&bd);
for(cocos2d::Value objectValue : collisionObjects)
{
    auto fixtureShape = createFixture(objectValue.asValueMap());
    if(fixtureShape != NULL) {
        collisionBody->CreateFixture(&fixtureShape->fixture);
        log("NULL here!");
    }
}

}

no, problem.
it’s my fault.
your code is working, thank you @egordm

Does anyone know why does my box collision on bunny is not aligned with bunny?
I used PTM_RATIO 128.
And the bunny is shaking.
I used

bodyHero->SetLinearVelocity(b2Vec2(0.0f, -2.50f));

The bunny is 128 x 128 pixel.
The location is 640, 360.
I divided the location with PTM_RATIO 128, but still doesn’t match.
It doesn’t align with box2D rect.

Is there a bug in Box2D?

I updated its position:

auto sp = (Sprite *)body->GetUserData();
sp->setPosition(Vec2(body->GetPosition().x * PTM_RATIO, body->GetPosition().y * PTM_RATIO));
sp->setRotation(-1 * CC_RADIANS_TO_DEGREES(body->GetAngle()));
body->SetLinearVelocity(b2Vec2(0.0f, -1.0f));

I know now. This is a bug.
When I tried to use circle, the circle aligns perfectly with the image (bunny).
Problem solved now.
If I used rectangle, I need to multiply the PTM_RATIO to match the body position.

Hi,
I would like to share my experience using this class, for help anyone else :smiley:

[+] The body in this class (for the rect, circle, polyline, polygon) is static (as default), so, if you want to generate collisions be sure to set to dynamic your other bodys. Remember: in box2d, static with static not collides. For move the dynamic body i recommend to use SetLinearVelocity.

[+] If you want to see the fixtures/shapes drawn in the map, you will need to debug using box2d. Here i share a class that works for me using tiled maps + box2d debug: b2DebugDraw.zip (3.7 KB) (thanks @stevetranby). Don’t forget to set the global Z order. Example:

auto debugLayer = B2DebugDrawLayer::create(_world, SCALE_RATIO);
debugLayer->setGlobalZOrder(99);
tileMap->addChild(debugLayer);

[+] I had problems with the sizes in polylines and polygons using cocos2d-x 3.17 and iPhone 7. I discovered that this problem is because the methods are not using content scale factor (CC_CONTENT_SCALE_FACTOR()) in polyline and polygon points. So, i recommend to fix this problem in both methods. This is the final versions that i’m using and it works for me:

FixtureDef* TiledBodyCreator::createPolygon(ValueMap object)
{
	ValueVector pointsVector = object["points"].asValueVector();
	auto position = Point(object["x"].asFloat() / PTMRATIO, object["y"].asFloat() / PTMRATIO);

	b2PolygonShape *polyshape = new b2PolygonShape();
	b2Vec2 vertices[b2_maxPolygonVertices];
	int vindex = 0;

	if(pointsVector.size() > b2_maxPolygonVertices) {
		CCLOG("Skipping TMX polygon at x=%d,y=%d for exceeding %d vertices", object["x"].asInt(), object["y"].asInt(), b2_maxPolygonVertices);
		return NULL;
	}

	auto fix = new FixtureDef();

	for(Value point : pointsVector) {
		vertices[vindex].x = ((point.asValueMap()["x"].asFloat() / PTMRATIO)/CC_CONTENT_SCALE_FACTOR() + position.x);
        vertices[vindex].y = ((-point.asValueMap()["y"].asFloat() / PTMRATIO)/CC_CONTENT_SCALE_FACTOR() + position.y);
		vindex++;
	}

	polyshape->Set(vertices, vindex);
	fix->fixture.shape = polyshape;

	return fix;
}



FixtureDef* TiledBodyCreator::createPolyline(ValueMap object)
{
	ValueVector pointsVector = object["polylinePoints"].asValueVector();
	auto position = Point(object["x"].asFloat() / PTMRATIO, object["y"].asFloat() / PTMRATIO);

 	b2ChainShape *polylineshape = new b2ChainShape();
	
	int verticesCapacity=32;
	b2Vec2* vertices = (b2Vec2*)calloc(verticesCapacity, sizeof(b2Vec2));

	int vindex = 0;

	auto fix = new FixtureDef();

	for(Value point : pointsVector) {
        
 		if(vindex>=verticesCapacity)
		{
			verticesCapacity+=32;
			vertices = (b2Vec2*)realloc(vertices, verticesCapacity*sizeof(b2Vec2));
		}
        
    	vertices[vindex].x = ((point.asValueMap()["x"].asFloat() / PTMRATIO)/CC_CONTENT_SCALE_FACTOR()  + position.x);
        vertices[vindex].y = ((-point.asValueMap()["y"].asFloat() / PTMRATIO)/CC_CONTENT_SCALE_FACTOR()  + position.y );
		vindex++;
	}

	polylineshape->CreateChain(vertices, vindex);
	fix->fixture.shape = polylineshape;

	return fix;
}

Hope it helps!
Thanks.

1 Like