PeteFL
PeteFL

Reputation: 33

LibGDX - NullPointerException when using ShapeRenderer

I'm trying to do some JUnit tests with a class on LibGDX but I'm getting a NullPointerError when trying to initialise said class, which traces back to using the ShapeRenderer.

Here is the class I have initialised:

import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.game.Point;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;

import java.util.ArrayList;

import static java.lang.Math.abs;

public class Player{
    int health;
    int range;
    public Point position;
    ShapeRenderer shape = new ShapeRenderer();
    public Sprite drawable;


    public Player(int health, int range, Point position,Texture img){
        this.health = health;
        this.range = range;
        this.position = position;
        this.drawable = new Sprite(img);
        drawable.setPosition(position.x - drawable.getWidth()/2,position.y - drawable.getHeight()/2);
    }

    public boolean inRange(Player target){
        if(abs(target.position.x - this.position.x) < this.range &&
                abs(target.position.y - this.position.y) < this.range){
            return true;
        }
        return false;
    }


    public void drawBox(ArrayList<Player> target, OrthographicCamera camera, Sprite sprite) {
        shape.setProjectionMatrix(camera.combined);
        shape.begin(ShapeType.Line);
        boolean redBox = false;
        for(Player player: target) {
            if (this.inRange(player)) {
                redBox = true;
            }
        }
        if (redBox) {
            shape.setColor(Color.RED);
        }
        else {
            shape.setColor(Color.GREEN);
        }
        shape.rect(position.x - range, position.y - range, range * 2, range * 2);
        shape.end();
    }

}

Here is the relevant code from PlayerTest:

 import com.entities.Player;
 import com.game.Point;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Assertions;

class PlayerTest {

@Test
    public void testInRangeBoundary() {
        Player e = new Player(0,5, new Point(5,5));
        Assertions.assertTrue(e.inRange(new Player(0,0,new Point(9,0))));
    }
}

And this is the error I get:

java.lang.NullPointerException
    at com.badlogic.gdx.graphics.glutils.ShaderProgram.loadShader(ShaderProgram.java:209)
    at com.badlogic.gdx.graphics.glutils.ShaderProgram.compileShaders(ShaderProgram.java:188)
    at com.badlogic.gdx.graphics.glutils.ShaderProgram.<init>(ShaderProgram.java:171)
    at com.badlogic.gdx.graphics.glutils.ImmediateModeRenderer20.createDefaultShader(ImmediateModeRenderer20.java:233)
    at com.badlogic.gdx.graphics.glutils.ImmediateModeRenderer20.<init>(ImmediateModeRenderer20.java:56)
    at com.badlogic.gdx.graphics.glutils.ShapeRenderer.<init>(ShapeRenderer.java:116)
    at com.badlogic.gdx.graphics.glutils.ShapeRenderer.<init>(ShapeRenderer.java:111)
    at com.badlogic.gdx.graphics.glutils.ShapeRenderer.<init>(ShapeRenderer.java:107)
    at com.entities.Player.<init>(Player.java:22)
    at PlayerTest.testInRangeBoundary(PlayerTest.java:10) <19 internal calls>
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) <9 internal calls>
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) <21 internal calls>

(for reference line 22 is the 'ShapeRenderer shape = new ShapeRenderer();' line)

I know that a NullPointerException usually means that something hasn't been initialised but I'm not sure what to do with this.

Any help would be much appreciated!

Upvotes: 1

Views: 343

Answers (1)

Nicolas
Nicolas

Reputation: 7081

Gdx.gl is null during testing, like all other Gdx fields. What is happening here is that you are creating a new ShapeRenderer object in the Entity constructor. ShapeRenderer then tries to access libGDX's rendering APIs but they aren't initialized, so you get an exception.

Since you're not really testing whether your entity is being drawn correctly, but whether the inRange method is working correctly, you want to prevent calling any of libGDX's API during testing.

There are many options to solve this. You could rearrange your code:

  • By separating game logic and rendering logic in two classes as @Code-Apprentice pointed out. You could have an Entity class and an EntityRenderer class. You only test the former.
  • Or by not instantiating a ShapeRenderer in your constructor. Instead, the ShapeRenderer could be passed as an argument to the drawBox method. If I remember correctly, ShapeRenderer are rather heavy objects so you might want to do that anyway if you have many entities.

Another possibility would be to mock libGDX's APIs during testing.

  • With Mockito, you can do Gdx.gl = mock(GL20.class).
  • Or you can create a HeadlessApplication which will correctly initialize the Gdx class.

Both these options are described here. However, prefer refactoring your code when possible, it's a good practice to keep game logic and rendering separated.

Upvotes: 1

Related Questions