0
votes

I'm currently trying to make a breakout clone using java and libgdx. I'm currently experiencing trouble getting the ball to bounce off of the blocks at the appropriate angle. In short the problem I'm having is that the ball moves 12 pixels every frame and doesn't always line up with the edge of a brick. If anyone has any suggestions on a better way to move the ball or a different way to check collision it would be much appreciated!

Main game class

    package com.kyleparker.breakout;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.utils.Array;

public class BreakoutGameScreen implements ApplicationListener {
   Texture dropImage;
   Sound dropSound;
   Music rainMusic;
   SpriteBatch batch;
   OrthographicCamera camera;
   Rectangle bucket;
   Paddle paddle;
   //Brick bricks[];
   Array<Brick> bricks;
   Ball ball;

   @Override
   public void create() {
      // load the images for the droplet, 64x64 pixels
      dropImage = new Texture(Gdx.files.internal("droplet.png"));

      // load the drop sound effect and the rain background "music"
      dropSound = Gdx.audio.newSound(Gdx.files.internal("drop.wav"));
      rainMusic = Gdx.audio.newMusic(Gdx.files.internal("rain.mp3"));

      // start the playback of the background music immediately
      rainMusic.setLooping(true);
      rainMusic.play();

      // create the camera and the SpriteBatch
      camera = new OrthographicCamera();
      camera.setToOrtho(false, 1280, 720);
      batch = new SpriteBatch();

      paddle = new Paddle(new Texture(Gdx.files.internal("bucket.png")));

      bricks = new Array<Brick>();
      populateBricks();

      ball = new Ball(new Texture(Gdx.files.internal("bucket.png")), paddle, bricks);
   }

   private void populateBricks() {
       bricks.add(new Brick(200,100));
       for (int i = 0; i < 5; i++) {
           for (int j = 0; j <= 7; j++) {
               bricks.add(new Brick (j * 144 + 76, i * 80 + 300)); //Offsets each new brick
           }
       }
   }

   @Override
   public void render() {
       // clear the screen with a dark blue color. The
       // arguments to glClearColor are the red, green
       // blue and alpha component in the range [0,1]
       // of the color to be used to clear the screen.
       Gdx.gl.glClearColor(0, 0, 0.2f, 1);
       Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

       // tell the camera to update its matrices.
       camera.update();

       // tell the SpriteBatch to render in the
       // coordinate system specified by the camera.
       batch.setProjectionMatrix(camera.combined);

       // begin a new batch and draw the bucket and
       // all drops
       batch.begin();

       paddle.render(batch, camera);

       ball.move();
       ball.render(batch, camera);

       for (int x = bricks.size - 1; x > 0; x--) {
           bricks.get(x).render(batch,camera);
       }

       batch.end();     
   }

   @Override
   public void dispose() {
      // dispose of all the native resources
      dropImage.dispose();
      dropSound.dispose();
      rainMusic.dispose();
      batch.dispose();
      paddle.dispose();
   }

   @Override
   public void resize(int width, int height) {
   }

   @Override
   public void pause() {
   }

   @Override
   public void resume() {
   }
}

Ball class

    package com.kyleparker.breakout;

import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.utils.Array;

public class Ball{
   Texture ballImage;
   Rectangle ball;
   private int xdir;
   private int ydir;
   Paddle paddle;
   Array<Brick> bricks;
   final int BALL_SPEED = 12;

   public Ball(Texture ballImage, Paddle paddle, Array<Brick> bricks) {
      // load the ball image
      this.ballImage = ballImage;
      xdir = 1;
      ydir = -1;

      // create a Rectangle for the balls collision
      ball = new Rectangle();
      ball.x = 1280 / 2 - 64 / 2; // center the ball
      ball.y = 100; // put the ball 200px away from the bottom of the screen
      ball.width = 64;
      ball.height = 64;

      this.paddle = paddle;
      this.bricks = bricks;
   }

   public void render(SpriteBatch batch, OrthographicCamera camera) {
       // draw the paddle onto the batch of the level
       batch.draw(ballImage, ball.x, ball.y);
   }

   public void move() {
       ball.x += xdir * BALL_SPEED;
       ball.y += ydir * BALL_SPEED;

       if (ball.x <= 0) {
           setXDir(1);
       }

       if (ball.x >= 1280 - 64) {
           setXDir(-1);
       }

       if (ball.y <= 0) {
           setYDir(1);
       }

       if (ball.y >= 720 - 64) {
           setYDir(-1);
       }

       if (ball.overlaps(paddle.getRect())) {        
          setYDir(1);
       }

       for (int i = 0; i < bricks.size; i++) {
           if (ball.overlaps(bricks.get(i).getRect())) {
               if ((ball.x == (bricks.get(i).getRect().x + 128))) 
               { 
                   setXDir(1);
                   bricks.get(i).setDestroyed(true);
                   System.out.println("Collision RIGHT");
               }
               if (((ball.x + 64) == bricks.get(i).getRect().x)) 
               { 
                   setXDir(-1);
                   bricks.get(i).setDestroyed(true);
                   System.out.println("Collision LEFT");
               }
               if ((ball.y == (bricks.get(i).getRect().y + 64))) 
               { 
                   setYDir(1);
                   bricks.get(i).setDestroyed(true);
                   System.out.println("Collision TOP");
               }
               if (((ball.y + 64) == bricks.get(i).getRect().y)) 
               {                  
                   setYDir(-1);
                   bricks.get(i).setDestroyed(true);
                   System.out.println("Collision BOTTOM");
               }
           }
       }// end of for
   }

   public void setXDir(int x) {
       xdir = x;
   }

   public void setYDir(int y) {
       ydir = y;
   }

   public int getYDir() {
       return ydir;
   }

   public int getXDir() {
       return xdir;
   }

   public Rectangle getRect() {
       // return the collision rectangle for checking overlaps
       return ball;
   }

   public void dispose() {
      // dispose of all the native resources
      ballImage.dispose();
   }
}// end of class

Brick code just in case

package com.kyleparker.breakout;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Rectangle;

public class Brick{
   Texture brickImage;
   Rectangle brick;
   boolean destroyed;

   public Brick(int x, int y) {
       brickImage = new Texture(Gdx.files.internal("brick.png"));   

       // create a Rectangle for the bricks collision
       brick = new Rectangle();
       brick.x = x;
       brick.y = y;
       brick.width = 128;
       brick.height = 64;

       destroyed = false;
   }

   public void render(SpriteBatch batch, OrthographicCamera camera) {
       // draw the brick onto the batch of the level
       batch.draw(brickImage, brick.x, brick.y);
   }

   public boolean isDestroyed() {
       // return the collision rectangle for checking overlaps
       return destroyed;
   }

   public void setDestroyed(boolean destroyed)
   {
     this.destroyed = destroyed;

     if (this.destroyed == true) {
         dispose();
         brick.x = -1000;
         brick.y = -1000;
     }
   }

   public Rectangle getRect() {
       return brick;
   }

   public void dispose() {
      // dispose of all the native resources
      brickImage.dispose();
   }
}
2
Do shorten your pasted code to the importand lines in the next questions please.BennX

2 Answers

2
votes

Don't worry about the fact that the ball doesn't always line up with an object for which the collision needs handled -- that's not actually relevant. You can (and should) handle your collisions less 'precisely.' That is, the ball's path is fixed, so you can calculate its position at any future point. Check its position, calculate its position in the next frame (which you have to do to draw it anyway), and add some code to handle the collision that is going to happen, rather than trying to detect and handle the collision which has happened. You can slow down the ball if you really want a clean reflection, or you can speed up your framerate, or you can let the ball be partially 'absorbed' by the object before it reflects:

public class Ball {
. . .
    public void move() {
    . . .
        if (collisionObject.overlaps(new Rectangle(ball.x + xdir, ball.y + ydir, ball.width, ball.height))) {
            //a collision will have occurred in the next frame
            //handle the collision however you please
        }
    }
}

I also note that your BALL_SPEED field is inaccurately named. As presently coded, the ball always moves at a 45° angle, with a speed of about 17 pixels per frame (in that direction). You've coded its x- and y-offset as 12 pixels, but if (when?) you change the ball's direction, you'll find that the speed fluctuates wildly depending on what values are placed in for the xdir and ydir fields. For example, if you were to (somewhat) randomize these, but keep the rest of your code as-is, you might find that xdir = 2 and ydir = 4 on one instance, and xdir = 6 and ydir = 12 on another. Note these describe the same direction, but the second version will move three times as fast.

To properly handle the ball's direction and speed, assign an angle, and calculate the xdir and ydir values through the appropriate trigonometric functions (xdir = BALL_SPEED * Math.cos(ballAngle) and ydir = BALL_SPEED * Math.sin(ballAngle)).

-1
votes

I would use box2d for the whole thing. That you haven't used box2d probably means you have no experience in it, so that would be a little hurdle, but I'm sure you'll be able to wrap your head around it quickly. Here's a link for that: http://code.google.com/p/libgdx/wiki/PhysicsBox2D