GamePanel.java - Game Engine

The heart of the Pong game - contains the game loop, rendering, collision detection, and input handling. This is where all the game logic comes together.

Purpose: Main game engine that coordinates all game objects, handles the game loop, processes input, and manages collision detection.

Source Code

  1import java.awt.*;
  2import java.awt.event.*;
  3import java.util.*;
  4import javax.swing.*;
  5
  6public class GamePanel extends JPanel implements Runnable{
  7
  8	static final int GAME_WIDTH = 1000;
  9	static final int GAME_HEIGHT = (int)(GAME_WIDTH * (0.5555));
 10	static final Dimension SCREEN_SIZE = new Dimension(GAME_WIDTH,GAME_HEIGHT);
 11	static final int BALL_DIAMETER = 20;
 12	static final int PADDLE_WIDTH = 25;
 13	static final int PADDLE_HEIGHT = 100;
 14	Thread gameThread;
 15	Image image;
 16	Graphics graphics;
 17	Random random;
 18	Paddle paddle1;
 19	Paddle paddle2;
 20	Ball ball;
 21	Score score;
 22	
 23	GamePanel(){
 24		newPaddles();
 25		newBall();
 26		score = new Score(GAME_WIDTH,GAME_HEIGHT);
 27		this.setFocusable(true);
 28		this.addKeyListener(new AL());
 29		this.setPreferredSize(SCREEN_SIZE);
 30		
 31		gameThread = new Thread(this);
 32		gameThread.start();
 33	}
 34	
 35	public void newBall() {
 36		random = new Random();
 37		ball = new Ball((GAME_WIDTH/2)-(BALL_DIAMETER/2),random.nextInt(GAME_HEIGHT-BALL_DIAMETER),BALL_DIAMETER,BALL_DIAMETER);
 38	}
 39	public void newPaddles() {
 40		paddle1 = new Paddle(0,(GAME_HEIGHT/2)-(PADDLE_HEIGHT/2),PADDLE_WIDTH,PADDLE_HEIGHT,1);
 41		paddle2 = new Paddle(GAME_WIDTH-PADDLE_WIDTH,(GAME_HEIGHT/2)-(PADDLE_HEIGHT/2),PADDLE_WIDTH,PADDLE_HEIGHT,2);
 42	}
 43	public void paint(Graphics g) {
 44		image = createImage(getWidth(),getHeight());
 45		graphics = image.getGraphics();
 46		draw(graphics);
 47		g.drawImage(image,0,0,this);
 48	}
 49	public void draw(Graphics g) {
 50		paddle1.draw(g);
 51		paddle2.draw(g);
 52		ball.draw(g);
 53		score.draw(g);
 54		Toolkit.getDefaultToolkit().sync(); // Helps with animation smoothness
 55	}
 56	public void move() {
 57		paddle1.move();
 58		paddle2.move();
 59		ball.move();
 60	}
 61	public void checkCollision() {
 62		
 63		//bounce ball off top & bottom window edges
 64		if(ball.y <=0) {
 65			ball.setYDirection(-ball.yVelocity);
 66		}
 67		if(ball.y >= GAME_HEIGHT-BALL_DIAMETER) {
 68			ball.setYDirection(-ball.yVelocity);
 69		}
 70		//bounce ball off paddles
 71		if(ball.intersects(paddle1)) {
 72			ball.xVelocity = Math.abs(ball.xVelocity);
 73			ball.xVelocity++; //optional for more difficulty
 74			if(ball.yVelocity>0)
 75				ball.yVelocity++; //optional for more difficulty
 76			else
 77				ball.yVelocity--;
 78			ball.setXDirection(ball.xVelocity);
 79			ball.setYDirection(ball.yVelocity);
 80		}
 81		if(ball.intersects(paddle2)) {
 82			ball.xVelocity = Math.abs(ball.xVelocity);
 83			ball.xVelocity++; //optional for more difficulty
 84			if(ball.yVelocity>0)
 85				ball.yVelocity++; //optional for more difficulty
 86			else
 87				ball.yVelocity--;
 88			ball.setXDirection(-ball.xVelocity);
 89			ball.setYDirection(ball.yVelocity);
 90		}
 91		//stops paddles at window edges
 92		if(paddle1.y<=0)
 93			paddle1.y=0;
 94		if(paddle1.y >= (GAME_HEIGHT-PADDLE_HEIGHT))
 95			paddle1.y = GAME_HEIGHT-PADDLE_HEIGHT;
 96		if(paddle2.y<=0)
 97			paddle2.y=0;
 98		if(paddle2.y >= (GAME_HEIGHT-PADDLE_HEIGHT))
 99			paddle2.y = GAME_HEIGHT-PADDLE_HEIGHT;
100		//give a player 1 point and creates new paddles & ball
101		if(ball.x <=0) {
102			score.player2++;
103			newPaddles();
104			newBall();
105			System.out.println("Player 2: "+score.player2);
106		}
107		if(ball.x >= GAME_WIDTH-BALL_DIAMETER) {
108			score.player1++;
109			newPaddles();
110			newBall();
111			System.out.println("Player 1: "+score.player1);
112		}
113	}
114	public void run() {
115		//game loop
116		long lastTime = System.nanoTime();
117		double amountOfTicks =60.0;
118		double ns = 1000000000 / amountOfTicks;
119		double delta = 0;
120		while(true) {
121			long now = System.nanoTime();
122			delta += (now -lastTime)/ns;
123			lastTime = now;
124			if(delta >=1) {
125				move();
126				checkCollision();
127				repaint();
128				delta--;
129			}
130		}
131	}
132	public class AL extends KeyAdapter{
133		public void keyPressed(KeyEvent e) {
134			paddle1.keyPressed(e);
135			paddle2.keyPressed(e);
136		}
137		public void keyReleased(KeyEvent e) {
138			paddle1.keyReleased(e);
139			paddle2.keyReleased(e);
140		}
141	}
142}

Architectural Overview

This class demonstrates several important programming patterns:

Game Loop Architecture

Threading with Runnable
1public class GamePanel extends JPanel implements Runnable
  • Extends JPanel: Provides drawing surface and component functionality
  • Implements Runnable: Enables the game loop to run in a separate thread
  • Thread Safety: Keeps the game running without blocking the UI thread
Game Constants
1static final int GAME_WIDTH = 1000;
2static final int GAME_HEIGHT = (int)(GAME_WIDTH * (0.5555));
3static final Dimension SCREEN_SIZE = new Dimension(GAME_WIDTH,GAME_HEIGHT);
  • Aspect Ratio: Height calculated as 55.55% of width (≈ 16:9 ratio)
  • Static Final: Constants shared across all instances
  • Dimension Object: Used for setting preferred panel size

Core Game Systems

1. Initialization System

Constructor Setup
 1GamePanel(){
 2    newPaddles();           // Create paddle objects
 3    newBall();              // Create ball object
 4    score = new Score(...); // Create score tracker
 5    this.setFocusable(true); // Enable keyboard input
 6    this.addKeyListener(new AL()); // Handle key events
 7    this.setPreferredSize(SCREEN_SIZE); // Set panel size
 8    
 9    gameThread = new Thread(this); // Create game thread
10    gameThread.start();     // Start the game loop
11}

Key Setup Steps:

  1. Object Creation: Initialize all game objects
  2. Input Setup: Enable keyboard focus and add listener
  3. Display Setup: Set panel dimensions
  4. Threading: Start the game loop in separate thread

2. Rendering System

Double Buffering
1public void paint(Graphics g) {
2    image = createImage(getWidth(),getHeight());
3    graphics = image.getGraphics();
4    draw(graphics);
5    g.drawImage(image,0,0,this);
6}

Double Buffering Process:

  1. Create Off-Screen Image: createImage() creates a buffer
  2. Draw to Buffer: All drawing operations happen off-screen
  3. Display Buffer: Single operation copies buffer to screen
  4. Prevents Flickering: No partial draws visible to user
Drawing Coordination
1public void draw(Graphics g) {
2    paddle1.draw(g);
3    paddle2.draw(g);
4    ball.draw(g);
5    score.draw(g);
6    Toolkit.getDefaultToolkit().sync();
7}

Rendering Order:

  • Paddles drawn first (background objects)
  • Ball drawn on top
  • Score overlay drawn last
  • sync() ensures smooth animation timing

3. Game Loop System

Delta Time Game Loop
 1public void run() {
 2    long lastTime = System.nanoTime();
 3    double amountOfTicks = 60.0;
 4    double ns = 1000000000 / amountOfTicks;
 5    double delta = 0;
 6    while(true) {
 7        long now = System.nanoTime();
 8        delta += (now - lastTime) / ns;
 9        lastTime = now;
10        if(delta >= 1) {
11            move();
12            checkCollision();
13            repaint();
14            delta--;
15        }
16    }
17}

Game Loop Breakdown:

  1. Target 60 FPS: amountOfTicks = 60.0
  2. Delta Calculation: Tracks time since last update
  3. Frame-Rate Independence: Updates only when enough time has passed
  4. Update Sequence: Move → Check Collisions → Repaint

4. Collision Detection System

Boundary Collisions
1// Ball bouncing off top and bottom
2if(ball.y <= 0) {
3    ball.setYDirection(-ball.yVelocity);
4}
5if(ball.y >= GAME_HEIGHT - BALL_DIAMETER) {
6    ball.setYDirection(-ball.yVelocity);
7}

Boundary Physics:

  • Top/Bottom: Reverse Y velocity (bounce vertically)
  • Left/Right: Trigger scoring and reset
  • Simple but Effective: Basic physics simulation
Paddle Collisions
1if(ball.intersects(paddle1)) {
2    ball.xVelocity = Math.abs(ball.xVelocity);
3    ball.xVelocity++; // Increase difficulty
4    // ... velocity adjustments
5    ball.setXDirection(ball.xVelocity);
6    ball.setYDirection(ball.yVelocity);
7}

Collision Response:

  1. Detection: Uses Rectangle.intersects() method
  2. Direction Change: Reverse X direction, maintain/modify Y
  3. Speed Increase: Game gets progressively harder
  4. Physics Update: Apply new velocities to ball
Paddle Boundaries
1// Prevent paddles from moving off-screen
2if(paddle1.y <= 0)
3    paddle1.y = 0;
4if(paddle1.y >= (GAME_HEIGHT - PADDLE_HEIGHT))
5    paddle1.y = GAME_HEIGHT - PADDLE_HEIGHT;

Constraint System:

  • Clamp Position: Keep paddles within screen bounds
  • Direct Position Setting: Override position if out of bounds
  • Applied to Both Paddles: Consistent behavior

5. Input Handling System

Event Delegation
 1public class AL extends KeyAdapter {
 2    public void keyPressed(KeyEvent e) {
 3        paddle1.keyPressed(e);
 4        paddle2.keyPressed(e);
 5    }
 6    public void keyReleased(KeyEvent e) {
 7        paddle1.keyReleased(e);
 8        paddle2.keyReleased(e);
 9    }
10}

Input Architecture:

  • KeyAdapter: Convenient base class (empty implementations)
  • Event Delegation: Forward events to appropriate objects
  • Decoupled Design: Paddles handle their own key mappings
  • Both Events: Handles press and release for smooth movement

Advanced Concepts

Performance Optimizations

Graphics Optimization:

  • Double buffering prevents screen flickering
  • Toolkit.sync() ensures smooth animation
  • Fixed frame rate prevents excessive CPU usage

Design Patterns Used

  1. Component Pattern: Each game object handles its own behavior
  2. Game Loop Pattern: Continuous update-render cycle
  3. Observer Pattern: KeyListener responds to input events
  4. Template Method: Consistent object interface (move, draw methods)

Threading Considerations

Thread Safety: The game loop runs in a separate thread from the UI thread. This prevents the game from blocking user interface operations but requires careful consideration of shared state.

Study Questions

  1. Performance: What would happen if the game loop ran on the main UI thread?
  2. Physics: How could you make the ball bouncing more realistic?
  3. Architecture: What if you wanted to add power-ups? Where would you handle them?
  4. Debugging: How could you add a pause feature to this game loop?

Next: Explore the individual game objects: Ball.java, Paddle.java, Score.java