[LIBGDX] Animation problem

I have a class that takes a TextureRegion+number of columns and number of rows of this region and builds an Animation object. The problem is with the second consructor. I’m trying to build an animation of a walking-spider with one row and ten columns.

public class AnimationRegions extends Actor {

	private Animation walkAnimation;
	private TextureRegion walkSheet;
	private TextureRegion[] walkFrames;
	private Sprite currentFrame;
	private float elapsedTime = 0f;

	public AnimationRegions(TextureRegion sheet, int Columns, int Rows,
			float duration) {
		walkSheet = sheet;
		TextureRegion[][] tmp = walkSheet.split(walkSheet.getRegionWidth()
				/ Columns, walkSheet.getRegionHeight() / Rows);
		walkFrames = new TextureRegion[Columns * Rows];
		int index = 0;
		for (int i = 0; i < Rows; i++) {
			for (int j = 0; j < Columns; j++) {
				walkFrames[index++] = tmp[i][j];
			}
		}
		walkAnimation = new Animation(duration, walkFrames);
		walkAnimation.setPlayMode(PlayMode.LOOP_REVERSED);
		currentFrame = new Sprite();
	}
	
	public AnimationRegions(TextureRegion sheet, int Columns, int Rows,
			float duration, int startingRow) {
		walkSheet = sheet;
		TextureRegion[][] tmp = walkSheet.split(walkSheet.getRegionWidth()
				/ Columns, walkSheet.getRegionHeight() / Rows);
		walkFrames = new TextureRegion[Columns * Rows];
		int index = 0;
		for (int i = startingRow; i < Rows; i++) {
			for (int j = 0; j < Columns; j++) {
				walkFrames[index++] = tmp[i][j];
			}
		}
		walkAnimation = new Animation(duration, walkFrames);
		walkAnimation.setPlayMode(PlayMode.LOOP_REVERSED);
		currentFrame = new Sprite();
	}

	@Override
	public void act(float delta) {
		super.act(delta);
		elapsedTime += delta;
		currentFrame.setRegion(walkAnimation.getKeyFrame(elapsedTime, true));
		currentFrame.setSize(getWidth(), getHeight());
		currentFrame.setPosition(getX(), getY());
		currentFrame.setOrigin(currentFrame.getX() - currentFrame.getWidth()
				/ 2, currentFrame.getY() - currentFrame.getHeight() / 2);
	}

	@Override
	public void draw(Batch batch, float parentAlpha) {
		super.draw(batch, parentAlpha);
		currentFrame.draw(batch);
	}

	@Override
	public void setScale(float scaleXY) {
		super.setScale(scaleXY);
		currentFrame.setScale(scaleXY);
	}

	@Override
	public float getScaleX() {
		return currentFrame.getScaleX();
	}

	@Override
	public float getScaleY() {
		return currentFrame.getScaleY();
	}

	public Sprite getCurrentFrame() {
		return currentFrame;
	}

	public boolean isAnimationFinished() {
		return walkAnimation.isAnimationFinished(elapsedTime);
	}

	public TextureRegion getRegion() {
		return currentFrame;
	}

	public void resetTime() {
		elapsedTime = 0;
	}

The problem is with this line:

currentFrame.setRegion(walkAnimation.getKeyFrame(elapsedTime, true));

I ran some tests and apperantely this call:

walkAnimation.getKeyFrame(elapsedTime, true)

returns null. I have no idea why.

Here is the full error:

Exception in thread "LWJGL Application" java.lang.NullPointerException
	at com.badlogic.gdx.graphics.g2d.TextureRegion.setRegion(TextureRegion.java:112)
	at engine.utils.graphics.AnimationRegions.act(AnimationRegions.java:103)
	at com.badlogic.gdx.scenes.scene2d.Group.act(Group.java:50)
	at com.badlogic.gdx.scenes.scene2d.Group.act(Group.java:50)
	at com.badlogic.gdx.scenes.scene2d.Group.act(Group.java:50)
	at com.badlogic.gdx.scenes.scene2d.Stage.act(Stage.java:223)
	at views.screens.GameScreen.render(GameScreen.java:74)
	at com.badlogic.gdx.Game.render(Game.java:46)
	at engine.MonkeyRun.render(MonkeyRun.java:68)
	at com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:207)
	at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:114)

Try [icode]System.out.println(Arrays.toString(walkFrames));[/icode] in your constructor, see if one (or more) of the frames is null.

EDIT: btw, you could clean up this code a bit by changing the first constructor to just this:


public AnimationRegions(TextureRegion sheet, int Columns, int Rows,
         float duration) {
     this(sheet, Columns, Rows, duration, 0);
}

Then you only have one constructor to maintain.

Apperantely all the frames are null… What’s going on?

I have got to ask. Why do you need to specify a starting row?

That seems to be the only difference between the 2 constructors. If you are making your sprite sheets correctly they should be going from top left to bottom right.

If you have a sprite sheet that say has a blank area, just have your constructor take an array of ints that represent those blank frames and remove them before creating the final animation.


public class AnimationBuilder {

	public static <T> Animation create(AssetDescriptor<T> asset,
			float duration, int cols, int rows, int[] blankFrames) {
		/* The spritesheet to work with */
		Texture sheet = (Texture) Assets.get(asset);
		/* Each key frame */
		Array<Sprite> frames = new Array<Sprite>();

		/* Array of regions to be split into frames */
		TextureRegion[][] region = TextureRegion.split(sheet, sheet.getWidth()
				/ cols, sheet.getHeight() / rows);

		/* Create frames from the split texture */
		for (int j = 0; j < rows; j++) {
			for (int i = 0; i < cols; i++) {
				frames.add(new Sprite(region[j][i]));

			}
		}

		/* Remove blank frames */
		if (blankFrames != null)
			for (int blank = 0; blank < blankFrames.length; blank++) {
				frames.removeIndex(blankFrames[blank]);
			}

		return new Animation(duration, frames);
	}

}

I use an Array when creating my animation, it is much easier to remove things (since it auto resizes).

I specify a starting row because I have a sprite sheet the contains all the animations of the walking spider(up, left, down, right) and I only want the down frames.

In that case then why not just split the texture region up into 4 and then pass that into the method, since it already accepts a TextureRegion. At the moment you are passing in the entire sheet, specifying the row yet still using the entire regions width/height (all 4 animations) to create your animation.

This is not the root of your problem but is one of them.

For simplicity, if you are still getting the hang of this, make it easier for yourself and just split the texture up in a editor and import all 4 of them separately and go from there.