I've been struggling with how to use and set up Viewports in LibGDX for quite some time. I want to be able to render everything like its on a display that is 1920x1080 and have it scale to fit the display its on, and I need some help getting to work like that.
This is what I want it to look like (taken from a computer with a 1920x1080 monitor), but when I run the same code on my laptop which is 1440x800, it looks like this. I apologize for the poor photo of a screen, I couldn't get it to take a screenshot of the game running for whatever reason, but it shows that the top of the display remains unused, and that not everything is being fit to the display. This is the main code running the show:
public class Main extends Game {
...
public void create() {
...
Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode());
//last
this.setScreen(new MainMenu(this));
}
public void render() {
super.render(); //important!
}
...
}
And then the MainMenu class
public class MainMenu implements Screen{
...
public MainMenu(final Main game) {
this.game = game;
cam = new OrthographicCamera();
cam.setToOrtho(false, 1920, 1080);
...
}
#Override
public void render(float delta) {
Gdx.gl.glClearColor(0.025f, .025f, 0.025f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
cam.update();
game.batch.setProjectionMatrix(cam.combined);
...
if(Gdx.input.isKeyPressed(Keys.ESCAPE)) {
Gdx.app.exit();
}
}
...
}
How would I implement a Viewport or something of the like to get it to look the same on the smaller screen as it does on the larger? Any help is really appreciated! If you want to see the code that I left out for brevity, its all on my GitHub. Thanks again!
That didn't take me long, hopefully someone will learn from me though, ha ha.
Turns out when you use the camera with fixed height and width like that, it does fill up the whole monitor, but the cameras width DOES NOT equal the value returned by Gdx.graphics.getWidth(). Because of this all my code was rendering like it was being compressed because it was referencing the width returned by Gdx.graphics, and not the camera.viewportWidth.
Lesson learned: Gdx.graphics.getWidth() can and will change depending on device and cam.veiwportWidth wont. Oops!
I'm trying to make a simple animated menu with images bouncing off around the screen but the images leave a trail where ever the move.
public void handle(long now) {
// TODO Auto-generated method stub
boolean intersectFlag = false;
for(Letter l : letters){
gameMenuGraphicsContext.drawImage(l.letterImage, l.letterRectangle.getX(), l.letterRectangle.getY());
l.moveSimple();
}
}};
Any idea on how to stop this happening?
Think of the Canvas as a piece of paper onto which you are writing. If you don't erase anything explicitly everything will be visible what you have ever drawn to it. Actually you should reconsider your decision to use a Canvas at all. It is not very well suited for such kind of animations.
As mipa stated, your problem is that the drawn image is never erased. To erase your canvas, use:
graphicsContext.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
To keep yourself from having to call the clearRectmethod each time you want to draw on the screen, one easy write-and-forget way is to combine the clearing and drawing into one method. You can even use a lambda expression to draw on the canvas, as in the following code:
private static void clearAndDraw(GraphicsContext gc, Consumer<GraphicsContext> draw) {
gc.clearRect(0, 0, gc.getCanvas().getWidth(), gc.getCanvas().getHeight());
draw.accept(gc);
}
public void handle(long now) {
//...
for(Letter l : letters) {
clearAndDraw(graphicsContext, gc -> gc.drawImage(l.letterImage, l.letterRectangle.getX(), l.letterRectangle.getY()));
//...
}
}
So, I have created a texture, and then a sprite.
On my render() method, I am check for user input. If the user has touched/clicked, then I want my sprite to rotate 90 degrees ONCE.
Right now the rotation works. However, it rotates multiple times per click!
How can I make it rotate only once per touch? I have a feeling that I might have to use delta time, and that occurs because the render method is being called frequently, but I don't know how to fix it... Thanks!
public class MyGame extends ApplicationAdapter {
SpriteBatch batch;
Texture img;
Sprite sprite;
#Override
public void create () {
batch = new SpriteBatch();
img = new Texture("badlogic.jpg");
sprite = new Sprite(img);
}
#Override
public void render () {
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
sprite.draw(batch);
batch.end();
if (Gdx.input.isTouched()) {
rotateRight();
}
}
private void rotateRight() {
sprite.setRotation(sprite.getRotation() - 90);
}
}
Right now you are polling input inside of your render method. Polling simply checks the status of the input (is it touched or not) and does not care for any actual "event" occurred.
Instead of this you need to look for input events via event handling as this will give you access to the actual event of the screen being touched or untouched. You can do this by implementing InputProcessor which will give you access to override a bunch of touch event methods so you can execute your rotate method only on the event of the touch.
Example:
public void update()
{
Gdx.input.setInputProcessor(new InputProcessor() {
#Override
public boolean TouchDown(int x, int y, int pointer, int button) {
if (button == Input.Buttons.LEFT) {
rotateRight();
return true;
}
return false
}
});
}
Don't forget to call your update() method from your render method. I put it in a separate function just for readability and not to clog up your rendering code.
You can also have your class implement InputProcessor and override the methods in your class so you do not have to do it inline as I did above.
if (Gdx.input.justTouched() && Gdx.input.isTouched()) {
rotateRight();
}
I am trying to construct a layout like this picture:
Should I use two stages?, two cameras?, one stage with splitPane?
I want the top screen to be half of the entire devices height. The top screen will have animated actors and a single background image with some labels close to the top of the screen.
The lower screen should be a Scrollpane with information and at the bottom ,the menu and back buttons.
How do I secure the ratio between them if the screen resizes?
Edit 2014-03-28:
Hi!
I tried using two stages as proposed and I am pretty happy. Thought I share my first try at it. The result was this code:
package se.appltini.mygdxtest;
//imports omitted
public class DualStageTutorial extends ScreenAdapter {
private Stage upperStage;
private Stage bottomStage;
private Skin skin,menuSkin;
#Override
public void show() {
upperStage = new Stage();
bottomStage = new Stage();
skin = new Skin(Gdx.files.internal("uiskin/uiskin.json"));
menuSkin = new Skin(Gdx.files.internal("menuSkin/uiSkin.json"),new TextureAtlas(Gdx.files.internal("menuSkin/uiskin.atlas")));
Image upperImage = new Image(menuSkin.getPatch("menuTexture"));
upperImage.setFillParent(true);
upperStage.addActor(upperImage);
Image bottomImage = new Image(menuSkin.getPatch("menuTexture"));
bottomImage.setFillParent(true);
//setting alpha to 0.5f so we can see the different stages
bottomImage.setColor(1f, 1f, 1f, 0.5f);
bottomStage.addActor(bottomImage);
}
#Override
public void resize(int width, int height) {
}
#Override
public void render(float delta) {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
upperStage.act(delta);
bottomStage.act(delta);
/*Upper Half*/
//set the openGl viewport to half the screenheight and starting y from the middle of the screen
Gdx.gl.glViewport(0,Gdx.graphics.getHeight()/2,Gdx.graphics.getWidth(),Gdx.graphics.getHeight()/2);
upperStage.draw();
/*bottom Half*/
//set the openGl viewport to half the screenheight and starting y from the bottom of the screen
Gdx.gl.glViewport(0,0,Gdx.graphics.getWidth(),Gdx.graphics.getHeight()/2);
bottomStage.draw();
}
#Override
public void hide() {
dispose();
}
#Override
public void dispose() {
upperStage.dispose();
bottomStage.dispose();
skin.dispose();
menuSkin.dispose();
}
}
The best practice here would be to use two different stages.
Using two stages will simplify design in sense that the viewport management for 2d animation will simplify by a considerable amount. Also the event handling has to be completely different style. Your information panel won't have much relative layout changes in actors as compared to 2d animation.
Using same stage would make sense only if both contents overlap each other in some form. I'm sure this is not the case here. Your info panel will always be ABOVE animation (most probably).
Hope this helps.
I have been looking for 10 hours (literally) and I'm done, I need to ask. Thing is I'm learning How use LibGdx to program Java games. I'm doing a Horizontal Space Ship Game. So, my worst problem here is that I do not know how do scroll (I think draw will explain better). I want to draw a huge background (Space) and make my OrthographicCamera move right like with my SpaceShip, so it will create a Scroll effect with the Space Background. No enemies and nothing but the ship on the screen.
I'm trying this:
public void moveCamera(float x,float y){
cam.position.set(x, y, 0);
}
Then I use that method in my WorldRender render() method:
public void render(float delta){
ship.Move(delta);
moveCamera(ship.getPosition().x,ship.getPosition().y);
cam.update();
System.out.println(""+cam.position);
spriteBatch.begin();
drawBackground();
drawShip();
spriteBatch.end();
}
I actually move the camera position (I can see that thanks to the println), but It isn't moving in the game, so SpaceShip just disappears by the edge of the window.
I also tried this before spriteBatch.end()
spriteBatch.setProjectionMatrix(camera.combined);
but when I do that windows only shows a black screen, no ship, no nothing.
As I said, I'm desperate, I see lot of examples (scroll with mouse, paralexscrolling etc) but all are to advanced or just nothing to do with my code.
This is how I draw stuff. Background and ship are textures inside WorldRender. I draw background image very wide, so my intention is do some scrolling over as I said. That's the code
private void loadTextures(){
shipTexture=new Texture(Gdx.files.internal("nave.png"));
background=new Texture(Gdx.files.internal("fondo.jpg"));
}
public void drawShip(){
spriteBatch.draw(shipTexture,ship.getPosition().x*ppuX,ship.getPosition().y*ppuY, ship.WIDTH*ppuX,ship.HEIGHT*ppuY);
}
public void drawBackground(){
spriteBatch.draw(background, -10*ppuX,0*ppuY, Gdx.graphics.getWidth()*10,Gdx.graphics.getHeight());
}
Here you can download the code if someone want to help in hardcore mode
My code (not working)
I FINALLY SOLVED IT!
That's the code I used in a class name WorldRenderer, which have methods that are called within GameScreen for render, resize etc
public WorldRenderer(World world) {
// TODO Auto-generated constructor stub
this.world=world;
this.ship=world.getShip();
this.cam = new OrthographicCamera(CAMERA_WIDTH,CAMERA_HEIGHT);
this.cam.setToOrtho(false,CAMERA_WIDTH,CAMERA_HEIGHT);
this.cam.position.set(ship.getPosition().x,CAMERA_HEIGHT/2,0);
this.cam.update();//actualizamos la camara
spriteBatch=new SpriteBatch();
loadTextures();
}
private void loadTextures(){
shipTexture=new Texture(Gdx.files.internal("nave.png"));
background=new Texture(Gdx.files.internal("fondo.jpg"));
}
public void drawShip(){
spriteBatch.draw(shipTexture,ship.getPosition().x,ship.getPosition().y,10,10);
}
public void drawBackground(){
spriteBatch.draw(background, 0,0, 500,50);
}
public void render(float delta){
ship.Move(delta);
moveCamera(ship.getPosition().x);
spriteBatch.setProjectionMatrix(cam.combined);
spriteBatch.begin();
drawBackground();
drawShip();
spriteBatch.end();
}
public void moveCemara(float x){
cam.position.set(x+20,cam.position.y, 0);
cam.update();
}
Inside the Ship I have this method which I call within render in WorldRenderer to move It
public void Move(float delta){
if(Gdx.input.isKeyPressed(Keys.LEFT)) this.position.x -=velocity *delta;
if(Gdx.input.isKeyPressed(Keys.RIGHT)) this.position.x +=velocity *delta;
if(Gdx.input.isKeyPressed(Keys.UP)) this.position.y +=velocity *delta;
if(Gdx.input.isKeyPressed(Keys.DOWN)) this.position.y -=velocity *delta;
}
Also I want to thanks very much to the people who helped me. I'm marking first answer as the good one, but, mix both was what gave me the real solution.
I leave here some tutorials I followed which are pretty good for noobs
That's a good everything-from-scratching-tutorial
LiGdxForNoobs
A simple platform game
platformGame
A very simple game
bucketGame
I can't tell if this is your only mistake, but this is ONE mistake. If this is what you say you were doing:
spriteBatch.begin();
drawBackground();
drawShip();
spriteBatch.setProjectionMatrix(camera.combined);
spriteBatch.end();
You wont see anything. When setProjectionMatrix is called inside a begin()/end() block. the current batch is flushed to the gpu. So, you are actually not drawing anything with the camera matrix. You should do this instead:
spriteBatch.setProjectionMatrix(camera.combined);
spriteBatch.begin();
drawBackground();
drawShip();
spriteBatch.end();
EDIT:
If you don't call that line, spriteBatch uses its own default camera (which wont notice your camera.update() modifications, so that's not what you want).
You should now pay more attention to the coordinates you are using. I'm not quite sure you really need the ppu conversion thing. To begin with, define everything in imaginary world coordinates, note that you'll see some stretching in your world.
public void drawShip(){
spriteBatch.draw(shipTexture,ship.getPosition().x,ship.getPosition().y, 10, 10);
}//your ship is 10 units wide and tall!
public void drawBackground(){
spriteBatch.draw(background, -10,0, 500, 100);
} //your background is 500 units wide, and 100 units tall
//camera setup
camera = new OrthographicCamera(50, 50);
//your camera will print to screen 50 units of your world
If you get to see a stretched world, try to understand how it's working (if you can't see anything, there is something wrong somewhere).
EDIT 2
I took a look at your code. First remove ppu's, as it obscures your code. You were setting your cam position to the ship.postion, while drawing at ship.position * ppu. Also your background was way too big (that's why you see it pixelated). You should see something reasonable with this. (someday you'll have to initialize your camera in another way to deal with stretching, but forget it until you understand how all works).
this.cam = new OrthographicCamera(CAMERA_WIDTH,CAMERA_HEIGHT);
public void drawShip(){
spriteBatch.draw(shipTexture, ship.getPosition().x ,ship.getPosition().y, 10, 10);
}
public void drawBackground(){
spriteBatch.draw(background, -CAMERA_WIDTH/2, -CAMERA_HEIGHT/2, 100, 100); //let bg begin where camera starts. (0,0)
}
public void render(float delta){
ship.Move(delta);
moverCamara(ship.getPosition().x, ship.getPosition().y);
spriteBatch.setProjectionMatrix(cam.combined);
spriteBatch.begin();
drawBackground();
drawShip();
spriteBatch.end();
}
Its not clear how your drawing? I'm not sure if your doing this approach correctly.. Can you provide details of your background and ship? Can you provide details on you background image, is it a huge image that your scrolling around or is it a repeated image you want to repeat as you scroll?
--EDIT--
ok i think i have an idea what might be up. I would normally apply the camera to the current context.
Place the following in your resize
public void resize(int width, int height) {
cam = new OrthographicCamera(width, height);
cam.translate(width / 2, height / 2, 0);
}
Place the following in the start of your render()
cam.position.set(posX,posY,0);
cam.update();
cam.apply(Gdx.gl10);
Gdx.gl.glClearColor(0,0,0,1);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // #14
This will make you have a clear screen with the origin set at the bottom left of the window. You should then draw your background first
spriteBatch.setProjectionMatrix(cam.combined);
spriteBatch.begin();
spriteBatch.draw(background,0,0,sizeX,sizeY);
spriteBatch.end()
see how that looks as you move your camera position posX and posY. Then add your ship to the mix
-- MORE EDITS ---
you can then calculate the posX and posY as
posX = defaultOffsetX+shipX
and so on..
Anyhow hope this helps
I'm still only learning myself so this might not be the best method.. but it seems to work.
I've edited your code. Have a look at the following:
public class WorldRenderer {
private World world;
private Ship ship;
private Texture shipTexture,background;
private SpriteBatch spriteBatch;
private OrthographicCamera cam;
float screenSizeX = 100;
float screenSizeY = 100;
float shipSizeX = 10;
float shipSizeY = 10;
public void setSize (int w, int h) {
cam = new OrthographicCamera(screenSizeX,screenSizeY);
}
public WorldRenderer(World world) {
this.world=world;
this.ship=world.getShip();
spriteBatch=new SpriteBatch();
loadTextures();
}
private void loadTextures(){
shipTexture=new Texture(Gdx.files.internal("nave.png"));
background=new Texture(Gdx.files.internal("fondo2.jpg"));
}
public void drawShip(){
spriteBatch.draw(shipTexture, ship.getPosition().x,ship.getPosition().y, shipSizeX,shipSizeY);
}
public void drawBackground(){
spriteBatch.draw(background, 0,0);
}
public void render(float delta){
ship.Move(delta);
moverCamara(ship.getPosition().x,ship.getPosition().y);
spriteBatch.setProjectionMatrix(cam.combined);
spriteBatch.begin();
drawBackground();
drawShip();
spriteBatch.end();
}
public void moverCamara(float x,float y){
cam.position.set(x, y, 0);
cam.update();
}
}
This way, your ship is always in the middle of the screen and the background moves. Hope this helps.