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:
- Object Creation: Initialize all game objects
- Input Setup: Enable keyboard focus and add listener
- Display Setup: Set panel dimensions
- 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:
- Create Off-Screen Image:
createImage()
creates a buffer - Draw to Buffer: All drawing operations happen off-screen
- Display Buffer: Single operation copies buffer to screen
- 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:
- Target 60 FPS:
amountOfTicks = 60.0
- Delta Calculation: Tracks time since last update
- Frame-Rate Independence: Updates only when enough time has passed
- 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:
- Detection: Uses
Rectangle.intersects()
method - Direction Change: Reverse X direction, maintain/modify Y
- Speed Increase: Game gets progressively harder
- 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
- Component Pattern: Each game object handles its own behavior
- Game Loop Pattern: Continuous update-render cycle
- Observer Pattern: KeyListener responds to input events
- 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
- Performance: What would happen if the game loop ran on the main UI thread?
- Physics: How could you make the ball bouncing more realistic?
- Architecture: What if you wanted to add power-ups? Where would you handle them?
- Debugging: How could you add a pause feature to this game loop?
Next: Explore the individual game objects: Ball.java, Paddle.java, Score.java