Search code examples
javanullpointerexceptionlibgdx

Why i get an NPE after changing Screen and Drawing a Sprite in Libgdx


this is my first question to ask here so i am sry beforehand that i do something wrong here.

I am currently trying to develop my first own game in libgdx. So for the start i have 4 classes.

  1. FlipX a class that extends from Game.
  2. StartMenu a class that implements from Screen.
  3. GameScreen a class that implements from Screen.
  4. Button a class that extens from Sprite.

I hope i can show you some code here, but let me first explain.
When starting the code the Class FlipX, where i have my SpriteBatch to draw, set the Screen to the StartMenu Class. There i can click on 2 Buttons which are drawn to the SpriteBatch from FlipX.
These Buttons came from the Button Class which i created to store multiple Textures to one so called Button. This Button Class extends from Sprite.
The PlayBtn will set the Screen to the GameScreen class when i click on it.
The Problem then occurs when the GameScreen Class draws another Button on the same batch after the Screen is set to GameScreen.
It is the Line from GameScreen in the drawBatch() function where i draw jumpBtn.draw(game.batch). When i do this i get the following error.

Exception in thread "LWJGL Application" java.lang.NullPointerException
    at com.badlogic.gdx.graphics.g2d.SpriteBatch.switchTexture(SpriteBatch.java:1067)
    at com.badlogic.gdx.graphics.g2d.SpriteBatch.draw(SpriteBatch.java:558)
    at com.badlogic.gdx.graphics.g2d.Sprite.draw(Sprite.java:580)
    at de.geecogames.flipx.Screens.GameScreen.drawBatch(GameScreen.java:88)
    at de.geecogames.flipx.Screens.GameScreen.render(GameScreen.java:63)
    at com.badlogic.gdx.Game.render(Game.java:46)
    at de.geecogames.flipx.FlipX.render(FlipX.java:26)
    at com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:232)
    at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:127)

This Error does not occur when i replace the Button with a simple Sprite, which is odd because the Button Class is just a class that extends Sprite. private Sprite jumpBtn = new Sprite(new Texture("game/UpDef.png"));

public class FlipX extends Game {
    public SpriteBatch batch;

//Filemanager to load Assets from an Asset Manager
    public FileManager manager;

    StartMenu startMenu;
    
    @Override
    public void create () {
        manager= new FileManager();
        startMenu=new StartMenu(this);
        batch = new SpriteBatch();
        setScreen(startMenu);
    }

    @Override
    public void render () {
        super.render();
    }
    
    @Override
    public void dispose () {
        batch.dispose();
    }

    public FileManager getFileManager(){
        return manager;
    }
}
public class StartMenu implements Screen {
private final FlipX game;
    private final FileManager fileM;

    private final OrthographicCamera camera;

    private final FitViewport viewport;

    private Sprite img = new Sprite(new Texture("badlogic.jpg"));

    private final Button playBtn, exitBtn;

    private final Sprite backGround;

    private boolean playPressed, exitPressed;
    public StartMenu(FlipX game) {
        this.game = game;

        //Get Manager and load Assets
        this.fileM = game.getFileManager();
        fileM.loadAssetsMenu();

        //Testlabel
        Label testlabel = new Label(String.format("test"), new Label.LabelStyle(new BitmapFont(), Color.BLACK));

        //Get the Assets from Manager
        playBtn = new Button(fileM.getTexture(fileM.playBtnDefault), fileM.getTexture(fileM.playBtnHover), fileM.getTexture(fileM.playBtnClick));

        exitBtn = new Button(fileM.getTexture(fileM.exitBtnDefault), fileM.getTexture(fileM.exitBtnHover), fileM.getTexture(fileM.exitBtnClick));

        backGround = fileM.getSprite(fileM.backgroundMenu);

        camera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
        viewport = new FitViewport(Z.V_WIDTH, Z.V_HEIGHT, camera);

        camera.position.set(viewport.getWorldWidth() / 2, viewport.getWorldHeight() / 2, 0);

        //Btn Position
        playBtn.setPosition(Z.V_WIDTH / 2 - playBtn.getWidth() / 2, Z.V_HEIGHT / 2 - playBtn.getHeight() / 2);
        exitBtn.setPosition(Z.V_WIDTH / 2 - exitBtn.getWidth() / 2, Z.V_HEIGHT / 2 - exitBtn.getHeight() / 2 - playBtn.getHeight() - playBtn.getHeight() / 2);

        img.setPosition(0, 0);
    }
    @Override
    public void render(float delta) {
        update(delta);

        clearColor();

        drawBatch();
    }

    private void update(float dt) {
        handleInput();

        cameraUpdates();
    }

    private void clearColor() {
        Gdx.gl.glClearColor(0, 0.5f, 0.9f, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    }

    private void drawBatch() {
        game.batch.setProjectionMatrix(camera.combined);
        game.batch.begin();
        backGround.draw(game.batch);
        img.draw(game.batch);

        //Buttons
        playBtn.draw(game.batch);
        exitBtn.draw(game.batch);

        game.batch.end();
    }

    private void handleInput() {
        Vector3 realCoords = viewport.unproject(new Vector3(Gdx.input.getX(), Gdx.input.getY(), 0));

        if (Gdx.input.isKeyPressed(Input.Keys.K)) {
            float addX = 2;
            float addY = 0;
            img.setPosition(img.getX() + addX, img.getY() + addY);
        }

        if (Gdx.input.isKeyPressed(Input.Keys.UP)) {
            camera.position.y = camera.position.y + 1;
        } else if (Gdx.input.isKeyPressed(Input.Keys.DOWN)) {
            camera.position.y = camera.position.y - 1;
        }

        if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
            camera.position.x = camera.position.x - 1;
        } else if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
            camera.position.x = camera.position.x + 1;
        }

        if (Gdx.input.isKeyPressed(Input.Keys.PAGE_UP)) {
            camera.zoom = camera.zoom + 0.1f;
        } else if (Gdx.input.isKeyPressed(Input.Keys.PAGE_DOWN)) {
            camera.zoom = camera.zoom - 0.1f;
        }

        //btn test
        if (BasicFunctions.isInside(realCoords.x, realCoords.y, playBtn)) {
            if (Gdx.input.justTouched()) {
                playBtn.setClick();
                playPressed = true;
                fileM.playSound(fileM.playBtnSound);
            } else {
                if (playPressed) {
                    game.setScreen(new GameScreen(game));
                }
                playBtn.setHover();
            }
        } else
            playBtn.setNormal();


        if (BasicFunctions.isInside(realCoords.x, realCoords.y, exitBtn)) {
            if (Gdx.input.isTouched()) {
                exitBtn.setClick();
                exitPressed = true;
            } else {
                if (exitPressed)
                    Gdx.app.exit();
                exitBtn.setHover();
            }
        } else
            exitBtn.setNormal();
    }

    private void cameraUpdates() {
        camera.update();
    }
}
public class GameScreen implements Screen {

private final FlipX game;

    private final FileManager fileM;

    private final OrthographicCamera camera;

    private final FitViewport viewport;

    private final Button jumpBtn;

    private final Sprite background;

    public GameScreen(FlipX game){
        this.game=game;
        this.fileM=game.getFileManager();

        fileM.loadAssetsGame();

        camera=new OrthographicCamera(Gdx.graphics.getWidth(),Gdx.graphics.getHeight());
        viewport = new FitViewport(Z.V_WIDTH,Z.V_HEIGHT,camera);



        jumpBtn = new Button(new Texture("game/UpDef.png"),new Texture("game/UpHov.png"),new Texture("game/UpClick.png"));
        jumpBtn.setPosition(2,2);

        background = fileM.getSprite(fileM.backgroundMenu);
    }

    @Override
    public void show() {

    }

    @Override
    public void render(float delta) {
        update(delta);

        clearColor();

        drawBatch();
    }

    private void update(float dt){
        handleInput(dt);
    }

    private void clearColor() {
        Gdx.gl.glClearColor(1, 0.5f, 0.9f, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    }

    private void drawBatch(){
        game.batch.setProjectionMatrix(camera.combined);
        game.batch.begin();

        background.draw(game.batch);

        //Bug here?
        jumpBtn.draw(game.batch);

        game.batch.end();
    }

    private void handleInput(float dt){
        Vector3 realCoords = viewport.unproject(new Vector3(Gdx.input.getX(), Gdx.input.getY(), 0));

        if(Gdx.input.isTouched()){
            if(BasicFunctions.isInside(realCoords.x,realCoords.y,jumpBtn)){
                EzLog.logging("X " + jumpBtn.getX() + " Y " + jumpBtn.getY());
            }
        }
    }
public class Button extends Sprite {
    private final Texture normal, hover, click;

    private float stateTimer;

    public Button(Texture normal, Texture hover, Texture click){
        this.normal=normal;
        this.hover=hover;
        this.click=click;

        stateTimer=0;

        setSize();
    }

    private void setSize(){
        float height,width;

        width=normal.getWidth();
        height=normal.getHeight();

        setBounds(0,0,width*3,height*3);
    }

    public void setNormal(){
        setRegion(normal);
    }
    public void setHover(){
        setRegion(hover);
    }
    public void setClick(){
        setRegion(click);
    }
}

Solution

  • After hours and hours i found the solution.

    The Problem is that my Class Button can save up to 3 textures. To draw one of the textures, we can use the draw function of it because Button extends from Sprite. To show it on the SpriteBatch it just needs to set the texture to the region.

    setRegion("TextureHere.png")
    

    So everytime i draw it with my playBtn.draw(game.batch) it will show this texture at the position i gave him before with setPosition(x,y)

    But i never actually do "setRegion()" in the Constructor, so it will try to draw something which isn't there. And this is the thing why it says NullPointerException. I just got confused of the error because of the functions switchTexture from SpriteBatch.
    In the Class StartMenu it coincidentally just calls the "setRegion()" because it is always called in the else part from the condition in "handleInput" with the function "setNormal()" from the Button Class.

    Solution i know has is the following:

    I added the setNormal() function in the Constructor so the first texture will always be set to region, if i can say it like this, and this works.

    public class Button extends Sprite {
        private final Texture normal, hover, click;
    
        public Button(Texture normal, Texture hover, Texture click){
            this.normal=normal;
            this.hover=hover;
            this.click=click;
    
            setSize();
            setNormal();
        }
    
        private void setSize(){
            float height,width;
    
            width=normal.getWidth();
            height=normal.getHeight();
    
            setBounds(0,0,width*3,height*3);
        }
    
        public void setNormal(){
            setRegion(normal);
        }
        public void setHover(){
            setRegion(hover);
        }
        public void setClick(){
            setRegion(click);
        }