How to Build a Simple Penalty Shootout Game with Java Swing
This tutorial walks through building a simple penalty‑shootout game in Java Swing by outlining the panel architecture, rendering the audience, grass, goal and net with Graphics2D, computing a QuadCurve2D trajectory, handling mouse dragging and clicks to animate the ball, detecting collisions, and updating score and timer.
This article demonstrates how to create a simple penalty‑shootout game using Java Swing. It explains the overall architecture, the drawing of each game component, the trajectory calculation, mouse interaction, collision detection, scoring, and timing.
1. Interface terminology – The game screen is divided into a game area and a score area . The game area further contains the audience zone, goal zone, game elements, and the shooting zone.
2. Drawing the audience zone – The audience is rendered with Swing Graphics2D primitives (rectangles, ovals, arcs). The code draws background rectangles and then composes circles and ellipses to form each spectator.
// Audience background
g2d.setColor(personBgColor);
g2d.fillRect(0, y, getWidth(), 100);
// Second row of spectators
for (int i = 0; i < getWidth(); i += 46) {
g2d.setColor(person2);
g2d.fillOval(i, 36, 16, 16);
g2d.fillArc(i - 7, 50, 30, 50, 0, 180);
g2d.fillOval(i + 24, 40, 13, 13);
g2d.fillArc(i + 18, 51, 24, 49, 0, 180);
}3. Drawing the grass – Two alternating colored rectangles are drawn across the screen to simulate a 3‑D lawn.
int count = 0;
int h1 = 60;
for (int i = y; i < getHeight(); i++) {
if (count % 2 == 0) {
g2d.setColor(bgColor1);
} else {
g2d.setColor(bgColor2);
}
g2d.fillRect(0, y, getWidth(), h1 + count * 10);
y += h1 + count * 10;
count++;
}4. Drawing the goal and net – A rounded rectangle forms the goal frame, and multiple lines create the net. The net is drawn with Graphics2D drawPolyline calls.
// Goal frame
g2d.setColor(doorColor);
Stroke stroke = new BasicStroke(9);
g2d.setStroke(stroke);
g2d.drawRoundRect(getWidth()*4/20, 85, getWidth()*3/5, 95, 20, 20);
// Net vertical lines
int step = 0;
for (int i = startX; i < getWidth()*4/5; i = startX) {
int[] x = {startX, startX + (startX < centerX ? +8 : -8), startX + (startX < centerX ? +12 : -12)};
int[] y = {startY, startY + 10, 155};
g2d.drawPolyline(x, y, x.length);
startX += step;
}5. Trajectory calculation – The start point (ball centre) and the end point (goal centre) are used to build a QuadCurve2D . Random points on the curve are extracted with a PathIterator and stored in a list.
QuadCurve2D quad = new QuadCurve2D.Double(startX, startY, startX + stepX, endY + stepY + 50, endX, endY + stepY);
PathIterator pi = quad.getPathIterator(g2d.getTransform(), 6);
points = new ArrayList<>(25);
while (!pi.isDone()) {
double[] coords = new double[6];
int type = pi.currentSegment(coords);
if (type == PathIterator.SEG_MOVETO || type == PathIterator.SEG_LINETO) {
points.add(new Point2D.Double(coords[0], coords[1]));
}
pi.next();
}6. Mouse interaction – Dragging the mouse moves the start and end points, updating the trajectory in real time. Holding Ctrl while dragging places the ball at the new location.
ball.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
int stepX = e.getX();
int stepY = e.getY();
line.reDraw(ball, BackgroundPanel.this, stepX, stepY, e.isControlDown());
repaint();
}
});7. Shooting the ball – Clicking the ball starts a new thread that moves the ball through the pre‑computed points, sleeping 100 ms between steps.
ball.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
new Thread(() -> {
for (Point2D.Double p : line.getPoints()) {
ball.setBounds((int) p.x, (int) p.y, ball.getWidth(), ball.getHeight());
try { Thread.sleep(100); } catch (InterruptedException ex) { ex.printStackTrace(); }
}
}).start();
}
});8. Collision detection – During the animation the ball is checked against stars, the goalkeeper and the stone obstacle. Overlap of any bounding rectangles triggers removal of the star or a “blocked” state.
Rectangle ballBounds = ball.getBounds();
for (Component c : getComponents()) {
if (c instanceof Obstacle) {
Rectangle r = ((Obstacle) c).getComponent().getBounds();
if (ballBounds.intersects(r)) {
// handle hit (remove star, play sound, etc.)
}
}
}9. Scoring and timing – A separate thread increments a time counter every second and repaints the timer. When the ball reaches the goal, the score is updated with the number of stars cleared plus one point for the goal.
long time = 0;
@Override
public void run() {
while (true) {
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
time++;
repaint(); // draw time as "mm:ss"
}
}
// After ball stops
infoPanel.addScore(starCount + 1); // 1 for goal, starCount for cleared starsAll of the above pieces are assembled into a Swing JPanel that continuously repaints the background, handles user input, and runs the game loop, resulting in a fully functional penalty‑shootout demo.
Tencent Cloud Developer
Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.