坦克大战1.0

坦克大战1.0

前言:重温童年经典的坦克大战!1.0版本是基础版本,是我方一辆坦克与敌方三辆坦克的简单射击对战游戏。后续会自己开发出新版本,加入新的功能,例如游戏的主界面与各类弹窗,不同玩家身份信息与成绩的记录,不同关卡墙壁与障碍物的设置,背景音乐与不同场景的切换等等,不久后会有坦克大战2.0版。

一、绘图技术(前置知识)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test {
public static void main(String[] args) {
new DrawCircle();
}
}
class DrawCircle extends JFrame{//窗口(画框)
private MyPanel mp;
public DrawCircle(){
this.mp = new MyPanel();//初始化面板
this.add(mp);//把面板放入窗口
this.setSize(400,400);//设置窗口大小
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//退出程序
this.setVisible(true);//设置可显示
}
}
class MyPanel extends JPanel{//面板
@Override//g可以看作画笔
public void paint(Graphics g){//绘图方法
super.paint(g);//调用父类的方法完成初始化
g.drawOval(10,10,100,100);
}
}

注意1:当组件第一次在屏幕上显示时,程序会自动调用paint()方法来绘制组件。
注意2:在以下情况paint()方法将被自动调用:
1.窗口先最小化,再最大化
2.窗口的大小发生变化
3.repaint()函数被调用(刷新组件的外观)

image-20240525091437505

二、绘制坦克

2.1 设计类结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//Tank父类
public class Tank {
private int x;
private int y;
private int direct = 0;//0上1下2左3右
//...(省略构造与getter和setter)
}
//Hero玩家类
public class Hero extends Tank{
public Hero(int x,int y){
super(x,y);
}
}
//Enemy敌人类
public class Enemy extends Tank{
public Enemy(int x,int y){
super(x,y);
}
}
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
//APP启动类
public class APP {
public static void main(String[] args) {
new GameJFrame();
}
}
//GameJFrame框架类
public class GameJFrame extends JFrame {
private MyPanel myPanel = null;
public GameJFrame(){
myPanel = new MyPanel();
this.add(myPanel);
this.setSize(1000,750);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
}
//MyPanel绘图类(主要代码)
public class MyPanel extends JPanel {
private Hero hero = null;
private Vector<Enemy> enemies = new Vector<>();//多线程安全
private int enemyAmount = 3;

public MyPanel() {
//初始化我方坦克
hero = new Hero(200, 200);
hero.setSpeed(2);
//初始化敌方坦克
for (int i = 0; i < enemyAmount; i++) {
Enemy enemy = new Enemy(100 * (i + 1), 100);
enemy.setDirect(1);
enemy.setSpeed(1);
enemies.add(enemy);
}
}
@Override
public void paint(Graphics g) {
super.paint(g);
g.fillRect(0, 0, 1000, 750);//填充背景
//画出我方坦克
drawTank(hero.getX(), hero.getY(), g, hero.getDirect(), 0);
//画出敌方坦克
for (Enemy enemy : enemies) {
drawTank(enemy.getX(), enemy.getY(), g, enemy.getDirect(), 1);
}
}
public void drawTank(int x, int y, Graphics g, int direct, int type) {...}

2.2 绘制坦克图像

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
//MyPanel类drawTank方法
public void drawTank(int x, int y, Graphics g, int direct, int type) {
//根据不同类型设置不同颜色
switch (type) {
case 0://Hero坦克
g.setColor(Color.yellow);
break;
case 1://Enemy坦克
g.setColor(Color.cyan);
break;
}
//根据不同方向绘制坦克
switch (direct) {
case 0://上
g.fill3DRect(x, y, 10, 60, false);
g.fill3DRect(x + 30, y, 10, 60, false);
g.fill3DRect(x + 10, y + 10, 20, 40, false);
g.fillOval(x + 10, y + 20, 20, 20);
g.drawLine(x + 20, y, x + 20, y + 30);
break;
case 1://下
g.fill3DRect(x, y, 10, 60, false);
g.fill3DRect(x + 30, y, 10, 60, false);
g.fill3DRect(x + 10, y + 10, 20, 40, false);
g.fillOval(x + 10, y + 20, 20, 20);
g.drawLine(x + 20, y + 30, x + 20, y + 60);
break;
case 2://左
g.fill3DRect(x, y, 60, 10, false);
g.fill3DRect(x, y + 30, 60, 10, false);
g.fill3DRect(x + 10, y + 10, 40, 20, false);
g.fillOval(x + 20, y + 10, 20, 20);
g.drawLine(x, y + 20, x + 30, y + 20);
break;
case 3://右
g.fill3DRect(x, y, 60, 10, false);
g.fill3DRect(x, y + 30, 60, 10, false);
g.fill3DRect(x + 10, y + 10, 40, 20, false);
g.fillOval(x + 20, y + 10, 20, 20);
g.drawLine(x + 30, y + 20, x + 60, y + 20);
break;
}
}

2.3 坦克移动(事件监听)

1
2
3
4
5
6
7
8
9
10
11
12
13
//Tank
public class Tank {
private int x;
private int y;
private int direct = 0;//0上1下2左3右
private int speed = 1;
//上下左右移动
public void moveUp() {y -= speed;}
public void moveDown() {y += speed;}
public void moveLeft() {x -= speed;}
public void moveRight() {x += speed;}
//构造方法与getter和setter不变...
}
1
2
//GameJFrame
this.addKeyListener(myPanel);//窗口监听面板发生的键盘事件
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
//MyPanel
//构造方法里设置坦克初始速度
public MyPanel() {
hero = new Hero(100, 100);
hero.setSpeed(2);
}
//重写接口函数(事件监听)
@Override
public void keyPressed(KeyEvent e) {
int d = e.getKeyCode();
switch (d) {
case KeyEvent.VK_W:
hero.setDirect(0);
hero.moveUp();
break;
case KeyEvent.VK_S:
hero.setDirect(1);
hero.moveDown();
break;
case KeyEvent.VK_A:
hero.setDirect(2);
hero.moveLeft();
break;
case KeyEvent.VK_D:
hero.setDirect(3);
hero.moveRight();
break;
}
this.repaint();//别忘了要重绘
}

三、发射子弹与移动(多线程)

3.1 我方发射子弹

image-20240525161710788

遇到问题:

  1. 射出子弹后,子弹出现位置不对,且不会自己动
    解决:debug发现子弹speed初始值为0,所以自然不会动

  2. 坦克方向为2、3时,子弹分别向左上与左下运动,坦克方向为0、1时,子弹仍不动。且输出的x与y坐标均正确,但绘出的子弹运动轨迹不符合
    解决:paint函数里绘制子弹的代码写错了!连写了两个x

    1
    2
    3
    4
    //MyPanel里的paint函数里绘制子弹的部分
    if (hero.getShot() != null && hero.getShot().isLive()) {
    g.fill3DRect(hero.getShot().getX(), hero.getShot().getX(), 3, 3, false);
    }
  3. 为什么相比于一直按键而快速移动的坦克,子弹速度那么慢呢
    解决:run方法里休眠的时间太长了 缩短至50ms就会很快

  4. 为什么坦克多按几次J键,每次按下之后,已射出的子弹就会消失,都会重新从炮筒处开始?
    解决:因为每次按下J键都是启动shoot方法,hero里的子弹属性就是新创建的一个子弹,并开启一个新的线程,原来的子弹就被直接替代。所以应该考虑使用集合存储创建过的子弹,且规定集合存储的上限来限制坦克短时间连续发射子弹的数量。

  5. 如何实现有上限的子弹连发?
    解决:创建一个子弹容器Vector,容量为3,重绘时遍历集合,将1至3枚不断运动的子弹绘出。

  6. 子弹容器无法限制住连发很多子弹?
    解决:因为Vector会自动扩容!stupid!创建集合时是初始化容量,而非限制容量,应该手动写出容量限制逻辑的代码,例如只有集合中的子弹全部遇到边界销毁时才能发射新的子弹。

  7. 坦克发射子弹时便不能移动。如何做到坦克边走边射子弹?
    解决:(暂时搁置 大概是没学过的知识)

执行代码:

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
//Shot子弹类
public class Shot implements Runnable {
private int x;
private int y;
private int direct = 0;
private int speed = 10;
private boolean isLive = true;
@Override
public void run() {
while (isLive) {
switch (direct) {
case 0:
y -= speed;
break;
case 1:
y += speed;
break;
case 2:
x -= speed;
break;
case 3:
x += speed;
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}//判断子弹是否存活
if (!(x >= 0 && x <= 1000 && y >= 0 && y <= 750)) {
isLive = false;
break;
}
}
}//省略构造器 getter和setter...
}
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
//Tank类新增代码
public class Tank {
private int x;
private int y;
private int direct = 0;//0上1下2左3右
private int speed = 1;
private Vector<Shot> shots = new Vector<>();
//射击行为
public void shoot() {
//判断是否达到容量上限
if (isFull())
return;
switch (getDirect()) {
case 0:
shots.add(new Shot(x + 20, y, 0));
break;
case 1:
shots.add(new Shot(x + 20, y + 60, 1));
break;
case 2:
shots.add(new Shot(x, y + 20, 2));
break;
case 3:
shots.add(new Shot(x + 60, y + 20, 3));
break;
}
//启动shot线程
for (Shot shot : shots) {
new Thread(shot).start();
}
}
//判断容量并且删除出边界子弹
public boolean isFull() {
for (Shot shot : shots) {
if (!shot.isLive())
shots.remove(shot);
}
if (shots.size() == 5)
return true;
return false;
}
}
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
//MyPanel新增代码
@Override
public void paint(Graphics g) {
...
drawShots(hero, g);
}
//画出我方发射子弹
public void drawShots(Hero hero, Graphics g) {
for (Shot shot : hero.getShots()) {
if (shot != null) {
g.fill3DRect(shot.getX(), shot.getY(), 3, 3, false);
}
}
}
//按下J键即发射子弹
@Override
public void keyPressed(KeyEvent e) {
int d = e.getKeyCode();
switch (d) {
...
case KeyEvent.VK_J:
hero.shoot();
break;
}
this.repaint();
}
//MyPanel需不断重绘才能画出子弹运动轨迹
@Override
public void run() {
while (true) {
this.repaint();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

3.2 敌方发射子弹与移动

image-20240528072506595

遇到问题:

  1. 怎么实现敌方不定时随机发射子弹?代码逻辑写在Enemy类内?
    解答:每个敌方坦克都是一个新的子线程,run方法里执行shoot过后休眠随机时间即可。

  2. 为什么敌方射过一波子弹后就停止了?
    解答:这个debug了好久也没搞懂原因。为了排除是随即休眠时间的影响,先改为固定休眠一秒,模仿Hero固定频率发射子弹,但是没改变,还是不知道哪里出错了。
    真的气笑了。找了那么久的代码逻辑bug硬是没找出来,最终把报错信息给GPT看,分分钟解决。原来是集合基础知识的缺漏。判断并删除无效子弹的isFull函数不能这样写。
    根据报错信息,问题出现在 Tank 类的 isFull() 方法中,这个方法使用了一个增强型 for 循环来遍历 shots 集合,而在这个循环中,可能会导致 ConcurrentModificationException 异常。此异常表示在迭代过程中,如果使用集合的方法改变了集合的结构(比如添加或删除元素),则会抛出这个异常。在你的代码中,在 isFull() 方法中,如果子弹的 isLive 属性为 false,则会删除这个子弹,这就可能导致集合结构发生变化,而当前的增强型 for 循环无法正确地处理这种情况。
    为了解决这个问题,可以考虑使用迭代器(Iterator)来遍历集合,因为迭代器是集合的一部分,它专门用于在迭代过程中对集合进行修改,避免了 ConcurrentModificationException 异常的发生。
    加深了对于迭代器的理解。原来这些不同的遍历方式确实有独特的用武之地。还有以后着重问问报错信息,不要无头无脑去改代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //原本错误代码
    public boolean isFull() {
    for (Shot shot : shots) {
    if (!shot.isLive())
    shots.remove(shot);
    }
    if (shots.size() == 5)
    return true;
    return false;
    }
    //修改后
    public boolean isFull() {
    Iterator<Shot> iterator = shots.iterator();
    while (iterator.hasNext()) {
    Shot shot = iterator.next();
    if (!shot.isLive()) {
    iterator.remove();//使用迭代器的remove方法删除元素
    }
    }
    return shots.size() == 5;
    }
  3. 敌方坦克自由移动时卡顿瞬移?
    解答:参考我方坦克的移动,每走一步都会有一定时间间隔,因此敌方也需要每走一步都间隔一定时间,所以需要开启射击和移动两个子线程,再放入run()里启动。
    还有,坦克移动范围限制时,不能简单的直接写上面板长宽,要结合坦克xy坐标与自身长度做出限制。很简单的,找个边界条件即可

  4. 敌方开启双线程自然实现移动发射,我方怎么做?是键盘监听只能一个的问题吗?
    解答:待定。。。

执行代码:

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
//Tank类
public class Tank {
private int x;
private int y;
private int direct = 0;//0上1下2左3右
private int speed = 1;
private boolean isLive = true;
private Vector<Shot> shots = new Vector<>();
//射击
public void shoot() {...}
//删除死亡子弹并判断容量
public boolean isFull() {
Iterator<Shot> iterator = shots.iterator();
while (iterator.hasNext()) {
Shot shot = iterator.next();
if (!shot.isLive()) {
iterator.remove(); //使用迭代器的remove方法删除元素
}
}
return shots.size() == 5;
}

//判断子弹是否击中对方
public boolean isHit(Tank tank) {//多态
int x = tank.getX();
int y = tank.getY();
Iterator<Shot> iterator = shots.iterator();
while (iterator.hasNext()) {
Shot shot = iterator.next();
switch (tank.getDirect()) {
case 0:
case 1:
if (shot.getX() >= x && shot.getX() <= x + 40 && shot.getY() >= y && shot.getY() <= y + 60) {
shot.setLive(false);
iterator.remove();
tank.setLive(false);
return true;
}
break;
case 2:
case 3:
if (shot.getX() >= x && shot.getX() <= x + 60 && shot.getY() >= y && shot.getY() <= y + 40) {
shot.setLive(false);
iterator.remove();
tank.setLive(false);
return true;
}
break;
}
}
return false;
}
//限制坦克移动范围
public void moveUp() {
if (y <= 0)
return;
y -= speed;
}
public void moveDown() {
if (y >= 690)
return;
y += speed;
}
public void moveLeft() {
if (x <= 0)
return;
x -= speed;
}
public void moveRight() {
if (x >= 940)
return;
x += speed;
}
}
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
//Enemy类
public class Enemy extends Tank implements Runnable {
public Enemy(int x, int y) {
super(x, y);
}
//随机移动
public void move() {
//随机改变坦克方向
setDirect((int) (Math.random() * 4));
int num = (int) (Math.random() * 100 + 50);//最小值时的最低步数
switch (getDirect()) {
case 0:
for (int i = 0; i < num; i++) {
moveUp();
extracted();
}
break;
case 1:
for (int i = 0; i < num; i++) {
moveDown();
extracted();
}
break;
case 2:
for (int i = 0; i < num; i++) {
moveLeft();
extracted();
}
break;
case 3:
for (int i = 0; i < num; i++) {
moveRight();
extracted();
}
break;
}
}
//控制时间
private static void extracted() {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//自由随机移动线程
private void moving() {
Thread moveThread = new Thread(() -> {
while (isLive()) {
move();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
moveThread.start();
}
//射击线程
private void shooting() {
Thread shootThread = new Thread(() -> {
while (isLive()) {
shoot();
try {
Thread.sleep((int) (Math.random() * 5000));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
shootThread.start();
}
@Override
public void run() {
shooting();
moving();
}
}
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
//MyPanel类
public class MyPanel extends JPanel implements KeyListener, Runnable {
private Hero hero;
private Vector<Enemy> enemies = new Vector<>();
private int enemyAmount = 3;

public MyPanel() {
//初始化我方坦克
hero = new Hero(500, 200);
hero.setSpeed(2);
//初始化敌方坦克
for (int i = 0; i < enemyAmount; i++) {
Enemy enemy = new Enemy(100 * (i + 1), 100);
enemy.setDirect(1);
enemy.setSpeed(1);
enemies.add(enemy);
}
//敌方开启自由射击
for (Enemy enemy : enemies) {
new Thread(enemy).start();
}
}

@Override
public void paint(Graphics g) {
super.paint(g);
//填充背景
g.fillRect(0, 0, 1000, 750);
//画出我方坦克
if (hero.isLive())
drawTank(hero.getX(), hero.getY(), g, hero.getDirect(), 0);
//画出敌方坦克
for (Enemy enemy : enemies) {
if (enemy != null && enemy.isLive())
drawTank(enemy.getX(), enemy.getY(), g, enemy.getDirect(), 1);
}
//画出我方发射子弹
drawShots(hero, g, 0);
//画出敌方发射子弹
for (Enemy enemy : enemies) {
drawShots(enemy, g, 1);
}
//判断子弹是否击中坦克
Iterator<Enemy> iterator = enemies.iterator();
while (iterator.hasNext()) {
Enemy enemy = iterator.next();
if (enemy.isHit(hero))
hero.setLive(false);
if (hero.isHit(enemy)) {
enemy.setLive(false);
iterator.remove();
}
}

}
//绘出子弹(注意颜色)
public void drawShots(Tank tank, Graphics g, int type) {
for (Shot shot : tank.getShots()) {
if (type == 0)
g.setColor(Color.yellow);
else
g.setColor(Color.cyan);
g.fill3DRect(shot.getX(), shot.getY(), 3, 3, false);
}
}
//画出坦克
public void drawTank(int x, int y, Graphics g, int direct, int type) {...}
//键盘监听
@Override
public void keyPressed(KeyEvent e) {...}
//开启重绘线程
@Override
public void run() {...}
}

3.3 爆炸特效

增加代码:

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
//Bomb类
public class Bomb implements Runnable {
private int x;
private int y;
private int life = 9;//生命值
private boolean isLive;

//减少生命值
public void LifeDown() {
if (life > 0)
life--;
else
isLive = false;
}
@Override
public void run() {
while (true) {
LifeDown();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (!isLive)
break;
}
}
//getter setter 构造器...
}
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
//MyPanel类
public class MyPanel extends JPanel implements KeyListener, Runnable {
private Hero hero;
private Vector<Enemy> enemies = new Vector<>();
private int enemyAmount = 3;
private Vector<Bomb> bombs = new Vector<>();
private Image image1 = ImageIO.read(getClass().getResourceAsStream("image/爆炸一.png"));
private Image image2 = ImageIO.read(getClass().getResourceAsStream("image/爆炸二.png"));
private Image image3 = ImageIO.read(getClass().getResourceAsStream("image/爆炸三.png"));

public MyPanel throws IOException() {...}

@Override
public void paint(Graphics g) {
super.paint(g);
//填充背景
g.fillRect(0, 0, 1000, 750);
//画出我方坦克
if (hero.isLive())
drawTank(hero.getX(), hero.getY(), g, hero.getDirect(), 0);
//画出敌方坦克
for (Enemy enemy : enemies) {
if (enemy != null && enemy.isLive())
drawTank(enemy.getX(), enemy.getY(), g, enemy.getDirect(), 1);
}
//画出我方发射子弹
drawShots(hero, g, 0);
//画出敌方发射子弹
for (Enemy enemy : enemies) {
drawShots(enemy, g, 1);
}
//判断子弹是否击中坦克
Iterator<Enemy> iterator = enemies.iterator();
while (iterator.hasNext()) {
Enemy enemy = iterator.next();
if (enemy.isHit(hero)) {
hero.setLive(false);
createBombs(hero.getX(), hero.getY());
}
if (hero.isHit(enemy)) {
enemy.setLive(false);
createBombs(enemy.getX(), enemy.getY());
iterator.remove();
}
}
//画出爆炸效果
Iterator<Bomb> iterator2 = bombs.iterator();
while (iterator2.hasNext()) {
Bomb bomb = iterator2.next();
new Thread(bomb);
if (bomb.getLife() > 6)
g.drawImage(image1, bomb.getX(), bomb.getY(), 60, 60, this);
else if (bomb.getLife() > 3)
g.drawImage(image2, bomb.getX(), bomb.getY(), 60, 60, this);
else
g.drawImage(image3, bomb.getX(), bomb.getY(), 60, 60, this);
if (!bomb.isLive())
iterator2.remove();
}

}
//绘出子弹(注意颜色)
public void drawShots(Tank tank, Graphics g, int type) {...}
//画出坦克
public void drawTank(int x, int y, Graphics g, int direct, int type) {...}
//键盘监听
@Override
public void keyPressed(KeyEvent e) {...}
//开启重绘线程
@Override
public void run() {...}
}
//初始化炸弹
public void createBombs(int x, int y) {
for (int i = 0; i < 3; i++) {
bombs.add(new Bomb(x, y));
}
}

四、防止坦克重叠

遇到问题:

  1. 看起来简单 实现太难了!粗略将坦克看作两个大正方形,怎么在判断重叠时停止move线程?直到对方移开再继续move?
    解答:要暂停 Enemy 类中的 move 子线程,可以通过设置一个标志来控制子线程的运行状态。你可以在 Enemy 类中添加一个标志,例如 isMoving,然后在 move 方法中根据这个标志来控制子线程的执行。当需要暂停子线程时,将 isMoving 设置为 false,子线程会在下一次迭代时检查到这个状态,并停止运行。
    因此逻辑就是:遍历集合中的敌方坦克,当两个坦克相遇时,其中一个坦克需要停止移动,静待另一个坦克的随即移动直到不再相遇,再继续move子线程。

  2. 勉强实现此功能,但是当两个坦克相遇时界面会卡住,子弹不再运动,所有坦克都无法移动包括玩家,且当玩家射击敌方坦克时也会卡住且直接报错。但是玩家被射中时正常爆炸。

    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
    @Override
    public void paint(Graphics g) {
    ...
    //暂停重叠敌方坦克
    for (int i = 0; i < enemyAmount; i++) {
    Enemy enemy = enemies.get(i);
    //此处是否还要判断存活或空?
    while (isMiss(enemy, i)) {
    enemy.setMissing(true);
    }
    enemy.setMissing(false);
    }
    }
    //判断敌方坦克是否重叠
    public boolean isMiss(Enemy enemy, int i) {
    int x = enemy.getX() + 30;
    int y = enemy.getY() + 30;
    for (int j = i + 1; j < enemyAmount; j++) {
    Enemy enemy1 = enemies.get(j);
    if (enemy1 != null && enemy1.isLive()) {
    int a = enemy1.getX() + 30;
    int b = enemy1.getY() + 30;
    if (Math.abs(a - x) <= 60 && Math.abs(b - y) <= 60)
    return true;
    }
    }
    return false;
    }

    不对,根本就不能这样写。虽然还是不知道上述代码哪里出错了,但是一开始我的思路就错了。如果其中一个坦克需要停止移动,静待另一个坦克的随即移动直到不再相遇,再继续move子线程的话,那么可以自由移动的坦克仍然可以移动至与静止坦克重叠。所以应该是两个坦克相撞则立即朝相反方向“弹开”才对。

  3. 逻辑差不多正确写好了,但是Enemy里还有一个子线程move,导致坦克有一定几率在重叠时仍然移动,如何解决?
    解答:加一个属性isMiss,在判断重叠时改为true,然后在moving子线程里加相应的逻辑,未重叠才能自由移动。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    private void moving() {
    moveThread = new Thread(() -> {
    while (isLive()) {
    if (!isMiss) //未重叠才能自由移动
    move();
    try {
    Thread.sleep(500);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    }
    });
    moveThread.start();
    }
  4. 大体功能实现了,但是代码逻辑还是有问题。坦克移动轨迹会很奇怪,而且即使没有重叠,轨迹也并不随机,且因为missTank时的moveUp与随机moving的速度不一。而且坦克会突然卡住不动。
    总之还是先放在后面再解决吧,总是出各种千奇百怪的bug,看来还是线程搞太多了。这个重叠问题搞不动了。之后在解决吧。

    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
    //重叠处理的Enemy代码
    public class Enemy extends Tank implements Runnable {
    private boolean isMiss = false;
    private Thread moveThread;
    private Thread shootThread;
    //判断是否重叠
    public void isMissing(int a, int b, int x, int y) {
    //判断是否重叠
    isMiss = !(Math.abs(a - x) <= 60 && Math.abs(b - y) <= 60);
    }
    //与对方重叠时反方向移动
    public void missMove(Enemy enemy) {
    int a = enemy.getX() + 30;
    int b = enemy.getY() + 30;
    int x = getX() + 30;
    int y = getY() + 30;
    //判断是否重叠
    isMissing(a, b, x, y);
    //注意此处若不重叠则无需进行方法
    if (isMiss)
    return;
    //己方坦克与对方坦克垂直与水平的相对位置
    int deltaX = x - a;
    int deltaY = y - b;
    //根据相对位置调整移动方向
    if (Math.abs(deltaX) > Math.abs(deltaY)) {
    if (deltaX > 0) {
    setDirect(3);
    moveRight();
    } else {
    setDirect(2);
    moveLeft();
    }
    } else {
    if (deltaY > 0) {
    // 己方坦克在对方坦克上方,向下移动
    setDirect(1); // 设置向下移动
    moveDown(); // 执行向下移动
    } else {
    // 己方坦克在对方坦克下方,向上移动
    setDirect(0); // 设置向上移动
    moveUp(); // 执行向上移动
    }
    }
    isMissing(a, b, x, y);
    }
    //随机移动
    public void move() {
    //随机改变坦克方向
    setDirect((int) (Math.random() * 4));
    int num = (int) (Math.random() * 100 + 50);//最小值时的最低步数
    switch (getDirect()) {
    case 0:
    for (int i = 0; i < num; i++) {
    moveUp();
    extracted();
    }
    break;
    case 1:
    for (int i = 0; i < num; i++) {
    moveDown();
    extracted();
    }
    break;
    case 2:
    for (int i = 0; i < num; i++) {
    moveLeft();
    extracted();
    }
    break;
    case 3:
    for (int i = 0; i < num; i++) {
    moveRight();
    extracted();
    }
    break;
    }
    }
    //控制时间
    private static void extracted() {
    try {
    Thread.sleep(20);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    }
    // 自由随机移动线程
    private void moving() {
    moveThread = new Thread(() -> {
    while (isLive()) {
    if (!isMiss) //未重叠才能自由移动
    move();
    try {
    Thread.sleep(500);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    }
    });
    moveThread.start();
    }
    //射击线程
    private void shooting() {
    shootThread = new Thread(() -> {
    while (isLive()) {
    shoot();
    try {
    Thread.sleep((int) (Math.random() * 5000));
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    }
    });
    shootThread.start();
    }
    @Override
    public void run() {
    shooting();
    moving();
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //重叠处理的部分MyPanel代码
    //防止坦克重叠
    public void missTank(){
    for (int i = 0; i < enemies.size(); i++) {
    Enemy enemy = enemies.get(i);
    for (int j = i+1; j < enemies.size(); j++) {
    Enemy enemy1 = enemies.get(j);
    enemy1.missMove(enemy);
    }
    }
    }

五、源代码

1
2
3
4
5
6
7
//APP
import java.io.IOException;
public class APP {
public static void main(String[] args) throws IOException {
new GameJFrame();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//GameJFrame
import javax.swing.*;
import java.io.IOException;
public class GameJFrame extends JFrame {
private MyPanel myPanel = null;
public GameJFrame() throws IOException {
myPanel = new MyPanel();
new Thread(myPanel).start();
this.add(myPanel);
this.addKeyListener(myPanel);//窗口监听面板发生的键盘事件
this.setSize(1000,750);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
}
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
//MyPanel
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;
import java.util.Iterator;
import java.util.Vector;

public class MyPanel extends JPanel implements KeyListener, Runnable {
private Hero hero;
private Vector<Enemy> enemies = new Vector<>();
private Vector<Bomb> bombs = new Vector<>();
private int enemyAmount = 3;
private Image image1 = ImageIO.read(getClass().getResourceAsStream("image/爆炸一.png"));
private Image image2 = ImageIO.read(getClass().getResourceAsStream("image/爆炸二.png"));
private Image image3 = ImageIO.read(getClass().getResourceAsStream("image/爆炸三.png"));

public MyPanel() throws IOException {
//初始化我方坦克
hero = new Hero(500, 200);
hero.setSpeed(2);
//初始化敌方坦克
for (int i = 0; i < enemyAmount; i++) {
Enemy enemy = new Enemy(100 * (i + 1), 100 * (i + 1));
enemy.setDirect(1);
enemy.setSpeed(1);
enemies.add(enemy);
}
//敌方开启自由射击
for (Enemy enemy : enemies) {
new Thread(enemy).start();
}
}

@Override
public void paint(Graphics g) {
super.paint(g);
//填充背景
g.fillRect(0, 0, 1000, 750);
//画出我方坦克
if (hero.isLive())
drawTank(hero.getX(), hero.getY(), g, hero.getDirect(), 0);
//画出敌方坦克
for (Enemy enemy : enemies) {
if (enemy != null && enemy.isLive())
drawTank(enemy.getX(), enemy.getY(), g, enemy.getDirect(), 1);
}
//画出我方发射子弹
drawShots(hero, g, 0);
//画出敌方发射子弹
for (Enemy enemy : enemies) {
drawShots(enemy, g, 1);
}
//判断子弹是否击中坦克
hitTank();
//画出爆炸效果
drawBombs(g);
}
//初始化炸弹
public void createBombs(int x, int y) {
for (int i = 0; i < 3; i++) {
bombs.add(new Bomb(x, y));
}
}
//子弹击中坦克
public void hitTank(){
Iterator<Enemy> iterator = enemies.iterator();
while (iterator.hasNext()) {
Enemy enemy = iterator.next();
if (enemy.isHit(hero)) {
hero.setLive(false);
createBombs(hero.getX(), hero.getY());
}
if (hero.isHit(enemy)) {
enemy.setLive(false);
createBombs(enemy.getX(), enemy.getY());
iterator.remove();
}
}
}

//画出爆炸效果
public void drawBombs(Graphics g) {
Iterator<Bomb> iterator2 = bombs.iterator();
while (iterator2.hasNext()) {
Bomb bomb = iterator2.next();
new Thread(bomb);
if (bomb.getLife() > 6)
g.drawImage(image1, bomb.getX(), bomb.getY(), 60, 60, this);
else if (bomb.getLife() > 3)
g.drawImage(image2, bomb.getX(), bomb.getY(), 60, 60, this);
else
g.drawImage(image3, bomb.getX(), bomb.getY(), 60, 60, this);
if (!bomb.isLive())
iterator2.remove();
}
}

public void drawShots(Tank tank, Graphics g, int type) {
for (Shot shot : tank.getShots()) {
if (type == 0)
g.setColor(Color.yellow);
else
g.setColor(Color.cyan);
g.fill3DRect(shot.getX(), shot.getY(), 3, 3, false);
}
}

public void drawTank(int x, int y, Graphics g, int direct, int type) {
//根据不同类型设置不同颜色
switch (type) {
case 0://Hero坦克
g.setColor(Color.yellow);
break;
case 1://Enemy坦克
g.setColor(Color.cyan);
break;
}
//根据不同方向绘制坦克
switch (direct) {
case 0://上
g.fill3DRect(x, y, 10, 60, false);
g.fill3DRect(x + 30, y, 10, 60, false);
g.fill3DRect(x + 10, y + 10, 20, 40, false);
g.fillOval(x + 10, y + 20, 20, 20);
g.drawLine(x + 20, y, x + 20, y + 30);
break;
case 1://下
g.fill3DRect(x, y, 10, 60, false);
g.fill3DRect(x + 30, y, 10, 60, false);
g.fill3DRect(x + 10, y + 10, 20, 40, false);
g.fillOval(x + 10, y + 20, 20, 20);
g.drawLine(x + 20, y + 30, x + 20, y + 60);
break;
case 2://左
g.fill3DRect(x, y, 60, 10, false);
g.fill3DRect(x, y + 30, 60, 10, false);
g.fill3DRect(x + 10, y + 10, 40, 20, false);
g.fillOval(x + 20, y + 10, 20, 20);
g.drawLine(x, y + 20, x + 30, y + 20);
break;
case 3://右
g.fill3DRect(x, y, 60, 10, false);
g.fill3DRect(x, y + 30, 60, 10, false);
g.fill3DRect(x + 10, y + 10, 40, 20, false);
g.fillOval(x + 20, y + 10, 20, 20);
g.drawLine(x + 30, y + 20, x + 60, y + 20);
break;
}
}

@Override
public void keyTyped(KeyEvent e) {
}

@Override
public void keyPressed(KeyEvent e) {
int d = e.getKeyCode();
switch (d) {
case KeyEvent.VK_W:
hero.setDirect(0);
hero.moveUp();
break;
case KeyEvent.VK_S:
hero.setDirect(1);
hero.moveDown();
break;
case KeyEvent.VK_A:
hero.setDirect(2);
hero.moveLeft();
break;
case KeyEvent.VK_D:
hero.setDirect(3);
hero.moveRight();
break;
case KeyEvent.VK_J:
hero.shoot();
break;
}
this.repaint();
}

@Override
public void keyReleased(KeyEvent e) {
}

@Override
public void run() {
while (true) {
this.repaint();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
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
//Tank
import java.util.Iterator;
import java.util.Vector;

public class Tank {
private int x;
private int y;
private int direct = 0;//0上1下2左3右
private int speed = 1;
private boolean isLive = true;
private Vector<Shot> shots = new Vector<>();
//射击
public void shoot() {
//判断是否达到容量上限
if (isFull())
return;
switch (getDirect()) {
case 0:
shots.add(new Shot(x + 20, y, 0));
break;
case 1:
shots.add(new Shot(x + 20, y + 60, 1));
break;
case 2:
shots.add(new Shot(x, y + 20, 2));
break;
case 3:
shots.add(new Shot(x + 60, y + 20, 3));
break;
}
//启动shot线程
for (Shot shot : shots) {
new Thread(shot).start();
}
}

//删除死亡子弹并判断容量
public boolean isFull() {
Iterator<Shot> iterator = shots.iterator();
while (iterator.hasNext()) {
Shot shot = iterator.next();
if (!shot.isLive()) {
iterator.remove(); //使用迭代器的remove方法删除元素
}
}
return shots.size() == 5;
}

//判断子弹是否击中对方
public boolean isHit(Tank tank) {
int x = tank.getX();
int y = tank.getY();
Iterator<Shot> iterator = shots.iterator();
while (iterator.hasNext()) {
Shot shot = iterator.next();
switch (tank.getDirect()) {
case 0:
case 1:
if (shot.getX() >= x && shot.getX() <= x + 40 && shot.getY() >= y && shot.getY() <= y + 60) {
shot.setLive(false);
iterator.remove();
tank.setLive(false);
return true;
}
break;
case 2:
case 3:
if (shot.getX() >= x && shot.getX() <= x + 60 && shot.getY() >= y && shot.getY() <= y + 40) {
shot.setLive(false);
iterator.remove();
tank.setLive(false);
return true;
}
break;
}
}
return false;
}

public void moveUp() {
if (y <= 0)
return;
y -= speed;
}

public void moveDown() {
if (y >= 690)
return;
y += speed;
}

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

public void moveRight() {
if (x >= 940)
return;
x += speed;
}

public Tank() {
}

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

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 getDirect() {
return direct;
}

public void setDirect(int direct) {
this.direct = direct;
}

public int getSpeed() {
return speed;
}

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

public Vector<Shot> getShots() {
return shots;
}

public void setShots(Vector<Shot> shots) {
this.shots = shots;
}

public boolean isLive() {
return isLive;
}

public void setLive(boolean live) {
isLive = live;
}
}
1
2
3
4
5
6
//Hero
public class Hero extends Tank{
public Hero(int x,int y){
super(x,y);
}
}
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
//Enemy
public class Enemy extends Tank implements Runnable {
private Thread moveThread;
private Thread shootThread;

public Enemy(int x, int y) {
super(x, y);
}

//随机移动
public void move() {
//随机改变坦克方向
setDirect((int) (Math.random() * 4));
int num = (int) (Math.random() * 100 + 50);//最小值时的最低步数
switch (getDirect()) {
case 0:
for (int i = 0; i < num; i++) {
moveUp();
extracted();
}
break;
case 1:
for (int i = 0; i < num; i++) {
moveDown();
extracted();
}
break;
case 2:
for (int i = 0; i < num; i++) {
moveLeft();
extracted();
}
break;
case 3:
for (int i = 0; i < num; i++) {
moveRight();
extracted();
}
break;
}
}

//控制时间
private static void extracted() {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

// 自由随机移动线程
private void moving() {
moveThread = new Thread(() -> {
while (isLive()) {
move();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
moveThread.start();
}

//射击线程
private void shooting() {
shootThread = new Thread(() -> {
while (isLive()) {
shoot();
try {
Thread.sleep((int) (Math.random() * 5000));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
shootThread.start();
}

@Override
public void run() {
shooting();
moving();
}

public Thread getMoveThread() {
return moveThread;
}

public void setMoveThread(Thread moveThread) {
this.moveThread = moveThread;
}

public Thread getShootThread() {
return shootThread;
}

public void setShootThread(Thread shootThread) {
this.shootThread = shootThread;
}
}
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
//Shot
public class Shot implements Runnable {
private int x;
private int y;
private int direct = 0;
private int speed = 10;
private boolean isLive = true;

public Shot() {
}

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

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 getSpeed() {
return speed;
}

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

@Override
public void run() {
while (isLive) {
switch (direct) {
case 0:
y -= speed;
break;
case 1:
y += speed;
break;
case 2:
x -= speed;
break;
case 3:
x += speed;
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (!(x >= 0 && x <= 1000 && y >= 0 && y <= 750)) {
isLive = false;
break;
}
}
}

public int getDirect() {
return direct;
}

public void setDirect(int direct) {
this.direct = direct;
}

public boolean isLive() {
return isLive;
}

public void setLive(boolean live) {
isLive = live;
}
}
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
//Bomb
public class Bomb implements Runnable {
private int x;
private int y;
private int life = 9;//生命值
private boolean isLive;

//减少生命值
public void LifeDown() {
if (life > 0)
life--;
else
isLive = false;
}

public Bomb() {
}

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

public Bomb(int x, int y, int life, boolean isLive) {
this(x, y);
this.life = life;
this.isLive = isLive;
}

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 getLife() {
return life;
}

public void setLife(int life) {
this.life = life;
}

public boolean isLive() {
return isLive;
}

public void setLive(boolean live) {
isLive = live;
}

@Override
public void run() {
while (true) {
LifeDown();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (!isLive)
break;
}
}
}