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();
	}
 }

How to create simple menu in libgdx

Actually making menu using libgdx is pretty easy, but most probably because of the lack of tutorial or (proper SEO) initially it took me a little more time then usual to make it done, It has become easier for me when I have a look at their test cases provided. Let me share a simplified code with you guys. Here in this tutorial we will write code for a button that starts the game.

In our main method we have to run a new instance of LwjglApplication class which requires an ApplicationListener to launch. So we need to implement ApplicationListener for sure. As the Game class provides few extra benefit like setting which screen to show up and things. So in this blog I will be extending Game class. Game class has a setScreen method just changing it decides which method will be shown in the screen. We want to take advantage of it in future when the button will get pressed so I am sending a self instance of this class.

//MainMenuGame.java

package com.sadafnoor.MainMenu;
 
import com.badlogic.gdx.Game;
 
public class MainMenuGame extends Game {
@Override
public void create() {
setScreen(new MenuScreen(this));
}
}

Now we will be writing the code we are interested about. We will extend the screen class as it is required for setScreen(). We need to create a Stage to add our button which handles the viewing region and at the same time takes care of the input actions so we need to send it to input process as well.

stage = new Stage();
Gdx.input.setInputProcessor(stage);

We need to define the skin of our button which is basically the background color, fonts and so on.

BitmapFont bfont=new BitmapFont();
bfont.scale(1);
skin.add("default",bfont);

Now we need to define the style of our button, which is TextButtonStyle. Here will get a lot of options to deal with. we can define what colors will it take when the mouse is over it, when the button is pressed and so on. and then finally we can create our TextButtion. Then we can set the position of it and add it to the stage.

import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton.TextButtonStyle;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;

public class MenuScreen implements Screen {
	Skin skin;
	Stage stage;
	SpriteBatch batch;

	Game g;
	public MenuScreen(Game g){
		create();
		this.g=g;
	}

	public MenuScreen(){
		create();
	}
	public void create(){
		batch = new SpriteBatch();
		stage = new Stage();
		Gdx.input.setInputProcessor(stage);

		// A skin can be loaded via JSON or defined programmatically, either is fine. Using a skin is optional but strongly
		// recommended solely for the convenience of getting a texture, region, etc as a drawable, tinted drawable, etc.
		skin = new Skin();
		// Generate a 1x1 white texture and store it in the skin named "white".
		Pixmap pixmap = new Pixmap(100, 100, Format.RGBA8888);
		pixmap.setColor(Color.GREEN);
		pixmap.fill();

		skin.add("white", new Texture(pixmap));

		// Store the default libgdx font under the name "default".
		BitmapFont bfont=new BitmapFont();
		bfont.scale(1);
		skin.add("default",bfont);

		// Configure a TextButtonStyle and name it "default". Skin resources are stored by type, so this doesn't overwrite the font.
		TextButtonStyle textButtonStyle = new TextButtonStyle();
		textButtonStyle.up = skin.newDrawable("white", Color.DARK_GRAY);
		textButtonStyle.down = skin.newDrawable("white", Color.DARK_GRAY);
		textButtonStyle.checked = skin.newDrawable("white", Color.BLUE);
		textButtonStyle.over = skin.newDrawable("white", Color.LIGHT_GRAY);

		textButtonStyle.font = skin.getFont("default");

		skin.add("default", textButtonStyle);

		// Create a button with the "default" TextButtonStyle. A 3rd parameter can be used to specify a name other than "default".
		final TextButton textButton=new TextButton("PLAY",textButtonStyle);
		textButton.setPosition(200, 200);
		stage.addActor(textButton);
		stage.addActor(textButton);
		stage.addActor(textButton);

	}

	public void render (float delta) {
		Gdx.gl.glClearColor(0.2f, 0.2f, 0.2f, 1);
		Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
		stage.act(Math.min(Gdx.graphics.getDeltaTime(), 1 / 30f));
		stage.draw();
		Table.drawDebug(stage);
	}

	@Override
	public void resize (int width, int height) {
		stage.setViewport(width, height, false);
	}

	@Override
	public void dispose () {
		stage.dispose();
		skin.dispose();
	}

	@Override
	public void show() {
		// TODO Auto-generated method stub

	}

	@Override
	public void hide() {
		// TODO Auto-generated method stub

	}

	@Override
	public void pause() {
		// TODO Auto-generated method stub

	}

	@Override
	public void resume() {
		// TODO Auto-generated method stub

	}
}

Now we will add callback function. When the key is pressed a callback function of addaction will get triggered and via our previously passed game we will change setScreen.

textButton.addListener(new ChangeListener() {
			public void changed (ChangeEvent event, Actor actor) {
				//System.out.println("Clicked! Is checked: " + button.isChecked());
				textButton.setText("Starting new game");
				g.setScreen( new GameScreen());

			}
		});

Altogether: