LibGDX: How to make tiled map tiles clickable?

How can I add click listeners for the tiles from tiled map so that when you select a tile with the mouse it becomes highlighted?


Solution 1:

That's not supported directly by libGDX as the TiledMap stuff is only for rendering.

You could easily create a Stage though, which will act as some kind of overlay-input-layer for your TiledMap. Just create an Actor for each tile which has the same size as position as that tile. Then you are able to add EventListeners to those actors to be able to recognize things like clicks on those actors.

Those actors should keep a reference to their "origin", namely TiledMapTileLayer.Cell. So you are able to go back from the actor to the cell anytime when processing those events.

The following shows how you might do it:

This Actor is responsible to catch the events and keep the information about the tile it's based on:

public class TiledMapActor extends Actor {

    private TiledMap tiledMap;

    private TiledMapTileLayer tiledLayer;

    private TiledMapTileLayer.Cell cell;

    public TiledMapActor(TiledMap tiledMap, TiledMapTileLayer tiledLayer, TiledMapTileLayer.Cell cell) {
        this.tiledMap = tiledMap;
        this.tiledLayer = tiledLayer;
        this.cell = cell;
    }

}

This little listener can be attached to one of those actors and will do any kind of logic:

public class TiledMapClickListener extends ClickListener {

    private TiledMapActor actor;

    public TiledMapClickListener(TiledMapActor actor) {
        this.actor = actor;
    }

    @Override
    public void clicked(InputEvent event, float x, float y) {
        System.out.println(actor.cell + " has been clicked.");
    }
}

The following class actually creates the actors from a given map and wires them to the listeners:

public class TiledMapStage extends Stage {

    private TiledMap tiledMap;

    public TiledMapStage(TiledMap tiledMap) {
        this.tiledMap = tiledMap;

        for (MapLayer layer : tiledMap.getLayers()) {
            TiledMapTileLayer tiledLayer = (TiledMapTileLayer)layer;
            createActorsForLayer(tiledLayer);
        }
    }

    private void createActorsForLayer(TiledMapTileLayer tiledLayer) {
        for (int x = 0; x < tiledLayer.getWidth(); x++) {
            for (int y = 0; y < tiledLayer.getHeight(); y++) {
                TiledMapTileLayer.Cell cell = tiledLayer.getCell(x, y);
                TiledMapActor actor = new TiledMapActor(tiledMap, tiledLayer, cell);
                actor.setBounds(x * tiledLayer.getTileWidth(), y * tiledLayer.getTileHeight(), tiledLayer.getTileWidth(),
                        tiledLayer.getTileHeight());
                addActor(actor);
                EventListener eventListener = new TiledMapClickListener(actor);
                actor.addListener(eventListener);
            }
        }
    }
}

Now the TiledMapStage will do all work for you. All you need to do is the following:

Stage stage = new TiledMapStage(tiledMap);
Gdx.input.setInputProcessor(stage);

And in render(...) you need to call stage.act(). Remember to use the same Viewport for the stage as you are using to render the TiledMap. Otherwise the input and your rendered map won't be aligned.