2D Game Development with Java and Swing

Written on , by Andrew Lalis.

Often times, new programmers will immediately jump into making graphical applications using their new-found skills, without getting a firm grasp of how to design something that doesn't require refactoring literally any time new functionality is required. Obviously, we can't just collectively tell all new programmers to just be patient; that will never work. So hopefully this article can serve as a guide to put you on the right track to developing 2D applications (games, most likely) using Java and Swing.
I will be referencing snippets of code that come from this repository, so check there if you want to read through all the code yourself.

Since this is quite a large guide, you can skip to a particular section if you want:

Model-View-Control Design

At first, it's super easy to make a new JFrame and start drawing on it, and you can add action listeners everywhere, whenever they're needed. If you ever plan on expanding beyond a simple 20-line drawing method however, you should organize your code using the Model-View-Controller pattern. For example, in my Before Asteroids sample game, I have split up my code into a few different packages.

In most cases, it's especially useful to define a single class that represents your entire game's state; a so-called Game Model. This model would contain all the entities in your game that exist in the world, for example. Or in the case of something like a simple tic-tac-toe game, it would contain data about the status of each of the nine squares on the board.

Keep this design pattern in mind, and working on your project as it grows larger hopefully won't become as much of a headache anymore.

Setting up a Swing GUI

Making a Swing GUI usually consists of two basic components: the JFrame that acts as your application's window, and a JPanel that is used for actually drawing your model. Usually we start by defining our own frame to extend from the base JFrame, and set its properties in a constructor.


				public class GameFrame extends JFrame {
					public GameFrame(GameModel model) {
						this.setTitle("My Game");
						this.setPreferredSize(new Dimension(800, 600));
						// Set the frame to be disposed when the top-right 'X' is clicked.
						this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
						GamePanel gamePanel = new GamePanel(model);
						this.setContentPane(gamePanel);
						this.pack();
						// Start with the frame centered in the screen.
						this.setLocationRelativeTo(null);
						// TODO: Setup listeners and game loop logic.
					}
				}
			
Notice that we pass in our game model as a constructor argument. This is so that we can pass it to our GamePanel, which will ultimately be drawing the model.

Inside the frame, there's a single GamePanel that we give a reference to the game model. This panel is responsible for rendering the model to the screen, and usually we can just define it as a child class of Swing's JPanel.


				public class GamePanel extends JPanel {
					private GameModel model;

					public GamePanel(GameModel model) {
						this.model = model;
					}

					@Override
					protected void paintComponent(Graphics g) {
						super.paintComponent(g);
						Graphics2D g2 = (Graphics2D) g;
						g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
						g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
						g2.setColor(Color.BLACK);
						g2.fillRect(0, 0, this.getWidth(), this.getHeight());
						// TODO: Draw model on screen.
					}
				}
			
To draw on a panel, all you need to do is override paintComponent. The Javadoc advise calling the super method to avoid graphical bugs, so we do that first. Then we set some rendering hints to turn on anti-aliasing and how strokes (outlines) are rendered. Finally we clear the screen to a black rectangle.

The Game Loop

First of all, this guide won't go too in-depth about the details of game loops in general, but focuses specifically on their implementation with Java/Swing. For more information, you can read this excellent article on the subject.

In Swing GUIs, your application is by-default multi-threaded, since Swing has its own thread for managing the user interface. In order to run our game and not interfere with Swing's own GUI processing, we should have our game update logic run in its own thread. The thread should periodically update the model, according to some defined frequency, and it should also trigger the rendering of frames of your game, once again according to some defined frequency. In the example below, we update physics 60 times per second, and render the model also 60 times per second, but these values can easily be changed due to how I've set up the math.


				public class GameUpdater extends Thread {
					public static final double PHYSICS_FPS = 60.0;
					public static final double MILLISECONDS_PER_PHYSICS_TICK = 1000.0 / PHYSICS_FPS;
					public static final double PHYSICS_SPEED = 1.0;

					public static final double DISPLAY_FPS = 60.0;
					public static final double MILLISECONDS_PER_DISPLAY_FRAME = 1000.0 / DISPLAY_FPS;

					private final GameModel model;
					private final GamePanel gamePanel;
					private volatile boolean running = true;

					public GameUpdater(GameModel model, GamePanel gamePanel) {
						this.model = model;
						this.gamePanel = gamePanel;
					}

					public void setRunning(boolean running) {
						this.running = running;
					}

					@Override
					public void run() {
						long lastPhysicsUpdate = System.currentTimeMillis();
						long lastDisplayUpdate = System.currentTimeMillis();
						while (this.running) {
							long currentTime = System.currentTimeMillis();
							long timeSinceLastPhysicsUpdate = currentTime - lastPhysicsUpdate;
							long timeSinceLastDisplayUpdate = currentTime - lastDisplayUpdate;
							if (timeSinceLastPhysicsUpdate >= MILLISECONDS_PER_PHYSICS_TICK) {
								double elapsedSeconds = timeSinceLastPhysicsUpdate / 1000.0;
								this.updateModelPhysics(elapsedSeconds * PHYSICS_SPEED);
								lastPhysicsUpdate = currentTime;
								timeSinceLastPhysicsUpdate = 0L;
							}
							if (timeSinceLastDisplayUpdate >= MILLISECONDS_PER_DISPLAY_FRAME) {
								this.gamePanel.repaint();
								lastDisplayUpdate = currentTime;
								timeSinceLastDisplayUpdate = 0L;
							}
							long timeUntilNextPhysicsUpdate = (long) (MILLISECONDS_PER_PHYSICS_TICK - timeSinceLastPhysicsUpdate);
							long timeUntilNextDisplayUpdate = (long) (MILLISECONDS_PER_DISPLAY_FRAME - timeSinceLastDisplayUpdate);

							// Sleep to reduce CPU usage.
							try {
								Thread.sleep(Math.min(timeUntilNextPhysicsUpdate, timeUntilNextDisplayUpdate));
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
					}
					// Other methods omitted for brevity.
				}
			
Basically, our game loop logic runs continuously until something else sets the running flag to false. During the loop, we determine how long it's been since the last physics and display updates, and if enough time has passed, we do an update and reset the "timeSince..." variable. To avoid wasting CPU usage on rapidly looping, we sleep until the next update.

We can add our game updater thread to the end of our frame's constructor, so that when we initialize the game frame, the game loop begins.


				public class GameFrame extends JFrame {
					public GameFrame(GameModel model) {
						this.setTitle("My Game");
						this.setPreferredSize(new Dimension(800, 600));
						// Set the frame to be disposed when the top-right 'X' is clicked.
						this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
						GamePanel gamePanel = new GamePanel(model);
						this.setContentPane(gamePanel);
						this.pack();
						// Start with the frame centered in the screen.
						this.setLocationRelativeTo(null);

						this.updater = new GameUpdater(model, gamePanel);
						this.updater.start();
						this.addWindowListener(new WindowAdapter() {
							@Override
							public void windowClosing(WindowEvent e) {
								updater.setRunning(false);
							}
						});
						// TODO: Setup listeners
					}
				}
			

Listeners and Control

Most often, you'll want something to happen when the player presses a key or clicks the mouse. To do this, you should create your own class which extends from KeyAdapter or MouseAdapter, depending on what you want to do.
You can also directly implement KeyListener and MouseListener at the same time, but this isn't as clean, and you have to implement every method declared in the interfaces, even if you aren't using them.

Let's take a look at a common case: doing something when a key is pressed. To do so, we override the KeyAdapter's keyPressed method like so:


				@Override
				public void keyPressed(KeyEvent e) {
					int c = e.getKeyCode();
					if (c == KeyEvent.VK_W) {
						player.getShip().forwardThrusterEnabled = true;
					} else if (c == KeyEvent.VK_A) {
						player.getShip().turningLeft = true;
					} else if (c == KeyEvent.VK_D) {
						player.getShip().turningRight = true;
					}
				}
			
To check what key was pressed, we call getKeyCode() on the key event that was received, and then check if it matches one of the constants defined in the KeyEvent class, and if so, then we do something to our player. In this case, we set some boolean flags that indicate how the player's ship is trying to move.

Handling mouse events works almost the same way, but instead we check what button of the mouse was pressed.


				@Override
				public void mouseClicked(MouseEvent e) {
					int b = e.getButton();
					if (b == MouseEvent.BUTTON1) {
						System.out.printf("Player clicked at x=%d, y=%d\n", e.getX(), e.getY());
						player.getShip().firingBlaster = true;
					}
				}
			
In this example, I show both checking which button was pressed, and also checking where the button was pressed. getX() and getY() return the x- and y-coordinates relative to the source component, which will most often be the JFrame or JPanel that the user clicked on.
Back to Articles