One Game, Many Frameworks: LibGDX

rectangles in a loop!

We finally arrive at Android, and my favorite framework: LibGDX. Unlike the most recent tutorials in which I did not go too deep into the code of the game because I had already discussed OpenGL or Objective-C in previous tutorials; for this one I've decided to do a full-on tutorial. One of those oh-my-God-he-never-stops-talking tutorials.

LibGdx to my mind is the most simple to learn of the frameworks, and is in a way the most complete: it supports Particle Systems, Tile Maps, Box2D, 3D objects, Fonts, UI elements, among other things... And it is the most optimized OpenGL framework/wrapper you will find anywhere. And if you have already developed for Android and have used frameworks like AndEngine, you will know how important optimization is and how lame frameworks can be!

One initial caveat: This is, of course, in part, a tutorial on how to build games (or opengl apps) in Android, however I will not cover many of the peculiarities of developing for Android, only the stuff important for this game and LibGDX.

Also, I will not go very deep into how to code in Java, because I did that already. If you don't know Java, you should probably do that tutorial first.

And now, the stuff you will need in order to run the code.

Install Java SDK
http://www.youtube.com/watch?v=V5iEUpSG7t8

Install Eclipse And Android
http://www.youtube.com/watch?v=ZoWVeXizfbo
http://www.youtube.com/watch?v=OIL1UouA4dE

If the previous links don't show you how to set up an AVD on Eclipse, you can do a search on youtube to help you with that also.

Download And Set up a test LibGDX project
http://www.youtube.com/watch?v=vLx_72qxK_0

LibGDX: The Point of Entry

being notified

I will first show you how LibGDX applications are structured. If you saw the video on how to set up a project in LibGDX you will know that one of the cool things about this API is that it allows you to code (and test most of your code) as a Desktop application. Then the Android application loads what you coded for the Desktop inside its one Activity.

When the code is compiled as a Desktop application or an Android application, platform specific things are mapped to the correct platform you are targetting.

So you start your application as a JoglApplication, which receives as its parameters the main class of your application/game.

FroggerGDXDesktop.java

package com.rengelbert.froggergdx;

import com.badlogic.gdx.backends.jogl.JoglApplication;


public class FroggerGDXDesktop {
	
	public static void main (String[] args) {
		
		//last parameter false = use OpenGL 1.1 and not 2.1+
		new JoglApplication(new FroggerGame(),
							"Frogger LibGDX",
							320, 480, false);
		
		
	}
}

In this case, I'm passing it a FroggerGame object which must implement an interface called ApplicationListener.

I also pass a title for the application and the screen size.

The real entry point of you app then is the object that implements ApplicationListener. Why?

Android applications receive notifications from the OS, just like iPhone applications do. You can think of these as a PureMVC-style notifications. Apps using LibGDX will listen to 6 main notifications: CREATE, RENDER, RESIZE, PAUSE, RESUME, DISPOSE.

- Create runs only once, when your application is instantiated.

- Render is the main loop, it gets fired up to 60 times a second. LibGDX does an amazing job at combining the two common threads, the main and user input thread, into one, as you will see later. For now remember that this is the main loop.

- Resize gets notified when the user changes orientation of the screen, or some notification from the OS pushed your game aside, like incoming calls. This is a very important notification in Android because normally when the screen is rotated, or the user goes back to Home, or receives a call, the OS will drop all unmanaged data. Any state information, values, images (in the case of OpenGL applications)... all goes to hell. It's weird, but it helps you organize your application and keeps you on your toes with non-persistent data (which MUST be managed). The good news is LibGDX already manages all loaded data (images, sounds), so Resize will not make you lose them.

- Pause does what you think it does. The interesting thing is that just before an application is destroyed (through Dispose), you receive a Pause notification. So this is the place where you save user app information, like preferences, high scores...

- Resume, comes back from Pause.

- Dispose kills it dead! But remember, it calls Pause first.

So when you implement the interface ApplicationListener you get methods to handle all those notifications.

Game.java

package com.rengelbert.froggergdx;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.rengelbert.froggergdx.data.GameData;
import com.rengelbert.froggergdx.screens.Screen;

public class Game implements ApplicationListener {

	public static final int GAME_STATE_PLAY = 0;
	public static final int GAME_STATE_PAUSE = 1;
	public static final int GAME_STATE_ANIMATE = 2;
	
	public Screen screen;
	
	public GameData gameData;
	public SpriteBatch spriteBatch;
	public OrthographicCamera camera;
	public int screenWidth = 0;
	public int screenHeight = 0;
	
	protected HashMap _screens;
	
	
	public void create() {
		_screens = new HashMap();
		
	}
		
	public void setScreen (String screenClassName) {
			
			screenClassName = "com.rengelbert.froggergdx.screens."+screenClassName;
			Screen newScreen = null;
			
			if (_screens.containsKey(screenClassName) == false) {
				
				try {
					Class screenClass =  Class.forName(screenClassName); 
				    Constructor constructor = screenClass.getConstructor(Game.class);    
				    newScreen = (Screen) constructor.newInstance(this);
				    _screens.put(screenClassName, newScreen);
				} catch ( InvocationTargetException ex ){
					System.err.println( ex + " Screen with Wrong args in Constructor.");
				} catch ( NoSuchMethodException ex ){
				} catch ( ClassNotFoundException ex ){
			      System.err.println( ex + " Screen Class Not Found.");
			    } catch( InstantiationException ex ){
			      System.err.println( ex + " Screen Must be a concrete class.");
			    } catch( IllegalAccessException ex ){
			      System.err.println( ex + " Screen with Wrong number of args.");
			    }
			} else {
				newScreen = _screens.get(screenClassName);
			}
			
			if (newScreen == null) return;
			
			if (screen != null) {
				//remove current screen!
				screen.destroy();
			}
			screen = newScreen;
			screen.createScreen();
		
	}
	
	
	public void update (float dt) {}
	

	public void dispose() {
		// TODO Auto-generated method stub
	}

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

	public void render() {
		// TODO Auto-generated method stub
	}

	public void resize(int arg0, int arg1) {
		// TODO Auto-generated method stub
	}

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

The important things here are the methods you get from ApplicationListener. They are the ones with the auto generated comments.

I tried to use as much of the logic from the previous versions of this game, so I kept a Dictionary with Screen objects. But when developing for Android you might want to keep the screens organized in a different way. You should probably destroy them when they leave the screen and recreate them when they reappear. Remember that unlike in iOS development, you are dealing with a huge variety of system configurations as targets for your application. Be extra considerate of memory and optimization when working with Android. But Frogger is a relatively small game so I could manage to store the Screens.

Notice too that the Game class stores references to some very interesting objects. These are a Camera and a SpriteBatch.

Why a Camera?

If you ever built a 2D game in Unity you will understand why there is a camera in this code. Remember that OpenGL deals with 2D and 3D. 2D graphics are created by using an Orthographic camera, meaning a camera that stays focused on a plane at a 90 degree angle. So in a way you are in a 3D world, looking at a picture.

This object gets updated within the Render notification, so it controls screen view clipping and other helpful things.

And what is a SpriteBatch?

Blitting in OpenGL happens through a number of State changes. You change the state of the OpenGL engine when you bind the image you want to copy onto the screen; you change the state when you feed the engine the array of texture and screen coordinates; when you set the projection matrix; when you enable/disable blending... SpriteBatch is one of the ways LibGDX minimizes state changes. You use it when you want to draw stuff on the screen. Like this:

SpriteBatch example

//outside your rendering loop
SpriteBatch spriteBatch =  new SpriteBatch();
OrthographicCamera camera = new OrthographicCamera(320, 480);

//inside your rendering loop
GLCommon gl = Gdx.gl;
//clear the screen with Black
gl.glClearColor(0, 0, 0, 1);
gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
		
camera.update();

spriteBatch.setProjectionMatrix(camera.combined);
spriteBatch.enableBlending();

spriteBatch.begin();//<--
spriteBatch.draw(someTextureRegion, 50, 50);
spriteBatch.end();//<--

One important thing is that you can, and probably should, use only one SpriteBatch instance in your game. These are quite heavy objects and there is no real need to create a bunch of them.

Here's an example of a SpriteBatch that uses different images (not just one sprite sheet)

SpriteBatch with multiple bindings

camera.update();
spriteBatch.setProjectionMatrix(camera.combined);

spriteBatch.disableBlending();
spriteBatch.begin();
spriteBatch.draw(backgroundRegion, 0, 0, 320, 480);
spriteBatch.end();

spriteBatch.enableBlending();
spriteBatch.begin();
spriteBatch.draw(someTextureRegion1, 10, 30);
spriteBatch.draw(someTextureRegion2, 50, 20);
spriteBatch.draw(someTextureRegion3, 30, 90);
spriteBatch.end();

So here there are two texture binding calls to the engine. One paints a background image and we can then disable the alpha (blending). The others come from the same sprite sheet. Notice that you call begin and end on the SpriteBatch for each pass. But again, all state changes are managed by the SpriteBatch, you can just sit back and relax.

This is by no means all I'll say about rendering and textures. I just wanted to show you what a camera is here for and what the hell are SpriteBatches.

To finish this section, I wanted to show you then what the FroggerGame class looks like:

FroggerGame.java

package com.rengelbert.froggergdx;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.GLCommon;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.rengelbert.froggergdx.data.GameData;
import com.rengelbert.froggergdx.data.ImageCache;
import com.rengelbert.froggergdx.data.Sounds;


public class FroggerGame extends Game {
	
	@Override
	public void create() {
		
		super.create();
		
		camera = new OrthographicCamera(320, 480);
		camera.position.set(320 * 0.5f, 480 * 0.5f, 0);
		
		screenWidth = 320;
		screenHeight = 480;
		
		Sounds.load ();
		ImageCache.load ();
		
		gameData = new GameData(this);
		spriteBatch = new SpriteBatch();
		
		setScreen("MenuScreen");
		
	}

	@Override
	public void dispose() {
		if (screen != null) screen.dispose();
	}

	@Override
	public void pause() {
		if (screen != null) screen.pause();
	}

	@Override
	public void render() {
		if (screen != null) {
			screen.update(Gdx.graphics.getDeltaTime());
		} else {
			
			GLCommon gl = Gdx.gl;
			gl.glClearColor(0, 0, 0, 1);
			gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
		}
	}

	@Override
	public void resize(int arg0, int arg1) {
		
	}

	@Override
	public void resume() {
		if (screen != null) screen.resume();
	}
}

When I receive the CREATE notification I load the Images and the sounds; I create the Camera object and position it at the center of the screen; I create the SpriteBatch object and I set the screen to MenuScreen.

In this game I don't have any pause logic or dispose logic implemented. And I ignore resize as well. But in more complex projects you would use these events to control pause, and save any information you need from the game.

In the RENDER notification I fill the stage with black if no Screen object is defined or if one is defined I update it with the value for delta time. Remember that this render method is the main loop of the game.

LibGDX: Loading Stuff

io silver!

Next in the game comes the IO stuff. Loading the images and the sounds you will use in the game. You might add the option to create a settings document, or load a default settings document. This is how you do it:

The Sounds

package com.rengelbert.froggergdx.data;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Sound;

public class Sounds {
	
	public static Sound jump;
	public static Sound hit;
	public static Sound pickup;
	public static Sound splash;
	public static Sound outofbounds;
	public static Sound target;
	
	public static void load () {
		
		jump = loadSound("jump.wav");
		hit = loadSound("hit.wav");
		pickup = loadSound("pickup.wav");
		splash = loadSound("splash2.wav");
		outofbounds = loadSound("outofbounds.wav");
		target = loadSound("target.wav");
	}
	
	private static Sound loadSound (String filename) {
		return Gdx.audio.newSound(Gdx.files.internal("data/sounds/" + filename));
	}
	
	public static void play (Sound sound) {
		sound.play(1);
	}
}

LibGDX comes packed with a very simple and effective Sound engine. I would recommend using .ogg files for effects and .mp3 for music. I used wav because I didn't have Audacity installed and was too lazy to get it.

Notice the Gdx.files.internal command. This is one of those commands that map to different locations depending on whether you are running the desktop version or the android version. In the desktop this will look for the folder "data/sounds" on the root of my application. In Android it will look for the same folder inside the Android Assets folder.

The Images

package com.rengelbert.froggergdx.data;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureRegion;

public class ImageCache {

	public static Texture sheet;
	public static TextureAtlas atlas;
	
	public static void load () {
		String textureFile = "data/frogger.txt";
		atlas = new TextureAtlas(Gdx.files.internal(textureFile), Gdx.files.internal("data"));
	}
	
	public static TextureRegion getTexture (String name) {
		return atlas.findRegion(name);
	}
	
	public static TextureRegion getFrame (String name, int index) {
		return atlas.findRegion(name, index);
	}
}

LibGDX handles textures in many different ways. It is more closely related to the way Sparrow/Starling does it than the way Cocos2D does it.

First, there is a Texture object. This is the source image you will use.

There is a TextureRegion object, which is the location in the source image where you find a specific texture. Of course, this is only used in sprite sheets. If you have a Texture that contains only one large png for the background for instance, there is no point in creating a TextureRegion associated with it, although you could. The TexureRegion would have a 0, 0 start point and the width and height would be the same as the whole Texture.

There is a TextureAtlas. This loads a data file that describes the sprite sheet's various textures. If you use TexturePacker, there is the option to export to LibGDX. It creates a file that looks a lot like a JSON file. This is what I'm loading for this game. Here is a sample:

frogger.txt

frogger.png
label_time
rotate: false
xy: 295, 621
size: 37, 13
orig: 37, 13
offset: 0, 0
index: -1
death_
rotate: false
xy: 272, 484
size: 17, 22
orig: 17, 22
offset: 0, 0
index: 3
car_
rotate: false
xy: 421, 2
size: 22, 19
orig: 22, 19
offset: 0, 0
index: 4
number_level_
rotate: false
xy: 491, 660
size: 10, 16
orig: 10, 16
offset: 0, 0
index: 7
number_time_
rotate: false
xy: 227, 706
size: 6, 10
orig: 6, 10
offset: 0, 0
index: 9
frog_bonus_side
rotate: false
xy: 329, 484
size: 16, 21
orig: 16, 21
offset: 0, 0
index: -1
number_time_
rotate: false
xy: 187, 706
size: 6, 10
orig: 6, 10
offset: 0, 0
index: 4

One weird thing that can be very helpful at times, and very annoying at other times, is that images that have the same root name, but different indexes, like: number_0.png, number_1.png, frame_1.png, frame_2.png... All receive as the name parameter the root of the file name, and the number gets moved to the index property of the data.

This is why I had to create the getFrame method so I could retrieve the elements that use indexes in their names.

You can drop the TextureAtlas entirely and hard code your own TextureRegions, so I could create the TextureRegion for the first item in the data like this:


Texture sourceTexture = new Texture(Gdx.files.internal("data/frogger.png"));

TextureRegion label_time = new TextureRegion (sourceTexture, 295, 621, 37, 13);

/* in the Data 
label_time
rotate: false
xy: 295, 621
size: 37, 13
orig: 37, 13
offset: 0, 0
index: -1
*/

Or I could, just as in Starling/Sparrow, create an empty TextureAtlas and use the addRegion method to add TextureRegions to it. TextureAtlas behaves as a kind of Dictionary cache for TextureRegions.

It is this TextureRegion object I will use in almost every single draw() call in the SpriteBatch during rendering.

Structuring yourt Application with MVC


Now, remember what I said about how Android does not keep loaded images hanging around in OpenGL applications if the screen is resized?


Generally, when developing OpenGL games for Android, you would have to store all your images, sounds, etc, in a centralized object that would work as the M in MVC. This object would have a LOAD method which would load all the assets and store references to each one of them. So when the OS got rid of the data, you would make a new LOAD call, probably in your Resize notification, so that all the data could be reloaded.


This meant that your game objects could not store references to the assets. For instance, your Player object could not store inside it a reference to the texture it used. Because once the data was lost and reloaded, that reference would no longer be valid.


To solve this, you need MVC once again!


Your V part would have to be completely encapsulated. The C part would grab the X and Y coordinates of the Player object, grab the current reference to the Player texture in the M part, and draw it onto the screen.


You will also remember that I said LibGDX manages data so that you never have to worry about that.


So why am I wasting your time?


Well, if you go through the sample projects you can download from the LibGDX site, you will notice that all examples use this MVC structure with a centralized Assets object that contains a reference to every texture region in the sprite sheet as well as to every sound.


Sure, the data no longer gets lost because LibGDX manages that for you, but you might not always use LibGDX with your Android applications, so you should be ready for everything.


So it might be a good idea to plan your projects that way, if for no other reason than that it will teach you to always be prepared. It will also help with other types of Android applications. MVC is the best way to go with both iOS and Android applications anyway and both Apple and Google advertise this fact heavily.


So following the Player example the application would render it like this:


1- Assets.load() loads the texture, and creates a bunch of static properties like playerTextureRegion, playerWalkAnimation, playerDeadTextureRegion...


2- The Player class is updated through the main loop. It updates its speed, position, rotation, scale... It knows nothing of its textures.


3- A renderer class (Usually called World in the LibGDX examples, or WorldRenderer) grabs the data from the Player class, determines which frame the player should display (for instance, if the player is moving it will grab a TextureRegion from Assets.playerWalkAnimation; if the player is dead it will grab Assets.playerDeadTextureRegion). The renderer class will then call the draw method in the SpriteBatch.


4- The user did something that caused the data to be lost. Call Assets.load() again from the resize event. (or maybe from the resume event). This will update the value for each one of those static properties inside Assets.


5- In the next iteration the renderer class will once again be able to combine the data from the Player class with the data from the Assets.


If nothing else, now you will be able to read through and understand the LibGDX sample projects a lot faster!

LibGDX: The Screen Object

seeing things

I should say here that LibGDX comes with an Abstract Game class that has helper methods very similar to the ones I used in my Game.java class. And that it has a Screen interface too, that I used in this game.

But LibGDX is not about how you organize your games. So you don't need to use its Game class, or organize your application as a series of screens.

It's just one of the nice things about LibGDX that it grew to implement a number of common procedures in Game development. It seems to have bits and pieces from a variety of frameworks: from Flixel to Cocos2D. But these implementations can be completely ignored by you, if you have your own preferences and personal system on how to build games. And better yet, you can easily expand LibGDX to include your way of doing things.

For instance, LibGDX does not use the container metaphor of say Sparrow or Cocos2D (the addChild calls for instance); it uses a more basic Canvas metaphor. But you can easily implement logic that recreates a display list inside LibGDX. (Although I warn you, you will lose some of the freedom the canvas metaphor gives you, as I will show you soon)

Screen.java

package com.rengelbert.froggergdx.screens;

import java.util.ArrayList;
import java.util.List;

import com.rengelbert.froggergdx.Game;
import com.rengelbert.froggergdx.sprites.GameSprite;

public abstract class Screen  implements com.badlogic.gdx.Screen {

	public List elements;
	protected Game _game;
	
	public Screen (Game game) {
		this._game = game;
		elements = new ArrayList();
	}
	
	public void pause () {};
	public void resume () {};
	public void dispose (){};
	public void hide (){};
	public void show (){};
	public void destroy () {};
	
	public abstract void createScreen ();
	public abstract void update (float dt);
	
	@Override
	public void render(float arg0) {
	}

	@Override
	public void resize(int arg0, int arg1) {
	}
}

The Screen interface has methods to hook up to 5 of the main Notifications I spoke about before. Screen can react to RENDER, PAUSE, RESUME, RESIZE, DISPOSE.

You can create your own Screen interface and hook up differently to the Notifications or ignore them completely (at your peril). You can for instance drop the render method and call it update. You can break it into update, place, render. You can break it into update, checkCollisions, render... It's up to you and your logic. Again, LibGDX does not care.

Just keep in mind this is Android and that the less method calls you make inside the loop the faster your application will be.

I added to my Screen a ELEMENTS array. Every element in this array will be drawn to the view.

Take a look at the MenuScreen class:

MenuScreen.java

package com.rengelbert.froggergdx.screens;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.GLCommon;
import com.badlogic.gdx.graphics.g2d.SpriteCache;
import com.rengelbert.froggergdx.Game;
import com.rengelbert.froggergdx.sprites.GameSprite;


public class MenuScreen extends Screen {

	private SpriteCache _spriteCache;
	private int _spriteCacheIndex;
	
	public MenuScreen (Game game) {
		super(game);
	}
	
	@Override
	public void createScreen() {
		
		if (elements.size() == 0) {
			
			GameSprite logo = new GameSprite ("logo", _game, _game.screenWidth * 0.5f,  _game.screenHeight * 0.7f);
			GameSprite label1 = new GameSprite ("label_how_to", _game, _game.screenWidth * 0.5f,  _game.screenHeight * 0.53f);
			GameSprite label2 = new GameSprite ("label_instructions", _game, _game.screenWidth * 0.5f,  _game.screenHeight * 0.2f);
			GameSprite label3 = new GameSprite ("label_tap", _game, _game.screenWidth * 0.5f,  _game.screenHeight * 0.02f);
			GameSprite control = new GameSprite ("control", _game, _game.screenWidth * 0.5f,  _game.screenHeight * 0.4f);
			
			
			/*
			//OPTION 1: With SpriteBatch
			elements.add(logo);
			elements.add(label1);
			elements.add(label2);
			elements.add(label3);
			elements.add(control);
			*/
			
			//OPTION 2: With SpriteCache
			_spriteCache = new SpriteCache();
			_spriteCache.beginCache();
			_spriteCache.add(logo.skin, logo.x, logo.y);
			_spriteCache.add(label1.skin, label1.x, label1.y);
			_spriteCache.add(label2.skin, label2.x, label2.y);
			_spriteCache.add(label3.skin, label3.x, label3.y);
			_spriteCache.add(control.skin, control.x, control.y);
			_spriteCacheIndex = _spriteCache.endCache();
			
		}
		
	}

	@Override
	public void update(float dt) {
		
		
		if (Gdx.input.justTouched()) {
			Gdx.app.log("A HIT!", "A MOST PALPABLE HIT");
			_game.setScreen("GameScreen");
		
		} else {
			GLCommon gl = Gdx.gl;
			gl.glClearColor(0.4f, 0.4f, 0.4f, 1);
			gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
			
			_game.camera.update();
			
			/*
			//OPTION 1: With SpriteBatch
			_game.spriteBatch.setProjectionMatrix(_game.camera.combined);
			_game.spriteBatch.enableBlending();
			_game.spriteBatch.begin();
			
			int len = elements.size();
			GameSprite element;
			for (int i = 0; i < len; i++) {
				element = elements.get(i);
				_game.spriteBatch.draw(element.skin, element.x, element.y);
			}
			_game.spriteBatch.end();
			*/
			
			//OPTION 2: With SpriteCache
			gl.glEnable(GL10.GL_BLEND);
	        gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
	        _spriteCache.setProjectionMatrix(_game.camera.combined);
			_spriteCache.begin();
	        _spriteCache.draw(_spriteCacheIndex);
	        _spriteCache.end();
	        
		}

	}
	
}

So as you can see, I dropped the render method entirely and did everything I needed in the update method. If you find this strange, feel free to break up update and render any way you want.

In this class you can already see one of my all time favorite things about LibGDX: the fact that user input is processed in the same thread.

if (Gdx.input.justTouched()) {
	Gdx.app.log("A HIT!", "A MOST PALPABLE HIT");
	_game.setScreen("GameScreen");
}

This is synchronism heaven! There is also getAccelerometerX, getAccelerometerY, getAccelerometerZ, getPitch, getRoll, getRotation, isKeyPressed among others...

I also added in this class two rendering options: SpriteBatch and SpriteCache.

If you did the Starling/Sparrow or Cocos2D tutorials you will remember that you could flatten or use so called pre-compiled sprites in those frameworks whenever you could draw something that didn't change in every frame. The equivalent in LibGDX is the SpriteCache.

It collects all the TextureRegions you want to draw and that will not change overtime (will not move, rotate, scale, change its alpha value...). And then you draw the entire cache object in one call. The vertex data for these regions are stored so they can be quickly accessed with every iteration

Each collection of TextureRegions in a SpriteCache has an id, which you get when you are done adding them to the SpriteCache object:

_spriteCacheIndex = _spriteCache.endCache();

You then use this value to draw the cache:

_spriteCache.begin();
_spriteCache.draw(_spriteCacheIndex);
_spriteCache.end();

So you can create multiple groupings of elements and draw them inside one begin and end pass, just by feeding different ids to the draw method.

There is an example online of how to use this to draw tiles, with support for multiple layers, all drawn with SpriteCache.

In Option 1 I use the SpriteBatch object I created in FroggerGame.

_game.spriteBatch.setProjectionMatrix(_game.camera.combined);
_game.spriteBatch.enableBlending();
_game.spriteBatch.begin();

int len = elements.size();
GameSprite element;
for (int i = 0; i < len; i++) {
	element = elements.get(i);
	_game.spriteBatch.draw(element.skin, element.x, element.y);
}
_game.spriteBatch.end();

The draw method in SpriteBatch receives a TextureRegion and the screen X and Y coordinates for that region.

There are a gazillion overloads for this draw method and I urge you to check them out on the reference docs for LibGDX. You can draw by passing it only a Texture object for instance. And also, the draw method can be made very powerful: you can resize things for instance just by drawing them in a certain way, and you can clip images too. (I will use the clipping option in this game later on)

I grab the TextureRegion and the screen coordinates from my own GameSprite objects.

In the GameScreen you will see a more evolved rendering logic, where I check to see if GameSprites are visible; and if the skin value is null I call a draw method on GameSprite. I'll explain that later.

Sprites

LibGDX has its own Sprite class. What does it do? It groups the TextureRegion (texture coordinates and source texture object) with the screen coordinates.

So when you create a Sprite, you pass it the TextureRegion (again, tons of overloaded methods here, check them out) and you can then update the X and Y properties of the Sprite.

And when you want to draw the Sprite, all you need is to pass it the SpriteBatch object you created.


Sprite mySprite = new Sprite (someTextureRegion);

mySprite.setPosition(10, 200);

mySprite.draw (_game.spriteBatch);

One interesting thing to point out here are the differences between a Sprite and a TextureRegion:

- TextureRegion cannot be scaled, rotated, positioned. It can be flipped however on the X and/or Y axis. The flip occurs relative to the center of the image. TextureRegion does not have an origin point.

- Sprites can be scaled, rotated, positioned. It can be flipped by using the flip() method, and by using the setScale() method. When using the setScale method you can later retrieve the current scale by using the getScaleX, getScaleY methods. With flip you cannot retrieve that information later. (The two things of course mean two different things)


//this will flip the texture the sprite uses on the X axis.
//but I cannot check later if the texture is flipped
mySprite.flip(true, false); 

//this will flip the sprite on the X axis
mySprite.setScale(-1,1);
//but now I can check to see if it's flipped
if (mySprite.getScaleX() == -1)

Sprites do have an origin point: by default the lower left corner. This point can be changed but only for scaling and rotation purposes. Changing it will not alter the positioning of the Sprite. That will still refer to its lower left corner

For the most part I used TextureRegions in this game. I used Sprite for the player, because I needed to retrieve the information of whether or not the texture was flipped either on the X axis or the Y axis. So I needed to use setScale, as I will show you next. I also used Sprites inside NumberSprite and the Lives display.

LibGDX: The Sprites

the skin

The GameSprite class is very similar to the ones I've used in the previous versions of the game. Only here, the skin property stores a TextureRegion.

GameSprite.java

package com.rengelbert.froggergdx.sprites;

import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Rectangle;
import com.rengelbert.froggergdx.Game;
import com.rengelbert.froggergdx.data.ImageCache;

public class GameSprite {
	
	public boolean active;
	public boolean visible;
	public float x = 0;
	public float y = 0;
	public int width = 0;
	public int height = 0;
	
	public TextureRegion skin;
	public Rectangle body;
	
	protected Game _game;
	
	public GameSprite (Game game, float x, float y) {
		_game = game;
		this.x = x;
		this.y = y;
		active = true;
		visible = true;
		skin = null;
	}
	
	public GameSprite (String skinName, Game game, float x, float y) {
		_game = game;
		active = true;
		visible = true;
		this.x = x;
		this.y = y;
		setSkin (skinName);	
	}
	
	public void setSkin (String skinName, int skinIndex) {
		setSkin (ImageCache.getFrame(skinName, skinIndex));
	}
	public void setSkin (String skinName) {
		setSkin (ImageCache.getTexture(skinName));
	}
	
	public void setSkin (TextureRegion texture) {
		this.skin = texture;
		width = skin.getRegionWidth();
		height = skin.getRegionHeight();
		x = x - skin.getRegionWidth() * 0.5f;
		y = y - skin.getRegionHeight() * 0.5f;
	}
	
	public float right () {
		return x + width;
	}
	
	public float left () {
		return x;
	}
	
	public float top () {
		return y + height;
	}
	
	public float bottom () {
		return y;
	}
	
	public Rectangle bounds () {
		return new Rectangle(x + width * 0.2f, y + height * 0.2f, width * 0.8f, height * 0.8f);
	}
	
	public void reset () {}
	public void update (float dt) {}
	public void show () {}
	public void hide () {}
	public void draw () {
		_game.spriteBatch.draw(skin, x, y);
	}
}

I overloaded the constructor and the setSkin methods. LibGDX has a lower left corner origin point for everything: screen, Rectangles, Sprites position ... And you can't set the origin point of a sprite to its center and hope this change will alter the position of the sprite. So in my helper methods that collect positioning information (left, right, top, bottom and bounds) I took that into account.

Notice that in the setSkin method I update the X and Y values with half the dimensions of the texture. I did this only because I didn't want to change the position values I've been using since the Starling version, which as you may recall, uses a center origin point for its textures.

It would be best to code your LibGDX game taking into consideration its lower left origin and avoid all these patches as I've done here. So you may ignore them.

One more thing is the reduced rectangle object I get with the bounds() method. In this game I use the LibGDX Rectangle object. This object does not have my favorite methods intersets() and intersection(). And although it has a method called overlaps() which works just like intersects(), it does not have a method like intersection() (or createIntersection() in Java) which returns the rectangle created by the intersection of two rectangles.

With the intersection method I can easily check for a threshold of collision. With overlap I must reduce the boundaries of every object in order to create the same threshold.

Special Sprites

First the NumberSprite:

package com.rengelbert.froggergdx.sprites;

import java.util.ArrayList;
import java.util.List;

import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.rengelbert.froggergdx.Game;
import com.rengelbert.froggergdx.data.ImageCache;

public class NumberSprite extends GameSprite {
	
	public int value;
	private List _textures;
	private List _numbers;

	public NumberSprite(Game game, float x, float y, String nameRoot) {
		
		super(game, x, y);
		
		skin = null;
		value = 0;
		_textures = new ArrayList();
		_numbers = new ArrayList();
		
		int i;
		for (i = 0; i < 10; i++) {
			_textures.add(ImageCache.getFrame(nameRoot, i));
		}
		
		Sprite sprite;
		for (i = 0; i < 10; i++) {
			//create Sprite with TextureRegion
			sprite = new Sprite(_textures.get(0));
			sprite.setPosition(x + i * (sprite.getRegionWidth() + 2), y);
			_numbers.add(sprite);
		}

		_game.screen.elements.add (this);
	}
	
	@Override 
	public void draw () {
		
		String string  = value + "";
		int len = string.length();
		if (len > 10) return;
		Sprite sprite;
		for (int i = 0; i < len; i++) {
			sprite = _numbers.get(i); 
			sprite.setRegion(_textures.get(Character.getNumericValue(string.charAt(i))));
			sprite.draw(_game.spriteBatch);
		}
		
	}
}

Here I use the Sprite object instead of a TextureRegion stored inside the skin property. I create a series of 10 sprites dislaying the texture for 0. These are never shown. Then in each rendering iteration the draw() method is called in NumberSprite, and I set the texture for each Sprite I need in order to display the number value. The remaining sprites are ignored. This is one of the advantages of having the rendering exposed to your code. You know that with every iteration you start with a blank screen and you can better choose what gets redrawn and what is ignored in each render.

Also, notice the following line:


_game.screen.elements.add (this);

Pretty much every GameSprite object will have this line, adding itself to the elements array in the current displayed screen so they get rendered inside the main loop.

TimeBar.java

package com.rengelbert.froggergdx.elements;

import java.util.Timer;
import java.util.TimerTask;

import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.rengelbert.froggergdx.Game;
import com.rengelbert.froggergdx.data.ImageCache;
import com.rengelbert.froggergdx.screens.GameScreen;
import com.rengelbert.froggergdx.sprites.GameSprite;

public class TimeBar extends GameSprite {
	
	public int seconds;
	
	private Timer _timer;
	private int _timeWidth;
	private float _timeDecrement;
	private TextureRegion _bar;
	
	public TimeBar(Game game, float x, float y) {
		
		super(game, x, y);
		
		seconds = 0;
		
		skin = null;
		_bar = ImageCache.getTexture("time_bar");
		width = _bar.getRegionWidth(); 
		height = _bar.getRegionHeight();
		
		_timeWidth = width;
		_timeDecrement = _timeWidth*0.001f;
		
		_timer = new Timer();
		
		_game.screen.elements.add(this);
		_timer.schedule(new TickTockTask(), 0l, 1000);
	}
	
	@Override 
	public void reset () {
		//reset width
		_timeWidth = width;
		visible = true;
		seconds = 0;
		
	}
	
	
	@Override
	public void draw () {
		_game.spriteBatch.draw(_bar, x, y, _timeWidth, height);
	}
	
	class TickTockTask extends TimerTask {
        public void run() {
        	if (_game.gameData.gameMode == Game.GAME_STATE_PLAY && visible) {
        		seconds++;
        		if (_timeWidth - _timeDecrement <= 0) {
    				visible = false;
    				_timer.cancel();
    				GameScreen screen = (GameScreen) _game.screen;
    				screen.gameOver();
    			} else {
    				_timeWidth -= _timeDecrement;
    			}
    		}
        }
    }
}

I use the draw methof with this element because I need to clip the image. Here I use a different SpriteBatch.draw() method:

_game.spriteBatch.draw(_bar, x, y, _timeWidth, height);

The value for _timeWidth gets reduced every second, creating the illusion the green bar texture is getting smaller.

Lives.java

package com.rengelbert.froggergdx.elements;

import java.util.ArrayList;
import java.util.List;

import com.badlogic.gdx.graphics.g2d.Sprite;
import com.rengelbert.froggergdx.Game;
import com.rengelbert.froggergdx.data.ImageCache;
import com.rengelbert.froggergdx.sprites.GameSprite;

public class Lives extends GameSprite {

	private List _lives;
	
	public Lives(Game game, float x, float y) {
		
		super(game, x, y);
		
		skin = null;
		
		_lives = new ArrayList();
		
		Sprite sprite;
		for (int i = 0; i < _game.gameData.lives; i++) {
			sprite = new Sprite (ImageCache.getTexture("frog_stand"));
			sprite.setPosition(x + i * sprite.getRegionWidth() + 5, y);
			_lives.add(sprite);
		}
		
		_game.screen.elements.add(this);
	}
	
	@Override 
	public void draw () {
		Sprite sprite;
		for (int i = 0; i < _game.gameData.lives; i++) {
			sprite = _lives.get(i);
			sprite.draw(_game.spriteBatch);
		}
	}
}

One more class where I use the Sprite. Again I only draw the number of icons necessary to display the number of lives. I don't have to worry about making the others invisible, or removing them from the stage.

Animation.java

package com.rengelbert.froggergdx.sprites;


import java.util.ArrayList;
import java.util.EventObject;
import java.util.List;

import com.badlogic.gdx.graphics.g2d.TextureRegion;

public class Animation {
	
	final TextureRegion[] keyFrames;
	final float frameDuration;
	private List _listeners;
	
	
	//THE EVENT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
	public class AnimationEvent extends EventObject {
		
		public AnimationEvent(Object source) {
              super(source);
        }
	}
	
	public interface AnimationEventListener {
	   public void onAnimationEnded(AnimationEvent e);
	}
	//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

	public Animation (float frameDuration, TextureRegion... keyFrames) {
		this.frameDuration = frameDuration;
		this.keyFrames = keyFrames;
		_listeners = new ArrayList();
	}

	public TextureRegion getKeyFrame (float stateTime, boolean loop) {
		int frameNumber = (int)(stateTime / frameDuration);

		if (!loop) {
			frameNumber = Math.min(keyFrames.length - 1, frameNumber);
			if (frameNumber == keyFrames.length - 1) {
				sendEvent();
			}
		} else {
			frameNumber = frameNumber % keyFrames.length;
		}
		
		return keyFrames[frameNumber];
	}
	
	public void addEventListener(Animation.AnimationEventListener listener)  {
		_listeners.add(listener);
	}
	public void removeEventListener(Animation.AnimationEventListener listener)   {
		_listeners.remove(listener);
	}
	
	private void sendEvent() {
		AnimationEvent event = new AnimationEvent(this);
		int len = _listeners.size();
		for (int i = 0; i < len; i++) {
			_listeners.get(i).onAnimationEnded(event);
			
		}
	}	 
}

I've decided to add the Animation class here. I used a version of the Animation class that comes with LibGDX. But unfortunately that one does not fire an event when the animation is completed. So I made those changes.

But you create the Animation by passing it a frame duration and the TextureRegion objects that will be used for each frame.

Then in the main loop you update a float variable with the delta time value, starting at 0 and then increasing as the game runs. This value is passed to the Animation object in the getKeyFrame method. So Animation will compute the time elapsed since the animation started (when the float value equaled 0) and will return a TextureRegion for the frame to be displayed.

If the animation should play in a loop, the object will continue to return TextureRegions as time progresses.

As I said, I added an event that gets fired when the animation ends.

Inside the Player class I set the animation like this:


_deathAnimation = new Animation (0.1f, ImageCache.getFrame("death_", 1),
		ImageCache.getFrame("death_", 2),
		ImageCache.getFrame("death_", 3),
		ImageCache.getFrame("death_", 4),
		ImageCache.getFrame("death_", 4),
		ImageCache.getFrame("death_", 4),
		ImageCache.getFrame("death_", 4),
		_frogStand
		);

_animationTime = 0f;
//PLAYER implements AnimationEventListener
_deathAnimation.addEventListener(this);

...
//Inside the update method
if (dead) {
	_animationTime += dt;
	return;
}

...

//Then inside the draw method
_sprite.setRegion(_deathAnimation.getKeyFrame(_animationTime, false));

Notice that I added frame 4 multiple times. I did this in Cocos2D also, because there is no method to increase the duration of a specific frame, like in Sparrow/Starling. Although, again, this could be very easily implemented, though as you can see unnecessary.

I also added as a last frame the original frame for the sprite: The frame I want to display when I reset the frog after its death animation. I did this because it was very hard to clear that last frame from the animation otherwise.

Possibly a better way of doing this would be to create a separate sprite for the animation and a sprite for the frog. But the way I did it works fine.

There are no methods to reset or pause the animation but I hope you can see how easily you may accomplish those things by managing the value in _animationTime (the stateTime value passed in getKeyFrame).

I'll cover the Player class in more depth later. But next, the GameScreen...

LibGDX: The GameScreen

touching

The things I need to cover in GameScreen are user input and rendering. The rest is part of the game logic and is not relevant to LibGDX (plus, they were shown already in previous tutorials). At any rate here is the complete GameScreen class.

GameScreen.java

package com.rengelbert.froggergdx.screens;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.GLCommon;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector3;
import com.rengelbert.froggergdx.Game;
import com.rengelbert.froggergdx.data.Sounds;
import com.rengelbert.froggergdx.elements.BonusFrog;
import com.rengelbert.froggergdx.elements.Controls;
import com.rengelbert.froggergdx.elements.FinalTier;
import com.rengelbert.froggergdx.elements.Level;
import com.rengelbert.froggergdx.elements.Lives;
import com.rengelbert.froggergdx.elements.Player;
import com.rengelbert.froggergdx.elements.Score;
import com.rengelbert.froggergdx.elements.Tier;
import com.rengelbert.froggergdx.elements.TimeBar;
import com.rengelbert.froggergdx.elements.TimeMsg;
import com.rengelbert.froggergdx.sprites.GameSprite;

public class GameScreen extends Screen {
	
	private Player _player;
	private BonusFrog _bonusFrog;
	private Controls _controls;
	private TimeBar _timeBar;
	private GameSprite _gameOverMsg;
	private GameSprite _newLevelMsg;
	private TimeMsg _levelTimeMsg;
	private Score _score;
	private Level _level;
	private Lives _lives;
	private Vector3 _touchPoint;
	private Rectangle _controlBounds;
	
	private List _tiers;
	
	
	public GameScreen(Game game) {
		super(game);
		_tiers = new ArrayList();
		_touchPoint = new Vector3();	
	}

	@Override
	public void createScreen() {
		
		if (elements.size() == 0) {
			
			//add bg
			elements.add(new  GameSprite ("bg", _game, _game.screenWidth * 0.5f, _game.screenHeight * 0.5f));
			
			//add tiers (cars, trees, crocodiles, turtles...)
			for (int i = 0; i < 12; i++) {
				_tiers.add(new Tier(_game, i));
			}
			_tiers.add(new FinalTier(_game, 12));
		
			elements.add(new GameSprite ("grass", _game, _game.screenWidth * 0.5f, _game.screenHeight - _game.screenHeight * 0.12f));
			
			_player = new Player (_game, _game.screenWidth * 0.5f, _game.screenHeight - _game.screenHeight * 0.89f);
			_bonusFrog = new BonusFrog (_game, -100, -100, _player);
			_bonusFrog.log = _tiers.get(8).getElement(0);
			
			elements.add(new GameSprite ("label_time", _game, _game.screenWidth * 0.1f, _game.screenHeight * 0.04f));
			
			_timeBar = new TimeBar (_game, _game.screenWidth * 0.18f, _game.screenHeight * 0.03f);
			
			_score = new Score (_game, _game.screenWidth * 0.2f, _game.screenHeight - _game.screenHeight * 0.05f, "number_score_");
			_level = new Level (_game, _game.screenWidth * 0.04f, _game.screenHeight - _game.screenHeight * 0.05f, "number_level_");
			_lives = new Lives (_game, _game.screenWidth * 0.68f, _game.screenHeight - _game.screenHeight * 0.06f);
		
			_controls = new Controls (_game, _game.screenWidth * 0.82f, _game.screenHeight - _game.screenHeight * 0.88f);
			_controlBounds = _controls.bounds();
			
			_gameOverMsg = new GameSprite("game_over_box", _game, _game.screenWidth * 0.5f, _game.screenHeight - _game.screenHeight * 0.53f);
			_gameOverMsg.visible = false;
			elements.add(_gameOverMsg);
			
			_newLevelMsg = new GameSprite("new_level_box", _game, _game.screenWidth * 0.5f, _game.screenHeight - _game.screenHeight * 0.53f);
			_newLevelMsg.visible = false;
			elements.add(_newLevelMsg);
			
			_levelTimeMsg = new TimeMsg(_game, _game.screenWidth * 0.5f, _game.screenHeight - _game.screenHeight * 0.53f);
			_levelTimeMsg.visible = false;
		
		} else {
			
			_timeBar.reset();
			_player.reset();
			_bonusFrog.reset();
			_score.reset();
			_level.reset();
			_game.gameData.reset();
			_lives.show();
			
			for (int i = 0; i < _tiers.size(); i++) {
				_tiers.get(i).reset();
			}
		}
		
		_game.gameData.gameMode = Game.GAME_STATE_PLAY;
	}

	
	@Override
	public void update(float dt) {
		
		//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
		//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
		//check for input
		if (Gdx.input.justTouched()) {
			if (_gameOverMsg.visible) {
				_gameOverMsg.visible = false;
				_game.setScreen("MenuScreen");
			} else {
				
				if (_game.gameData.gameMode == Game.GAME_STATE_PAUSE) return;
				
				//test for touch on Controls
				if (!_player.moving && _player.visible && _game.gameData.gameMode == Game.GAME_STATE_PLAY) {
					_game.camera.unproject(_touchPoint.set(Gdx.input.getX(), Gdx.input.getY(), 0));
					if (_controlBounds.contains(_touchPoint.x, _touchPoint.y)) {
						switch (_controls.getDirection(_touchPoint)) {
							case Player.MOVE_TOP:
								_player.moveFrogUp();
								break;
							case Player.MOVE_DOWN:
								_player.moveFrogDown();
								break;
							case Player.MOVE_LEFT:
								_player.moveFrogLeft();
								break;
							case Player.MOVE_RIGHT:
								_player.moveFrogRight();
								break;
						}
					}
				}
			}
		}
		
		
		//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
		//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
		//update elements!
		_player.update(dt);
		_player.place();
		_bonusFrog.update(dt);
		_bonusFrog.place();
		
		int i;
		int len = _tiers.size();
		//update all tiers
		for (i = 0; i < len; i++) {
		 _tiers.get(i).update(dt);
		}
		
		//check for collisions!
		if (_player.active) {
			//check collision of frog and tier sprites
			if (_tiers.get(_player.tierIndex).checkCollision(_player)) {
				//if tiers with vehicles, and colliding with vehicle
				if (_player.tierIndex < 6) {
					Sounds.play(Sounds.hit);
					//if not colliding with anything in the water tiers, drown frog
				} else {
					Sounds.play(Sounds.splash);					
				}
				//kill player
				_player.kill();
				_game.gameData.lives--;				 
			}
			//check collision of frog and bonus frog
			//if bonus frog is visible and not on frog
			if (_bonusFrog.visible) {
				if (_bonusFrog.bounds().overlaps(_player.bounds())) {
					_player.hasBonus = true; 
				}
			} 
		} else {
			if (_player.hasBonus) {
				_bonusFrog.visible = false;
				_player.hasBonus = false;
			}
		}
		
		//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
		//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
		//render all elements
		GLCommon gl = Gdx.gl;
		gl.glClearColor(0, 0, 0, 1);
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
		
		_game.camera.update();
		
		
		_game.spriteBatch.setProjectionMatrix(_game.camera.combined);
		_game.spriteBatch.enableBlending();
		_game.spriteBatch.begin();
		
		len = elements.size();
		GameSprite element;
		for (i = 0; i < len; i++) {
			element = elements.get(i);
			if (!element.visible) continue;
			if (element.skin == null) {
				element.draw();
			} else {
				_game.spriteBatch.draw(element.skin, element.x, element.y);
			}
		}
		_game.spriteBatch.end();
	}
	
	public void gameOver () {
		_gameOverMsg.visible  = true;
		_game.gameData.gameMode = Game.GAME_STATE_PAUSE;
	}
	
	public void targetReached () {
		
		//show the time needed to reach this target
		_levelTimeMsg.timeLabel.value = _timeBar.seconds;
		_levelTimeMsg.show();
		
		final Timer timer = new Timer();
		timer.schedule(new TimerTask() {
        	@Override
        	public void run() {
            	_levelTimeMsg.hide();
            	timer.cancel();
        	}
    	}, 3000, 1000);
		
		_player.reset();
		_timeBar.seconds = 0;
		
	}
	
	//start new level
	public void newLevel () {
		
		_game.gameData.gameMode = Game.GAME_STATE_PAUSE;
		//increase the speeds in the tiers
		_game.gameData.tierSpeed1 += 0.1;
		_game.gameData.tierSpeed2 += 0.2;
		_game.gameData.level++;
		
		for (int  i  = 0; i < _tiers.size(); i++) {
			_tiers.get(i).refresh();
		}
		
		_timeBar.reset();
		_game.gameData.gameMode = Game.GAME_STATE_PLAY;
		
	}
	
}

User Input

if (Gdx.input.justTouched()) {

	if (_gameOverMsg.visible) {
		_gameOverMsg.visible = false;
		_game.setScreen("MenuScreen");
	} else {
		
		if (_game.gameData.gameMode == Game.GAME_STATE_PAUSE) return;
		
		//test for touch on Controls
		if (!_player.moving && _player.visible && _game.gameData.gameMode == Game.GAME_STATE_PLAY) {
			_game.camera.unproject(_touchPoint.set(Gdx.input.getX(), Gdx.input.getY(), 0));
			if (_controlBounds.contains(_touchPoint.x, _touchPoint.y)) {
				switch (_controls.getDirection(_touchPoint)) {
					case Player.MOVE_TOP:
						_player.moveFrogUp();
						break;
					case Player.MOVE_DOWN:
						_player.moveFrogDown();
						break;
					case Player.MOVE_LEFT:
						_player.moveFrogLeft();
						break;
					case Player.MOVE_RIGHT:
						_player.moveFrogRight();
						break;
				}
			}
		}
	}
}

In the main update method I process user input, update the game elements, check for collisions and render the elements.

As far as touches are concerned, if the GameOver message is being displayed, and the user taps the screen, he is taken back to the MenuScreen.

Otherwise, I once again process only the touches that fall inside the controls area.

The camera.unproject() method is similar to the one used in Cocos2D where points are translated from the OpenGL space to screen space.

The controls GameSprite will grab the point and based on the angle it forms with its center it will determine the direction the frog should move to.

If you compile the game you will notice that I'm using a larger controls graphic than I used in the Starling version. The reasons should be obvious, it becomes easier to control the frog (fat fingers!)

Rendering the Elements

//render all elements
GLCommon gl = Gdx.gl;
gl.glClearColor(0, 0, 0, 1);
gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

_game.camera.update();


_game.spriteBatch.setProjectionMatrix(_game.camera.combined);
_game.spriteBatch.enableBlending();
_game.spriteBatch.begin();

len = elements.size();
GameSprite element;
for (i = 0; i < len; i++) {
	element = elements.get(i);
	if (!element.visible) continue;
	if (element.skin == null) {
		element.draw();
	} else {
		_game.spriteBatch.draw(element.skin, element.x, element.y);
	}
}
_game.spriteBatch.end();

Similar to the SpriteBatch option in the MenuScreen. Only now I check to see if the element is visible, and whether or not its skin property is null.

As you saw earlier, objects that require special logic in order to be drawn will set their skin properties to null so that they use their own special draw() method instead.

Otherwise it is business as usual, and the SpriteBatch object grabs the TextureRegion from each element and its current X and Y coordinates.

LibGDX: The Big Elements

player and tiers

Player.java

package com.rengelbert.froggergdx.elements;


import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector3;
import com.rengelbert.froggergdx.Game;
import com.rengelbert.froggergdx.data.GameData;
import com.rengelbert.froggergdx.data.ImageCache;
import com.rengelbert.froggergdx.data.Sounds;
import com.rengelbert.froggergdx.screens.GameScreen;
import com.rengelbert.froggergdx.sprites.Animation;
import com.rengelbert.froggergdx.sprites.MovingSprite;
import com.rengelbert.froggergdx.sprites.Animation.AnimationEvent;

public class Player extends MovingSprite implements Animation.AnimationEventListener{

	public static final int MOVE_TOP = 0;
	public static final int MOVE_DOWN = 1;
	public static final int MOVE_LEFT = 2;
	public static final int MOVE_RIGHT = 3;
	
	
	public boolean hasBonus = false;
	public float tierSpeed = 0;
	public boolean dead = false;
	public boolean moving = false;
	
	public int tierIndex = 0;
	
	private TextureRegion _frogStand;
	private TextureRegion _frogSide;
	private TextureRegion _frogJump;
	private TextureRegion _frogSideJump;
	private TextureRegion _restFrame;

	private Animation _deathAnimation;
	private float _animationTime;
	
	private int _sideStep = 22;
	private Vector3 _startPoint;
	private Sprite _sprite;
	private float _moveCnt = 0.0f;
	private int _moveInterval = 6;
	
	public Player(Game game, float x, float y) {
		
		super(game, x, y);
		
		tierSpeed = 0.0f;
		
		//store textures for frog
		_frogStand = ImageCache.getTexture("frog_stand");
		_frogJump = ImageCache.getTexture("frog_jump");
		_frogSide = ImageCache.getTexture("frog_side");
		_frogSideJump = ImageCache.getTexture("frog_side_jump");
		_restFrame = _frogStand;
		
		_deathAnimation = new Animation (0.1f, ImageCache.getFrame("death_", 1),
				ImageCache.getFrame("death_", 2),
				ImageCache.getFrame("death_", 3),
				ImageCache.getFrame("death_", 4),
				ImageCache.getFrame("death_", 4),
				ImageCache.getFrame("death_", 4),
				ImageCache.getFrame("death_", 4),
				_frogStand
				);
		
		_animationTime = 0f;
		_deathAnimation.addEventListener(this);
		
		setSkin(_frogStand);
		
		//null skin so we draw sprite instead
		skin = null;
		_sprite = new Sprite (_frogStand);
		_sprite.setPosition(x, y);
		
		_startPoint = new Vector3(x - width*0.5f, y - height*0.5f, 0f);
		
		_game.screen.elements.add(this);
		
	}
	
	@Override 
	public void reset () {
		visible = true;
		dead = false;
		_animationTime = 0f;
		
		x = nextX = _startPoint.x;
		y = nextY = _startPoint.y;
		_sprite.setPosition(x, y);
		
		tierIndex = 0;
		active = true;
		hasBonus = false;
		moving = false;
		
	}
	
	public void moveFrogUp () {
		if (!moving) {
			moving = true;
			tierIndex++;
			if (tierIndex >= Tier.TIER_Y.length) tierIndex = Tier.TIER_Y.length - 1;
			nextY = _game.screenHeight - Tier.TIER_Y[tierIndex] - height;
			_game.gameData.score += GameData.POINTS_JUMP;
			
			Sounds.play(Sounds.jump);
			showMoveFrame(MovingSprite.UP);
			
		}
	}
	
	
	public void moveFrogDown () {
		if (!moving) {
			moving = true;
			tierIndex--;
			if (tierIndex < 0) tierIndex = 0;
			nextY = _game.screenHeight - Tier.TIER_Y[tierIndex] - height;
			_game.gameData.score +=  GameData.POINTS_JUMP;
			Sounds.play(Sounds.jump);
			showMoveFrame(MovingSprite.DOWN);
		}
	}
	
	public void moveFrogLeft ()  {
		if (!moving) {
			moving = true;
			nextX -= _sideStep;
			Sounds.play(Sounds.jump);
			showMoveFrame(MovingSprite.LEFT);
		}
	}
	
	public void moveFrogRight () {
		if (!moving) {
			moving = true;
			nextX += _sideStep;
			Sounds.play(Sounds.jump);
			showMoveFrame(MovingSprite.RIGHT);
		}
	}
	
	@Override 
	public void update (float dt) {
		
		if (dead) {
			_animationTime += dt;
	        return;
	    }
	    if (moving) {
	        if (_moveCnt > _moveInterval) {
	            moving = false;
	            _sprite.setRegion(_restFrame);
	            _moveCnt = 0.0f;
	        }
	        _moveCnt += 20*dt;
	    }
	    //add tier speed if player is on top of a moving object
	    nextX += tierSpeed * dt;
	}
	
	@Override 
	public void place () {
		//limit movement if player is not on water Tiers so frog does not leave the screen
		if (tierIndex < 7) {	
			if (nextX < 0) 
				nextX = 0;
			if (nextX > _game.screenWidth - width)
				nextX = _game.screenWidth - width;
		} else {
			//make player go back to start if frog leaves screen on water Tiers
			if (nextX < 0 || nextX > _game.screenWidth - width) {
				Sounds.play(Sounds.outofbounds);
				reset();
			}
		}
		super.place();
		_sprite.setPosition(x, y);
	}
	
	public void kill () {
		tierSpeed = 0;
		_game.gameData.gameMode = Game.GAME_STATE_ANIMATE;
		active = false; 
		dead = true;
		_sprite.setScale(1f, 1f);
	}
	
	private void showMoveFrame (int dir) {
		switch (dir) {
			case MovingSprite.LEFT:
				_sprite.setScale(-1, 1);
				_sprite.setRegion(_frogSideJump);
				_restFrame = _frogSide;
				break;
			case MovingSprite.RIGHT:
				_sprite.setScale(1, 1);
				_sprite.setRegion(_frogSideJump);
				_restFrame = _frogSide;
				break;
			case MovingSprite.UP:
				_sprite.setScale(1, 1);
				_sprite.setRegion(_frogJump);
				_restFrame = _frogStand;
				break;
			case MovingSprite.DOWN:
				_sprite.setScale(1, -1);
				_sprite.setRegion(_frogJump);
				_restFrame = _frogStand;
				break;
		}
		
	}
	
	@Override
	public void draw () {
		if (dead) {
			//draw frame from death animation
			_sprite.setRegion(_deathAnimation.getKeyFrame(_animationTime, false));
		}
		_sprite.draw(_game.spriteBatch);
	}
	
	public void onAnimationEnded(AnimationEvent e) {
		visible = false;
		dead = false;
		_sprite.setRegion(_frogStand);
		_restFrame = _frogStand;
		
	    if (_game.gameData.lives >= 0) {
	    	reset();
			_game.gameData.gameMode = Game.GAME_STATE_PLAY;
	    } else {
	    	GameScreen screen = (GameScreen) _game.screen;
	    	screen.gameOver();
	    }
	}
}

As I mentioned before, I used a Sprite to store the TextureRegions for the frog because that way I could control its flipping when it moved. If I had created one image frame for each possible position of the frog, I could have used TextureRegions instead.

Tier.java

package com.rengelbert.froggergdx.elements;

import java.util.ArrayList;
import java.util.List;

import com.badlogic.gdx.math.Rectangle;
import com.rengelbert.froggergdx.Game;
import com.rengelbert.froggergdx.sprites.GameSprite;
import com.rengelbert.froggergdx.sprites.TierSprite;

public class Tier extends GameSprite {
	
	public static final int TIER_TYPE_GROUND = 0;
	public static final int TIER_TYPE_WATER = 1;
	public static final int TIER_TYPE_GRASS = 2;
	
	//the y values the frog can be at
	public static final int[] TIER_Y = {418,388,357,328,300,272,244,216,182,149,117,84,52};
	//the y values for the elements in each tier
	public static final int[] TIER_ELEMENT_Y = {0,396,367,339,310,281,0,228,194,161,129,95,65};
	//which tier is GROUND, or WATER or GRASS
	public static final int[] TIER_TYPES = {0,0,0,0,0,0,0,1,1,1,1,1,2};
	//the speeds of each Tier
	public static final int[] TIER_SPEEDS = {0,-20,25,-20,40,-25,0,-30,20,40,-25,20,0};

	public int type;
	public float speed;
	
	protected List _elements;
	protected int _index;
	
	
	public Tier(Game game, int index) {
		super(game, 0, Tier.TIER_Y[index]);
		
		_index = index;
		type = Tier.TIER_TYPES[index];
		speed = Tier.TIER_SPEEDS[index];
		
		_elements = new ArrayList();
		skin = null;
		createElements ();
	}
	
	//at the start of each new level, speeds will be updated
	public void refresh () {
		if (_index % 2 != 0) {
			speed = Tier.TIER_SPEEDS[_index] * _game.gameData.tierSpeed1;
		} else {
			speed = Tier.TIER_SPEEDS[_index] * _game.gameData.tierSpeed2;	
		}
	}
	
	public TierSprite getElement (int index)  {
		if (_elements.size() <= index) return null;
		return _elements.get(index);
	}
	
	public boolean checkCollision (Player player) {
		
		TierSprite sprite;
		boolean collision = false;
		Rectangle player_rec = player.bounds();
		Rectangle spriteBounds;
		player.tierSpeed = 0;
		
		int len = _elements.size();
		for (int i = 0; i < len; i++ ) {
			sprite = _elements.get(i);
			spriteBounds = sprite.bounds();
			if (spriteBounds == null) continue;
			
			//check intersects
			if (spriteBounds.overlaps(player_rec)) {
				collision = true;
				//collidingWith = sprite;
				break;
			}
		}
		
		//if on a tier with vehicles... 
		if (type == Tier.TIER_TYPE_GROUND) {
			//if collision, kill player
			if (collision) return true;
		//if on a tier with logs and turtles...
		} else if (type == Tier.TIER_TYPE_WATER) {
			//if no collision drown player
			if (!collision) return true;
			//else, if collision, transfer tier speed to frog 
			player.tierSpeed = speed; 
		} 
		
		return false;
	}
	
	//move elements back to original X position
	@Override
	public void reset() {
		
		float[] element_x = getElementsX();
		if (element_x != null) {
			for (int i = 0; i < _elements.size(); i++) {
				_elements.get(i).x = element_x[i];
				_elements.get(i).nextX = element_x[i];
			}
		}
		
	}
	
	
	protected void createElements () {
		
		float[] element_x = getElementsX();
		
		boolean[] element_type;
		int i;
		TierSprite sprite;
		
		switch (_index) {
			
			//VEHICLES!!!!
			case 1:
				for (i = 0; i < element_x.length; i++) {
					sprite = new Vehicle("car_", 1, _game, element_x[i], _game.screenHeight - TIER_ELEMENT_Y[_index]);
					_elements.add(sprite);	
				}
			break;
			
			case 2:
				for (i = 0; i < element_x.length; i++) {
					sprite = new Vehicle("car_", 3, _game, element_x[i], _game.screenHeight - TIER_ELEMENT_Y[_index]);
					_elements.add(sprite);	
				}
				
			break;
			case 3:
				for (i = 0; i < element_x.length; i++) {
					sprite = new Vehicle("car_", 4, _game, element_x[i], _game.screenHeight - TIER_ELEMENT_Y[_index]);
					_elements.add(sprite);	
				}
				
				
			break;
			case 4:
				for (i = 0; i < element_x.length; i++) {
					sprite = new Vehicle("car_", 2, _game, element_x[i], _game.screenHeight - TIER_ELEMENT_Y[_index]);
					_elements.add(sprite);	
				}
				
			break;
			case 5:
				for (i = 0; i < element_x.length; i++) {
					sprite = new Vehicle("car_", 5, _game, element_x[i], _game.screenHeight - TIER_ELEMENT_Y[_index]);
					_elements.add(sprite);	
				}
			break;
			
			
			//LOGS AND TURTLES!!!!
			case 7:
				element_type = new boolean[9];
				element_type[0] = false;
				element_type[1] = false;
				element_type[2] = false;
				element_type[3] = true;
				element_type[4] = true;
				element_type[5] = true;
				element_type[6] = false;
				element_type[7] = false;
				element_type[8] = false;
				
				for (i = 0; i < element_x.length; i++) {
					sprite = new Turtle(_game, element_x[i], _game.screenHeight - TIER_ELEMENT_Y[_index], element_type[i]);
					_elements.add(sprite);	
				}
				
			break;
			case 8:
				for (i = 0; i < element_x.length; i++) {
					sprite = new TreeLog("log_small", _game, element_x[i], _game.screenHeight - TIER_ELEMENT_Y[_index]);
					_elements.add(sprite);	
				}
			break;
			case 9:
				for (i = 0; i < element_x.length; i++) {
					sprite = new TreeLog("log_large", _game, element_x[i], _game.screenHeight - TIER_ELEMENT_Y[_index]);
					_elements.add(sprite);	
				}
			break;
			case 10:
				element_type = new boolean[8];
				element_type[0] = true;
				element_type[1] = true;
				element_type[2] = false;
				element_type[3] = false;
				element_type[4] = true;
				element_type[5] = true;
				element_type[6] = false;
				element_type[7] = false;
				
				for (i = 0; i < element_x.length; i++) {
					sprite = new Turtle(_game, element_x[i], _game.screenHeight - TIER_ELEMENT_Y[_index], element_type[i]);
					_elements.add(sprite);	
				}
			break;
			case 11:
				for (i = 0; i < element_x.length; i++) {
					if (i == 1) {
						sprite = new Crocodile(_game, element_x[i], _game.screenHeight - TIER_ELEMENT_Y[_index]);	
					} else {
						sprite = new TreeLog("log_medium", _game, element_x[i], _game.screenHeight - TIER_ELEMENT_Y[_index]);
					}
					_elements.add(sprite);	
				}
			break;
		}
		
		speed = Tier.TIER_SPEEDS[_index];
		
		//calculate distance between sprites (for smoother screen wrapping)
		int len = _elements.size();
		for (i = 0; i < len; i++) {
			sprite  = _elements.get(i);
			//if moving to the left
			if (Tier.TIER_SPEEDS[_index] < 0) {
				//if not the first element, distance is between this element and previous element
				if (i != 0) {
					_elements.get(i).distance = _elements.get(i).x - _elements.get(i-1).x;
				//else, distance is between this one and the last element
				} else {
					_elements.get(i).distance = _elements.get(i).x + (_game.screenWidth - _elements.get(_elements.size()-1).x) + sprite.width;
				}
			//if moving to the right
			} else if (Tier.TIER_SPEEDS[_index] > 0) {
				//if not the last element, distance is between next element and this element
				if (i != _elements.size()-1) {
					_elements.get(i).distance = _elements.get(i+1).x - _elements.get(i).x;
				} else {
					_elements.get(i).distance = (_game.screenWidth - _elements.get(i).x) + _elements.get(0).x + sprite.width;
				}
			}
		}
	}
	
	
	@Override 
	public void update (float dt) {
		
		int len = _elements.size();
		int i;
		TierSprite sprite;
		
		switch (_index) {
			case 1:
			case 2:
			case 3:
			case 4:
			case 5:
			case 7:
			case 8:
			case 9:
			case 10:
			case 11:
				//move sprites and wrap them on screen
				TierSprite nextSprite;
				
				for (i = 0; i < len; i++) {
					sprite = _elements.get(i);
					sprite.speed = speed;
					sprite.update(dt);
					
					if (speed < 0) {
						if (sprite.next_right() <= 0) {
							if (i != 0) {
								nextSprite = _elements.get(i-1);
							} else {
								nextSprite = _elements.get(len-1);
							}
							sprite.nextX = nextSprite.nextX + sprite.distance;
						}
					} else {
						if (sprite.next_left() >= _game.screenWidth) {
							if (i != len - 1) {
								nextSprite = _elements.get(i+1);
							} else {
								nextSprite = _elements.get(0);
							}
							sprite.nextX = nextSprite.nextX - sprite.distance;
						}
					}
					sprite.place();
				}
				break;
		}
	}
	
	protected float[] getElementsX () {
		
		float[] element_x;
		
		switch (_index) {
			
			//VEHICLES!!!!
			case 1:
				element_x = new float[4];
				element_x[0] = _game.screenWidth*0.1f;
				element_x[1] = _game.screenWidth*0.4f;
				element_x[2] = _game.screenWidth*0.6f;
				element_x[3] = _game.screenWidth*0.9f;
				return element_x;
			case 2:
				element_x = new float[3];
				element_x[0] = _game.screenWidth*0.2f;
				element_x[1] = _game.screenWidth*0.45f;
				element_x[2] = _game.screenWidth*0.7f;
				return element_x;
			case 3:
				element_x = new float[3];
				element_x[0] = _game.screenWidth*0.3f;
				element_x[1] = _game.screenWidth*0.6f;
				element_x[2] = _game.screenWidth*0.9f;
				return element_x;
			case 4:
				element_x = new float[2];
				element_x[0] = _game.screenWidth*0.5f;
				element_x[1] = _game.screenWidth*0.35f;
				return element_x;
			case 5:
				element_x = new float[3];
				element_x[0] = _game.screenWidth*0.2f;
				element_x[1] = _game.screenWidth*0.5f;
				element_x[2] = _game.screenWidth*0.8f;
				return element_x;
			//LOGS AND TURTLES!!!!
			case 7:
				element_x = new float[9];
				element_x[0] = _game.screenWidth*0.1f;
				element_x[1] = _game.screenWidth*0.18f;
				element_x[2] = _game.screenWidth*0.26f;
				element_x[3] = _game.screenWidth*0.45f;
				element_x[4] = _game.screenWidth*0.53f;
				element_x[5] = _game.screenWidth*0.61f;
				element_x[6] = _game.screenWidth*0.8f;
				element_x[7] = _game.screenWidth*0.88f;
				element_x[8] = _game.screenWidth*0.96f;
				return element_x;
			case 8:
				element_x = new float[3];
				element_x[0] = _game.screenWidth*0.2f;
				element_x[1] = _game.screenWidth*0.5f;
				element_x[2] = _game.screenWidth*0.8f;
				return element_x;
			case 9:
				element_x = new float[2];
				element_x[0] = _game.screenWidth*0.2f;
				element_x[1] = _game.screenWidth*0.8f;
				return element_x;
			case 10:
				element_x = new float[8];
				element_x[0] = _game.screenWidth*0.05f;
				element_x[1] = _game.screenWidth*0.13f;
				element_x[2] = _game.screenWidth*0.35f;
				element_x[3] = _game.screenWidth*0.43f;
				element_x[4] = _game.screenWidth*0.62f;
				element_x[5] = _game.screenWidth*0.7f;
				element_x[6] = _game.screenWidth*0.9f;
				element_x[7] = _game.screenWidth*0.98f;
				return element_x;
			case 11:
				element_x = new float[3];
				element_x[0] = _game.screenWidth*0.15f;
				element_x[1] = _game.screenWidth*0.5f;
				element_x[2] = _game.screenWidth*0.85f;
				return element_x;
		}
		return null;
	}

}

FinalTier.java

package com.rengelbert.froggergdx.elements;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

import com.badlogic.gdx.math.Rectangle;
import com.rengelbert.froggergdx.Game;
import com.rengelbert.froggergdx.data.GameData;
import com.rengelbert.froggergdx.data.Sounds;
import com.rengelbert.froggergdx.screens.GameScreen;

public class FinalTier extends Tier {
	
	private List _targets;
	private List _flies;
	private List _crocs;
	private List _bonus200;
	private List _bonus400;

	private int _bonusCnt;
	private Random _random;
	private int selIndex;
	private GameScreen _screen;
	
	public FinalTier(Game game, int index) {
		
		super(game, index);
		
		_bonusCnt = 0;
		_random = new Random();
		_screen = (GameScreen) _game.screen;
		
	}
	
	@Override
	public boolean checkCollision (Player player) {
		
		Target sprite;
		boolean collision = false;
		Rectangle player_rec = player.bounds();
		Rectangle sprite_rec;
		player.tierSpeed = 0;
		selIndex = -1;
		
		int len = _targets.size();
		for (int i = 0; i < len; i++) {
			sprite = _targets.get(i);
			sprite_rec = sprite.bounds();
			if (sprite_rec == null) continue;
			//check intersects
			if (sprite_rec.overlaps(player_rec)) {
				collision = true;
				selIndex = i;
				break;
			}
		}
		
		Target fly = _flies.get(selIndex);
		Target croc = _crocs.get(selIndex);
		Target target = _targets.get(selIndex);
		Target bonus200 = _bonus200.get(selIndex);
		Target bonus400 = _bonus400.get(selIndex);
		
		if (collision) {
			//if this target has been reached already...
			if (target.visible) {
				//send player back to beginning
				player.reset();
				return false;
			} else {
				player.active = false;
				player.visible = false;
				//check if croc head is in the slot
				if (croc.visible) {
					//kill player!!!
					return true;
				} else {
					
					int bonus = 0;
					//check if there are flies in this slot
					if (fly.visible) {
						fly.visible = false;
						bonus += GameData.POINTS_FLY;
					}
					if (player.hasBonus) bonus += GameData.POINTS_BONUS;
					
					//show bonus points!!!
					if (bonus > 0) {
						if (bonus > GameData.POINTS_BONUS) {
							bonus400.visible = true;
						} else {
							bonus200.visible = true;
						}
						_game.gameData.score += bonus;
						//show target reached icon after displaying bonus for some time
						final Timer timer = new Timer();
						timer.schedule(new TimerTask() {
				        	@Override
				        	public void run() {
				            	_bonus400.get(selIndex).visible = false;
				            	_bonus200.get(selIndex).visible = false;
				            	_targets.get(selIndex).visible = true;
				            	timer.cancel();
				        	}
				    	}, 300, 1);
						
					} else {
						target.visible = true;
					}
					
					_game.gameData.targetsReached++;
					Sounds.play(Sounds.pickup);
					
					final Timer timer2 = new Timer();
					timer2.schedule(new TimerTask() {
			        	@Override
			        	public void run() {
			        		_screen.targetReached();
							if (_game.gameData.targetsReached == 5)  {
								Sounds.play(Sounds.target);
								//start new level
								_screen.newLevel();
								hide();
							}
			            	timer2.cancel();
			        	}
			    	}, 1000, 1);
					
					
					//add points for reaching a target
					_game.gameData.score += GameData.POINTS_TARGET;
					player.hide();
					
				}
				return false;
			}
		}		
		return true;
	}
	
	@Override 
	public void update(float dt) {
		
		//show fly or croc head
		int len = _crocs.size();
		Target croc;
		Target target;
		for (int i = 0; i < len; i++) {
			croc = _crocs.get(i); 
			if (croc.visible) {
				if (_targets.get(i).visible) {
					croc.visible = false;	
				} else {
					target = _targets.get(i);
					if (croc.x < target.x) {
						croc.x += 0.4;
					}
				}
			}
		}
		
		if (_bonusCnt > 80) {
			_bonusCnt = 0;
			if (_random.nextInt(10) > 6) {
				//pick an index
				final int index = _random.nextInt(_targets.size());
				
				if (!_targets.get(index).visible && 
					!_flies.get(index).visible && !_crocs.get(index).visible) {
					if (_random.nextInt(10) > 6) {
						_crocs.get(index).x -= _crocs.get(index).width;
						_crocs.get(index).visible = true;
					} else {
						_flies.get(index).visible = true;
					}
					final Timer timer = new Timer();
					timer.schedule(new TimerTask() {
			        	@Override
			        	public void run() {
			        		_crocs.get(index).visible = false;
							_flies.get(index).visible = false;
			            	timer.cancel();
			        	}
			    	}, 4000, 1);
				}
			}
			
		}
		_bonusCnt++;
	}
	
	@Override 
	public void reset () {
		hide();
		_bonusCnt = 0;
	}
	
	@Override 
	public void hide() {
		for (int i = 0; i < _targets.size(); i++) {
			_targets.get(i).visible = false;
			_flies.get(i).visible = false;
			_crocs.get(i).visible = false;
			_bonus200.get(i).visible = false;
			_bonus400.get(i).visible = false;
			
		}
	}
	
	@Override 
	protected void createElements () {
		
		_targets = new ArrayList();
		_flies = new ArrayList();
		_crocs = new ArrayList();
		_bonus200 = new ArrayList();
		_bonus400 = new ArrayList();
		
		Target sprite;
		
		
		float[] element_x = {
			_game.screenWidth*0.07f,
			_game.screenWidth*0.29f,
			_game.screenWidth*0.5f,
			_game.screenWidth*0.715f,
			_game.screenWidth*0.93f
		};
		
		for (int i = 0; i < element_x.length; i++) {
			
			sprite = new Target(_game, element_x[i], _game.screenHeight - TIER_ELEMENT_Y[_index], Target.FLY);
			_flies.add(sprite);	
			
			sprite = new Target(_game, element_x[i], _game.screenHeight - TIER_ELEMENT_Y[_index], Target.CROC);
			_crocs.add(sprite);	
			
			sprite = new Target(_game, element_x[i], _game.screenHeight - TIER_ELEMENT_Y[_index], Target.TARGET);
			_targets.add(sprite);	
			
			
			sprite = new Target(_game, element_x[i], _game.screenHeight - TIER_ELEMENT_Y[_index], Target.BONUS_200);
			_bonus200.add(sprite);	
			
			sprite = new Target(_game, element_x[i], _game.screenHeight - TIER_ELEMENT_Y[_index], Target.BONUS_400);
			_bonus400.add(sprite);
			
			
		}
	}
}

And this is it for LibGDX. You can download the source code here.

There are some things I have not discussed, because they were not important or relevant to this game: like how to update alpha values in your sprites, and how to dispose unused textures to save memory. But I hope to post more tutorials on this framework soon.