LibGDX Zombie Bird Tutorial (Flappy Bird Clone/Remake)
Day 5 - The Flight of the Dead - Adding the Bird
Today, we are going to implement our Bird into the game.
Let me tell you a little bit about our main character. Flaps here was a happy red bird flying about his business until someone smashed him into a sewer pipe. Now he has returned, and he's watching you very intently. (Sorry Kyle, I still think this is the ugliest bird I've ever seen).
Now that you've met our main character, let's make him fly.
Now that you've met our main character, let's make him fly.
Quick Reminder
We have five Java projects that we have generated using libGDX's setup tool; however, we only have to worry about three of them while we are implementing our game:
1. Whenever I say open a class or create a new package, I will be asking you to modify the ZombieBird project.
2. If I ask you to run your code, you will open your ZombieBird-desktop project and run DesktopLauncher.java.
3. When we add assets (images and sounds), we will add to the ZombieBird-android project. All other projects will receive a copy of the assets that are in this project.
1. Whenever I say open a class or create a new package, I will be asking you to modify the ZombieBird project.
2. If I ask you to run your code, you will open your ZombieBird-desktop project and run DesktopLauncher.java.
3. When we add assets (images and sounds), we will add to the ZombieBird-android project. All other projects will receive a copy of the assets that are in this project.
Coordinate System
I forgot to mention (you probably have figured it out already), that we will use a Y Down coordinate system. This means that the upper left corner of the screen has the coordinates (0, 0).
What does this mean? It means that if our Bird has a positive Y velocity, we will be moving downwards.
What does this mean? It means that if our Bird has a positive Y velocity, we will be moving downwards.
The Screen Resolution
Our game will run on everything from iPhone to iPad to all the different Android devices out there. We need to handle screen resolution properly.
To do this, we are going to assume that the width of the game is always 136. The height will be dynamically determined! We will calculate our device's screen resolution and determine how tall the game should be.
To do this, we are going to assume that the width of the game is always 136. The height will be dynamically determined! We will calculate our device's screen resolution and determine how tall the game should be.
Creating the Bird.java class
We need to give Flaps his own Class. Let's do that.
- Create a new package called com.kilobolt.gameobjects and create a Bird class.
- Create a new package called com.kilobolt.gameobjects and create a Bird class.
The Instance Variables:
Our Bird needs a position, a velocity and acceleration (more on this below). We also need a rotation angle (for when the bird rotates) and a width and height.
Our Bird needs a position, a velocity and acceleration (more on this below). We also need a rotation angle (for when the bird rotates) and a width and height.
private Vector2 position; private Vector2 velocity; private Vector2 acceleration; private float rotation; // For handling bird rotation private int width; private int height;
Vector2 is a powerful built-in libGDX class. If you are not familiar with mathematical vectors, that's okay! We are just going to treat it as an object that can hold two values: an x component and a y component.
position.x then refers to the x coordinate, and velocity.y would correspond to the speed in the y direction. acceleration just means change in velocity, just like velocity means change in position.
This will become much clearer in just a second.
The Constructor:
What information do we need to create a new Bird? We need to know what its X position should be, what its Y position should be, how wide it should be and how tall it should be.
position.x then refers to the x coordinate, and velocity.y would correspond to the speed in the y direction. acceleration just means change in velocity, just like velocity means change in position.
This will become much clearer in just a second.
The Constructor:
What information do we need to create a new Bird? We need to know what its X position should be, what its Y position should be, how wide it should be and how tall it should be.
public Bird(float x, float y, int width, int height) { this.width = width; this.height = height; position = new Vector2(x, y); velocity = new Vector2(0, 0); acceleration = new Vector2(0, 460); }
Our Bird will belong to the GameWorld, and these are the methods we need:
1.The update method (which will be called when GameWorld updates)
2. onClick method (which will be called when the screen is clicked or touched).
We also need to create a bunch of getters to allow access to some of our Bird object's instance variables.
1.The update method (which will be called when GameWorld updates)
2. onClick method (which will be called when the screen is clicked or touched).
We also need to create a bunch of getters to allow access to some of our Bird object's instance variables.
package com.kilobolt.gameobjects; import com.badlogic.gdx.math.Vector2; public class Bird { private Vector2 position; private Vector2 velocity; private Vector2 acceleration; private float rotation; // For handling bird rotation private int width; private int height; public Bird(float x, float y, int width, int height) { this.width = width; this.height = height; position = new Vector2(x, y); velocity = new Vector2(0, 0); acceleration = new Vector2(0, 460); } public void update(float delta) { velocity.add(acceleration.cpy().scl(delta)); if (velocity.y > 200) { velocity.y = 200; } position.add(velocity.cpy().scl(delta)); } public void onClick() { velocity.y = -140; } public float getX() { return position.x; } public float getY() { return position.y; } public float getWidth() { return width; } public float getHeight() { return height; } public float getRotation() { return rotation; } }
The logic for this is pretty simple. Every time that the Bird's update method is called, we do two things:
1. We add our scaled acceleration vector (we will come back to this) to our velocity vector. This gives us our new velocity. For those of you not familiar, this is how the earth's gravity works. Your downward speed increases by 9.8 m/s every second.
2. Remember that Flappy Bird physics has a max velocity cap (there's some sort of terminal velocity). I have experimented with this and set a velocity.y cap at 200.
3. We add the updated scaled velocity to the bird's position (this gives us our new position).
What do I mean by scaled in #1 and #3 above? I mean that we multiply the acceleration and velocity vectors by the delta, which is the amount of time that has passed since the update method was previously called. This has a normalizing effect.
If your game slows down for any reason, your delta will go up (your processor will have taken longer time to complete the previous iteration, or repetition, of the loop). By scaling our Vectors with delta, we can achieve frame-rate independent movement. If the update method took twice as long to execute, then we just move our character by 2x the original velocity, and so on.
We will be applying this scaling to our rotation later on!
Now that our bird is ready, we will unleash it into the GameWorld!
1. We add our scaled acceleration vector (we will come back to this) to our velocity vector. This gives us our new velocity. For those of you not familiar, this is how the earth's gravity works. Your downward speed increases by 9.8 m/s every second.
2. Remember that Flappy Bird physics has a max velocity cap (there's some sort of terminal velocity). I have experimented with this and set a velocity.y cap at 200.
3. We add the updated scaled velocity to the bird's position (this gives us our new position).
What do I mean by scaled in #1 and #3 above? I mean that we multiply the acceleration and velocity vectors by the delta, which is the amount of time that has passed since the update method was previously called. This has a normalizing effect.
If your game slows down for any reason, your delta will go up (your processor will have taken longer time to complete the previous iteration, or repetition, of the loop). By scaling our Vectors with delta, we can achieve frame-rate independent movement. If the update method took twice as long to execute, then we just move our character by 2x the original velocity, and so on.
We will be applying this scaling to our rotation later on!
Now that our bird is ready, we will unleash it into the GameWorld!
Caution
Every time you create a new Object, you allocate a little bit of memory in RAM for that object (specifically in the Heap). Once your Heap fills up, a subroutine called a Garbage Collector will come in and clean up your memory to ensure that you do not run out of space. This is great, except when you are building a game. Every time the garbage collector comes into play, your game will stutter for several essential milliseconds. To avoid garbage collection, you need to avoid allocating new objects whenever possible.
I've recently discovered that the method Vector2.cpy() creates a new instance of a Vector2 rather than recycling one instance. This means that at 60 FPS, calling Vector2.cpy() would allocate 60 new Vector2 objects every second, which would cause the Java garbage collector to get involved every frequently.
Just keep this in mind as you go through the remainder of Unit 1. We will solve this issue later in the series.
I've recently discovered that the method Vector2.cpy() creates a new instance of a Vector2 rather than recycling one instance. This means that at 60 FPS, calling Vector2.cpy() would allocate 60 new Vector2 objects every second, which would cause the Java garbage collector to get involved every frequently.
Just keep this in mind as you go through the remainder of Unit 1. We will solve this issue later in the series.
Open the GameWorld class
Let's remove the rectangle object we have created earlier. This is what you should have:
package com.kilobolt.gameworld; public class GameWorld { public void update(float delta) { } }
If you'd like, you can remove the rectangle drawing code from the GameRenderer to remove errors. We will do this in Day 6.
Let's first create a constructor for our GameWorld. Add the following constructor above the update method:
public GameWorld() {
}
Import Bird, and create a new Bird object as an instance variable (do not initialize it). Create a getter too. And call the bird's update method in the GameWorld.update(float delta). This is what that should look like:
Let's first create a constructor for our GameWorld. Add the following constructor above the update method:
public GameWorld() {
}
Import Bird, and create a new Bird object as an instance variable (do not initialize it). Create a getter too. And call the bird's update method in the GameWorld.update(float delta). This is what that should look like:
package com.kilobolt.gameworld; import com.kilobolt.gameobjects.Bird; public class GameWorld { private Bird bird; public GameWorld() { // Initialize bird here } public void update(float delta) { bird.update(delta); } public Bird getBird() { return bird; } }
Now we must initialize the bird. What information do we need? The x, y, width and height (these are the four values we need to call the Bird's constructor that we've created above).
The x should be 33 (that's where the bird stays the entire time). The width should be 17. The height will be 12.
What about the y? Based on my calculations, it should be 5 pixels above the vertical middle of the screen. (Remember that we are scaling everything down to a 137 x ??? screen resolution, where the height is determined by getting the ratio between device screen height and screen width, and multiplying by 137).
Add this line to the constructor:
bird = new Bird(33, midPointY - 5, 17, 12);
So how do we get midPointY? We will ask our GameScreen to give it to us. Remember that the GameWorld constructor is invoked when the GameScreen creates a GameWorld object. So then we can create an additional parameter to ask GameScreen to give us the midPointY.
Add this to the constructor: (int midPointY)
This is what you should have at this point:
The x should be 33 (that's where the bird stays the entire time). The width should be 17. The height will be 12.
What about the y? Based on my calculations, it should be 5 pixels above the vertical middle of the screen. (Remember that we are scaling everything down to a 137 x ??? screen resolution, where the height is determined by getting the ratio between device screen height and screen width, and multiplying by 137).
Add this line to the constructor:
bird = new Bird(33, midPointY - 5, 17, 12);
So how do we get midPointY? We will ask our GameScreen to give it to us. Remember that the GameWorld constructor is invoked when the GameScreen creates a GameWorld object. So then we can create an additional parameter to ask GameScreen to give us the midPointY.
Add this to the constructor: (int midPointY)
This is what you should have at this point:
package com.kilobolt.gameworld; import com.kilobolt.gameobjects.Bird; public class GameWorld { private Bird bird; public GameWorld(int midPointY) { bird = new Bird(33, midPointY - 5, 17, 12); } public void update(float delta) { bird.update(delta); } public Bird getBird() { return bird; } }
Now we need to go make some changes to our GameScreen.
Let's open that up:
Let's open that up:
We have an error, as expected, in the line where we call the GameWorld constructor. We have just said that "to create a new GameWorld, you must give us an integer", so we must do that!
Let's ask GameScreen to calculate the midPointY of the screen and pass it into the constructor.
When I say midPointY, this is what I mean. Remember that our game will be 136 units wide. Our screen may be 1080 pixels wide, so we must scale everything down by about 1/8. To get the game height, we must take the screen height and scale that down by the same factor!
We can retrieve the screen width and height using the following methods: Gdx.graphics.getWidth() and Gdx.graphics.getHeight().
Let's use this information to implement our logic inside the constructor:
Let's ask GameScreen to calculate the midPointY of the screen and pass it into the constructor.
When I say midPointY, this is what I mean. Remember that our game will be 136 units wide. Our screen may be 1080 pixels wide, so we must scale everything down by about 1/8. To get the game height, we must take the screen height and scale that down by the same factor!
We can retrieve the screen width and height using the following methods: Gdx.graphics.getWidth() and Gdx.graphics.getHeight().
Let's use this information to implement our logic inside the constructor:
package com.kilobolt.screens; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Screen; import com.kilobolt.gameworld.GameRenderer; import com.kilobolt.gameworld.GameWorld; public class GameScreen implements Screen { private GameWorld world; private GameRenderer renderer; // This is the constructor, not the class declaration public GameScreen() { float screenWidth = Gdx.graphics.getWidth(); float screenHeight = Gdx.graphics.getHeight(); float gameWidth = 136; float gameHeight = screenHeight / (screenWidth / gameWidth); int midPointY = (int) (gameHeight / 2); world = new GameWorld(midPointY); renderer = new GameRenderer(world); } @Override public void render(float delta) { world.update(delta); renderer.render(); } @Override public void resize(int width, int height) { } @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 } }
Now that we have created our Bird, we need to be able to control it. So let's add an input handler!
Creating the ZBHelpers

The diagram is back! We will briefly turn our attention to the Framework Helpers on the 3rd level. The ZBGame needs help in order to handle input, images, sounds and etc.
We will be creating two classes right now.
The first class will be the InputHandler which, as the name suggests, will react to various inputs. In our case, the only input we need to worry about is the touch (clicks are registered as touches on PC/Mac).
The second class will be the AssetLoader which will load various images, animations and sounds for us.
We will get back to the AssetLoader very soon. Let's first implement our InputHandler.
Create the package called com.kilobolt.zbHelpers, and create an InputHandler class.
We will be creating two classes right now.
The first class will be the InputHandler which, as the name suggests, will react to various inputs. In our case, the only input we need to worry about is the touch (clicks are registered as touches on PC/Mac).
The second class will be the AssetLoader which will load various images, animations and sounds for us.
We will get back to the AssetLoader very soon. Let's first implement our InputHandler.
Create the package called com.kilobolt.zbHelpers, and create an InputHandler class.
The InputHandler is extremely easy to implement. We just have to implement the InputProcessor, which is an interface between platform-dependent code and our code. When our platform (Android, iOS, etc) receives some kind of input, such as a touch, it will call a method inside the current InputProcessor, which we will provide by implementing it.
Add the line implements InputProcessor to the class declaration (import accordingly). This will give us an error asking us to implement the unimplemented methods. We will do that:
Add the line implements InputProcessor to the class declaration (import accordingly). This will give us an error asking us to implement the unimplemented methods. We will do that:
Your code should look like this:
package com.kilobolt.ZBHelpers; import com.badlogic.gdx.InputProcessor; import com.kilobolt.GameObjects.Bird; public class InputHandler implements InputProcessor { @Override public boolean touchDown(int screenX, int screenY, int pointer, int button) { return false; } @Override public boolean keyDown(int keycode) { return false; } @Override public boolean keyUp(int keycode) { return false; } @Override public boolean keyTyped(char character) { return false; } @Override public boolean touchUp(int screenX, int screenY, int pointer, int button) { return false; } @Override public boolean touchDragged(int screenX, int screenY, int pointer) { return false; } @Override public boolean mouseMoved(int screenX, int screenY) { return false; } @Override public boolean scrolled(int amount) { return false; } }
That gives us a bunch of methods that we can work with. For now, we only have to worry about the touchDown() method, however.
The TouchDown method should call our Bird's onClick method, but we have no reference to our Bird object. We can't call any of Bird's methods until get a reference to our Bird. Then we ask ourselves, who has a reference to our current bird? GameWorld does, which belongs to GameScreen! So we will ask GameScreen to send the Bird to us.
Before we go back to GameScreen, let's first finish up the InputHandler class:
1. Create an instance variable the InputHandler class:
private Bird myBird;
2. We must ask for a Bird object inside the constructor:
public InputHandler(Bird bird) {
myBird = bird;
}
3. And then we can call the following inside our touchDown() method:
myBird.onClick()
The TouchDown method should call our Bird's onClick method, but we have no reference to our Bird object. We can't call any of Bird's methods until get a reference to our Bird. Then we ask ourselves, who has a reference to our current bird? GameWorld does, which belongs to GameScreen! So we will ask GameScreen to send the Bird to us.
Before we go back to GameScreen, let's first finish up the InputHandler class:
1. Create an instance variable the InputHandler class:
private Bird myBird;
2. We must ask for a Bird object inside the constructor:
public InputHandler(Bird bird) {
myBird = bird;
}
3. And then we can call the following inside our touchDown() method:
myBird.onClick()
package com.kilobolt.zbhelpers; import com.badlogic.gdx.InputProcessor; import com.kilobolt.gameobjects.Bird; public class InputHandler implements InputProcessor { private Bird myBird; // Ask for a reference to the Bird when InputHandler is created. public InputHandler(Bird bird) { // myBird now represents the gameWorld's bird. myBird = bird; } @Override public boolean touchDown(int screenX, int screenY, int pointer, int button) { myBird.onClick(); return true; // Return true to say we handled the touch. } @Override public boolean keyDown(int keycode) { return false; } @Override public boolean keyUp(int keycode) { return false; } @Override public boolean keyTyped(char character) { return false; } @Override public boolean touchUp(int screenX, int screenY, int pointer, int button) { return false; } @Override public boolean touchDragged(int screenX, int screenY, int pointer) { return false; } @Override public boolean mouseMoved(int screenX, int screenY) { return false; } @Override public boolean scrolled(int amount) { return false; } }
Now we just have to go back to the GameScreen and create a new InputHandler, and attach it to our game!
Open that up:
Open that up:
Update your constructor to be as follows: In the very last line, we are telling libGDX to take our new InputHandler as its processor.
public GameScreen() { float screenWidth = Gdx.graphics.getWidth(); float screenHeight = Gdx.graphics.getHeight(); float gameWidth = 136; float gameHeight = screenHeight / (screenWidth / gameWidth); int midPointY = (int) (gameHeight / 2); world = new GameWorld(midPointY); renderer = new GameRenderer(world); Gdx.input.setInputProcessor(new InputHandler(world.getBird())); }
Gdx.input.setInputProcessor() takes in an InputProcessor object. Since we implemented InputProcessor in our very own InpurHandler, we can give our InputHandler to this instead.
Notice that we are calling the constructor, passing in a reference to our Bird object that we retrieve from World. This is just a simplification of the following:
Bird bird = world.getBird();
InputHandler handler = new InputHandler(bird);
Gdx.input.setInputProcessor(handler);
Rather than type all this, the one-line solution in the code above should work fine.
Notice that we are calling the constructor, passing in a reference to our Bird object that we retrieve from World. This is just a simplification of the following:
Bird bird = world.getBird();
InputHandler handler = new InputHandler(bird);
Gdx.input.setInputProcessor(handler);
Rather than type all this, the one-line solution in the code above should work fine.
Now where are we?
We have created our Bird class, created a Bird object inside our GameWorld, and created an InputHandler which will call our Bird's onClick method, which will make it fly upwards! Join me in Day 6, where we will Render our bird and its grim necropolis habitat!
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_5.zip | |
File Size: | 10282 kb |
File Type: | zip |
Like us on Facebook to be informed as soon as the next lesson is available.
|
|
comments powered by Disqus