LibGDX Cross Platform Development Blueprints - Sample Chapter
LibGDX Cross Platform Development Blueprints - Sample Chapter
$ 44.99 US
28.99 UK
P U B L I S H I N G
Indraneel Potnis
LibGDX Cross-Platform
Development Blueprints
ee
pl
C o m m u n i t y
E x p e r i e n c e
D i s t i l l e d
LibGDX Cross-Platform
Development Blueprints
Develop four exciting cross-platform games with increasing
complexity using LibGDX and understand its key concepts
Sa
m
Indraneel Potnis
in diverse areas of the IT industry, such as web development, QA, and mobile
application development.
Since childhood, he has been interested in playing computer games, and he became
interested in making them in college. He made a card game called Mendhicoat with a
friend on the Android platform and released it on the Google Play store.
Preface
LibGDX is a game framework with which people can make efficient games that run
on all the platforms (mobile/web/desktop) with a single code base. Games are also a
big source of monetization in the mobile market. The programming language is Java,
which is widely used everywhere, and is very easy to learn.
This book will focus on practical things by introducing a different variety of
game projects in each chapter. This book will expose you to different areas, types,
techniques, and tactics of game development.
Preface
Detecting collisions
Implementing screens
[ 65 ]
We will make a basic game screen that has a basket that can be controlled with touch.
[ 66 ]
Chapter 3
You will find that the code is pretty self-explanatory. It's nothing new from what we
have learned in earlier chapters.
com.badlogic.gdx.Gdx;
com.badlogic.gdx.graphics.Texture;
com.badlogic.gdx.graphics.g2d.Sprite;
com.badlogic.gdx.graphics.g2d.SpriteBatch;
com.packtpub.catchtheball.gameobjects.Basket;
[ 67 ]
catchtheball package:
package com.packtpub.catchtheball;
import com.badlogic.gdx.ApplicationAdapter;
[ 68 ]
Chapter 3
import
import
import
import
import
com.badlogic.gdx.Gdx;
com.badlogic.gdx.graphics.GL20;
com.badlogic.gdx.graphics.OrthographicCamera;
com.badlogic.gdx.graphics.g2d.SpriteBatch;
com.packtpub.catchtheball.managers.GameManager;
Now, if you run the game, it should look something like this:
[ 70 ]
Chapter 3
This method will set the basket's x coordinate to wherever the user has touched/
clicked on the screen. We will set the position in such a way that the basket's center
coincides with the touch coordinate. But if the user touches too close to the left end of
the screen, the basket will be drawn outside the visible area. In that case, we just set
the basket's position to (0, 0).
Let's make a new class called InputManager, which will handle the touch/click input
in our game. We will use a different strategy this time to handle the input. We have
used a strategy called polling previously. What we used to do is that at every frame,
we polled/queried the processor whether the user had touched the screen. This
wastes some processing time.
The strategy we are going to use now is called event handling. Basically, we set up
some callback methods for different types of inputs, which are automatically called
by the framework when they are triggered.
In the com.packtpub.catchtheball.managers package, add a new class named
InputManager:
package com.packtpub.catchtheball.managers;
import com.badlogic.gdx.InputAdapter;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.math.Vector3;
public class InputManager extends InputAdapter {
OrthographicCamera camera;
static Vector3 temp = new Vector3();
public InputManager(OrthographicCamera camera) {
this.camera = camera;
}
@Override
public boolean touchUp(int screenX, int screenY, int pointer,
int button) {
temp.set(screenX,screenY, 0);
//get the touch coordinates with respect to the camera's
viewport
camera.unproject(temp);
[ 71 ]
This class extends the InputAdapter class of LibGDX, which implements the
callback methods to handle the input. We override a method called touchup(),
which is a callback method that is called when the user taps/clicks on the screen. It
takes four arguments, out of which the first two are the x and the y coordinates of the
touch. The third one is the pointer ID, which is used for multi-touch handling. The
last one identifies the button that was clicked on the desktop mouse.
The constructor receives the camera instance as an argument, which is saved in
its instance variable. This is used to get the correct touch/click coordinates of the
viewport. After we get them in the touchUp() method, we pass them to the basket's
handleTouch() method to handle its movement. To enable receiving input events in
our class, add the following line to the CatchTheBall class' constructor:
Gdx.input.setInputProcessor(new InputManager(camera));
// enable InputManager to receive input events
[ 72 ]
Chapter 3
package com.packtpub.catchtheball.gameobjects;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
public class Ball {
public Sprite ballSprite; //sprite to represent a ball
public void render(SpriteBatch batch){
ballSprite.draw(batch);
}
}
In the GameManager class, we will instantiate and initialize the ball, as we did for the
basket. Let's add some new variables and constants:
static Ball ball; // ball instance
static Texture ballTexture; // texture image for the ball
private static final float BALL_RESIZE_FACTOR = 2500f;
We will initialize the ball in the initialize() method of the GameManager class:
ball = new Ball();
ballTexture = new Texture(Gdx.files.internal("data/ball.png"));
ball.ballSprite = new Sprite(ballTexture);
ball.ballSprite.setSize(ball.ballSprite.getWidth()*
(width/BALL_RESIZE_FACTOR), ball.ballSprite.getHeight()*(width/BALL_
RESIZE_FACTOR));
ball.ballSprite.setPosition(0.0f, heightball.ballSprite.getHeight());
[ 73 ]
Adding movement
Let's add two more variables to our Ball class:
public Vector2 position = new Vector2(); // vector to represent
the position
public Vector2 velocity = new Vector2(); // vector to represent
the velocity
The position variable represents the current x and y coordinates of the ball. Velocity
is defined as the rate of change of displacement. It indicates how fast the ball is
moving. You can think of it as speed. If the velocity of a car is 100 km/hr, the car will
travel 100 kilometers in one hour. Similarly, if we define the velocity of the ball as 10
units per second, then the ball will move 10 units in the game world in one second.
Let's add an update() method that will be called in every frame. The position
changes every time with velocity. So, we will add the velocity component to the
position in this method:
public void update() {
position.add(velocity);// update the position w.r.t velocity
ballSprite.setPosition(position.x, position.y);
// set the position of the sprite
}
Since we are dropping the ball from above, let's set the velocity to -5 units/frame
(since it will be added to every frame) in the ve y direction. We will do this when
we initialize the ball in the GameManager class' initialize() method:
ball = new Ball();
ballTexture = new Texture(Gdx.files.internal("data/ball.png"));
ball.ballSprite = new Sprite(ballTexture);
ball.ballSprite.setSize(ball.ballSprite.getWidth()*
(width/BALL_RESIZE_FACTOR), ball.ballSprite.getHeight()*(width/BALL_
RESIZE_FACTOR));
ball.position.set(0.0f, height-ball.ballSprite.getHeight());
ball.velocity.set(0, -5);
We will call the update() method of the ball in the renderGame() method just
before drawing it:
ball.update();
ball.render(batch);
[ 74 ]
Chapter 3
Now if you run the game, you should see the ball falling from above.
Adding gravity
To have a more realistic simulation of the ball falling down, we need to factor in
gravity. Here, gravity means acceleration due to gravity. Acceleration is defined as
the rate of change of velocity. It tells us how much the velocity changes over time.
Let's define a variable for gravity in the Ball class:
public final Vector2 gravity = new Vector2(0,-0.4f);
// vector to represent the acceleration due to gravity
Since gravity is constantly acting on the ball, it will constantly change its velocity.
Edit the update() method to add gravity to the ball's velocity:
velocity.add(gravity); // update the velocity with gravity
position.add(velocity);// update the position w.r.t velocity
// Update the initial velocity to 0 in the GameManager's
initialize() method
ball.velocity.set(0, 0);
When you run the game now, you should see the ball accelerating toward the
ground as it falls.
Detecting collisions
If you run the game, you will notice that the ball falls right off the screen. In this
topic, we are going to check for collisions between the ball and the ground and
between the ball and the basket.
The only way to know whether the ball has hit the ground is by checking the y
coordinate. If it falls below zero, it means that the ball has touched the ground. We
call this method in the update() method, and we can display a simple text if the ball
goes below the ground:
if(checkCollisions()){
System.out.println("Collided with ground"); // just to check.
can remove later
}
velocity.add(gravity); // update the velocity with gravity
Now, in order to correctly detect collisions, the circle's radius needs to be at the
center of the ball sprite and the radius should be height/2. We set the radius and
center of the circle in the initialize() method of the GameManager class. The
Circle constructor takes the first argument as the center and the next argument as
the radius:
ball.velocity.set(0, 0);
Vector2 center = new Vector2();
//set the center at the center of ball sprite
center.x=ball.position.x + (ball.ballSprite.getWidth()/2);
center.y=ball.position.y + (ball.ballSprite.getHeight()/2);
ball.ballCircle = new Circle(center, (ball.ballSprite.
getHeight()/2));
We will have to update the position of the rectangle in every frame in the update()
method of the Ball class:
ballSprite.setPosition(position.x, position.y);
// set the position of the sprite
ballCircle.setPosition(position.x+ (ballSprite.getWidth()/2),
(position.y+ ballSprite.getHeight()/2));
[ 76 ]
Chapter 3
We will follow similar steps for the basket. In the Basket class, add the following
line of code:
public Rectangle basketRectangle = new Rectangle();
// collision rectangle for the basket
We are going to separate the logic of detecting collisions with the ground into two
functions in the Ball class. The first one is detectCollisionwithGround():
public boolean checkCollisionsWithGround(){
// check if the ball hits the ground
if(position.y<=0.0){
System.out.println("Collided with ground");
return true;
}
return false;
}
It's the same as what we did earlier. We just change the name of the function and
print the output if a collision takes place. Secondly, we will create a function named
checkCollisionsWithBasket() to detect collisions with the basket:
public boolean checkCollisionsWithBasket(){
// check if the ball collided with the basket
if(Intersector.overlaps(ballCircle,
GameManager.basket.basketRectangle)){
System.out.println("Collided with basket");
return true;
}
return false;
}
[ 77 ]
[ 78 ]
Chapter 3
We will set the flag to false if it collides with either the basket or the ground. In the
checkCollisionsWithBasket() method, add the following lines of code:
if(Intersector.overlaps(ballCircle, GameManager.basket.
basketRectangle)){
isAlive=false;
return true;
}
In the GameManager class, we will set the ball to be alive at the start:
ball.velocity.set(0, 0);
// set the ball as alive
ball.isAlive=true;
We will only update and display the ball if it is alive. This will save some CPU
cycles and make the game faster. In the renderGame() method, add the following
lines of code:
if(ball.isAlive){
ball.update();
//Render(draw) the ball
ball.render(batch);
}
Now, as we want to throw multiple balls, let's make an array called balls in our
GameManager class to represent this:
public static Array<Ball> balls = new Array<Ball>();
// array of ball objects
[ 79 ]
We will create a new class called SpawnManager that handles the creation and
deletion of new Ball objects based on the interval:
package com.packtpub.catchtheball.managers;
import com.badlogic.gdx.graphics.Texture;
public class SpawnManager {
static float delayTime = 0.8f; // delay between two throwing
two balls
static float delayCounter=0.0f; // counter to keep track of
delay
static float width,height; //viewport width and height
static Texture ballTexture; // texture image for the ball
public static void initialize(float width,float height,Texture
ballTexture){
SpawnManager.width=width;
SpawnManager.height=height;
SpawnManager.ballTexture=ballTexture;
delayCounter=0.0f;// reset delay counter
}
}
Here, we declare a delayTime variable to indicate the delay between the creation
of the two balls. The delayCounter variable keeps track of the time elapsed since
the creation of the previous ball. We will instantiate and initialize the balls in this
class. That is why we declare the viewport dimensions and the texture of the ball.
We initialize these values that are passed from GameManager in the initialize()
method. Next, we define the createNewBall() method in the same class. We will
use a similar initialization logic for the ball as in GameManager. Also, we move the
BALL_RESIZE_FACTOR constant to this class from GameManager:
public static Ball createNewBall(){
Ball ball = new Ball();
ball.ballSprite = new Sprite(ballTexture);
ball.ballSprite.setSize(ball.ballSprite.getWidth()
*(width/BALL_RESIZE_FACTOR), ball.ballSprite.getHeight()*
(width/BALL_RESIZE_FACTOR));
ball.position.set(0.0f, height-ball.ballSprite.getHeight());
ball.velocity.set(0, 0);
[ 80 ]
Chapter 3
ball.isAlive=true;
Vector2 center = new Vector2();
//set the center at the center of ball sprite
center.x=ball.position.x + (ball.ballSprite.getWidth()/2);
center.y=ball.position.y + (ball.ballSprite.getHeight()/2);
ball.ballCircle = new Circle(center, (ball.ballSprite.
getHeight()/2));
return ball;
}
This method is called when we want to spawn a new ball. We create and initialize
a new ball and return it. Along with this, we also need to remove the balls, which
are not alive. Let's declare a variable to capture the indices of the balls, which are
not alive:
static List<Integer> removeIndices = new ArrayList<Integer>();
// holds indices of the balls to remove
Here, we iterate the balls array to see which objects are not alive or not active. We
record the indices of these objects in the removeIndices list. Note that we start from
the top end of the array as we want the indices in descending order. This will ensure
proper deletion of the elements. Next, we will define the run() method that will
implement the timing logic and creation of ball objects:
public static void run(Array<Ball> balls){
// delaycounter has exceeded delay time
if(delayCounter>=delayTime){
[ 81 ]
Here, we check whether the delay counter exceeds the delay time. If it exceeds,
then we spawn a new ball object. We then add it to the balls array. Otherwise,
we accumulate the delay counter with the delta time.
With all of this in place, in the GameManager class, we need to make some
modifications. First of all, we need to remove the single ball instance and
the initialization code for it. Keep the texture initialization code though. Next,
we need to add the initialization method call of the SpawnManager class in the
initialize() method:
SpawnManager.initialize(width, height, ballTexture);
Finally, we need to remove the update() and render() methods of the ball and
replace them with the following code:
SpawnManager.run(balls);
for(Ball ball:balls){
if(ball.isAlive){
ball.update();
ball.render(batch);
}
}
SpawnManager.cleanup(balls);
If you run the game, you will see balls falling after a set delay time.
[ 82 ]
Chapter 3
In our createNewBall() method, we set the x coordinate to 0 for the ball. Replace
this line with the following:
ball.position.set(random.nextInt((int) (width - ball.ballSprite.
getWidth())), height-ball.ballSprite.getHeight());
The nextInt() method is a method in the random class, which takes an integer
argument. It gives a random number between 0 and that integer. If we call it
random.nextInt(5), then it will return a random number between 0 and 5. We call
it width - ball.ballSprite.getWidth() as we want to drop the ball between the
left end of the screen (0) and the right end without the ball going out of the screen
(width - ball.ballSprite.getWidth()).
To optimize our code, we are going to follow a strategy called pooling. In our code,
we will create and delete objects from time to time. In the long run, this might cause
memory issues or performance issues, especially on mobile devices as they have less
memory and CPU speed than desktops. The key concept here is reuse.
To understand how pooling is implemented, think of a bag full of footballs.
Whenever a child needs a ball to play, he takes one out of the bag. When he is done
playing with the ball, he puts it back. The next child then does the same. This is
exactly what we are doing here. In our scenario, we call this bag a pool. Whenever
we need to display a ball in the game, we request the pool for a ball. The pool then
gives us the ball from its collection.
In the event where there are no free balls in the pool, it just creates a new ball object
and gives it back to us. Once we are done with the ball object, we release it back to
the pool. This increases our game's performance to a good amount, as we are not
creating new objects and thereby allocating memory every time. LibGDX provides
a class for object pooling called Pool. Copy the following code to the SpawnManager
class:
private final static Pool<Ball> ballPool = new Pool<Ball>() {
// this method runs when a new instance of the ball object
needs to be created (pool is empty and an object has
been requested)
@Override
protected Ball newObject() {
Ball ball = new Ball();
// instantiate basket sprite
ball.ballSprite = new Sprite(ballTexture);
return ball;
}
};
[ 83 ]
The ballPool variable is our object pool. This will create a new ball object when it is
empty and return the recycled ones from its collection when it's not. We override the
newObject() method that is called when somebody requests an object from the pool
and it is empty. Therefore, a new object has to be created and returned to the caller.
Here, we instantiate the Ball class and the sprite within it and return it. We need
to replace the createNewBall()and resetBall() methods and paste them in the
following code:
public static Ball resetBall(Ball ball){
ball.ballSprite.setSize(ball.ballSprite.getTexture().
getWidth()*(width/BALL_RESIZE_FACTOR),ball.ballSprite.
getTexture().getHeight()*(width/BALL_RESIZE_FACTOR));
ball.position.set(random.nextInt((int) (width - ball.
ballSprite.getWidth())), height-ball.ballSprite.
getHeight());
ball.velocity.set(0, 0);
ball.isAlive=true;
Vector2 center = new Vector2();
//set the center at the center of ball sprite
center.x=ball.position.x + (ball.ballSprite.getWidth()/2);
center.y=ball.position.y + (ball.ballSprite.getHeight()/2);
ball.ballCircle = new Circle(center, (ball.
ballSprite.getHeight()/2));
return ball;
}
As we can get recycled ball objects, the state is unknown. We will reset the ball's
properties in this method. We set the size of the ball with respect to the texture, as it
stays the same every time. In the run() method, we need to replace the code where
we created the new ball:
if(delayCounter>=delayTime){
Ball ball= ballPool.obtain(); // get a ball from the ball
pool
resetBall(ball); // reinitialize the ball
balls.add(ball); // add the ball to our list
delayCounter=0.0f;// reset delay counter
}
We also need to free the ball object pool in the initialize() method:
ballPool.clear(); // clear the object pool
[ 84 ]
Chapter 3
When it is time to spawn the ball, we request a ball object from the ball pool,
reinitialize it, and add it to our active ball list. In our cleanup() method, instead of
just removing the ball objects, we return them to the pool with the free() method:
If you want to test how many new ball objects have been created, add a print
statement inside the newObject() method.
[ 85 ]
To display the score, we are going to add a new class called TextManager
to the com.packtpub.catchtheball.managers package, similar to what
we did previously:
package com.packtpub.catchtheball.managers;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
public class TextManager {
static BitmapFont font; // we draw the text to the screen
using this variable
// viewport width and height
static float width,height;
public static void initialize(float width,float height){
font = new BitmapFont();
TextManager.width = width;
TextManager.height= height;
//set the font color to red
font.setColor(Color.RED);
//scale the font size according to screen width
font.setScale(width/500f);
}
public static void displayMessage(SpriteBatch batch){
float fontWidth = font.getBounds( "Score: "+GameManager
.score).width; // get the width of the text being
displayed
//top the score display at top right corner
font.draw(batch, "Score: "+GameManager.score, width fontWidth - width/15f,height*0.95f);
}
}
[ 86 ]
Chapter 3
If you run the game now, you can see the score increasing when we catch the balls:
[ 87 ]
Custom fonts
Let's see how to use custom fonts in a game. LibGDX allows you to specify the font
file to use within the font's constructor. We cannot use the TrueType font or the .ttf
file as LibGDX requires the bitmap font format.
The BitmapFont file format stores each character as an image. This is very easy and
efficient to render instead of the TTF format. So, we need to convert our font file from
TTF to the bitmap format. Fortunately, there is a tool called Hiero which can do this
for us.
You can download Hiero from https://libgdx.googlecode.com/files/hiero.
jar. You will get a JAR file, which you can double-click to open:
In the Font section, there is a file input area where you can select the TTF file. Once
you select it, you can see how the font looks in the rendering section. To keep it
simple, we will not add any extra effects:
[ 88 ]
Chapter 3
Save the font by navigating to File | Save BMFont files (text). Give the file a .fnt
extension and save it. Hiero creates one more file with the .png extension. You can
actually open the image in any image viewer/editor to see how the font characters
are stored. To load the font in our game, create a new folder called fonts in the
assets/data directory. Copy the font file and the image to this folder.
In the code where we instantiated BitmapFont, replace the line with the
following code:
// load the font from the font file
font = new BitmapFont(Gdx.files.internal
("data/fonts/[fontname].fnt"));
Since we set the font size to 32, we need to resize the font to look better. Next, we set
the scale:
font.setScale(width/1400f);
[ 89 ]
That's it. You can now see the score text in your custom font:
Don't ship system fonts with your game. You might not have a license for
this. You can use royalty-free fonts from the Internet.
[ 90 ]
Chapter 3
We get the preferences and then we get the current high score from them. In the Ball
class' checkCollisionsWithBasket() method, we set the current score to the high
score if it exceeds the current high score:
GameManager.score++;
if(GameManager.score>GameManager.highScore){
GameManager.highScore=GameManager.score;
}
In the dispose() method of GameManager, when we close our game, we will save
the high score:
prefs.putInteger("highscore", score);
prefs.flush();
To display the high score, add this line to the TextManager class' displayMessage()
method:
font.draw(batch, "High Score: "+GameManager.highScore,
width/40f,height*0.95f);
This is similar to what we did for the score, except that here we will display the high
score text in the top-left corner of our screen:
[ 91 ]
Implementing screens
In this section, we will learn how to implement a menu screen for our game and how
to transition between it and the game screen.
[ 92 ]
Chapter 3
}
@Override
public void hide() {
}
@Override
public void dispose() {
}
}
We declare textures and sprites for the Start and Exit buttons. As there is no
create() method, we will initialize the variables in the constructor. Let's first
initialize the camera and the batch:
public MenuScreen(){
// get window dimensions and set our viewport dimensions
float height= Gdx.graphics.getHeight();
float width = Gdx.graphics.getWidth();
[ 93 ]
Next, we will initialize our textures and the sprites for the buttons in the
same method:
//initialize button textures and sprites
startButtonTexture = new Texture(Gdx.files.internal
("data/start_button.png"));
exitButtonTexture = new Texture(Gdx.files.internal
("data/exit_button.png"));
backGroundTexture = new Texture(Gdx.files.internal
("data/menubackground.jpg"));
startButtonSprite = new Sprite(startButtonTexture);
exitButtonSprite = new Sprite(exitButtonTexture);
backGroundSprite = new Sprite(backGroundTexture);
// set the size and positions
startButtonSprite.setSize(startButtonSprite.getWidth()
*(width/BUTTON_RESIZE_FACTOR), startButtonSprite.
getHeight()*(width/BUTTON_RESIZE_FACTOR));
exitButtonSprite.setSize(exitButtonSprite.getWidth()
*(width/BUTTON_RESIZE_FACTOR), exitButtonSprite.
getHeight()*(width/BUTTON_RESIZE_FACTOR));
backGroundSprite.setSize(width,height);
startButtonSprite.setPosition((width/2f -startButtonSprite.
getWidth()/2) , width/START_VERT_POSITION_FACTOR);
exitButtonSprite.setPosition((width/2f -exitButtonSprite.
getWidth()/2) , width/EXIT_VERT_POSITION_FACTOR);
// set the transparency for the background
backGroundSprite.setAlpha(0.2f);
The Sprite class has a method called setAlpha() where you can set the
transparency. The values range from 0 to 1. The 0 value makes it completely
transparent and 1 makes it completely opaque.
[ 94 ]
Chapter 3
[ 95 ]
In the create() method, we call the setScreen() method to change our currently
displayed screen to MenuScreen by passing an instance of it. In the launcher
classes, pass the instance of this class. For example, in the desktop launcher,
it is implemented as follows:
new LwjglApplication(new MainGame(), config);
If you run the game now, you can see the menu screen:
Let's now implement the transition of the game screens. First, let's edit the
CatchTheBall class so that we can call this from the menu screen:
public class CatchTheBall implements Screen {
[ 96 ]
Chapter 3
In the MenuScreen class, let's declare an instance of the MainGame class. We will need
this to call its setScreen() method to transition between the screens. Parameterize
the constructor of the MenuScreen class to set this instance:
MainGame game; // instance of the main game, to call setScreen
methods
MenuScreen(MainGame game){
this.game= game;
We will also need to modify the line in the create() method of the MainGame class
where we set the menu screen to pass its instance:
setScreen(new MenuScreen(this));
Let's now handle the click/touch input for the buttons. Declare a temporary vector in
the MenuScreen class to store the input coordinates:
Vector3 temp = new Vector3(); //temporary vector to capture
input coordinates
We will add a new method called handleTouch() to our MenuScreen class to handle
the touch input:
void handleTouch(){
// Check if the screen is touched
if(Gdx.input.justTouched()) {
// Get input touch coordinates and set the temp vector
with these values
temp.set(Gdx.input.getX(),Gdx.input.getY(), 0);
//get the touch coordinates with respect to the camera's
viewport
camera.unproject(temp);
float touchX = temp.x;
float touchY= temp.y;
// handle touch input on the start button
if((touchX>=startButtonSprite.getX()) && touchX<=
(startButtonSprite.getX()+startButtonSprite.
getWidth()) && (touchY>=startButtonSprite.getY()) &&
touchY<=(startButtonSprite.getY()+startButtonSprite.
getHeight()) ){
game.setScreen(new CatchTheBall()); // Bring the game
screen to front
[ 97 ]
In this method, after capturing the input coordinates, we first check whether the
user has touched the Start button. If he has touched it, then we bring the game
screen to the front by calling the setScreen() method of the game. If the user has
touched the Exit button, then we quit the application. We call this method in the
render() method:
batch.end();
handleTouch();
We call the dispose() method to free resources in the hide() method, as it is not
called by the framework automatically this time. When we switch from one screen to
another, the hide() method is called for the first screen:
public void hide() {
dispose();
}
[ 98 ]
Chapter 3
Now, we'll declare the texture and the sprite for the Back button in the
GameManager class:
public static Sprite backButtonSprite; // back button sprite
public static Texture backButtonTexture; // texture image for the
back button
Set the Back button's dimensions and position it on the top center of the screen:
backButtonSprite.setSize(backButtonSprite.getWidth()*
(width/BACK_BTN_RESIZE_FACTOR), backButtonSprite.getHeight()*
(width/BACK_BTN_RESIZE_FACTOR));
// set the button's position to top center
backButtonSprite.setPosition(width/2- backButtonSprite.
getWidth()/2, height*0.935f);
Now, we need to handle touch/tap events on the Back button so that we can go
back to the menu screen. We will add a method named handleBackButton() to the
InputManager class. This will check whether the Back button has been touched and
set the screen back to the menu screen:
public void handleBackButton(float touchX,float touchY){
// handle touch input on the back button
if((touchX>=GameManager.backButtonSprite.getX()) && touchX
<=(GameManager.backButtonSprite.getX()+GameManager.
backButtonSprite.getWidth()) && (touchy>=GameManager.
[ 99 ]
We will call the dispose() method in the hide() method of the CatchTheBall class:
@Override
public void hide() {
dispose();
}
[ 100 ]
Chapter 3
Now, our InputManager will receive the Back keypress event. We need to handle
this by implementing the keyUp() method:
@Override
public boolean keyUp(int keycode) {
if(keycode==Keys.BACK){
CatchTheBall.game.setScreen(new MenuScreen(CatchTheBall.
game)); // Bring the menu screen to front
}
return false;
}
This method receives the key code as an argument. We then check whether the key
pressed was the Back button, and if it is, then we set the current screen to the menu
screen. We can even handle the Esc key on the desktop and cause the transition from
the game screen to the menu screen as well, as shown in the following code:
if(keycode==Keys.BACK || keycode==Keys.ESCAPE)
[ 101 ]
play
ground
play
basket
Make a new folder called sounds in the Android project's assets/data directory
and copy the two files for the effects in it. Let's initialize the instance in the
initialize() method:
//load the sound effects from file
groundHitSound = Gdx.audio.newSound(Gdx.files.internal
("data/sounds/groundHit.wav"));
basketHitSound = Gdx.audio.newSound(Gdx.files.internal
("data/sounds/basketHit.wav"));
Finally, we will dispose of the sound instances when they are not needed using the
dispose() method:
//dispose the sound instances
groundHitSound.dispose();
basketHitSound.dispose();
[ 102 ]
Chapter 3
Copy the music file to the sounds folder. We will load the music in the
initialize() method:
backgroundMusic = Gdx.audio.newMusic(Gdx.files.internal
("data/sounds/backmusic.mp3")); // load the background music
from file
Let's set the music to looping, which will replay the music after it is over:
backgroundMusic.setLooping(true); // set the music to loop
We will play the music by calling the play() method on the instance:
backgroundMusic.play(); // play the music
Finally, we will dispose of the instance in the dispose() method if not needed:
backgroundMusic.dispose();
We need to stop the music instance when we dispose of the resources in the
CatchTheBall class' dispose() method:
@Override
public void dispose() {
//dispose the batch and the textures
batch.dispose();
GameManager.backgroundMusic.stop();
GameManager.dispose();
}
To pause the music, we can call the pause() method on the instance, and to stop it,
there is a stop() method as well.
[ 103 ]
Summary
In this chapter, we made a game called Catch the Ball and learned some concepts
along the way. These include the following:
Motion physics
Collision detection
Optimizing memory
Adding music
In the next chapter, we will begin learning about creating a platformer game called
Dungeon Bob. We will learn about character motion and animation.
[ 104 ]
www.PacktPub.com
Stay Connected: