Libgdx – Textures, Animations, User Input

Continuing education about the Libgdx library in this tutorial will be discussed basics about drawing textures, animations and detecting user input.

 
So let’s start with create a simple game.
 
We create create a base class MyGdxGame that inherits from the Game class. It will be the input class by which we can manage our application screens. Its structure does not differ greatly from any screen in our application and it looks like this:
 

package com.binaryalchemist.game;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.binaryalchemist.screens.FirstScreen;

public class MyGdxGame extends Game {
	
	@Override
	public void create () {
		setScreen(new FirstScreen());
	}

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

	@Override
	public void resize(int width, int height) {
		super.resize(width, height);
	}

	@Override
	public void pause() {
		super.pause();
	}

	@Override
	public void resume() {
		super.resume();
	}

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

It implements methods called at different times of application life. Let’s not make any changes expect from creating a new FirstScreen. In fact, we could make only one screen inheriting ApplicationListener and leave it that way but in this form it will be easier to managed project in the future when we have more screens and objects between which we have to delegate tasks.
 
So we go to the correct part of this tutorial. At the beginning lets make simple application which loads image and a background. Create FirstScreen class which inherits from the Screen class and implement all necessary methods. Then we create two textures representing background and spacecraft (player) and draw them on the screen. The code looks like this:
 

package com.binaryalchemist.screens;

public class FirstScreen implements Screen{

	private Sprite sprite;
	private SpriteBatch spriteBatch;
	private Texture background;
	private Texture spacecraft;

	@Override
	public void show() {
		
		spriteBatch = new SpriteBatch();
		background = new Texture("space.jpg");
		spacecraft = new Texture("spacecraft.png");
		sprite = new Sprite(background);
		sprite.setSize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
	}
	
	@Override
	public void render(float delta) {
		Gdx.gl.glClearColor(0, 0, 0, 1);
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

		spriteBatch.begin();
		sprite.draw(spriteBatch);
		spriteBatch.draw(spacecraft,0,0,100f,200f);
		spriteBatch.end();
	}

	@Override
	public void resize(int width, int height) {
	}

	@Override
	public void pause() {
	}

	@Override
	public void resume() {
	}

	@Override
	public void hide() {
	}

	@Override
	public void dispose() {
		sprite.getTexture().dispose();
		spacecraft.dispose();
	}
}

Object spriteBatch serves us to draw textures on the screen. All drawing objects must be preceded by spriteBatch.begin() and ended with spriteBatch.end() Objects can be drew two ways. Using the method draw() of SpriteBatch object in which we give the name of the variable where is stored texture, its location on the screen and size. Another convenient way is to create a Sprite object that stores the information about the object, such as its size or location. All textures must be removed at the end of the application life to avoid a memory leak so get rid of them in the dispose() method.
 
We should get such result of this simple application:
 
app
 
Let’s make it a little more interesting. To start with let’s give the user to possibility to navigate our ship. So we implement the class InputProcessor and improve code by the following elements:
 

package com.binaryalchemist.screens;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;

public class FirstScreen implements Screen, InputProcessor {

	private Sprite sprite;
	private SpriteBatch spriteBatch;
	private Texture background;
	private Texture spacecraft;
	
	public float x=0;
	public float y=0;

	@Override
	public void show() {
		
		spriteBatch = new SpriteBatch();
		background = new Texture("space.jpg");
		spacecraft = new Texture("spacecraft.png");
		sprite = new Sprite(background);
		sprite.setSize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
		
		Gdx.input.setInputProcessor(this);
	}

	@Override
	public void render(float delta) {
		Gdx.gl.glClearColor(0, 0, 0, 1);
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

		spriteBatch.begin();
		sprite.draw(spriteBatch);
		spriteBatch.draw(spacecraft,x-(spacecraft.getWidth()/2),y-(spacecraft.getHeight()/2),100f,200f);
		spriteBatch.end();
	}

	@Override
	public void resize(int width, int height) {
	}

	@Override
	public void pause() {
	}

	@Override
	public void resume() {
	}

	@Override
	public void hide() {
	}

	@Override
	public void dispose() {
		sprite.getTexture().dispose();
                spacecraft.dispose();
	}

	@Override
	public boolean keyDown(int keycode) {
		return false;
	}

	@Override
	public boolean keyUp(int keycode) {
		return false;
	}

	@Override
	public boolean keyTyped(char character) {
		return false;
	}

	@Override
	public boolean touchDown(int screenX, int screenY, int pointer, int button) {
		this.x = screenX;
		this.y =  Gdx.graphics.getHeight()-screenY;
		return false;
	}

	@Override
	public boolean touchUp(int screenX, int screenY, int pointer, int button) {
		this.x = screenX;
		this.y = Gdx.graphics.getHeight()-screenY;
		return false;
	}

	@Override
	public boolean touchDragged(int screenX, int screenY, int pointer) {
		this.x = screenX;
		this.y = Gdx.graphics.getHeight()-screenY;
		return false;
	}

	@Override
	public boolean mouseMoved(int screenX, int screenY) {
		return false;
	}

	@Override
	public boolean scrolled(int amount) {
		return false;
	}
}

Class Input Processor is used to listen for user clicks whether it is coming from the keyboard, mouse or touch screen of smartphone. These methods are activated when:
 
public boolean keyDown(int keycode) – when user presses select key on the keyboard
public boolean keyUp(int keycode) – when user releases desired key on the keyboard
public boolean keyTyped(char character) – when entered by user key will be processed
public boolean touchDown(int screenX, int screenY, int pointer, int button) – when user clicks a button on the mouse or touches smartphone screen
public boolean touchUp(int screenX, int screenY, int pointer, int button) – when user releases mouse button or will move finger from the screen
public boolean touchDragged(int screenX, int screenY, int pointer) – when user moves mouse or moves finger on the screen
public boolean mouseMoved(int screenX, int screenY) – when user moves the mouse
public boolean scrolled(int amount) – when user moves mouse wheel
 
In this application we overwriten methods responsible only for detecting touch on your smartphone. These methods allow us to read the position of the user’s finger. These values are written in the variables x and y and are passed to draw method of spriteBatch. To put the texture in the center of the touch subtract its half of height and width.
 
Probably many of you have noticed that when determining position y we are subtract the height of the screen:
this.y = Gdx.graphics.getHeight()-screenY;
This is due to the fact that the coordinates obtained from the touchscreen are not the same as the coordinates of drawing object. (For drawn objects position 0,0 is located in the lower left corner of the screen. The coordinates of the touchscreen point 0.0 are defined as the upper left corner of the screen). This can be fixed in showed way, or by use of camera class which about will probably be next tutorial. At the end, do not forget to register our listener Gdx.input.setInputProcessor (this);
 
We should now be able to move our spaceship.
 
In order to achieve identical results, it is possible that you will have to change in AndroidManifest file android: screenOrientation to “portrait”
 
The last thing I wanted to speake here about are animations. By which our application can gain a lot of life. The animations are nothing more than many single pictures displayed at the correct speed. To create animation to start with we need a spritesheet containing all the frames of our animation. Personally, to create such spritesheet I use Photoshop CS6 which allows to create animations and save them as individual images.
 
To connect all the individual images in one whole spritesheet I use ImageMagick software (http://www.imagemagick.org/script/index.php). During installation, select all the extras and do not forget to add software to the environmental variables:

MAGICK_HOME="$HOME/ImageMagick-7.0.3"

Now we can go to the command line and use the following command:

montage -tile 10x7 -mode concatenate -background none "*.png" spacecraftanimation.png

This command merges all the images contained in open folder to a single file. Images are placed in 10 columns and 7 rows while maintaining a transparent background. Personally, I created spritesheet of 64 images, here are obtained results:
 
spacecraftanimation
 
It should be remembered that a single png image should not exceed the size of 2048×2048 because with larger images many android devices can not work.
 
When we do it remains for us only to implement a solution in the application:
 

package com.binaryalchemist.screens;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;

public class FirstScreen implements Screen, InputProcessor {

	private Sprite sprite;
	private SpriteBatch spriteBatch;
	private Texture background;
	private Texture spacecraft;
	
	private Animation playerAnimation;	
	private TextureRegion[] playerTextureRegion;
	private Texture playerAnimationTexture;
	private TextureRegion currentFrame;
	
	float stateTime = 0;        
	public float x=0;
	public float y=0;

	@Override
	public void show() {
		
		spriteBatch = new SpriteBatch();
		background = new Texture("space.jpg");
		spacecraft = new Texture("spacecraft.png");
		sprite = new Sprite(background);
		sprite.setSize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
		
		Gdx.input.setInputProcessor(this);
		
		playerAnimationTexture = new Texture("spacecraftanimation.png");
		playerTextureRegion = createRegion(playerTextureRegion, playerAnimationTexture, 100, 200, 3, 10);
		playerAnimation = new Animation(0.1f, playerTextureRegion);
		currentFrame = new TextureRegion();
	}
	
	@Override
	public void render(float delta) {
		Gdx.gl.glClearColor(0, 0, 0, 1);
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
	
		stateTime += Gdx.graphics.getDeltaTime(); 
		currentFrame = playerAnimation.getKeyFrame(stateTime, true);

		spriteBatch.begin();
		sprite.draw(spriteBatch);
		spriteBatch.draw(currentFrame,x-(spacecraft.getWidth()/2),y-(spacecraft.getHeight()/2),100f,200f);
		spriteBatch.end();
	}

	@Override
	public void resize(int width, int height) {
	}

	@Override
	public void pause() {
	}

	@Override
	public void resume() {
	}

	@Override
	public void hide() {
	}

	@Override
	public void dispose() {
		sprite.getTexture().dispose();
		spacecraft.dispose();
		playerAnimationTexture.dispose();
	}

	@Override
	public boolean keyDown(int keycode) {
		return false;
	}

	@Override
	public boolean keyUp(int keycode) {
		return false;
	}

	@Override
	public boolean keyTyped(char character) {
		return false;
	}

	@Override
	public boolean touchDown(int screenX, int screenY, int pointer, int button) {
		this.x = screenX;
		this.y =  Gdx.graphics.getHeight()-screenY;
		return false;
	}

	@Override
	public boolean touchUp(int screenX, int screenY, int pointer, int button) {
		this.x = screenX;
		this.y = Gdx.graphics.getHeight()-screenY;
		return false;
	}

	@Override
	public boolean touchDragged(int screenX, int screenY, int pointer) {
		this.x = screenX;
		this.y = Gdx.graphics.getHeight()-screenY;
		return false;
	}

	@Override
	public boolean mouseMoved(int screenX, int screenY) {
		return false;
	}

	@Override
	public boolean scrolled(int amount) {
		return false;
	}
	
    public static TextureRegion[] createRegion(TextureRegion[] outputRegion, Texture texture, int sizeX, int sizeY, int row, int column){
    	
    	TextureRegion[][] tmp = TextureRegion.split(texture, sizeX, sizeY);
    	outputRegion = new TextureRegion[row*column];
        int index = 0;
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < column; j++) {
            	outputRegion[index++] = tmp[i][j];
            }
        }
		return outputRegion;
    }
}

 
We create four objects these are:
 
private Animation playerAnimation – object responsible for displaying animation
private TextureRegion[] playerTextureRegion -a table for storing all the individual areas (images) of the sprite sheet;
private Texture playerAnimationTexture – Texture storing whole spritesheet (png file here);
private TextureRegion currentFrame – currently displayed image;
 
To split a png file to singular areas(images) we can create such method as createRegion() IT takes few parameters.
outputRegion – zmienna do której zapisujemy wyniki
texture – texture which are divided into single areas
sizeX – width of single area
sizeY – height of single area
int row – number of rows in texture
int column – number of columns in the texture
 
At the end remains displaying animation for this purpose has been created variable stateTime. This variable monitors the time elapsed in the application. Thanks to it we know that if had passed for eg 5 seconds with 30 seconds of animation then we have to choose from array 5 area. What we do in the variable
currentFrame = playerAnimation.getKeyFrame(stateTime, true);
A value of true tells us that the animation should be looped. Currently selected area we are drawing by use of spriteBatch.draw(currentFrame,x-(spacecraft.getWidth()/2),y-(spacecraft.getHeight()/2),100f,200f);
 
After all the steps we should get such result.
 

 

Original background and spacecraft:
background
spaceship

Leave a Comment

WordPress Video Lightbox Plugin