Adding animation on character movement using Libgdx

reviously, we have talked about making a character. But our character was lifeless. It was like moving image left or right. Now it is time to add animation to our character bob so that it look more like a character who has life. Most importantly this time I am writing my own code. This time I am not explaining other’s code in my own word like my previous 2 libgdx blog.

First thing we got to do is, we have to make a sprite-sheet.

We have to store it in the asset directory of android.

Now to contemplate where actually we were rendering our character bob? We have done it in WorldRenderer.java. It has a render() method which is responsible for decoration of the environment.

public void render() {
spriteBatch.begin();
drawBlocks();
drawBob();
spriteBatch.end();

if (debug)
drawDebug();
}

we are more interested about the character animation today so we will be rewriting our drawBob() method today. But before that we must load our textures first in loadTextures() method which is being called in the constructor of this class. What actually we need to load here? We need to load our sprite image. Then we need to divide and split it into frames into a 1D array and then we have to make Animation object using this Array and we also need to define the time each frame will consume. Thats it.

	private static final int        FRAME_COLS = 6;      
    private static final int        FRAME_ROWS = 5;        
    Animation                       walkAnimation;          
    Texture                         walkSheet;              
    TextureRegion[]                 walkFrames;             
    TextureRegion                   currentFrame;           

    float stateTime;                                        

	private void loadTextures() {
		walkSheet = new  Texture(Gdx.files.internal("animation_sheet.png"));
		TextureRegion[][] tmp = TextureRegion.split(walkSheet, walkSheet.getWidth() / 
				FRAME_COLS, walkSheet.getHeight() / FRAME_ROWS);
		walkFrames = new TextureRegion[FRAME_COLS * FRAME_ROWS];
        int index = 0;
        for (int i = 0; i < FRAME_ROWS; i++) {
                for (int j = 0; j < FRAME_COLS; j++) {
                        walkFrames[index++] = tmp[i][j];
                }
        }
        walkAnimation = new Animation(0.025f, walkFrames);

        stateTime = 0f;

        bobTexture = new  Texture(Gdx.files.internal("images/bob.png"));
        blockTexture = new Texture(Gdx.files.internal("images/block.png"));
	}

As I was saying, we need to move to our main focused function drawBob(). To access the sprite frames we need to access to the frames via time elapsed. So we need to keep updating stateTimewith a delta otherwise the same frame will keep appearing. Gdx.graphics.getDeltaTime() returns the time passed since the last call to render() in seconds. So basically I was explaining this code.

private void drawBob() {
	Bob bob = world.getBob();
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        stateTime += Gdx.graphics.getDeltaTime();
        currentFrame = walkAnimation.getKeyFrame(stateTime, true);
        spriteBatch.draw(currentFrame, bob.position.x * ppuX, bob.position.y * ppuY, facex*Bob.SIZE * ppuX, Bob.SIZE * ppuY);
}

Bingo! Sprite works. BUT! This is odd because bob is animating its run state all the time even when he is not running, it need to be fixed, right? Actually we have been preparing for this moment from the first day. Remember we are keeping an enum to keep the current state of bob and using controller we are updating bob’s state. So here it will only need a if else condition to check what is the current state of bob. Is he running? Or another word, is user pressing arrow keys? If yes then show this animation:

if (bob.state==bob.state.WALKING){
		Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        stateTime += Gdx.graphics.getDeltaTime();
        currentFrame = walkAnimation.getKeyFrame(stateTime, true);
        spriteBatch.draw(currentFrame, bob.position.x * ppuX, bob.position.y * ppuY, Bob.SIZE * ppuX, Bob.SIZE * ppuY);

}

and if he is idle show that old static image.

else if(bob.state==bob.state.IDLE){
			spriteBatch.draw(bobTexture, bob.position.x * ppuX, bob.position.y * ppuY,Bob.SIZE * ppuX, Bob.SIZE * ppuY);
		}

We are good now, aren’t we? Yes we have improved a lot. But still when we move left the bob face in one direction thats what we want so no complain but when we move right bob keep his face to that old direction again! Thats stupid, is not it? So we need to flip the direction of our sprite and image. It is not hard at all. It requires trick. if add negative to the size of the image it will flip the image in X axis. So lets put this function together:

private void drawBob() {
		Bob bob = world.getBob();

		int facex=1;
		if(bob.facingLeft){
			facex=-1;
		}

		if (bob.state==bob.state.WALKING){
		Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        stateTime += Gdx.graphics.getDeltaTime();
        currentFrame = walkAnimation.getKeyFrame(stateTime, true);
        spriteBatch.draw(currentFrame, bob.position.x * ppuX, bob.position.y * ppuY, facex*Bob.SIZE * ppuX, Bob.SIZE * ppuY);

		}
		else if(bob.state==bob.state.IDLE){
			spriteBatch.draw(bobTexture, bob.position.x * ppuX, bob.position.y * ppuY, facex* Bob.SIZE * ppuX, Bob.SIZE * ppuY);
		}

			//spriteBatch.draw(bobTexture, bob.position.x * ppuX, bob.position.y * ppuY, Bob.SIZE * ppuX, Bob.SIZE * ppuY);

	}

We are basically done!

Now put the hole class together:

package com.me.mygdxgame;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.Rectangle;

public class WorldRenderer {

 private World world;
 private OrthographicCamera cam;

 /** for debug rendering **/
 ShapeRenderer debugRenderer = new ShapeRenderer();

 public WorldRenderer(World world) {
  this.world = world;
  this.cam = new OrthographicCamera(10, 7);
  this.cam.position.set(5, 3.5f, 0);
  this.cam.update();

  spriteBatch = new SpriteBatch();
  loadTextures();
 }

	private static final float CAMERA_WIDTH = 10f;
	private static final float CAMERA_HEIGHT = 7f;
	private SpriteBatch spriteBatch;
	private boolean debug = false;
	private int width;
	private int height;
	private float ppuX;	// pixels per unit on the X axis
	private float ppuY;	// pixels per unit on the Y axis
	public void setSize (int w, int h) {
		this.width = w;
		this.height = h;
		ppuX = (float)width / CAMERA_WIDTH;
		ppuY = (float)height / CAMERA_HEIGHT;
	}
   private void drawBlocks() {

	          for (Block block : world.getBlocks()) {
	              spriteBatch.draw(blockTexture, block.position.x * ppuX, block.position.y * ppuY, Block.SIZE * ppuX, Block.SIZE * ppuY);
	          }

	      }

/** Textures **/
	private Texture bobTexture;
	private Texture blockTexture;

	private static final int        FRAME_COLS = 6;      
    private static final int        FRAME_ROWS = 5;        
    Animation                       walkAnimation;          
    Texture                         walkSheet;              
    TextureRegion[]                 walkFrames;             
    TextureRegion                   currentFrame;           

    float stateTime;                                        

	private void loadTextures() {
		walkSheet = new  Texture(Gdx.files.internal("animation_sheet.png"));
		TextureRegion[][] tmp = TextureRegion.split(walkSheet, walkSheet.getWidth() / 
				FRAME_COLS, walkSheet.getHeight() / FRAME_ROWS);
		walkFrames = new TextureRegion[FRAME_COLS * FRAME_ROWS];
        int index = 0;
        for (int i = 0; i < FRAME_ROWS; i++) {
                for (int j = 0; j < FRAME_COLS; j++) {
                        walkFrames[index++] = tmp[i][j];
                }
        }
        walkAnimation = new Animation(0.025f, walkFrames);

        stateTime = 0f;

        bobTexture = new  Texture(Gdx.files.internal("images/bob.png"));
        blockTexture = new Texture(Gdx.files.internal("images/block.png"));
	}
	public void render() {
		 spriteBatch.begin();
				drawBlocks();
				drawBob();
		 spriteBatch.end();

			if (debug)
				drawDebug();
		  }
	private void drawBob() {
		Bob bob = world.getBob();

		int facex=1;
		if(bob.facingLeft){
			facex=-1;
		}

		if (bob.state==bob.state.WALKING){
		Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        stateTime += Gdx.graphics.getDeltaTime();
        currentFrame = walkAnimation.getKeyFrame(stateTime, true);
        spriteBatch.draw(currentFrame, bob.position.x * ppuX, bob.position.y * ppuY, facex*Bob.SIZE * ppuX, Bob.SIZE * ppuY);

		}
		else if(bob.state==bob.state.IDLE){
			spriteBatch.draw(bobTexture, bob.position.x * ppuX, bob.position.y * ppuY, facex* Bob.SIZE * ppuX, Bob.SIZE * ppuY);
		}

			//spriteBatch.draw(bobTexture, bob.position.x * ppuX, bob.position.y * ppuY, Bob.SIZE * ppuX, Bob.SIZE * ppuY);

	}
	private void drawDebug() {
		// render blocks
		debugRenderer.setProjectionMatrix(cam.combined);
		debugRenderer.begin(ShapeType.Line);
		for (Block block : world.getBlocks()) {
			Rectangle rect = block.bounds;
			float x1 = block.position.x + rect.x;
			float y1 = block.position.y + rect.y;
			debugRenderer.setColor(new Color(1, 0, 0, 1));
			debugRenderer.rect(x1, y1, rect.width, rect.height);
		}
		// render Bob
		Bob bob = world.getBob();
		Rectangle rect = bob.bounds;
		float x1 = bob.position.x + rect.x;
		float y1 = bob.position.y + rect.y;
		debugRenderer.setColor(new Color(0, 1, 0, 1));
		debugRenderer.rect(x1, y1, rect.width, rect.height);
		debugRenderer.end();
	}
 }