飞机大战是一个经典的小游戏,下面将介绍何使用Java Swing和GUI来创建一个完整的飞机大战游戏。本文游戏的实现参考了尚学堂的飞机大战游戏并进行一些改进,添加更多功能。

源码下载:Java Swing实现飞机大战小游戏.zip

一、简介

飞机大战小游戏使用的技术包括使用面向对象编程思想创建游戏元素,如敌机、敌方Boos、敌方子弹、我方飞机、子弹、道具等,并通过监听器来实现游戏的交互,以及如何在游戏中处理碰撞、控制游戏流程,以及实现一些基本的游戏逻辑。

本文将以代码为主,通过代码实现来详细阐述游戏的设计思路和实现过程。我们将会使用Java中的Swing库来实现GUI,并通过游戏元素之间的交互来控制游戏的进行,通过爆炸类的实现了解到如何实现简单的动画效果,如何在屏幕上绘制图像和文本等一些基本的Java游戏编程技巧。

二、思维导图类的关系图

下方图片是思维导图和本项目中各个类之间的关系图。游戏窗口类是程序的入口,GameObject类是游戏中所有实体类的父类,它包含了背景类、敌机类、我方飞机类、爆炸类、道具类类等多个子类。
思维导图:

三、游戏窗口类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

/**
* @author RationalDysaniaer
* @date 2023/4/20 23:10
* @version 1.3
* @BlogAddress <a href="https://blog.csdn.net/qq_43000128/article/details/129863206">【使用Java Swing创建飞机大战小游戏】</a>
*/

public class GameWin extends JFrame {
//窗口大小
public final int WIDTH = 600;
public final int HEIGHT = 800;
//游戏状态
public static int state = 0;
//在内存中创建一个offScreenImage的缓冲图像(双缓存)
Image offScreenImage = null;
//计数
int count = 1;//时间计数
int enemyCount = 0;//敌机数量
static int score = 0;//得分
static int level = 1;
int levelProps = 0;
int lifeProps = 0;
int enhanceBoos = 0;
int isBarrier1 = 0;
int isBarrier2 = 0;
int isBarrier3 = 0;
int invincible = 0;//无敌
int guard = 3;
int cls = 3;
//将背景图片和我方飞机绘制到刚才创建的这块缓冲区上

Background background = new Background(GameUtils.background, 0, -400, 2);
Background background2 = new Background(GameUtils.background2, 0, -1600, 2);
Plane planeObj = new Plane(GameUtils.red, 0, 0, 50, 50, 0, this);

//创建boos空对象
public Boos boos = null;

public void reGame(){
count = 1;
enemyCount = 0;
score = 0;
enhanceBoos = 0;
level = 1;
lifeProps = 0;
levelProps = 0;
boos = null;
this.planeObj.life = 30;
offScreenImage = null;
isBarrier1 = 0;
isBarrier2 = 0;
isBarrier3 = 0;
guard = 3;
cls = 3;
GameUtils.gameObjectList.clear();
GameUtils.removeList.clear();
GameUtils.enemyShellList.clear();
GameUtils.enemyObjList.clear();
GameUtils.shellObjList.clear();
GameUtils.explodeList.clear();
GameUtils.gameObjectList.add(background);
GameUtils.gameObjectList.add(background2);
GameUtils.gameObjectList.add(planeObj);
}

public static void main(String[] args) {
GameWin game = new GameWin();
game.launch();
}

@Override
public void paint(Graphics g){
if(offScreenImage == null){
offScreenImage = createImage(WIDTH,HEIGHT);
}
Graphics gImage = offScreenImage.getGraphics();
gImage.fillRect(0,0,WIDTH,HEIGHT);
//游戏状态
//开始界面
if(state == 0){
gImage.drawImage(GameUtils.background,0,-400,this);
gImage.drawImage(GameUtils.enemyPlane,20,240,this);
gImage.drawImage(GameUtils.enemyPlane,500,240,this);
gImage.drawImage(GameUtils.red,250,600,this);
gImage.drawImage(GameUtils.title,(600-475)/2-12,80,this);
GameUtils.drawWord(gImage,"<点击屏幕开始游戏>",Color.yellow,40,112,400);
}
//游戏运行
if(state == 1){
GameUtils.gameObjectList.addAll(GameUtils.explodeList);//????
for (int i = 0; i < GameUtils.gameObjectList.size(); i++) {
GameUtils.gameObjectList.get(i).paintSelf(gImage);
}
GameUtils.heartShow(gImage, planeObj.life,this);
GameUtils.gameObjectList.removeAll(GameUtils.removeList);
}
//游戏失败
if(state == 3){
for (int i = 0; i < GameUtils.gameObjectList.size(); i++) {
GameUtils.gameObjectList.get(i).paintSelf(gImage);
}
gImage.drawImage(GameUtils.ExplodeSmall,planeObj.getX()-15, planeObj.getY()-10,this);
GameUtils.heartShow(gImage, planeObj.life,this);
GameUtils.drawWord(gImage,"GAME OVER",Color.yellow,60,160,330);
GameUtils.drawWord(gImage,"*按空格键重新开始*",Color.yellow,60,20,400);
}
//游戏胜利
if(state == 4){
for (int i = 0; i < GameUtils.gameObjectList.size(); i++) {
GameUtils.gameObjectList.get(i).paintSelf(gImage);
}
gImage.drawImage(GameUtils.ExplodeBig, boos.getX()-10, boos.getY()-10,this);
GameUtils.heartShow(gImage, planeObj.life,this);
GameUtils.drawWord(gImage,"VICTORY!",Color.yellow,60,160,330);
GameUtils.drawWord(gImage,"*按空格键重新开始*",Color.yellow,60,20,400);
}


//BOOS警告和变异字幕
if(state == 1){
if(enemyCount > 10 && enemyCount<20){
GameUtils.drawWord(gImage,"============================================================",Color.red,100,0,300);
if(enemyCount%2 == 0)
GameUtils.drawWord(gImage," 警告!!!",Color.red,100,0,400);
GameUtils.drawWord(gImage,"============================================================",Color.red,100,0,500);
}

if(this.boos != null && this.boos.life <= 200 && enhanceBoos < 50){
GameUtils.drawWord(gImage,"============================================================",Color.red,100,0,300);
if(enemyCount%2 == 0){
GameUtils.drawWord(gImage," BOOS增强!!!",Color.red,100,0,400);
enhanceBoos ++;
}
GameUtils.drawWord(gImage,"============================================================",Color.red,100,0,500);
}

if(this.boos != null && this.boos.life <= 100 && enhanceBoos < 100){
GameUtils.drawWord(gImage,"============================================================",Color.red,100,0,300);
if(enemyCount%2 == 0){
GameUtils.drawWord(gImage," BOOS变异!!!",Color.red,100,0,400);
enhanceBoos ++;
}
GameUtils.drawWord(gImage,"============================================================",Color.red,100,0,500);
}
}

//得分、等级、技能次数
if(state != 0){
GameUtils.drawWord(gImage,score+" 分",Color.green,40,460,100);
GameUtils.drawWord(gImage,"Level:"+level,Color.green,30,455,775);
GameUtils.drawWord(gImage,"护盾(C):"+guard,Color.blue,20,330,785);
GameUtils.drawWord(gImage,"清屏(X):"+cls,Color.blue,20,330,760);
}
//将缓冲图像画入
g.drawImage(offScreenImage,0,0,null);
//时间计数
count++;
}

public void launch(){
Music music = new Music();
music.play();
this.setSize(WIDTH,HEIGHT);//窗口大小
this.setLocationRelativeTo(null);//窗口居中
this.setTitle("飞机大战");
this.setVisible(true);
this.setResizable(false);//不可改变大小
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);//关闭游戏结束程序

GameUtils.gameObjectList.add(background);
GameUtils.gameObjectList.add(background2);
GameUtils.gameObjectList.add(planeObj);

//鼠标监听
this.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if(e.getButton() == 1 && state == 0){
state = 1;//开始游戏
repaint();
}
}
});

//游戏暂停、重新开始
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == 32){
switch (state){
case 1:
state = 2;
break;
case 2:
state = 1;
break;
case 3:
case 4:
reGame();
state = 0;
repaint();
break;
}
}
if(e.getKeyCode() == 67){
if(guard > 0){
guard--;
invincible = 1;
Thread th = new Thread(){
@Override
public void run() {
super.run();
try {
Thread.sleep(5000);
System.out.println(66);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
invincible = 0;
}
};
th.start();
}
}
if(e.getKeyCode() == 88){
if(cls > 0){
GameUtils.gameObjectList.removeAll(GameUtils.enemyObjList);
GameUtils.gameObjectList.removeAll(GameUtils.enemyShellList);
cls--;
}
}
}
});

while (true){
if(state == 1){
createObject();
repaint();
}
try {
Thread.sleep(25);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//Thread.sleep()是Thread类的一个静态方法,使当前线程休眠,进入阻塞状态(暂停执行),如果线程在睡眠状态被中断,将会抛出InterruptedException中断异常
}
}

void createObject() {
//生成我方飞机子弹
int shellSpeedRed = 11 - level;
int speed = 5 + (level-1)*2;
if (count % shellSpeedRed == 0) {
GameUtils.shellObjList.add(new Shell(GameUtils.shell, planeObj.getX(), planeObj.getY() - 50, 30, 50, speed, this));
GameUtils.gameObjectList.add(GameUtils.shellObjList.get(GameUtils.shellObjList.size() - 1));
}

//生成敌方飞机
if (count % 15 == 0) {
GameUtils.enemyObjList.add(new Enemy(GameUtils.enemyPlane, (int) (Math.random() * 12) * 46, 10, 60, 36, (int) (Math.random() * (10 - 3 + 1) + 3), this));
GameUtils.gameObjectList.add(GameUtils.enemyObjList.get(GameUtils.enemyObjList.size() - 1));
enemyCount++;
}
//生成敌方boos子弹
int shellSpeed;
if (boos != null && this.boos.life >= 100)
shellSpeed = 5;
else
shellSpeed = 3;
if (count % shellSpeed == 0 && boos != null) {
//散弹
int z, speed2;
z = (int) (Math.random() * (3 - 1 + 1) + 1);
speed2 = (int) (Math.random() * (5 - 3 + 1) + 3);
//追踪弹
int x1 = boos.getX() + 76;
int y1 = boos.getY() + 85;
int x2 = planeObj.getX();
int y2 = planeObj.getY() - 50;
int k;
if (x2 != x1)
k = (y2 - y1) / (x2 - x1);
else k = 0;
int b = (int) (y1 - 1.0 * k * x1);
GameUtils.enemyShellList.add(new EnemyBullet(GameUtils.enemyShell, boos.getX() + 82, boos.getY() + 85, 5, 20, (int) (Math.random() * (15 - 5 + 1) + 5), z, speed2, k, b, this));
GameUtils.gameObjectList.add(GameUtils.enemyShellList.get(GameUtils.enemyShellList.size() - 1));
}
//出现二十架敌机后出现boos
if (enemyCount > 20 && boos == null) {
boos = new Boos(GameUtils.boos, 250, 0, 170, 100, 5, this);
GameUtils.gameObjectList.add(boos);
}

//生成升级道具
if (boos != null
&& boos.life <= 300 - levelProps*50
&& levelProps < 5){
levelProps++;
GameUtils.gameObjectList.add(new Props(GameUtils.level, "level", (int) (Math.random() * 12) * 46, 10, 35, 50, 2, this));
}

//生成生命道具
if (score > 100 + lifeProps * 100) {
lifeProps++;
GameUtils.gameObjectList.add(new Props(GameUtils.heart, "life", (int) (Math.random() * 12) * 46, 10, 50, 50, 2, this));
}

//生成障碍
if(score > 0 && isBarrier1 == 0){
isBarrier1 = 1;
GameUtils.gameObjectList.add(new barrier(50,200,5,15,50,50,this));
GameUtils.gameObjectList.add(new barrier(350,200,5,15,50,50,this));
}
if(boos!= null && boos.life < 100 && isBarrier1 == 1){
isBarrier1 = 2;
GameUtils.gameObjectList.add(new barrier(50,200,5,30,50,50,this));
GameUtils.gameObjectList.add(new barrier(350,200,5,30,50,50,this));
}
if(score > (isBarrier2+1)*100){
isBarrier2++;
GameUtils.gameObjectList.add(new barrier(200,250,5,10,50,50,this));
}
if(score > (isBarrier3+1)*140){
isBarrier3++;
GameUtils.gameObjectList.add(new barrier(50,150,5,5,50,50,this));
GameUtils.gameObjectList.add(new barrier(350,150,5,5,50,50,this));
GameUtils.gameObjectList.add(new barrier(50,250,5,5,50,50,this));
GameUtils.gameObjectList.add(new barrier(350,250,5,5,50,50,this));
}
}
}

该代码是飞机大战游戏窗口类GameWin,它继承自 JFrame 类,用于显示和进入游戏窗口。
代码中定义了一些变量用于游戏状态的管理、游戏对象的维护等,包括窗口大小、游戏状态、时间计数、敌机数量、得分、等级、boos、道具数量等。GameWin 类实现了paint方法用于绘制游戏窗口的内容,具体的绘制根据游戏状态的不同而不同,分别绘制开始界面、游戏运行界面、游戏失败界面和游戏胜利界面。
游戏画面的绘制使用双缓存解决了画面闪烁问题。
createObject方法用于游戏中敌机、我方子弹、Boos、敌方子弹、道具、障碍的生成,并控制Boos血量低于百分之六十追踪发射子弹,低于百分之三十进入暴走状态,发射的子弹数量翻倍。
此外,代码中还定义了 reGame 方法用于初始化游戏状态和对象等,实现了游戏胜利或失败后能重新开始的功能。
游戏加入鼠标监听和键盘监听,实现在开始界面单击鼠标左键开始游戏,游戏进行中单击空格键暂停,再次单击空格键继续游戏。

四、游戏父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import java.awt.*;
public abstract class GameObject {
Image img;
int x;
int y;
int width;
int height;
double speed;
GameWin frame;

public GameObject() {
}

public GameObject(int x, int y) {
this.x = x;
this.y = y;
}

public GameObject(Image img, int x, int y, double speed) {
this.img = img;
this.x = x;
this.y = y;
this.speed = speed;
}

public GameObject(Image img, int x, int y, int width, int height, double speed, GameWin frame) {
this.img = img;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.speed = speed;
this.frame = frame;
}

public Image getImj() {
return img;
}

public void setImj(Image imj) {
this.img = img;
}

public int getX() {
return x;
}

public void setX(int x) {
this.x = x;
}

public int getY() {
return y;
}

public void setY(int y) {
this.y = y;
}

public int getWidth() {
return width;
}

public void setWidth(int width) {
this.width = width;
}

public int getHeight() {
return height;
}

public void setHeight(int height) {
this.height = height;
}

public double getSpeed() {
return speed;
}

public void setSpeed(double speed) {
this.speed = speed;
}

public GameWin getFrame() {
return frame;
}

public void setFrame(GameWin frame) {
this.frame = frame;
}

public void paintSelf(Graphics gImage){
gImage.drawImage(img,x,y,null);
}
public Rectangle getRec(){
return new Rectangle(x,y,width,height);
}
}

这是一个抽象类 GameObject,包含了游戏中所有物体共有的属性和方法,其他游戏物体类都应该继承自该类。

该类包含的成员变量包括:

  • Image img:图片对象
  • int x:图片横坐标
  • int y:图片纵坐标
  • int width:矩形宽度
  • int height:矩形高度
  • double speed:移动速度
  • GameWin frame:游戏窗口对象

该类包含的构造方法包括:

  • GameObject():
  • GameObject(int x, int y):
  • GameObject(Image img, int x, int y, double speed):
  • GameObject(Image img, int x, int y, int width, int height, double speed, GameWin frame):

该类包含的成员方法包括:

  • Image getImj():获取图片对象
  • void setImj(Image imj):设置图片对象
  • int getX():获取横坐标
  • void setX(int x):设置横坐标
  • int getY():获取纵坐标
  • void setY(int y):设置纵坐标
  • int getWidth():获取宽度
  • void setWidth(int width):设置宽度
  • int getHeight():获取高度
  • void setHeight(int height):设置高度
  • double getSpeed():获取移动速度
  • void setSpeed(double speed):设置移动速度
  • GameWin getFrame():获取游戏窗口对象
  • void setFrame(GameWin frame):设置游戏窗口对象
  • void paintSelf(Graphics gImage):绘制自身的方法
  • Rectangle getRec():获取物体的矩形边界对象

五、我方飞机类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class Plane extends Background {
int life = 30;
public Plane() {
super();
}

public Plane(Image img, int x, int y, int width, int height, double speed, GameWin frame) {
super(img, x, y, width, height, speed, frame);
this.frame.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
Plane.super.x = e.getX()-25;
Plane.super.y = e.getY()-25;
}
});
}

@Override
public void paintSelf(Graphics gImage) {
gImage.drawImage(img,x-25,y-25,null);
//我方飞机与敌方boos的碰撞检测
if(this.frame.boos!= null && this.getRec().intersects(this.frame.boos.getRec())){
if(this.frame.invincible == 0) {
life = 0;
GameWin.state = 3;
}
}
if(this.frame.invincible==1){
gImage.drawImage(GameUtils.guard,x-35,y-35,null);
}
}

@Override
public Image getImj() {
return super.getImj();
}

@Override
public Rectangle getRec() {
return super.getRec();
}
}


这部分的代码主要是飞机类Plane的实现,其中包含了鼠标移动事件的监听和碰撞检测。
在构造方法中,为Plane添加了鼠标移动事件的监听,使飞机能跟随鼠标移动,实现用鼠标操控飞机的移动。
在paintSelf方法中,绘制了飞机的图像,并进行了与boos飞机的碰撞检测。如果碰到Boos,则生命清零,游戏结束。
getImj方法和getRec方法都是调用父类Background的方法。

六、我方子弹类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.awt.*;

public class Shell extends GameObject {
public Shell() {
super();
}

public Shell(Image img, int x, int y, int width, int height, double speed, GameWin frame) {
super(img, x, y, width, height, speed, frame);
}

@Override
public Image getImj() {
return super.getImj();
}

@Override
public void paintSelf(Graphics gImage) {
super.paintSelf(gImage);
y -= speed;

if(y<-200){
this.x = -100;
this.y = 100;
GameUtils.removeList.add(this);
}
}

@Override
public Rectangle getRec() {
return super.getRec();
}
}


在这段代码是Shell类,它是GameObject的一个子类,该类表示我方子弹。

该类继承父类GameWin的成员变量,调用父类的构造方法,同时重写了父类paintSelf(Graphics gImage)方法,绘制子弹的图片,并使子弹沿Y轴移动,并判断当子弹超出游戏窗口时,将其从游戏窗口中移除。

这个类的主要作用是通过重写paintSelf(Graphics gImage)方法控制子弹的运动和销毁,实现了子弹的移动,如果子弹移出游戏窗口,则将其从游戏中移除,避免资源浪费。

七、敌方飞机类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import java.awt.*;

public class Enemy extends GameObject {
public Enemy() {
super();
}

public Enemy(Image img, int x, int y, int width, int height, double speed, GameWin frame) {
super(img, x, y, width, height, speed, frame);
}

@Override
public void paintSelf(Graphics gImage) {
super.paintSelf(gImage);
y += speed;
//敌我飞机碰撞检测
if(this.getRec().intersects(this.frame.planeObj.getRec())){
if(this.frame.planeObj.life > 1){
if(this.frame.invincible == 0) {
this.frame.planeObj.life--;
}
Explode explode = new Explode(x+5,y);
GameUtils.explodeList.add(explode);
GameUtils.removeList.add(explode);
this.x = -200;
this.y = 200;
GameUtils.removeList.add(this);
}else
GameWin.state = 3;
}
if(y> frame.HEIGHT){
this.x = -100;
this.y = 100;
GameUtils.removeList.add(this);
}
for(Shell shellObj:GameUtils.shellObjList){
if(this.getRec().intersects(shellObj.getRec())){
Explode explode = new Explode(x+5,y);
GameUtils.explodeList.add(explode);
GameUtils.removeList.add(explode);
shellObj.setX(-100);
shellObj.setY(100);
this.x = -200;
this.y = 200;
GameUtils.removeList.add(shellObj);
GameUtils.enemyObjList.add(this);
GameWin.score++;
}
}
}

@Override
public Rectangle getRec() {
return super.getRec();
}
}


在这段代码中定义了一个 Enemy 类,表示游戏中的敌机对象,这个类继承自 GameObject 类。

在 paintSelf(Graphics gImage) 方法中,我们先将敌机对象沿 y 轴方向以速度 speed 移动。如果敌机对象与我方飞机对象相撞,则判断我方飞机对象的生命值是否大于1。如果是,则扣除生命值,生成一个爆炸对象,将敌机对象和爆炸对象加入删除列表中,此时画面中会出现爆炸动画效果。如果生命值不大于1,游戏状态变为游戏失败。

接着,我们判断敌机对象是否超出窗口的下边缘。如果超出,则将敌机对象移出屏幕,并将其加入删除列表中。

最后,我们检查敌机对象是否与所有炮弹对象相撞。相撞后击毁敌机,将敌机对象生成一个爆炸对象,将敌机对象和炸弹对象加入删除列表中。击毁敌机增加游戏得分。

Enemy 类的实现涉及到了游戏的碰撞检测、删除对象等核心逻辑,这些逻辑是实现游戏的重要组成部分。

八、敌方Boos类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import java.awt.*;

public class Boos extends GameObject {
int life = 300;
public Boos() {
super();
}

public Boos(Image img, int x, int y, int width, int height, double speed, GameWin frame) {
super(img, x, y, width, height, speed, frame);
}

@Override
public void paintSelf(Graphics gImage) {
super.paintSelf(gImage);
if(x > 450 || x < -50){
speed = -speed;
}
x += speed;
for(Shell shellObj:GameUtils.shellObjList){
if(this.getRec().intersects(shellObj.getRec())){
shellObj.setX(-100);
shellObj.setY(100);
GameUtils.removeList.add(shellObj);
life--;
}
if(life <= 0){
GameWin.state = 4;
}
}
//血条
if(life>200){
gImage.setColor(Color.yellow);
gImage.fillRect(x-50,y+130,300,5);
gImage.setColor(Color.green);
gImage.fillRect(x-50,y+130,(life-200)*3,5);
} else if (life > 100) {
gImage.setColor(Color.red);
gImage.fillRect(x-50,y+130,300,5);
gImage.setColor(Color.yellow);
gImage.fillRect(x-50,y+130,(life-100)*3,5);
}else{
gImage.setColor(Color.white);
gImage.fillRect(x-50,y+130,300,5);
gImage.setColor(Color.red);
gImage.fillRect(x-50,y+130,life*3,5);
}
}

@Override
public Rectangle getRec() {
return super.getRec();
}
}


这段代码是一个 Boos 类,继承自 GameObject 类。这个类代表了游戏中的一个 Boss 角色,具有生命值、移动速度等属性。以下是Boos类包含的属性和方法:

增加成员变量:

  • life:表示 Boss 的生命值,初始值为100。

Boos类调用父类的构造方法,重写父类paintSelf(Graphics gImage)方法,绘制 Boss的图像,并进行移动、碰撞检测、生命值计算以及血条绘制等操作。

在 paintSelf(Graphics gImage) 方法中,Boss 对象进行了以下操作:

  1. 绘制自身的图像,通过调用父类的paintSelf(Graphics gImage)方法实现。
  2. 进行水平方向的移动。如果Boss的位置超出了一定范围,速度就会反向,从而实现左右来回移动的效果。
  3. 对Shell对象进行碰撞检测。如果Boss的矩形区域与某个Shell对象的矩形区域相交,说明两者发生了碰撞,此时将Shell对象从屏幕上移除,并减少Boss的生命值,实现了我方子弹对Boos的攻击。
  4. 如果Boss的生命值小于等于0,将游戏状态修改为失败状态(state=4)。
  5. 绘制Boss的血条。血条由两个矩形组成,一个白色矩形表示总血量,一个红色矩形表示当前血量。

九、敌方子弹类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import java.awt.*;

public class EnemyBullet extends GameObject {
public EnemyBullet() {
super();
}
private int z;
private int speed2;
private int k;
private int b;
public EnemyBullet(Image img, int x, int y, int width, int height, double speed,int z,int speed2,int k,int b,GameWin frame) {
this.img = img;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.speed = speed;
this.frame = frame;
this.z = z;
this.speed2 = speed2;
this.k = k;
this.b = b;
}

@Override
public void paintSelf(Graphics gImage) {
super.paintSelf(gImage);


if(this.frame.boos.life > 200) {
if(z == 1)
x += speed2;
else if(z == 2)
x -= speed2;
y += speed;
}
else if(this.frame.boos.life > 100){
y += 15;
if(k != 0)
x = (y-b)/k;
}
else{
if(z == 1)
x += speed2;
else if(z == 2)
x -= speed2;
y += speed;
}


//碰撞检测
if(this.getRec().intersects(this.frame.planeObj.getRec())){
if(this.frame.planeObj.life >1){
if(this.frame.invincible == 0) {
this.frame.planeObj.life--;
}
this.x = -100;
this.y = 100;
GameUtils.removeList.add(this);
}else
GameWin.state = 3;
}
if(y > frame.HEIGHT){
this.x = -100;
this.y = 100;
GameUtils.removeList.add(this);
}
}

@Override
public Rectangle getRec() {
return super.getRec();
}
}


这是一个控制敌人子弹的类,实现子弹的运动规律和碰撞检测。

在这个类中,z参数表示子弹的水平方向运动方式,1表示向右,2表示向左。speed2表示子弹的水平方向运动速度。

k和b是子弹运动的一条直线的参数,可以表示为y = kx + b,用于控制敌人子弹在boos的生命值小于30时运动轨迹,此时Boos追踪发射子弹。

十、爆炸类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.awt.*;

public class Explode extends GameObject{
static Image[] picture = new Image[16];
int explodeCount = 0;
static{
for(int i = 0;i<picture.length;i++){
picture[i] = Toolkit.getDefaultToolkit().getImage("C:\\Users\\ASUS\\javaworkspace\\untitled\\AircraftBattle\\src\\Images\\e"+(i+1)+".gif");
}
}
public Explode(int x, int y) {
super(x, y);
}

@Override
public void paintSelf(Graphics gImage) {
super.paintSelf(gImage);
if (explodeCount < 16){
img = picture[explodeCount];
super.paintSelf(gImage);
explodeCount++;
}
// 每一次绘制不会消失,下一次repaint方法再次调用重绘,知道爆炸图片绘制完
if(explodeCount == 16) {
GameUtils.explodeList.remove(this);
}
}
}

Explode类用于处理游戏中的爆炸效果。在游戏中,当敌方飞机被击中时,我们需要显示爆炸效果,这就需要使用到Explode类。

静态代码块用于获取爆炸图片资源,在类加载器中仅执行一次。

Explode类的paintSelf方法中,我们需要根据参数explodeCount来选择显示哪一张爆炸图片,达到爆炸动画的效果。

十一、道具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.awt.*;

public class Props extends GameObject{
private String name;
public Props() {
}

public Props(Image img, String name,int x, int y, int width, int height, double speed, GameWin frame) {
super(img, x, y, width, height, speed, frame);
this.name = name;
}

@Override
public void paintSelf(Graphics gImage) {
super.paintSelf(gImage);
y+=speed;
if(this.getRec().intersects(this.frame.planeObj.getRec())){
if(name.equals("level")) {
GameWin.level += 1;
}
if(name.equals("life") && this.frame.planeObj.life < 30) {
if(this.frame.planeObj.life >= 24)
this.frame.planeObj.life = 30;
else
this.frame.planeObj.life += 6;
}

GameUtils.removeList.add(this);
}
}

@Override
public Rectangle getRec() {
return super.getRec();
}
}


Props类实现了两种游戏道具,分别是升级道具和生命回复道具,增加了游戏的趣味性。
在paintSelf方法中,通过传入的字符串判断是升级道具还是生命回复道具,并进行碰撞检测来判断我方飞机是否吃到道具。
我方飞机吃到升级道具后等级加1,吃到生命回复道具时,如果生命值不满,则回复6点生命。

十二、游戏工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import java.awt.*;
import java.util.ArrayList;
import java.util.List;

public class GameUtils {
public static String s = "untitled\\AircraftBattle\\src\\Images\\";
public static Image background = Toolkit.getDefaultToolkit().getImage(
s + "background.jpg");

public static Image background2 = Toolkit.getDefaultToolkit().getImage(
s +"background2.jpg");
public static Image boos = Toolkit.getDefaultToolkit().getImage(
s +"boos.png");
public static Image ExplodeBig = Toolkit.getDefaultToolkit().getImage(
s +"ExplodeBig.png");
public static Image ExplodeSmall = Toolkit.getDefaultToolkit().getImage(
s +"ExplodeSmall.png");
public static Image red = Toolkit.getDefaultToolkit().getImage(
s +"red.png");
public static Image shell = Toolkit.getDefaultToolkit().getImage(
s +"shell.png");
public static Image enemyPlane = Toolkit.getDefaultToolkit().getImage(
s +"enemyPlane.png");
public static Image enemyShell = Toolkit.getDefaultToolkit().getImage(
s +"enemyShell.png");
public static Image heart = Toolkit.getDefaultToolkit().getImage(
s +"heart.png");
public static Image heart2 = Toolkit.getDefaultToolkit().getImage(
s +"heart2.png");
public static Image title = Toolkit.getDefaultToolkit().getImage(
s +"title.png");
public static Image level = Toolkit.getDefaultToolkit().getImage(
s +"level.png");
public static Image barrier1 = Toolkit.getDefaultToolkit().getImage(
s +"barrier1.png");
public static Image barrier2 = Toolkit.getDefaultToolkit().getImage(
s +"barrier2.png");
public static Image guard = Toolkit.getDefaultToolkit().getImage(
s +"guard.png");

public static List<GameObject> gameObjectList = new ArrayList<>();
//我方子弹的集合
public static List<Shell> shellObjList = new ArrayList<>();
//敌机的集合
public static List<Enemy> enemyObjList = new ArrayList<>();
//要删除元素的集合
public static List<GameObject> removeList = new ArrayList<>();
//敌方子弹集合
public static List<EnemyBullet> enemyShellList = new ArrayList<>();
//
public static List<Explode> explodeList = new ArrayList<>();

public static void drawWord(Graphics gImage,String str,Color color,int size,int x,int y){
gImage.setColor(color);
gImage.setFont(new Font("黑体",Font.BOLD,size));
gImage.drawString(str,x,y);
}

public static void heartShow(Graphics gImage,int life,GameWin frame){
int height = 35,width = 20,cha = 2;
gImage.setColor(Color.white);
for (int i = 0; i < 15; i++) {
gImage.fillRect(width*i+5,750+cha*i,width,height-cha*i);
}
int[][] rgb = {{17,255,0},{33,238,0},{49,221,0},{65,204,0},
{80,187,0},{96,170,0},{112,153,0},{128,136,0},
{144,119,0},{160,102,0},{176,85,0},{192,68,0},
{207,51,0},{223,34,0},{255,0,0}};
for (int i = 0; i < 15; i++) {
gImage.setColor(new Color(rgb[i][0],rgb[i][1],rgb[i][2]));
if(frame.planeObj.life>=15*2-2*i)
gImage.fillRect(width*i+5,750+cha*i,width/2,height-cha*i);
if(frame.planeObj.life>=15*2-2*i-1)
gImage.fillRect(width*i+5+width/2,750+cha*i,width/2,height-cha*i);
}
}
}

GameUtils类是一个工具类,用于存储游戏中需要使用的图片、游戏对象集合。在这个类中使用了Java AWT工具包中的Toolkit类,它允许我们加载并处理图像。在类中加载了游戏所需的所有图像,并将其存储为静态变量。

drawWord方法用于绘制字符串,能根据需要设置字符串的颜色、字体、大小以及位置,用于在界面上显示文字。

heartShow方法实现了显示我方飞机的生命值,在屏幕左下角以多个向右减小的矩形来表示生命值,矩形颜色由绿色到红色渐变。

十三、背景类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.awt.*;

public class Background extends GameObject {
public Background() {
super();
}

public Background(Image img, int x, int y, int width, int height, double speed, GameWin frame) {
super(img, x, y, width, height, speed, frame);
}

public Background(Image img, int x, int y, double speed) {
super(img, x, y, speed);
}

@Override
public void paintSelf(Graphics gImage) {
super.paintSelf(gImage);
y += speed;
if(y >= 800){
y = -1600;
}
}
}

这段代码定义了一个背景类,在paintSelf方法中实现了背景图片的循环移动。

十四、障碍类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import java.awt.*;

public class barrier extends GameObject{
int life;
int lifeMax;
int state = 0;
int Y = 0;
public barrier() {
}

public barrier(int x,int y,int speed,int life,int width,int height,GameWin frame) {
this.frame = frame;
this.speed = speed;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.life = life;
this.lifeMax = life;
}



@Override
public void paintSelf(Graphics gImage) {
gImage.drawImage(GameUtils.barrier1,x,Y,50,50,frame);
gImage.drawImage(GameUtils.barrier2,x+150,Y,50,50,frame);

if(Y<y && life == lifeMax)
Y += speed;
if(Y == y && life == lifeMax){
gImage.setColor(Color.red);
gImage.fillRect(x+50-5,Y+20,110,10);
state = 1;
}
if(state == 1){
gImage.setColor(Color.red);
gImage.fillRect(x+50-5,Y+20,110,10);

for(Shell shellObj:GameUtils.shellObjList){
if(shellObj.getRec().intersects(new Rectangle(x+20,Y+20,160,10))){
if(life > 0){
life--;
shellObj.setX(-100);
shellObj.setY(100);
GameUtils.removeList.add(shellObj);
}
else {
state = 2;
break;
}
}
}
int n = 60/lifeMax;
gImage.setColor(Color.white);
gImage.fillRect(x+70,y+40,60,5);
gImage.setColor(Color.green);
gImage.fillRect(x+70,y+40,life*n,5);
}
if(state == 2){
Y -= speed;
if(Y<-50){
GameUtils.removeList.add(this);
}
}
}

@Override
public Rectangle getRec() {
return super.getRec();
}
}m

barrier类实现了游戏中障碍的添加,通过两个有一定距离的小球向下移动,停止后释放激光障碍,阻拦玩家子弹,激光障碍有一定生命值,可以在打掉障碍所有生命值后摧毁障碍,两个小球原路返回。该类增加了游戏难度和趣味性。

十五、音乐类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import javax.sound.sampled.*;
import java.io.*;

public class Music {
private Clip clip;

public Music() {
try {
File file = new File("C:\\Users\\ASUS\\JavaWorkspace\\untitled\\AircraftBattle\\src\\Music\\BackgroundMusic.wav");
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(file);
clip = AudioSystem.getClip();
clip.open(audioInputStream);
clip.loop(Clip.LOOP_CONTINUOUSLY);
} catch (UnsupportedAudioFileException | IOException | LineUnavailableException e) {
e.printStackTrace();
}
}

public void play() {
clip.start();
}
}

这段代码定义了Music类,其中包含一个Clip类型的私有变量clip。在类的构造函数中,它从指定文件路径中读取音频文件,并将其转换为AudioInputStream类型,然后使用AudioSystem.getClip()方法创建一个新的Clip对象。
最后,使用clip.open()方法打开音频流并使用clip.loop()方法将其设置为循环播放。
该类实现了循环播放背景音乐的功能。

十六、总结

在本文中,我们介绍了如何使用Java语言和AWT/Swing库来创建一个简单的2D飞行射击游戏。我使用了许多基本的Java编程概念,并使用它们来构建游戏世界中的各种对象。

在这个游戏中,实现了一个基本的游戏循环,在每帧中更新并绘制游戏中的所有对象。还实现了基本的碰撞检测来处理玩家飞机和敌人之间的冲突,以及玩家子弹和敌人之间的碰撞。

游戏中还了解了如何使用Java的鼠标和键盘事件来捕获用户的输入,并使用这些事件来控制游戏中的对象。
游戏截图如下:

以上,我们完成了一个简单的2D飞行射击游戏,并使用它来练习Java编程和游戏开发的基础知识。这个项目不仅是一个有趣的挑战,还提供了许多有用的技术经验,可以应用到更复杂的游戏开发项目中。