This is what I’m using for Tiled -> Box2d-body conversion. I translated a big part of this logic from another game library (HaxeFlixel), where it’s also used for Tiled -> physics-body conversion, but for another physics library (which I don’t think is being maintained today). I have some code for adding sloped (45 degree, upper- and lower parts) tiles in another project, but I haven’t yet brought that over to this one yet, so it’s not here atm.
Btw, I use 16x16 pixels for my tiles.
Here’s the code I ported: https://github.com/HaxeFlixel/flixel-addons/blob/master/flixel/addons/nape/FlxNapeTilemap.hx
First, in your init()
std::vector<unsigned int> wallsAndFloors = {};
std::vector<unsigned int> slopesTL = { 1926 };
std::vector<unsigned int> slopesTR = { 2037 };
std::vector<unsigned int> slopesBL = { 1889 };
std::vector<unsigned int> slopesBR = { 2000 };
std::vector<unsigned int> exludeIndices = {};
getTileIndexes(wallsAndFloors, EntityType::COLLIDABLE_TILES_START, EntityType::COLLIDABLE_TILES_END, exludeIndices, false);
setupTileIndices(wallsAndFloors, map->getLayer(LAYER_NAME_FOREGROUND));
// addSlopes(slopesTL, slopesBL, slopesBR, slopesTR, map->getLayer(LAYER_NAME_FOREGROUND));
Where the magic happens
void PlayState::getTileIndexes(std::vector<unsigned int>& indexes, unsigned int lowerLimit, unsigned int upperLimit, std::vector<unsigned int>& exclude, bool includeUpper) {
if (includeUpper)
upperLimit++;
if (exclude.size() == 0) {
for (size_t i = lowerLimit; i < upperLimit; i++) {
indexes.push_back(i);
}
} else {
for (size_t i = lowerLimit; i < upperLimit; i++) {
if (std::find(exclude.begin(), exclude.end(), i) == exclude.end()) {
indexes.push_back(i);
}
}
}
}
void PlayState::setupTileIndices(std::vector<unsigned int> &tileIndices, TMXLayer* tmxLayer) {
int tileIdx = 0;
auto layerSize = tmxLayer->getLayerSize();
std::vector<unsigned int> _binaryData;
uint32_t* tiles = tmxLayer->getTiles();
for (size_t y = 0; y < layerSize.height; y++) {
for (size_t x = 0; x < layerSize.width; x++) {
tileIdx = x + (y * layerSize.width);
// make it match the value reported inside Tiled editor
int curTile = tiles[tileIdx]-1;
if (std::find(tileIndices.begin(), tileIndices.end(), curTile) != tileIndices.end()) {
_binaryData.push_back(1);
} else {
_binaryData.push_back(0);
}
}
}
constructCollider(_binaryData, tmxLayer);
}
void PlayState::constructCollider(std::vector<unsigned int> &_binaryData, TMXLayer* tmxLayer) {
int tileIndex = 0;
int startRow = -1;
int endRow = -1;
std::vector<cocos2d::Rect> rects;
//Go over every column, then scan along them
for (size_t x = 0; x < tmxLayer->getLayerSize().width; x++)
{
for (size_t y = 0; y < tmxLayer->getLayerSize().height; y++)
{
tileIndex = x + (y * tmxLayer->getLayerSize().width);
//Is that tile solid?
if (_binaryData[tileIndex] == 1)
{
//Mark the beginning of a new rectangle
if (startRow == -1)
startRow = y;
//Mark the tile as already read
_binaryData[tileIndex] = -1;
}
//Is the tile not solid or already read
else if (_binaryData[tileIndex] == 0 || _binaryData[tileIndex] == -1)
{
//If we marked the beginning a rectangle, end it and process it
if (startRow != -1)
{
endRow = y - 1;
rects.push_back(constructRectangle(x, startRow, endRow, tmxLayer, _binaryData));
startRow = -1;
endRow = -1;
}
}
}
//If we reached the last line and marked the beginning of a rectangle, end it and process it
if (startRow != -1)
{
endRow = tmxLayer->getLayerSize().height - 1;
rects.push_back(constructRectangle(x, startRow, endRow, tmxLayer, _binaryData));
startRow = -1;
endRow = -1;
}
}
//Convert the rectangles to polygons
for (size_t r = 0; r < rects.size(); r++)
{
auto rect = rects.at(r);
auto w = (rect.size.width - rect.origin.x) + 1;
auto h = (rect.size.height - rect.origin.y) + 1;
rect.setRect(
rect.getMinX() * tmxLayer->getMapTileSize().width,
rect.getMinY() * tmxLayer->getMapTileSize().height,
w * tmxLayer->getMapTileSize().width,
h * tmxLayer->getMapTileSize().height
);
unsigned int numPts = 4;
b2Vec2* vertices = new b2Vec2[numPts];
auto mapMaxY = map->getBoundingBox().getMaxY();
vertices[0] = b2Vec2(rect.getMinX() / PTM_RATIO, (mapMaxY - rect.getMaxY()) / PTM_RATIO);
vertices[1] = b2Vec2(rect.getMaxX() / PTM_RATIO, (mapMaxY - rect.getMaxY()) / PTM_RATIO);
vertices[2] = b2Vec2(rect.getMaxX() / PTM_RATIO, (mapMaxY - rect.getMinY()) / PTM_RATIO);
vertices[3] = b2Vec2(rect.getMinX() / PTM_RATIO, (mapMaxY - rect.getMinY()) / PTM_RATIO);
auto stageMaterial = getFixtureDef(StageComponents::wallsAndFloors);
makePoly(vertices, numPts, b2BodyType::b2_staticBody, &stageMaterial);
/* 3 <<<<<<<<< 2
v ^
v ^
v ^
v ^
start 0 >>>>>>>>> 1 */
}
}
b2Body* PlayState::makePoly(b2Vec2* vertices, int32 count, b2BodyType type, b2FixtureDef *fixDef) {
b2BodyDef bDef;
bDef.type = type;
b2Body* b = world->CreateBody(&bDef);
b2PolygonShape polygonShape;
polygonShape.Set(vertices, count);
b2FixtureDef fixtureDef;
fixtureDef.shape = &polygonShape;
if (type == b2BodyType::b2_staticBody)
{
fixtureDef.filter.categoryBits = FilterCategory::CT_STAGE;
}
b->CreateFixture(&fixtureDef);
delete[] vertices;
return b;
}
Rect PlayState::constructRectangle(int startX, int startY, int endY, TMXLayer* tmxLayer, std::vector<unsigned int> &_binaryData)
{
//Increase StartX by one to skip the first column, we checked that one already
startX++;
bool rectFinished = false;
int tileIndex = 0;
auto layerSize = tmxLayer->getLayerSize();
//go along the columns from StartX onwards, then scan along those columns in the range of StartY to EndY
for (size_t x = startX; x < layerSize.width; x++)
{
for (size_t y = startY; y < (endY + 1); y++)
{
tileIndex = x + (y * layerSize.width);
//If the range includes a non-solid tile or a tile already read, the rectangle is finished
if (_binaryData[tileIndex] == 0 || _binaryData[tileIndex] == -1)
{
rectFinished = true;
break;
}
}
if (rectFinished)
{
//If the rectangle is finished, fill the area covered with -1 (tiles have been read)
for (size_t u = startX; u < x; u++)
{
for (size_t v = startY; v < (endY + 1); v++)
{
tileIndex = u + (v * layerSize.width);
_binaryData[tileIndex] = -1;
}
}
//StartX - 1 to counteract the increment in the beginning
//Slight misuse of Rectangle here, width and height are used as x/y of the bottom right corner
return Rect(startX - 1, startY, x - 1, endY);
}
}
//We reached the end of the map without finding a non-solid/alread-read tile, finalize the rectangle with the map's right border as the endX
for (size_t u = startX; u < layerSize.width; u++)
{
for (size_t v = startY; v < (endY + 1); v++)
{
tileIndex = u + (v * layerSize.width);
_binaryData[tileIndex] = -1;
}
}
return Rect(startX - 1, startY, layerSize.width - 1, endY);
}