最近学了一些 Java 的图形界面编程,因此想自己写一个小游戏试试身手,思来想去,弹球小游戏还是挺简单的,一个球拍,一个小球,使用球拍接住小球,保证小球不掉下去。在网上找了一些参考资料,感觉设计的都不怎么“面向对象”,于是自己也写了一个——一颗小球的畅想,对原来的游戏做了一些功能的增强。目前的功能还有许多不足的地方,因此暂定为 v1.0 版本。
游戏运行效果图
游戏开始界面
游戏结束界面
image.png
功能需求分析
弹球游戏需要以下功能:
(1)游戏界面展示:游戏界面具有两个球拍,界面上方一个,界面下方一个,同时界面中央有一个红色的小球。
(2)移动小球:游戏开始时小球能够按照一定的速度朝某一个方向进行移动,当小球遇到障碍物,能够自动的反弹。
(3)移动球拍:通过点击键盘左键和键盘右键可以实现两个球拍的左右移动。
面向对象程序设计
通过以上的分析,我将这个程序的实体类进行了提取,并划分为不同的模块。
image.png
- Constant 类:用于存放游戏中的常量数据
- Ball 类:球类
- GameView 类:游戏视图类
- Racket 类:球拍类
- Rule 类:游戏规则类
- GameApplication 类:游戏启动类
代码实现
「Constant 类的设计与实现」
首先看一下 Constant 类,其包含以下信息,这些信息是后来添加的,但为了方便后续的代码查看,先放出来。
public class Constant {
// 桌面宽高
public static final int TABLE_WIDTH = 400;
public static final int TABLE_HEIGHT = 600;
// 球的大小
public static final int BALL_SIZE = 16;
// 球的颜色
public static final Color BALL_COLOR = Color.RED;
// 球的速度
public static final int speedX = 5;
public static final int speedY = 10;
// 球拍长宽
public static final int racketWidth = 60;
public static final int racketHeight = 20;
// 球拍颜色
public static final Color RACKET_COLOR = Color.LIGHT_GRAY;
// 球拍每次移动距离
public static final int moveLength = 10;
}
「球类 Ball 的设计与实现」
球类需要包含以下属性:球的大小、球的颜色、球的位置坐标、球的 X 轴 Y 轴速度大小。
// 大小
private int ballSize = Constant.BALL_SIZE;
// 颜色
private Color color = Constant.BALL_COLOR;
// 坐标位置
private int ballX;
private int ballY;
// 速度大小
private int speedX = Constant.speedX;
private int speedY = Constant.speedY;
在构造小球时,需要传入两个参数,ballX 和 ballY 来确定小球的位置。
public Ball(int ballX, int ballY) {
this.ballX = ballX;
this.ballY = ballY;
}
小球移动,需要根据速度 speedX 和 speedY 来改变小球的位置,当小球碰到左右两边墙或者上下两个球拍时,其速度方向需要发生改变。这里为了确定球拍的位置,需要传入一个参数 racketX 代表球拍所在 X 轴的位置。
/**
* 小球移动
* @param racketX :球拍位置,目前游戏中上球拍和下球拍racketX位置相同
*/
public void moveBall(int racketX) {
// 小球速度方向改变
if (ballX <= 0 || ballX >= Constant.TABLE_WIDTH - ballSize) { // X轴方向速度
speedX = -speedX;
}
if ((ballY <= Constant.racketHeight || ballY >= Constant.TABLE_HEIGHT - Constant.racketHeight - ballSize)
&& ballX > racketX - ballSize && ballX < racketX + Constant.racketWidth) { // Y轴方向速度
speedY = -speedY;
}
ballX += speedX;
ballY += speedY;
}
「球拍类 Racket 的设计与实现」
球拍类需要包含的属性有:球拍长宽、球拍颜色、球拍位置。
// 长宽
private int racketWidth = Constant.racketWidth;
private int racketHeight = Constant.racketHeight;
// 颜色
private Color color = Constant.RACKET_COLOR;
// 位置
private int racketX;
private int racketY;
在构造球拍类时,需要传入 racketX 和 racketY 来确定球拍的位置。
public Racket(int racketX, int racketY) {
this.racketX = racketX;
this.racketY = racketY;
}
球拍需要进行移动,通过 moveLength 参数告知球拍移动的距离。
/**
* 左移动球拍
* @param moveLength 移动长度
*/
public void moveLeft(int moveLength) {
if (racketX > 0) {
racketX -= moveLength;
}
}
/**
* 右移动球拍
* @param moveLength 移动长度
*/
public void moveRight(int moveLength) {
if (racketX < Constant.TABLE_WIDTH - racketWidth) {
racketX += moveLength;
}
}
「游戏规则类 Rule 的设计与实现」
游戏规则类用于进行游戏结束的判断、游戏功能的设置。对于游戏结束的判断,需要传入两个参数,ball 和 racketX,ball 代表球类,racketX 代表球拍 X 轴的位置。
/**
* 游戏是否结束
* @param ball 球类
* @param racketX 球拍位置,目前游戏中上球拍和下球拍racketX位置相同
*/
public boolean isOver(Ball ball, int racketX) {
// 游戏结束
if ((ball.getBallY() <= Constant.racketHeight || ball.getBallY() >= Constant.TABLE_HEIGHT - Constant.racketHeight - ball.getBallSize())
&& (ball.getBallX() <= racketX - ball.getBallSize() || ball.getBallX() >= racketX + Constant.racketWidth)) {
return true;
}
return false;
}
「游戏视图类 GameView 的设计与实现」
GameView 需要继承 Canvas 类来实现游戏视图绘制的功能。游戏视图类包含以下属性,分别是视图桌面宽高、游戏规则、球、上球拍、下球拍。同时需要为视图类声明一个定时器,使其能够定时地对视图进行重绘。
// 桌面宽高
private final int TABLE_WIDTH = Constant.TABLE_WIDTH;
private final int TABLE_HEIGHT = Constant.TABLE_HEIGHT;
// 游戏规则
private Rule rule;
// 球
private Ball ball;
// 上球拍
private Racket racketTop;
// 下球拍
private Racket racketBottom;
// 声明定时器
private Timer timer;
在游戏视图类的构造方法中对游戏的各个参数进行初始化。
public GameView(Ball ball, Racket racketTop, Racket racketBotton) {
// 设置桌面
this.setPreferredSize(new Dimension(TABLE_WIDTH, TABLE_HEIGHT));
this.rule = new Rule();
this.ball = ball;
this.racketTop = racketTop;
this.racketBottom = racketBotton;
// 定时刷新任务
timer = new Timer(100, event -> {
// 小球移动
ball.moveBall(racketBotton.getRacketX());
this.repaint();
});
timer.start();
}
为了实现我们需要的绘制,我们需要重写 paint 方法。这里通过规则类 Rule 的 isOver 方法来判断游戏是否结束。如果游戏没有结束,调用 paintGame 方法,绘制游戏界面。如果游戏结束,需要调用 paintGameOver 方法绘制游戏结束界面,同时停止定时器的运行。
@Override
public void paint(Graphics g) {
if (!rule.isOver(ball, racketBottom.getRacketX())) { // 游戏进行
paintGame(g);
} else { // 游戏结束
paintGameOver(g);
timer.stop();
}
}
paintGame 方法如下,其核心是对小球、球拍的重绘:
/**
* 绘制游戏
*/
private void paintGame(Graphics g) {
// 绘制小球
g.setColor(ball.getColor());
g.fillOval(ball.getBallX(), ball.getBallY(), ball.getBallSize(), ball.getBallSize());
// 绘制上球拍
g.setColor(racketTop.getColor());
g.fillRect(racketTop.getRacketX(), racketTop.getRacketY(), racketTop.getRacketWidth(), racketTop.getRacketHeight());
// 绘制下球拍
g.setColor(racketBottom.getColor());
g.fillRect(racketBottom.getRacketX(), racketBottom.getRacketY(), racketBottom.getRacketWidth(), racketBottom.getRacketHeight());
}
paintGameOver 方法如下,绘制游戏结束界面:
/**
* 绘制游戏结束
*/
private void paintGameOver(Graphics g) {
g.setColor(Color.BLUE);
g.setFont(new Font("隶书", Font.BOLD, 30));
g.drawString("游戏结束", 130, 300);
}
「游戏启动类的设计与实现」
游戏启动类需要定义以下属性:
Frame frame = new Frame("一颗小球的畅想");
private Ball ball;
private Racket racketTop;
private Racket racketBottom;
private GameView gameView;
// 球拍每次点击移动距离
private int moveLength = Constant.moveLength;
在 init 方法中对这些参数进行初始化,这里对游戏视图类进行了构造,同时构建了两个监听,一个是对游戏关闭的监听,还有一个是对键盘左右移动按键事件的监听,来进行球拍的移动。
private void init() {
ball = new Ball(192, 300);
racketTop = new Racket(170, 0);
racketBottom = new Racket(170, 580);
gameView = new GameView(ball, racketTop, racketBottom);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) { // 关闭窗口
System.exit(0);
}
});
// 操作键盘
KeyListener listener = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_LEFT) { // 按左键,球拍左移动
racketTop.moveLeft(moveLength);
racketBottom.moveLeft(moveLength);
} else if (keyCode == KeyEvent.VK_RIGHT) { // 按右键,球拍右移动
racketTop.moveRight(moveLength);
racketBottom.moveRight(moveLength);
}
}
};
frame.addKeyListener(listener);
frame.add(gameView);
frame.pack();
frame.setVisible(true);
}
总结
游戏的设计与实现到此也就基本完成了,但是游戏的功能其实还有许多需要完善的地方,例如游戏的开始、暂停等操作;球的初始速度、方向不是随机的,导致球的运行基本是可预测的;两个球拍的移动受同一个按键控制,这就导致球拍的左右移动是同步的,后续可以对球拍的控制进行分离,这就可以成为一个双人游戏;游戏没有计分的规则;球的速度、球拍的长度不能随着游戏的进度发生变化;游戏中没有随机事件:例如通过小球来吃其他具有特殊功能的东西等。以上这些都是当前版本中所不具有的,因此需要在后续的版本中进行不断的完善。
欢迎关注
技术号:小猿君的算法笔记
如果需要获得完整的代码,也可以关注我的技术号 小猿君的算法笔记,发送 一颗小球的畅想
获取,让我们一起学习,一起成长。
网友评论