I'm making a game on both desktop and android and the same code results in two different screens (cam.zoom is the same in both):
The desired result is the left screen and the code used to render is like so: (the image is 8000x4000 and is a place holder for my map)
public class MainMenuScreen implements Screen {
private final Application application;
private SpriteBatch batch;
private ShapeRenderer sr;
private OrthographicCamera cam;
private Viewport viewport;
private Button startGame;
private Texture background = new Texture(Gdx.files.internal("background.jpg"));
public MainMenuScreen(final Application application) {
this.application = application;
cam = new OrthographicCamera(WIDTH, HEIGHT);
viewport = new FitViewport(WIDTH, HEIGHT, cam);
batch = this.application.batch;
sr = this.application.sr;
startGame = new Button(-50, -25, 100, 50);
startGame.setColour(new Color(0.01f, 0.35f, 0.47f, 1));
startGame.setText("Start game", this.application.font);
startGame.setHasBorder(true);
startGame.setBorderThickness(3);
startGame.setBorderColour(new Color(0.02f, 0.62f, 0.79f, 1));
startGame.setTextColour(new Color(0.02f, 0.62f, 0.79f, 1));
}
public void update() {
Vector2 mousePos = Extras.getMousePos(viewport);
if (startGame.isClickedOn(mousePos)) {
}
}
#Override
public void render(float delta) {
ScreenUtils.clear(0, 0, 0, 1);
batch.setProjectionMatrix(cam.combined);
sr.setProjectionMatrix(cam.combined);
update();
batch.begin();
batch.draw(background, -background.getWidth() / 2, -background.getHeight() / 2);
batch.end();
startGame.render(this.sr, this.batch);
}
How do I make sure all different devices have the same zoom in the camera?
A possible solution I don't know how to implement yet could be setting cam.zoom to a value dependent on the screen size somehow but there has to be a better way for this using libGDX itself.
First, are you trying to make the button size's same on all device's screen resolution? or you want to make camera zooming have same value?
Ok, How the variable WIDTH and HEIGHT was declared?if you have declared both with one spesific size. Its will be not same if you're trying in another resolutions.
EXAMPLE:
//Constant Spesific Value of WIDTH and HEIGHT Size.
public static final int WIDTH = 1280, HEIGHT = 720;
///////////////////////////////////////////////////
//Non-Constant Value of WIDTH and HEIGHT Size.
public static int WIDTH, HEIGHT;
...
//Define Both In Main.class
public MainGameScreen(){
WIDTH = Gdx.graphics.getWidth();
HEIGHT = Gdx.graphics.getHeight();
}
However, LibGDX's screen resolution can be calculated. Some object size in your game must be spesific, when calculating the size between both WIDTH and HEIGHT you need to know, how the size will be same in all device with one spesific calculation.
EXAMPLE:
/* Button (256x72) in Screen Resolution (1280x720)
Button (160x48) in Screen Resolution (800x480) */
startGame = new Button(0,0, WIDTH/5, HEIGHT/10);
//Button (400x100) in All Screen (Constant Value)
startGame = new Button(0,0, 400, 100);
So, i suggest you to make a calculation to sizing some objects in your game. Because its will be different if you trying to make value in constant.
Ok, the next is, OrthographicCamera Which you want to make same value of zoom right? the cam.zoom is a floating value. Its not a constant value, but it was a variable value that can be changed in all time.
You can trying this code, if you want to zoom camera directly on game
if(Gdx.input.isTouched()){
//Zoom Out
cam.zoom += Gdx.graphics.getDeltaTime();
//Zoom In
cam.zoom -= Gdx.graphics.getDeltaTime();
}
Or if you talking about static-zoom, in a spesific value (not directly changed)
cam.zoom = 1f; //for 1 value of Zoom Out
I hope you understand what i means and im sorry if my answers can't be helpful for you, cause im still learning too, all about LibGDX.
#Alf Equilfe was the one who helped me get to it with his answer and here is the actual solution:
Set WIDTH, HEIGHT to a constant value independent of device size (make it so that any device can handle the size, in my case i chose 1024, 512) and make the aspect ratio the scale so it looks same on all devices.
Related
I know there are quite some questions (and answers) on this topic, but they all have different solutions, and none of them seems to be working in my case.
I'm developing a small test project with libGDX, in which I tried to add a simple tilemap. I created the tilemap using Tiled, which seems to be working quite good, except for the texture bleeding, that causes black lines (the background color) to appear between the tiles sometimes.
What I've tried so far:
I read several SO-questions, tutorials and forum posts, and tried almost all of the solutions, but I just don't seem to get this working. Most of the answers said that I would need a padding between the tiles, but this doesn't seem to fix it. I also tried loading the tilemap with different parameters (e.g. to use the Nearest filter when loading them) or rounding the camera's position to prevent rounding problems, but this did even make it worse.
My current setup:
You can find the whole project on GitHub. The branch is called 'tile_map_scaling'
At the moment I'm using a tileset that is made of this tile-picture:
It has two pixels of space between every tile, to use as padding and margin.
My Tiled tileset settings look like this:
I use two pixels of margin and spacing, to (try to) prevent the bleeding here.
Most of the time it is rendered just fine, but still sometimes there are these lines between the tiles like in this picture (sometimes they seem to appear only on a part of the map):
I'm currently loading the tile map into the asset manager without any parameters:
public void load() {
AssetManager manager = new AssetManager();
manager.setLoader(TiledMap.class, new TmxMapLoader(new InternalFileHandleResolver()));
manager.setErrorListener(this);
manager.load("map/map.tmx", TiledMap.class, new AssetLoaderParameters());
}
... and use it like this:
public class GameScreen {
public static final float WORLD_TO_SCREEN = 4.0f;
public static final float SCENE_WIDTH = 1280f;
public static final float SCENE_HEIGHT = 720f;
//...
private Viewport viewport;
private OrthographicCamera camera;
private TiledMap map;
private OrthogonalTiledMapRenderer renderer;
public GameScreen() {
camera = new OrthographicCamera();
viewport = new FitViewport(SCENE_WIDTH, SCENE_HEIGHT, camera);
map = assetManager.get("map/map.tmx");
renderer = new OrthogonalTiledMapRenderer(map);
}
#Override
public void render(float delta) {
//clear the screen (with a black screen)
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
moveCamera(delta);
renderer.setView(camera);
renderer.render();
//... draw the player, some debug graphics, a hud, ...
moveCameraToPlayer();
}
private void moveCamera(float delta) {
if (Gdx.input.isKeyPressed(Keys.LEFT)) {
camera.position.x -= CAMERA_SPEED * delta;
}
else if (Gdx.input.isKeyPressed(Keys.RIGHT)) {
camera.position.x += CAMERA_SPEED * delta;
}
// ...
//update the camera to re-calculate the matrices
camera.update();
}
private void moveCameraToPlayer() {
Vector2 dwarfPosition = dwarf.getPosition();
//movement in positive X and Y direction
float deltaX = camera.position.x - dwarfPosition.x;
float deltaY = camera.position.y - dwarfPosition.y;
float movementXPos = deltaX - MOVEMENT_RANGE_X;
float movementYPos = deltaY - MOVEMENT_RANGE_Y;
//movement in negative X and Y direction
deltaX = dwarfPosition.x - camera.position.x;
deltaY = dwarfPosition.y - camera.position.y;
float movementXNeg = deltaX - MOVEMENT_RANGE_X;
float movementYNeg = deltaY - MOVEMENT_RANGE_Y;
camera.position.x -= Math.max(movementXPos, 0);
camera.position.y -= Math.max(movementYPos, 0);
camera.position.x += Math.max(movementXNeg, 0);
camera.position.y += Math.max(movementYNeg, 0);
camera.update();
}
// ... some other methods ...
}
The question:
I am using padding on the tilemap and also tried different loading parameters and rounding the camera position, but still I have this texture bleeding problem in my tilemap.
What am I missing? Or what am I doing wrong?
Any help on this would be great.
You need to pad the edges of your tiles in you tilesheet.
It looks like you've tried to do this but the padding is transparent, it needs to be of the color of the pixel it is padding.
So if you have an image like this (where each letter is a pixel and the tile size is one):
AB
CB
then padding it should look something like this
A B
AAABBB
A B
C C
CCCCCC
C C
The pixel being padded must be padded with a pixel of the same color.
(I'll try try create a pull request with a fix for your git-repo as well.)
As a little addition to bornander's answer, I created some python scripts, that do all the work to generate a tileset texture, that has the correct edge padding (that bornander explained in his answer) from a texture, that has no padding yet.
Just in case anyone can make use of it, it can be found on GitHub:
https://github.com/tfassbender/libGdxImageTools
There is also a npm package that can extrude the tiles. It was built for the Phaser JS game library, but you could still use it. https://github.com/sporadic-labs/tile-extruder
I'm very new to java and I'm currently developping a 2D game.
I'm trying to use Java Swing for the graphics and I have a problem doing so :
Displaying the background, which is a fairly high definition image, (currently 2000x2000 but will grow bigger for higher definition), with a map of 50 units in width and height.
The problem is that I don't want to display the whole map but only a fixed amount of cells in width and height of it (here i chose 20). I first tried to rescale the image wider than the screen to make the 20 cells fit perfectly (a bit like a zoom on the area we want) and then draw it with an offset related to the player's position (which is always displayed on the centered of the screen).
But while i try to go with bigger images, i get a java heap space memory exception.
So i was thinking of getting a cropped version of the image and then rescale it to the screen's dimension to have a smaller rescaled image. I'm not getting exceptions any more but I have some important performances issues with a drop of 30 fps.
I was also thinking about getting all cropped images possible and storing them but I wanted to know if that's a thing or not.
To sum up, I was wondering what were the best ways to display a background in games.
Since 2D games or even 3D games have maps even larger than I do, I think I must be missing something, I don't get how to display sprites with high resolution while keeping a decent frame rate.
Thank your for your time.
edit:
To put a bit of context : The map is a big maze and the player should only be able to see a local view of the maze. And because I would want a rather detailed Background, i have to be able to display large images.
Here is a reduced view of my code sample :
public class Background implements Drawable, Anchor {
private final String name;
private final Image image;
private final int width;
public Background(String name){
this.name = name;
BufferedImage image = FileSystem.readBufferedImage(GraphicType.BACKGROUND, name);
//image is a 2000x2000 image
this.image = image.getScaledInstance((int)(behavior.width()
* (Game.frame.getWidth()/(double) Settings.NB_CELLS_ON_SCREEN_WIDTH)),
-1,
0
);
//result in a 19200x19200 image
this.width = (int)(behavior.width()
* (Game.frame.getWidth()/(double)Settings.NB_CELLS_ON_SCREEN_WIDTH));
}
#Override
public void draw(Graphics g) {
g.drawImage(
image,
-8640,
-9060,
null);
}
}
With the GraphicPosition class computing the position on screen, with the following arguments in constructor : An anchor object, an xOffset and a yOffset
I now draw the background using the drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer)
As matt suggested, it causes a less significant fps drop because i'm no longer creating a huge temporary image and the good thing about this solution is that the difference in frame rate with bigger images seems to be small.
Here is the code I used and worked for me :
public class Foreground implements Drawable {
private final String name;
private final BufferedImage image;
private final Behavior behavior;
private final int width;
public Foreground(String name, Behavior behavior){
this.name = name;
this.image = FileSystem.readBufferedImage(GraphicType.FOREGROUND, name);
this.behavior = behavior;
}
#Override
public void draw(Graphics g) {
/*This variable represents the width on the image of the number of cells
In my code its is the width on the image of 20 cells*/
int widthOnImageForNbCellsDisplayed = (image.getWidth()/behavior.width()) * Settings.NB_CELLS_ON_SCREEN_WIDTH;
/*This posXInImage represents the position of the top left corner of the
area of the image we want to draw*/
int posXInImage = (int)(
Game.player.posX()
* image.getWidth()
/ (double)behavior.width()
- widthOnImageForNbCellsDisplayed/2
);
int posYInImage = (int)(
Game.player.posY()
* image.getHeight()
/ (double)behavior.height()
- widthOnImageForNbCellsDisplayed/2
);
/*Here is struggled with the fact that the position of the second
coordinates is absolute and not relative to the first coordinates
,what I was originally thinking*/
g.drawImage(image,
0,
-(Game.frame.getWidth() - Game.frame.getHeight())/2,
Game.frame.getWidth(),
Game.frame.getWidth()-(Game.frame.getWidth() - Game.frame.getHeight())/2,
posXInImage,
posYInImage,
posXInImage + widthOnImageForNbCellsDisplayed,
posYInImage + widthOnImageForNbCellsDisplayed,
null);
}
}
I'm trying to stick toolbar buttons to the top-right of my screen, but whenever I resize the window, the buttons stay in the same absolute position (relative to the bottom-left).
Before resize:
After resize:
This is the method that generates the UI button (Which is currently only called in the Screen's constructor):
private void generateUiToolbar(){
// Toolbar Table
Table table = new Table();
table.setFillParent(true);
table.top();
table.right();
// Menu button
table.row();
Texture myTexture = new Texture(Gdx.files.internal("ui/stamp.png"));
TextureRegion myTextureRegion = new TextureRegion(myTexture);
TextureRegionDrawable myTexRegionDrawable = new TextureRegionDrawable(myTextureRegion);
ImageButton menuButton = new ImageButton(myTexRegionDrawable);
menuButton.addListener( new ClickListener() {
#Override
public void clicked(InputEvent event, float x, float y) {
stage.clear();
generateUiMenu();
}
});
table.add(menuButton).size(60,60).padTop(10).padRight(10);
stage.addActor(table);
}
This is the resize() method:
public void resize(int width, int height) {
stage.getViewport().update(width, height, true);
stage.getCamera().viewportWidth = width;
stage.getCamera().viewportHeight = height;
stage.getCamera().position.set(stage.getCamera().viewportWidth / 2, stage.getCamera().viewportHeight / 2, 0);
stage.getCamera().update();
}
The variable stage is initiated in the constructor with stage = new Stage();
For some reason stage.getViewport().update(width, height, true); doesn't do anything. When I print stage.getWidth() after resizing, it will return the same number. What's a good way to update the location of the button or Actors in general? Simply clearing the stage and generating the UI again won't work for me, because sometimes when you resize, there are different actors currently displayed. If I missed any information, please let me know.
EDIT 1:
Added table.setFillParent(true); as mentioned in an answer, but that didn't fix it either.
EDIT 2:
After reading the answers and comments, I deleted the stage.getCamera() manipulation, but I'm still not getting the correct result. This is what happens when I resize with just the line of code stage.getViewport().update(width, height, true);:
I think you misunderstand the usage of Viewport.
In your resize method you do:
stage.getViewport().update(width, height, true);
stage.getCamera().viewportWidth = width;
stage.getCamera().viewportHeight = height;
stage.getCamera().position.set(stage.getCamera().viewportWidth / 2, stage.getCamera().viewportHeight / 2, 0);
stage.getCamera().update();
But stage.getViewport().update(width, height, true); already does what you do in the next four lines.
viewport.update(width, height) set the new width and height to the camera and updates the camera. So:
viewport.update(width, height)
is equal to
camera.viewportWidth = width;
camera.viewportHeight = height;
camera.update();
The only difference is that the viewport looks to the aspect ratio depending on which Viewport you use.
And the third parameter: stage.getViewport().update(width, height, true); is a boolean value which says should the camera be centered. So if the third parameter is true the viewport will do:
camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0);
In your resize method you override the work of the viewport because with viewport.update(width, height, true) you already have done all of the resize and you don't need the four other lines.
In the resize method this is enough:
public void resize(int width, int height) {
stage.getViewport().update(width, height, true);
}
You can read this for a better understanding of Viewport: How Camera works in Libgdx and together with Viewport
The Viewport object is a camera frustum manager. When you start modifying the camera's parameters (other than position), you are undoing what you did by calling viewport.update().
If you want the table to automatically fill the screen so stuff in the top right corner stays there after a resize, don't set a width and height on the table. Instead, call table.setFillParent(true) so it will automatically update its size to match the viewport size.
I am developing a game which has 480x800 VIRTUAL screen sizes. Also I render my map using TiledMapRenderer. My problem is fitting background and HUD elements into the screen which has different ratio than 480/800 (Mostly taller devices). Some devices show blank area at the bottom of screen.
//my viewport (WIDTH = 480, HEIGHT = 800)
viewport = new ScalingViewport(Scaling.fillX,MyGdxGame.WIDTH,MyGdxGame.HEIGHT,camera);
#Override
public void resize(int width, int height) {
viewport.update(width, height, true);
camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0);
barriers.getRenderer().setProjectionMatrix(viewport.getCamera().combined);
shapeRenderer.setProjectionMatrix(viewport.getCamera().combined);
}
My screen should fit the X, but background image should fit X and Y without changing the aspect ratio. In this case ScalingViewport does not solve my problem, and if I change the viewport, I have to code everything from beginning.
#Override
public void render(float delta) {
update(delta);
SpriteBatch sb = game.batch;
Gdx.gl.glClearColor(46f/255,46f/255,46f/255,1f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
sb.setProjectionMatrix(camera.combined);
sb.begin();
//this has to change in somehow
sb.draw(AssetManager.backgroundMenu,0,0,MyGdxGame.WIDTH,MyGdxGame.HEIGHT);
sb.end();
}
Should I use multiple viewport? Or is there any way to fit my background into the screen? By the way I do not want to change my camera if there is a way.
Yes, you should use second viewport for your HUD elements.
I would recommend using ExtendViewport
ExtendViewport viewport = new ExtendViewport(MyGdxGame.WIDTH,MyGdxGame.HEIGHT);
It fills all screen (without black bars) and keeps aspect ratio for all resolutions.
I have trouble with my app. On iPhone (tested on 5c, 5s, 6) i have two black stripes on both sides (on android all looks well).
How i can dispose of them?
This is my code for drawing
#Override
public void create () {
mWidth = Gdx.graphics.getWidth();
mHeight = Gdx.graphics.getHeight();
mScale = Math.max(mWidth, mHeight) / 20f;
backgroundTexture = new Texture(Gdx.files.internal("backBlue.png"));
ShaderProgram.pedantic = false;
backgroundShader = new ShaderProgram(VERT, FRAG);
if (!backgroundShader.isCompiled()) {
System.err.println(backgroundShader.getLog());
System.exit(0);
}
if (backgroundShader.getLog().length()!=0)
System.out.println(backgroundShader.getLog());
backgroundBatch = new SpriteBatch(5, backgroundShader);
}
#Override
public void render () {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
backgroundBatch.begin();
backgroundBatch.draw(backgroundTexture, 0, 0, mWidth, mHeight);
backgroundBatch.end();
...
}
#Override
public void resize (int width, int height) {
mWidth = width;
mHeight = height;
mScale = Math.max(width, height) / 20f;
backgroundShader.begin();
backgroundShader.setUniformf("resolution", width, height);
backgroundShader.end();
}
This is probably happening because the viewport you are using, which is usually set in the constructor or the create method.
What is a viewport?
Basically, a viewport is an object that is created to define the policy of how the game will be drawn on different screens. E.g., say your camera is set to be 480 x 800 (pixels). What happens if your game is running on a screen that has a different ratio, for instance, the iPhone 5 that has a screen of 1,136 × 640 pixels? Should the game be stretched? Should the image be cropped? Or should libgdx add black bars on either side to fit the camera size you are using? This decision is made by the viewport your camera is using.
This is it in a nutshell. It is highly recommended to read more on this on the wiki.
Additionally, a good tutorial on this subject can be found here.