libGDX: size of object in FitViewport is distorted - java

My goal is to create a game that is always displayed with an aspect ratio of 9:16 (basically 16:9, but upright) using FitViewport; it should be independet of a target device's resolution. In order to test this setup, I created the following minimal working example. A small green square indicates the origin of the coordinate system:
MyGame.java
public class MyGame extends ApplicationAdapter {
final int WORLD_WIDTH = 900;
final int WORLD_HEIGHT = 1600;
Stage stage;
Viewport vp;
public void create() {
stage = new Stage();
vp = new FitViewport(WORLD_WIDTH, WORLD_HEIGHT, stage.getCamera());
stage.setViewport(vp);
stage.addActor(new MySquare());
}
public void render() {
stage.act();
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.draw();
}
public void resize(int width, int height) {
stage.getViewport().update(width, height, true);
}
// dispose...
}
MySquare.java
public class MySquare extends Actor {
ShapeRenderer renderer = new ShapeRenderer();
#Override
public void draw(Batch batch, float alpha){
batch.end();
renderer.begin(ShapeRenderer.ShapeType.Filled);
renderer.setColor(Color.GREEN);
renderer.rect(0, 0, 50, 50);
renderer.end();
batch.begin();
}
}
Unfortunately, the result is not as expected: As you can see, the green square is actually not a square. This behavior is the same for both Windows and Android (in landscape mode):
However, when setting the size of the window programmatically and explicitly via LwjglApplicationConfiguration in DesktopLauncher.java to a valid 9:16 resolution, the green square is displayed correctly. Why is that and how can I avoid this workaround (which does not work for Android anyway)?

Your problem is that your shape renderer is ignoring the camera. Update it like this:
public void draw(Batch batch, float alpha){
batch.end();
renderer.setProjectionMatrix(batch.getProjectionMatrix()); // <<<<< Add this
renderer.begin(ShapeRenderer.ShapeType.Filled);
renderer.setColor(Color.GREEN);
renderer.rect(0, 0, 50, 50);
renderer.end();
batch.begin();
}
If you are planning to eventually use sprites, and you're simply wanting rectangle placeholders for your actors, you don't need a custom actor for this. You can use a generic Actor and call setDebug(true) on it, and Stage will automatically draw its outline using an internal ShapeRenderer. Of course, you must first set a size and position on the Actor.

Related

When converting a position from my HUD viewport to my game-area viewport, why are the Y-values incorrect on my android app?

I'm building a mobile Box2D body/joint editor in libGDX, but I'm getting inconsistent results between my desktop and android builds, the android build producing incorrect position Y-values. In summary, the editor consists of a HUD with its own FitViewport for consistent screen presence, and the actual build area grid with its own FitViewport that can be navigated and zoomed, much like any run-of-the-mill image editor. The user touches the screen, and a target appears above that position that can be dragged around to select points on the grid, connecting points that serve as the vertices of the shapes that will become Box2D bodies. This target-selector exists on the HUD viewport so that it can remain a static size if the user were to zoom in on the grid area. So, in order to select a point on this grid, I have to convert the target's position in the HUD viewport to screen coordinates, then convert it to that point on the build area grid. Next to the target, I have a BitmapFont that renders text of the targets current position.
So here's an example of my problem. Let's say the target is at the arbitrary point (0,0) on the grid. The text would say "0,0". In my desktop build it does this accurately. In my Android build, the Y-value is offset by varying values, based on camera zoom, even though that point is without a doubt (0, 0) in that space(The bottom-left of the grid is coded at 0,0). I don't understand how this could happen, since both builds share the core module where this code resides. I knew this would be hard to explain, so I took some screen shots.
These images show the desktop build, no issues, correct values:
desktop app image, default zoom, y-value is correct
desktop app image, camera zoomed in, y-value is still correct
These images show the android build, y-value is incorrect but becomes more correct as the camera zooms in:
android app image, default zoom, y-value is way off
android app image, zoomed in, y-value is closer to correct but still off
I really want to show some code, but I have no idea which parts are relevant to this issue. Essentially what is happening is that the user touches the screen, the target pops up 200px (HUD stage local) above that point, the targets position is converted to screen coordinates, then unprojected to build area grid coordinates. What gets me is that the Y-values are correct in the desktop build, so why would they be different in the android build. Here is my TargetSelector class where everything goes down:
public class TargetSelector extends Group {
private ShapeRenderer shapeRenderer;
private BitmapFont font;
private Vector2 position;
private float yOffset;
private boolean targetActive;
private Vector2 unprojectedPosition;
private OrthographicCamera linkedCamera;
private boolean hasLink = false;
public TargetSelector(){
initShapeRenderer();
initTargetFields();
initFont();
initInputListener();
}
private void initShapeRenderer(){
shapeRenderer = new ShapeRenderer();
shapeRenderer.setAutoShapeType(true);
shapeRenderer.setColor(Color.RED);
}
private void initTargetFields(){
unprojectedPosition = new Vector2();
position = new Vector2();
yOffset = 200;
targetActive = false;
}
private void initFont(){
font = new BitmapFont();
font.getData().scale(1.0f);
font.setColor(Color.WHITE);
}
private void initInputListener(){
setBounds(0, 0, Info.SCREEN_WIDTH, Info.SCREEN_HEIGHT);
addListener(new InputListener(){
#Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
targetActive = true;
position.set(x, y).add(0, yOffset);
return true;
}
#Override
public void touchDragged(InputEvent event, float x, float y, int pointer) {
position.set(x, y).add(0, yOffset);
}
#Override
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
targetActive = false;
}
#Override
public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) {
targetActive = false;
}
});
}
#Override
public void act(float delta) {
super.act(delta);
if(targetActive && hasLink){
updateUnprojectedPosition();
}
}
#Override
public void draw(Batch batch, float parentAlpha) {
if(targetActive){
batch.end();
Gdx.gl.glEnable(GL20.GL_BLEND);
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
shapeRenderer.setProjectionMatrix(getStage().getCamera().combined);
shapeRenderer.begin();
// Render target
Gdx.gl20.glLineWidth(3);
shapeRenderer.set(ShapeRenderer.ShapeType.Line);
shapeRenderer.setColor(Color.RED);
shapeRenderer.circle(position.x, position.y, 32);
shapeRenderer.circle(position.x, position.y, 1);
// Render position text background
Vector2 fontPosition = position.cpy().add(64, 64);
shapeRenderer.set(ShapeRenderer.ShapeType.Filled);
shapeRenderer.setColor(0, 0, 0, 0.5f);
shapeRenderer.rect(fontPosition.x - 8, fontPosition.y - 32, 172, 40);
shapeRenderer.end();
Gdx.gl.glDisable(GL20.GL_BLEND);
Gdx.gl20.glLineWidth(1);
batch.setProjectionMatrix(getStage().getCamera().combined);
batch.begin();
// Render position text
String text;
if(hasLink)
text = "(" + (int) unprojectedPosition.x + " , " + (int) unprojectedPosition.y + ")";
else
text = "No Link";
font.draw(batch, text, fontPosition.x, fontPosition.y, 100, 10, false);
}
super.draw(batch, parentAlpha);
}
public void updateUnprojectedPosition(){
Vector2 screenPosV2 = localToScreenCoordinates(position.cpy());
Vector3 posV3 = new Vector3(screenPosV2.x, screenPosV2.y, 0);
linkedCamera.unproject(posV3);
unprojectedPosition.set(MathUtils.round(posV3.x), MathUtils.round(posV3.y));
}
public void linkToCamera(OrthographicCamera camera){
if(camera != null){
linkedCamera = camera;
hasLink = true;
}
}
}
The linkedCamera is assigned in a manager class that handles the relationship between all HUD actions and the build area grid.
This is my first time ever asking a question here, I'm sorry if I'm being too verbose but the issue is complicated to explain, at least it was for me.

libgdx TiledMap not correct size

I'm trying to use a TiledMap in a test game but I'm having issues with the size. I'm using an ExtendViewport with width 160 and height 90. I guess the problem is that the tiled map is drawing using the screen size, because it's zoomed in. Do I need 2 seperate cameras for the tiled map and the rest of the game (players, enemies, ...)?
This is all of my code:
public class Main extends Game {
private OrthographicCamera camera;
private ExtendViewport viewport;
private TiledMap tiledMap;
TiledMapRenderer tiledMapRenderer;
#Override
public void create () {
camera = new OrthographicCamera(160, 90);
camera.setToOrtho(false, 160, 90);
camera.update();
viewport = new ExtendViewport(160, 90, camera);
viewport.apply();
tiledMap = new TmxMapLoader().load("map1.tmx");
tiledMapRenderer = new OrthogonalTiledMapRenderer(tiledMap);
}
#Override
public void render () {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
camera.update();
tiledMapRenderer.setView(camera);
tiledMapRenderer.render();
}
#Override
public void resize(int width, int height) {
viewport.update(width, height, false);
}
#Override
public void dispose () {
}
}
Thanks
OrthogonalTiledMapRenderer's second parameter is unitScale. The unit scale tells the renderer how many pixels map to a single world unit. And it defaults to 1.
So in your case one pixel is equals to one unit size in tiled map. Try changing the unitScale parameter.
https://github.com/libgdx/libgdx/wiki/Tile-maps#rendering-tiled-maps

LibGdx Understanding the viewports and associtate it with a Scene2D

Whenever I try to set my viewport to a Stage, the actors, that I puting into the Scene are blured and very large. I want to set my Game world size as a rectangle of 50x100 units and then scale everything (Sprites, actors, labels, fonts) according that units. Every state inharitate form a class State which provides render function etc.
public abstract class State {
public static final float GAME_SIZE_WIDTH = 50 ;
public static final float GAME_SIZE_HEIGHT = 100 ;
protected OrthographicCamera camera;
protected GameStateManager gsm;
protected Viewport viewport;
public State(GameStateManager gsm) {
this.gsm = gsm;
camera = new OrthographicCamera();
viewport = new ExtendViewport(GAME_SIZE_WIDTH, GAME_SIZE_HEIGHT, camera);
viewport.apply();
camera.position.set(GAME_SIZE_WIDTH / 2, GAME_SIZE_HEIGHT / 2, 0);
}
public abstract void handleInput();
public abstract void update(float dt);
public abstract void render (SpriteBatch sb);
public abstract void dispose();
public abstract void resize(int width, int height) ;
}
In every State I want to add the Stage and pass the viewPort for my State class according to keep the GAME_SIZE_WIDTH /GAME_SIZE_HEIGHT dimension, but Ive got unadjusted backgrounds (even if its full HD picture the white border on the left) and the Buttons are blured with unadjusted text on it.
public class MenuState extends State implements InputProcessor {
private Skin skin;
private Stage stage;
private Sprite background;
private Table table;
private TextButton startButton,quitButton;
private Sprite flappyButton;
private Sprite chodzenieButton;
private Sprite memoryButton;
private Sprite rememberSquare;
private static final String TAG = "kamil";
private Vector3 touchPoint;
public MenuState(GameStateManager gsm) {
super(gsm);
skin = new Skin(Gdx.files.internal("button/uiskin.json"));
stage = new Stage(viewport);
table = new Table();
table.setWidth(stage.getWidth());
table.align(Align.center | Align.top);
table.setPosition(0,GAME_SIZE_HEIGHT);
startButton = new TextButton("New Game",skin);
quitButton = new TextButton("Quit Game",skin);
startButton.addListener(new ClickListener() {
#Override
public void clicked(InputEvent event, float x, float y) {
Gdx.app.log("Clicked button","Yep, you did");
event.stop();
}
});
table.padTop(5);
table.add(startButton).padBottom(5).size(20,10);
table.row();
table.add(quitButton).size(20,10);
stage.addActor(table);
background =new Sprite(new Texture(Gdx.files.internal("backgrounds/bg.jpg")));
background.setSize(GAME_SIZE_WIDTH,GAME_SIZE_HEIGHT);
background.setPosition(0,0);
touchPoint= new Vector3();
InputMultiplexer im = new InputMultiplexer(stage,this);
Gdx.input.setInputProcessor(im);
flappyButton=new Sprite(new Texture("menuButton/floopyBirdButtonTexture.png"));
chodzenieButton =new Sprite(new Texture("menuButton/swipeMovementsButtonTexture.png"));
memoryButton =new Sprite(new Texture("menuButton/memory.png"));
rememberSquare = new Sprite(new Texture("menuButton/rememberSquare.png"));
rememberSquare.setSize(20,10);
flappyButton.setSize(20,10);
chodzenieButton.setSize(20,10);
memoryButton.setSize(20,10);
flappyButton.setPosition(GAME_SIZE_WIDTH/2-flappyButton.getWidth()/2,GAME_SIZE_HEIGHT/2-flappyButton.getHeight()/2);
chodzenieButton.setPosition(GAME_SIZE_WIDTH/2-flappyButton.getWidth()/2,flappyButton.getY()- chodzenieButton.getHeight() -2);
memoryButton.setPosition(chodzenieButton.getX(),chodzenieButton.getY()- flappyButton.getHeight() -2);
rememberSquare.setPosition(flappyButton.getX(),flappyButton.getY()+flappyButton.getHeight()+2);
}
#Override
public void render(SpriteBatch sb) {
sb.setProjectionMatrix(camera.combined);
sb.begin();
background.draw(sb);
flappyButton.draw(sb);
chodzenieButton.draw(sb);
memoryButton.draw(sb);
rememberSquare.draw(sb);
sb.end();
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}
#Override
public void dispose() {
flappyButton.getTexture().dispose();
chodzenieButton.getTexture().dispose();
background.getTexture().dispose();
}
#Override
public void resize(int width, int height) {
Gdx.app.log("kamil","Resize");
viewport.update(width,height);
}
#Override
public void handleInput() {
if(Gdx.input.justTouched())
{
//
viewport.unproject(touchPoint.set(Gdx.input.getX(),Gdx.input.getY(),0));
if(flappyButton.getBoundingRectangle().contains(touchPoint.x,touchPoint.y)) {
gsm.set(new FlopyBirdState(gsm));
}
else if (chodzenieButton.getBoundingRectangle().contains(touchPoint.x,touchPoint.y)){
gsm.set(new ChodzenieState(gsm));
}
else if (memoryButton.getBoundingRectangle().contains(touchPoint.x,touchPoint.y)){
gsm.set(new MemoryState(gsm));
}
else if (rememberSquare.getBoundingRectangle().contains(touchPoint.x,touchPoint.y)){
gsm.set(new FoodEaterState(gsm));
}}
}
blured and very large : This is due to font size you're using.
Blured when some font scaled in appropriate manner, doesn't matter it is by you or by viewport.
You're using 50 and 100 as world width and height of viewport, and you're using BitmapFont from skin that having greater size as compare to 50 and 100. so when you update viewport by screenwidth and height then BitmapFont also scale up so look very large and blurry too.
Solution:
Use some greater world width and height of viewport by comparing your BitmapFont that you're using.
Use small size of your BitmapFont or you can scale down so that not scaled too much and look ugly.
skin.get("font-name",BitmapFont.class).getData().setScale(.25f);
you're using Table so you may need to check where your Actor is in cell/Table. You can enable debugging by this flag stage.setDebugAll(true);
EDIT
You're using ExtendViewport, from wiki
The ExtendViewport keeps the world aspect ratio without black bars by extending the world in one direction. The world is first scaled to fit within the viewport, then the shorter dimension is lengthened to fill the viewport.
Let's suppose you're device width and height is 400 and 640, then what happen when you update viewport by 400 and 640. First your background Sprite scaled in height and set his height to 640(because viewport worldheight is equal to background height) done, now it's time for width of background Sprite because you set width is half of height so scaled and set size 640/2 = 320. done
Your problem arrive, my device width is 400 but my background sprite size is only 320 rest (40*2 unit is white from both side).
Use 48*80(most of devices is in this ratio) instead of 50*100

Fit Viewport Black Bars

I'm currently developing a game where you have to avoid Asteroids. To make the Game look same on every device I use the FitViewport. Unfortunately I somehow get White Bars on the top and on the Bottom instead of Black ones. My Game Background is also white, so it looks a bit weird.
GameScreen:
#Override
public void create()
{
float aspectRatio = (float)Gdx.graphics.getWidth() / (float)Gdx.graphics.getHeight();
cam = new OrthographicCamera();
viewport = new FitViewport(MyGdxGame.WIDTH * aspectRatio, MyGdxGame.HEIGHT, cam);
[...]
}
#Override
public void render(SpriteBatch batch)
{
cam.update();
batch.setProjectionMatrix(cam.combined);
batch.begin();
em.render(batch); //render Ship and Asteroids
[...]
}
#Override
public void resize(int width, int height)
{
viewport.update(width, height);
cam.position.set(MyGdxGame.WIDTH / 2, MyGdxGame.HEIGHT /2, 0);
}
I dragged the Ship into the white Bar.
LibGDX provides viewports as a more convenient way of dealing with different aspect ratios. You don't have to multiply MyGdxGame.WIDTH with aspectRatio. Just initialize it with MyGdxGame.WIDTH and MyGdxGame.HEIGHT.
Also, in resize function, you can change the cam position using viewport values (instead of using constants):
cam.position.set(cam.viewportWidth / 2, cam.viewportHeight / 2, 0);
I found some issues in your code. for best practice while handling with the different screen ratio just try with the fill viewPort. Here is simply editing your code with the fill viewport . Just try it once.
#Override
public void create()
{
float aspectRatio = (float)Gdx.graphics.getWidth() / (float)Gdx.graphics.getHeight();
camera = new OrthographicCamera();
camera.position.set(0, 0, 0);
camera.update();
//1280 is the screen width and 800 is screen height
camera.setToOrtho(false, 1280, 800);
viewPort = new FillViewport(1280, 800, camera);
}
#Override
public void render(SpriteBatch batch)
{
batch.setProjectionMatrix(camera.combined);
batch.begin();
em.render(batch); //render Ship and Asteroids
[...]
}
#Override
public void resize(int width, int height)
{
viewPort.update(width, height);
}
just try with the above code . it will definitely work

LibGDX Screen Support Sizes in Android

I'm trying to create a game with LibGDX. My problem comes when I put the background in the screen that I have. I created a Base Screen (abstract) in order to make easier. In Desktop, the screen fits good, but in Android, trying different devices, the screen doesn't scale to full screen. I solved this situation using the next easy code:
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
batch.draw(texture, 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
batch.end();
}
That was enough to see my game in full screen on android devices. Now, I want to use Scene2D(which I don't know so much...) So I create a Stage, I'm dealing with the background like an actor. Here's my code.
public class MenuScreen extends BaseScreen {
private FitViewport viewport;
private Stage stage;
private Image imageFondo, imagePrueba;
//private float escala;
public MenuScreen(MissingWords missingwords) {
super(missingwords);
}
#Override
public void show() {
//viewport = new StretchViewport(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
viewport = new FitViewport(800, 480);
stage = new Stage(viewport, missingwords.getSB());
//Gdx.input.setInputProcessor(stage);
/* Crear fondo */
imageFondo = new Image(missingwords.getAM().get("fondo.png", Texture.class));
stage.addActor(imageFondo);
imagePrueba = new Image(missingwords.getAM().get("prueba.png", Texture.class));
imagePrueba.setPosition(50, 50);
stage.addActor(imagePrueba);
}
#Override
public void render(float delta) {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act();
stage.draw();
}
#Override
public void resize(int width, int height) {
stage.getViewport().update(width, height, true);
}
However, it doesn't work and I don't know if I'm using the Viewports in the correct way. How can I write this line in order to be compatible with Scene 2D and support android devices?
batch.draw(texture, 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Thanks in advance.
Finally I "solved" it using a ScalingViewport. Like this:
viewport = new ScalingViewport(Scaling.stretch, 800, 480);
Scaling.stretch as javadoc says: the world is scaled to take the whole screen. So, it doesn't keep the aspect ratio but for me is good. Maybe it looks wrong using large screens, but I'm still learning and I don't know if this is the best solution. So, I hope this helps someone.

Categories

Resources