Lab Manual X · CSE 202
Animation with Threads
Animate bouncing balls: a worker thread updates positions about 60 times a second, a custom panel draws each frame with double buffering, and Start, Pause and Reset buttons control it.
← → move between slides · F full screen · M slide list
Before we start
Why this lab
A film is just still pictures shown quickly. How can a Java program make a ball look like it is moving?
Draw it, move it a little, draw it again — about 60 times a second. A worker thread runs that loop while Swing keeps the window responsive.
Lab 10 of 10
Lab at a glance
After this lab you can
- Explain frame-based animation
- Implement an update–repaint–sleep animation loop in a worker thread
- Control the frame rate with
Thread.sleep() - Draw frames with paintComponent and Graphics2D
- Detect collisions with walls and between balls
- Control the animation with Start, Pause and Reset buttons
Before you start
- Lab 7: threads and Runnable
- Lab 8: synchronized methods
- Lab 9: JFrame, JPanel, paintComponent, ActionListener
Plan for the session
- Theory recap
- Walk through the program, section by section, and run it
- In-lab tasks, shown to the instructor
- Report exercises at home
Lab 10 of 10
Contents
Select a part to jump to it.
Theory 1 of 5
Frame-based animation
- Each frame: update the state, redraw, wait. Repeated quickly, the eye sees smooth motion.
- About 16 ms per frame gives roughly 60 frames per second.
Frame-based animation
x = x + vx; y = y + vy;
Theory 2 of 5
The worker thread and the EDT
while (true) {
panel.updateAnimation(); // move the balls
panel.repaint(); // ask Swing to draw
Thread.sleep(16); // ~60 FPS
}- The loop runs in
AnimationThreadso the Event Dispatch Thread stays free for clicks and painting. - Both threads touch the balls, so AnimationPanel’s methods are
synchronized.
Theory 3 of 5
Double buffering
BufferedImage buffer = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); Graphics2D g2d = buffer.createGraphics(); // ... draw everything on g2d ... g.drawImage(buffer, 0, 0, null);
- Draw the whole frame off-screen, then copy it at once: no half-drawn frames, no flicker.
- Swing already double-buffers; doing it by hand shows the idea.
Theory 4 of 5
Collisions
- Wall: reverse the velocity component. Ball–ball: centres closer than the sum of radii.
Collisions
if (x - radius < 0 || x + radius > panelWidth) vx = -vx; // wall
double d = Math.sqrt(dx * dx + dy * dy);
if (d <= r1 + r2) { /* balls touch: swap velocities */ }
Theory 5 of 5
Applets and modern animation
- Classic textbook animations used applets; browsers dropped them, so this lab animates in a JFrame.
- The ideas — update state, repaint, control timing — are exactly the same.
Implementation design
Program design
Implementation design
Execution flow
- main creates AnimationApp on the EDT
- AnimationApp builds the panel and buttons and starts a daemon AnimationThread
- The thread loops: updateAnimation(), repaint(), sleep the rest of the 16 ms frame
- updateAnimation moves balls only while running and handles collisions
- Start / Pause flip the running flag; Reset restores the three balls
- Closing the window ends the program (the thread is a daemon)
Ball = model, AnimationPanel = rendering + state, AnimationThread = loop, AnimationApp = controls.
Code walkthrough 1 of 10 · Ball.java
Moving and bouncing
public void update(int panelWidth, int panelHeight) {
x = x + vx;
y = y + vy;
if (x - radius < 0 || x + radius > panelWidth) {
vx = -vx;
x = Math.max(radius, Math.min(panelWidth - radius, x));
}
if (y - radius < 0 || y + radius > panelHeight) {
vy = -vy;
y = Math.max(radius, Math.min(panelHeight - radius, y));
}
}- Add the velocity each frame; reverse it at a wall and keep the ball inside.
Code walkthrough 2 of 10 · Ball.java
Ball-to-ball collisions
public boolean collidesWith(Ball other) {
double dx = this.x - other.x;
double dy = this.y - other.y;
double distance = Math.sqrt(dx * dx + dy * dy);
return distance <= this.radius + other.radius;
}
public void bounceOff(Ball other) {
double tempVx = this.vx;
Code walkthrough 2 of 10 · Ball.java
Ball-to-ball collisions (cont.)
double tempVy = this.vy;
this.vx = other.vx;
this.vy = other.vy;
other.vx = tempVx;
other.vy = tempVy;
}- Touching when the centres are closer than the radii added together.
- Swapping velocities is a simple bounce.
Code walkthrough 3 of 10 · AnimationPanel.java
Double buffering: draw off screen
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (getWidth() <= 0 || getHeight() <= 0) {
return;
}
BufferedImage buffer = new BufferedImage(getWidth(), getHeight(),
BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = buffer.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, getWidth(), getHeight());
drawGrid(g2d);- Draw the whole frame into a BufferedImage first, so the screen never shows half a frame.
Code walkthrough 4 of 10 · AnimationPanel.java
…then copy it at once
synchronized (this) {
for (Ball ball : balls) {
ball.draw(g2d);
}
g2d.setColor(Color.BLACK);
g2d.drawString("Balls: " + balls.size() + " | "
+ (running ? "Running" : "Paused"), 10, getHeight() - 10);
}
g.drawImage(buffer, 0, 0, null);
g2d.dispose();- The synchronized block reads the balls safely while the worker thread may update them.
- One drawImage call shows the finished frame.
Code walkthrough 5 of 10 · AnimationPanel.java
One animation step: move
public synchronized void updateAnimation() {
if (!running) {
return;
}
int width = getWidth();
int height = getHeight();
if (width <= 0 || height <= 0) {
return;
}
for (Ball ball : balls) {
ball.update(width, height);
}- Nothing happens while paused.
- Each ball moves and bounces off the walls.
Code walkthrough 6 of 10 · AnimationPanel.java
One animation step: collide
for (int i = 0; i < balls.size(); i++) {
for (int j = i + 1; j < balls.size(); j++) {
Ball first = balls.get(i);
Ball second = balls.get(j);
if (first.collidesWith(second)) {
first.bounceOff(second);
}
}
}jstarts ati + 1, so each pair is tested once.
Code walkthrough 7 of 10 · AnimationThread.java
The animation loop
@Override
public void run() {
while (true) {
long frameStartTime = System.currentTimeMillis();
panel.updateAnimation();
panel.repaint();- An endless loop: one pass per frame.
- Update the positions, then ask Swing to repaint.
Code walkthrough 8 of 10 · AnimationThread.java
Keeping a steady frame rate
long elapsedTime = System.currentTimeMillis() - frameStartTime;
long sleepTime = FRAME_TIME_MS - elapsedTime;
if (sleepTime > 0) {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
System.out.println("Animation thread interrupted.");
Thread.currentThread().interrupt();
break;
}
}- Sleep only for what is left of the 16 ms frame (about 60 FPS).
- An interrupt ends the loop.
Code walkthrough 9 of 10 · AnimationApp.java
Buttons control a flag
private void addActionListeners() {
startButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
animationPanel.startAnimation();
updateStatus("Running");
}
});- Start only sets
running; the loop keeps going. - Pause and Reset are written the same way.
Code walkthrough 10 of 10 · AnimationApp.java
Starting the worker thread
private void startWorkerThread() {
animationThread = new Thread(new AnimationThread(animationPanel));
animationThread.setDaemon(true);
animationThread.start();
}- A daemon thread does not keep the program alive after the window closes.
Step through the code · 1 of 2
Trace: one animation frame
Move, bounce off the wall, detect a collision and swap velocities.
Step through the code · 1 of 2
Trace: one animation frame (cont.)
Step through the code · 2 of 2
Trace: the animation thread and the EDT
The Animator moves the balls, the EDT draws them. The panel’s lock makes sure the EDT never paints a half-moved frame.
Step through the code · 2 of 2
Trace: the animation thread and the EDT (cont.)
For reference · 5 files
The complete program
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
public class Ball {
private double x;
private double y;
private double vx;
private double vy;
private int radius;
private Color colour;
public Ball(double x, double y, double vx, double vy, int radius, Color colour) {
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.radius = (radius > 0) ? radius : 15;
this.colour = colour;
}
public void update(int panelWidth, int panelHeight) {
x = x + vx;
y = y + vy;
if (x - radius < 0 || x + radius > panelWidth) {
vx = -vx;
x = Math.max(radius, Math.min(panelWidth - radius, x));
}
if (y - radius < 0 || y + radius > panelHeight) {
vy = -vy;
y = Math.max(radius, Math.min(panelHeight - radius, y));
}
}
public void draw(Graphics2D g) {
int diameter = 2 * radius;
g.setColor(colour);
g.fillOval((int) (x - radius), (int) (y - radius), diameter, diameter);
g.setColor(Color.BLACK);
g.setStroke(new BasicStroke(2));
g.drawOval((int) (x - radius), (int) (y - radius), diameter, diameter);
}
public boolean collidesWith(Ball other) {
double dx = this.x - other.x;
double dy = this.y - other.y;
double distance = Math.sqrt(dx * dx + dy * dy);
return distance <= this.radius + other.radius;
}
public void bounceOff(Ball other) {
double tempVx = this.vx;
double tempVy = this.vy;
this.vx = other.vx;
this.vy = other.vy;
other.vx = tempVx;
other.vy = tempVy;
}
public double getX() { return x; }
public double getY() { return y; }
public int getRadius() { return radius; }
}import javax.swing.JPanel;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
public class AnimationPanel extends JPanel {
private ArrayList<Ball> balls;
private boolean running;
public AnimationPanel() {
balls = new ArrayList<Ball>();
running = false;
initializeBalls();
setBackground(Color.WHITE);
}
private void initializeBalls() {
balls.clear();
balls.add(new Ball(100, 100, 3.0, 2.0, 15, Color.RED));
balls.add(new Ball(300, 150, -2.5, 3.5, 15, Color.BLUE));
balls.add(new Ball(200, 300, 2.0, -2.0, 15, Color.GREEN));
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (getWidth() <= 0 || getHeight() <= 0) {
return;
}
BufferedImage buffer = new BufferedImage(getWidth(), getHeight(),
BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = buffer.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, getWidth(), getHeight());
drawGrid(g2d);
synchronized (this) {
for (Ball ball : balls) {
ball.draw(g2d);
}
g2d.setColor(Color.BLACK);
g2d.drawString("Balls: " + balls.size() + " | "
+ (running ? "Running" : "Paused"), 10, getHeight() - 10);
}
g.drawImage(buffer, 0, 0, null);
g2d.dispose();
}
private void drawGrid(Graphics2D g2d) {
g2d.setColor(new Color(220, 220, 220));
for (int x = 0; x < getWidth(); x += 50) {
g2d.drawLine(x, 0, x, getHeight());
}
for (int y = 0; y < getHeight(); y += 50) {
g2d.drawLine(0, y, getWidth(), y);
}
}
public synchronized void updateAnimation() {
if (!running) {
return;
}
int width = getWidth();
int height = getHeight();
if (width <= 0 || height <= 0) {
return;
}
for (Ball ball : balls) {
ball.update(width, height);
}
for (int i = 0; i < balls.size(); i++) {
for (int j = i + 1; j < balls.size(); j++) {
Ball first = balls.get(i);
Ball second = balls.get(j);
if (first.collidesWith(second)) {
first.bounceOff(second);
}
}
}
}
public synchronized void startAnimation() { running = true; }
public synchronized void pauseAnimation() { running = false; }
public synchronized void resetAnimation() {
running = false;
initializeBalls();
repaint();
}
public synchronized boolean isRunning() { return running; }
public synchronized int getBallCount() { return balls.size(); }
}public class AnimationThread implements Runnable {
private AnimationPanel panel;
private static final int TARGET_FPS = 60;
private static final long FRAME_TIME_MS = 1000 / TARGET_FPS;
public AnimationThread(AnimationPanel panel) {
this.panel = panel;
}
@Override
public void run() {
while (true) {
long frameStartTime = System.currentTimeMillis();
panel.updateAnimation();
panel.repaint();
long elapsedTime = System.currentTimeMillis() - frameStartTime;
long sleepTime = FRAME_TIME_MS - elapsedTime;
if (sleepTime > 0) {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
System.out.println("Animation thread interrupted.");
Thread.currentThread().interrupt();
break;
}
}
}
}
}import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class AnimationApp extends JFrame {
private AnimationPanel animationPanel;
private JButton startButton;
private JButton pauseButton;
private JButton resetButton;
private JLabel statusLabel;
private Thread animationThread;
public AnimationApp() {
setTitle("Bouncing Balls Animation");
setSize(800, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setupUI();
addActionListeners();
startWorkerThread();
setVisible(true);
}
private void setupUI() {
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
JLabel titleLabel = new JLabel("Bouncing Balls Animation");
titleLabel.setFont(new Font("Arial", Font.BOLD, 16));
topPanel.add(titleLabel);
animationPanel = new AnimationPanel();
animationPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2));
JPanel controlPanel = new JPanel(new BorderLayout());
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
startButton = new JButton("Start");
pauseButton = new JButton("Pause");
resetButton = new JButton("Reset");
buttonPanel.add(startButton);
buttonPanel.add(pauseButton);
buttonPanel.add(resetButton);
statusLabel = new JLabel("Status: Initialized");
controlPanel.add(buttonPanel, BorderLayout.CENTER);
controlPanel.add(statusLabel, BorderLayout.SOUTH);
mainPanel.add(topPanel, BorderLayout.NORTH);
mainPanel.add(animationPanel, BorderLayout.CENTER);
mainPanel.add(controlPanel, BorderLayout.SOUTH);
setContentPane(mainPanel);
}
private void addActionListeners() {
startButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
animationPanel.startAnimation();
updateStatus("Running");
}
});
pauseButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
animationPanel.pauseAnimation();
updateStatus("Paused");
}
});
resetButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
animationPanel.resetAnimation();
updateStatus("Reset");
}
});
}
private void startWorkerThread() {
animationThread = new Thread(new AnimationThread(animationPanel));
animationThread.setDaemon(true);
animationThread.start();
}
private void updateStatus(String status) {
statusLabel.setText("Status: " + status + " | Balls: " + animationPanel.getBallCount());
}
}import javax.swing.SwingUtilities;
public class Lab10Demo {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new AnimationApp();
}
});
}
}Copy or download each file, save it under the same name, then compile and run: javac *.java → java Lab10Demo
Implementation
Expected output
The program opens a window instead of printing to the console. When it works you should see:
- A window titled “Bouncing Balls Animation” with a gridded white panel and three coloured balls
- Start moves the balls; they bounce off the walls and swap directions when they collide
- Pause freezes them; Reset puts them back and pauses
- The panel shows the ball count and Running / Paused
Interactive
Try the bouncing balls
In-lab task 1 of 3
Task 1: Add a fourth ball
Show a hint
In-lab task 2 of 3
Task 2: Velocity damping
Show a hint
if (Math.abs(vx) < 0.05) vx = 0; and the same for vy.In-lab task 3 of 3
Task 3: Frame-rate display
Show a hint
In-lab tasks
Show your work during the lab
For each task, show the instructor:
- the source code
- a successful compilation
- the running output
- a short explanation of the logic you used
Lab report · exercise 1 of 2
Physics: gravity and bounce
Lab report · exercise 2 of 2
Create balls with the mouse
Lab report
Writing the lab report
- Problem statement and your design (classes, fields, methods)
- Complete source code, formatted and commented
- Sample output or screenshots for normal and edge cases
- A short discussion: what each OOP concept did in your program
Academic integrity
Copying from the internet, classmates, seniors or any other unauthorized source is strictly prohibited. Full marks may be deducted if plagiarism, copied work or academic dishonesty is detected. Complete the tasks, implementation, output analysis and report yourself.
Discussion and analysis
Viva questions (1 of 2)
Show the answer
Show the answer
Discussion and analysis
Viva questions (2 of 2)
Show the answer
Show the answer
Check yourself
Quiz
Summary
Summary
- Animation = update state, redraw, wait — repeated about 60 times a second.
- A worker thread runs the loop; the EDT handles painting and events.
- Synchronize state shared between the two threads.
- Double buffering draws complete frames off-screen.
- Walls reverse a velocity; overlapping balls swap velocities.