目录
七、代码静态扫描:(未成功修改,有的看不太懂),其余代码基本符合规范
一、团队成员介绍
姓名 | 学号 | 任务分配及实现 |
包佳莉(组长) | 202221336069 | 球(Ball)我的球(MyBall),其他球(OtherBall),方向接口(Direction),小组博客编写 |
张子翔(组员) | 202221336080 | 多线程实现球类的移动和倒计时(Frame2),选择难度和代码的整合(SelectFrame) |
胡凌瑞(组员) | 202221336090 | 背景音乐的添加(Music),登陆界面(GameLogin),重开/退出(Again) |
二、前期调查:
借鉴了一些别人的代码:Java多线程-球球小游戏 · 3053a03 · hello world007/球球小游戏 - Gitee.com
三、项目功能架构图与主要功能流程图
四、包名类名
五、项目运行截图
1、登陆注册
2、选择难度
3、游戏界面
游戏胜利:
游戏失败:
(点击是,则开始上一次选择的难度的游戏)
(点击否,则重新选择游戏难度)
六、项目代码
Ball类:实现对球的移动,吃球,绘画球
public void move(int m) {
// 根据随机生成的方向值来移动球
switch (this.direction) {
//随机方向
case 1 -> x -= m;
case 2 -> x += m;
case 3 -> y -= m;
case 4 -> y += m;
case 5 -> {
x -= m;
y -= m;
}
case 6 -> {
x -= m;
y += m;
}
case 7 -> {
x += m;
y -= m;
}
case 8 -> {
x += m;
y += m;
}
default -> {
}
}
}
public void eat(Ball b1, int i) {
// eat方法,用于处理球吃掉其他球的逻辑
int x1 = b1.getX();
int y1 = b1.getY();
int d1 = b1.getD();
// 如果被吃球的直径小于当前球的直径
if (d1 < this.d) {
// 计算两球中心点的距离
// 小球被吃掉
int d = (int) Math.sqrt((x1 + d1 / 2 - this.getX() - this.getD() / 2) * (x1 + d1 / 2 - this.getX() - this.getD() / 2) + (y1 + d1 / 2 - this.getY() - this.getD() / 2) * (y1 + d1 / 2 - this.getY() - this.getD() / 2));
// 如果距离小于两球半径之和,表示可以吃掉
if (d < (d1 + this.getD()) / 2) {
// 增加当前球的直径,并将被吃球移出界面
this.setD(this.d + d1 / i);
b1.setX(-100);
b1.setY(-100);
b1.setD(0);
b1 = null;
}
// b1 = null;
} else if (d1 > this.getD()) {
// 如果被吃球的直径大于当前球的直径
int d = (int) Math.sqrt((x1 + d1 / 2 - this.getX() - this.getD() / 2) * (x1 + d1 / 2 - this.getX() - this.getD() / 2) + (y1 + d1 / 2 - this.getY() - this.getD() / 2) * (y1 + d1 / 2 - this.getY() - this.getD() / 2));
// 如果距离小于两球半径之和,表示当前球被吃掉
if (d < (d1 + this.getD()) / 2) {
// 小球应该被吃掉
// 将当前球移出界面
this.setX(-100);
this.setY(-100);
this.setD(0);
}
}
}
public void draw(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
// 设置剪辑区域为圆形
g2d.setClip(new Ellipse2D.Float(this.x - this.d / 2, this.y - this.d / 2, this.d, this.d));
// 在剪辑区域内绘制图片
g2d.drawImage(this.image, this.x - this.d / 2, this.y - this.d / 2, this.d, this.d, null);
g2d.dispose();
}
}
SelectFrame类:添加游戏难度选择界面,球的移动数值初始化,背景音乐播放
Music audioPlayWave = new Music("E:\\ideal\\BallGamebjl\\src\\BalleatBall\\6.wav");
JButton bt1 = new JButton();
JButton bt2 = new JButton();
JButton bt3 = new JButton();
JButton bt4 = new JButton();
public SelectFrame() {
audioPlayWave.start();
JPanel panel = new JPanel();
this.setContentPane(panel);
this.setTitle("选择难度");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(400, 400);
this.setVisible(false);
// 初始时隐藏选择难度的窗口
bt1.setText("初级");
bt2.setText("中级");
bt3.setText("高级");
bt4.setText("终极");
bt1.setActionCommand("bt1");
bt2.setActionCommand("bt2");
bt3.setActionCommand("bt3");
bt4.setActionCommand("bt4");
bt1.addActionListener(this);
bt2.addActionListener(this);
bt3.addActionListener(this);
bt4.addActionListener(this);
panel.add(bt1);
panel.add(bt2);
panel.add(bt3);
panel.add(bt4);
panel.setLayout(new GridLayout(4, 1));
}
/**
* 球的移动
*/
@Override
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("bt1")) {
dispose();
audioPlayWave.stop();
this.setVisible(false);
Frame2 frame2 = new Frame2(2, 2, 1, 10);
} else if (e.getActionCommand().equals("bt2")) {
dispose();
audioPlayWave.stop();
this.setVisible(false);
Frame2 frame2 = new Frame2(4, 2, 1, 10);
} else if (e.getActionCommand().equals("bt3")) {
dispose();
audioPlayWave.stop();
this.setVisible(false);
Frame2 frame2 = new Frame2(6, 2, 1, 10);
} else if (e.getActionCommand().equals("bt4")) {
dispose();
audioPlayWave.stop();
this.setVisible(false);
Frame2 frame2 = new Frame2(6, 2, 1, 8);
}
}
Frame2类:创建MyBall和OtherBall,多线程实现小球的移动,设置键盘监听器实现上下左右移动,设置倒计时增加游戏难度
OtherBall ob = new OtherBall();
/**
* 创建一个MyBall对象,表示玩家控制的球
*/
MyBall mb = new MyBall(10, 300, 25, Color.pink, "E:\\ideal\\BallGamebjl\\src\\BalleatBall\\微信图片_20240111185319.jpg");
/**
* 创建一个JButton对象,用于重新开始游戏
* JButton btn = new JButton("重新开始");
* 创建一个标志,用于控制游戏的暂停和恢复
*/
private boolean isAlive = true;
Music audioPlayWave = new Music("E:\\ideal\\BallGamebjl\\src\\BalleatBall\\游戏开始音乐.wav");
private GamePanel gamePanel;
/**
* 构造函数,初始化游戏窗口
*/
public Frame2() {
}
public Frame2(int i, int k, int m, int j) {
audioPlayWave.start();
@SuppressWarnings("unused")
int musicOpenLab = 1;
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setFocusable(true);
setSize(new Dimension(1000, 700));
setLocationRelativeTo(null);
setResizable(false);
setTitle("大球吃小球");
getContentPane().setBackground(Color.black);
this.setVisible(true);
// Initialize the timer label
timerLabel = new JLabel("Time left: 01:00", SwingConstants.CENTER);
timerLabel.setForeground(Color.WHITE);
timerLabel.setFont(new Font("Arial", Font.BOLD, 20));
getContentPane().add(timerLabel, BorderLayout.NORTH);
// Initialize the timer
timer = new Timer(1000, e -> {
timeLeft--;
int minutes = timeLeft / 60;
int seconds = timeLeft % 60;
timerLabel.setText(String.format("Time left: %02d:%02d", minutes, seconds));
if (timeLeft <= 0) {
timer.stop();
isAlive = false;
JOptionPane.showMessageDialog(Frame2.this, "时间到,游戏失败!", "游戏结束", JOptionPane.ERROR_MESSAGE);
// showContinueGameDialog(i, k, m, j);
}
});
timer.start();
gamePanel = new GamePanel();
add(gamePanel, BorderLayout.CENTER);
this.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
// 根据按键改变玩家控制的球的移动方向
Direction dir = mb.getDirection();
switch (e.getKeyCode()) {
case KeyEvent.VK_A:
dir = Direction.LEFT;
System.out.println("左键");
break;
case KeyEvent.VK_W:
dir = Direction.UP;
System.out.println("上键");
break;
case KeyEvent.VK_S:
dir = Direction.DOWN;
System.out.println("下键");
break;
case KeyEvent.VK_D:
dir = Direction.RIGHT;
System.out.println("右键");
break;
case KeyEvent.VK_LEFT:
dir = Direction.LEFT;
System.out.println("左键");
break;
case KeyEvent.VK_UP:
dir = Direction.UP;
System.out.println("上键");
break;
case KeyEvent.VK_DOWN:
dir = Direction.DOWN;
System.out.println("下键");
break;
case KeyEvent.VK_RIGHT:
dir = Direction.RIGHT;
System.out.println("右键");
break;
case KeyEvent.VK_SPACE:
isAlive = !isAlive;
requestFocus();
break;
default:
break;
}
if (dir != mb.getDirection()) {
mb.changeDir(dir);
}
}
});
// 启动一个新的线程,用于控制游戏的主要逻辑, 匿名内部类和线程池
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
if (isAlive) {
// 判断是否吃球,球的移动,以及游戏结束后的处理
ob.isEat(mb, i);
ob.isAlive();
gamePanel.repaint();
if (mb.getDiameter() != 0 && mb.getX() > 0 && mb.getX() < 1000 && mb.getY() > 0 && mb.getY() < 1000) {
//判断小球球在游戏界面内
ob.move(m);
mb.move(k);
repaint();
//请求重新绘制组件
} else {
// 停止计时器
timer.stop();
isAlive = false;
audioPlayWave.stop();
// showContinueGameDialog(i, k, m, j);
int choice = JOptionPane.showConfirmDialog(Frame2.this, "游戏失败,是否重新开始游戏?", "游戏结束", JOptionPane.YES_NO_OPTION);
if (choice == JOptionPane.YES_OPTION) {
// 创建新的 Frame2 对象来重新开始游戏
Frame2 newGame = new Frame2(i, k, m, j);
Frame2.this.dispose();
} else {
// 关闭当前窗口
Frame2.this.dispose();
// 创建一个新的选择难度的窗口
SelectFrame frame = new SelectFrame();
frame.setLocationRelativeTo(null); // 将窗口居中显示(可选)
frame.setVisible(true);
}
}
try {
Thread.sleep(j);//线程刷新,
//控制刷新率在游戏中非常重要,因为它影响到游戏的响应速度和流畅度。
// 如果刷新率设置得过高,可能会导致屏幕闪烁或游戏运行不稳定;如果设置得太低,则可能会影响游戏的响应性和视觉效果。
// 因此,根据游戏的需求和性能要求,合理设置线程的刷新率是必要的。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
setVisible(true);
}
Again类:实现游戏重新开始后的选择
public Again() {
audioPlayWave.start();
@SuppressWarnings("unused")
int musicOpenLab = 1;
// 设置窗口关闭时的操作
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 设置窗口为可见
this.setVisible(true);
// 设置窗口的大小
this.setSize(200, 200);
// 设置按钮的动作命令,这些命令将在动作事件中使用
bt1.setActionCommand("bt1");
bt2.setActionCommand("bt2");
// 为按钮添加动作监听器
bt1.addActionListener(this);
bt2.addActionListener(this);
// 创建一个JPanel对象,并将其设置为窗口的内容面板
JPanel panel = new JPanel();
this.setContentPane(panel);
// 将按钮添加到面板
panel.add(bt1);
panel.add(bt2);
// 设置面板的布局为网格布局,1行2列
panel.setLayout(new GridLayout(1, 2));
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("bt1")) {
// 关闭当前窗口
this.dispose();
// 如果点击的是"重新开始"按钮,那么创建一个新的选择难度的窗口
SelectFrame frame = new SelectFrame();
// 确保选择难度的窗口是可见的
frame.setVisible(true);
}
if (e.getActionCommand().equals("bt2")) {
// 如果点击的是"结束游戏"按钮,那么结束程序
System.exit(0);
}
}
七、代码静态扫描:(未成功修改,有的看不太懂),其余代码基本符合规范
八、尚待改进或者新的想法
1.球球的碰撞检测和吃球善待改进,有时候还没碰到大球就gameover了,此bug还没解决。
2.调整吞噬、成长等机制,确保游戏在不同阶段都有足够的挑战性。
3.增加玩家排名和排行榜,让玩家可以比较自己的分数和排名。
4.增加一个游戏内聊天功能,提高玩家互动感,
5.增加更多独特的关卡和模式,如团队战、生存模式等。