Search code examples
javaswingjlabelrunnablepong

Using a JLabel to continuously update a score in Java, JFrame-based game


I'm building a little "pong" game in Java.

I'm trying to add a scorekeeper up top that shows the updated score (+1) everytime the player saves the ball with the paddle.

I'm trying to use a JLabel but the problem is that I can't think of a way to continuously update the JLabel each time the paddle is hit.

Any ideas?

My code:

MainPanel Class (the one with the Paddle and Ball and Label)

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

//import swing.graphics.BounceFrame;
//import swing.graphics.Circle;

public class MainPanel extends JPanel implements ActionListener, KeyListener, Runnable{

    public Paddle paddle;
    public Ball ball;

    public MainPanel(){
        ball = new Ball(50, 50, 10); //centerX, centerY, radius
        setSize(300, 300);
        paddle = new Paddle();
        JLabel scoreKeeper = new JLabel("Score" + ball.getScore());
        add(scoreKeeper);
        Thread thread = new Thread(this);
        thread.start();
    }

    public void paint(Graphics g) {
        super.paint(g);
        Graphics2D g2 = (Graphics2D)g;
        paddle.draw(g2);
        ball.draw(g2);
    }

    public void actionPerformed(ActionEvent e) {
        String direction = e.getActionCommand();

        switch(direction){
            case "left": Paddle.movePaddleLeft(); break;
            case "right": Paddle.movePaddleRight(); break;
        }
        this.repaint();
    }

    public void run() {
        try {
            while(true){
                    ball.move(getBounds());
                repaint();
                Thread.sleep(500/30);
            }
            }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == 37){
            Paddle.movePaddleLeft();
        }
        if (e.getKeyCode() == 39){
            Paddle.movePaddleRight();
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {

    }

    @Override
    public void keyTyped(KeyEvent e) {

    }
}

And my Ball class:

import java.awt.Color; import java.awt.Graphics2D; import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; import java.util.Random;

public class Ball {

    private Ellipse2D ball;
    private double radius;
    private double ballCircumference;
    private Color color;

    private double x;
    private double y;
    private double dx = 5;
    private double dy = 5;

    private int score = 0;
    public int getScore() {
        return score;
    }
    //Boundaries to determine if ball is hit by paddle
    private double criticalBoundaryX;
    private double criticalBoundaryY1; 
    private double criticalBoundaryY2;
    private double paddleHalfwayPoint;
    private boolean inGame = true;
    public void recalculateCriticals(){
        criticalBoundaryX = Paddle.getYPosition() - ballCircumference;
        criticalBoundaryY1 = Paddle.getXPosition()- ballCircumference; //Left boundary
        criticalBoundaryY2 = Paddle.getXPosition()+Paddle.getPaddleWidth()+ballCircumference; //Right Boundary
        paddleHalfwayPoint = (Paddle.getXPosition()+Paddle.getPaddleWidth())/2;
    }

    public Ball(int centerX, int centerY, int radius) {
        this.x = centerX - radius;
        this.y = centerY - radius;
        this.radius = radius;
        ballCircumference = 2*radius;
        Random randomRGB = new Random();
        color = new Color(randomRGB.nextInt(255), randomRGB.nextInt(255), randomRGB.nextInt(255));
        this.ball = new Ellipse2D.Double(x, y, 2*radius, 2*radius);
    }

    public void move(Rectangle2D bounds) {
        recalculateCriticals();
        x += dx;
        y += dy;
        if (x < bounds.getMinX()) {
            x = bounds.getMinX();
            dx = -dx;
        }
        if (x + 2*radius >= bounds.getMaxX()) {
            //System.out.println(bounds.getMaxX());
            x = bounds.getMaxX() - 2*radius;
            dx = -dx;
        }
        if (y < bounds.getMinY()) {
            y = bounds.getMinY();
            dy = -dy;
        }
        if (y > criticalBoundaryX){
             if (x < criticalBoundaryY1 || x > criticalBoundaryY2){
                 inGame = false;
             }
             if (!inGame && hittingEdge(x))
                 dx = -dx;
        }
        if (y > criticalBoundaryX && inGame){ //When it hits the paddle
            changeColor();
            score++;
            y = criticalBoundaryX;
            dy = -dy;
         }
        if (y > bounds.getMaxY()){
            System.out.println("Game Over");
            System.exit(0);
        }
        recalculateCriticals();
        ball.setFrame(x, y, 2*radius, 2*radius);
    }

    public boolean onPaddle(double x){
        return ((x > Paddle.getXPosition()) && (x < Paddle.getXPosition()+Paddle.getPaddleWidth()) && (y > Paddle.getYPosition()-10));
    }
    public boolean hittingEdge(double x){
        return ((x >= criticalBoundaryY1 && x < paddleHalfwayPoint)
                ||
                (x <= criticalBoundaryY1 && x > paddleHalfwayPoint)); //Return true if x is hitting the side edge of the paddle
    }
    public void changeColor(){
        Random randomColor = new Random();
        color = new Color(randomColor.nextInt(255), randomColor.nextInt(255), randomColor.nextInt(255));
    }

    public void draw(Graphics2D g2) {
        g2.setColor(color);
        g2.fill(ball);
    }
}

Solution

  • The "Java way" of doing this would be to define a listener interface, for example:

    public interface BallListener {
      void paddleHit();
    }

    In the Ball class, you should add a field

    private List<BallListener> listeners;

    as well as methods

    public void addBallListener(BallListener l) { listeners.add(l); }
    public void removeBallListener(BallListener l) { listeners.remove(l); }

    When the paddle is hit, you go:

    for (BallListener l : listeners)
      l.paddleHit();

    The main class should implement the BallListener interface, and register itself with the ball (ball.addBallListener(this)).

    This approach also enables you to, when needed, inform other parts of your program about different events that happen to the ball (i.e. add a new method to BallListener for each event you'd like to signal).