Wrapping a tiled map can be achieved by rendering the TiledMap
multiple times if the camera position is closer to the borders of the map than the size of the camera viewport.
So for example on a map that is 16 world units wide and viewed by a camera with a viewport width of 8 and the camera is at X=2
then the map needs to first be renderer as normal using the camera (2, y)
, and then once more with a camera that is at (16 + x, y)
(or (mapWidth + x, y)
), because 2 is less than 8 so we know that the camera position is far enough to the left for us to require to render the right-most part of the map to the left of the map.
For when x is close to the right-hand side of the map the position is (x - mapWidth, y)
.
The same is replicated for y
and there's also the case when both x
and y
are close to the edges, in which cases the map needs to be rendered once more for the corner.
It's also important to wrap the position of the main camera to always be inside of the map:
if (position.x < 0.0f)
position.x = mapWidth + position.x;
if (position.x > mapWidth)
position.x = mapWidth - position.x;
if (position.y < 0.0f)
position.y = mapHeight + position.y;
if (position.y > mapHeight)
position.y = mapHeight - position.y;
When the wrapping is done the "extra" rendering of the TiledMap
might look like this:
// Render normally first
camera.update();
mapRenderer.setView(camera);
mapRenderer.render();
// Now render the extra views depending on where the camera is
if (position.x < camera.viewportWidth)
{
wrapCamera.position.set(mapWidth + position.x, position.y, 1.0f);
wrapCamera.update();
mapRenderer.setView(wrapCamera);
mapRenderer.render();
if (position.y < camera.viewportHeight)
{
wrapCamera.position.set(mapWidth + position.x, mapHeight + position.y, 1.0f);
wrapCamera.update();
mapRenderer.setView(wrapCamera);
mapRenderer.render();
}
if (position.y > mapHeight - camera.viewportHeight)
{
wrapCamera.position.set(mapWidth + position.x, position.y - mapHeight,1.0f);
wrapCamera.update();
mapRenderer.setView(wrapCamera);
mapRenderer.render();
}
}
if (position.x > mapWidth - camera.viewportWidth)
{
wrapCamera.position.set(position.x - mapWidth, position.y, 1.0f);
wrapCamera.update();
mapRenderer.setView(wrapCamera);
mapRenderer.render();
if (position.y < camera.viewportHeight)
{
wrapCamera.position.set(position.x - mapWidth, mapHeight + position.y, 1.0f);
wrapCamera.update();
mapRenderer.setView(wrapCamera);
mapRenderer.render();
}
if (position.y > mapHeight - camera.viewportHeight)
{
wrapCamera.position.set(position.x - mapWidth, position.y - mapHeight,1.0f);
wrapCamera.update();
mapRenderer.setView(wrapCamera);
mapRenderer.render();
}
}
if (position.y < camera.viewportHeight)
{
wrapCamera.position.set(position.x, mapHeight + position.y, 1.0f);
wrapCamera.update();
mapRenderer.setView(wrapCamera);
mapRenderer.render();
}
if (position.y > mapHeight - camera.viewportHeight)
{
wrapCamera.position.set(position.x, position.y - mapHeight,1.0f);
wrapCamera.update();
mapRenderer.setView(wrapCamera);
mapRenderer.render();
}
In the above example there are two Camera
s, one called camera
which is the main camera that follows the player, and wrapCamera
that has the same viewport size as camera
but has a position that is updated for each wrapped view that needs to be rendered.
Complete source code for the gif is:
package com.bornander.sandbox;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.maps.MapProperties;
import com.badlogic.gdx.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.TiledMapRenderer;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
import com.badlogic.gdx.maps.tiled.TmxMapLoader;
import com.badlogic.gdx.maps.tiled.renderers.BatchTiledMapRenderer;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
import com.badlogic.gdx.math.Vector2;
public class MyGdxGame extends ApplicationAdapter {
TiledMap map;
float mapWidth;
float mapHeight;
OrthogonalTiledMapRenderer mapRenderer;
OrthographicCamera camera;
OrthographicCamera wrapCamera;
SpriteBatch batch;
Texture tank;
Vector2 position = new Vector2();
Vector2 direction = new Vector2(1, 0);
float speed = 0.0f;
@Override
public void create () {
float aspectRatio = (float)Gdx.graphics.getWidth() / (float)Gdx.graphics.getHeight();
// Set camera to view 16 tiles wide
camera = new OrthographicCamera(16, 16 * (1.0f / aspectRatio));
wrapCamera = new OrthographicCamera(16, 16 * (1.0f / aspectRatio));
batch = new SpriteBatch();
tank = new Texture("tank_huge.png");
TmxMapLoader mapLoader = new TmxMapLoader();
map = mapLoader.load("map.tmx");
mapWidth = map.getProperties().get("width", Integer.class);
mapHeight = map.getProperties().get("height", Integer.class);
mapRenderer = new OrthogonalTiledMapRenderer(map, 1.0f / 128.0f);
}
@Override
public void render () {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
if (Gdx.input.isKeyPressed(Input.Keys.A))
direction.rotate( 90f * Gdx.graphics.getDeltaTime());
if (Gdx.input.isKeyPressed(Input.Keys.D))
direction.rotate(-90f * Gdx.graphics.getDeltaTime());
if (Gdx.input.isKeyPressed(Input.Keys.W))
speed = Math.min(8.0f, speed + Gdx.graphics.getDeltaTime());
if (Gdx.input.isKeyPressed(Input.Keys.S))
speed = Math.max(-4.0f, speed - Gdx.graphics.getDeltaTime());
position.add(direction.x * speed * Gdx.graphics.getDeltaTime(), direction.y * speed * Gdx.graphics.getDeltaTime());
if (position.x < 0.0f)
position.x = mapWidth + position.x;
if (position.x > mapWidth)
position.x = mapWidth - position.x;
if (position.y < 0.0f)
position.y = mapHeight + position.y;
if (position.y > mapHeight)
position.y = mapHeight - position.y;
System.out.println(String.format("(%.2f, %.2f)", position.x, position.y));
camera.position.set(position, 1.0f);
// Render normally first
camera.update();
mapRenderer.setView(camera);
mapRenderer.render();
// Now render the extra views depending on where the camera is
if (position.x < camera.viewportWidth)
{
wrapCamera.position.set(mapWidth + position.x, position.y, 1.0f);
wrapCamera.update();
mapRenderer.setView(wrapCamera);
mapRenderer.render();
if (position.y < camera.viewportHeight)
{
wrapCamera.position.set(mapWidth + position.x, mapHeight + position.y, 1.0f);
wrapCamera.update();
mapRenderer.setView(wrapCamera);
mapRenderer.render();
}
if (position.y > mapHeight - camera.viewportHeight)
{
wrapCamera.position.set(mapWidth + position.x, position.y - mapHeight,1.0f);
wrapCamera.update();
mapRenderer.setView(wrapCamera);
mapRenderer.render();
}
}
if (position.x > mapWidth - camera.viewportWidth)
{
wrapCamera.position.set(position.x - mapWidth, position.y, 1.0f);
wrapCamera.update();
mapRenderer.setView(wrapCamera);
mapRenderer.render();
if (position.y < camera.viewportHeight)
{
wrapCamera.position.set(position.x - mapWidth, mapHeight + position.y, 1.0f);
wrapCamera.update();
mapRenderer.setView(wrapCamera);
mapRenderer.render();
}
if (position.y > mapHeight - camera.viewportHeight)
{
wrapCamera.position.set(position.x - mapWidth, position.y - mapHeight,1.0f);
wrapCamera.update();
mapRenderer.setView(wrapCamera);
mapRenderer.render();
}
}
if (position.y < camera.viewportHeight)
{
wrapCamera.position.set(position.x, mapHeight + position.y, 1.0f);
wrapCamera.update();
mapRenderer.setView(wrapCamera);
mapRenderer.render();
}
if (position.y > mapHeight - camera.viewportHeight)
{
wrapCamera.position.set(position.x, position.y - mapHeight,1.0f);
wrapCamera.update();
mapRenderer.setView(wrapCamera);
mapRenderer.render();
}
batch.setProjectionMatrix(camera.combined);
batch.begin();
batch.draw(
tank,
position.x-0.5f,
position.y-0.5f,
0.5f, 0.5f, /* origin */
1.0f, 1.0f, /* width, height */
0.5f, 0.5f, /* scale */
direction.angle() + 90,
0, 0,
tank.getWidth(), tank.getHeight(),
false, false /* flip */);
batch.end();
}
}