Learn how to program
by playing video games.

Java Projects

How to make a Video Game in Java (2D Basics)

February 4, 2021
Part of: Java Projects

This project will get you making your first game in Java! Take my starter code (I explain how it works) and build your own game! Whether your a beginner or intermediate programmer, I layout some ideas for you on what to build next to progress your skills. This is a great project for all Java students, and might even look good on that portfolio for your resume. Have fun developing your first video game!

Links
Grab this starter code on GitHub: https://github.com/learncodebygaming/java_2d_game
Need help getting setup for Java development? https://www.youtube.com/watch?v=U3UV8c8TFG4
Missed the Joy of Coding 2D Graphics project? https://www.youtube.com/watch?v=IyBsWymfqms
Recommended Java 2D Game resource: https://zetcode.com/javagames/

What's up guys? Today I wanna show you how to make a video game in Java. If you're in that beginner-to-intermediate range of learning how to program, and you're looking for a project that isn't super boring, you're in the right place. Or even if you need a project to add to your resume: this is something that's really visual, that can be really impressive looking.

This is an active tutorial. I've designed this project specifically for you to write your own code, and to make a video game that's all your own.

We'll be making a 2D game, using the Java Swing library, and this project builds off of the work we did in the Bob Ross "Joy of Coding" video. So if you get really confused as I start to go through this, or if you need some more practice with 2D graphics, then you should go back and do that project first. And that should give you everything you need to be ready for this one.

My idea for this project was: there's certain things in developing a game that are just kinda difficult, but they're unavoidable. And I don't want to overwhelm you with things you might not be ready for yet, that maybe are acting as a barrier to you having fun with code. So I thought I'd start you off with like a template project. Just a real simple game that gets you passed some of those initial hurdles, so you can get to the fun parts.

Now I'm not going to do a full code-along this time, but I want to get you familiar with the codebase you'll be working with. So what I think we'll do is: in the first half of this video I want to show you how I built up this code, So that you can feel comfortable with it even if you don't understand what every line does.

Then in the second half I want to give you a bunch of ideas and direction for different ways you can build out your game. Sort of like little homework projects that can all add up to a game that's uniquely yours. And I'm going to be breaking down those ideas from easiest to hardest, so you can properly progress your skills as you work on your game.

So if you're ready, the first thing you'll want to do is: download this project off of Github, run it once to make sure it's working, and then come back here and we'll talk through the code. You can either clone it, if you're familiar with Git, or you can just download the .ZIP file.

App.java

import javax.swing.*;

class App {

    private static void initWindow() {
        // create a window frame and set the title in the toolbar
        JFrame window = new JFrame("Can't Stop, Won't Stop, GameStop");
        // when we close the window, stop the app
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // create the jpanel to draw on.
        // this also initializes the game loop
        Board board = new Board();
        // add the jpanel to the window
        window.add(board);
        // pass keyboard inputs to the jpanel
        window.addKeyListener(board);
        
        // don't allow the user to resize the window
        window.setResizable(false);
        // fit the window size around the components (just our jpanel).
        // pack() should be called after setResizable() to avoid issues on some platforms
        window.pack();
        // open window in the center of the screen
        window.setLocationRelativeTo(null);
        // display the window
        window.setVisible(true);
    }

    public static void main(String[] args) {
        // invokeLater() is used here to prevent our graphics processing from
        // blocking the GUI. https://stackoverflow.com/a/22534931/4655368
        // this is a lot of boilerplate code that you shouldn't be too concerned about.
        // just know that when main runs it will call initWindow() once.
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                initWindow();
            }
        });
    }
}

Board.java

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.*;

public class Board extends JPanel implements ActionListener, KeyListener {

    // controls the delay between each tick in ms
    private final int DELAY = 25;
    // controls the size of the board
    public static final int TILE_SIZE = 50;
    public static final int ROWS = 12;
    public static final int COLUMNS = 18;
    // controls how many coins appear on the board
    public static final int NUM_COINS = 5;
    // suppress serialization warning
    private static final long serialVersionUID = 490905409104883233L;
    
    // keep a reference to the timer object that triggers actionPerformed() in
    // case we need access to it in another method
    private Timer timer;
    // objects that appear on the game board
    private Player player;
    private ArrayList coins;

    public Board() {
        // set the game board size
        setPreferredSize(new Dimension(TILE_SIZE * COLUMNS, TILE_SIZE * ROWS));
        // set the game board background color
        setBackground(new Color(232, 232, 232));

        // initialize the game state
        player = new Player();
        coins = populateCoins();

        // this timer will call the actionPerformed() method every DELAY ms
        timer = new Timer(DELAY, this);
        timer.start();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // this method is called by the timer every DELAY ms.
        // use this space to update the state of your game or animation
        // before the graphics are redrawn.

        // prevent the player from disappearing off the board
        player.tick();

        // give the player points for collecting coins
        collectCoins();

        // calling repaint() will trigger paintComponent() to run again,
        // which will refresh/redraw the graphics.
        repaint();
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        // when calling g.drawImage() we can use "this" for the ImageObserver 
        // because Component implements the ImageObserver interface, and JPanel 
        // extends from Component. So "this" Board instance, as a Component, can 
        // react to imageUpdate() events triggered by g.drawImage()

        // draw our graphics.
        drawBackground(g);
        drawScore(g);
        for (Coin coin : coins) {
            coin.draw(g, this);
        }
        player.draw(g, this);

        // this smooths out animations on some systems
        Toolkit.getDefaultToolkit().sync();
    }

    @Override
    public void keyTyped(KeyEvent e) {
        // this is not used but must be defined as part of the KeyListener interface
    }

    @Override
    public void keyPressed(KeyEvent e) {
        // react to key down events
        player.keyPressed(e);
    }

    @Override
    public void keyReleased(KeyEvent e) {
        // react to key up events
    }

    private void drawBackground(Graphics g) {
        // draw a checkered background
        g.setColor(new Color(214, 214, 214));
        for (int row = 0; row < ROWS; row++) {
            for (int col = 0; col < COLUMNS; col++) {
                // only color every other tile
                if ((row + col) % 2 == 1) {
                    // draw a square tile at the current row/column position
                    g.fillRect(
                        col * TILE_SIZE, 
                        row * TILE_SIZE, 
                        TILE_SIZE, 
                        TILE_SIZE
                    );
                }
            }    
        }
    }

    private void drawScore(Graphics g) {
        // set the text to be displayed
        String text = "$" + player.getScore();
        // we need to cast the Graphics to Graphics2D to draw nicer text
        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(
            RenderingHints.KEY_TEXT_ANTIALIASING,
            RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g2d.setRenderingHint(
            RenderingHints.KEY_RENDERING,
            RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(
            RenderingHints.KEY_FRACTIONALMETRICS,
            RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        // set the text color and font
        g2d.setColor(new Color(30, 201, 139));
        g2d.setFont(new Font("Lato", Font.BOLD, 25));
        // draw the score in the bottom center of the screen
        // https://stackoverflow.com/a/27740330/4655368
        FontMetrics metrics = g2d.getFontMetrics(g2d.getFont());
        // the text will be contained within this rectangle.
        // here I've sized it to be the entire bottom row of board tiles
        Rectangle rect = new Rectangle(0, TILE_SIZE * (ROWS - 1), TILE_SIZE * COLUMNS, TILE_SIZE);
        // determine the x coordinate for the text
        int x = rect.x + (rect.width - metrics.stringWidth(text)) / 2;
        // determine the y coordinate for the text
        // (note we add the ascent, as in java 2d 0 is top of the screen)
        int y = rect.y + ((rect.height - metrics.getHeight()) / 2) + metrics.getAscent();
        // draw the string
        g2d.drawString(text, x, y);
    }

    private ArrayList populateCoins() {
        ArrayList coinList = new ArrayList<>();
        Random rand = new Random();

        // create the given number of coins in random positions on the board.
        // note that there is not check here to prevent two coins from occupying the same
        // spot, nor to prevent coins from spawning in the same spot as the player
        for (int i = 0; i < NUM_COINS; i++) {
            int coinX = rand.nextInt(COLUMNS);
            int coinY = rand.nextInt(ROWS);
            coinList.add(new Coin(coinX, coinY));
        }

        return coinList;
    }

    private void collectCoins() {
        // allow player to pickup coins
        ArrayList collectedCoins = new ArrayList<>();
        for (Coin coin : coins) {
            // if the player is on the same tile as a coin, collect it
            if (player.getPos().equals(coin.getPos())) {
                // give the player some points for picking this up
                player.addScore(100);
                collectedCoins.add(coin);
            }
        }
        // remove collected coins from the board
        coins.removeAll(collectedCoins);
    }

}

Player.java

import java.awt.event.KeyEvent;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.awt.Point;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

public class Player {

    // image that represents the player's position on the board
    private BufferedImage image;
    // current position of the player on the board grid
    private Point pos;
    // keep track of the player's score
    private int score;

    public Player() {
        // load the assets
        loadImage();

        // initialize the state
        pos = new Point(0, 0);
        score = 0;
    }

    private void loadImage() {
        try {
            // you can use just the filename if the image file is in your
            // project folder, otherwise you need to provide the file path.
            image = ImageIO.read(new File("images/player.png"));
        } catch (IOException exc) {
            System.out.println("Error opening image file: " + exc.getMessage());
        }
    }

    public void draw(Graphics g, ImageObserver observer) {
        // with the Point class, note that pos.getX() returns a double, but 
        // pos.x reliably returns an int. https://stackoverflow.com/a/30220114/4655368
        // this is also where we translate board grid position into a canvas pixel
        // position by multiplying by the tile size.
        g.drawImage(
            image, 
            pos.x * Board.TILE_SIZE, 
            pos.y * Board.TILE_SIZE, 
            observer
        );
    }
    
    public void keyPressed(KeyEvent e) {
        // every keyboard get has a certain code. get the value of that code from the
        // keyboard event so that we can compare it to KeyEvent constants
        int key = e.getKeyCode();
        
        // depending on which arrow key was pressed, we're going to move the player by
        // one whole tile for this input
        if (key == KeyEvent.VK_UP) {
            pos.translate(0, -1);
        }
        if (key == KeyEvent.VK_RIGHT) {
            pos.translate(1, 0);
        }
        if (key == KeyEvent.VK_DOWN) {
            pos.translate(0, 1);
        }
        if (key == KeyEvent.VK_LEFT) {
            pos.translate(-1, 0);
        }
    }

    public void tick() {
        // this gets called once every tick, before the repainting process happens.
        // so we can do anything needed in here to update the state of the player.

        // prevent the player from moving off the edge of the board sideways
        if (pos.x < 0) {
            pos.x = 0;
        } else if (pos.x >= Board.COLUMNS) {
            pos.x = Board.COLUMNS - 1;
        }
        // prevent the player from moving off the edge of the board vertically
        if (pos.y < 0) {
            pos.y = 0;
        } else if (pos.y >= Board.ROWS) {
            pos.y = Board.ROWS - 1;
        }
    }

    public String getScore() {
        return String.valueOf(score);
    }

    public void addScore(int amount) {
        score += amount;
    }

    public Point getPos() {
        return pos;
    }

}

Coin.java

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.awt.Point;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

public class Coin {
    
    // image that represents the coin's position on the board
    private BufferedImage image;
    // current position of the coin on the board grid
    private Point pos;

    public Coin(int x, int y) {
        // load the assets
        loadImage();

        // initialize the state
        pos = new Point(x, y);
    }

    private void loadImage() {
        try {
            // you can use just the filename if the image file is in your
            // project folder, otherwise you need to provide the file path.
            image = ImageIO.read(new File("images/coin.png"));
        } catch (IOException exc) {
            System.out.println("Error opening image file: " + exc.getMessage());
        }
    }

    public void draw(Graphics g, ImageObserver observer) {
        // with the Point class, note that pos.getX() returns a double, but 
        // pos.x reliably returns an int. https://stackoverflow.com/a/30220114/4655368
        // this is also where we translate board grid position into a canvas pixel
        // position by multiplying by the tile size.
        g.drawImage(
            image, 
            pos.x * Board.TILE_SIZE, 
            pos.y * Board.TILE_SIZE, 
            observer
        );
    }

    public Point getPos() {
        return pos;
    }

}

Now let's talk about some of the things you can do with this starter code, to build out your own game. You're not meant to do all of these. Just pick and choose things you like or want to try. And if you have any ideas that aren't on these lists, definitely just go for it.

Beginner

  • Change the colors that are being used.
  • Change the image files that are being used.
  • Change how many points you get per coin.
  • Make instance or class variables to control hardcoded values like the ones just mentioned.
  • Use WASD instead of arrows for movement.
  • Change the dimensions of the game board.
  • Make a new coin appear whenever the player picks one up.
  • Change the tile size.
    • Remember to update your image files, or scale the images.

Intermediate

  • Make coins disappear after some time.
    • By ticks, or using a separate timer, or after the player has moved so many squares.
  • Make more coins appear at random intervals.
  • Replace the checkered background with an image.
  • Player and Coin share a lot of commonalities. Create a parent class that both of these classes extend from to reduce code duplication.
  • Make a special coin that looks different and is worth more points.
  • End or restart the game when all coins are collected, or when a certain score is reached.
  • Decide what winning means, then redraw the whole canvas with a celebration graphic when you win the game.
  • Add a game clock.
    • Could count up or down.
    • Could replace the score or be in addition to the score.
    • Display it like the score.
  • Keep track of high scores.
    • In a single play session.
    • Or across all sessions by reading/writing to a file.
  • Allow the player to wrap around the edges of the board.

Advanced

  • Add obstacles to block player movement.
  • Add an object type that reduces your score when touched. Or maybe it ends the game.
  • Make an object type that moves around on its own. Could be like the ghosts from pacman.
  • Add sounds.
    • When a player moves, or picks up coins.
    • A constant sound track.
    • A sound that plays when you first open the game.
  • Implement delta movements to only allow players to move one tile per tick.
    • Can play around with the tick rate when developing this.
    • React to both pressed and released.
    • Can enable diagonal movements.
    • Fixes the issue caused by holding down a key, and makes for a more responsive experience.
  • Make the total game area larger than the portion of that grid we see in the game window.
    • So maybe the viewport moves as the player approaches an edge.
    • Or maybe the player stays in the middle and the whole viewport moves with the player whenever the player moves.
  • Think about other keyboard keys that might perform some other action.
    • Can be as simple as changing some colors in the game.
    • Or maybe you've got health potions to restore player health.
  • Add more scoreboard/HUD elements.
    • Maybe move this off of the game board itself, to some designated area on the canvas. I recommend still using just the single JPanel if you want to do this.
  • Eliminate the grid concept altogether and use pixel positions instead.

Expert

To dive even deeper into developing your Java 2D game, I recommend checking out this series of tutorials for guidance and inspiration: https://zetcode.com/javagames/


Alright so good luck on building what's probably your first computer game! Hopefully you've got a lot of ideas, and some sense of how you might accomplish them. I really tried to strip this project down, and make it as easy for you to build off of as possible.

Once you've finished your project, and you're wondering what's next? First of all I'm super proud of you: you've accomplished something most people will only ever dream of. And the next step would be to just make another game, but push yourself to be a little more ambitious this second time around.

And then after that you're ready to graduate to a proper game development library. I've never used any Java game development libraries, so I can't really give you any recommendations there. But let me know if you end up finding one you like, and maybe I can check that out in a future video.


AP Computer Science A - Study Session
Get prepared for the AP Computer Science A exam! I'll let you know what to expect and go through some example questions from the official …
Ben Johnson My name is Ben and I help people learn how to code by gaming. I believe in the power of project-based learning to foster a deep understanding and joy in the craft of software development. On this site I share programming tutorials, coding-game reviews, and project ideas for you to explore.