Java GUI Practice: Building a Snake Game with Swing
A Swing practice project covering JFrame setup, custom JPanel painting, keyboard input, timers, sprite resources, movement, food, scoring, and collision detection.
Contents6 sections
Java GUI Practice
Although I was ill for a few days, I continued learning Java GUI programming and followed a small Snake project. The original course source is available from KuangStudy. You can also download my annotated Snake project and the broader GUI exercise package.
The interesting part is not complex game logic; it is learning how Swing painting, layouts, keyboard events, and timers fit together. Configure the project character encoding correctly before generating JavaDoc so comments render properly.
Application entry point
The entry point creates a fixed-size JFrame, installs the game panel, and closes the application with the window.
package com.zaunekko.gui.game.snake;
import javax.swing.*;
public class StartGame {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setBounds(50, 50, 915, 738);
frame.setResizable(false);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(new GamePanel());
frame.setVisible(true);
}
}
Game state and timing
GamePanel extends JPanel and implements both KeyListener and ActionListener.
The panel keeps:
- arrays for every snake segment's
xandyposition; - random food coordinates;
- current length and score;
- current direction;
- flags for running and failed states;
- one Swing
Timerthat advances the game every 500 ms.
The timer must be initialized only once. Recreating it from the reset method would leave multiple timers running after a death and restart.
Timer timer = new Timer(500, this);
public GamePanel() {
init();
setFocusable(true);
addKeyListener(this);
timer.start();
}
init() resets the three starting segments, creates a random food position on the 25-pixel grid, resets the score, points the snake right, and clears the running/failure flags.
Painting the game
paintComponent clears the panel, paints the header and play area, draws food, chooses a head sprite for the current direction, and draws the remaining body segments. It also prints length and score and overlays either the start prompt or the failure prompt.
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
setBackground(Color.WHITE);
Data.header.paintIcon(this, g, 25, 0);
g.fillRect(25, 75, 850, 600);
Data.food.paintIcon(this, g, foodX, foodY);
switch (direction) {
case "R" -> Data.right.paintIcon(this, g, snakeX[0], snakeY[0]);
case "L" -> Data.left.paintIcon(this, g, snakeX[0], snakeY[0]);
case "U" -> Data.up.paintIcon(this, g, snakeX[0], snakeY[0]);
case "D" -> Data.down.paintIcon(this, g, snakeX[0], snakeY[0]);
}
for (int i = 1; i < length; i++) {
Data.body.paintIcon(this, g, snakeX[i], snakeY[i]);
}
}
Keyboard controls
Space starts or pauses the game. After failure it resets the state. Arrow keys change direction only after the game has started, preventing the initial direction from changing while the start prompt is still visible.
@Override
public void keyPressed(KeyEvent event) {
int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.VK_SPACE) {
if (isFail) {
init();
} else {
isStart = !isStart;
}
repaint();
} else if (isStart && keyCode == KeyEvent.VK_UP) {
direction = "U";
} else if (isStart && keyCode == KeyEvent.VK_DOWN) {
direction = "D";
} else if (isStart && keyCode == KeyEvent.VK_LEFT) {
direction = "L";
} else if (isStart && keyCode == KeyEvent.VK_RIGHT) {
direction = "R";
}
}
Advancing one game tick
On each timer event:
- If the head reaches food, increase the length and score and choose another food position.
- Move each body segment to the previous segment's old position.
- Move the head by one 25-pixel cell.
- Wrap the head at each play-area boundary.
- Compare the head with every body segment; a match ends the game.
- Repaint the panel.
for (int i = length - 1; i > 0; i--) {
snakeX[i] = snakeX[i - 1];
snakeY[i] = snakeY[i - 1];
}
for (int i = 1; i < length; i++) {
if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
isFail = true;
break;
}
}
repaint();
Loading image resources
The Data class resolves packaged PNG files relative to the class and exposes an ImageIcon for the header, four head directions, food, and body.
public static URL headerURL = Data.class.getResource("header.png");
public static URL upURL = Data.class.getResource("up.png");
public static URL downURL = Data.class.getResource("down.png");
public static URL leftURL = Data.class.getResource("left.png");
public static URL rightURL = Data.class.getResource("right.png");
public static URL foodURL = Data.class.getResource("food.png");
public static URL bodyURL = Data.class.getResource("body.png");
Together these three classes demonstrate the basic Swing game loop: input changes state, a timer advances state, and paintComponent renders that state.
Related posts
Discussion / approved
Comments
No comments yet.