LibGDX - Storing tile position and then resizing the screen

Hi guys, i’m looking for some guidance on storing all the tiles in my game, highlight where the mouse hovers, and dealing with the resize of all this to make sure the mouse still highlights the correct tile.

So I usually make my games a fixed size but I feel like allowing users to resize as they wish and I plan on making an Android version that uses a Fit-viewport (keep the aspect ratio and put black bars around the game). I was thinking of storing my tiles inside an array and then doing the following code that will allow me to access the tile where the user is hovering their mouse and then drawing a texture at that position.


Tile currentTile = mapTiles[Gdx.input.getX()/32][Gdx.input.getY()/32];
batch.draw(Assets.highlightedTileTexture, currentTile.x(), currentTile.y )

Now here is my concern. This will work great for a fixed resolution as I’m simply getting the mouse location and then getting the tile position that the mouse is over, and the map is the same size as the screen. However, when I resize the screen all the coordinates will get messed up (I think) as I stored the tiles positions like so:


// For loop above that gets the tiles, cells and layer width/height
int tx  = x * tileWidth:
int ty = y * tileHeight;

Due to my viewport trying to keep the same aspect ratio, there will be black bars either horizontally or vertically which means that the mouse might be over a blank spot on the screen coords and still highlight a tile.

I was thinking that maybe I could use the camera.unproject on the mouse position to solve this or some resize logic but i’m not sure. I could also be completely wrong on the whole thing but I’d rather think about this now than getting stumped by it when I’m creating the game.

Thanks :slight_smile:

You should use Libgdx Viewport class as well as a Camera to do this things for you.
The advantage of the Viewport is, that you don’t have do take care about the aspect ratio anymore, Viewport does this for you.
Just call viewport.update(width, height) inside the resize(int width, int height) method.
The advantage of the Camera is, that you don’t have to take care about the resolution, the Camera does this for you.
That means, that you can calculate everything in your own world-unit (maybe meters?), the camera then “converts” this units to pixels.
To highlight the tile, your mouse is hovering over, you can unproject the mouse-position (given in pixels) to world units using the camera.

Well I went ahead and implemented this and as expected, there is an issue, even with the camera.unproject, viewport and camera. I’m unprojecting the mouse coords and then getting the tile that the mouse is over, so I can highlight that tile.

I’ve made a video to explain the issue :slight_smile: and what my thoughts are on it:

xAuxd2YIx00

Could you post the relevant code so we can see how you’re currently determining the selected tile? (Nice video by the way :))

Hi There.

I have encountered that exact problem before but it not that hard when you think about it. This happens because the mouse is not scaled with the view port or the size of the drawing area (I will refer to this as canvas). I have fixed this in my engine and have used a simple equation to help for both the mouse x position and y position.

I am going to assume that the camera is centered in the middle and not the top left or you are collecting the mouse location from the center of the window. I will also assume that you are taking the offset of the camera off the mouse position to get the desired tiles.

Keep in mind i do not use LibGDX but i am sure you can find and access these variables in the library somewhere. You will only need to access the current mouse position, window size and canvas size.

So on to the fix. You will need to scale the mouse so it will work with the view correctly (think of it as the mouse position in the actual view). You need to first determine the scale of a direction and you can do this by taking a length from the window (x axis in this case so width) and also the width of the rendering pane which would be the orthographic view (glOrtho).

scale_x = window.width / canvas.width

Once you have this you need to take the offset of the view off of the mouse variable so you can have the mouse position relative to the world without the letterbox or postbox irritating that and adding additional offset to your mouse positions once you have them.

mouse_x = (mouse.x - canvas.offsetX) * (window.width / canvas.width);

That should give you the position in the world (canvas) relative to the top left.


//I use float because it will be a decimal when we scale the mouse positions.
public float getMouseXOnCanvas() {
    return (mouse.x - canvas.offsetX) * ((float) window.width / (float) canvas.width);
}

public float getMouseYOnCanvas() {
    return (mouse.y - canvas.offsetY) * ((float) window.height / (float) canvas.height);
}

You can then use those methods to get the mouse position in the world and then use that instead of using the actual mouse positions. It doesn’t happen to the y axis because its in postbox mode so it is only affecting the x axis. If you squish the window on the sides it will affect the y axis.

As a side note if the window was the exact size as the canvas then you would not experience this at all because the mouse scaled by 1 is still the same and would not cause a problem.

When this problem occurs, I just make the camera un-project the mouse coordinates.

Make a vector for the mouse position:

Vector mousePos = new Vector(0, 0, 0);

In your update loop:

mousePos.setX(Gdx.input.getX());
mousePos.setY(Gdx.graphics.getHeight() - Mouse.getY());  //subtract height of window (might not have to if you have ortho set to true)

camera.unproject(mousePos);

Now set calculations based off of mousePos instead of Gdx.input.getBlahblahblah

Sure! :slight_smile:

Creating a tile positions



TiledMapTileLayer availbleTowerTiles = (TiledMapTileLayer) map.getLayers().get("Ground");
		
		gameTiles = new Tile[availbleTowerTiles.getWidth()][availbleTowerTiles.getHeight()];
		
		for(int x=0; x < availbleTowerTiles.getWidth(); x++){
			for(int y=0; y < availbleTowerTiles.getHeight(); y++){
				Cell cell = availbleTowerTiles.getCell(x, y);
				
				if(cell != null){
					TiledMapTile currentTile = cell.getTile();
					
					if(currentTile != null && currentTile.getProperties().containsKey("Placeable")){
						int tx = (int) (x * availbleTowerTiles.getTileWidth());
						int ty = (int) (y * availbleTowerTiles.getTileHeight());
						
						gameTiles[x][y]  = new Tile(tx, ty);
					}
				}
			}
		}


Getting the tile


Vector3 input = new Vector3(Gdx.input.getX(), Gdx.input.getY(), 0);
camera.unproject(input);
		// 32 is the tile width/height
Tile currentTile = gameTiles[(int) (input.x / 32)][(int) (input.y / 32)];
		
mapRenderer.getBatch().begin();
mapRenderer.getBatch().draw(Assets.tileHighlightTexture, currentTile.getX(), currentTile.getY());
mapRenderer.getBatch().end();

@NormalKarl I think this is in fact the issue, it makes sense when I think about it. The canvas size would be my virtual width/height, the width would just be the display and then the mouse position i’m already getting. I’m also dealing with camera projections so i’ll need to use this alongside camera.unproject, right?

@FairyTailz I’m already using the camera.unproject() method :slight_smile: I forgot to post the code, so I’ve posted it above

Just a question: Why are your Tiles 3232, not 11?
Haveing 11 Tiles would make many calculations much easier in my opinion.
All you would have to do is change the Virtual-Size, Viewport and Camera would do the rest.
Just think about, how many Tiles you want to see on Screen in x and y direction.
If you want to see 16 Tiles in x and 9 Tiles in y direction, your virtuals size would be 16
9.
Thats all. I guess this would make things much easier right?

The tiles are 32x32 due to using the Tiled map editor (Sometimes I use 16x16, sometimes 32x32). My Tile class also extends the rectangle class so they take in an x, y, width, height which I can use for collision detection etc if needed.

The input is then dividing by the tile width/height and stores as an int, this allows me to draw the tile highlight assets at the current tile x/y which makes the tile ‘snap’ to the highlight tile, rather than free movement. All of this then allows simple rendering and calculations are easily done when needed as I can divide/multiply by the tile width/height freely.

TLDR; allows me to do easy rendering, collision detection and movement to what i’m use to :slight_smile:

Well this is interesting, some nice person left a message on the YouTube video and the issue is now fixed. I’ve been a bit busy so I wasn’t able to test anything else.

Apparently, because i’m using a viewport I needed to use the viewport.unproject instead of the camera.unproject. Code change below :slight_smile:


Vector3 input = new Vector3(Gdx.input.getX(), Gdx.input.getY, 0);
viewport.unproject(input);