© Lee Stemkoski 2018
Lee StemkoskiJava Game Development with LibGDXhttps://doi.org/10.1007/978-1-4842-3324-5_2

2. The LibGDX Framework

Lee Stemkoski
(1)
DEPT OF MATH & CS, ADELPHI UNIVERSITY DEPT OF MATH & CS, Garden City, New York, USA
 
This chapter will introduce many of the major features of the LibGDX library . It will illustrate how to use them in the process of creating a game called Starfish Collector , where you help the player’s character, a turtle, swim around the ocean floor while looking for a starfish. A screenshot of this game in action appears in Figure 2-1. At first, you will create a basic, functional game. Following a motivational discussion of object-oriented design principles, you will rewrite parts of this project using some of the LibGDX classes to improve the organization of the code. Future chapters will revisit this example and use it as a basis to introduce new game-design principles and features of LibGDX.
A352797_2_En_2_Fig1_HTML.jpg
Figure 2-1.
The main screen for the game Starfish Collector

The Life Cycle of a Video Game

Before jumping into the programming aspect of game development, it is important to understand the overall structure of a game program: the major stages that a game program progresses through and the tasks that a game program must perform in each stage. The stages are as follows:
  • Startup: During this stage, any files needed (such as images or sounds) are loaded, game objects are created, and values are initialized.
  • The game loop: A stage that repeats continuously while the game is running and that consists of the following three sub-stages:
  • Process input : The program checks to see if the user has performed any action that sends data to the computer: pressing keyboard keys, moving the mouse or clicking mouse buttons, touching or swiping on a touchscreen, or pressing joysticks or buttons on a game pad.
  • Update : Performs tasks that involve the state of the game world and the entities within it. This could include changing the positions of entities based on user input or physics simulations, performing collision detection to determine when two entities come in contact with each other and what action to perform in response, or selecting actions for nonplayer characters.
  • Render : Draws all graphics on the screen, such as background images, game-world entities, and the user interface (which typically overlays the game world).
  • Shutdown: This stage begins when the player provides input to the computer indicating that he is finished using the software (for example, by clicking a Quit button) and may involve removing images or data from memory, saving player data or the game state, signaling the computer to stop monitoring hardware devices for user input, and closing any windows that were created by the game.
The flowchart in Figure 2-2 illustrates the order in which these stages occur.
A352797_2_En_2_Fig2_HTML.jpg
Figure 2-2.
The stages of a game program
Some game developers may include additional stages in the game loop, such as the following:
  • A sleep stage that pauses the execution of the program for a given amount of time. Many game developers aim to write programs that can run at 60 frames per second (FPS), meaning that the game loop will run once every 16.67 milliseconds.1 If the game loop can run faster than this, the program can be instructed to pause for whatever amount of time remains in the 16.67-millisecond interval, thus freeing up the CPU for any other applications that may be running in the background. LibGDX automatically handles this for us, so we won’t worry about including it here.
  • An audio stage, where any background music is streamed or sound effects are played. In this book, we will consider playing audio as part of the update stage, and we will discuss how to accomplish this in a later chapter.
Most of these stages are handled by a corresponding method in LibGDX. For example, the startup stage is carried out by a method named create, the update and render stages are both handled by the render method,2 and any shutdown actions are performed by a method named dispose.
In fact, when your driver class creates any kind of application (such as a LwjglApplication ), the application will work correctly only if given an object that contains a certain set of methods (including create, render, and dispose); this is a necessary convention so that the application knows what to do during each stage of the game program’s life cycle. You are able to enforce such requirements in Java programs by using interfaces.
Interfaces
Informally, you can think of an interface as a kind of contract that other classes can promise to fulfill. As a simple example, let’s say that you write a Player class, which contains a method named talkTo that is used to interact with objects in your environment. The talkTo method takes a single input, called creature, and in the code that follows, you have
creature.speak();
For the talkTo method to work correctly, whatever type of object that creature is an instance of, it must have a method named speak. Maybe sometimes creature is an instance of a Person class, while at other times creature is an instance of a Monster class. In general, you would like the talkTo method to be as inclusive as possible—any object with a speak method should be permitted as input. You can specify this behavior by using interfaces.
First, you create an interface as follows:
public interface Speaker
{
    public void speak();
}
At first glance, an interface appears similar to a class, except that the methods are only declared; they do not contain any actual code. All that is required is the signature of the method: the name, the output type, the input types (if any), and any modifiers, such as public. This information is followed by a semicolon instead of the familiar set of braces that encompass code. The classes that implement this interface will provide the code for their version of the speak function. It is important to emphasize that since Speaker is not a class, you cannot create an instance of a Speaker object; instead, you write other classes that include the methods as specified in the Speaker interface.
A class indicates that it meets the requirements of an interface (that it contains all the indicated fields and methods) by including the keyword implements, followed by the name of the interface, after the name of the class. Any class that implements the Speaker interface must provide the code for its version of the speak function. The following demonstrates with a class called Person and a class called Monster:
public class Person implements Speaker
{
    // additional code above
    public void speak()
    {   System.out.println( "Hello." );  }
    // additional code below
}
public class Monster implements Speaker
{
    // additional code above
    public void speak()
    {  System.out.println("Grrr!");  }
    // additional code below
}
Always remember: When implementing an interface, you must write methods for everything declared in the interface; otherwise, there will be a compile-time error. You could even write a method that contains no code between the braces, as shown next (for a class that represents a particularly untalkative piece of furniture). This can be convenient when you need to use only part of the functionality of the interface.
public class Chair implements Speaker
{
    // additional code above
    public void speak()  { }
    // additional code below
}
Finally, you write the method talkTo so that it takes a Speaker as input:
public class Player
{
        // additional code above
        public void talkTo(Speaker creature)
        {
                creature.speak();
        }
        // additional code below
}
Any class that implements the Speaker interface may be used as input for a Player object’s talkTo method. For example, we present some code that creates instances of each of these classes, and then we describe the results in the accompanying comments:
Player dan = new Player();
Person chris = new Person();
Monster grez = new Monster();
Chair footstool = new Chair();
dan.talkTo(chris); // prints "Hello."
dan.talkTo(grez); // prints "Grrr!"
dan.talkTo(footstool); // does not print anything
An application in LibGDX requires user-created classes to implement the ApplicationListener interface so that it can handle all stages of a game program’s life cycle. You may recall, however, that in our example from Chapter 1, the HelloWorldImage class did not implement the ApplicationListener class; it only extended the Game class. Why didn’t this result in an error when the class was compiled? If you take a look “under the hood” (which, in the context of computer programming, typically means to inspect the source code3), you’ll notice that the Game class itself implements the ApplicationListener class and includes “empty” versions of the functions; there is no code between the braces that define the body of each function. This enables you to write only variations of the interface methods that you need to use in the class that extends the Game class, which will then override the versions in the Game class; any interface method that you don’t write will default to the empty version in the Game class. (In fact, the ApplicationListener interface requires a total of six methods: create, render, resize, pause, resume, and dispose; in our example, you wrote only two of these.)

Game Project: Starfish Collector

This section will introduce the game Starfish Collector , previously shown in Figure 2-1. Before starting to write the code for this game, it is helpful to precisely describe its features: the game mechanics and rules, how the player interacts with the software, the graphics that will be required, and so forth. Such a description is called a game-design document and is explained more fully in Appendix A of this book. Since this is the first game you will be creating with LibGDX, only the following minimal set of features will be created:
  • The player will control a turtle, whose goal is to collect a single starfish.
  • Movement is controlled by the arrow keys. The up arrow key moves the turtle toward the top of the screen, the right arrow key moves the turtle toward the right side of the screen, and so on. Multiple arrow keys can be pressed at the same time to move the turtle in diagonal directions. Movement speed is constant .
  • The turtle collects the starfish by coming into contact with it (when their graphics overlap). When this happens, the starfish disappears, and a message that reads “You Win” appears.
  • The graphics required by this game include images of a turtle, a starfish, water, and a message that contains the text “You Win.”
One version of the code that accomplishes these tasks is presented next. Some of the code and concepts will be familiar from the HelloWorldImage example, such as the Texture and SpriteBatch classes , the purpose of the create and render methods, and the role of the driver class. There are a few new additions as well. Since the coordinates of the turtle may change, you use variables to store these values. Most significantly, you introduce some code that makes our program interactive—you will process keyboard input from the user. Finally, you’ll include a Boolean variable that keeps track of whether the player has won, which becomes true when the turtle reaches the starfish and affects when the “You Win” message is displayed on the screen.
In this section, as well as the sections that follow, you are invited to create a new project in BlueJ (after closing the previous project by selecting Close from the BlueJ Project menu) and enter the code that is presented, or, alternatively, to simply download the source code from the website for this book and run the code via the included BlueJ project files. The online source code also contains all the images that you will need, stored in the assets folder in each project, and referenced in the following code.
To begin, download the source code files for this chapter on the website for this book. Create a new project in BlueJ named Starfish Collector Ch2 (since there will be many versions of this project created throughout this book). In the project directory that is created by BlueJ, create a new folder called assets. Copy the image files from the downloaded project's assets folder into your new project's assets folder; keeping the source code separate from the images in this manner will help keep your files organized. Next, in your project directory, create a new folder named +libs. Copy the JAR files from the downloaded project’s +libs folder into your new project’s +libs folder. Restart BlueJ so that the JAR files newly added to the +libs folder are properly recognized by BlueJ.
In your BlueJ project, create a new class called StarfishCollectorAlpha . The source code for this class appears next. There are new import statements that enable you to create a variety of new objects, which are also explained in what follows:
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.Game;
public class StarfishCollectorAlpha extends Game
{
    private SpriteBatch batch;
    private Texture turtleTexture;
    private float turtleX;
    private float turtleY;
    private Rectangle turtleRectangle;
    private Texture starfishTexture;
    private float starfishX;
    private float starfishY;
    private Rectangle starfishRectangle;
    private Texture oceanTexture;
    private Texture winMessageTexture;
    private boolean win;
    public void create()
    {
        batch = new SpriteBatch();
        turtleTexture = new Texture( Gdx.files.internal("assets/turtle-1.png") );
        turtleX = 20;
        turtleY = 20;
        turtleRectangle = new Rectangle( turtleX, turtleY,
            turtleTexture.getWidth(), turtleTexture.getHeight() );
        starfishTexture = new Texture( Gdx.files.internal("assets/starfish.png") );
        starfishX = 380;
        starfishY = 380;
        starfishRectangle = new Rectangle( starfishX, starfishY,
            starfishTexture.getWidth(), starfishTexture.getHeight() );
        oceanTexture = new Texture( Gdx.files.internal("assets/water.jpg") );
        winMessageTexture = new Texture( Gdx.files.internal("assets/you-win.png") );
        win = false;
    }
    public void render()
    {
         // check user input
        if (Gdx.input.isKeyPressed(Keys.LEFT))
            turtleX--;
        if (Gdx.input.isKeyPressed(Keys.RIGHT))
            turtleX++;
        if (Gdx.input.isKeyPressed(Keys.UP))
            turtleY++;
        if (Gdx.input.isKeyPressed(Keys.DOWN))
            turtleY--;
        // update turtle rectangle location
        turtleRectangle.setPosition(turtleX, turtleY);
        // check win condition: turtle must be overlapping starfish
        if (turtleRectangle.overlaps(starfishRectangle))
            win = true;
        // clear screen
        Gdx.gl.glClearColor(0,0,0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        // draw graphics
        batch.begin();
        batch.draw( oceanTexture, 0, 0 );
        if (!win)
            batch.draw( starfishTexture, starfishX, starfishY );
        batch.draw( turtleTexture, turtleX, turtleY );
        if (win)
            batch.draw( winMessageTexture, 180, 180 );
        batch.end();
    }
}
At this point, you can compile the code; if any error messages appear, double-check that the code you entered precisely matches the preceding code. You will also need a launcher-style class to create an instance of this class and run it. To accomplish this, create a new class named LauncherAlpha and enter the code as follows. Notice that additional parameters have been included in the LwglApplication constructor to set the title that is displayed and the size (the width and height, in pixels) of the window.
import com.badlogic.gdx.Game;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
public class LauncherAlpha
{
    public static void main (String[] args)
    {
        Game myGame = new StarfishCollectorAlpha();
        LwjglApplication launcher =
            new LwjglApplication( myGame, "Starfish Collector", 800, 600 );
    }
}
At this point, the code can be compiled, after which the game can be run from the main BlueJ window by right-clicking the orange rectangle labeled LauncherAlpha, selecting the main method, and clicking the OK button in the window that appears (similar to the process for running the “Hello, World!” program from Chapter 1).
Note
When you are writing code, if there is a runtime error (such as an image file’s failing to load due to a misspelled filename, even after fixing the error and running the program again), BlueJ may report another error containing a message such as “No OpenGL context found in the current thread.” This is a highly technical error with a simple fix: in the BlueJ main window Tools menu, select the option Reset Java Virtual Machine and try to run the main method again. Provided that there are no additional runtime errors, your program should load as expected.
In the class StarfishCollectorAlpha, the create method initializes variables and loads textures. This program contains four images, which are stored as Texture objects: the turtle, the starfish, the water, and an image containing the words You Win. For brevity, instead of creating a new variable to store each of the FileHandle objects created by the internal method, you initialize them in the same line where you construct each new Texture object. The coordinates of the turtle’s position are stored using floating-point numbers since you need to store decimal values, and the LibGDX game development framework uses float rather than double variables in its classes for a slight increase in program efficiency. Even though the coordinates of the starfish texture will not be changing, you still store them using variables so that future code involving these values is more readable. The oceanTexture and winMessageTexture objects do not require variables to store their coordinates, as their positions will not be changing, and their positions will be specified in the render method (discussed later in this section). Rectangle objects are also created that correspond to the turtle and starfish; these store the position and size of a rectangular area and are used because the Rectangle class contains a method named overlap that can be used to detect when the turtle has reached the starfish. The Boolean variable win indicates whether the player has won the game and is initialized to false.
The render method contains three main blocks of code that roughly correspond to the game loop sub-stages: process input, update, and render.
First, a sequence of commands use a method named isKeyPressed, belonging to (an object belonging to) the Gdx class, which determines whether a key on the keyboard is currently being pressed. The names of each key are represented using constant values from the Keys class. When one of the arrow keys is pressed, the corresponding x or y coordinate of the turtle is adjusted accordingly; x values increase toward the right side of the window, while y values increase toward the top of the window.4 Note that if the user presses the left and right arrow keys at the same time, the effects of the addition and subtraction cancel each other out, and the position of the turtle will not change; a similar situation also applies when the user presses the up and down arrow keys at the same time. Also note that a sequence of if statements is used rather than a sequence of if-else if statements, enabling the program to respond appropriately if two keys are being pressed at the same time.
The second set of commands performs collision detection, determining whether the rectangular region corresponding to the turtleTexture image overlaps the rectangular region containing starfishTexture . The turtle’s rectangle must be updated (using the setPosition method), since the position of the turtle may have changed. If the two rectangles overlap, then the Boolean variable win is set to true, which in turn affects which textures are drawn to the screen. As you test this game, you might notice that the starfish seem to disappear before the turtle actually reaches it. This is because rectangular shapes are being used to check for overlap; some of the transparent parts of the images may be overlapping, as shown in Figure 2-3. (This situation will be addressed and improved upon in the next chapter.)
A352797_2_En_2_Fig3_HTML.jpg
Figure 2-3.
Overlapping rectangles in transparent areas
Finally, the third set of commands in the render method is where the actual rendering takes place. The glClear method draws a solid-colored rectangle on the screen using the color specified in the glClearColor method (in terms of red/green/blue/alpha values). The screen must be cleared in this manner during every rendering pass, effectively “erasing” the screen, because the images from previous render calls might be visible otherwise. The order of the draw method calls is particularly important: textures that are rendered later appear on top of those rendered earlier. Thus, you typically want to draw the background elements first, followed by the main in-game entities, and then the user interface elements last. The Batch class, used for drawing, optimizes graphics operations by sending multiple images at once to the computer’s graphics processing unit (GPU).

Managing Game Entities with Actors and Stages

In the previous example—our first iteration of the Starfish Collector game—you saw that each game entity (such as the turtle and the starfish) has a lot of related information that you need to keep track of, such as textures and (x,y) coordinates. A central design principle in an object-oriented programming language like Java is to encapsulate related information in a single class. While you could create a Turtle class, a Starfish class, and so forth to manage this information, this approach would result in a lot of redundancy in your program, which is both inefficient and difficult to manage. Since another guiding principle in software engineering is to write reusable code, you want to implement a single class that contains the basic information common to all game entities, which you can then extend when necessary.
The LibGDX libraries provide a few different classes that can be used to manage the data associated with a game object; you will use the LibGDX class Actor as a foundation. This class stores data such as position, size (width and height), rotation, scaling factors, and more. However, it does not include a variable to store an image. This seeming “omission” in fact gives you greater flexibility in allowing you to specify how the object will be represented. You can extend the Actor class and include any additional information you need in your game, such as collision shapes, one or more images or animations, custom rendering methods, and more. The constructor for such an object should call the constructor of the class that it extends; this is accomplished by including the statement super(). For example, a game object that just requires a single Texture could use the following code:
public class TexturedActor extends Actor
{
    private Texture image;
    // constructor
    public TexturedActor()
    {  super();  }
    public void setTexture(Texture t)
    {  image = t;  }
    public Texture getTexture()
    {  return image;  }
    public void draw(Batch b)
    {
        b.draw( getTexture(), getX(), getY() );
    }
}
For a more complex example, your game might feature a character that has health points, and you might want to use different textures depending on how much health the character has. Such an object could be created with the following code:
public class HealthyActor extends Actor
{
    private int HP;
    private Texture healthyImage;
    private Texture damagedImage;
    private Texture deceasedImage;
    // omitted: constructor
    // omitted: methods to get/set preceding fields
    public void draw(Batch b)
    {
        if (HP > 50)
            b.draw( healthyImage, getX(), getY() );
        else if (HP > 0 && HP <= 50)
            b.draw( damagedImage, getX(), getY() );
        else    // in this case, HP <= 0
            b.draw( deceasedImage, getX(), getY() );
    }
}
There are a few additional features of the default Actor class that should be mentioned here. First, in addition to a draw method, the Actor class has an act method that can serve to update or modify the state of an Actor. Second, the Actor class was designed to be used in concert with a class called Stage (which you will be using in the near future). The main role of the Stage class is to store a list of Actor instances; it also contains methods (named act and draw) that call the act and draw methods of every Actor that has been added to it, which frees you from having to remember to draw every individual Actor instance yourself. The Stage class also creates and handles a Batch object, which reduces the amount of code that you need to write.
The next goal is to write an extension of the Actor class; this will require accessing and overriding methods already present in the Actor class.
Overriding Methods And Accessing Superclass Methods
When extending a class in Java, you may want to replace a method of the base class with a new version of the method. For example, consider a class that represents a person, with a method that simulates the person saying hello:
public class Person
{
    public void sayHello()
    {  System.out.print("Hello.");  }
    // other methods omitted
}
In your project, you may want to create extensions of this class, keeping the sayHello method, but changing the message that is printed. To do so, you only need to write a method with the same signature: the same method name, parameters, and output type. For example, you could write:
public class Shopkeeper extends Person
{
    public void sayHello()
    {  System.out.print("Greetings, travelers. Do you need any supplies?");  }
}
If you create an instance of a Shopkeeper, it will have access to all the fields and methods from the Person class (since Shopkeeper extends the Person class), but if a Shopkeeper instance calls the sayHello method, the version in the Shopkeeper class takes precedence and will be the one that is called. This technique is called overriding a method.
In some situations, however, you will want to build upon (and not replace) the code from the class that is being extended (sometimes called the superclass). For example, once again you might have a class that represents a person that stores a value (HP) corresponding to their health, as well as a method that restores it to its maximum value, as follows:
public class Person
{
    int HP;
    int maxHP;
    public void restore()
    {
        HP = maxHP;
    }
}
You might also have a class representing a wizard, which is a person that also has magical abilities and thus a value corresponding to the amount of magic (MP) they have available for use. It is natural to extend the Person class, and for a wizard the restore method should set both HP and MP to their maximum values. In this situation, you need the Wizard class to override the restore method of the Person class so as to add the magic-related functionality, while at the same time you want the Wizard class to be able to also run the restore method from the Person class to avoid your having to re-type the corresponding code. The way this is accomplished is by using the keyword super, which refers to the superclass. Within the context of the Wizard class, the expression restore() activates the method present in the Wizard class, while the expression super.restore() activates the method present in the Person class, as desired.
Making use of super, the Wizard class can be written as follows:
public class Wizard extends Person
{
    int MP;
    int maxMP;
    public void restore()
    {
        super.restore();
        MP = maxMP;
    }
}
As you write new classes that extend and build upon the functionality and methods of other classes, you will have the opportunity to use this technique repeatedly.
The extension of the Actor class that will be created for use in this chapter is called ActorBeta. It will be used to store a TextureRegion (which builds upon the functionality of the Texture class) and a Rectangle (used for detecting overlap between two objects). In the same BlueJ project, create a new class called ActorBeta with the following code:
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.Rectangle;
/**
 *  Extend the Actor class to include graphics and collision detection.
 *  Actor class stores data such as position and rotation.
 */
public class ActorBeta extends Actor
{
    private TextureRegion textureRegion;
    private Rectangle rectangle;
    public ActorBeta()
    {
        super();
        textureRegion = new TextureRegion();
        rectangle = new Rectangle();
    }
    public void setTexture(Texture t)
    {
        textureRegion.setRegion(t);
        setSize( t.getWidth(), t.getHeight() );
        rectangle.setSize( t.getWidth(), t.getHeight() );
    }
    public Rectangle getRectangle()
    {
        rectangle.setPosition( getX(), getY() );
        return rectangle;
    }
    public boolean overlaps(ActorBeta other)
    {
        return this.getRectangle().overlaps( other.getRectangle() );
    }
    public void act(float dt)
    {
        super.act(dt);
    }
    public void draw(Batch batch, float parentAlpha)
    {
        super.draw( batch, parentAlpha );
        Color c = getColor(); // used to apply tint color effect
        batch.setColor(c.r, c.g, c.b, c.a);
        if ( isVisible() )
            batch.draw( textureRegion,
                getX(), getY(), getOriginX(), getOriginY(),
                getWidth(), getHeight(), getScaleX(), getScaleY(), getRotation() );
    }
}
The following are some observations about this code:
  • Instead of using a Texture, you are using a TextureRegion to store your image, which will enable greater flexibility when rendering an image. The main difference between these classes is that a TextureRegion can be used to store a Texture that contains multiple images or animation frames, and a TextureRegion also stores coordinates, called (u,v) coordinates, that determine which rectangular sub-area of the Texture is to be used.
  • Within the constructor of the ActorBeta class, the statement super() activates the constructor of the superclass (Actor), which initializes the data structures used by that class.
  • In the setTexture method , the sizes of the actor and the rectangle also need to be set (based on the width and height of the texture) so that the draw and overlaps methods work correctly.
  • In the overlaps method , the keyword this is used to indicate the instance of the object calling the method. In this case, it is being used for clarity (to distinguish it from the instance named other that is a parameter for the method); using this is not strictly necessary and could be omitted if desired.
  • Both the act and the draw methods begin by calling the corresponding superclass method (super.act and super.draw) so that their functionality is not lost, even though the methods are overridden in this class.
  • In the draw method , if a color has been set (via the setColor method), it can be used to tint the image when it is rendered and must be passed along to the Batch object, which handles the actual rendering. (The default color is white, which has no effect on the image.) In addition, the Batch object can make use of the data stored in the actor object (such as position, rotation, and scaling factors) when rendering the image, although most of this functionality isn’t used in this project.
  • Occasionally, you will want to write even more specialized extensions of these classes. For this project, the turtle requires even more functionality than what is provided by the ActorBeta class . In particular, the turtle should move depending on which of the arrow keys are currently being pressed. In the interest of good object-oriented design practices (in particular, encapsulation), the corresponding code from the update method of the StarfishCollectorAlpha class will be moved to the act method of a new Turtle class. Also, notice the use of the moveBy method (inherited from the Actor class), which adjusts the position of the turtle in the same way as before, in terms of x and y coordinates.
Next, you will build upon the functionality of the ActorBeta class. Create a new class named Turtle with the following code:
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
public class Turtle extends ActorBeta
{
    public Turtle()
    {
        super();
    }
    public void act(float dt)
    {
        super.act(dt);
        if (Gdx.input.isKeyPressed(Keys.LEFT))
           this.moveBy(-1,0);
        if (Gdx.input.isKeyPressed(Keys.RIGHT))
            this.moveBy(1,0);
        if (Gdx.input.isKeyPressed(Keys.UP))
            this.moveBy(0,1);
        if (Gdx.input.isKeyPressed(Keys.DOWN))
            this.moveBy(0,-1);
    }
}
  • After entering this code, you can compile it to check that everything was entered correctly. Next, you will create the new version of the Starfish Collector game source code. Create a new class named StarfishCollectorBeta; it will use the new ActorBeta class throughout. There are a few changes from the alpha version of the class. In particular:
    • A Stage object will be created, and Actor objects will be added to it. In addition, the act and draw methods of the Stage must be called (recall that calling the act and draw methods on a Stage results in the Stage object’s calling the act and draw methods of all the Actor objects that have been added to it).
  • The order in which actors are added to the Stage is important, just as the ordering of the draw statements was important when using the Batch object in the alpha version of this program. Actor objects that are added to the Stage first will be drawn first, thus appearing below those that are added later. For this reason, the background should be added first, and user interface elements (such as text displays) should be added last.
  • The initial visibility of winMessage is set to false, because the player should not be able to see that particular image until later, after they have won the game.
  • The act method requires an input: the amount of time (in seconds) that has elapsed since the previous iteration of the game loop. Since games in LibGDX run at 60 frames per second by default whenever possible, we have used 1/60 for this value. (In practice, the amount of time required for each iteration of the game loop could fluctuate, and there are methods available to obtain the exact value, but they are not necessary here.)
  • When the turtle overlaps the starfish, the remove method is called on the starfish. This removes the starfish from the Stage object to which it was previously added. As a result, the starfish object’s act and draw methods are no longer called at that point, causing the starfish graphic to disappear from the screen. (However, even though it is no longer visible, the starfish object remains in computer memory.)
The code for the StarfishCollectorBeta class is as follows:
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.scenes.scene2d.Stage;
public class StarfishCollectorBeta extends Game
{
    private Turtle turtle;
    private ActorBeta starfish;
    private ActorBeta ocean;
    private ActorBeta winMessage;
    private Stage mainStage;
    private boolean win;
    public void create()
    {
        mainStage = new Stage();
        ocean = new ActorBeta();
        ocean.setTexture( new Texture( Gdx.files.internal("assets/water.jpg") )  );
        mainStage.addActor(ocean);
        starfish = new ActorBeta();
        starfish.setTexture( new Texture(Gdx.files.internal("assets/starfish.png")) );
        starfish.setPosition( 380,380 );
        mainStage.addActor( starfish );
        turtle = new Turtle();
        turtle.setTexture( new Texture(Gdx.files.internal("assets/turtle-1.png")) );
        turtle.setPosition( 20,20 );
        mainStage.addActor( turtle );
        winMessage = new ActorBeta();
        winMessage.setTexture( new Texture(Gdx.files.internal("assets/you-win.png")) );
        winMessage.setPosition( 180,180 );
        winMessage.setVisible( false );
        mainStage.addActor( winMessage );
        win = false;
    }
    public void render()
    {
        // check user input
        mainStage.act(1/60f);
        // check win condition: turtle must be overlapping starfish
        if (turtle.overlaps(starfish))
        {
            starfish.remove();
            winMessage.setVisible(true);
        }
        // clear screen
        Gdx.gl.glClearColor(0,0,0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        // draw graphics
        mainStage.draw();
    }
}
These changes make the code simpler to understand and will make it easier to incorporate additional elements in the future. At this point, you should compile the code you have written to verify that it is error-free. In order to run this game, create a new launcher class called LauncherBeta with the following code:
import com.badlogic.gdx.Game;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
public class LauncherBeta
{
    public static void main (String[] args)
    {
        Game myGame = new StarfishCollectorBeta();
        LwjglApplication launcher =
            new LwjglApplication( myGame, "Starfish Collector", 800, 600 );
    }
}
In the rest of the chapter, you will reorganize this code in accordance with programming design principles.

Reorganizing the Game Flow

An important programming design practice is that each method should correspond to a single task. If a single method is handling multiple tasks, then it should be broken up into multiple methods. In the first section of this chapter, when the life cycle of a video game was discussed, you learned that in LibGDX, the render method is the method that is called repeatedly as the game is running, and therefore all the code related to the game loop is contained in that method. However, the game loop has multiple tasks: processing input, updating the state of the game world (such as determining what happens when two objects overlap), and drawing the graphics to the screen. These tasks should be separated into different methods if possible. Using the act method of the Actor class to process the input that affects each actor is a step in the right direction.
The next step is to separate the code used for updating and the code used for rendering into different methods. At the same time, you can also streamline future development projects by including commonly used code in a base class that can be extended when necessary, as you previously did with the Actor-derived classes. In particular, all future game projects will need to declare and initialize a Stage object and run its act and draw methods. You can use the method getDeltaTime to determine how much time has elapsed since the previous iteration of the game loop (which is typically 1/60 of a second, as discussed previously, but may fluctuate depending on the computer being used and the complexity of the program). Also, you typically clear the screen before redrawing the graphics in each iteration of the game loop.5
To accomplish these tasks, create a new class named GameBeta as shown next. However, there will be a few modifications made to this class later on in the chapter for reasons that will be explained.
import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.scenes.scene2d.Stage;
public class GameBeta extends Game
{
    protected Stage mainStage;
    public void create()
    {
        mainStage = new Stage();
        initialize();
    }
    public void initialize() {  }
    public void render()
    {
        float dt = Gdx.graphics.getDeltaTime();
        // act method
        mainStage.act(dt);
        // defined by user
        update(dt);
        // clear the screen
        Gdx.gl.glClearColor(0,0,0,1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        // draw the graphics
        mainStage.draw();
    }
    public void update(float dt) {  }
}
With this code in place, the StarfishCollectorBeta class could be changed to extend the GameBeta class. In the StarfishCollectorBeta class, the methods create and render would be changed to initialize and update (overriding the corresponding methods from the GameBeta class), and the redundant lines of code from the StarfishCollectorBeta class (those that also appear in the GameBeta class) would be removed.
However, before continuing on, there is one final topic that needs to be discussed to make this code more robust. In practice, any program that uses this class as a basis should be required to provide its own versions of the initialize and update methods. This brings to mind the concept of an interface (discussed earlier in this chapter), but an interface is not suitable in this situation because an interface can only declare the required method, and does not include any actual code. In this situation, there is code that would be helpful to reuse. What you need is a structure that combines elements of both an interface and a standard class that can be extended. Java provides exactly what is needed for this situation: abstract classes .
Abstract Classes
Often in programming you will want to reduce redundant code by refactoring repeated features in a base class and then extending that class with specialized subclasses. In some situations, you will also know that all of the extending classes will need to implement a particular method—but they will all do it in a different way, so the code can’t be written ahead of time in the base class, although the method needs to be declared in the base class.
For example, consider a fantasy-style role-playing game. Assume that the base class for player characters is named Person. This class will likely contain some standard fields and methods that all Person objects should have, such as a String called name that stores the name of the character, and get and set methods to access this information. You might want to create two classes, Wizard and Warrior, which extend the Person class. Assume that the user interface of this game is consistent for all types of characters and that it contains a Sword button that activates a method named useSword and a Spell button that activates a method named useSpell.
Although the Person class will declare these methods, their implementation will differ greatly in the Wizard and Warrior classes. Traditionally, warriors wield swords, and wizards do not; wizards cast magic spells, and warriors do not. Other types of characters might be imagined that can use both swords and spells, or characters that can use neither. Regardless, if the player clicks a button corresponding to an action that a character is unable to perform, a message should display onscreen explaining this; otherwise, the action should take place as expected.
In this situation, you will want to require extensions of the Person class in order to provide customized code to implement the methods useSword and useSpell, as an interface does; however, Person cannot be an interface, because it provides code for some of its methods, such as getting and setting the name field. In an interface, methods are only declared, not written.
The solution to this scenario is to create an abstract class. You declare the method as a class with the additional keyword abstract (similar to the way an interface is declared using the keyword interface), which indicates that some methods may be declared with the code being provided, and that other classes that extend this class must provide the code for such methods. Any such method is also declared using the modifier abstract, as shown in the example code that follows. Note that since not all of the code for this class is provided, you cannot create an instance of this class (again, similar to an interface).
public abstract class Person
{
    private String name;
    public void setName(String n)  { name = n; }
    public String getName()  { return name; }
    public abstract void useSword();
    public abstract void useSpell();
}
Any class that extends Person must provide an implementation of each method that was previously declared as abstract. For example:
public class Wizard extends Person
{
    public void useSword()
    {
        System.out.print("Wizards are unable to wield a sword...");
    }
    public void useSpell()
    {
        // insert code here to select a spell and its target
    }
}
public class Warrior extends Person
{
    public void useSword()
    {
        // insert code here to attack an enemy
    }
    public void useSpell()
    {
        System.out.print("Warriors are unable to use magic...");
    }
}
In this way, an abstract class combines the advantages of a standard class and an interface.
Using an abstract class will require any class that extends GameBeta to provide implementations of methods to initialize and update the game world. This also helps developers avoid potential mistakes such as misspelling the names of the methods that they are supposed to be overriding.
The changes that need to be made to the GameBeta class, presented previously, are as follows. Note in particular that the bodies of the initialize and update methods are removed, and there are semicolons added to the lines where these methods are declared.
// import statements remain the same
public abstract class GameBeta extends Game
{
    protected Stage mainStage;
    // create method remains the same
    public abstract void initialize();
    // render method remains the same
    public abstract void update(float dt);
}
With the GameBeta class serving as a base, the StarfishCollectorBeta class can be greatly simplified, as previously explained. The changes to the class are explained in the commented code that follows. Note that as you change the code in one class, other classes that depend on it will appear to have errors until the changes have been completed.
// import statements remain the same
// this class now extends GameBeta rather than Game
public class StarfishCollectorBeta extends GameBeta
{
    // removed declaration of mainStage
    private Turtle turtle;
    private ActorBeta starfish;
    private ActorBeta ocean;
    private ActorBeta winMessage;
    private boolean win;
    // create method renamed to initialize
    public void initialize()
    {
        // removed initialization of mainStage
        // the rest of the method remains the same
    }
    // render method renamed to update; requires float parameter dt
    public void update(float dt)
    {
        // most code of the render method removed
        // check win condition: turtle must be overlapping starfish
        if (turtle.overlaps(starfish))
        {
            starfish.remove();
            winMessage.setVisible(true);
        }
    }
}
As you can see, this class now focuses on the game itself (the game objects and their interaction with each other) rather than framework issues (such as setting up and using the stage). You can now play the game by running the main method in the corresponding launcher class, as before.
Congratulations on creating your first game in LibGDX!

Summary and Next Steps

This chapter introduced many features of the LibGDX library. You began with an overview of the life cycle of a game program and learned how the stages of the life cycle are performed by methods with a particular naming convention, enforced by an interface. You learned how to process keyboard input by using the Gdx class and how to encapsulate game-entity data with the Actor class. You learned how the Stage class can be used to manage Actor instances, and how to extend the Actor class for greater functionality. You also learned how to reorganize the game flow and simplify future game development projects by extending the Game class.
To practice the skills you have developed in this chapter, you could try adding another object to the game: a shark that the turtle must avoid; if the turtle overlaps the shark, then the turtle should be removed from the stage, and a message that reads “Game Over” should be displayed on the screen. Graphics for this addition are provided in the assets folder for this project.
In the next chapter, you’ll create a more polished version of the Starfish Collector game powered by a new extension of the Actor class (which will replace ActorBeta) that supports animation, physics-based movement, improved collision detection, and the ability to manage multiple actor instances with lists.
Footnotes
1
Running faster than this is usually unnecessary, because most computer display hardware is incapable of displaying images at a greater rate than this.
 
2
A later section in this chapter will demonstrate how to organize code more intuitively so that the update and render stages are handled by separate methods.
 
3
The source code for LibGDX is currently hosted on GitHub at https://github.com/libgdx/libgdx .
 
4
The design choice to have y increase toward the top, while consistent with mathematical conventions, is the opposite of most computer science coordinate-system conventions, which place the origin point (0,0) at the top-left corner of a window so that the y value increases toward the bottom.
 
5
Technically, if your program features a background graphic that covers the entire screen area, then this step is not necessary.
 
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset