web analytics
Paste your Google Webmaster Tools verification code here

Tutorial 3 – libGDX : Encapsulating Game Components

 

Introduction

In our third tutorial we’ll show you how you can encapsulate your game elements and wrap some of the GDX functionality. The example will have a man walking across the screen back and forth.  Above the man will be a floating ball.

Creating the walking man

As we saw in our previous tutorial, we can create an animation of a man walking simply by splicing an image that contains the animation into an Animation object. Below is our walking man object

clip_image002

Remember, we can create our animation by reading in a strip of walking images into a TextureRegion in GDX, putting the frames in an Animation object and then rendering the AnimationObject each time it enters the render method. We read the texture in the create method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void create () {
batch = new SpriteBatch();
 
manWalking = new Texture("manwalking.png"); // explosion image sheet shown above
CreateManWalkingAnimation();
}
 
private void CreateManWalkingAnimation() {
TextureRegion[][] textureRegions = TextureRegion.split(manWalking, manWalking.getWidth() / FRAME_COLS, manWalking.getHeight());              // #10
manWalkingFrames = new TextureRegion[FRAME_COLS ];
int index = 0;
 
for (int j = 0; j < FRAME_COLS; j++) {
manWalkingFrames[index++] = textureRegions[0][j];
}
 
manWalkingAnimation = new Animation(0.20f, manWalkingFrames);
}
@Override
public void create () {
batch = new SpriteBatch();

manWalking = new Texture("manwalking.png"); // explosion image sheet shown above
CreateManWalkingAnimation();
}

private void CreateManWalkingAnimation() {
TextureRegion[][] textureRegions = TextureRegion.split(manWalking, manWalking.getWidth() / FRAME_COLS, manWalking.getHeight());              // #10
manWalkingFrames = new TextureRegion[FRAME_COLS ];
int index = 0;

for (int j = 0; j < FRAME_COLS; j++) {
manWalkingFrames[index++] = textureRegions[0][j];
}

manWalkingAnimation = new Animation(0.20f, manWalkingFrames);
}

Now we are ready to animate the walking man in the render method. This method plays each frame according to the time set in the Animation object. In this case .20 seconds:

1
2
3
4
5
6
7
8
9
10
@Override
public void render () {
    Gdx.gl.glClearColor(1, 0, 0, 1);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    stateTime += Gdx.graphics.getDeltaTime();           // #15
    currentFrame = manWalkingAnimation.getKeyFrame(stateTime, true);  // #16
    batch.begin();
    batch.draw(currentFrame, 50, 50);
    batch.end();
}
@Override
public void render () {
    Gdx.gl.glClearColor(1, 0, 0, 1);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    stateTime += Gdx.graphics.getDeltaTime();           // #15
    currentFrame = manWalkingAnimation.getKeyFrame(stateTime, true);  // #16
    batch.begin();
    batch.draw(currentFrame, 50, 50);
    batch.end();
}

When we run this code, we notice that the man is walking, but he is walking in place at location 50,50. We really want him to walk back and forth across the screen. In order to do this, we need to calculate the man’s position each time in the game. We’ll need to move the man a certain number of pixels each time the render method is entered. The number of pixels the man moves will serve as the man’s velocity. When the man reaches the limits of the screen, he can reverse direction and walk the other way.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public void render () {
 
Gdx.gl.glClearColor(1, 0, 0, 1);
 
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
 
stateTime += Gdx.graphics.getDeltaTime(); // #15
 
velocity = 10;
 
currentFrame = manWalkingAnimation.getKeyFrame(stateTime, true); // #16
 
batch.begin();
 
batch.draw(currentFrame, currentManPosition, 50);
 
if (direction == 1) {
 
currentManPosition += velocity;
 
if (currentManPosition > screenWidth)
 
{
 
direction = -1;
 
}
 
}
 
else
 
{
 
currentManPosition -= velocity;
 
if (currentManPosition < 0)
 
{
 
direction = 1;
 
}
 
}
 
batch.end();
 
}
public void render () {

Gdx.gl.glClearColor(1, 0, 0, 1);

Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

stateTime += Gdx.graphics.getDeltaTime(); // #15

velocity = 10;

currentFrame = manWalkingAnimation.getKeyFrame(stateTime, true); // #16

batch.begin();

batch.draw(currentFrame, currentManPosition, 50);

if (direction == 1) {

currentManPosition += velocity;

if (currentManPosition > screenWidth)

{

direction = -1;

}

}

else

{

currentManPosition -= velocity;

if (currentManPosition < 0)

{

direction = 1;

}

}

batch.end();

}

A Better Way to Deal with Sprites

The GDX Framework gives you a great way to put the basic game elements on the screen, but if you are developing a game, you generally want to design a good object model for the elements in your game. Below, I have introduced two classes: Sprite and AnimatedSprite. These classes will make it much easier for you to create elements in the game.

The Sprite class is an abstract class which you can inherit specific sprite classes in which you want to control the behavior of in your game. The sprite class has two main methods you can override: Update and Draw. Update allows you to control the position and other attributes of your sprite each frame. The Draw method, renders the sprite in the game. Draw requires that you pass in the SpriteBatch so the Sprite is rendered at the same time as the other sprites. Other properties on the sprite include the dimensions of the screen, the texture making up the sprite image, and the position of the sprite.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package com.tutorial3.game.Sprites;
 
import com.badlogic.gdx.graphics.Texture;
 
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
 
import com.badlogic.gdx.graphics.g2d.TextureRegion;
 
import com.badlogic.gdx.math.Vector2;
 
/**
 
* Created by jcity_000 on 7/10/2015.
 
*/
 
public abstract class Sprite {
 
Texture texture;
 
protected int screenWidth;
 
protected int screenHeight;
 
protected Vector2 position;
 
public Sprite(String imageTextureName) {
 
try
 
{
 
texture = new Texture(imageTextureName);
 
}
 
catch (Exception ex)
 
{
 
texture = null;
 
}
 
}
 
public Sprite(Texture imageTexture) {
 
try
 
{
 
texture = imageTexture;
 
}
 
catch (Exception ex)
 
{
 
texture = null;
 
}
 
}
 
public Sprite() {
 
texture = null;
 
}
 
abstract public void Draw(SpriteBatch batch);
 
abstract public void Update();
 
}
 
AnimatedSprite inherits from Sprite and has an additional abstract method to override specific for animations in GDX:
 
package com.tutorial3.game.Sprites;
 
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
 
import com.badlogic.gdx.graphics.g2d.TextureRegion;
 
/**
 
* Created by jcity_000 on 7/10/2015.
 
*/
 
public abstract class AnimatedSprite extends Sprite {
 
abstract public void Draw(SpriteBatch batch, TextureRegion currentFrame);
 
}
package com.tutorial3.game.Sprites;

import com.badlogic.gdx.graphics.Texture;

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

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

import com.badlogic.gdx.math.Vector2;

/**

* Created by jcity_000 on 7/10/2015.

*/

public abstract class Sprite {

Texture texture;

protected int screenWidth;

protected int screenHeight;

protected Vector2 position;

public Sprite(String imageTextureName) {

try

{

texture = new Texture(imageTextureName);

}

catch (Exception ex)

{

texture = null;

}

}

public Sprite(Texture imageTexture) {

try

{

texture = imageTexture;

}

catch (Exception ex)

{

texture = null;

}

}

public Sprite() {

texture = null;

}

abstract public void Draw(SpriteBatch batch);

abstract public void Update();

}

AnimatedSprite inherits from Sprite and has an additional abstract method to override specific for animations in GDX:

package com.tutorial3.game.Sprites;

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

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

/**

* Created by jcity_000 on 7/10/2015.

*/

public abstract class AnimatedSprite extends Sprite {

abstract public void Draw(SpriteBatch batch, TextureRegion currentFrame);

}

Now we can encapsulate all our game logic for individual objects in the game in the instances of those objects themselves instead of in the main game. For example, we can create a new Man class that inherits from AnimatedSprite and put all the movement behavior inside of the man class instead of in the main game class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package com.tutorial3.game.Sprites;
 
import com.badlogic.gdx.graphics.Texture;
 
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
 
import com.badlogic.gdx.graphics.g2d.TextureRegion;
 
import com.badlogic.gdx.math.Vector2;
 
/**
 
* Created by jcity_000 on 7/10/2015.
 
*/
 
public class Man extends AnimatedSprite {
 
private int direction;
 
private float velocity = 10;
 
public Man(int screenWidth, int screenHeight, float positionX, float positionY)
 
{
 
this.screenWidth = screenWidth;
 
this.screenHeight = screenHeight;
 
position = new Vector2(positionX, positionY);
 
}
 
@Override
 
public void Draw(SpriteBatch batch, TextureRegion currentFrame) {
 
batch.draw(currentFrame, position.x, 50);
 
}
 
@Override
 
public void Draw(SpriteBatch batch) {
 
}
 
@Override
 
public void Update() {
 
if (direction == 1) {
 
position.x += velocity;
 
if (position.x > screenWidth)
 
{
 
direction = -1;
 
}
 
}
 
else
 
{
 
position.x -= velocity;
 
if (position.x < 0)
 
{
 
direction = 1;
 
}
 
}
 
}
 
}
package com.tutorial3.game.Sprites;

import com.badlogic.gdx.graphics.Texture;

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

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

import com.badlogic.gdx.math.Vector2;

/**

* Created by jcity_000 on 7/10/2015.

*/

public class Man extends AnimatedSprite {

private int direction;

private float velocity = 10;

public Man(int screenWidth, int screenHeight, float positionX, float positionY)

{

this.screenWidth = screenWidth;

this.screenHeight = screenHeight;

position = new Vector2(positionX, positionY);

}

@Override

public void Draw(SpriteBatch batch, TextureRegion currentFrame) {

batch.draw(currentFrame, position.x, 50);

}

@Override

public void Draw(SpriteBatch batch) {

}

@Override

public void Update() {

if (direction == 1) {

position.x += velocity;

if (position.x > screenWidth)

{

direction = -1;

}

}

else

{

position.x -= velocity;

if (position.x < 0)

{

direction = 1;

}

}

}

}

Now that the responsibility of the drawing and positioning of the man is moved outside into a separate class, the main game module becomes much more streamlined and much less code. Here is the refactored game code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package com.tutorial3.game;
 
import com.badlogic.gdx.ApplicationAdapter;
 
import com.badlogic.gdx.Gdx;
 
import com.badlogic.gdx.graphics.GL20;
 
import com.badlogic.gdx.graphics.Texture;
 
import com.badlogic.gdx.graphics.g2d.Animation;
 
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
 
import com.badlogic.gdx.graphics.g2d.TextureRegion;
 
import com.badlogic.gdx.math.Affine2;
 
import com.tutorial3.game.Sprites.Man;
 
import com.tutorial3.game.Sprites.PointBall;
 
import com.tutorial3.game.Text.DisplayText;
 
public class Tutorial3Game extends ApplicationAdapter {
 
private static final int FRAME_COLS = 6 ;
 
SpriteBatch batch;
 
Texture img;
 
Animation manWalkingAnimation;
 
private Texture manWalking;
 
private TextureRegion[] manWalkingFrames;
 
private float stateTime;
 
private TextureRegion currentFrame;
 
private int screenWidth;
 
private int screenHeight;
 
private Man man;
 
private PointBall ball;
 
private DisplayText score;
 
@Override
 
public void create () {
 
batch = new SpriteBatch();
 
manWalking = new Texture("manwalking.png");
 
screenWidth = Gdx.graphics.getWidth();
 
screenHeight = Gdx.graphics.getHeight();
 
CreateManWalkingAnimation();
 
ball = new PointBall("ball.png", screenWidth, screenHeight );
 
score = new DisplayText("0", 0,screenHeight - 50);
 
}
 
private void CreateManWalkingAnimation() {
 
TextureRegion[][] textureRegions = TextureRegion.split(manWalking, manWalking.getWidth() / FRAME_COLS, manWalking.getHeight()); // #10
 
manWalkingFrames = new TextureRegion[FRAME_COLS ];
 
int index = 0;
 
for (int j = 0; j < FRAME_COLS; j++) {
 
manWalkingFrames[index++] = textureRegions[0][j];
 
}
 
manWalkingAnimation = new Animation(0.20f, manWalkingFrames);
 
man = new Man(screenWidth, screenHeight, 0, 50);
 
}
 
@Override
 
public void render () {
 
Gdx.gl.glClearColor(1, 0, 0, 1);
 
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
 
stateTime += Gdx.graphics.getDeltaTime(); // #15
 
currentFrame = manWalkingAnimation.getKeyFrame(stateTime, true); // #16
 
batch.begin();
 
ball.Draw(batch);
 
man.Draw(batch, currentFrame);
 
score.Draw(batch);
 
man.Update();
 
ball.Update();
 
batch.end();
 
}
 
}
package com.tutorial3.game;

import com.badlogic.gdx.ApplicationAdapter;

import com.badlogic.gdx.Gdx;

import com.badlogic.gdx.graphics.GL20;

import com.badlogic.gdx.graphics.Texture;

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

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

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

import com.badlogic.gdx.math.Affine2;

import com.tutorial3.game.Sprites.Man;

import com.tutorial3.game.Sprites.PointBall;

import com.tutorial3.game.Text.DisplayText;

public class Tutorial3Game extends ApplicationAdapter {

private static final int FRAME_COLS = 6 ;

SpriteBatch batch;

Texture img;

Animation manWalkingAnimation;

private Texture manWalking;

private TextureRegion[] manWalkingFrames;

private float stateTime;

private TextureRegion currentFrame;

private int screenWidth;

private int screenHeight;

private Man man;

private PointBall ball;

private DisplayText score;

@Override

public void create () {

batch = new SpriteBatch();

manWalking = new Texture("manwalking.png");

screenWidth = Gdx.graphics.getWidth();

screenHeight = Gdx.graphics.getHeight();

CreateManWalkingAnimation();

ball = new PointBall("ball.png", screenWidth, screenHeight );

score = new DisplayText("0", 0,screenHeight - 50);

}

private void CreateManWalkingAnimation() {

TextureRegion[][] textureRegions = TextureRegion.split(manWalking, manWalking.getWidth() / FRAME_COLS, manWalking.getHeight()); // #10

manWalkingFrames = new TextureRegion[FRAME_COLS ];

int index = 0;

for (int j = 0; j < FRAME_COLS; j++) {

manWalkingFrames[index++] = textureRegions[0][j];

}

manWalkingAnimation = new Animation(0.20f, manWalkingFrames);

man = new Man(screenWidth, screenHeight, 0, 50);

}

@Override

public void render () {

Gdx.gl.glClearColor(1, 0, 0, 1);

Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

stateTime += Gdx.graphics.getDeltaTime(); // #15

currentFrame = manWalkingAnimation.getKeyFrame(stateTime, true); // #16

batch.begin();

ball.Draw(batch);

man.Draw(batch, currentFrame);

score.Draw(batch);

man.Update();

ball.Update();

batch.end();

}

}

Drawing Text in GDX
Unfortunately, drawing text isn’t as straightforward as you would think it would be in GDX. The way to draw text in your game is to use a BitmapFont.  This, alone, however, doesn’t give you easy access to all the true type fonts on your system.  So the recommended way to draw fonts is to use something called a FreeTypeFontGenerator.  In order to access this class, you needed to have installed FreeType when you ran your gdx-setup project shown in the figure below:

gdx-setup
.

If you didn’t install it, you can still access it, by adding the following to your android gradle file as a dependency:

compile “com.badlogicgames.gdx:gdx-freetype:$gdxVersion

 

You will also need to add all the different .so files to your lib directories from the nightly build. I ended up copying three libraries from the nightly build extensions/gdx-freetype directory to my lib subdirectories: armeabi, armeabi-v7a, and x86.

In order to easily utilize creating text objects in my game, I created a class called DisplayText below which utilizes the FreeType extension to create BitmapFonts from TTF fonts. I also added the BRITANIC.TTF font to my assets folder so the class can access it.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.tutorial3.game.Text;
 
import com.badlogic.gdx.Gdx;
 
import com.badlogic.gdx.files.FileHandle;
 
import com.badlogic.gdx.graphics.Color;
 
import com.badlogic.gdx.graphics.g2d.Batch;
 
import com.badlogic.gdx.graphics.g2d.BitmapFont;
 
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
 
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
 
import com.badlogic.gdx.math.Vector2;
 
/**
 
* Created by jcity_000 on 7/31/2015.
 
*/
 
public class DisplayText {
 
private final String text;
 
private Vector2 position;
 
BitmapFont bitmapFont;
 
public DisplayText(String text, float positionX, float positionY, int fontSize)
 
{
 
this.text = text;
 
position = new Vector2(positionX, positionY);
 
FileHandle handle = Gdx.files.internal("BRITANIC.TTF");
 
try
 
{
 
FreeTypeFontGenerator generator = new FreeTypeFontGenerator(handle);
 
bitmapFont = generator.generateFont(fontSize);
 
generator.dispose();
 
}
 
catch(Exception e) {
 
bitmapFont = new BitmapFont();
 
}
 
bitmapFont.setColor(Color.BLACK);
 
}
 
public void Draw(Batch batch)
 
{
 
bitmapFont.draw(batch,text, position.x, position.y);
 
}
 
}
package com.tutorial3.game.Text;

import com.badlogic.gdx.Gdx;

import com.badlogic.gdx.files.FileHandle;

import com.badlogic.gdx.graphics.Color;

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

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

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

import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;

import com.badlogic.gdx.math.Vector2;

/**

* Created by jcity_000 on 7/31/2015.

*/

public class DisplayText {

private final String text;

private Vector2 position;

BitmapFont bitmapFont;

public DisplayText(String text, float positionX, float positionY, int fontSize)

{

this.text = text;

position = new Vector2(positionX, positionY);

FileHandle handle = Gdx.files.internal("BRITANIC.TTF");

try

{

FreeTypeFontGenerator generator = new FreeTypeFontGenerator(handle);

bitmapFont = generator.generateFont(fontSize);

generator.dispose();

}

catch(Exception e) {

bitmapFont = new BitmapFont();

}

bitmapFont.setColor(Color.BLACK);

}

public void Draw(Batch batch)

{

bitmapFont.draw(batch,text, position.x, position.y);

}

}

Conclusion

We are starting to see the elements of our game come together. By creating different sprites and animations through encapsulated classes, we can quickly add game elements and behaviors for these elements. Adding fonts with GDX, isn’t as easy as I’d like, but this behavior can also be wrapped in a class to make it fairly simple. In the next tutorial, we’ll start to interact with the sprite elements through touch and detect collisions in the game.

Share

Share This Post

Recent Articles

Leave a Reply

*

© 2016 ToDroid. All rights reserved.
Disclaimer: The content on this site is copyrighted. Do not copy the content. The content on this site must not be reproduced anywhere else.