3
\$\begingroup\$

I have implemented a little Flappy Bird clone with swing. I'm pretty new to Java, especially in graphic programming, so I'm really glad if someone an give me a feedback to my Code and maybe to movement of the bird, because it's not really smooth.

The bird is represented as a red square and the pipes are green rectangles. On my GameBoard i have a moving Bufferedimage as background, so it looks like the bird is moving and pipes were randomily placed.

MainWindow:

public class MainWindow {
private GameBoard gameBoard = new GameBoard();

public MainWindow() {
     EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {

                JFrame frame = new JFrame("FlappyBirds");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setSize(GameBoard.screenWidth,GameBoard.screenHeight);                   
                frame.add(gameBoard);                  
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
}

public static void main(String[] args) {
    new MainWindow();
 }
}

GameBoard:

public class GameBoard extends JPanel implements KeyListener {

private boolean running = true;
private int pressedKey;
private BufferedImage backgroundImage;
private int backgroundImageCoordX = 0;
private Bird bird = new Bird(new Rectangle(50, 250, 50, 50));
private Pipe pipe = new Pipe(bird);

public static int screenWidth;
public static int screenHeight;

public GameBoard() {

    try {
        backgroundImage = ImageIO.read(getClass().getResourceAsStream("Ressources/background.png"));
    } catch (IOException ex) {  // read Image from Ressourcefolder
        ex.printStackTrace();
    }
    screenWidth = backgroundImage.getWidth();
    screenHeight = backgroundImage.getHeight();

    addKeyListener(this);
    setFocusable(true);
    setPreferredSize(new Dimension(backgroundImage.getWidth(), backgroundImage.getHeight()));
    GameLoop gameLoop = new GameLoop();
    new Thread(gameLoop).start();
}

class GameLoop extends Thread {  // Gameloopfor udpdating all with 60 FPS
    private static final int FPS = 60;
    private static final long maxLoopTime = 1000 / FPS;

    @Override
    public void run() {
        long timestamp;
        long oldTimestamp;

        while (running == true) {

            oldTimestamp = System.currentTimeMillis();

            timestamp = System.currentTimeMillis();

            updateGameObjects();
            updateScreen();

            if (timestamp - oldTimestamp <= maxLoopTime) {

                try {
                    Thread.sleep(maxLoopTime - (timestamp - oldTimestamp));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

public void checkGameOver() { /* if bird touches pipes or border Gameloop stop running  */

    if (bird.getBirdShape().intersects(pipe.getPipeShapeUp()) 
            || bird.getBirdShape().intersects(pipe.getPipeShapeBottom())
            || bird.getBirdShape().intersects(pipe.getPipeShapeBottom2())
            || bird.getBirdShape().intersects(pipe.getPipeShapeUp2())|| (int) bird.getBirdShape().getY() <= 0
            || bird.getBirdShape().getY() + bird.getBirdShape().getHeight() >= backgroundImage.getHeight()) {           
        running = false;  
    }
}

public void updateGameObjects() { // moves all GameObjects
    bird.move();
    backgroundImageCoordX = backgroundImageCoordX - bird.getSpeed();
    pipe.move();

    if (bird.getJump() == true) {
        bird.makeJump();
        bird.setJump(false);
    }
    checkGameOver();
}

public void updateScreen() { // repaint Screen
    super.repaint();
}

@Override
public void paintComponent(Graphics g) { /*moving background Image, 
so it seems bird is moving, render bird and random Pipes*/
    super.paintComponent(g);
    int w = backgroundImage.getWidth();
    g.drawImage(backgroundImage, backgroundImageCoordX % w, 0, this);

    g.drawImage(backgroundImage, (backgroundImageCoordX % w) + backgroundImage.getWidth(), 0, this);
    bird.renderBird(g);
    pipe.renderPipe(g);

    if (backgroundImageCoordX >= 0) { // draw new Image and append it to current 
        g.drawImage(backgroundImage, (backgroundImageCoordX % w) - backgroundImage.getWidth(), 0, this);
        bird.renderBird(g);
        pipe.renderPipe(g);
    }

    if (pipe.getPipeShapeUp2().getX() <= 0) { // create  new Pipes
        pipe = new Pipe(bird);
        pipe.renderPipe(g);
    }
}

@Override
public void keyPressed(KeyEvent e) { // jump bird
    pressedKey = e.getKeyCode();

    if (pressedKey == KeyEvent.VK_UP) {
        bird.setJump(true);
    }
}

@Override
public void keyReleased(KeyEvent arg0) {}               


@Override
public void keyTyped(KeyEvent arg0) {}  

}

Bird:

public class Bird {

private Rectangle birdShape;
private final int speed = 5;
private boolean jump = false;

public Bird(Rectangle birdShape) {
    this.birdShape = birdShape;
}

public void move() {
    birdShape.setLocation((int) birdShape.getX(), (int) birdShape.getY() + speed);
}

public void makeJump() {  /* bird goes up per default it falling down*/
    if (jump == true) {
        birdShape.setLocation((int) birdShape.getX(), (int) birdShape.getY() - 75);
    }
}

public int getSpeed() {
    return this.speed;
}

public boolean getJump() {
    return this.jump;
}

public void setJump(boolean jump) {
    this.jump = jump;
}

public Rectangle getBirdShape() {
    return this.birdShape;
}

public void renderBird(Graphics g) {
    g.setColor(Color.RED);
    g.fillRect((int) birdShape.getX(), (int) birdShape.getY(), (int) birdShape.getWidth(),
            (int) birdShape.getHeight());
 }
}  

Pipe:

public class Pipe {

private Bird bird;

private Rectangle pipeShapeBottom;
private Rectangle pipeShapeUp;
private Rectangle pipeShapeBottom2;
private Rectangle pipeShapeUp2;

private final Random random = new Random();
private final int pipeWidth = 50;

public Pipe(Bird bird) { /* creates 2 random Pipes on the Screen, if two pipes are out of screen,create new 2 pipes*/

    this.bird = bird;

    int heightPipeUp = random.nextInt(120) + 100;
    int heightPipeBottom = random.nextInt(175) + 125;

    int randomX = random.nextInt(150) + 225;

    pipeShapeBottom = new Rectangle(randomX, 0, pipeWidth, heightPipeBottom);
    pipeShapeUp = new Rectangle(randomX, GameBoard.screenWidth - heightPipeUp, pipeWidth, heightPipeUp);

    heightPipeUp = random.nextInt(200) + 100;
    heightPipeBottom = random.nextInt(175) + 125;

    int randomX2 = random.nextInt(250) + 150;
    pipeShapeBottom2 = new Rectangle(randomX + randomX2, 0, pipeWidth, heightPipeBottom);
    pipeShapeUp2 = new Rectangle(randomX + randomX2, GameBoard.screenWidth - heightPipeUp, pipeWidth, heightPipeUp);
}

public Rectangle getPipeShapeUp() {
    return this.pipeShapeUp;
}

public Rectangle getPipeShapeBottom() {
    return this.pipeShapeBottom;
}

public Rectangle getPipeShapeUp2() {
    return this.pipeShapeUp2;
}

public Rectangle getPipeShapeBottom2() {
    return this.pipeShapeBottom2;
}

public void renderPipe(Graphics g) {
    g.setColor(Color.GREEN);

    g.fillRect((int) pipeShapeUp.getX(), (int) pipeShapeUp.getY(), (int) pipeShapeUp.getWidth(),
            (int) pipeShapeUp.getHeight());
    g.fillRect((int) pipeShapeBottom.getX(), (int) pipeShapeBottom.getY(), (int) pipeShapeBottom.getWidth(),
            (int) pipeShapeBottom.getHeight());

    g.fillRect((int) pipeShapeUp2.getX(), (int) pipeShapeUp2.getY(), (int) pipeShapeUp2.getWidth(),
            (int) pipeShapeUp2.getHeight());
    g.fillRect((int) pipeShapeBottom2.getX(), (int) pipeShapeBottom2.getY(), (int) pipeShapeBottom2.getWidth(),
            (int) pipeShapeBottom2.getHeight());
}

public void move() { // Pipes moves with same v as background v and bird
    pipeShapeBottom.setLocation((int) pipeShapeBottom.getX() - bird.getSpeed(), (int) pipeShapeBottom.getY());
    pipeShapeUp.setLocation((int) pipeShapeUp.getX() - bird.getSpeed(), (int) pipeShapeUp.getY());

    pipeShapeBottom2.setLocation((int) pipeShapeBottom2.getX() - bird.getSpeed(), (int) pipeShapeBottom2.getY());
    pipeShapeUp2.setLocation((int) pipeShapeUp2.getX() - bird.getSpeed(), (int) pipeShapeUp2.getY());
 }
}

Any help would be appreciated.

\$\endgroup\$
4
\$\begingroup\$

Not bad for a start!

Some details though:

  • Starting the actual program up in the constructor of MainWindow (or actually: any business behaviour in a constructor) is unexpected. What if someone just wants to instantiate the window, but not start the game just yet? Better: do the actual initialization in the constructor while moving the start-the-game command (in fact: setVisible(true) in this case) to another method.
  • Error handling in GameBoard constructor: ex.printStackTrace() does not do anything useful, and if the image cannot be loaded, your program will crash will a NullPointerException in the following line. In this case, as the presence of the image is clearly absolutely necessary for the program to run, better print a message and do a hard exit (System.exit(1) or the like)
  • Game loop: if I am not totally mistaken, the second timestamp should be determined after the calls to updateGameObjects() and updateScreen()...
  • Game loop 2: once more, exception handling. What good does e.printStackTrace() do? If there is a legal and expected way of interrupting your loop thread, you should handle it in a graceful way. If there is not, a thread interrupt may well be handled as program exit again.
  • OO: try to treat your objects as black boxes as much as possible, which includes their behaviour in specific cases. Here, I'd try to move the jumping execution logic to bird's move() method instead of handling it in updateGameObjects(). Naturally, this might make it necessary to handle the background animation / position in a different way.
  • OO2 - decoupling: you pass the bird to the pipe just to use the (constant) speed. You could instread pass the speed in, so that the pipe might act without any birds or in conjunction with planes/superman/flying dogs. This is unnecessary coupling.

All in all: quite well done. Disclaimer to the points above: I only read the source and have not actually tried anything out.

\$\endgroup\$
  • \$\begingroup\$ Ok, thanks a lot for your advices. I will try to improve my program :) \$\endgroup\$ – Marten Jun 13 '18 at 13:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.