Developing a Simple Game in Plain Java with Swing: My Journey So Far
Developing a Simple Game in Plain Java with Swing: My Journey So Far
I recently started working on a small game using plain Java and Swing, without relying on any game engine. I wanted to build something simple yet functional from the ground up. So far, I’ve added a player sprite that can walk around on a black screen, implemented a 60 FPS loop, and displayed the FPS on the screen. Here's a detailed look at how I approached this project and the code that makes it work.
Setting Up the Game Window
To begin, I needed a basic window where the game would be displayed. Swing is a reliable choice for creating graphical user interfaces in Java, and it allowed me to easily set up a window and a drawing canvas.
The GamePanel
class is the heart of the game, handling rendering, user input, and the game loop. Here’s how I set it up:
package main;
import entity.Player;
import javax.swing.*;
import java.awt.*;
public class GamePanel extends JPanel implements Runnable {
// SCREEN SETTINGS
final int originalTileSize = 16; // 16x16 tile
final int scale = 3;
public final int tileSize = originalTileSize * scale; // 48x48 tile
final int maxScreenCol = 16;
final int maxScreenRow = 12;
final int screenWidth = tileSize * maxScreenCol; // 768 pixels
final int screenHeight = tileSize * maxScreenRow; // 576 pixels
int FPS = 60;
KeyHandler keyHandler = new KeyHandler();
Thread gameThread;
Player player = new Player(this, keyHandler);
int globalFPS = 0;
public GamePanel() {
this.setPreferredSize(new Dimension(screenWidth, screenHeight));
this.setBackground(Color.BLACK);
this.setDoubleBuffered(true);
this.addKeyListener(keyHandler);
this.setFocusable(true);
}
public void startGameThread() {
gameThread = new Thread(this);
gameThread.start();
}
@Override
public void run() {
double drawInterval = 1_000_000_000 / FPS; // 0.016666 seconds per frame
double delta = 0;
long lastTime = System.nanoTime();
long currentTime;
long timer = 0;
int drawCount = 0;
while (gameThread != null) {
currentTime = System.nanoTime();
delta += (currentTime - lastTime) / drawInterval;
timer += (currentTime - lastTime);
lastTime = currentTime;
if (delta >= 1) {
update();
repaint();
delta--;
drawCount++;
}
if (timer >= 1_000_000_000) {
System.out.println("FPS: " + drawCount);
globalFPS = drawCount;
drawCount = 0;
timer = 0;
}
}
}
public void update() {
player.update();
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
player.draw(g2d);
// Draw FPS on screen
g2d.setColor(Color.WHITE);
g2d.drawString("FPS: " + globalFPS, 10, 20);
g2d.dispose();
}
}
In this GamePanel
class, I’ve set up the screen dimensions, handled key inputs, and initiated the game loop. The screen dimensions are calculated based on a tile size of 48x48 pixels, with a grid of 16x12 tiles. This gives the game a classic retro feel, which I’m aiming for.
The game loop runs at a target of 60 FPS. I used a method to calculate the time interval between frames, ensuring smooth animation by updating the game state and redrawing the screen within the specified time frame.
Creating the Player Entity
With the game window and loop in place, the next step was to create a player sprite that could move around the screen. For this, I created a Player class that extends an Entity class. The Player class handles the sprite’s movement and rendering.
Here’s what the Player class looks like:
package entity;
import main.GamePanel;
import main.KeyHandler;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
public class Player extends Entity {
GamePanel gp;
KeyHandler keyHandler;
public Player(GamePanel gp, KeyHandler keyHandler) {
this.gp = gp;
this.keyHandler = keyHandler;
setDefaultValues();
}
public void setDefaultValues() {
x = 100;
y = 100;
speed = 4;
direction = EntityDirection.down;
getPlayerImage();
}
public void getPlayerImage() {
try {
up1 = ImageIO.read(getClass().getClassLoader().getResourceAsStream("player/boy_up_1.png"));
up2 = ImageIO.read(getClass().getClassLoader().getResourceAsStream("player/boy_up_2.png"));
down1 = ImageIO.read(getClass().getClassLoader().getResourceAsStream("player/boy_down_1.png"));
down2 = ImageIO.read(getClass().getClassLoader().getResourceAsStream("player/boy_down_2.png"));
left1 = ImageIO.read(getClass().getClassLoader().getResourceAsStream("player/boy_left_1.png"));
left2 = ImageIO.read(getClass().getClassLoader().getResourceAsStream("player/boy_left_2.png"));
right1 = ImageIO.read(getClass().getClassLoader().getResourceAsStream("player/boy_right_1.png"));
right2 = ImageIO.read(getClass().getClassLoader().getResourceAsStream("player/boy_right_2.png"));
} catch (IOException e) {
e.printStackTrace();
}
}
public void update() {
if(keyHandler.upPressed) {
direction = EntityDirection.up;
y -= speed;
}
if(keyHandler.downPressed) {
direction = EntityDirection.down;
y += speed;
}
if(keyHandler.leftPressed) {
direction = EntityDirection.left;
x -= speed;
}
if(keyHandler.rightPressed) {
direction = EntityDirection.right;
x += speed;
}
spriteCounter++;
if(spriteCounter > 10) {
if(spriteNum == 1) {
spriteNum = 2;
} else if (spriteNum == 2) {
spriteNum = 1;
}
spriteCounter = 0;
}
}
public void draw(Graphics2D g2d) {
BufferedImage image = null;
switch (direction) {
case up -> {
if (spriteNum == 1) {
image = up1;
}
if (spriteNum == 2) {
image = up2;
}
}
case down -> {
if (spriteNum == 1) {
image = down1;
}
if (spriteNum == 2) {
image = down2;
}
}
case left -> {
if (spriteNum == 1) {
image = left1;
}
if (spriteNum == 2) {
image = left2;
}
}
case right -> {
if (spriteNum == 1) {
image = right1;
}
if (spriteNum == 2) {
image = right2;
}
}
}
g2d.drawImage(image, x, y, gp.tileSize, gp.tileSize, null);
}
}
The Player
class is responsible for handling the player’s movement based on keyboard input, as well as for drawing the player sprite on the screen. I’ve used simple directional controls, where pressing the arrow keys moves the sprite in the corresponding direction. The sprite's image changes depending on the direction of movement, giving a basic animation effect.
Adding Sprite Animation and FPS Counter
To add some visual interest, I implemented basic sprite animation. The player’s sprite alternates between two images when moving, creating a walking effect. This is done by tracking a spriteCounter
that determines when to switch the displayed image.
The paintComponent
method in GamePanel
handles rendering the player sprite and displaying the FPS count on the screen. I used Graphics2D for drawing the image and text, which provides better control over the rendering process compared to the basic Graphics class.
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
player.draw(g2d);
// Draw FPS on screen
g2d.setColor(Color.WHITE);
g2d.drawString("FPS: " + globalFPS, 10, 20);
g2d.dispose();
}
This setup allows me to monitor the performance of the game in real-time, ensuring that the game maintains a steady frame rate.
What’s Next?
At this point, I’ve managed to create a simple game framework where a player sprite can move around a screen at 60 FPS. Although it’s still rudimentary, this foundation is solid. Next, I plan to expand the game by adding more entities, such as enemies or obstacles, and implementing collision detection. I’m also considering adding different levels or a scrolling background to create a more dynamic environment.
Building this game has been a great learning experience so far. Working directly with Java and Swing has given me a deeper understanding of the underlying mechanics of game development, and I’m excited to continue expanding on this project.
Stay tuned for more updates as I continue this journey!