Zombie Bird Tutorial (Flappy Bird Clone/Remake)
Day 3 - Understanding the libGDX Framework
In this section we will be building some of the helper Classes and Methods that will help us when we build our game. Before we write any code, here are a few technical details you should know.
libGDX is under an Apache 2.0 license, which allows you to freely modify and distribute the code, provided that you give credit to the original author. All built-in libGDX classes will have an Apache 2.0 header. As we will not be modifying these files, and since the licenses are already included for us, we do not have to include it ourselves or modify the header in any way.
Here is the relevant license file (already included in our libGDX projects): http://www.apache.org/licenses/LICENSE-2.0.html
libGDX is under an Apache 2.0 license, which allows you to freely modify and distribute the code, provided that you give credit to the original author. All built-in libGDX classes will have an Apache 2.0 header. As we will not be modifying these files, and since the licenses are already included for us, we do not have to include it ourselves or modify the header in any way.
Here is the relevant license file (already included in our libGDX projects): http://www.apache.org/licenses/LICENSE-2.0.html
Basic Structure (How we will design and implement our game)
Let's take the time to discuss how we will implement our game. Here's a quick diagram I threw together to describe the design.
We are going to be working with the ZBGame branch first. We will start implementing the Framework Helpers and the Screen Classes, among which is the GameScreen.
GameScreen relies on two helpers: the World and the Renderer. The World will need to talk to the Gameplay classes in order to make our game objects.
If that all makes sense, let's move on.
GameScreen relies on two helpers: the World and the Renderer. The World will need to talk to the Gameplay classes in order to make our game objects.
If that all makes sense, let's move on.
Warning. The following will be the most conceptually challenging section in the whole series.
HOWEVER... you can skip it.
You can skim and try to understand as much as you can, ask questions and move forward. Do not be discouraged, as most of this information is not essential for our game. No need to get stuck on the inessentials.
If you think that this section is too confusing, scroll ahead to the "Writing some code" section below. We will still be able to build our Zombie Bird.
Feeling brave? Read on. Nervous? Scroll ahead.
If you think that this section is too confusing, scroll ahead to the "Writing some code" section below. We will still be able to build our Zombie Bird.
Feeling brave? Read on. Nervous? Scroll ahead.
Extends and Implements (Difficult - skip if necessary)
If you need a quick refresher on Inheritance (extends) and Interface (implements), click here for a short review.
Recall that an interface is a list of requirements -- method headers with no bodies. An interface lists all the methods that some class must implement (provide a method body to all the empty method headers) in order to become a type of that interface. Yes, that is confusing, so here is an illustration.
Consider the following. The Java library has the List interface, which by itself provides no functionality. The List interface is a file that lists all the methods that another class must implement in order to become categorized as a List object.
These methods include (among many, many others):
1. list.get(int index), which retrieves an item at that index.
2. list.add(), which adds an item to the end of the List.
3. list.isEmpty(), which returns 'true' if the List is empty.
Let's say we create another class, called ArrayList. We them implement the List, by fulfilling all the requirements of the List interface, meaning that we complete all the empty methods such as list.add() and list.get(int index).
After doing so in its Class file, ArrayList gets granted the ability to refer to itself as a type of List, as shown below:
Consider the following. The Java library has the List interface, which by itself provides no functionality. The List interface is a file that lists all the methods that another class must implement in order to become categorized as a List object.
These methods include (among many, many others):
1. list.get(int index), which retrieves an item at that index.
2. list.add(), which adds an item to the end of the List.
3. list.isEmpty(), which returns 'true' if the List is empty.
Let's say we create another class, called ArrayList. We them implement the List, by fulfilling all the requirements of the List interface, meaning that we complete all the empty methods such as list.add() and list.get(int index).
After doing so in its Class file, ArrayList gets granted the ability to refer to itself as a type of List, as shown below:
List<String> strings = new ArrayList<String>();
Notice that the variable strings is of type List, created as an ArrayList. It can behave as either a List or an ArrayList, depending on your need.
Because we know string is an implementation of List, we are certain that it has all the methods that a List must have. Because of this, whenever we need to provide a List to a method, we can provide the ArrayList called strings (polymorphism):
Because we know string is an implementation of List, we are certain that it has all the methods that a List must have. Because of this, whenever we need to provide a List to a method, we can provide the ArrayList called strings (polymorphism):
public void printLastWordFrom(List<String> someList) { if (someList.isEmpty()) { System.out.println("Your list is empty."); return; } String lastWord = someList.get(someList.size() - 1)); System.out.println("Your last word is" + lastWord); }
This principle will be important in understanding the WHY behind some of the foundational steps that we will take next.
Conventions used in this Tutorial (Read!)
There will be a few times when I reference existing code in the libGDX library, such as the Game class below. These classes are already built-in to your project, and you do NOT need to write any code. Simply refer to the classes as I ask, so that we can build around them. All of these classes will have the Apache 2.0 header in their original form, with the following authors cited: https://github.com/libgdx/libgdx/blob/master/gdx/AUTHORS.
I will use a WHITE background for built-in classes, and a dark background for code that you need to write. I will make this clear as we progress.
Have a look at the Game class really quickly. You do NOT need to write any code. Just skim through it.
I will use a WHITE background for built-in classes, and a dark background for code that you need to write. I will make this clear as we progress.
Have a look at the Game class really quickly. You do NOT need to write any code. Just skim through it.
Game Class (Built-in)
public abstract class Game implements ApplicationListener { private Screen screen; @Override public void dispose () { if (screen != null) screen.hide(); } @Override public void pause () { if (screen != null) screen.pause(); } @Override public void resume () { if (screen != null) screen.resume(); } @Override public void render () { if (screen != null) screen.render(Gdx.graphics.getDeltaTime()); } @Override public void resize (int width, int height) { if (screen != null) screen.resize(width, height); } /** Sets the current screen. {@link Screen#hide()} is called on any old screen, and {@link Screen#show()} is called on the new * screen, if any. * @param screen may be {@code null} */ public void setScreen (Screen screen) { if (this.screen != null) this.screen.hide(); this.screen = screen; if (this.screen != null) { this.screen.show(); this.screen.resize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); } } /** @return the currently active {@link Screen}. */ public Screen getScreen () { return screen; } }
Examining the Game class (Difficult - skip if necessary)
The above Game class is an implementation of the ApplicationListener, which will be the interface between our code and the platform-dependent application that is directly launched by our target device.
For example, when Android launches our app, it will look for an ApplicationListener. We can easily provide one by providing a Game object, as declared above.
There is one minor issue. Notice that Game is an abstract class. This means that it is choosing to NOT implement some of the methods that are listed in our ApplicationListener interface. Because of this, to get a true ApplicationListener we must implement these missing methods ourselves.
We could do this by copying the Game class and then providing the implementation to the missing method - it is actually just one, called create().
Rather than doing that, however, the better thing to do is to apply inheritance.
Inheritance is a simpler concept than interface. We just take the abstract Game class and create an extension (called a subclass), which inherits all the non-private methods and variables from the Game class as its own. Then, we can provide the subclass with its own methods and variables.
Let's implement this.
For example, when Android launches our app, it will look for an ApplicationListener. We can easily provide one by providing a Game object, as declared above.
There is one minor issue. Notice that Game is an abstract class. This means that it is choosing to NOT implement some of the methods that are listed in our ApplicationListener interface. Because of this, to get a true ApplicationListener we must implement these missing methods ourselves.
We could do this by copying the Game class and then providing the implementation to the missing method - it is actually just one, called create().
Rather than doing that, however, the better thing to do is to apply inheritance.
Inheritance is a simpler concept than interface. We just take the abstract Game class and create an extension (called a subclass), which inherits all the non-private methods and variables from the Game class as its own. Then, we can provide the subclass with its own methods and variables.
Let's implement this.
Writing some code! (Finally)
Extend Game
We are going to extend the Game class, which will serve as an ApplicationInterface between our code and platform-dependent code (on iOS, Android, etc).
1. Add extends Game
2. Add the following import:
import com.badlogic.gdx.Game;
Importing means telling the compiler, "here is the full address to the Game class I am referring to." You need to do this because there are (potentially) many Game classes, and you want to specify this one specifically by telling the compiler where it is.
1. Add extends Game
2. Add the following import:
import com.badlogic.gdx.Game;
Importing means telling the compiler, "here is the full address to the Game class I am referring to." You need to do this because there are (potentially) many Game classes, and you want to specify this one specifically by telling the compiler where it is.
package com.kilobolt.ZombieBird; import com.badlogic.gdx.Game; public class ZBGame extends Game { }
Eclipse will give you the following warning.
This is saying for ZBGame to become a Game, there is a requirement: it must have a method called create().
We simply click on "Add unimplemented methods," and it will be automatically generated. Let's add one line of code to this auto-generated method:
(Note that we use Gdx.app.log instead of System.out.println(). The Gdx.app.log method is uesd for printing values and is implemented specifically for each platform (in Android, this would use the Log class to implement printing. On Java, this uses System.out.println(). The parameters for Gdx.app.log() should be the name of the current class and the message you would like to print).
We simply click on "Add unimplemented methods," and it will be automatically generated. Let's add one line of code to this auto-generated method:
(Note that we use Gdx.app.log instead of System.out.println(). The Gdx.app.log method is uesd for printing values and is implemented specifically for each platform (in Android, this would use the Log class to implement printing. On Java, this uses System.out.println(). The parameters for Gdx.app.log() should be the name of the current class and the message you would like to print).
package com.kilobolt.zombiebird; import com.badlogic.gdx.Game; import com.badlogic.gdx.Gdx; public class ZBGame extends Game { @Override public void create() { Gdx.app.log("ZBGame", "created"); } }
Let's slow things down for a minute...
Why did we want ZBGame to become a Game object again?
Reason #1:
As I've previously mentioned, libGDX hides the platform-dependent code from us. All the code we would otherwise need to write in order to create a game on iOS/Android/HTML/Windows/Mac are already written for us. As game developers, we just need to provide the high-level stuff, and we do that by creating an ApplicationInterface.
By extending Game (a subclass of ApplicationInterface), ZBGame becomes this interface between our code and our target platform. Now the behind-the-scenes code from Android, iOS, HTML and etc. can talk to ZBGame and work some magic together.
Reason #2:
In addition to the above, ZBGame also gains access to all the useful methods that belong to the Game class (scroll up if you want to see these again).
This is actually related to Reason #1 above. These are the methods that will eventually be called by the platform-dependent code.
Now when we run our game on one of our target platforms, the platform-dependent code will execute, and begin by calling the create() method above, printing "created."
Let's see what this all means.
We are going to attach our first Screen (which will soon be our GameScreen) to ZBGame to explore this.
Reason #1:
As I've previously mentioned, libGDX hides the platform-dependent code from us. All the code we would otherwise need to write in order to create a game on iOS/Android/HTML/Windows/Mac are already written for us. As game developers, we just need to provide the high-level stuff, and we do that by creating an ApplicationInterface.
By extending Game (a subclass of ApplicationInterface), ZBGame becomes this interface between our code and our target platform. Now the behind-the-scenes code from Android, iOS, HTML and etc. can talk to ZBGame and work some magic together.
Reason #2:
In addition to the above, ZBGame also gains access to all the useful methods that belong to the Game class (scroll up if you want to see these again).
This is actually related to Reason #1 above. These are the methods that will eventually be called by the platform-dependent code.
Now when we run our game on one of our target platforms, the platform-dependent code will execute, and begin by calling the create() method above, printing "created."
Let's see what this all means.
We are going to attach our first Screen (which will soon be our GameScreen) to ZBGame to explore this.
Creating the GameScreen
Right click on the src folder inside the CORE ZombieBird project and create a new Java package called com.kilobolt.screens.
Inside it, create a new class like so, importing Screen:
Inside it, create a new class like so, importing Screen:
package com.kilobolt.screens; import com.badlogic.gdx.Screen; public class GameScreen implements Screen { }
We must implement the methods in the Screen interface. You can do the auto-fix (as we did with ZBGame) by clicking "Add unimplemented methods," or by adding the methods as I have below. Add the simple System.out.println() code that I have added to each method:
package com.kilobolt.screens; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Screen; import com.badlogic.gdx.graphics.GL20; public class GameScreen implements Screen { public GameScreen() { Gdx.app.log("GameScreen", "Attached"); } @Override public void render(float delta) { // Sets a Color to Fill the Screen with (RGB = 10, 15, 230), Opacity of 1 (100%) Gdx.gl.glClearColor(10/255.0f, 15/255.0f, 230/255.0f, 1f); // Fills the screen with the selected color Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); } @Override public void resize(int width, int height) { Gdx.app.log("GameScreen", "resizing"); } @Override public void show() { Gdx.app.log("GameScreen", "show called"); } @Override public void hide() { Gdx.app.log("GameScreen", "hide called"); } @Override public void pause() { Gdx.app.log("GameScreen", "pause called"); } @Override public void resume() { Gdx.app.log("GameScreen", "resume called"); } @Override public void dispose() { // Leave blank } }
Attaching GameScreen to our ZBGame ClassWe want to set the current screen in the ZBGame class to the GameScreen that we have just created. To do so, we go back to ZBGame.java.
1. Add the following to the create() class: setScreen(new GameScreen()); Note: This setScreen() method is available via inheritance! 2. Import GameScreen: import com.kilobolt.screens.GameScreen; |
package com.kilobolt.zombiebird; import com.badlogic.gdx.Game; import com.badlogic.gdx.Gdx; import com.kilobolt.screens.GameScreen; public class ZBGame extends Game { @Override public void create() { Gdx.app.log("ZBGame", "created"); setScreen(new GameScreen()); } } |
Now we can run the game. (To do this, as always, we must go to a different Project - the ZombieBird - desktop and run DesktopLauncher. You will see a beautiful blue window.
Look at the output generated:
Look at the output generated:
I know, this was not the most exciting lesson, but please spend some time looking at your code again, walking through the order in which each line of code was called.
The important thing is that we didn't call any of these methods ourselves. We let libGDX call them for us. (Re-read the section above called: Let's slow things down for a minute...).
It is important that you understand this order in which various methods are called, so that we can create objects at the right time and transition smoothly in our game.
If you are ready, let's move on. Join me in Day 4, where we will implement some gameplay!
The important thing is that we didn't call any of these methods ourselves. We let libGDX call them for us. (Re-read the section above called: Let's slow things down for a minute...).
It is important that you understand this order in which various methods are called, so that we can create objects at the right time and transition smoothly in our game.
If you are ready, let's move on. Join me in Day 4, where we will implement some gameplay!
Source Code for the day.
If you didn't feel like writing that code yourself, download the completed code here:
Download, extract and import into eclipse:
Download, extract and import into eclipse:

zombiebird_day_3.zip | |
File Size: | 10354 kb |
File Type: | zip |
Like us on Facebook to be informed as soon as the next lesson is available.
|
|