使用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(打砖块)的主要内容,如果未能解决你的问题,请参考以下文章
Unity3D OpenVR 虚拟现实 保龄球打砖块游戏开发
Unity3D OpenVR 虚拟现实 保龄球打砖块游戏开发
Unity3D OpenVR 虚拟现实 保龄球打砖块游戏开发