libGDX change my system from pixels to units - java

I want everyone to see the same things on their screen regardless of their screen size and aspect ratio so this is the code I am currently using. (also I am sending net data across with the coordinates of where the other players are on the screen)
int width = 1920, height = 1080;
public OrthographicCamera camera;
Viewport viewport;
//constructor
camera = new OrthographicCamera();
viewport = new ScalingViewport(Scaling.stretch, width, height, camera);
viewport.apply();
camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0);
camera.update();
public void resize(int width, int height) {
viewport.update(width, height);
camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0);
}
now for example I wanted 10 perfect squares going across the middle of the screen so I made then 192 pixels by 192 pixels so I could have 10 perfect squares going across the middle of the screen my system right now works perfect except for the fact that it is rendered internally 1920x1080 on all devices big and small. How would I convert my camera to units and get the size needed for 10 perfect squares to go across the screen? Is that even possible?
Here is my code to draw 10 squares across the screen
float size = 192;
for(int i = 0; i<10; i++){
walls.add(new Stuff(i*size,height/2-size/2,size,size,"middle",1,1,0,1));
}
How would I convert all this code to say units? Or is this an acceptable approach?

You are already using units, they just aren't very meaningful (and it certainly aren't pixels). If you want to use meaningful units (e.g. SI units), then the only thing you have to change in this code are the values. E.g. if the size of your stuff (wall?) is, say 2 meter, then use the value 2 instead of 192. And if you want your users screen to be, say 20 meters (10 walls e.g.) in width and 16:9 aspect ratio, then use that for the Viewport worldWidth and worldHeight.
float worldWidth = 20;
float worldHeight = worldWidth * 9f / 16f;
...
viewport = new StretchViewport(worldWidth, worldHeight, camera);
Make sure to understand that these "pixels" you are talking about only exist in your imagination. See also: http://blog.xoppa.com/pixels/.

You created your ScalingViewport with a width of 1920, so the width in world units will be 1920 on all screens, no matter what. Also, your scene will be distorted on any screen that is not 16:9, since you are stretching to fit whatever the screen is. (Because of the distortion, I personally would never use ScalingViewport with Scaling.stretch, aka StretchViewport.)
If you want your squares to look square on all screens with this type of viewport, you'll have to do some math to change their height (but their width should always be 192 if you want exactly ten to fit across the screen).
public void resize(int width, int height){
float viewportAspect = 1920f / 1080f;
float screenAspect = (float)width / (float)height; //Make sure you cast to floats
boxHeight = 192 * screenAspect / viewportAspect;
viewport.update(width, height, true);
}
The camera always shows the scene in world units, so there's no conversion to do.

Related

How Camera works in Libgdx and together with Viewport

If you work with LibGdx it goes not long until you come to Camera and viewport. If you work the first time with camera and Viewport you get some questions about how it works and how to use it. So:
How can I use a Camera in LibGdx? What's viewport width and height?
What is a Viewport, how can I use it and how it works together with the Camera?
How can I use a Camera in LibGdx? What's viewport width and height?
Firstly it's important that you know the Camera works with World units not with Pixels. World units are not a regular Unit. You self can define how much one World Unit is. Later more.
First, we create an OrthographicCamera a SpriteBatch and a Texture:
private OrthographicCamera camera;
private SpriteBatch batch;
private Texture img;
#Override
public void create () {
//We create a OrthographicCamera through which we see 50x50 World Units
camera = new OrthographicCamera(50,50);
batch = new SpriteBatch();
img = new Texture("badlogic.jpg");
}
We create a OrthographicCamera and in the Constructor we define how many World Units we see if we look through this camera into our world. In our example 50 x 50 World Units these are the viewport width and height.
So we have created a Camera with a viewport width and height of 50.
In the render() method we render our image:
#Override
public void render () {
//Clear the screen (1)
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
//Set ProjectionMatrix of SpriteBatch (2)
batch.setProjectionMatrix(camera.combined);
batch.begin();
//Draw image on position 0, 0 with width 25 and height 25 (3)
batch.draw(img, 0, 0, 25, 25);
batch.end();
}
(1) Clear the Screen, if we don't do that every Texture will draw over the other and if we draw a Animation we will see the old Frames.
(2) The batch is our Drawer he draws our Images, Animations etc. Default he draws a World which has so many World Units like the Screen has Pixels so in this case 1 World Unit = 1 Pixel. But now we will see 50 x 50 World Units doesn't matter how big the screen is. To say the Batch that he should draw what we see through our camera we must call: batch.setProjectionMatrix(camera.combined);
(3) Now we draw our img on Position 0,0 But 0, 0 doesn't mean on Pixel position 0,0 it means the Image will be drawn on World position 0,0 also width and height are not in Pixels they are in World units so the img will be drawn on Position 0,0 25x25 World Units big. So on a 50x50 viewport, the image fills one-quarter of the whole screen.
The Image fill one-quarter of the whole screen exactly as expected. But why it is on the right top and not on the bottom left?
The Problem is that the center of the Camera point on the position 0,0
So our Image is drawn on position 0,0 he fills the top right corner.
We must set the position of the camera so 0,0 is in the bottom left corner:
camera = new OrthographicCamera(50,50);
camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0);
In the render() method we must add camera.update() because every time we change the position or the scale or what else of the camera we must update the camera.
Now the Image is in the bottom left.
Where are the Pixels?
We always speak about World units but where are the Pixels? The pixels are still there. If we have a Screen size of 200 x 200 pixels the batch will always draw 200 x 200 pixels. With the method batch.setProjectionMatrix(camera.combined); we only say the batch how much World Units are one Pixel.
If we have a Screen with 200 x 200 pixels and we create a Camera with a viewport of 50 x 50 world units the SpriteBatch know 1 WorldUnit = 4 Pixels.
Now we draw a Image which is 25 x 25 World Units big the SpriteBatch knows he must draw the image 25 * 4 = 100 pixel big.
So the pixels still there but it's easier to think in World Units.
If it's not clear enough here is a little bit more detailed description: Libgdx's World Units
Box2d
It's also very important to think in World Units if you use Box2d because Box2d works with Meters. So if you create a Body with a Force off 5 on the x axis, the Body is 5 m/s fast.
And now it's very cool to work with World Units because you can say 1 World Unit = 1 Meter so you can create a object with a width of 10 and you know after one second the Body will be in the Center of the Object. If you work with Pixels you will have a Problem if you have a different Screensize.
What is a Viewport, how can I use it and how it works together with the Camera?
Now we have the big Problem about different Screen sizes.
Suddenly we have a Screen size of 350 x 200 pixels, now the Image will be stretched and don't look so nice as before.
For this Problem we use Viewports a few Viewports are StretchViewport, FitViewport and ExtendViewport. All viewports you can find here: https://github.com/libgdx/libgdx/wiki/Viewports.
Firstly what is a Viewport.
Imagine the camera is a speaker who speaks English. Different Screen Sizes are other People who speak German, French, Chinese etc. and the Viewport is the translator. The Translator doesn't change the sense of that what the English Speaker says but he adapts it so the others can understand it. Same are camera and Viewport. Viewport doesn't say or change what you can see on your screen if you run the program. He only handles that you always see the same on different Screen sizes. A Camera can life without Viewport. A Viewport not without Camera.
Add a viewport Object:
private Viewport viewport;
and the resize() method:
#Override
public void resize (int width, int height) {
viewport.update(width, height);
}
StretchViewport
Create a StretchViewport:
camera = new OrthographicCamera(50, 50);
camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0);
viewport = new StretchViewport(camera.viewportWidth, camera.viewportHeight, camera);
In the StretchViewport Constructor, we define the viewport width and height and the Camera.
Now we get the same result as before if we have different Screensizes the Images will be stretched.
FitViewport
Maybe we won't stretch our Images we will matter about the ratio of x and y.
The ratio of x and y means: is an Object 2 width and 1 height he will always twice as wide as high for example 200x100, 30x15 but not 20x15.
Create a FitViewport:
camera = new OrthographicCamera(50, 50);
camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0);
viewport = new FitViewport(camera.viewportWidth, camera.viewportHeight, camera);
Now the Image will always be a square. To see the Bars on the Side lets draw the Image as big as our viewport:
batch.draw(img, 0, 0, 50, 50);
The Image has a Ratio of 1 because of 50(width)/50(height) = 1 so the Image will always have the same width and height. The Bars on the side are outside of our Viewport and will be drawn in the color you define here: Gdx.gl.glClearColor(1, 1, 1, 1);
ExtendViewport
Maybe we won't Bars on the Side then we can take a ExtendViewport. The ExtendViewport keeps the world aspect ratio without bars by extending the world in one direction. Means on a screen where the aspect ratio between width and height are bigger you will see more of the world.
On a screen 400x200 aspect ration = (400/200 = 2) you will see more than on a screen of 300x200 (300/200 = 1.5);
To show this create a ExtendViewport and draw the Image bigger than the viewport and a second small Image:
camera = new OrthographicCamera(50, 50);
camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0);
viewport = new ExtendViewport(camera.viewportWidth, camera.viewportHeight, camera);
// in render() method
batch.begin();
batch.draw(img, 0, 0, 100, 50);
batch.draw(img, -20, 0, 20, 20);
batch.end();
If we now start our Program with a Screen size of 200x200 we see:
And if we resize the Screen on x axis To make the screen wider:
Now we can see more off the first Image and additinal the Second image but the ratio will always be the same. The Image is only stretched because we draw it 100x50 not because of resizing.
I hope this will clear some Questions about Camera and Viewport if you will learn more, read and look some tutorials and read the LibGdx wiki: https://github.com/libgdx/libgdx/wiki

Huge Image when Using Pixel Per Meter on Libgdx Box2d World

hi guys I am trying to implement a box2d world. I have read that box2d uses meters. and You need to convert it from pixels to meters.
I tried to draw an image but do I have to scale down also the image? I think that is a bad I idea to draw the image, the image are very huge and can't figure what to do to make it work with the box2d pixel per meter
public class TestScreen extends ScreenAdapter {
private final Body body;
private int V_WIDTH = 320;
private int V_HEIGHT = 480;
private int PPM = 100;
private SpriteBatch batch;
private OrthographicCamera camera;
private World world;
private Sprite sprite;
Box2DDebugRenderer box2DDebugRenderer;
public TestScreen(){
batch = new SpriteBatch();
camera = new OrthographicCamera();
camera.setToOrtho(false, V_WIDTH / PPM, V_HEIGHT / PPM);
camera.position.set(0,0,0);
world = new World(new Vector2(0,0) , true);
sprite = new Sprite(new Texture("test/player.png"));
box2DDebugRenderer = new Box2DDebugRenderer();
BodyDef bodyDef = new BodyDef();
bodyDef.type = BodyDef.BodyType.KinematicBody;
body = world.createBody(bodyDef);
FixtureDef fixtureDef = new FixtureDef();
PolygonShape shape = new PolygonShape();
shape.setAsBox(sprite.getWidth()/2 / PPM, sprite.getHeight()/2 / PPM);
fixtureDef.shape = shape;
body.createFixture(fixtureDef);
sprite.setPosition(body.getPosition().x - sprite.getWidth() /2 ,body.getPosition().y - sprite.getHeight() / 2 );
}
#Override
public void render(float delta) {
super.render(delta);
camera.position.set( body.getPosition().x, body.getPosition().y , 0);
camera.update();
world.step(1/60.0f, 6, 2);
batch.setProjectionMatrix(camera.combined);
batch.begin();
sprite.draw(batch);
batch.end();
box2DDebugRenderer.render(world, camera.combined);
}
}
with out ppm
with PPm
should I scale down the image? what is the best way to draw the image
You don't need to convert from pixel to meter. As a matter of fact you should forget about pixels. They exist only on your screen and you game logic should not know anything about your screen. That is what a camera or viewport is for, you specify how much of the world to show and if the display should be stretched or blackboxed or whatever. So no pixels, period. They are evil and give you wrong ideas.
Now if you create your own game you can say that a single unit represents 1mm, 34cm or a couple of lightyears. You tell the object responsible for displaying your game how much of these units to display. However you are using Box2D, and Box2D has already filled in the unit for you 1 unit == 1m. It is probably possible to change this or at least create a wrapper class that converts you units to the Box2D unit.
The reason why it is important to keep true to the Box2D unit is the following. If you drop a marble on the ground it seems to be moving faster then the sun in the sky. But believe me, the sun is moving a lot faster but since it is a lot further away it seems to move slowly. Since Box2D is all about movement you should keep true to the unit or things will start to act strange.
Let's just use 1 unit == 1m for now and suddenly everything should become a lot simpler by asking a view questions.
how much of your game world do you want to show in meters?
float width = 20; // 20 meters
//You can calculate on your chosen width or height to maintain aspect ratio
float height = (Gdx.graphics.getHeight() / Gdx.graphics.getWidth()) * width;
camera = new OrthographicCamera(width, height);
//Now the center of the camera is on 0,0 in the game world. It's often more desired and practical to have it's bottom left corner start out on 0,0
//All we need to do is translate it by half it's width and height since that is the offset from it's center point (and that is currently set to 0,0.
camera.translate(camera.viewportWidth / 2, camera.viewportHeight / 2, 0);
camera.update();
How large is our object? Keep in mind that mass, weight and size are completely different things.
Sprite mySprite = new Sprite(myTexture);
//position it somewhere within the bounds of the camera, in the below case the center
//This sprite also gets a size of 1m by 1m
mySprite.setBounds(width / 2, height / 2, 1, 1);
How do we want the SpriteBatch to draw to the screen?
//We tell the SpriteBatch to use out camera settings to draw
spriteBatch.setProjectionMatrix(camera.combined);
//And draw the sprite using this SpriteBatch
mySprite.draw(spriteBatch);
Same counts for the Box2dDebugRenderer implemenation. If you want shapes to show you need to use that combined matrix from your camera again to draw it.
box2DDebugRenderer.render(world, camera.combined);
Of course, when things move around you need to update your sprite position accordingly. You can get this information from the box2d.Body object. But this is beyond the scope of your question.
To finally show you what is going wrong:
camera.setToOrtho(false, V_WIDTH / PPM, V_HEIGHT / PPM);
Your camera shows 320/100 == 3.2f x 480/100 == 4.8f of your game world. Your sprite might be 64x64 pixels. You are not telling anywhere at what size to draw your sprite so it will assume 1 pixel = 1 unit and you set your camera to show 3.2f units in width. We can and should leave pixels out of the equation and just ask what size you want your object to be. Then set the Sprite to that size. Here you see that thinking in pixels just gives you problems.
For a space game where you fly a ship of 100x20 meters in 3th person you probably want your camera viewport to be very large. But for a ant game where your ants are real size you want a very small camera viewport. Do think about physics in real life. Galileo Galilei discovered that objects fall at the same speed, disregarding resistance. So if that ant would drop a sand grain it would look like it would fall very fast because your screen represents much less meters.
For a implementation of a dropping soccer ball look at my answer here. It creates a box2D body and attaches a image to it. I keep the functionality of the ball encapsulated within the Ball() class. (disclaimer: I have just played around a bit with Box2D and I don't know the exact physical behaviors of a soccer ball so I am not stating this is a correct implementation, but it does show how to setup your scene and have a image represent your Box2D body).

Correct pixel dimensions regardless of screen size and resolution

I draw a bitmap on a canvas like this:
float multiplier = (float) canvas.getWidth() / (float) background.getWidth();
Matrix bgMatrix = new Matrix();
bgMatrix.postScale(multiplier, multiplier);
canvas.drawBitmap(background, bgMatrix, paint);
multiplier is used to scale the image to fit the canvas.
Next I need to draw another bitmap on top of it, aligned to the center horizontally and a little bit above the center vertically. I calculate the center point of the second bitmap like this:
int centerX = (int) (canvas.getWidth() - overlay.getWidth() * multiplier) / 2;
int centerY = (int) (canvas.getHeight() - overlay.getHeight() * multiplier) / 2;
where overlay is the overlaying bitmap.
Everything so far is fine. Next part is where it gets tricky. I need to subtract 39 pixels (measured from the original image, "real pixels") from the vertical center point to get the right coordinates. However, as I scale the images and Android handles widths and heights relative to the screens resolution and the screen size, I don't know how I can make the 39 pixels look the same size on all devices.
Currently I draw the overlay image in this way
Matrix matrix = new Matrix();
matrix.setRotate(rotation, overlay.getWidth() / 2, overlay.getHeight() / 2);
matrix.postScale(multiplier, multiplier);
matrix.postTranslate(centerX, centerY);
canvas.drawBitmap(overlay, matrix, paint);
How do I make the 39 pixel reduction look the same on all devices?

Render Fog-Of-War on 2d tilemap

I am currently creating a small 2d-game with lwjgl.
I tried to figure out a way of implementing a Fog-Of-War.
I used a black backgound with alpha set to 0.5.
Then I added a Square, to set alpha to 1 for each tile, which is lit, ending up having a black Background with differend Alpha values.
Then I rendered my Background using the blendfunction:
glBlendFunc(GL_ZERO, GL_SRC_ALPHA)
This works well, but now I have a problem with adding a second layer with transparent parts and apply the Fog-Of-War on them, too.
I've read something about FrameBufferObjects, but I don't know how to use them and if they are the right choice.
Later on I want to lit tiles with an texture/Image to give it a smoother look. So these textures may overlap. This is the reason why I chose to first render the Fog-Of-War.
Do you have an idea how to fix this problem?
Thanks to samgak.
Now I try to render a dark square on each dark tile exept the lit tiles.
I divided each tile in an 8x8 grid for more details. This is my method:
public static void drawFog() {
int width = map.getTileWidth()>>3; //Divide by 8
int height = map.getTileHeight()>>3;
int mapWidth = map.getWidth() << 3;
int mapHeight = map.getHeight() << 3;
//background_x/y is the position of the background in pixel
int mapStartX = (int) Math.floor(background_x / width);
int mapStartY = (int) Math.floor(background_y / height);
//Multiply each color component with 0.5 to get a darker look
glBlendFunc(GL_ZERO, GL_SRC_ALPHA);
glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
glBegin(GL_QUADS);
//RENDERED_TILES_X/Y is the amount of tiles to fill the screen
for(int x = mapStartX; x < (RENDERED_TILES_X<<3) + mapStartX
&& x < mapWidth; x++){
for(int y = mapStartY; y < (RENDERED_TILES_Y<<3) + mapStartY
&& y < mapHeight; y++){
//visible is an boolean-array for each subtile
if(!visible[x][y]){
float tx = (x * width) - background_x;
float ty = (y * height) - background_y;
glVertex2f(tx, ty);
glVertex2f(tx+width, ty);
glVertex2f(tx+width, ty+height);
glVertex2f(tx, ty+height);
}
}
}
glEnd();
}
I set the visible array to false except for an small square.
It will render fine, but if I move the background the whole screen except the visible square turns black.
One approach is to render the Fog-of-War layer last, using an untextured black square rendered over the top of all the other layers after they have been rendered.
Use this blend function:
glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA)
and set the Fog-of-War alpha per-vertex so that when it is 1.0 the black overlay is transparent, and when it is 0.0, it is entirely black. (If you want the alpha to have the opposite meaning, just swap the arguments).
To make it more smooth you can set the alpha per vertex at each of the corners of the square to vary smoothly across it. You could also use a texture with varying alpha values instead of a plain black square, or subdivide the square into 4 or 16 squares to allow finer control.

Camera following target in Box2D

I am trying to follow player with camera in Box2D world. But there is an offset. And I think it has something to do with pixel per meter conversion. Before you check my code you should know that Values.WTB = World_To_Box and has a values of 0.032f and Values.BTW = Box_To_World and has a values of 32f.
Here is the render part:
#Override
public void render(float delta) {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
Gdx.gl.glClearColor(0.105f,0.105f,0.105f,1f);
camera.position.set(player.getPosition().x*Values.BTW, player.getPosition().y*Values.BTW, 0);
camera.update();
Matrix4 cameraCopy = camera.combined.cpy();
cameraCopy.scl(Values.BTW);
batch.setProjectionMatrix(cameraCopy);
shapeRenderer.setProjectionMatrix(cameraCopy);
batch.begin();
player.draw(batch);
batch.end();
debugRenderer.render(world, cameraCopy);
world.step(1/60f, 6, 2);
shapeRenderer.begin(ShapeType.Filled);
shapeRenderer.setColor(Color.GREEN);
shapeRenderer.circle(player.getPosition().x, player.getPosition().y, 5*Values.WTB,10);
shapeRenderer.setColor(Color.ORANGE);
shapeRenderer.circle(camera.position.x*Values.WTB, camera.position.y*Values.WTB, 5*Values.WTB,10);
shapeRenderer.end();
}
and here is picture to demonstrate:
Green point is where the center of player is and Orange point is where the camera center is. And further you go from 0,0 coordinates the bigger is offset.
What am I doing wrong?
Values.WTB = World_To_Box and has a values of 0.032f and Values.BTW = Box_To_World and has a values of 32f
There is no reason to change your WTB / BTW values to 0.01f and 100f like it was suggested by others, since yours are nearly correct. Conversions in powers of two are also a lot faster than conversions by 100.
If you want 32 screen pixels per box2d meter then keep using Values.BTW = 32f. But then Values.WTB would be 1f / 32f = 0.03125f, not 0.032f. It is just a small difference, but it makes a difference in the end.
Change your values to:
static final float WORLD_TO_BOX = 0.01f;
static final float BOX_TO_WORLD = 100f;
Why 0.032 and 32 are not working:
For example if you want to convert 100px to Box2d units:
100 * 0.032 = 3.2
And then from Box2d units to pixels:
3.2 * 32 = 102.4
And of course the difference will be bigger if you are converting bigger values.

Categories