Libgdx zoom and pan map

This time I’m on a surprising problem and after some work I’m here to ask for help. ???

My point is to develop a “simple” way to have a decent zoom and pan functionality for a turn based game. In my mind requirement is to realize a zoom camera on top of a map, something like

s9tKaoap1bI

take a look to zoom in/out an pan.

Now, after some work I have to admit I don’t have find a tutorial/documentation on how to organize my codebase to solve this problem, so the following points are my thoughts on this topic so far:

  1. in my init code:
  • create a new OrthographicCamera: it’s a 2d game, I don’t need anything else
  • create a new Stage with some actors in (for example a window or a button)
  • use a InputMultiplexer and add two InputProcessor: one for stage (UI) and one for game entities: so I can separate input on ui and on game entities
  1. in my render code every frame:
  • clear everything :smiley:
  • update camera
  • batch.begin
  • draw the baseMap
  • batch.end
  • stage.act: update ui
  • stage.draw: render ui
  1. when zoom change, only camera zoom change, so ui is not scaled (this is good of course!)

package test;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.InputMultiplexer;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Dialog;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;

public class MapPanZoomExample extends ApplicationAdapter {

	private OrthographicCamera cam;
	private Image map;
	public SpriteBatch batch;
	private Stage stage;
	private Skin skin;

	@Override
	public void create() {
		batch = new SpriteBatch();
		// create ui
		stage = new Stage();
		skin = new Skin(Gdx.files.internal("defaultSkin/uiskin.json"));
		final Dialog dialog = new Dialog("Welcome", skin, "dialog") {
			public void result(Object obj) {
				System.out.println("Closed dialog!");
			}
		};
		dialog.setPosition(Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight() / 2, 0);
		dialog.text("Hello World ! Try to move with mouse and zoom with mouse wheel");
		dialog.button("Close", true); // sends "true" as the result
		dialog.pack();
		dialog.setVisible(true);
		stage.addActor(dialog);
		// init camera
		cam = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
		cam.setToOrtho(false, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
		// init map image
		map = new Image(new Texture("exampleMap.jpg"));
		// init input processor
		InputMultiplexer multi = new InputMultiplexer();
		multi.addProcessor(stage);
		multi.addProcessor(new InputProcessor() {

			@Override
			public boolean touchUp(int screenX, int screenY, int pointer, int button) {
				return false;
			}

			@Override
			public boolean touchDragged(int screenX, int screenY, int pointer) {
				return false;
			}

			@Override
			public boolean touchDown(int screenX, int screenY, int pointer, int button) {
				System.out.println("clicked: " + screenX + "," + screenY);
				return false;
			}

			@Override
			public boolean scrolled(int amount) {
				// arbitrary max zoomIn and max zoomOut values here
				if (cam.zoom - amount >= -0.25f && cam.zoom - amount <= 2) {
					cam.zoom -= (float) amount / 10f;
				}
				return true;
			}

			@Override
			public boolean mouseMoved(int screenX, int screenY) {
				return false;
			}

			@Override
			public boolean keyUp(int keycode) {
				return false;
			}

			@Override
			public boolean keyTyped(char character) {
				return false;
			}

			@Override
			public boolean keyDown(int keycode) {
				System.out.println("Key down: " + keycode);
				int step = 20;
				int dx = 0;
				int dy = 0;
				if (keycode == Keys.LEFT) {
					dx = -step;
				}
				if (keycode == Keys.RIGHT) {
					dx = step;
				}
				if (keycode == Keys.UP) {
					dy = step;
				}
				if (keycode == Keys.DOWN) {
					dy = -step;
				}
				cam.translate(dx, dy);
				return true;
			}
		});

		Gdx.input.setInputProcessor(multi);
	}

	@Override
	public void render() {
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
		cam.update();
		batch.setProjectionMatrix(cam.combined);
		batch.begin();
		map.draw(batch, 1);
		batch.end();
		stage.act(Gdx.graphics.getDeltaTime());
		stage.draw();
	}

	public static void main(String[] arg) {
		LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
		config.width = 1024;
		config.height = 768;
		new LwjglApplication(new MapPanZoomExample(), config);
	}

}


Questions:

  1. zoom works fine, but how to translate/scale coordinates on screen when map is scaled ?
  2. why If user try to move the map using keyboard, nothing moves ? see row 96 - 112
  3. how to implement to move the map when mouse is “near” borders ?
  4. order in render() method is fine ? see row 122

thanks in advance!

question #1: you can use Camera.unproject() to change mouse screen coordinates into game world coordinates, which accounts for the zoom and translation.

okay, thanks! Bu why translate does not work ?

Yay, one of the rare times I can help.

You are missing a camera.position.set() call…