- 1 : Overview
- 2 : Environment SetUp
- 3 : Creating Project in libGDX
- 4 : Importing Projects in Eclipse
- 5 : Executing demo project for Windows
- 6 : Executing demo project for Android
- 7 : Executing demo project for HTML5
- 8 : Developing a real flappy bird game remake game using libGDX from scratch
- 9 : Environment Set Up for Game
- 10 : Understand basics of Game
- 11 : Exploring States and Game State Manager
- 12 : Create Play State and Make Bird Fly
- 13 : Building Obstacles and flying through them
- 14 : Collision and sound effect
- 15 : Porting the Game in Android Device
- 16 : libGDX More possibilities to Brisky Demo
- libGDX Example Code : Implementing Google Play Services Leader Boards in LibGDX
- libGDX Example Code : Text and line Animation
- libGDX Example Code : Experimenting Viewports with Text and Shape Animation for multiple screen Resolutions
- Step by Step Tutorial on libGDX
Building Obstacles and flying through them
Till Now we have successfully created a transition from Menu State to Play State and once we are in play state on a Click/Tap the bird jumps as if she is flying .
The next i.e. in this section we will cover following :
- Add Obstacles i.e. adding pipes to play state
- Flying of bird through the pipes i.e. obstacles .
Tubes are the obstacles for our bird in this flappy clone bird game so we will add tubes now . Lets create a new Tube class to our program as follows
- Create a new Java class Tube.java under sprites package
- declare 2 textures here , one for top tube and one for bottom tube so add it as follows
-
private Texture TopTube; private Texture BottomTube;
- Also Declare Variables for position of tubes one for top and one bottom tube
-
private Vector2 posTopTube,posBottomTube; // Position of top and bottom tubes on x axis
- Also declare following more variables and initialize them . I have explained them in line to the code below
-
public static final int TUBE_WIDTH = 40; // pixel width of the tube from the image private static final int FLUCTUATION = 140; // so it can move randomly between 0 and 140 private static final int TUBE_GAP = 100; // this will be the gap between 2 tubes private static final int LOWEST_OPENING = 130 ; // where from the bottom of the screen should can we have top tube private Random rand; // to get random top and bottom positions on y xis public Rectangle boundsTop,boundsBot; private Rectangle boundsSpace;
- Generate Constructor for the Tube Class and write below code in it . Here we are providing texture images to Top and Bottom tube , then initializing rand object . Next is posTopTube i.e. to get position of our Top tube .Here x is from the constructor but y is using a random value belween 0 to FLUCTUATION i.e. 140 plus TUBE_GAP i.e. 100 + Lowest opening i.e. 130 . So this will generate a variable to find out position of our Top tube and note that this positions y axis will be randomly generated due to random parameter FLUCTUATION . Next is posBottomTube i.e. position of Bottom Tube which we are generating using values of top tube . x axis for both is going to be same but for y axis we will have y axis position of top tube - TUBEGAP - height of the bottom tube . Next 3 are bounds i.e. rectangles covering top tube , bottom tube and space between the tubes .
-
public Tube(float x){ TopTube = new Texture("pipe_down.png"); BottomTube = new Texture("pipe_up.png"); rand = new Random(); posTopTube = new Vector2(x, rand.nextInt(FLUCTUATION) + TUBE_GAP + LOWEST_OPENING); posBottomTube = new Vector2(x,posTopTube.y - TUBE_GAP - BottomTube.getHeight()); boundsTop = new Rectangle(posTopTube.x,posTopTube.y,TopTube.getWidth() - 5,TopTube.getHeight() + 5); //Set position of invisible rectangle for top tube boundsBot = new Rectangle(posBottomTube.x,posBottomTube.y,BottomTube.getWidth() - 5,BottomTube.getHeight() -5); //Set position of invisible rectangle for bottom tube boundsSpace = new Rectangle(posTopTube.x,posTopTube.y - TUBE_GAP ,TopTube.getWidth() ,TopTube.getHeight() ); }
- Assest for tube can be taken from guthub repository of this code here
- Generate getters for position and textures of the tubes , basically we need getters as follows but you can generate both .
-
public Texture getTopTube() { return TopTube; } public Texture getBottomTube() { return BottomTube; } public Vector2 getPosTopTube() { return posTopTube; } public Vector2 getPosBottomTube() { return posBottomTube; }
- Lets create a collides function which we will use from play state to check if bird i.e player has collided to any of the tube
-
public boolean collides(Rectangle player){ return player.overlaps(boundsBot) || player.overlaps(boundsTop) ; }
- Dispose function which will dispose both bottom and top tubes
-
public void dispose(){ TopTube.dispose(); BottomTube.dispose(); }
- Finally the most important function of this class i.e. reposition which takes a float as input . Basically this function is used to reposition top and bottom tubes on given x axis input . We will call this function from play state which will change position of the tubes on the play state screen . It is also setting the position of the bounds because bounds also moves with the tube position .
-
public void reposition(float x){ posTopTube.set(x, rand.nextInt(FLUCTUATION) + TUBE_GAP + LOWEST_OPENING); posBottomTube.set(x,posTopTube.y - TUBE_GAP - BottomTube.getHeight()); boundsTop.setPosition(posTopTube.x,posTopTube.y); boundsBot.setPosition(posBottomTube.x,posBottomTube.y); boundsSpace.setPosition(posTopTube.x,posTopTube.y - TUBE_GAP); }
- Next we now want to really draw tube to the screen . We can draw a tube easily but in the game we at any point of time can see a maximum of 2 tubes and as soon as tube 1 goes out of viewport another tube is seen in the viewport at the right of the screen . So , its always a god idea to reposition a tube which has gone out of viewport to the right of the viewport so we are able to see same tube again . So , we will create an array of tubes and will initialize it to contain 4 tubes . You might ask Why 4 and not 2 as we can see only 2 tubes at a time , is because the third one just has to enter the viewport so we would like to keep it there ready so in-case re positioning takes more time it doesn't affect the next pipe . So lets add following variables to Play State now :
-
private static final int TUBE_COUNT = 4; private Array<Tube> tubes; private static final int TUBE_SPACING = 125; // Spacing between tubes horizontally
- Add following code to the constructor of the Play State . This code will be used to initialize the tube
-
tubes = new Array<Tube>(); for (int i = 1; i <= TUBE_COUNT; i++) { // for loop for adding tubes to the array tubes.add(new Tube(i * (TUBE_SPACING + Tube.TUBE_WIDTH))); }
- Now in the update method of Play State lets add all the reposition logic i.e. if a tube is moved to the last of the cam , we need to position it to the right of the screen i.e viewport
for (Tube tube : tubes){ if (cam.position.x - (cam.viewportWidth / 2 ) > tube.getPosTopTube().x + tube.getTopTube().getWidth()){ tube.reposition(tube.getPosTopTube().x + (Tube.TUBE_WIDTH + TUBE_SPACING) * TUBE_COUNT); } }
- Next important thing is to update camera in the play state according to the bird movement so bird is always in the camera i.e. viewport
open update method of play state and add below code there -
cam.position.x = bird.getPosition().x + 80; cam.update();
- Next in render method of Play State lets add the tubes as follows :
-
for (Tube tube : tubes) { if (tube.getPosTopTube().x > 320) { sb.draw(tube.getTopTube(), tube.getPosTopTube().x, tube.getPosTopTube().y); sb.draw(tube.getBottomTube(), tube.getPosBottomTube().x, tube.getPosBottomTube().y);} }
- Another change now is to change position of background as the cam or bird moves , because else as we will move in right direction once background image is gone we will see red background , so lets remove the line and add as below . Also we make sure BriskyDemo.HEIGHT is changed to 800 as of now in BriskyDemo.java
-
//sb.draw(background, 0, 0, BriskyDemo.WIDTH, BriskyDemo.HEIGHT); sb.draw(background, cam.position.x - (cam.viewportWidth / 2), 0, BriskyDemo.WIDTH, BriskyDemo.HEIGHT);
Cool Once this is done . Try Executing the code and you will see once we are in Play State we can see our bird and if we click button it flaps and then we see tubes coming on the way .
There s much more interesting stuff in the next sections starting from , How to detect collision , adding ground to the play state , additon of sounds and so on .
Below is the code till this section 13 .
BriskyBird.java
package com.versionpb.briskybird; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import States.GameStateManager; import States.MenuState; public class BriskyDemo extends ApplicationAdapter { public static final int WIDTH = 480; public static final int HEIGHT = 800; public static final String TITLE = "Brisky Bird Demo"; Texture img; private GameStateManager gsm; private SpriteBatch batch; @Override public void create () { gsm = new GameStateManager(); batch = new SpriteBatch(); Gdx.gl.glClearColor(1, 0, 0, 1); gsm.push(new MenuState(gsm)); } @Override public void render () { Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); gsm.update(Gdx.graphics.getDeltaTime()); gsm.render(batch); } }
Bird.java
package Sprites; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector3; public class Bird { private static final int GRAVITY = -15; public static int MOVEMENT = 100; private Vector3 position; private Vector3 velocity; private Texture bird; private Sound flap; private Rectangle bounds; public Bird(int x, int y ){ position = new Vector3(x,y,0); // z axis is 0 because we are not using it velocity = new Vector3(0,0,0); bird = new Texture("bird.png"); bounds = new Rectangle(x,y,bird.getWidth()/3,bird.getHeight()); flap = Gdx.audio.newSound(Gdx.files.internal("wing.ogg")); } public void update(float dt){ if (position.y > 0) velocity.add(0, GRAVITY, 0);// adding GRAVITY to the velocity velocity.scl(dt); // we are scaling velocity with delta time position.add(MOVEMENT * dt , velocity.y,0); if (position.y < 0) position.y = 0; velocity.scl(1/dt); //reversing the velocity scaling was done to basically adding scaled version of velocity to position bounds.setPosition(position.x,position.y); } public Vector3 getPosition() { return position; } public Texture getTexture(){ return bird; } public void jump(){ velocity.y = 250; flap.play(0.3f); } public Rectangle getBounds(){ return bounds; } public void dispose( ){ bird.dispose(); flap.dispose(); } }
Tube.java
package Sprites; import java.util.Random; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; public class Tube { public static final int TUBE_WIDTH = 40; // pixel width of the tube from the image private static final int FLUCTUATION = 140; // so it can move randomaly between 0 and 140 private static final int TUBE_GAP = 100; // this will be the gap between 2 tubes private static final int LOWEST_OPENING = 130 ; // where from the bottom of the screen should can we have top tube private Texture TopTube; private Texture BottomTube; private Vector2 posTopTube,posBottomTube; // Position of top and bottom tubes on x axis private Random rand; // to get random top and bottom positions on y xis public Rectangle boundsTop,boundsBot; private Rectangle boundsSpace; public Tube(float x){ TopTube = new Texture("pipe_down.png"); BottomTube = new Texture("pipe_up.png"); rand = new Random(); posTopTube = new Vector2(x, rand.nextInt(FLUCTUATION) + TUBE_GAP + LOWEST_OPENING); posBottomTube = new Vector2(x,posTopTube.y - TUBE_GAP - BottomTube.getHeight()); boundsTop = new Rectangle(posTopTube.x,posTopTube.y,TopTube.getWidth() - 5,TopTube.getHeight() + 5); //Set position of invisible rectangle for top tube boundsBot = new Rectangle(posBottomTube.x,posBottomTube.y,BottomTube.getWidth() - 5,BottomTube.getHeight() -5); //Set position of invisible rectangle for bottom tube boundsSpace = new Rectangle(posTopTube.x,posTopTube.y - TUBE_GAP ,TopTube.getWidth() ,TopTube.getHeight() ); } public Texture getTopTube() { return TopTube; } public void setTopTube(Texture topTube) { TopTube = topTube; } public Texture getBottomTube() { return BottomTube; } public void setBottomTube(Texture bottomTube) { BottomTube = bottomTube; } public Vector2 getPosTopTube() { return posTopTube; } public void setPosTopTube(Vector2 posTopTube) { this.posTopTube = posTopTube; } public Vector2 getPosBottomTube() { return posBottomTube; } public void setPosBottomTube(Vector2 posBottomTube) { this.posBottomTube = posBottomTube; } public boolean collides(Rectangle player){ return player.overlaps(boundsBot) || player.overlaps(boundsTop) ; } public void dispose(){ TopTube.dispose(); BottomTube.dispose(); } public void reposition(float x){ posTopTube.set(x, rand.nextInt(FLUCTUATION) + TUBE_GAP + LOWEST_OPENING); posBottomTube.set(x,posTopTube.y - TUBE_GAP - BottomTube.getHeight()); boundsTop.setPosition(posTopTube.x,posTopTube.y); boundsBot.setPosition(posBottomTube.x,posBottomTube.y); boundsSpace.setPosition(posTopTube.x,posTopTube.y - TUBE_GAP); } }
GameStateManager Class -- No changes
MenuState Class -- No Changes
State -- No Changes
PlayState.java
package States; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.utils.Array; import com.versionpb.briskybird.BriskyDemo; import Sprites.Bird; import Sprites.Tube; public class PlayState extends State { private Bird bird; //add this line private Texture background; private static final int TUBE_COUNT = 4; private static final int TUBE_SPACING = 125; private Array<Tube> tubes; public PlayState(GameStateManager gsm) { super(gsm); cam.setToOrtho(false, BriskyDemo.WIDTH / 2, BriskyDemo.HEIGHT / 2); bird = new Bird(50,50); background = new Texture("playBg_menu.png"); tubes = new Array<Tube>(); for (int i = 1; i <= TUBE_COUNT; i++) { // for loop for adding tubes to the array tubes.add(new Tube(i * (TUBE_SPACING + Tube.TUBE_WIDTH))); } } @Override public void handleInput() { if(Gdx.input.justTouched()) bird.jump(); } @Override public void update(float dt) { // TODO Auto-generated method stub handleInput(); bird.update(dt); cam.position.x = bird.getPosition().x + 80; for (Tube tube : tubes){ if (cam.position.x - (cam.viewportWidth / 2 ) > tube.getPosTopTube().x + tube.getTopTube().getWidth()){ tube.reposition(tube.getPosTopTube().x + (Tube.TUBE_WIDTH + TUBE_SPACING) * TUBE_COUNT); } } cam.update(); } @Override public void render(SpriteBatch sb) { sb.setProjectionMatrix(cam.combined); sb.begin(); //sb.draw(background, 0, 0, BriskyDemo.WIDTH, BriskyDemo.HEIGHT); sb.draw(background, cam.position.x - (cam.viewportWidth / 2), 0, BriskyDemo.WIDTH, BriskyDemo.HEIGHT); //sb.draw(bird,50,50); sb.draw(bird.getTexture(), bird.getPosition().x,bird.getPosition().y); for (Tube tube : tubes) { if (tube.getPosTopTube().x > 320) { sb.draw(tube.getTopTube(), tube.getPosTopTube().x, tube.getPosTopTube().y); sb.draw(tube.getBottomTube(), tube.getPosBottomTube().x, tube.getPosBottomTube().y); } } sb.end(); } @Override public void dispose() { // TODO Auto-generated method stub } }
Table of Contents
- Overview of libGDX
- Environment SetUp
- Creating Project in libGDX
- Importing Projects in Eclipse
- Executing demo project for Windows
- Executing demo project for Android
- Executing demo project for HTML5
- Developing a real flappy bird game remake game using libGDX from scratch
- Environment Set Up for Game
- Understand basics of Game
- Exploring States and Game State Manager
- Create Play State and Make Bird Fly
- Building Obstacles and flying through them
- Collision and sound effect
- Porting the Game in Android Device
- libGDX More possibilities to Brisky Demo