使用Java制作小游戏:DxBall(打砖块)

Posted akyna-zh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Java制作小游戏:DxBall(打砖块)相关的知识,希望对你有一定的参考价值。

使用Java复刻经典游戏——打砖块DxBall

游戏需要实现的功能。要求如下:

1、 实现游戏菜单界面;

2、 实现游戏主体功能;

3、 实现实时在左(右)上角分数显示;

4、 实现穿刺球、火箭球、炸弹球效果;

5、 实现物品掉落效果,当撞击到某一块砖块会以一定概率掉落奖惩物品,奖励物品包括但不限于:穿刺球、火箭球、炸弹球、板变长(x2、x4、x8)、球变大;惩罚物品包括但不限于:球变小、板变短(x1/2、x1/4、x1/8)、死亡;

6、 可以设置多关卡,也可以仅制作一关卡;

其余可玩点与创意可自由发挥。

设计思路

在这里插入图片描述

代码模块

1.开始界面:

/**
 * @author jzh
 * @date 2021/6/21
 * class 开始界面类
 */

public class StartInterface extends JFrame {
    private int choose = 0, speed = 3, passNum = 0;
    public final int GAME_X = GameInterface.GAME_X, GAME_Y = GameInterface.GAME_Y,
            GAME_WIDTH = GameInterface.GAME_WIDTH, GAME_HEIGHT = GameInterface.GAME_HEIGHT;

    public StartInterface() {
        setTitle("DxBall");
        setVisible(true);
        setBounds(GAME_X, GAME_Y, GAME_WIDTH, GAME_HEIGHT);
        JPanel myPanel = new JPanel();
        myPanel.setBackground(new Color(82, 139, 139));

        // 游戏名称
        JLabel gameName = new JLabel("DxBall");
        gameName.setFont(new Font("Times New Roman", Font.BOLD, 100));
        myPanel.add(gameName);

        // 规则目录
        JButton textButton = new JButton();
        textButton.setBackground(Color.gray);
        textButton.setText("<html>" +
                "1.基本原则:接住小球并打下尽可能多的砖块。<br>" +
                "2.键盘操作:按左右键即可移动砖块。<br>" +
                "3.关卡设置:菜鸟;新手;专业;大师。<br>" +
                "4.过关要求:获得超过2000分则可过关。<br>" +
                "5.加分规则1:每打一个砖块加100分。<br>" +
                "6.加分规则2:每接收到一个道具加500分。<br>" +
                "7.技巧1:低难度下尽可能在短时间内获得高分。<br>" +
                "8.技巧2:高难度下尽可能存活更长时间。<br>" +
                "9.道具说明:撞击砖块会随机掉落道具。<br>" +
                "10.道具类型:<br>" +
                "板变长(绿);球变大(白);炸弹球(红);<br>" +
                "穿刺球(粉);火箭球(橙);冰球;(蓝);<br>" +
                "球变小(紫);板变短(棕);死亡(黑)。<br>" +
                "<html/>");

        // 开始游戏按钮
        JButton optionButton1 = new JButton("开始游戏");
        optionButton1.setBackground(Color.white);
        optionButton1.addActionListener(event -> {
            Queue<GameInterface> gameInterfaces = new ArrayDeque<>();
            gameInterfaces.add(new GameInterface(speed, 0));
            assert gameInterfaces.peek() != null;
            gameInterfaces.peek().setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            // 检测闯关成功与否
            Timer timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    assert gameInterfaces.peek() != null;
                    if (gameInterfaces.peek().success && gameInterfaces.peek().continuePlay) {
                        gameInterfaces.peek().timer.stop();
                        assert gameInterfaces.peek() != null;
                        gameInterfaces.peek().remove(myPanel);
                        assert gameInterfaces.peek() != null;
                        gameInterfaces.peek().dispose();
                        gameInterfaces.poll();
                        speed++;
                        passNum++;
                        GameInterface.time = 0;
                        GameInterface.score = 0;
                        gameInterfaces.add(new GameInterface(speed, passNum));
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else if (gameInterfaces.peek().restartFlag) {
                        gameInterfaces.peek().timer.stop();
                        assert gameInterfaces.peek() != null;
                        gameInterfaces.peek().remove(myPanel);
                        assert gameInterfaces.peek() != null;
                        gameInterfaces.peek().dispose();
                        gameInterfaces.poll();
                        GameInterface.time = 0;
                        GameInterface.score = 0;
                        passNum = 0;
                        speed = 3;
                        timer.cancel();
                    }
                }
            }, 1000, 2000);
        });

        // 退出游戏按钮
        JButton optionButton2 = new JButton("退出游戏");
        optionButton2.setBackground(Color.LIGHT_GRAY);
        optionButton2.addActionListener(event -> System.exit(0));

        // 难度选择按钮
        JButton optionButton3 = new JButton("选择难度");
        optionButton3.setBackground(new Color(135, 206, 235));
        optionButton3.addActionListener(event -> {
            if (choose == 0) {
                optionButton3.setText("菜鸟");
                choose = 1;
                speed = 3;
            } else if (choose == 1) {
                optionButton3.setText("新手");
                choose = 2;
                speed = 4;
            } else if (choose == 2) {
                optionButton3.setText("专业");
                choose = 3;
                speed = 5;
            } else if (choose == 3) {
                optionButton3.setText("大师");
                choose = 0;
                speed = 6;
            }
        });

        // 查看规则按钮
        JButton optionButton4 = new JButton();
        optionButton4.setText("游戏规则");
        optionButton4.setBackground(new Color(84, 255, 159));
        optionButton4.addActionListener(event -> {
            add(textButton);
            textButton.setBounds(GAME_WIDTH / 2 - 300 / 2, GAME_HEIGHT / 4 + 180, 300, 250);
        });

        // 添加按钮
        add(optionButton1);
        add(optionButton2);
        add(optionButton3);
        add(optionButton4);
        optionButton1.setBounds(GAME_WIDTH / 2 - 100 / 2, GAME_HEIGHT / 4, 100, 45);
        optionButton2.setBounds(GAME_WIDTH / 2 - 100 / 2, GAME_HEIGHT / 4 + 45, 100, 45);
        optionButton3.setBounds(GAME_WIDTH / 2 - 100 / 2, GAME_HEIGHT / 4 + 90, 100, 45);
        optionButton4.setBounds(GAME_WIDTH / 2 - 100 / 2, GAME_HEIGHT / 4 + 135, 100, 45);

        // 添加面板
        add(myPanel);
    }
}

2.游戏界面:

/**
 * @author jzh
 * @date 2021/6/21
 * class 游戏界面类
 */

public class GameInterface extends JFrame {
    public MyPanel myPanel;
    private final JMenu timeMenu;
    private final JMenu scoreMenu;
    public static final int GAME_X = 200, GAME_Y = 60, GAME_WIDTH = 900, GAME_HEIGHT = 700;

    public static double time = 0;
    public static int score = 0;
    public int passNum;
    public Timer timer;
    public boolean success, overFlag, continuePlay, restartFlag;

    public GameInterface(int speed, int passNum) {
        success = overFlag = continuePlay = false;
        this.passNum = passNum;
        setTitle("DxBall");
        setVisible(true);
        setBounds(GAME_X, GAME_Y, GAME_WIDTH, GAME_HEIGHT);
        myPanel = new MyPanel(speed, passNum);
        add(myPanel);

        // 设置菜单栏
        var menuBar = new JMenuBar();
        this.setJMenuBar(menuBar);

        // 设置菜单选项
        var exitMenu = new JMenu("退出游戏");
        exitMenu.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                System.exit(0);
            }
        });

        scoreMenu = new JMenu("得分: " + score);
        scoreMenu.addActionListener(new TimeAndScoreActionListener());

        timeMenu = new JMenu("用时: " + time + "s");
        timeMenu.addActionListener(new TimeAndScoreActionListener());

        var colorMenu = new JMenu("改变游戏背景");
        colorMenu.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                Random r1 = new Random(), r2 = new Random(), r3 = new Random();
                int r, g, b;
                r = r1.nextInt(256);
                g = r2.nextInt(256);
                b = r3.nextInt(256);
                myPanel.setBackground(new Color(r, g, b));
                repaint();
            }
        });

        // 添加菜单选项
        menuBar.add(scoreMenu);
        menuBar.add(timeMenu);
        menuBar.add(exitMenu);
        menuBar.add(colorMenu);
    }

    // 监控游戏时间,得分情况,闯关情况
    private class TimeAndScoreActionListener implements ActionListener {
        public TimeAndScoreActionListener() {
            timer = new Timer(1000, this);
            timer.start();
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (!myPanel.overFlag) time += 0.5;
            if (!myPanel.overFlag) score = myPanel.bricksNum * 100 + myPanel.additionalScore;
            if (myPanel.overFlag) {
                overFlag = true;
                if (myPanel.success) success = true;
                if (myPanel.continuePlay) continuePlay = true;
                if (myPanel.restartFlag) restartFlag = true;
            }
            timeMenu.setText("用时: " + time + "s");
            scoreMenu.setText("得分: " + score);
        }
    }
}

3.游戏组件

球类:

import java.awt.*;

/**
 * @author jzh
 * @date 2021/6/21
 * class 球类
 */

public class Ball {
    public int x, y;
    public int r;
    public boolean hd, vd;
    public Color color;
    public int type;

    public Ball(int x, int y, int r, Color c) {
        this.x = x;
        this.y = y;
        this.r = r;
        this.color = c;
        hd = vd = false;
    }
}

砖块类:

/**
 * @author jzh
 * @date 2021/6/21
 * class 砖块类
 */

public class Brick {
    public int x, y;
    public int width, height;
    public int type;
    public boolean exist;
    public boolean propFall;


    public Brick(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        this.exist = true;
        this.propFall = false;
    }
}

板类:

/**
 * @author jzh
 * @date 2021/6/21
 * class 板类
 */

public class Plank {
    public int x, y;
    public int width, height;

    public Plank(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }
}

道具类:

/**
 * @author jzh
 * @date 2021/6/21
 * class 道具类
 */

public class Prop {
    public int x, y;
    public int width, height;
    public int type;
    public boolean exist;

    public Prop(int x, int y, int width, int height, int type) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        this.type = type;
        this.exist = true;
    }
}

4.游戏逻辑:

(包含在游戏面板类中,这里只展示关键代码)

在timer监控下固定时间执行repaint(),即调用paint(),这里可能会遇到小球闪烁问题,可以使用双缓冲来解决:

    // 双缓冲解决小球闪烁问题
 Image offScreenImage = this.createImage(GAME_WIDTH, GAME_HEIGHT);

 public void update(Graphics g) {
     Graphics gOffScreen = offScreenImage.getGraphics();
     gOffScreen.fillRect(GAME_X, GAME_Y, GAME_WIDTH, GAME_HEIGHT);
     paint(gOffScreen);
 }
}

以下是paint函数内部的执行顺序:

super.paint(g);

判断玩家是否移动砖块,移动则开始游戏:

 if (!startFlag) {
     if (plank.x != PLANK_X) {
         startFlag = true;
         ball.y -= speed;
     }
 }

检测砖块和道具情况:

for (Brick brick : bricks) {
    if (brick.exist
            && ball.x >= brick.x && ball.x <= brick.x + brick.width
            && ball.y >= brick.y - ball.r && ball.y <= brick.y + brick.height + ball.r) {
        brick.exist = false;
        bricksNum++;
        if (ball.type != 4) ball.vd = !ball.vd;
        if (ball.type == 3) {
            boom_x = brick.x;
            boom_y = brick.y;
        }
        if (brick.type != 0) {
            brick.propFall = true;
        }
        break;
    }
}

判断是否与板碰撞,若碰撞则改变方向

if (ball.x >= plank.x && ball.x <= plank.x + plank.width
        && ball.y >= plank.y - ball.r && ball.y <= plank.y) {
    ball.vd = !ball.vd;
}

判断板是否没接到小球

if (ball.y >= GAME_HEIGHT - GAME_Y - ball.r) {
  	overFlag = true;
}

判断是否打完所有砖块

if (bricksNum == bricks.length) {
    overFlag = true;
}

若碰壁则改变方向,否则小球正常运动

 ball.x = ball.hd ? ball.x - speed : ball.x + speed;
 if (ball.x <= ball.r || ball.x >= GAME_WIDTH - ball.r * 2) ball.hd = !ball.hd;
 ball.y = ball.vd ? ball.y + speed : ball.y - speed;
 if (ball.y <= ball.r || ball.y >= GAME_HEIGHT - ball.r) ball.vd = !ball.vd;

绘制结束界面

if (overFlag) {
    var overMessage1 = "成功";
    var overMessage2 = "失败";
    var finalScore = "得分: " + GameInterface.score;
    var finalTime = "用时: " + GameInterface.time + "s";
    var title = "等级: ";
    var pass = "过关数: ";
    if (originSpeed == 3) {
        if (GameInterface.time <= 20 && GameInterface.score >= 3500) title += "新手";
        else title += "菜鸟";
    } else if (originSpeed == 4) {
        if (GameInterface.time <= 20 && GameInterface.score >= 3500) title += "专业";
        else title += "新手";
    } else if (originSpeed == 5) {
        if (GameInterface.score <= 2000) title += "新手";
        else if (GameInterface.time > 30) title += "大师";
        else title += "专业";
    } else {
        if (GameInterface.score <= 1000) title += "新手";
        else if (GameInterface.score <= 3000) title += "专业";
        else if (GameInterface.time > 15) title += "大师";
        else title += "专业";
    }
	 var f1 = new Font("宋体", Font.BOLD, 90);
	 var f2 = new Font("宋体", Font.PLAIN, 40);
	 g.setFont(f1);
	 g.setColor(new Color(139, 28, 98));
	 if (GameInterface.score >= 2000) {
	     success = true;
	     g.drawString(overMessage1, GAME_WIDTH / 3, GAME_HEIGHT / 4);
	 } else {
	     success = false;
	     g.drawString(overMessage2, GAME_WIDTH / 3, GAME_HEIGHT / 4);
	 }
	 g.setFont(f2);
	 g.drawString(finalScore, GAME_WIDTH / 3, GAME_HEIGHT / 4 + 90);
	 g.drawString(finalTime, GAME_WIDTH / 3, GAME_HEIGHT / 4 + 140);
	 g.drawString(title, GAME_WIDTH / 3, GAME_HEIGHT / 4 + 190);
	 if (success) pass += passNum + 1;
	 else pass += passNum;
	 g.drawString(pass, GAME_WIDTH / 3, GAME_HEIGHT / 4 + 240);

	 JButton continueButton = new JButton("继续闯关");
	 continueButton.setBackground(Color.GREEN);
	 continueButton.setBounds(GAME_WIDTH / 3, GAME_HEIGHT / 4 + 270, 150, 50);
	 continueButton.addActionListener(event -> continuePlay = true);
	
	 JButton overButton = new JButton();
	 overButton.setBackground(new Color(169, 169, 169));
	 overButton.setBounds(GAME_WIDTH / 3, GAME_HEIGHT / 4 + 270, 150, 50);
	 overButton.addActionListener(event -> System.exit(0));
	
	 JButton restartButton = new JButton("返回主菜单");
	 restartButton.setBackground(new Color(169, 169, 169));
	 restartButton.setBounds(GAME_WIDTH / 3, GAME_HEIGHT / 4 + 320, 150, 50);
	 restartButton.addActionListener(event-> restartFlag = true);
	 add(restartButton);

	if (!addFlag) {
	    if (originSpeed < 6 && success) add(continueButton);
	    else {
	        overButton.setText("退出游戏");
	        add(overButton);
	    }
	    addFlag = true;
	}
}

如果游戏未结束则绘制图形

if (!overFlag) {
      g.setColor(ball.color);
      g.fillOval(ball.x, ball.y, ball.r, ball.r);
      g.setColor(new Color(192, 255, 62));
      g.fillRect(plank.x, plank.y, plank.width, plank.height);
      g.setColor(new Color(192, 255, 62));
      for (Brick brick : bricks) {
          if (brick.exist) {
              if (ball.type != 3) {
                  g.fillRect(brick.x, brick.y, brick.width, brick.height);
              } else {
                  if (brick.x >= boom_x - 2 * brick.width && brick.x <= boom_x + 3 * brick.width &&
                          brick.y >= boom_y - 2 * brick.height && brick.y <= boom_y + 3 * brick.height) {
                      brick.exist = false;
                  } else {
                      g.fillRect(brick.x, brick.y, brick.width, brick.height);
                  }
              }
          }
          if (brick.propFall) {
              int propType = brick.type;
              Prop prop = new Prop(brick.x, brick.y, brick.width, brick.height, propType);
              if (prop.type == 1 || prop.type == 2) g.setColor(Color.GREEN);
              else if (prop.type == 3) g.setColor(Color.RED);
              else if (prop.type == 4) g.setColor(Color.cyan);
              else if (prop.type == 5) g.setColor(Color.ORANGE);
              else if (prop.type == 6) g.setColor(Color.blue);
              else if (prop.type == 7 || prop.type == 8 || prop.type == 9) g.setColor(Color.BLACK);
              g.fillOval(prop.x, prop.y, prop.width, prop.height);
              props.add(prop);
              brick.propFall = false;
          }
      }

      // 绘制掉落道具
      for (Prop p : props) {
          p.y += originSpeed - 1;
          if (p.type == 1) g.setColor(Color.GREEN);
          else if (p.type == 2) g.setColor(Color.white);
          else if (p.type == 3) g.setColor(Color.RED);
          else if (p.type == 4) g.setColor(new Color(255, 110, 180));
          else if (p.type == 5) g.setColor(Color.ORANGE);
          else if (p.type == 6) g.setColor(Color.blue);
          else if (p.type == 7) g.setColor(new Color(155, 48, 255));
          else if (p.type == 8) g.setColor(new Color(139, 69, 19));
          else if (p.type == 9) g.setColor(Color.BLACK);
          g.fillOval(p.x, p.y, p.width, p.height);
          // 如果板接收到道具
          if (p.y >= plank.y && p.y <= plank.y + 20 && p.x >= plank.x && p.x <= plank.x + 70) {
              int propType = p.type;
              additionalScore += 500;
              if (propType == 1) {
                  // 板变长
                  plank.width += 15;
                  p.exist = false;
              } else if (propType == 2) {
                  // 球变大
                  ball.r += 5;
                  p.exist = false;
              } else if (propType == 3) {
                  // 炸弹球
                  ball.color = Color.BLACK;
                  ball.type = 3;
                  p.exist = false;
              } else if (propType == 4) {
                  // 穿刺球
                  ball.color = Color.MAGENTA;
                  ball.type = 4;
                  p.exist = false;
              } else if (propType == 5) {
                  // 火箭球
                  ball.color = Color.orange;
                  speed++;
                  p.exist = false;
              } else if (propType == 6) {
                  // 冰球
                  ball.color = Color.blue;
                  if (speed >= 3) speed--;
                  p.exist = false;
              } else if (propType == 7) {
                  // 球变小
                  if (ball.r > 8) ball.r -= 2;
                  p.exist = false;
              } else if (propType == 8) {
                  // 板变短
                  if (plank.width > 30) plank.width -= 20;
                  p.exist = false;
              } else if (propType == 9) {
                  // 死亡
                  overFlag = true;
                  p.exist = false;
              }
          } else if (p.y > GAME_HEIGHT) p.exist = false;
      }
      props.removeIf(p -> !p.exist);
  }
}

打包为exe执行文件

可以看这个博主的教程,很详细:制作exe应用程序

这是我的步骤总结:

1.打Jar包,就是将out文件输出为jar,其中Jar中的MANIFEST.MF指明了一些配置信息。

2.使用exe4j对Jar包进行处理并生成exe文件。注意在配置jre环境时选择的是directory,然后写 /jre,表明使用与exe文件同一目录下的jre环境,注意这个jre必须与java写的代码使用的jre相同。

3.将exe文件和jre文件放在同一目录中即可。

在这里插入图片描述
4.这里注意java11以后安装的JDK中不含jre,需要自己生成:

使用管理员模式打开cmd,输入一下语句即可生成jre:

在这里插入图片描述
5.生成exe后注意将.exe4j文件保存,方便在后续进行版本更新时轻松地生成新的exe。

游戏展示

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

在这里插入图片描述

总结

这次第一次一个人手敲代码肝完一个项目,也是自己写的第一个游戏,意义非凡,在不断地修改与完善中,在解决问题的过程中,感受到编程能力的提升,在最终看到精美的游戏界面时,内心的成就感油然而生!

以上是关于使用Java制作小游戏:DxBall(打砖块)的主要内容,如果未能解决你的问题,请参考以下文章

Unity项目 - 打砖块游戏

Unity3D OpenVR 虚拟现实 保龄球打砖块游戏开发

Unity3D OpenVR 虚拟现实 保龄球打砖块游戏开发

Unity3D OpenVR 虚拟现实 保龄球打砖块游戏开发

Python游戏开发,pygame模块,Python实现打砖块小游戏

打砖块c语言源代码,打砖块游戏的源代码(请多指教)