坦克大战游戏
绘图原理
frame
类继承JFrame
是窗口的框架。Mypanel
类继承JPanel
是画布。重写画布类的
paint()
方法绘制图形。在框架类中初始化画布类,将画布加入窗口
1 | import javax.swing.*; |
绘制坦克
Tank
类为所有角色的父类Player
玩家类继承自Tank
类MyPanel
画布类继承JPanel
类,绘制图形Frame
画框类实现主函数,初始化画布
1 | package com.tankgame; |
1 | package com.tankgame; |
1 | package com.tankgame; |
1 | package com.tankgame; |
效果图
事件处理机制
事件处理机制解决如何接收键盘输入实现小球移动问题
MyPanel
类实现KeyListener
接口,重写其中的方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void keyTyped(KeyEvent e) {
}
//某个键按下时,该方法触发
public void keyPressed(KeyEvent e) {
}
//某个键释放时,该方法触发
public void keyReleased(KeyEvent e) {
}在
class BallMove extends JFrame
类里加入this.addKeyListener(mp);
即可监听面板发生的键盘事件
1 | package com.event_; |
让坦克动起来
- 在
Tank
类增加属性direction
和speed
分别表示坦克的方向和速度。 - 在
Tank
类增加四个移动函数,改变坦克的方向和位置 - 在
MyPanel
类绘制另外三个方向的坦克 - 添加监听事件
1 | package com.tankgame02; |
1 | package com.tankgame02; |
1 | package com.tankgame02; |
增加敌人
- 添加
Enemy
类继承Tank
,修改方向让敌人的炮筒指向下 - 用
Vector
存放敌人
1 | package com.tankgame02; |
1 | package com.tankgame02; |
效果图
发射子弹
玩家发射子弹
实现按下J
键,玩家坦克发射子弹
- 创建
Bullet
类,用重写方式实现线程重写run()
方法 - 在
run()
方法里实现子弹向炮筒的方向移动 - 在
Player
类里实现shoot()
方法,每次调用该方法就启动一个子弹线程 - 在
MyPanel
类里添加按键操作,和绘制子弹方法
1 | package com.tankgame03; |
1 | package com.tankgame03; |
1 | package com.tankgame03; |
public void keyPressed(KeyEvent e)
这个函数里每次按下键盘画面重绘一次。虽然能够实现坦克的移动(因为不按键盘坦克就会静止也就不需要重绘了),但是子弹不能一直移动,也是只能每次按一下键盘才能动一次。
所以为了让MyPanel
不停重绘画面,让MyPanel
类继承Runnable
类,在run()
方法中不断刷新
最后在Frame
里启动线程
1 | package com.tankgame03; |
1 | package com.tankgame03; |
敌人发射子弹
- 在
Enemy
类创建Vector存储敌人子弹 - 每创建一个敌人,就创建一个子弹线程并启动
- 重绘子弹
1 | package com.tankgame04; |
1 | package com.tankgame04; |
攻击效果
实现玩家子弹攻击到敌人时,敌人消失的效果。
在
MyPanel
类实现hitTank()
方法实现子弹玩家子弹击中敌人效果- 跟据敌人的方向分为上下和左右两组
- 分别实现两组击中判断
- 如果击中将子弹和玩家的
isLive
设置为false
,并将对应敌人从集合删除
在
run()
方法中循环hitTank()
1 | package com.tankgame04; |
爆炸效果
玩家击中敌人时出现爆炸效果。爆炸效果用三张图片实现
- 创建爆炸效果
Bomb
类,实现lifeDown
函数,生存时间递减。 - 在
MyPanel
类创建集合存储Bomb
对象,当击中坦克时加入一个对象 - 遍历集合绘制爆炸效果,当生存时间为0,在集合中删除对象
1 | package com.tankgame04; |
1 | package com.tankgame04; |
敌人移动
- 让
Enemy
类实现Runnable
接口 - 在
run()
方法中实现敌人自由移动 - 每次新建一个敌人就启动一个线程
1 | package com.tankgame04; |
1 | package com.tankgame04; |
限制移动范围
将敌人和玩家的移动范围限制在窗口内
修改Tank
类的移动函数,加入判断条件
1 | public void moveup() { |
完善子弹
玩家可以连发
现在玩家只能发射一颗子弹,每次按下J键,上一颗子弹会消失然后发射一颗新子弹。现在实现连发功能
- 在
Player
类里初始化集合bullets
存储所有的子弹 - 在
shoot()
方法里实现每次发射一颗子弹就将子弹加入集合 - 修改击中逻辑
1 | package com.tankgame04; |
1 | package com.tankgame04; |
public void paint(Graphics g) {
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
for (int i = 0; i < player.bullets.size(); i++) { //绘制每一颗子弹
Bullet bullet = player.bullets.get(i);
g.fill3DRect(bullet.getX(), bullet.getY(),
5, 5, false);
if (!bullet.isLive()) {
player.bullets.remove(bullet);
}
}
}
public void run() { //重绘
while (true) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//判断是否击中敌人坦克
for (int i = 0; i < player.bullets.size(); i++) { //遍历子弹数组和敌人数组判断是否击中
Bullet bullet = player.bullets.get(i);
if (bullet != null && bullet.isLive()) {
for (int j = 0; j < enemies.size(); j++) {
Enemy enemy = enemies.get(j);
hitTank(bullet, enemy);
}
}
}
this.repaint();
}
}
}
敌人可以连发
现在敌人只能发一次子弹。实现子弹连发功能
- 在
Enemy
类实现Shoot()
方法,直接复制玩家类的 - 把
Shoot()
方法加入到run()
方法中
1 | package comclone.tankgame04; |
shoot();
1
2
}
}
玩家被击中效果
现在只有玩家能击中敌人,敌人不能击中玩家
- 在
MyPanel
类添加hitHero()
方法,实现击中玩家逻辑 - 如果玩家被击中,将玩家的
isLive
设置为false
- 为绘制玩家增加判断玩家是否存活的判断条件
1 | package comclone.tankgame04; |
防止坦克重叠
为坦克增加碰撞体积防止坦克重叠
- 在敌人类添加
Vector<Enemy> enemies
,用来记录所有的敌人 - 遍历集合,判断当前坦克是否和其他坦克产生碰撞
1 | package com.tankgame05; |
存档
记录玩家总成绩
记录玩家击毁坦克总数。
创建Recorder
类,实现文件操作
1 | package com.tankgame05; |
在
Mypanel
的构造函数添加loadRecord()
击中敌人时增加杀敌数
Frame
中实现关闭窗口时调用saveRecord()
1 | package com.tankgame05; |
Recorder.loadRecord();
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
}
public void hitTank(Bullet b, Enemy e) {
switch (e.getDirection()) {
case 0: //坦克向上
case 2: //坦克向下
if (b.getX() > e.getX() && b.getX() < e.getX() + 40 && b.getY() > e.getY() && b.getY() < e.getY() + 60) {
Bomb bomb = new Bomb(b.getX(), b.getY());
bombs.add(bomb);
b.setLive(false);
e.setLive(false);
enemies.remove(e);
Recorder.addKill(); //击杀坦克增加杀敌数
}
break;
case 1: //坦克向右
case 3: //坦克向左
if (b.getX() > e.getX() && b.getX() < e.getX() + 60 &&
b.getY() > e.getY() && b.getY() < e.getY() + 40) {
Bomb bomb = new Bomb(b.getX(), b.getY());
bombs.add(bomb);
b.setLive(false);
e.setLive(false);
enemies.remove(e);
Recorder.addKill(); //击杀坦克增加杀敌数
}
}
}
}
```
```java
package com.tankgame05;
import javax.swing.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class Frame extends JFrame {
MyPanel mp = null;
public static void main(String[] args) {
new Frame();
}
public Frame() {
mp = new MyPanel();
this.add(mp);
new Thread(mp).start();
this.addKeyListener(mp);
this.setSize(1300, 797);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
//相应关闭窗口的处理
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
Recorder.saveRecord();
System.exit(0);
}
});
}
}
```
## 记录敌人信息
增加继续游戏新功能,加载上局敌人信息
* 创建`Node`类,每一个`Node`对象是一个敌人信息,记录坐标和方向
* 在`Recorder`类中实现存储和加载功能
* 开始游戏前接收玩家输入来决定是新游戏还是继续游戏
---
```java
package com.tankgame05;
/*
一个Node对象表示一个敌人坦克信息
*/
public class Node {
private int x;
private int y;
private int direction;
public Node(int x, int y, int direction) {
this.x = x;
this.y = y;
this.direction = direction;
}
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 getDirection() {
return direction;
}
public void setDirection(int direction) {
this.direction = direction;
}
}
```
---
```java
package com.tankgame05;
/*
用于记录相关信息,和文件交互
*/
import java.io.*;
import java.util.Vector;
public class Recorder {
//定义变量,记录玩家击杀数
private static int kills = 0;
//定义IO对象,用于写数据到文件中
private static BufferedWriter bw = null;
private static BufferedReader br = null;
private static String recordFile = "src\\myRecord.txt";
//定义Vector指向MyPanel敌人数组
private static Vector<Enemy> enemies = null;
//保存敌人信息
private static Vector<Node> nodes = new Vector<>();
//读取recordFile,恢复相关信息
public static Vector<Node> load(){
try {
br = new BufferedReader(new FileReader(recordFile));
kills = Integer.parseInt(br.readLine());
//循环读取文件生成nodes集合
String line = "";
while((line=br.readLine())!=null){
String[] xyd = line.split(" ");
Node node = new Node(Integer.parseInt(xyd[0]),
Integer.parseInt(xyd[1]),
Integer.parseInt(xyd[2]));
nodes.add(node);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return nodes;
}
//保存文件
public static void saveRecord() {
try {
bw = new BufferedWriter(new FileWriter(recordFile));
bw.write(String.valueOf(kills));
bw.newLine();
//遍历敌人坦克的集合,跟据情况保存
for(Enemy i:enemies){
if(i.isLive()){
String record = i.getX()+" "+i.getY()+" "+i.getDirection();
//写入文件
bw.write(record+"\r\n");
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//加载文件
public static int getKills() {
return kills;
}
public static void setKills(int kills) {
Recorder.kills = kills;
}
//击杀
public static void addKill() {
Recorder.kills++;
}
public static void setEnemies(Vector<Enemy> enemies) {
Recorder.enemies = enemies;
}
public static String getRecordFile() {
return recordFile;
}
}
```
---
````java
package com.tankgame05;
/*
* 游戏的绘图区
* */
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Vector;
public class MyPanel extends JPanel implements KeyListener, Runnable {
```
//恢复敌人坦克坐标
Vector<Node> nodes = new Vector<>();
public MyPanel(String key) {
File file = new File(Recorder.getRecordFile());
if(file.exists()){
nodes = Recorder.load();
}
else {
key = "1";
}
switch(key){
case "1": //新游戏
Recorder.load();
player = new Player(600, 500,0);//初始化玩家
for (int i = 0; i < enemySize; i++) { // 创建敌人
Enemy enemy = new Enemy((i * 100 + 100), 0,2);
enemy.setEnemies(enemies);
enemies.add(enemy);
new Thread(enemy).start();
Bullet bullet = new Bullet(enemy.getX() + 20, enemy.getY() + 60, enemy.getDirection());
enemy.bullets.add(bullet);
new Thread(bullet).start();
}
break;
case "2": //继续游戏
nodes = Recorder.load();
player = new Player(600, 500,0);//初始化玩家
for (int i = 0; i < nodes.size(); i++) { // 创建敌人
Node node = nodes.get(i);
Enemy enemy = new Enemy(node.getX(),node.getY(),node.getDirection());
enemy.setEnemies(enemies);
enemies.add(enemy);
new Thread(enemy).start();
Bullet bullet = new Bullet(enemy.getX() + 20, enemy.getY() + 60, enemy.getDirection());
enemy.bullets.add(bullet);
new Thread(bullet).start();
}
break;
default:
System.out.println("输入有误");
}
```
}
1 | package com.tankgame05; |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 5G研究室!