Bounce With Collision#
Introduction#
This Java program creates a graphical window where you can add multiple colored balls. These balls move around within a defined area, bouncing realistically off the edges of that area. Crucially, the balls also detect collisions with each other and bounce off one another. You can add new balls, delete the oldest ball, or clear all balls from the screen using the provided buttons
Object-Oriented Programming (OOP): Demonstrates class design (
Ball
,Frame1
), encapsulation (hiding data inBall
with getters/setters), inheritance (Ball extends JComponent
,Frame1 extends JFrame
), polymorphism (paintComponent
override), and object interaction (Frame1
managingBall
objects).GUI Programming (Java Swing): Shows use of standard components (
JFrame
,JPanel
,JButton
), creating custom visual components (Ball
), handling events (ActionListener
for timer/buttons), custom drawing (paintComponent
), and layout management (absolute positioning).Animation and Timing: Uses
javax.swing.Timer
for the animation loop, manages object state changes over time, and usesrepaint()
to update the display visually.Collision Detection & Simple Physics: Implements boundary checking (wall bouncing), object-to-object collision detection (distance check), and collision response (swapping vectors, resolving overlap).
Data Structures: Utilizes
ArrayList
to manage a dynamic collection ofBall
objects and demonstrates iterating over collections.
Frame Class#
package j2_bounce_rev25_collision;
import j2_bounce_rev25_collision.Ball;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.Timer;
/*
Bounce Balls
Programmer: James Goudy
Rev: April, 2024 / Modified: 4/2025 for inter-ball collision
*/
public class Frame1 extends javax.swing.JFrame {
// Global Variables
Timer panelTimer; //this will control the repaint of the panel
boolean TimerStatus;
Random RNG = new Random();
// Setup to show one can instantiate an object later
// Random RNG2; // RNG2 seems unused, consider removing
// initial ball info (defaults for adding balls)
// int x = 1; // No longer needed here
// int y = 1; // No longer needed here
// int radius = 25; // Default radius used below
// Timer Tick speed (delay in milliseconds)
int clockTick = 20; // Reduced for smoother animation
// Arraylist to hold the balls
ArrayList<Ball> alBalls = new ArrayList<>(); // Use diamond operator
/**
* Creates new form Frame1
*/
public Frame1()
{
initComponents();
TimerStatus = false;
// RNG2 = new Random(); // Unused
this.setLocationRelativeTo(null);
this.setTitle("Bouncing Balls with Collision"); // Set a title
}
private void PanelClock() {
panelTimer = new Timer(clockTick, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e)
{
// 1. Move all balls (calculate next position based
// on velocity and wall collision)
for (Ball ball : alBalls) {
ball.moveBall();
}
// 2. Check for and handle collisions between balls
for (int i = 0; i < alBalls.size(); i++) {
for (int j = i + 1; j < alBalls.size(); j++) {
Ball ball1 = alBalls.get(i);
Ball ball2 = alBalls.get(j);
if (ball1.collidesWith(ball2)) {
// Collision detected!
// Resolve overlap first to prevent sticking
ball1.resolveCollision(ball2);
// Swap movement vectors for a simple bounce effect
ball1.swapMovement(ball2);
// Alternative: Simple reverse (less realistic)
// ball1.reverse();
// ball2.reverse();
}
}
}
// 3. Update the visual location of each ball component on the panel
// Use the calculated xpos/ypos which are now adjusted for collisions
for (Ball ballComponent : alBalls) {
// Ball IS the component, just set its location
ballComponent.setLocation(ballComponent.getXpos(), ballComponent.getYpos());
}
// 4. Repaint the panel to show updated ball positions
// Note: Repainting the panel itself might not be enough if balls are separate components.
// Repainting each ball or the container might be necessary depending on Swing's behavior.
// Calling revalidate() and repaint() on the container (jPanel1) is generally robust.
//jPanel1.revalidate(); // Recalculate layout if sizes changed (e.g., radius)
jPanel1.repaint(); // Redraw the panel and its children
// panelTimer.setDelay(20); // Delay is set when creating the timer
}
});
panelTimer.start();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents()
{
jPanel1 = new javax.swing.JPanel();
jbttnAddBall = new javax.swing.JButton();
jbttnDelete = new javax.swing.JButton();
jbttnClear = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
jPanel1.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
jPanel1.setLayout(jPanel1Layout);
jPanel1Layout.setHorizontalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 600, Short.MAX_VALUE)
);
jPanel1Layout.setVerticalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 317, Short.MAX_VALUE)
);
jbttnAddBall.setText("Add");
jbttnAddBall.addActionListener(new java.awt.event.ActionListener()
{
public void actionPerformed(java.awt.event.ActionEvent evt)
{
jbttnAddBallActionPerformed(evt);
}
});
jbttnDelete.setText("Delete");
jbttnDelete.addActionListener(new java.awt.event.ActionListener()
{
public void actionPerformed(java.awt.event.ActionEvent evt)
{
jbttnDeleteActionPerformed(evt);
}
});
jbttnClear.setText("Clear");
jbttnClear.addActionListener(new java.awt.event.ActionListener()
{
public void actionPerformed(java.awt.event.ActionEvent evt)
{
jbttnClearActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(39, 39, 39)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(jbttnAddBall, javax.swing.GroupLayout.PREFERRED_SIZE, 88, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(33, 33, 33)
.addComponent(jbttnDelete, javax.swing.GroupLayout.PREFERRED_SIZE, 93, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(27, 27, 27)
.addComponent(jbttnClear, javax.swing.GroupLayout.PREFERRED_SIZE, 88, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap(99, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(41, 41, 41)
.addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(31, 31, 31)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jbttnAddBall)
.addComponent(jbttnDelete)
.addComponent(jbttnClear))
.addContainerGap(42, Short.MAX_VALUE))
);
pack();
}// </editor-fold>
private void jbttnClearActionPerformed(java.awt.event.ActionEvent evt)
{
// Stop the timer if it's running
if (TimerStatus) {
panelTimer.stop();
TimerStatus = false;
}
// Remove all ball components from the panel
jPanel1.removeAll();
// Clear the list of balls
alBalls.clear();
// Repaint the empty panel
//jPanel1.revalidate();
jPanel1.repaint();
}
private void jbttnDeleteActionPerformed(java.awt.event.ActionEvent evt)
{
// Remove the first ball added (FIFO)
if (!alBalls.isEmpty()) {
Ball ballToRemove = alBalls.remove(0); // Remove from list first
jPanel1.remove(ballToRemove); // Then remove component from panel
//jPanel1.revalidate();
jPanel1.repaint();
}
// Stop timer if no balls left
if (alBalls.isEmpty() && TimerStatus) {
panelTimer.stop();
TimerStatus = false;
}
}
private void jbttnAddBallActionPerformed(java.awt.event.ActionEvent evt)
{
// create a ball
Ball aball = new Ball();
int r = RNG.nextInt(25) + 10; // Radius between 10 and 34
aball.setRadius(r);
// Set boundaries based on panel size *and* ball radius
aball.setCompHeight(jPanel1.getHeight());
aball.setCompWidth(jPanel1.getWidth());
// Set initial position randomly within bounds (considering radius)
aball.setXpos(RNG.nextInt(jPanel1.getWidth() - 2 * r));
aball.setYpos(RNG.nextInt(jPanel1.getHeight() - 2 * r));
// ball characteristics
aball.setVisible(true);
// aball.setBallspeed(RNG.nextInt(50) + 5); // Speed not directly used in movement yet
aball.setSlopeRun(RNG.nextInt(5) + 1); // Keep slopes reasonable
aball.setSlopeRise(RNG.nextInt(5) + 1); // Ensure non-zero rise initially
aball.setXDir(RNG.nextBoolean() ? 1 : -1); // Random initial direction
aball.setYDir(RNG.nextBoolean() ? 1 : -1); // Random initial direction
aball.setBallColor(new Color(RNG.nextInt(256),
RNG.nextInt(256), RNG.nextInt(256)));
// Set the component's bounds (position and size)
// Position is set via setLocation in the timer loop
// Size is set implicitly by setRadius -> setSize
aball.setSize(aball.getRadius() * 2, aball.getRadius() * 2);
// add the ball component to the panel and the list
// Ensure Null Layout is set on jPanel1 in the designer or code for absolute positioning
jPanel1.setLayout(null); // Make sure layout is null
jPanel1.add(aball);
alBalls.add(aball);
// Start the master timer if it's not running
if (!TimerStatus) {
PanelClock();
TimerStatus = true;
}
jPanel1.repaint(); // Repaint after adding
}
/**
* @param args the command line arguments
*/
public static void main(String args[])
{
/* Set the Nimbus look and feel */
//<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
/* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
* For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
*/
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(Frame1.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
java.util.logging.Logger.getLogger(Frame1.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(Frame1.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(Frame1.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
//</editor-fold>
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run()
{
new Frame1().setVisible(true);
}
});
}
// Variables declaration - do not modify
private javax.swing.JPanel jPanel1;
private javax.swing.JButton jbttnAddBall;
private javax.swing.JButton jbttnClear;
private javax.swing.JButton jbttnDelete;
// End of variables declaration
}
Okay, let’s look at the Frame1.java
code. If Ball.java
is the blueprint for a single bouncing ball, then Frame1.java
is like the director and the stage for the whole show.
Think of it as the main program window you see and interact with. Here’s what it does, step-by-step, for a freshman CS student:
It’s the Main Window: The line
public class Frame1 extends javax.swing.JFrame
meansFrame1
defines the main window of your application.JFrame
is Java’s standard class for creating windows with borders, title bars, close buttons, etc.Sets Up the Stage:
Inside
Frame1
, there’s ajPanel1
. This is a blank panel within the window, acting as the “stage” or container where the balls will actually bounce around.The
initComponents()
method (usually auto-generated by GUI builders like NetBeans) sets up this panel, along with the buttons (“Add Ball”, “Delete Ball”, “Clear All”) and the title label (“Bounce”). It arranges them visually within theFrame1
window.
Keeps Track of the Balls: It uses an
ArrayList<Ball> alBalls = new ArrayList();
. AnArrayList
is just a flexible list that can grow or shrink.Frame1
uses this list to keep track of every singleBall
object that is currently active in the animation.Handles User Actions:
When you click the “Add Ball” button, the
jbttnAddBallActionPerformed
method runs. It creates a newBall
object (using theBall
blueprint), gives it random properties (size, color, speed, starting position), adds this new ball to thealBalls
list, and physically places the ball component onto thejPanel1
stage.Similarly,
jbttnDeleteBallActionPerformed
removes a ball from the list and the stage, andjbttnClearAllActionPerformed
removes all of them.
Manages the Animation (The Heartbeat):
This is the most important part!
Frame1
has aTimer panelTimer
. Think of this as a metronome or a heartbeat for the animation. It “ticks” at regular intervals (everyclockTick
milliseconds).The
PanelClock()
method sets up and starts this timer.Every time the timer ticks
, the code inside the
actionPerformed
method runs. This is the animation loop:
Tell Balls to Move: It goes through the
alBalls
list and tells each ball to run itsmoveBall()
method. This updates each ball’s internal position and handles bouncing off the walls.Check for Ball Collisions: It then uses nested loops to check every possible pair of balls in the list. For each pair, it asks
if (ball1.collidesWith(ball2))
. If they are colliding, it calls methods (resolveCollision
,swapMovement
) to make them bounce off each other realistically.Update the Screen: After calculating all the new positions (including adjustments from collisions), it goes through the list again. For each ball, it calls
ballComponent.setLocation(x, y)
to visually move the ball component to its new coordinates onjPanel1
.Redraw: Finally, it calls
jPanel1.repaint()
. This tells the Java graphical system, “Okay, I’ve moved everything, now redraw the panel (and all the balls on it) so the user sees the changes.”
Starts the Program: The
public static void main(String args[])
method is the universal starting point for any Java application. Its job here is simply to create theFrame1
window and make it visible on your computer screen.
In Simple Terms:
Frame1
is the manager. It sets up the window and the bouncing area (jPanel1
). It keeps a list of all the balls. It responds to button clicks to add or remove balls. Most importantly, it runs a timer that acts as the animation’s heartbeat. On each heartbeat, it tells all the balls to move, checks if they bump into each other, updates their visual positions on the screen, and redraws everything.
Ball Class#
package j2_bounce_rev25_collision;
/*
Bounce Balls
Programmer: James Goudy
Rev: April, 2024 / Modified: 4/2025 for inter-ball collision
*/
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JComponent;
import javax.swing.Timer;
import java.awt.Color;
import java.util.Random;
import java.awt.Color;
import java.util.Random;
/**
*
* @author jgoudy
*/
public class Ball extends JComponent {
// private members
// position of the ball (top-left corner)
int xpos = 1;
int ypos = 1;
// direction of the ball - forward or backwards
int xdir = 1;
int ydir = 1;
// size of the panel: these are going to be defaults
// Note: compWidth and compHeight will now represent the maximum center x/y coordinates
int compWidth = 500;
int compHeight = 500;
// angle of the ball (movement step size)
int slopeRun = 1;
int slopeRise = 1;
// speed (not currently used in movement logic, but kept for potential future use)
int ballspeed = 2;
// radius of the ball
int radius = 25;
// Timer (seems unused within Ball class itself, consider removing if not needed here)
// Timer bTimer;
// ball color
Color ballColor;
Random RNG;
// boolean wasReversed = false; // Removed as collision logic is moved to Frame1
//-------------------------------------------------------
// Constructor
public Ball() {
RNG = new Random();
ballColor = Color.blue;
// Set component size based on radius for drawing
this.setSize(radius * 2, radius * 2);
this.setPreferredSize(this.getSize()); // Important for layout managers
}
// Setters
public void setXpos(int xpos) {
// Ensure xpos stays within bounds if set externally
this.xpos = Math.max(0, Math.min(xpos, compWidth - radius));
}
public void setYpos(int ypos) {
// Ensure ypos stays within bounds if set externally
this.ypos = Math.max(0, Math.min(ypos, compHeight - radius));
}
// Getters for position (top-left)
public int getXpos() {
return xpos;
}
public int getYpos() { // Corrected method name from getYPos to getYpos
return ypos;
}
// Getters for Center Coordinates
public int getCenterX() {
return xpos + radius;
}
public int getCenterY() {
return ypos + radius;
}
//-------------------------------------------------------
// Setters for component bounds (adjust based on radius)
public void setCompWidth(int containerWidth) {
// Max center x-coordinate is container width minus radius
this.compWidth = containerWidth - radius;
}
public void setCompHeight(int containerHeight) {
// Max center y-coordinate is container height minus radius
this.compHeight = containerHeight - radius;
}
// Setters and Getters for movement properties
public void setSlopeRun(int slopeRun) {
this.slopeRun = slopeRun;
}
public int getSlopeRun() {
return slopeRun;
}
public void setSlopeRise(int slopeRise) {
this.slopeRise = slopeRise;
}
public int getSlopeRise() {
return slopeRise;
}
public void setBallspeed(int ballspeed) {
this.ballspeed = ballspeed;
}
public int getBallspeed() { // Added getter
return ballspeed;
}
public void setRadius(int radius) {
this.radius = radius;
// Update component size when radius changes
this.setSize(radius * 2, radius * 2);
this.setPreferredSize(this.getSize());
}
public int getRadius() {
return this.radius;
}
public void setBallColor(Color ballColor) {
this.ballColor = ballColor;
}
public Color getBallColor() {
return ballColor;
}
public int getXDir() {
return xdir;
}
public void setXDir(int xdir) {
this.xdir = xdir;
}
public int getYDir() {
return ydir;
}
public void setYDir(int ydir) {
this.ydir = ydir;
}
//-------------------------------------------------------
// Draw the ball
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// Graphics context is relative to the component's top-left corner (0,0)
g.setColor(ballColor);
// Draw the oval filling the component bounds
g.fillOval(0, 0, radius * 2, radius * 2);
}
//-------------------------------------------------------
public void moveBall() {
// Calculate next potential center position
int nextCenterX = getCenterX() + (xdir * slopeRun);
int nextCenterY = getCenterY() + (ydir * slopeRise);
// Check for collision on the left and right walls based on center position
if (nextCenterX > compWidth) { // Hit right wall
xdir *= -1;
// Adjust position to be exactly at the boundary to avoid overshooting
xpos = compWidth - radius;
} else if (nextCenterX < radius) { // Hit left wall
xdir *= -1;
// Adjust position to be exactly at the boundary
xpos = 0;
}
// Check for collision on the top and bottom walls based on center position
if (nextCenterY > compHeight) { // Hit bottom wall
ydir *= -1;
// Adjust position to be exactly at the boundary
ypos = compHeight - radius;
} else if (nextCenterY < radius) { // Hit top wall
ydir *= -1;
// Adjust position to be exactly at the boundary
ypos = 0;
}
// Update the position (top-left corner) based on direction and slope
// Use the potentially updated directions
xpos += (xdir * slopeRun);
ypos += (ydir * slopeRise);
// Ensure position doesn't go out of bounds after adjustment (safety check)
xpos = Math.max(0, Math.min(xpos, compWidth - radius));
ypos = Math.max(0, Math.min(ypos, compHeight - radius));
}
//-------------------------------------------------------
// Method to reverse direction (can be used for simple collision response)
public void reverse() {
xdir *= -1;
ydir *= -1;
}
// Method to swap movement vectors with another ball
public void swapMovement(Ball other) {
int tempXDir = this.xdir;
int tempYDir = this.ydir;
int tempSlopeRun = this.slopeRun;
int tempSlopeRise = this.slopeRise;
this.setXDir(other.getXDir());
this.setYDir(other.getYDir());
this.setSlopeRun(other.getSlopeRun());
this.setSlopeRise(other.getSlopeRise());
other.setXDir(tempXDir);
other.setYDir(tempYDir);
other.setSlopeRun(tempSlopeRun);
other.setSlopeRise(tempSlopeRise);
}
// Method to check collision with another ball
public boolean collidesWith(Ball other) {
if (this == other) { // A ball cannot collide with itself
return false;
}
int dx = this.getCenterX() - other.getCenterX();
int dy = this.getCenterY() - other.getCenterY();
// Use distance squared to avoid expensive square root calculation
double distanceSq = dx * dx + dy * dy;
int sumRadii = this.radius + other.radius;
double sumRadiiSq = sumRadii * sumRadii;
return distanceSq < sumRadiiSq;
}
// Method to resolve collision by slightly moving balls apart
// This helps prevent balls from sticking together
public void resolveCollision(Ball other) {
int dx = this.getCenterX() - other.getCenterX();
int dy = this.getCenterY() - other.getCenterY();
double distance = Math.sqrt(dx * dx + dy * dy);
int sumRadii = this.radius + other.radius;
// Calculate overlap amount
double overlap = sumRadii - distance;
if (overlap > 0 && distance > 0) { // Check distance > 0 to avoid division by zero
// Calculate normalized direction vector (dx/distance, dy/distance)
double moveX = (dx / distance) * (overlap / 2.0); // Move each ball by half the overlap
double moveY = (dy / distance) * (overlap / 2.0);
// Update positions (move them apart along the collision axis)
// Ensure positions stay within bounds
this.xpos = Math.max(0, Math.min(this.compWidth - this.radius, (int)(this.xpos + moveX)));
this.ypos = Math.max(0, Math.min(this.compHeight - this.radius, (int)(this.ypos + moveY)));
other.xpos = Math.max(0, Math.min(other.compWidth - other.radius, (int)(other.xpos - moveX)));
other.ypos = Math.max(0, Math.min(other.compHeight - other.radius, (int)(other.ypos - moveY)));
} else if (distance == 0) {
// If balls are exactly on top of each other, move one randomly
this.xpos = Math.max(0, Math.min(this.compWidth - this.radius, this.xpos + RNG.nextInt(3) - 1));
this.ypos = Math.max(0, Math.min(this.compHeight - this.radius, this.ypos + RNG.nextInt(3) - 1));
}
}
}
Okay, let’s break down the Ball.java
code. Imagine you’re building a simple game or animation with bouncing balls on the screen. This Ball.java
file acts as the blueprint for creating each individual ball object.
Think of it like this: Ball.java
is the recipe, and every time you follow the recipe (create a new Ball()
), you get an actual ball that can be put onto the screen (the JPanel
in Frame1.java
).
Here’s a summary of what the Ball
class does, explained simply:
It’s a Visual Thing: The class
Ball extends JComponent
. In Java’s Swing library (used for creating graphical user interfaces), aJComponent
is basically something you can see on the screen. So, everyBall
object is a visual component.Knows Its Properties:
Each ball object keeps track of its own information:
Position:
xpos
,ypos
store the coordinates (like on a graph) of the ball’s top-left corner within its container panel.Size:
radius
determines how big the ball is.Color:
ballColor
stores the ball’s color.Movement Direction:
xdir
andydir
tell the ball if it’s currently moving left/right (xdir
is 1 for right, -1 for left) and up/down (ydir
is 1 for down, -1 for up).Speed/Angle:
slopeRun
andslopeRise
determine how many pixels the ball moves horizontally and vertically in each step. Changing these changes the ball’s speed and angle.Boundaries:
compWidth
andcompHeight
store the width and height of the area the ball is allowed to bounce within.
Can Draw Itself: The
paintComponent(Graphics g)
method is like the ball’s instruction manual for drawing itself. When the program needs to display the ball, it calls this method, which draws a filled oval using the ball’s currentballColor
andradius
.Knows How to Move:
The
moveBall()
method contains the logic for updating the ball’s position in each frame or time step.It first checks if the ball is about to hit one of the edges (walls) of its container panel. It does this by looking ahead at where the ball’s center will be in the next step.
If it detects a wall hit, it reverses the corresponding direction (
xdir
orydir
) so the ball “bounces” off. It also adjusts the position slightly to prevent the ball from going slightly past the wall.Finally, it updates the
xpos
andypos
based on the current direction (xdir
,ydir
) and speed/angle (slopeRun
,slopeRise
).
Knows How to Interact with Other Balls:
collidesWith(Ball other)
: This method checks if this ball is currently overlapping with another ball (other
). It calculates the distance between their centers and compares it to the sum of their radii. If the distance is smaller, they’ve collided.swapMovement(Ball other)
: This is one way to handle a collision. It makes the two colliding balls swap their direction and speed/angle variables. It’s a simple way to simulate a bounce between them.resolveCollision(Ball other)
: Sometimes, when balls collide, they might get stuck together slightly. This method gently pushes the two colliding balls apart just enough so they aren’t overlapping anymore, making the collision look smoother.
Can Change Its Properties: The class includes many “getter” (like
getRadius()
,getCenterX()
) and “setter” (likesetRadius(int r)
,setBallColor(Color c)
) methods. These allow other parts of the program (likeFrame1.java
) to get information about a ball or change its properties after it’s been created.
In a Nutshell:
The Ball.java
class defines everything a single ball needs to know and do: its appearance (size, color), its position, how to move, how to bounce off walls, and how to detect and react to collisions with other balls. It’s a self-contained blueprint for creating individual, interactive ball objects that can be managed and animated by another class like Frame1
.
Coding and Computer Science Concepts#
If you were using this code in a freshman computer science course, it would effectively highlight several important coding techniques and concepts:
Object-Oriented Programming (OOP):
Class Design: Demonstrates how to define classes (
Ball
,Frame1
) to represent real-world concepts or application components.Encapsulation: The
Ball
class hides its internal state (likexpos
,ypos
,radius
) and provides controlled access through getter and setter methods.Inheritance: Shows how classes can inherit properties and behaviors from parent classes (
Ball extends JComponent
,Frame1 extends JFrame
).Ball
inherits the ability to be a visual component, andFrame1
inherits the features of a standard window.Polymorphism: The
Ball
class overrides thepaintComponent
method inherited fromJComponent
to provide its specific drawing behavior.Object Interaction:
Frame1
creates and manages multipleBall
objects, storing them in a list and calling their methods (moveBall
,collidesWith
, etc.), demonstrating how objects collaborate.
GUI Programming (using Java Swing):
Components: Introduces basic GUI elements like windows (
JFrame
), panels (JPanel
), and buttons (JButton
).Custom Components: Shows how to create a custom visual component (
Ball
) by extendingJComponent
and implementing custom drawing logic.Event Handling: Uses
ActionListener
to respond to events, specifically timer ticks (panelTimer
) for animation and button clicks (jbttnAddBall
, etc.) for user interaction.Drawing: Demonstrates basic 2D drawing using the
Graphics
object withinpaintComponent
(drawing ovals).Layout Management: Uses absolute positioning (
setLayout(null)
) withinjPanel1
to place the balls at specific coordinates.
Animation and Timing:
Timers: Employs
javax.swing.Timer
to create a discrete animation loop, triggering updates at regular intervals.State Management: Shows how the state of objects (
Ball
positions and directions) is updated over time within the timer’s event handler.Repainting: Highlights the necessity of calling
repaint()
to refresh the display and show the updated state after changes have been made.
Collision Detection and Simple Physics:
Boundary Checking: Implementing logic to detect when an object hits the edge of its container (
moveBall
checking againstcompWidth
,compHeight
).Object Interaction Logic: Developing algorithms to detect collisions between multiple objects (
collidesWith
using distance formula).Collision Response: Implementing simple reactions to collisions (reversing direction for walls, swapping vectors for ball-ball collisions).
Data Structures:
Collections: Uses
ArrayList
(alBalls
) to store and manage a variable number ofBall
objects, demonstrating dynamic data structures.Iteration: Shows how to iterate over collections (the
ArrayList
) to process each object (e.g., moving each ball, checking collisions).
These techniques provide a practical context for understanding core CS concepts beyond basic syntax, connecting OOP, algorithms (collision detection), data structures, and event-driven programming within a visual application.