• Sponsored Links

Step 13: Step by Step libGDX Tutorial : Building Obstacles and flying through them

Share this :

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 :

  1. Add Obstacles i.e. adding pipes to play state
  2. 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 .

 

PlayState Obstacles

PlayState Obstacles

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

}

}

Leave a Reply

Your email address will not be published. Required fields are marked *

50 − 45 =