TMXLayer::getPositionAt returns wrong values for Isometric map?

I’m a bit confusing by the getPositionAt method behavior when it is called upon isometric TMX. As far as I know this method should return the screen coordinates of the tile you reference as parameter.
Here is my code where I simply try to place a duck over a specific tile (eg. row 0, col 5).

//TMX map
auto map = TMXTiledMap::create("farmer/isometric_grass_and_water.tmx");
this->addChild(map, 0);
auto layer = map->getLayer("Tile Layer 1");
Vec2 pos = layer->getPositionAt(Vec2(0,5));    
Sprite *duck = Sprite::create("farmer/duck.png");
duck->setPosition(pos);
this->addChild(duck);

But the result isn’t what I expected as you can see by the following screenshot:

My goal is to get the screen coordinates of the map tiles to move my character over them.

Couple things:

  1. The position should be correct and I think it’s your duck’s anchor point that is causing it to look incorrect.
  2. You may or may not want to add the duck as a child to the map instead of attaching it to the scene or layer if you want to move the tile map.

Try setting your duck’s anchorpoint to position it based on its feet location.
duck->setAnchorPoint(Vec2(.5f,.1f));

Thanks for your reply steve, unfortunately the anchorPoint improve the situation, but it doesn’t fix it. The position is still incorrect, now exactly for just 1 more column. I set the row 0, col 5 position for the duck and I found it in row 0, but column 6.

In addition, I don’t understand why if I change the map position the getPositionAt method returns always the same coordinates, like if the map is even in the default position ( I well notice the new position on the scene).

getPositionAt returns the world position with respect to the map. So if you ask for tile 0,5 then you’ll always get the same position. This is a good reason to attach the duck to the map, if you want the duck to move with the map if you change the map’s position, unless there’s reasons you don’t want that?

Try adding the duck as child to the map, see if the position is more accurately on 0,5.

If you move the map then the screen coord of a given tile will be different depending on the maps position (including any position changes in the parents of the map that are also children of the scene). You need to either use one of the worldToScreen node transforms or offset the world position of the duck by the position of the map. So if you move the map to 50,100 then to get the screen position of a tile you need to add (50,100).

Note: we have a diamond isometric map in one of our games, getPositionAt works fine for us. There are some off-by-one issues when reading in objects from the tmx, but tile->map_position should work

Thanks very much for your interest.

It seems incredible, but also adding the duck directly to the map, the off-by-one issue is present. To have my duck in first tile (0,0) I have to ask getPositionAt for the position (0,-1). I guess I can’t do much more, anyway I attach the tmx and the duck image so anyone can try with my same code.

Iso_duck.zip (125.2 KB)

//TMX map
auto map = TMXTiledMap::create(“isometric_grass_and_water.tmx”);
this->addChild(map, 0);

auto layer = map->getLayer(“Tile Layer 1”);
Vec2 pos = layer->getPositionAt(Vec2(0,0));

Sprite *duck = Sprite::create(“farmer/duck.png”);
duck->setAnchorPoint(Vec2(.5f,.1f));
duck->setPosition(pos);
map->addChild(duck);

Just a last curiosity Steve. Why you suggest me to set the anchor point to (0.5, 0.1) instead of 0.5, 0?
Thanks in advance.

No good reason other than I figured maybe your duck image asset had some transparent padding around it and so the feet wouldn’t be on the bottom pixel row :smiley:

I’ll take a look at your code, or tmx, I’m definitely curious to fix your issue so that 0,0 positions the duck at 0,0 and also for any future onlookers. Whether it’s your code, the engine code, or the tmx/images.

Ah figured it out. I didn’t add one key thing: how to center on a tile after getting position.

 //TMX map
    auto map = TMXTiledMap::create("res/isometric_grass_and_water.tmx");
    this->addChild(map, 0);

    auto layer = map->getLayer("Tile Layer 1");
    Vec2 pos = layer->getPositionAt(Vec2(1,1));
    auto tileSet = layer->getTileSet();
    auto tilesetTileSize = tileSet->_tileSize;

    // we can do `size * float` and assign that to Vec2
    Vec2 centerTileOffset = tilesetTileSize * .5f;

    Sprite *duck = Sprite::create("res/duck.png");
    duck->setPosition(pos + centerTileOffset);
    // duck right foot center 20 pixels from bottom
    // so 20/150 = .133333f of image from bottom
    duck->setAnchorPoint(Vec2(0.5f, .133333f));
    this->addChild(duck);

Here’s an image poorly explaining this with respect to the tileset. With isometric maps (and hex too) the purple square shows a tile in the tile set and its size is 64x64 which is then positioned on top of the invisible squares in the map where each tile has a tile size of 64 x 32.

Hope that helps. Lemme know if that was too confusing.

Edit: not that my code here is meant for readability and understanding. You’ll want to optimize this with constants, cached sizes and offsets and not create copies of Vec2/Size by using .set() and such.

I really don’t know why Steve, but also copying your last script the duck remains in the wrong position for me :frowning: I’m using cocos2dx v3.3 if this matter.
The only change I made is on the last line: map->addChild(duck) instead of adding it to the scene, as you suggested in your previous post.

Anyway I went ahead by applying to the getPositionAt result the -1 offset to the column. Maybe it isn’t formally correct, but for my situation works like a charm. Your help saved many hours for me, thank you very much!!

Interesting, I just created a new project for test and added your code and assets. Maybe they fixed something from 3.3 to 3.6 (which I’m using).

// tile 0,5 (1st column, 6th row)
Vec2 pos = layer->getPositionAt(Vec2(0,5));

// tile 0,5 (2nd column, 2nd row)
Vec2 pos = layer->getPositionAt(Vec2(1,1));

Hey @stevetranby

Nice explanation of stuffs.
I manage to make some stuffs work out.

I have some sprites in a left side panel, touching them would create an instance of the sprite.
I position them using convertToNodeSpace to that specific layer.

but could you tell me how could I get to know what tile it is added to !!
and possibly snapping to grid feature… ??

That would really help. :smile:

I would put up and image to bring some colour to the post.

Regards,
Pabitra

You need to take the position inside the layer and invert the positionAt() function. I believe the experimental TMX layer uses a matrix transform and for diamond isometric you should be able to use the inverse to get tile from position.

If you want to update the engine code and are using CCFastTMXLayer you should be able to use this:

auto p = PointApplyTransform(posWorld, _tileToNodeTransformInverse);
return iVec2(p.x, p.y);

Here’s our relevant map methods. We always have a “Meta” layer in every map we pass in to these. I removed any error checking and expanded into temp vars to prevent wrapping. YMMV of course.

// tile from world
Vec2 Map::tileCoordForPosition(const Vec2& pos, STTMXLayer* mapLayer)
{
    float x = pos.x * CC_CONTENT_SCALE_FACTOR();
    float y = pos.y * CC_CONTENT_SCALE_FACTOR();

    float tw = mapLayer->getMapTileSize().width;
    float th = mapLayer->getMapTileSize().height;
    float mw = mapLayer->getLayerSize().width;
    float mh = mapLayer->getLayerSize().height;

    float isox = floorf(mh - y/th + x/tw - mw/2);
    float isoy = floorf(mh - y/th - x/tw + mw/2 - 1/2);

    return Vec2(isox,isoy);
}
// world (at center of tile) from coord
Vec2 Map::centerForTileCoordVec2(const Vec2& coord, STTMXLayer* mapLayer)
{
    const auto& tileSize = getTileSize();
    Vec2 xy = this->getPositionAt(coord, mapLayer);
    float tileW = int(tileSize.width * 1.f/CC_CONTENT_SCALE_FACTOR();
    float tileH = int(tileSize.height * 1.f/CC_CONTENT_SCALE_FACTOR());
    return Vec2(xy.x + tileW * .5f, xy.y + tileH * .5f);
}
// vertex z order (could be used for setLocalZ as well)
float Map::vertexZForTileCoord(const Vec2& pos, STTMXLayer* mapLayer)
{
    // this depends on your desired Z value range
    // we usually increase the default orthographic Z range
    // you may need to reduce this by * 0.1f or whatever
    Size layerSize = mapLayer->getLayerSize();
    float maxVal = layerSize.width + layerSize.height;
    float ret = - (maxVal - (pos.x + pos.y)) - 0.5f;
    return ret;
}