项目简介

此项目是bilibili软帝学院的教学项目,由纯Java编写。本博客仅为记录自己遇到的一些错误,同时尽力帮助同时在做类似游戏的同学。
原项目链接:Java小游戏制作:三国战记\捕鱼达人\飞机大战\飞扬小鸟

小知识点

窗体、面板方面

  1. 创建窗体
    • 设置标题
      setTitle(“标题”);
    • 设置窗体大小
      setSize(宽,高);
    • 设置不允许玩家改变窗体大小
      setResizable(false);
    • 设置窗口关闭时自动停止程序
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  2. 创建面板
    • 设置面板的可见性
      frame.setVisible(true);
  3. 将面板加入到窗体中
    frame.add(panel);

线程方面

创建并启动一个线程,控制游戏场景中活动的物体
固定格式

1
2
3
4
5
new Thread(){
public void run(){
线程需要做的事
}
}.start();

键鼠监听器

鼠标监听器

     鼠标移动事件mouseMoved(MouseEvent e){
           相对应函数
         }
        其他监听事件的格式类似
        鼠标单击事件mouseClicked()
        鼠标按下去的事件mousePressed()
        鼠标移入游戏界面的事件mouseEntered()
        鼠标移出游戏界面的事件mouseExited()
1
2
3
4
5
6
7
8
9
MouseAdapter adapter=new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
相应的方法
}
};
//将适配器加入到监听器中
addMouseListener(adapter);
addMouseMotionListener(adapter);

键盘监听器

可用getKeyCode()函数获得按键的编号

1
2
3
4
5
6
7
8
KeyAdapter kd=new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
相应的方法
}
};
//同样,需要将适配器加入到监听器中
frame.addKeyListener(kd);

绘图

固定开头
@Override
public void paint(Graphics g) {
super.paint(g);
用画笔画图
g.drawImage(图片,位置(横坐标),位置(纵坐标),null)

设置字体颜色、粗体、大小等
g.setColor(Color.WHITE);
g.setFont(new Font(“楷体”,Font.BOLD,20));

用画笔写字
g.drawString(“字符串”,位置(横坐标),位置(纵坐标));

遇到的问题

java.lang.IllegalArgumentException: input == null!

如图:java.lang.IllegalArgumentException: input == null!

bug原因:

  1. 路径错误
  2. idea的out文件不同步,out中并没有img文件夹,如图
    在这里插入图片描述

解决方法:

原因一:
1. 查看控制台错误提示信息
2. 检查App类中26行附近语句的路径是否正确
原因二:
1. 选中src文件夹后,点击顶部横栏中的build
2. 点击ReBuild ‘img’
3. 点击左上角File,点击Close Project。
4. 再次打开项目即可。
如果还不行,就重启电脑。
本人两种方法都试过,都可以。

源码

  1. GameFrame.class
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
package ui;

import javax.swing.*;

/**
* Created by IntelliJ IDEA.
* User: Zengc
* Date: 2020/11/12
* Time: 18:23
**/
public class GameFrame extends JFrame{
public GameFrame(){
//设置窗体标题
setTitle("飞机大战");
//设置窗体大小
setSize(512,768);
//设置位置居中(null表示相对左上角居中)
setLocationRelativeTo(null);
//设置不允许玩家改变窗体大小
setResizable(false);
//设置窗口关闭时自动停止程序
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
//主函数
public static void main(String[] args){
//创建窗体
GameFrame frame=new GameFrame();

//创建面板
GamePanel panel=new GamePanel(frame);

//将面板加入到窗体中
frame.add(panel);

panel.action();

//设置面板的可见性
frame.setVisible(true);
}
}

  1. GamePanel.java
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
336
337
338
339
340
341
package ui;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

/**
* Created by IntelliJ IDEA.
* User: Zengc
* Date: 2020/11/12
* Time: 18:44
**/
//面板类
public class GamePanel extends JPanel {
BufferedImage bg; //背景
Hero hero=new Hero();
/**
* 使用集合代表 敌机大本营
* 不用数组,是因为不能提前知道大本营中敌机的数量
* 若用数组,还需要扩容
* 创建的敌机都会放到这个集合中
* 绘图时遍历此集合即可
*/
List<Ep> eps=new ArrayList<Ep>();
/**
* 英雄机的弹药库
* 与敌机大本营类似
*/
ArrayList<Fire> fs=new ArrayList<Fire>();
int score; //得分
int power=1; //火力
boolean gameover=false; //游戏开关

//创建面板
public GamePanel(GameFrame frame){

//设置背景颜色
setBackground(Color.black);

//App是工具类,专门用来获取图片的,参数是相对路径
bg=App.getImg("/img/bg1.jpg");

/**
* 鼠标监听事件
* 鼠标移动事件mouseMoved(MouseEvent e){
* 相对应函数
* }
* 其他监听事件的格式类似
* 鼠标单击事件mouseClicked()
* 鼠标按下去的事件mousePressed()
* 鼠标移入游戏界面的事件mouseEntered()
* 鼠标移出游戏界面的事件mouseExited()
*/
MouseAdapter adapter=new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {

//获取鼠标的位置
int mx=e.getX();
int my=e.getY();

//游戏未结束时,调用函数,将英雄机移动到鼠标位置
if(!gameover)
hero.moveToMouse(mx,my);

//重画,刷新一下
repaint();
}
@Override
public void mouseClicked(MouseEvent e) {

/**
* 游戏结束时,鼠标点击画面,重新开始新游戏
* 恢复hp
* 游戏结束标志置假
* 得分清零
* 敌机大本营清零
* 弹药库清零
* 刷新,重画
*/
if(gameover){
hero = new Hero();
gameover=false;
score=0;
eps.clear();
fs.clear();
power=1;
repaint();
}
}
};

//将适配器加入到监听器中
addMouseListener(adapter);
addMouseMotionListener(adapter);

//键盘监听事件
KeyAdapter kd=new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {

/**
* 游戏未结束时,才可以调用相应函数移动
* getKeyCode()函数获得按键的编号
* KeyEvent.VK_UP、KeyEvent.VK_DOWN、KeyEvent.VK_LEFT、KeyEvent.VK_RIGHT
* 分别表示上下左右键的编号
* 移动之后需要重画,刷新页面
*/
if(!gameover){
if(e.getKeyCode()==KeyEvent.VK_UP){
hero.moveUp();
}else if(e.getKeyCode()==KeyEvent.VK_DOWN){
hero.moveDown();
}else if(e.getKeyCode()==KeyEvent.VK_LEFT){
hero.moveLeft();
}else if(e.getKeyCode()==KeyEvent.VK_RIGHT){
hero.moveRight();
}
}
repaint();
}
};

//同样,需要将适配器加入到监听器中
frame.addKeyListener(kd);
}

public void action(){
/**
* 创建并启动一个线程,控制游戏场景中活动的物体
* 固定格式
* new Thread(){public void run(){..线程需要做的事..}}.start();
*/
new Thread(){
public void run(){
while(true){
if(!gameover){
epEnter(); //敌机入场
epMove(); //敌机向下移动
shoot(); //英雄机发射子弹
firemove(); //子弹移动
shootEp(); //
hit();
repaint(); //刷新
}
try {
Thread.sleep(10); //暂停10ms
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}

//英雄机被敌机击中
private void hit() {
/**
* 遍历敌机大本营
* 若敌机与英雄机相撞
* 则将相撞的英雄机删除,
* 英雄机hp-1
* 得分-10(最低为0)
* 火力回归最初水平
* 若血量为0,则游戏结束
*/
for (int i = 0; i < eps.size(); i++) {
Ep ep = eps.get(i);
if(ep.hitBy(hero)){
eps.remove(ep);
hero.hp--;
score-=10;
if(score<0)
score=0;
power=1;
if(hero.hp<=0)
gameover=true;
}
}
}

//遍历敌机大本营
private void shootEp() {
for (int i = 0; i < fs.size(); i++) {
Fire fire=fs.get(i);
bang(fire);
}
}

//敌机被击中后
private void bang(Fire fire){
for (int i = 0; i < eps.size(); i++) {
Ep ep=eps.get(i);

//如果击中目标
if(ep.shootBy(fire)){

//敌机血量下降
ep.hp--;

//如果敌机的血量为零,则将此敌机从大本营删除,并加分
if(ep.hp==0){

//如果被击毁的是14号敌机,那么英雄机可以加血或者升级武器
if(ep.type==14){
if(hero.hp<=5)
hero.hp++;
else if(power<=3)
power++;
}

eps.remove(ep);
score+=10;
}
//将发生碰撞的子弹从子弹库中删去
fs.remove(fire);
}
}
}

//用于控制子弹发射频率
int fireindex;
//英雄机发射子弹
private void shoot() {

//GamePanel类action方法中的while循环10次,发射一次子弹
if(fireindex%5==0){

//火力为1,英雄机正中间发射子弹
if(power==1) {
Fire fire2 = new Fire(hero.x + 33, hero.y - 20); //中间弹道子弹的起始位置应在靠前方一点
fs.add(fire2);
}else if(power==2){

//火力为2,英雄机两侧发射子弹
Fire fire1=new Fire(hero.x+11,hero.y);
fs.add(fire1);
Fire fire3=new Fire(hero.x+58,hero.y);
fs.add(fire3);
}else{

//火力为3,三弹道一起发射子弹
Fire fire1=new Fire(hero.x+11,hero.y);
fs.add(fire1);
Fire fire2=new Fire(hero.x+33,hero.y-20);
fs.add(fire2);
Fire fire3=new Fire(hero.x+58,hero.y);
fs.add(fire3);
}
}
fireindex++;
}

//子弹移动
private void firemove(){

//获取子弹库中每发子弹,并调用move函数,进行向下移动
for (int i = 0; i < fs.size(); i++) {
Fire fire=fs.get(i);
fire.move();
}
}

//飞机移动(与子弹移动类似)
private void epMove() {
for (int i = 0; i < eps.size(); i++) {
Ep ep=eps.get(i);
ep.move();
}
}

int index; //用于控制敌机进场频率(与子弹发射频率类似)

//飞机进场
private void epEnter(){
if(index%20==0){
Ep ep=new Ep();
eps.add(ep);
}
index++;
}

/**
* 绘图
* 画笔Graphics
* super.paint(g);
*/
@Override
public void paint(Graphics g) {
//Graphics g 画笔
super.paint(g);

//用画笔画图g.drawImage(图片,位置(横坐标),位置(纵坐标),null)

//画背景及英雄机
g.drawImage(bg,0,0,null);
g.drawImage(hero.img,hero.x,hero.y,hero.h,hero.w,null);

//设置字体颜色、大小等
g.setColor(Color.WHITE);
g.setFont(new Font("楷体",Font.BOLD,20));

//用画笔写字
g.drawString("分数:"+score,10,30);
g.drawString("血量:",240,30);

//绘画英雄机
for(int i = 0; i < hero.hp; i++) {
g.drawImage(hero.img,300+35*i,5,30,30,null);
}

//绘画敌机
for(int i=0;i<eps.size();i++){
Ep ep = eps.get(i);
g.drawImage(ep.img,ep.x,ep.y,ep.h,ep.w,null);
}

//绘画子弹
for(int i=0;i<fs.size();i++) {
Fire fire = fs.get(i);
g.drawImage(fire.img, fire.x, fire.y, fire.h, fire.w, null);
}

//游戏结束时,绘画结束界面
if(gameover){
g.setColor(Color.RED);
g.setFont(new Font("楷体",Font.BOLD,40));
g.drawString("我大意了啊~",150,300);
g.setColor(Color.CYAN);
g.setFont(new Font("楷体",Font.BOLD,20));
g.drawString("单击任意位置,即可重新开始游戏。",80,350);
}
}
}

  1. App.java
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
package ui;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;

/**
* Created by IntelliJ IDEA.
* User: Zengc
* Date: 2020/11/12
* Time: 18:56
**/
public class App {
/**
* static 方法 特点
* 所有对象都共用
* 并且,不需要创建对象就可以直接调用
*/
public static BufferedImage getImg(String path){
try{
/**
* Java的IO流(输入输出流)
* App.class获取App类的路径
* getResource()方法获取资源
*/
BufferedImage img = ImageIO.read(App.class.getResource(path));
return img;
} catch (IOException e){
//找不到图片则输出将异常情况输出
e.printStackTrace();
}
return null;
}
}

  1. Hero.java
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
package ui;

import java.awt.image.BufferedImage;
import java.io.File;
import java.security.PublicKey;
import java.util.ArrayList;

/**
* Created by IntelliJ IDEA.
* User: Zengc
* Date: 2020/11/12
* Time: 20:23
**/
public class Hero extends FlyObject{
int hp; //英雄机的血量
public Hero(){
img=App.getImg("/img/hero.png");
//英雄机的坐标(x,y),此处是英雄机图片的左上角的坐标
x=200;
y=500;
hp=5;
//确定所绘制的英雄机的宽、高(此处分别等于图片的宽和高)
w=img.getWidth();
h=img.getHeight();
}
// 让英雄机移动到“鼠标位置“
public void moveToMouse(int mx,int my){
x=mx-w/2; //让英雄机的中心点(而不是左上角)位于鼠标位置
y=my-w/2;
}

/**
* 键盘控制时所调用的函数
* 其中的if条件判断是为了确保英雄机始终处于屏幕内
*/
public void moveUp(){
y-=20;
if(y<0)
y=0;
}

public void moveDown(){
y+=20;
if(y>768)
y=768;
}

public void moveLeft(){
x-=20;
if(x<0)
x=0;
}

public void moveRight(){
x+=20;
if(x>512)
x=512;
}
}

  1. Ep.java
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
package ui;

import java.util.Random;

/**
* Created by IntelliJ IDEA.
* User: Zengc
* Date: 2020/11/12
* Time: 21:54
**/
public class Ep extends FlyObject{
int speed; //敌机的移动速度
int hp=6; //敌机的血量
int type; //敌机的类型(用于确定Boss机,奖品等)
public Ep(){

//获取随机数
Random rd=new Random();

//将随机数取整(rd.nextInt()范围是[0,15)),所以index范围是[1,16)即[1,15]
int index=rd.nextInt(15)+1;

//确定路径,如果index在1~9,则在index前加0,否则不加
String path="/img/ep"+(index<10?"0":"")+index+".png";

//通过工具类App 获得图片
img=App.getImg(path);

//绘制大小和图片的大小一致
w=img.getWidth();
h=img.getHeight();

//位置横坐标范围为[0,512-w)
//纵坐标为-h ,让其开始位置在屏幕外
x=rd.nextInt(512-w);
y=-h;

/**
* 图片的编号与大小正相关
* 此处想让大飞机速度慢
* 小飞机速度块
* 因此,让敌机速度与编号成反相关
*/
speed=17-index;

//记录飞机型号
type=index;
}

//敌飞向下移动
public void move() {
y+=speed;
}

//判断敌机是否被击中
public boolean shootBy(Fire fire) {
//画图即可判断清楚条件
boolean shoot = x<=fire.x+fire.w &&
x>=fire.x-w &&
y<=fire.y+fire.h &&
y>=fire.y-h;
return shoot;
}

public boolean hitBy(Hero hero) {
//与ShootBy(Fire fire)类似
boolean hit = x<=hero.x+hero.w &&
x>=hero.x-w &&
y<=hero.y+hero.h &&
y>=hero.y-h;
return hit;
}
}

  1. FlyObject.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package ui;

import java.awt.image.BufferedImage;

/**
* Created by IntelliJ IDEA.
* User: Zengc
* Date: 2020/11/12
* Time: 21:59
**/
public class FlyObject {
BufferedImage img;
//定义飞行物的横坐标
int x;
//定义飞行物的纵坐标
int y;
//定义飞行物的宽
int w;
//定义飞行物的高
int h;
}

  1. Fire.java
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
package ui;

/**
* Created by IntelliJ IDEA.
* User: Zengc
* Date: 2020/11/13
* Time: 11:25
**/
public class Fire extends FlyObject{

//英雄机的横纵坐标(hx,hy)
public Fire(int hx,int hy){

//通过工具类App 获得子弹的图片
img=App.getImg("/img/fire.png");

//绘制的大小为图片大小的1/4
w=img.getWidth()/4;
h=img.getHeight()/4;

//子弹的起始位置在英雄机的位置
x=hx;
y=hy;
}

public void move() {
y-=10;
}
}