How to convert Touch coords to Isometric Tilemap tile coords

I’ve got an Isometric 10x10 TileMap, with each tile being 132x132. The TMXTiledMap is added directly to the base layer, and I’m looking to be able to tap on a tile and change it to another.

The code I’ve got right now is something like

BaseScene::init_map(){
    auto map = TMXTiledMap::create("tilemaps/test_map.tmx");
    this->addChild(map);
    auto layer = map->getLayer("background");

    auto listener = EventListenerTouchOneByOne::create();
    listener->onTouchBegan = [layer, this, map](Touch* touch, Event* event){
        //try to calculate touch coord to tilemap tile x,y
        Vec2 world_pos = this->convertToNodeSpace(touch->getLocation());

        tile_pos.x = floor((world_pos.y / 132) + (world_pos.x / 132));
        tile_pos.y = floor((world_pos.y / 132) - (world_pos.x / 132));
        layer->setTileGID(59, tile_pos);



}

The problem is that when I touch any tile along the right, cocos asserts because the tile_pos is too large. If I tap the leftmost tile, it works out to 3,2, and the other to the top right of it is 3,1. I can’t make sense of the logic here.

Right now, I’m assuming that the camera won’t change, and neither will the map’s position on the screen, but I plan on working on that next.

Here’s how the tilemap looks on the screen before any touching happens, if it helps any.

Not sure why this hasn’t been added yet to the default TMXLayer class. I should prob submit a PR for review.

We use this for ‘regular’ (default diamond-ish, non-staggered) isometric. Others have used similar.

auto world_pos = this->convertToNodeSpace(touch->getLocation());
auto tile_pos = layer->tileFromPosition(world_pos);
layer->setTileGID(59, tile_pos);
// custom TMXLayer subclass method (Vec2 can replace iVec2) 
iVec2 XX::tileFromPosition(const Vec2& pWorld)
{
        // iso diamond
	float wx = pWorld.x;
	float wy = pWorld.y;
	wx *= CC_CONTENT_SCALE_FACTOR();
	wy *= CC_CONTENT_SCALE_FACTOR();

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

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

	return iVec2(isox,isoy);
}

Related Discussion Topics



2 Likes

Thanks, this works pretty well. I’ll need to check out the related discussions to see how to fine tune this. Seems like its offset by about half vertically, where the tile doesn’t register a click until you’re passed the center point.

Oh yeah. There’s often a half tile offset with touch or world positions. There’s also often an off by one Y tile coord. Also tile coord from various tiled types (objects, etc) require map height - y + 1 or similar. Hopefully this code got you close and you find tweaking it to perfection not difficult.

I’m sure I could find our supplemental code in various circumstances where we tweak it.

I’ll keep those in mind because I think I’ll be dealing with all that very soon. I don’t think offsetting it will be too tricky. Getting to be able to pan the map around should only be a matter of offsetting the isox and y by the panned position.

There was that MouseMap post I saw that might help me with the offsets, where you math out the part of the square sprite you’re in and offset the touch by that. I think that would be the ticket for this. http://www.gamedev.net/page/resources/_/technical/game-programming/isometric-n-hexagonal-maps-part-i-r747

Oh and if you’re panning the map we subtract that out from our world position in that code. Something like this:

Get world_touch like above.
World_pos = world_touch - tilemap.position

It seems like this->convertToNodeSpace(world_pos) works the same way, where it offsets the world pos to wherever the map’s pos is, given that map is a direct child of this.

Still trying to figure the offset business though.

Ah yeah you’re correct. I don’t think I knew about that method or what it did say back when this touch to tile code was written. Haha.

Haha, yeah there’s so many little helper functions. The source for convertToNodeSpace gets into some math that’s a little too much for me right now, so I’m going to assume it’s accounting for a lot. Thanks a lot for your help.

Man, there’s even a helper for convertTouchToNodeSpace which takes a Touch* and then getLocation on it, and saves you even another step lol.

I gave up on mapping to touch, and went for the directional keys you have to press instead.

It’s live in my game right now, if anyone wants to try it out