Game Development 41 min read

Step‑by‑Step Java Swing Game Development Tutorial: Building the “RunDay” Runner Game with MVC Architecture

This tutorial walks through the complete development of a Java Swing runner game called RunDay, covering requirement documentation, MVC design, login, start screen, loading screen with threading, main gameplay including background scrolling, player animation, multiple obstacle types, collision detection, pause/continue logic, and the final end‑screen UI, all illustrated with full source code snippets.

Java Captain
Java Captain
Java Captain
Step‑by‑Step Java Swing Game Development Tutorial: Building the “RunDay” Runner Game with MVC Architecture

The article begins with a requirement document for the project "RunDay" (天天酷跑), describing the game concept, functional modules, developer, version, and development timeline.

Development Mode: MVC

Model (data layer) stores entity classes, View (display layer) stores UI classes, and Controller (logic layer) stores the core logic.

Enterprise Project Naming Convention

Package name example: cn.sqc.runday.view

1. Login Interface

UI layout includes username and password fields with login and cancel buttons.

package cn.sqc.runday.view;

import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;

/**
 * @author Huey
 * @date 2020-11-16
 * 登录界面:用户名输入框  密码输入框  登录取消按钮 功能
 */
public class LoginFrame extends JFrame{
  JLabel userLabel;
  JTextField userField;
  JLabel userLabel2;
  JPasswordField userField2;
  JButton Login,Cancel;

  public LoginFrame() {
    userLabel = new JLabel("用户名");
    userLabel.setFont(new Font("微软雅黑",Font.BOLD,18));
    userLabel2 = new JLabel("密  码");
    userLabel2.setFont(new Font("微软雅黑",Font.BOLD,18));
    userLabel.setBounds(20, 220, 100, 30);
    this.add(userLabel);
    userLabel2.setBounds(20, 280, 100, 30);
    this.add(userLabel2);
    userField = new JTextField();
    userField.setBounds(80, 220, 100, 30);
    userField.setBorder(BorderFactory.createLoweredBevelBorder());
    userField.setOpaque(false);
    this.add(userField);
    userField2 = new JPasswordField();
    userField2.setBounds(80, 280, 100, 30);
    userField2.setBorder(BorderFactory.createLoweredBevelBorder());
    userField2.setOpaque(false);
    this.add(userField2);
    Login = new JButton("登录");
    Login.setBounds(45,350,60,36);
    Login.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        String userName = userField.getText();
        String passWord = userField2.getText();
        if("Huey".equals(userName) && "123".equals(passWord)){
          JOptionPane.showMessageDialog(null, "欢迎"+userName+"来到天天酷跑游戏");
          dispose();
        }else if("".equals(userName) || "".equals(passWord)){
          JOptionPane.showMessageDialog(null, "用户名 / 密码不能为空,请重新输入!");
        }else{
          JOptionPane.showMessageDialog(null, "用户名 / 密码输入错误,请重新输入!");
        }
      }
    });
    this.add(Login);
    Cancel = new JButton("取消");
    Cancel.setBounds(135,350,60,36);
    this.add(Cancel);
    Cancel.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        dispose();
      }
    });
    LoginPanel panel = new LoginPanel();
    this.add(panel);
    this.setSize(900,530);
    this.setLocationRelativeTo(null);
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setUndecorated(true);
    this.setIconImage(new ImageIcon("Image/115.png").getImage());
    this.setVisible(true);
  }

  public static void main(String[] args) {
    new LoginFrame();
  }

  class LoginPanel extends JPanel{
    Image background;
    public LoginPanel() {
      try {
        background = ImageIO.read(new File("Image/login.jpg"));
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    @Override
    public void paint(Graphics g) {
      super.paint(g);
      g.drawImage(background, 0, 0,900,530, null);
    }
  }
}

2. Start Game Interface

Mouse hover changes button brightness; clicking starts the game.

package cn.sqc.runday.view;

import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import cn.sqc.runday.controller.WindowFrame;

public class MainFrame extends JFrame implements MouseListener {
  JLabel start,help,exit;
  JPanel MainPanel;
  public MainFrame() {
    start = new JLabel(new ImageIcon("Image/hh1.png"));
    start.setBounds(350,320,150,40);
    start.setEnabled(false);
    start.addMouseListener(this);
    this.add(start);
    help = new JLabel(new ImageIcon("Image/hh2.png"));
    help.setBounds(350,420,150,40);
    help.setEnabled(false);
    help.addMouseListener(this);
    this.add(help);
    exit = new JLabel(new ImageIcon("Image/hh3.png"));
    exit.setBounds(350, 520, 150, 40);
    exit.setEnabled(false);
    exit.addMouseListener(this);
    this.add(exit);
    MainPanel panel = new MainPanel();
    this.add(panel);
    this.setSize(1200,730);
    this.setLocationRelativeTo(null);
    this.setUndecorated(true);
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setIconImage(new ImageIcon("Image/115.png").getImage());
    this.setVisible(true);
  }

  public static void main(String[] args) {
    new MainFrame();
  }

  class MainPanel extends JPanel{
    Image background;
    public MainPanel() {
      try {
        background = ImageIO.read(new File("Image/main.png"));
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    @Override
    public void paint(Graphics g) {
      super.paint(g);
      g.drawImage(background, 0, 0,1200,730, null);
    }
  }

  @Override
  public void mouseClicked(MouseEvent e) {
    if(e.getSource().equals(start)){
      new WindowFrame().Start();
    }else if(e.getSource().equals(exit)){
      dispose();
    }else if(e.getSource().equals(help)){
      JOptionPane.showMessageDialog(null, "有疑问请联系开发者:Huey");
    }
  }

  @Override
  public void mouseEntered(MouseEvent e) {
    if(e.getSource().equals(start)){
      start.setEnabled(true);
    }else if(e.getSource().equals(help)){
      help.setEnabled(true);
    }else if(e.getSource().equals(exit)){
      exit.setEnabled(true);
    }
  }

  @Override
  public void mouseExited(MouseEvent e) {
    if(e.getSource().equals(start)){
      start.setEnabled(false);
    }else if(e.getSource().equals(help)){
      help.setEnabled(false);
    }else if(e.getSource().equals(exit)){
      exit.setEnabled(false);
    }
  }

  // Unused MouseListener methods omitted for brevity
}

3. Loading Screen (Threaded Progress Bar)

A separate thread updates a JProgressBar to simulate loading.

package cn.sqc.runday.controller;

import java.awt.BorderLayout;
import java.awt.Color;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JProgressBar;

/**
 * @author Huey
 * @date 2020-11-18
 * 缓存加载界面:背景图片、进度条
 * 动态加载过程。(线程)
 */
public class WindowFrame extends JFrame implements Runnable{
  JLabel background;
  JProgressBar jdt;

  public void Start(){
    WindowFrame frame = new WindowFrame();
    Thread t = new Thread(frame);
    t.start();
    dispose();
  }

  public WindowFrame() {
    background = new JLabel(new ImageIcon("Image/hbg.jpg"));
    this.add(BorderLayout.NORTH,background);
    jdt = new JProgressBar();
    jdt.setStringPainted(true);
    jdt.setBackground(Color.ORANGE);
    this.add(BorderLayout.SOUTH,jdt);
    this.setSize(568,340);
    this.setLocationRelativeTo(null);
    this.setDefaultCloseOperation(3);
    this.setUndecorated(true);
    this.setIconImage(new ImageIcon("Image/115.png").getImage());
    this.setVisible(true);
  }

  public static void main(String[] args) {
    new WindowFrame().Start();
  }

  @Override
  public void run() {
    int [] values = {0,1,3,10,23,32,40,47,55,66,76,86,89,95,99,99,99,100};
    for(int i=0; i

4. Main Game Interface

The core gameplay includes background scrolling, player animation, five obstacle types, collision detection, pause/continue, and game‑over handling.

Game Frame

package cn.sqc.runday.view;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import cn.sqc.runday.controller.GamePanel;

public class GameFrame extends JFrame {
  public static final int WIDTH=1500;
  public static final int HEIGHT=900;
  public GameFrame() {
    GamePanel panel = new GamePanel();
    panel.action();
    this.addKeyListener(panel);
    this.add(panel);
    this.setSize(WIDTH,HEIGHT);
    this.setLocationRelativeTo(null);
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setIconImage(new ImageIcon("Image/115.png").getImage());
    this.setUndecorated(true);
    this.setVisible(true);
  }

  public static void main(String[] args) {
    new GameFrame();
  }
}

Game Panel (Core Logic)

package cn.sqc.runday.controller;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
import cn.sqc.runday.model.*;
import cn.sqc.runday.view.EndFrame;
import cn.sqc.runday.view.GameFrame;

public class GamePanel extends JPanel implements KeyListener{
  Image background;
  Image score;
  Image pause;
  Image proceed;
  Person person;
  Barrs_2 barrs_2;
  Barrs_1[] barrs1 = {};
  Barrs_3[] barrs3 = {};
  Barrs_4[] barrs4 = {};
  Barrs_5[] barrs5 = {};
  int x=0;
  boolean flag = true;
  public static boolean isGameOver = false;

  public GamePanel(){
    person = new Person();
    barrs_2 = new Barrs_2();
    try{
      background = ImageIO.read(new File("Image/cc.png"));
      score = ImageIO.read(new File("Image/a12.png"));
      pause = ImageIO.read(new File("Image/b2.png"));
      proceed = ImageIO.read(new File("Image/b1.png"));
    }catch(IOException e){
      e.printStackTrace();
    }
  }

  @Override
  public void paint(Graphics g){
    super.paint(g);
    if(flag){ x-=20; }
    g.drawImage(background, x, 0, GameFrame.WIDTH, GameFrame.HEIGHT, null);
    g.drawImage(background, x+GameFrame.WIDTH, 0, GameFrame.WIDTH, GameFrame.HEIGHT,null);
    if(x<=-GameFrame.WIDTH){ x=0; }
    person.paintPerson(g);
    for(int i=0;i
10 && y1>10){
      person.setY(y-25);
      barrs_2.setY(y-25);
    }
    if(e.getKeyCode()==KeyEvent.VK_DOWN && y<=560 && y1<560){
      person.setY(y+30);
      barrs_2.setY(y-30);
    }
    if(e.getKeyCode()==KeyEvent.VK_LEFT && x>=0){
      person.setX(x-30);
      barrs_2.setX(x1-30);
    }
    if(e.getKeyCode()==KeyEvent.VK_RIGHT){
      person.setX(x+22);
      barrs_2.setX(x1+22);
      if(x>=GameFrame.WIDTH-Person.WIDTH){
        person.setX(GameFrame.WIDTH-Person.WIDTH);
      }
      if(x1>=GameFrame.WIDTH-barrs_2.WIDTH){
        barrs_2.setX(GameFrame.WIDTH - barrs_2.WIDTH);
      }
    }
    if(e.getKeyCode() == KeyEvent.VK_SPACE){
      flag = !flag; // pause / continue
    }
  }

  // Remaining KeyListener methods omitted.
}

5. Obstacle Classes

Five obstacle types are implemented as separate model classes (Crab, Pet, Missile, Harpoon, Coin), each with image loading, movement, rendering, and boundary detection.

Example: Crab (Barrs_1)

package cn.sqc.runday.model;

import java.awt.Graphics;
import java.awt.Image;
import java.io.File;
import javax.imageio.ImageIO;
import cn.sqc.runday.view.GameFrame;

public class Barrs_1 {
  private Image image;
  private Image [] images;
  public static final int WIDTH=100;
  public static final int HEIGHT=110;
  private int x,y;
  int  index;
  private int speed;

  public Barrs_1(){
    images = new Image[2];
    try {
      images[0]=ImageIO.read(new File("image/a2.png"));
      images[1]=ImageIO.read(new File("image/a4.png"));
    } catch (Exception e) {}
    image = images[0];
    x=GameFrame.WIDTH+100;
    y=580;
    speed =30;
    index = 0;
  }

  public void step(){
    image =images[index++/5%images.length];
    x-=speed;
  }
  public void paintBarrs(Graphics g){
    g.drawImage(image, x,y,WIDTH,HEIGHT, null);
  }
  public boolean outofBounds(){
    return this.x <= -WIDTH;
  }
  // getters and setters omitted for brevity
}

Collision Logic Example (Player vs. Missile)

for(int i = 0;i
= barrs3[i].getX() &&
     person.getX() <= barrs3[i].getX() + Barrs_3.WIDTH &&
     person.getY() + Person.getHeight() >= barrs3[i].getY() &&
     person.getY() <= barrs3[i].getY() + Barrs_3.HEIGHT){
    if(person.getX() + Person.WIDTH <= barrs3[i].getX() + Barrs_3.WIDTH){
      person.setX(barrs3[i].getX() - Barrs_3.WIDTH);
    }else{
      person.setX(barrs3[i].getX() + Barrs_3.WIDTH);
    }
  }
}

6. Pause / Continue

Pressing the space bar toggles the boolean flag , which controls whether the game loop updates positions and renders the moving background; the UI shows a pause or continue icon accordingly.

7. End Screen

The end screen displays the final score, distance, and total score, and provides buttons for "Again", "Back to Main Menu", and "Exit".

package cn.sqc.runday.view;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import cn.sqc.runday.model.Person;

public class EndFrame extends JFrame implements MouseListener {
  JLabel again,back,exit;

  public EndFrame(Person person) {
    again = new JLabel(new ImageIcon("Image/hh5.png"));
    again.setBounds(520, 622, 60, 25);
    again.addMouseListener(this);
    this.add(again);
    back = new JLabel(new ImageIcon("Image/hh6.png"));
    back.setBounds(520, 722, 60, 25);
    back.addMouseListener(this);
    this.add(back);
    exit = new JLabel(new ImageIcon("Image/hh3.png"));
    exit.setBounds(520, 822, 60, 25);
    exit.addMouseListener(this);
    this.add(exit);
    EndPanel end = new EndPanel(person);
    this.add(end);
    this.setSize(1500, 900);
    this.setLocationRelativeTo(null);
    this.setUndecorated(true);
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setIconImage(new ImageIcon("Image/115.png").getImage());
    this.setVisible(true);
  }

  class EndPanel extends JPanel{
    Image background;
    Person p;
    public EndPanel(Person person){
      this.p = person;
      try {
        background = ImageIO.read(new File("Image/chou.png"));
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    @Override
    public void paint(Graphics g){
      super.paint(g);
      g.drawImage(background, 0, 0,1500,900 ,null);
      g.setColor(Color.CYAN);
      g.setFont(new Font("宋体",Font.BOLD,30));
      g.drawString(p.getScore()+"",1110,705);
      g.drawString(p.getDistance() + " ", 1110, 622);
      g.setFont(new Font("宋体",Font.BOLD,50));
      g.setColor(Color.ORANGE);
      g.drawString(p.getTotalScore() + "", 1075, 500);
    }
  }

  @Override
  public void mouseClicked(MouseEvent e){
    if(e.getSource().equals(again)){
      new WindowFrame().Start();
      dispose();
    }else if(e.getSource().equals(back)){
      new MainFrame();
      dispose();
    }else if(e.getSource().equals(exit)){
      System.exit(0);
    }
  }

  // Other MouseListener methods omitted.
}

The tutorial concludes with a reminder to like and share if the content was helpful.

JavaMVCGame developmentthreadingcollision detectionSwing
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.