[Java][Libgdx] Isometric Tilemap Scaling and Coordinate Conversion - java

I have a 15x15 Isometric Tilemap consisting of 128 x 64 pixel tiles. Using an ExtendViewport and OrthogonalCamera I am able to render the map. However, it does not scale properly when resizing the window as the aspect ratio of the tiles becomes distorted and occasionally the map will move over to the right side of the window. The map does look fine if I force fullscreen resolution, but not if it's manually scaled or in windowed mode. When I say that the aspect ratio becomes distorted I mean that the tiles will appear stretched or jagged resulting in poor aesthetics.
Is there a simple way to render a perfectly squared (e.g. 10x10, 15x15) sized Isometric Tilemap, display it in the centre of the screen and have it scale properly without distorting the aspect-ratio as the screen size increases?
Then there is the issue of conversion between cartesian and isometric coordinates.
http://clintbellanger.net/articles/isometric_math/ The following post explains how to convert between cartesian and isometric coordinates, but I am not able to make it work. The code below is the closest thing I've come to a working solution so far, but it becomes increasingly more off as you move towards the right of the screen, not by terribly much, but it's definitely noticeable.
public static Vector2 cartesianToIsometric(Camera camera, GameMap map, Vector3 point) {
camera.unproject(point);
float tileWidth = (float)map.getMapWidth() * unitScale(map);
float tileHeight = (float)map.getMapHeight() * unitScale(map);
point.x /= tileWidth;
point.y = (point.y - tileHeight / 2) / tileHeight + point.x;
point.x -= point.y - point.x;
return new Vector2((int)point.x, (int)point.y);
}
Here is a snippet of the relevant code from my GameRenderer Class.
public GameRenderer(GameState world) {
this.map = new Map01();
this.world = world;
this.camera = new OrthographicCamera();
this.viewport = new ExtendViewport(0, 3072, camera);
centerCamera(map);
batchRenderer = new SpriteBatch();
}
public void render() {
Gdx.gl.glClearColor(0.11f, 0.6f, 0.89f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT | (Gdx.graphics.getBufferFormat().coverageSampling?GL20.GL_COVERAGE_BUFFER_BIT_NV:0));
renderer.setView(camera);
renderer.render();
map.render();
batchRenderer.begin();
AssetLoader.font.getData().setScale(8);
AssetLoader.font.draw(batchRenderer, map.getSelectedTile().getName(), -400, 1500);
batchRenderer.end();
}
public void resize(int width, int height){
viewport.update(width, height);
renderer = new IsometricTiledMapRenderer(map.getTiledMap(), MapUtils.unitScale(map));
batchRenderer.setProjectionMatrix(camera.combined);
centerCamera(map);
}
private void centerCamera(GameMap map){
camera.position.set(MapUtils.getMapCenter(map));
}
I could almost certainly make it work with what I have, but it seems like I'm missing something painfully simple. It's probably a good idea to have a solid foundation and functional coordinate system, before I start implementing the actual gameplay.
Here is a picture of the map in question if that is of any help. Thanks in advance for any advice.

Related

Why doesn't simply scaling things up in LibGDX and Box2D work correctly?

I'm trying to get rid of having to scale all the coordinates on my sprites when using Box2D and LibGDX.
Here are the settings for my viewport and physics world:
// Used to create an Extend Viewport
public static final int MIN_WIDTH = 480;
public static final int MIN_HEIGHT = 800;
// Used to scale all sprite's coordinates
public static final float PIXELS_TO_METERS = 100f;
// Used with physics world.
public static final float GRAVITY = -9.8f;
public static final float IMPULSE = 0.15f;
world.setGravity(new Vector2(0f, GRAVITY));
When I apply a linear impulse to my character (when the user taps the screen) everything works fine:
body.setLinearVelocity(0f, 0f);
body.applyLinearImpulse(0, IMPULSE, body.getPosition().x, body.getPosition().y, true);
The body has a density of 0f, but changing this to 1f or even 100f doesn't seem to have any real effect.
This means that I have to scale all the sprite's locations in the draw method by PIXELS_TO_METERS. I figured (perhaps incorrectly) that I could simply scale GRAVITY and IMPULSE by PIXELS_TO_METERS and have it work exactly the same. This doesn't seem to be the case. Gravity seems really small, and applying the impulse barely has any effect at all.
// Used to scale all sprite's coordinates
public static final float PIXELS_TO_METERS = 1f;
// Used with physics world.
public static final float GRAVITY = -9.8f * 100;
public static final float IMPULSE = 0.15f * 100;
So:
1) why doesn't simply scaling up all the values make it work the same?
2) Is there a better way to do this?
It looks like you're over complicating your design by using some imaginary pixel units (i doubt it are actual pixels you're referring to). I'd advice you to use meaningful units instead, for example meters, and stick to it. Thus, use meters for all coordinates (including your virtual viewport). So, practically modify you code to look like this:
// Used to create an Extend Viewport
public static final float MIN_WIDTH = 4.8f; // at least 4.8 meters in width is visible
public static final float MIN_HEIGHT = 8.0f; // at least 8 meter in height is visible
This completely removes the need to scale meter to your imaginary pixel units. The actual scaling from your units (virtual viewport size) to the screen (values between -1 and +1) is done by the camera. You should not have to think about scaling units in your design.
Make sure to remove your PIXELS_TO_METERS constant (don't set it to 1f, it is only complicating your code at no gain) and make sure you're not using imaginary pixels at all in your code. The latter includes all sprites that you create without explicitly specifying its size in meters.
It is still possible to "scale" your units (in your game logic) compared to SI units, because of valid reasons. For example, when creating a space game, you might find yourself using very large numbers when using meters. Typically you'd want to keep the values around 1f to avoid floating point errors. In such case it can be useful to use e.g. dekameters (x10), hectometers (x100) or kilometers (x1000) instead. If you do this, make sure to be consistent. It might help to add the units in comments so you don't forget to scale properly (e.g. GRAVITY = -0.0098f; // kilometer per second per second).
I have implemented as this:
// in declaration
float PIXELS_TO_METERS = 32; // in my case: 1m = 32 pixels
Matrix4 projection = new Matrix4();
// in creation
viewport = new FitViewport(
Application.width
, Application.height
, Application.camera);
viewport.apply();
// set vieport dimensions
camera.setToOrtho(false, gameWidth, gameHeight);
// in render
projection.set(batch.getProjectionMatrix());
projection.scl(PIXELS_TO_METERS);
box2dDebugRenderer.render(world, projection);
// in player body creation
BodyDef bodyDef = new BodyDef();
bodyDef.type = BodyDef.BodyType.DynamicBody;
bodyDef.position.x = getPosition().x / PIXELS_TO_METERS;
bodyDef.position.y = getPosition().y / PIXELS_TO_METERS;
CircleShape shape = new CircleShape();
shape.setRadius((getBounds().width * 0.5f) / PIXELS_TO_METERS);
// in player update
setPosition(
body.getPosition().x * PIXELS_TO_METERS - playerWidth,
body.getPosition().y * PIXELS_TO_METERS - playerHeight);
So to set pixels to meters in box2d methods you have to divide pixel-positions by PIXELS_TO_METERS and to set meters to pixels in player position you have to multiply box2d values by PIXELS_TO_METERS.
Set your PIXELS_TO_METERS correctly to how much pixels in your screens match to 1 meter.
Good luck.

Libgdx TiledMap bug in render

I do a Mario like game with the libgdx library.
All works fine but sometime (especially when the camera goes fast) my TileMap has a little bug during the render.
A picture worth thousand word, so here it is : http://postimg.org/image/4tudtwewn/
I have tried to increment FPS, but there is no change. I have no idea where that is come from.
Here is my code :
public void show() {
TmxMapLoader loader = new TmxMapLoader();
this.plan = loader.load("maps/level-"+this.world+"-"+this.level+".tmx");
this.renderer = new OrthogonalTiledMapRenderer(this.plan);
...
public void render(float delta) {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
this.renderer.render();// rendu de la carte
Batch batch = this.renderer.getSpriteBatch();
...
This happens when your Camera's position is not perfectly aligned with screen-space coordinates (pixels).
This results in some sprites being rounded to the next pixel while some other (that were connected to those) being rounded to the previous one, resulting in visible ugly glitches.
The easiest fix I could come up with is making sure that the Camera position is always perfectly aligned with screen-space coordinates.
public class TileMapCamera extends OrthographicCamera {
// Map tile size to round to
private int tileSize;
/**
* Create a pixel-perfect camera for a map with the specified tile size
* #param tileSize
*/
public TileMapCamera(int tileSize){
this.tileSize = tileSize;
}
#Override
public void update(){
// Round position to avoid glitches
float prevx = position.x;
float prevy = position.y;
position.x = (int)(position.x * tileSize) / (float)tileSize;
position.y = (int)(position.y * tileSize) / (float)tileSize;
super.update();
position.set(prevx, prevy, 0);
}
}
This works for a tile-based coordinate viewport:
mapViewport = new FitViewport(16, 15, new TileMapCamera(map.getProperties().get("tilewidth", Integer.class)));
If you're working with pixel-based coordinate viewports, you should round the camera position to the nearest integer instead.
I think its about the filtering.
This will help:
TiledMapRenderer Artifact
If the problem you are referring to, is the spacing you can fix when you import the tileset as it says Tenfour04
add or change pixel padding.

Libgdx Box2D - Draw Sprite on Body - Heavy problems

I'm heaving heavy problems with drawing a Sprite on a Box2D body.
I'm creating a platformer and I did draw a sprite on a body before but then realized that my gravity is really floaty. After googling I found out that I should work with meters when using Box2D and I changed my code to work with a pixel to meter conversion ratio of 25.
Since then I can't get everything to work though, my sprite just won't draw on my body.
Camera:
float width = Gdx.graphics.getWidth() * PIXELS_TO_METERS;
float height = Gdx.graphics.getHeight() * PIXELS_TO_METERS;
camera = new OrthographicCamera(width / 2, height / 2);
camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0);
camera.update();
Here is the code for my body:
idleRegion = new TextureRegion(xeonTexture, 20, 13, 50, 65);
xeonSprite = new Sprite(idleRegion);
//Physics
bodyDef = new BodyDef();
bodyDef.type = BodyDef.BodyType.DynamicBody;
bodyDef.position.set(100 * PIXELS_TO_METERS, 100 * PIXELS_TO_METERS);
bodyDef.fixedRotation = true;
body = world.createBody(bodyDef);
PolygonShape shape = new PolygonShape();
shape.setAsBox((xeonSprite.getWidth() / 2) * PIXELS_TO_METERS, (xeonSprite.getHeight() / 2) * PIXELS_TO_METERS);
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = shape;
fixtureDef.density = 1f;
fixtureDef.friction = 1f;
fixtureDef.restitution = 0f;
fixtureDef.isSensor = false;
physicsFixture = body.createFixture(fixtureDef);
Here is how I set the position of my sprite:
final float widthD2 = (xeonSprite.getWidth() / 2);
final float heightD2 = (xeonSprite.getHeight() / 2);
final float angle = this.getBodyAngle();
xeonSprite.setOrigin(widthD2, heightD2);
xeonSprite.setPosition(body.getPosition().x - xeonSprite.getWidth() / 2, body.getPosition().y - xeonSprite.getHeight() / 2);
xeonSprite.setRotation((float) Math.toRadians(angle));
I also tried the following:
xeonSprite.setPosition(body.getPosition().x - xeonSprite.getWidth() / 2 * METERS_TO_PIXELS, body.getPosition().y - xeonSprite.getHeight() / 2 * METERS_TO_PIXELS);
And here is how I draw my Sprite:
penguinBatch.begin();
xeon.getPenguinSprite(stateTime, Gdx.graphics.getDeltaTime()).draw(penguinBatch);
penguinBatch.end();
This is yet another case of "Pixels do not exist in your game world", they are only there to represent you game world to your client. A camera man for TV need to know as much about your TV as you need to know about your clients screens. Just capture what you want to show and let LibGDX do the rest.
So, you should never, ever work with pixels. In the case you want pixel perfect drawing you might want to setup your camera to the amount of pixels of the screen you are targeting but after that you treat these "pixels" as units.
Box2D does not work with pixel per meter. It works with 1 unit == 1 meter and you should stick to that. But how much meters a pixel should represent is still up to you.
If you want to draw a 1m x 1m box you should not multiply by some number you create based of screen pixels like you do now, you should just give it 1 x 1 to represent the box.
shape.setAsBox(1, 1);
Your sprite should be exactly the same
xeonSprite.setSize(1, 1);
And you should position them on the same position. It's really that simple, 1 UNIT == 1m and there is nothing more to it, the camera does the rest. If you want to show 9m x 16m of your game world you setup your camera like this.
camera = new OrthographicCamera(9, 16);
If you want to represent the empire state building you would give the body and sprite a size of 57, 443 and then your camera needs to represent a much larger area, if you don't want to render a small portion of it. If you want to fit the height exactly on screen without stretching you need your own conversion.
float camHeight = 443;
float aspectRatio = Gdx.graphics.width / Gdx.graphics.height
float camWidth = aspectRatio * 443;
Although those Gdx calls give the pixels your clients are running you should still not treat these as pixels about these since you don't know at what screen I will be playing your game.
Now forget about the empire state building example and your camera's center is positioned at 0, 0 world space so bottom left is at -4.5, -8 so you might want to translate it and don't forget to .update the camera. You are currently doing this in your code.
If you start drawing your sprite and updating Box2D you will see a ball drop from the center of your screen. Of course you need to keep updating your sprites position to match the body position for it to move along with the Box2D body.
Just forget pixels, unless you want to code your own camera or viewports, which you probably do not want because you chose LibGDX. The rest of your game code does not need to work with pixels in any way. The OrthographicCamera can calculate world position to screen position for you by camera.unproject(touchVector).

Libgdx Box2D pixel to meter conversion?

When trying to program a game using Box2D, I ran into a problem with Box2D. I filled in pixel numbers for the lengths of the the textures and sprites to create a box around it. Everything was at the right place, but for some reason everything went very slowly. By looking on the internet I found out that if you didn't convert pixels to meters box2d might handle shapes as very large objects. this seemed to be a logical cause of everything moving slowly.
I found similar questions on this site, but the answers didn't really seem to help out. in most of the cases the solution was to make methods to convert the pixel numbers to meters using a scaling factor. I tried this out, but everything got misplaced and had wrong sizes. this seemed logical to me since the numbers where changed but had the same meaning.
I was wondering if there is a way to make the pixels mean less meters, so everything whould be at the same place with the same (pixel) size, but mean less meters.
If you have a different way which you think might help, I whould also like to hear it..
Here is the code i use to create the camera
width = Gdx.graphics.getWidth() / 5;
height = Gdx.graphics.getHeight() / 5;
camera = new OrthographicCamera(width, height);
camera.setToOrtho(false, 1628, 440);
camera.update();
This is the method I use to create an object:
public void Create(float X, float Y, float Width, float Height, float density, float friction, float restitution, World world){
//Method to create an item
width = Width;
height = Height;
polygonDef = new BodyDef();
polygonDef.type = BodyType.DynamicBody;
polygonDef.position.set(X + (Width / 2f), Y + (Height / 2f));
polygonBody = world.createBody(polygonDef);
polygonShape = new PolygonShape();
polygonShape.setAsBox(Width / 2f, Height / 2f);
polygonFixture = new FixtureDef();
polygonFixture.shape = polygonShape;
polygonFixture.density = density;
polygonFixture.friction = friction;
polygonFixture.restitution = restitution;
polygonBody.createFixture(polygonFixture);
}
To create an item, in this case a table, I use the following:
Table = new Item();
Table.Create(372f, 60f, 152f, 96f, 1.0f, 0.2f, 0.2f, world);
The Sprites are drawn on the item by using the following method:
public void drawSprite(Sprite sprite){
polygonBody.setUserData(sprite);
Utils.batch.begin();
if(polygonBody.getUserData() instanceof Sprite){
Sprite Sprite = (Sprite) polygonBody.getUserData();
Sprite.setPosition(polygonBody.getPosition().x - Sprite.getWidth() / 2, polygonBody.getPosition().y - Sprite.getHeight() / 2);
Sprite.setRotation(polygonBody.getAngle() * MathUtils.radiansToDegrees);
Sprite.draw(Utils.batch);
}
Utils.batch.end();
}
The sprites also have pixel sizes.
Using this methods it displays the images at the right places, but everything moves slowly.
I was wondering how or if I whould have to change this to make the objects move correctly, and / or mean less. Thanks in advance.
Box2D is an entirely independent of the graphics library that you use. It doesn't have any notion of sprites and textures. What you read online is correct, you'll have to convert pixels to metres, as Box2D works with metres(the standard unit for distance).
For example, if you drew a sprite of size 100x100 pixels, that's the size of the sprite that you want the user to see on the screen. In real world the size of the object should be in metres and not in pixels - so if you say 1px = 1m, then that'll map the sprite to a gigantic 100x100 meter object. In Box2D, large world objects will slow down calculations. So what you need to do is map the 100 pixels to a smaller number of meters, say, 1 meter - thus 100x100px sprite will be represented in Box2D world by a 1x1 meter object.
Box2D doesn't work well with very small numbers and very large numbers. So keep it in between, say between 0.5 and 100, to have good performance.
EDIT:
Ok. Now I get your question.
Don't code to pixels. Its as simple as that. I know it'll take some time to understand this(it took for me). But once you get the hang of it, its straight forward.
Instead of pixels, use a unit, say, you call it meter.
So we decide our viewport should be say 6mx5m.
So initialization is
Constants.VIEWPORT_WIDTH = 6;
Constants.VIEWPORT_HEIGHT = 5;
...
void init() {
camera = new OrthographicCamera(Constants.VIEWPORT_WIDTH, Constants.VIEWPORT_HEIGHT);
camera.position.set(Constants.VIEWPORT_WIDTH/2, Constants.VIEWPORT_HEIGHT/2, 0);
camera.update();
}
Once you know the actual width and height, you call the following function in order to maintain aspect ratio:
public void resize(int width, int height) {
camera.viewportHeight = (Constants.VIEWPORT_WIDTH / width) * height;
camera.update();
}
resize() can be called anytime you change your screen size(eg: when you screen orientation changes). resize() takes the actual width and height (320x480 etc), which is the pixel value.
Now you specify you sprite sizes, their positions etc. in this new world of size 6x5. You can forget pixels. The minimum size of the sprite that'll fill the screen will be 6x5.
You can now use the same unit with Box2D. Since the new dimensions will be smaller, it won't be a problem for Box2D. If I remember correctly Box2D doesn't have any unit. We just call it meter for convenience sake.
Now you might ask where you specify the dimensions of the window. It depends on the platform. Following code shows a 320x480 windowed desktop game:
public class Main {
public static void main(String[] args) {
LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
cfg.title = "my-game";
cfg.useGL20 = false;
cfg.width = 480;
cfg.height = 320;
new LwjglApplication(new MyGame(), cfg);
}
}
Our camera will intelligently map the 6x5 viewport to 480x320.

Achieve parallax effect in libGDX game

I'm working in a game with some friends in which we have a large horizontal world and a OrthographicCamera that shows only 1/3 of it. This camera it's moved when the horizontal position of the player change so the camera only move to the left and to the right.
Some of the objects showed in the game are near the player point-of-view but others are far away (for example, islands). With this in consideration, we cannot set fixed positions for elements and move only the camera. We need to achieve a parallax effect taking in consideration the distance of the elements.
Here is a simple image to explain it better:
The viewport to the left shows 3 objects of the game. The green one is near the player, the red ellipse is far and the yellow one is in the middle. In the viewport to the right the camera has been moved to the right so all the objects disappear to the left. The thing is that the relative movement of the green rectangle is greater than the movement of the yellow. In the same way, movement of yellow object is greater than red object movement.
I created all my assets scaled taking in consideration how far they are but now, how can I simulate this perspective using libGDX? Is there any class to do it? If I have to set elements position in each iteration, how could I calculate the right position?
Note that the example below is not tested as I am just recalling how I did it. The idea is simple - create layers with an extra layer for each with initial positions and velocity and move them. If a layer goes off the edge, put another one (that is why we create an extra layer) at the opposite edge.
Say you have a parallax object that takes initial positions, size, and velocity-
public class Parallax extends DynamicGameObject {
public float width, height; // Use setter/getter if you prefer
public Parallax(float x, float y, float width, float height, float velocityX, float velocityY) {
super(x, y, width, height);
velocity.set(velocityX, velocityY);
this.width = width;
this.height = height;
}
public void update(float deltaTime) {
position.add(velocity.x * deltaTime, velocity.y * deltaTime);
}
public void setPosition(float x, float y) {
position.set(x, y);
}
}
DynamicGameObject is taken from SuperJumper demo-
public class DynamicGameObject extends GameObject {
public final Vector2 velocity;
public final Vector2 accel;
public DynamicGameObject(float x, float y, float width, float height) {
super(x, y, width, height);
velocity = new Vector2();
accel = new Vector2();
}
}
GameObject as well-
public class GameObject {
public final Vector2 position;
public final Rectangle bounds;
public GameObject(float x, float y, float width, float height) {
this.position = new Vector2(x,y);
this.bounds = new Rectangle(x - width/2f, y - height/2f, width, height);
}
}
Say we have two layers - one in front and the other goes at back. We have one texture for each. Each texture fills the entire screen. We create two instances for each layer so that when one texture starts going off the screen, the other shows up at the edge to fill the gap. If you have smaller textures, you need to determine first how many textures you need to fill the screen and then create layers with one extra to fill the gap in between.
We can create an array of parallax layers during world creation-
Array<Parallax> parallaxList = new Array<Parallax>(4);
We can create the layers like this-
// Back
/* First parallax for back layer is at 0 x-axis. If you want to move the texture from right to left, the value of BACK_VELOCITY_X should be negative. You can experiment with velocity value for desire pace of movement. We do not want our layer to move on y-axis. Hence, it is set to 0. */
parallaxList.add(new Parallax(0, BACK_TEXTURE_HEIGHT, BACK_TEXTURE_WIDTH, BACK_TEXTURE_HEIGHT, BACK_VELOCITY_X, 0));
/* This one is also for back layer but it is positioned at the right edge of the layer above*/
parallaxList.add(new Parallax(BACK_TEXTURE_WIDTH, BACK_TEXTURE_HEIGHT, BACK_TEXTURE_WIDTH, BACK_TEXTURE_HEIGHT, SOME_VELOCITY_X, 0));
// Front
parallaxList.add(new Parallax(0, 0, FRONT_TEXTURE_WIDTH, FRONT_TEXTURE_HEIGHT, FRONT_VELOCITY_X, 0));
parallaxList.add(new Parallax(FRONT_TEXTURE_WIDTH, 0, FRONT_TEXTURE_WIDTH, FRONT_TEXTURE_HEIGHT, FRONT_VELOCITY_X, 0));
We update the layers on an update call in each frame-
// In our example, TOTAL_LAYERS is 4
for (int i = 0; i < TOTAL_LAYERS; i++) {
int tmpInt;
Parallax parallax = parallaxList.get(i);
parallax.update(deltaTime);
// If one layer is off the edge, put it at the right of the next one
// In this example, layers are moving from right to left
if (parallax.position.x <= -parallax.width) {
// We know that parallaxList's indexes 0 and 1 hold the back layers
// and indexes 2 and 3 have the front layers. You can add additional
// parameters in Parallax class to indicate a group so that you do not
// have to determine the group in dirty way like this
if(i == 0){
tmpInt = 1;
} else if(i == 1) {
tmpInt = 0;
} else if(i == 2) {
tmpInt = 3;
} else {
tmpInt = 2;
}
parallax.setPosition(parallaxList.get(tmpInt).position.x + parallax.width, parallax.position.y);
}
}
You can use an OrthographicCamera and a SpriteBatch to draw the parallax layers. You can actually use the game camera you have but I think using a separate camera is much cleaner. Anyways, parallax textures are usually big enough to be batched in a separate call so using the game camera most probably will not save you a draw call.

Categories

Resources