This is my first time ever making a game. It is a pong-like game, where the user has a paddle and has to prevent the ball from passing them. Since this is my first game, I want any recommendations/improvements/advice from more experienced people. The advice can range from simple Java errors I've missed to more game-development technicalities. I seek to make more advanced games in the future so any advice about this simple game can help me a lot.
In addition, I've noticed some bugs in the program, but I cannot seem to find them.
- The game has a short but noticeable lag spike.
- The ball gets stuck in the paddle, and that causes the score to rapidly increase.
Here is the code:
Main.java
import java.awt.EventQueue;
import javax.swing.JFrame;
public class Main extends JFrame {
private static final long serialVersionUID = 1L;
public Main() {
initUI();
}
private void initUI() {
add(new PaintSurface());
this.addKeyListener(new KeyEvents());
setTitle("Pong");
setResizable(false);
pack();
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame main = new Main();
main.setVisible(true);
});
}
}
PaintSurface.java
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JPanel;
public class PaintSurface extends JPanel {
private static final long serialVersionUID = 1L;
private static final Dimension paintSurfaceDimension = new Dimension(400, 600);
public static Ball ball;
public static Paddle paddle;
private Timer timer;
public static Dimension getPaintSurfaceDimension() {
return paintSurfaceDimension;
}
public PaintSurface() {
initPaintSurface();
}
private void initPaintSurface() {
setBackground(Color.BLACK);
setPreferredSize(paintSurfaceDimension);
generateBall();
generatePaddle();
timer = new Timer();
timer.scheduleAtFixedRate(new ScheduleTask(), 100, 17);
}
private void generateBall() {
final int diameter = 40;
int x = 0, y = 0;
ball = new Ball(x, y, diameter);
}
private void generatePaddle() {
final int width = 70, height = 15;
int x = 5;
final int y = paintSurfaceDimension.height - height - 5;
paddle = new Paddle(x, y, width, height);
}
public void paintComponent(Graphics g) {
Graphics2D graphics = (Graphics2D) g;
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
super.paintComponent(graphics);
ball.setGraphics(graphics);
paddle.setGraphics(graphics);
ball.drawBall();
paddle.drawPaddle();
graphics.setFont(new Font("Calibri", Font.PLAIN, 30));
graphics.drawString(Score.score + "", 200, 50);
graphics.dispose();
}
private class ScheduleTask extends TimerTask {
public void run() {
ball.move();
paddle.move();
System.out.println(Score.score);
repaint();
}
}
}
Ball.java
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.geom.Rectangle2D;
public class Ball extends Rectangle2D.Float {
private static final long serialVersionUID = 1L;
private Color ballColor = new Color(255, 255, 255);
float speedX = 10, speedY = 10;
private Graphics2D graphics;
public void setGraphics(Graphics2D graphics) {
this.graphics = graphics;
}
public Ball(int x, int y, float diameter) {
super(x, y, diameter, diameter);
}
public void move() {
collisionDetection();
scoreDetection();
x += speedX;
y += speedY;
}
private void collisionDetection() {
if(x < 0 || x > PaintSurface.getPaintSurfaceDimension().width - width)
speedX = -speedX;
if(y < 0 || y > PaintSurface.getPaintSurfaceDimension().height - height)
speedY = -speedY;
if(this.intersects(PaintSurface.paddle)) {
speedY = -speedY;
}
}
private void scoreDetection() {
if(y >= PaintSurface.getPaintSurfaceDimension().height - PaintSurface.paddle.height - 15)
Score.score++;
}
public void drawBall() {
graphics.setColor(ballColor);
graphics.fill(this);
Toolkit.getDefaultToolkit().sync();
}
}
Paddle.java
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
public class Paddle extends Rectangle2D.Float {
private static final long serialVersionUID = 1L;
private Color paddleColor = new Color(255, 255, 255);
private float speedX = 10;
public boolean movingLeft = false, movingRight = false;
private Graphics2D graphics;
public void setGraphics(Graphics2D graphics) {
this.graphics = graphics;
}
public Paddle(int x, int y, int width, int height) {
super(x, y, width, height);
}
public void move() {
collisionDetection();
if(movingLeft)
x -= speedX;
if(movingRight)
x += speedX;
}
private void collisionDetection() {
if(x < 0) x = 0;
if(x > PaintSurface.getPaintSurfaceDimension().width - width) x =
PaintSurface.getPaintSurfaceDimension().width - width;
}
public void drawPaddle() {
graphics.setColor(paddleColor);
graphics.fill(this);
}
}
KeyEvents.java
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class KeyEvents implements KeyListener {
public void keyTyped(KeyEvent event) {}
public void keyPressed(KeyEvent event) {
switch(event.getKeyCode()) {
case KeyEvent.VK_LEFT: PaintSurface.paddle.movingLeft = true; break;
case KeyEvent.VK_RIGHT: PaintSurface.paddle.movingRight = true; break;
}
}
public void keyReleased(KeyEvent event) {
switch(event.getKeyCode()) {
case KeyEvent.VK_LEFT: PaintSurface.paddle.movingLeft = false; break;
case KeyEvent.VK_RIGHT: PaintSurface.paddle.movingRight = false; break;
}
}
}
Score.java
public class Score {
public static int score;
}
Thank you. Any advice is appreciated.