• Home
  • Tutorials
    • Game Development Tutorial>
      • Unit 1: Beginning Java>
        • Before you begin...
        • Day 1: Setting Up
        • Day 2: Java Basics
        • Day 3: More Basics
        • Day 4: Java Math
        • Day 5: More Math
        • Day 6: If... else...
        • Day 7: More Control Flow
        • Day 8: Looping
        • Day 9: More on Looping
        • Day 10: Inheritance, Interface
        • Day 11: Threads and Graphics
      • Unit 2: Creating a Game I>
        • Day 1: Foundations
        • Day 2: Basic Framework
        • Day 3: Taking User Input
        • Day 4: Enter the Robot
        • Day 5: Background and Sprites
        • Day 6: Adding Enemies
        • Day 7: Shooting Bullets
        • Day 8: Animations
        • Day 9: 2D-Arrays
        • Day 10: Painting the Tilemap
      • Unit 3: Creating a Game II>
        • Day 1: Level Creation - Part 1
        • Day 2: Level Creation - Part 2
        • Day 3: Level Creation - Part 3
        • Collision Detection Basics
        • Day 4: Collision Detection Part 1
        • Day 5: Collision Detection Part 2
        • Day 6: Collision Detection Part 3
        • Day 7: Health System & Death
        • Day 8: Basic AI & Final Touches
      • Unit 4: Android Game Development>
        • Day 1: Introduction to Android
        • Day 2: Setting up for Development
        • Day 3: Creating our First Android Application
        • Day 4: Parts of an Android Application
        • Day 5: The Android Game Framework: Part I
        • Day 6: The Android Game Framework: Part II
        • Create an Android Game From Scratch (or port your existing game)
        • Day 7: Creating an Android Game (From Start to Finish)
      • Reference Sheet
    • Zombie Bird Tutorial (Flappy Bird Remake)>
      • Unit 1: Building the Game>
        • Introduction
        • Day 1: Flappy Bird - An In-depth Analysis
        • Day 2: Setting up libGDX
        • Day 3: Understanding the libGDX Framework
        • Day 4: GameWorld and GameRenderer and the Orthographic Camera
        • Day 5: The Flight of the Dead - Adding the Bird
        • Day 6: Adding Graphics - Welcome to the Necropolis
        • Day 7: The Grass, the Bird and the Skull Pipe
        • Day 8: Collision Detection and Sound Effects
        • Day 9: Finishing Gameplay and Basic UI
        • Day 10: GameStates and High Score
        • Day 11: Supporting iOS/Android + SplashScreen, Menus and Tweening
        • Day 12: Completed UI & Source Code
    • Android Application Development Tutorial>
      • Unit 1: Writing Basic Android Apps>
        • Before you begin...
        • Day 1: Android 101
        • Day 2: Getting to Know the Android Project
        • Day 3: The Development Machine
        • Day 4: Building a Music App - Part 1: Building Blocks
        • Day 5: Building a Music App - Part 2: Intents
        • Day 6: Building a Music App - Part 3: Activity Lifecycles
  • New Forum
  • About Us
    • Contact Us
  • Our Games
    • TUMBL: FallDown
  • Facebook
  • Twitter

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. 
Picture
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.

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.

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.

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.

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.
Picture
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.
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.
 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.
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!

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.

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:
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:
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:
Picture
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:
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

Picture
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.

Picture
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:
Picture
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()
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:
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.

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:
zombiebird_day_5.zip
File Size: 10282 kb
File Type: zip
Download File

Like us on Facebook to be informed as soon as the next lesson is available.
Go to Day 6!
comments powered by Disqus
© 2014 Kilobolt, LLC. All rights reserved.