- 总体内容(增加的功能)
- 1. 敌方坦克发射子弹
- 思路
- 实现
- 2. 敌方坦克击中就消失
- 实现
- 3. 击中的爆炸效果
- 实现
- 4. 敌方坦克自由移动
- 思路
- 5. 控制坦克移动范围
- 实现
- 版本2.1的全部代码
- Tank类
- MyTank类
- EnemyTank类
- MyPanel类(面板类)
- Shoot类(子弹类)
- Bomb类(爆炸效果类)
- TankGame类(窗口类)
总体内容(增加的功能)
- 2.1版本包括
①敌方坦克发射子弹
②敌方坦克击中就消失
③击中的爆炸效果
④敌方坦克自由移动
⑤控制坦克移动范围
1. 敌方坦克发射子弹
思路 实现需求: 让敌人的坦克也能发射子弹(可以有多颗)
EnemyTank类和MyPanel类
- EnemyTank类中:①定义子弹线程的集合(因为允许一个敌方坦克发射多颗子弹) ②明白为什么不能在构造器中创建子弹线程(代码的注释里写了原因)
- MyPanel类类中:①修改:初始化敌方坦克后,创建+启动这个坦克的子弹线程,并把这个线程添加到自己的子弹集合中 ②修改:在画敌方坦克时,从每个坦克子弹集合中取出每颗子弹并绘制子弹
package com.wpz.tankgame;
import java.util.Vector;
public class EnemyTank extends Tank {
//千万注意要初始化这个Vector...不然会报空指针
Vector shoots = new Vector();//一个敌方坦克可以发射多颗子弹,所以这里用集合
public EnemyTank(int x, int y) {
super(x, y);
//每创建一个敌方坦克都分配一个子弹线程并启动=>把(创建+启动)线程 写到初始化敌人坦克后
// 不能在这个构造器中创建线程,因为初始化EnemyTank时,只给x,y赋值了,没给direction赋值,
// 而初始化子弹线程时,需要EnemyTank的方向,如果在这个类中创建线程,会报空指针异常
//也就是下面这两句:
//EnemyTank enemyTank = new EnemyTank(100 * (i + 1), 0);
//shoot = new Shoot(enemyTank.getX() + 20, enemyTank.getY() + 60, enemyTank.getDirection());
}
}
package com.wpz.tankgame;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Vector;
//为了让面板不停重绘子弹,让面板实现Runnable接口,当作一个线程使用(在run()中写重绘)
public class MyPanel extends JPanel implements KeyListener, Runnable {
MyTank myTank = null;//定义一个自己的坦克
Vector enemyTanks = new Vector<>();//定义敌人的坦克,放到Vector中
int enemyTankSize = 3;//敌人坦克的数量
public MyPanel() {
myTank = new MyTank(100, 100);//初始化自己的坦克
myTank.setSpeed(5);//设置坦克移动的速度
//初始化敌人的坦克(注意:使用循环来添加。因为敌人坦克数量多,不要一个一个add)
for (int i = 0; i < enemyTankSize; i++) {
// - 创建一个敌人的坦克
EnemyTank enemyTank = new EnemyTank(100 * (i + 1), 0);
// - 设置方向
enemyTank.setDirection(2);
// - 创建敌方坦克时,就为这个坦克(创建+启动)一个子弹线程,并把这个子弹线程添加到自己的子弹集合中
Shoot shoot = new Shoot(enemyTank.getX() + 20, enemyTank.getY() + 60, enemyTank.getDirection());
shoot.start();
enemyTank.shoots.add(shoot);
// - 加入
enemyTanks.add(enemyTank);
}
}
@Override
public void paint(Graphics g) {
super.paint(g);
g.fillRect(0, 0, 1000, 750);//绘图区域:填充矩形,默认是黑色
//画出自己的坦克->封装到画坦克的方法中
// - 对direction做了修改,把它放到了tank父类中(使用get()方法访问)
drawTank(myTank.getX(), myTank.getY(), g, myTank.getDirection(), 0);//画出我的坦克
//画敌人的坦克->遍历集合
for (int i = 0; i < enemyTanks.size(); i++) {
// - 取出坦克
EnemyTank enemyTank = enemyTanks.get(i);
drawTank(enemyTank.getX(), enemyTank.getY(), g, enemyTank.getDirection(), 1);
//画敌方坦克的子弹 => 画出敌方坦克时,也画出每个坦克的(多颗)子弹(遍历每隔坦克的子弹集合)
for (int j = 0; j < enemyTank.shoots.size(); j++) {
// - 取出子弹
Shoot shoot = enemyTank.shoots.get(j);
if (shoot.isLive) {
g.setColor(Color.white);
g.fillOval(shoot.x, shoot.y, 2, 2);
} else {
// - 如果子弹不存在,就把该子弹从Vector中移除
enemyTank.shoots.remove(j);
}
}
}
//画我方坦克的子弹=>如果子弹线程非空且子弹存在
if (myTank.shoot != null && myTank.shoot.isLive) {
g.setColor(Color.white);
g.fillOval(myTank.shoot.x, myTank.shoot.y, 2, 2);
}
}
public void drawTank(int x, int y, Graphics g, int direction, int type) {
//两种类型的坦克①我方②敌方 -> 不同类型的坦克,颜色不同
switch (type) {
case 0://我方
g.setColor(Color.CYAN);
break;
case 1://敌方
g.setColor(Color.YELLOW);
break;
}
//四种移动方向:不同移动方向使用画笔绘制的坦克是不同的
//direction:0:向上,1:向右,2:向下,3:向左
switch (direction) {
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, 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;
case 2://向下
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 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, y + 20);//画坦克的炮筒
break;
}
}
//事件处理方法(对按键进行监听)
@Override
public void keyPressed(KeyEvent e) {
//判断事件(当按下WDSA键时进行处理)
if (e.getKeyCode() == KeyEvent.VK_W) {//上
myTank.moveUp();//改变坦克的坐标(将改变坐标封装到父类的moveUp()方法中)
myTank.setDirection(0);//改变坦克的方向(将direction作为所有坦克的属性放到父类中)
} else if (e.getKeyCode() == KeyEvent.VK_D) {//右
myTank.moveRight();
myTank.setDirection(1);
} else if (e.getKeyCode() == KeyEvent.VK_S) {//下
myTank.moveDown();
myTank.setDirection(2);
} else if (e.getKeyCode() == KeyEvent.VK_A) {//左
myTank.moveLeft();
myTank.setDirection(3);
}
//按下J键时,我方坦克发射子弹
if (e.getKeyCode() == KeyEvent.VK_J) {
//- 出现的问题:
// -- 按一下J,只会调用一次keyPressed(),那面板只会重绘一次
// -- 因此按一下J,只能看到一个不会动小球
// -- 解决方法:让面板每隔100ms,自动重绘=>用多线程(面板实现Runnable接口)
myTank.shootEnemyTank();
}
//重绘
this.repaint();
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
//每隔100ms,重绘面板,使子弹移动
// - 要while不停的循环这些内容,不然线程只执行一次就退出了
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.repaint();
}
}
}
输出效果:
2. 敌方坦克击中就消失
实现需求:当我方坦克击中敌方坦克时,敌方坦克就消失,如果能做出爆炸效果更好
MyPanel类,EnemyTank类,Shoot类
- MyPanel类中:
① 增加一个方法hitTank(Shoot s,EnemyTank enemyTank),判断子弹是否击中坦克。因为坦克的方向不同,坦克所在的范围也不同,所以根据坦克的方向来判断=>子弹的x是否进入坦克的范围内,若进入,则将子弹isLive置false,坦克的isLive置false。这里没有直接遍历敌人坦克,没有直接获取我方坦克的子弹,而使用传参的方式:这样的处理使得这个方法更加灵活,使得方法还可以用于敌人坦克打我方坦克。这里没有将坦克从集合中移除而是用了isLive的方式:也是为了上面的原因,我方坦克不能使用集合方式来移除
② 修改:画敌方坦克时,增加判断。只有坦克存在的时候才画敌人坦克和敌人子弹。
③ 增加:Q:在哪里调用hitTank() 呢 A:在MyPanel的run()中,遍历坦克集合,判断我方坦克是否击中。不能在按下J键时调用,原因在注释里写了- EnemyTank类中:增加属性IsLive,表示坦克是否存在
- Shoot类中:修改线程结束的条件为=>碰到墙壁或击中坦克。解决了击中坦克时,子弹消失(不再绘制子弹s.isLive=false),但是线程还没结束的问题。
package com.wpz.tankgame;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Vector;
//为了让面板不停重绘子弹,让面板实现Runnable接口,当作一个线程使用(在run()中写重绘)
public class MyPanel extends JPanel implements KeyListener, Runnable {
MyTank myTank = null;//定义一个自己的坦克
Vector enemyTanks = new Vector<>();//定义敌人的坦克,放到Vector中
int enemyTankSize = 3;//敌人坦克的数量
public MyPanel() {
myTank = new MyTank(100, 100);//初始化自己的坦克
myTank.setSpeed(5);//设置坦克移动的速度
//初始化敌人的坦克(注意:使用循环来添加。因为敌人坦克数量多,不要一个一个add)
for (int i = 0; i < enemyTankSize; i++) {
// - 创建一个敌人的坦克
EnemyTank enemyTank = new EnemyTank(100 * (i + 1), 0);
// - 设置方向
enemyTank.setDirection(2);
// - 创建敌方坦克时,就为这个坦克(创建+启动)一个子弹线程,并把这个子弹线程添加到自己的子弹集合中
Shoot shoot = new Shoot(enemyTank.getX() + 20, enemyTank.getY() + 60, enemyTank.getDirection());
shoot.start();
enemyTank.shoots.add(shoot);
// - 加入
enemyTanks.add(enemyTank);
}
}
@Override
public void paint(Graphics g) {
super.paint(g);
g.fillRect(0, 0, 1000, 750);//绘图区域:填充矩形,默认是黑色
//画出自己的坦克->封装到画坦克的方法中
// - 对direction做了修改,把它放到了tank父类中(使用get()方法访问)
drawTank(myTank.getX(), myTank.getY(), g, myTank.getDirection(), 0);//画出我的坦克
//画敌人的坦克->遍历集合
for (int i = 0; i < enemyTanks.size(); i++) {
// - 取出坦克
EnemyTank enemyTank = enemyTanks.get(i);
// - 判断坦克是否存在,存在时才画这个坦克和它的子弹
if (enemyTank.isLive) {
drawTank(enemyTank.getX(), enemyTank.getY(), g, enemyTank.getDirection(), 1);
//画敌方坦克的子弹 => 画出敌方坦克时,也画出每个坦克的(多颗)子弹(遍历每隔坦克的子弹集合)
for (int j = 0; j < enemyTank.shoots.size(); j++) {
// - 取出子弹
Shoot shoot = enemyTank.shoots.get(j);
if (shoot.isLive) {// - 如果子弹存在就画
g.setColor(Color.white);
g.fillOval(shoot.x, shoot.y, 2, 2);
} else {
// - 如果子弹不存在,就把该子弹从Vector中移除
enemyTank.shoots.remove(j);
}
}
}
}
//画我方坦克的子弹=>如果子弹线程非空且子弹存在
if (myTank.shoot != null && myTank.shoot.isLive) {
g.setColor(Color.white);
g.fillOval(myTank.shoot.x, myTank.shoot.y, 2, 2);
}
}
public void drawTank(int x, int y, Graphics g, int direction, int type) {
//两种类型的坦克①我方②敌方 -> 不同类型的坦克,颜色不同
switch (type) {
case 0://我方
g.setColor(Color.CYAN);
break;
case 1://敌方
g.setColor(Color.YELLOW);
break;
}
//四种移动方向:不同移动方向使用画笔绘制的坦克是不同的
//direction:0:向上,1:向右,2:向下,3:向左
switch (direction) {
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, 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;
case 2://向下
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 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, y + 20);//画坦克的炮筒
break;
}
}
//判断是否击中,若击中则坦克消失(这里按判断我方击中敌方)
// - 什么时候判断我方坦克是否击中敌方坦克?=> 在MyPanel的run()中,每隔100ms判断一次。
// -- 开始时想到当按下J时判断,但是这样的只会判断一次,而且里面的内容(包括:子弹的位置和坦克的位置都只获取了一次),那这是永远判断不成功的
public void hitTank(Shoot s, EnemyTank enemyTank) {
// - 判断子弹s 是否击中坦克=>因为方向不同,坦克所在的范围也不同,所以这里用switch穿透来判断敌人坦克方向
switch (enemyTank.getDirection()) {
case 0://坦克向上
case 2://坦克向下
if (s.x > enemyTank.getX() && s.x < enemyTank.getX() + 40 &&
s.y > enemyTank.getY() && s.y < enemyTank.getY() + 60) {
s.isLive = false;//子弹消失
enemyTank.isLive = false;//敌人坦克消失
}
case 1://坦克向右
case 3://坦克向左
if (s.x > enemyTank.getX() && s.x < enemyTank.getX() + 60 &&
s.y > enemyTank.getY() && s.y < enemyTank.getY() + 40) {
s.isLive = false;//子弹消失
enemyTank.isLive = false;//敌人坦克消失
}
}
}
//事件处理方法(对按键进行监听)
@Override
public void keyPressed(KeyEvent e) {
//判断事件(当按下WDSA键时进行处理)
if (e.getKeyCode() == KeyEvent.VK_W) {//上
myTank.moveUp();//改变坦克的坐标(将改变坐标封装到父类的moveUp()方法中)
myTank.setDirection(0);//改变坦克的方向(将direction作为所有坦克的属性放到父类中)
} else if (e.getKeyCode() == KeyEvent.VK_D) {//右
myTank.moveRight();
myTank.setDirection(1);
} else if (e.getKeyCode() == KeyEvent.VK_S) {//下
myTank.moveDown();
myTank.setDirection(2);
} else if (e.getKeyCode() == KeyEvent.VK_A) {//左
myTank.moveLeft();
myTank.setDirection(3);
}
//按下J键时,我方坦克发射子弹
if (e.getKeyCode() == KeyEvent.VK_J) {
//- 出现的问题:
// -- 按一下J,只会调用一次keyPressed(),那面板只会重绘一次
// -- 因此按一下J,只能看到一个不会动小球
// -- 解决方法:让面板每隔100ms,自动重绘=>用多线程(面板实现Runnable接口)
myTank.shootEnemyTank();
}
//重绘
this.repaint();
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
//每隔100ms,重绘面板,使子弹移动
// - 要while不停的循环这些内容,不然线程只执行一次就退出了
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//- 判断是否击中敌人坦克
//-- 如果我方发射线程不为空且子弹存在,再判断是否击中
// 在调用run()之前,myTank已经进行了初始化,所以myTank不为空,但是shoot线程是在按下J键时创建的,
// 程序刚开始不按J键时,shoot为空,所以不写myTank.shoot != null这个条件,会报空指针异常
//- 遇到一个问题:子弹打到坦克时,子弹的isLive=false,所以子弹不再绘制,但是子弹的xy还在继续变化
// 这是因为在isLive=false时,发射线程并没有退出=>前面设置的是 子弹碰到墙壁后退出线程,所以要为退出线程增加一个条件
if (myTank.shoot != null && myTank.shoot.isLive) {
// -- 遍历敌方坦克,判断击中哪个
for (int i = 0; i < enemyTanks.size(); i++) {
EnemyTank enemyTank = enemyTanks.get(i);
hitTank(myTank.shoot, enemyTank);
}
}
this.repaint();
}
}
}
package com.wpz.tankgame;
import java.util.Vector;
public class EnemyTank extends Tank {
//千万注意要初始化这个Vector...不然会报空指针
Vector shoots = new Vector();//一个敌方坦克可以发射多颗子弹,所以这里用集合
boolean isLive = true;//敌人坦克是否存在
public EnemyTank(int x, int y) {
super(x, y);
//每创建一个敌方坦克都分配一个子弹线程并启动=>所以把(创建+启动)线程 写到初始化敌人坦克里面写
// 不能在这个构造器中创建线程,因为初始化EnemyTank时,只给x,y赋值了,没给direction赋值,
// 而初始化子弹线程时,需要EnemyTank的方向,如果在这个类中创建线程,会报空指针异常
//也就是下面这两句:
//EnemyTank enemyTank = new EnemyTank(100 * (i + 1), 0);
//shoot = new Shoot(enemyTank.getX() + 20, enemyTank.getY() + 60, enemyTank.getDirection());
}
}
package com.wpz.tankgame;
public class Shoot extends Thread {
int x;//子弹的横坐标
int y;//子弹的纵坐标
int direction;//子弹的方向
int speed = 4;//子弹发射的速度
boolean isLive = true;//子弹是否存在=>线程创建后子弹就存在,所以默认为true
//构造器:需要这三个属性是因为 这几个需要根据我方坦克的x,y和direction来定
public Shoot(int x, int y, int direction) {
this.x = x;
this.y = y;
this.direction = direction;
}
@Override
public void run() {
//该线程的任务是:
// ①控制子弹的移动(根据子弹的方向去移动)
// ②控制子弹是否存在
while (true) {
try {
Thread.sleep(50);//休眠一下,不然子弹一下子就打到墙上了
} catch (InterruptedException e) {
e.printStackTrace();
}
// - 根据方向改变x,y坐标
if (direction == 0) {//上
y -= speed;
} else if (direction == 1) {//右
x += speed;
} else if (direction == 2) {//下
y += speed;
} else if (direction == 3) {//左
x -= speed;
}
System.out.println("x = " + x + " y = " + y);//测试
//- 如果碰到墙壁,则线程结束(break),子弹消失(置false,面板中不绘制子弹)
//- 解决子弹打到坦克时,线程还未退出的问题:判断的条件增加isLive
//把正确条件取反 就得到了它反面的条件(正确条件为:没碰到墙壁且子弹存在)
if (!(x >= 0 && x <= 1000 && y >= 0 && y <= 750 && isLive)) {
isLive = false;//子弹消失
System.out.println("线程结束");
break;//线程结束
}
}
}
}
3. 击中的爆炸效果 实现
增加Bomb类,修改MyPanel类
- 准备三张图片放到out/production/项目下
- 新建Bomb爆炸类:①图片显示的位置xy ②控制图片逐个显示+显示的时间:life ③显示时间为0时,就让图片都不再绘制且把这个类从集合中移除:isLive=>具体的解释看代码注释
- MyPanel类中:①增加bombs集合(存放被击中坦克的爆炸效果),三张图片 ②在构造器中初始化这三张图片 ③在hitTank()中修改:如果击中坦克,则初始化一个Bomb对象放到bombs集合中 ④在paint()中:画出爆炸效果的三张图片=>注意只需要循环遍历bombs集合,不需要循环减少效果的生命值,因为run()中一直在重绘。⑤为了解决击中已死亡的坦克还能出现爆炸效果的问题:在hitTank()中,将已死亡坦克从集合中移除。
package com.wpz.tankgame;
public class Bomb {
int x, y;//爆炸效果图片的坐标
int life = 9;//爆炸效果的生命周期
boolean isLive = true;//是否存在(生命周期是否结束(<=0时结束))
public Bomb(int x, int y) {
this.x = x;
this.y = y;
}
//减少生命值,配合出现图片的爆炸效果,
//- 当生命值减少为0时,爆炸效果显示完毕,控制让炸弹不再显示isLive=false(在paint()中控制)
public void lifeDown() {
if (life > 0) {
life--;
} else {
isLive = false;//
}
}
}
package com.wpz.tankgame;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Vector;
//为了让面板不停重绘子弹,让面板实现Runnable接口,当作一个线程使用(在run()中写重绘)
public class MyPanel extends JPanel implements KeyListener, Runnable {
MyTank myTank = null;//定义一个自己的坦克
Vector enemyTanks = new Vector<>();//定义敌人的坦克,放到Vector中
int enemyTankSize = 3;//敌人坦克的数量
//定义炸弹,放到Vector中=>炸弹爆炸不属于坦克属性,属于面板的属性
//- 当子弹击中坦克时,加入一个Bomb对象到bombs:被击中的每个坦克都有一个Bomb对象,加入集合中统一管理
Vector bombs = new Vector<>();
//定义三张炸弹爆炸的图片,用于显示炸弹爆炸效果
Image image1 = null;
Image image2 = null;
Image image3 = null;
public MyPanel() {
myTank = new MyTank(100, 100);//初始化自己的坦克
myTank.setSpeed(5);//设置坦克移动的速度
//初始化敌人的坦克(注意:使用循环来添加。因为敌人坦克数量多,不要一个一个add)
for (int i = 0; i < enemyTankSize; i++) {
// - 创建一个敌人的坦克
EnemyTank enemyTank = new EnemyTank(100 * (i + 1), 0);
// - 设置方向
enemyTank.setDirection(2);
// - 创建敌方坦克时,就为这个坦克(创建+启动)一个子弹线程,并把这个子弹线程添加到自己的子弹集合中
Shoot shoot = new Shoot(enemyTank.getX() + 20, enemyTank.getY() + 60, enemyTank.getDirection());
shoot.start();
enemyTank.shoots.add(shoot);
// - 加入
enemyTanks.add(enemyTank);
}
//初始化三个爆炸的图片对象
image1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_1.gif"));
image2 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_2.gif"));
image3 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_3.gif"));
}
@Override
public void paint(Graphics g) {
super.paint(g);
g.fillRect(0, 0, 1000, 750);//绘图区域:填充矩形,默认是黑色
//画出自己的坦克->封装到画坦克的方法中
// - 对direction做了修改,把它放到了tank父类中(使用get()方法访问)
drawTank(myTank.getX(), myTank.getY(), g, myTank.getDirection(), 0);//画出我的坦克
//画敌人的坦克->遍历集合
for (int i = 0; i < enemyTanks.size(); i++) {
// - 取出坦克
EnemyTank enemyTank = enemyTanks.get(i);
// - 判断坦克是否存在,存在时才画这个坦克和它的子弹
if (enemyTank.isLive) {
drawTank(enemyTank.getX(), enemyTank.getY(), g, enemyTank.getDirection(), 1);
//画敌方坦克的子弹 => 画出敌方坦克时,也画出每个坦克的(多颗)子弹(遍历每隔坦克的子弹集合)
for (int j = 0; j < enemyTank.shoots.size(); j++) {
// - 取出子弹
Shoot shoot = enemyTank.shoots.get(j);
if (shoot.isLive) {// - 如果子弹存在就画
g.setColor(Color.white);
g.fillOval(shoot.x, shoot.y, 2, 2);
} else {
// - 如果子弹不存在,就把该子弹从Vector中移除
enemyTank.shoots.remove(j);
}
}
}
}
//画我方坦克的子弹=>如果子弹线程非空且子弹存在
if (myTank.shoot != null && myTank.shoot.isLive) {
g.setColor(Color.white);
g.fillOval(myTank.shoot.x, myTank.shoot.y, 2, 2);
}
//画子弹爆炸效果的图片
//- 遍历bombs集合,画出每个(不用判断是否为空,因为size=0时,循环0次)
for (int i = 0; i < bombs.size(); i++) {
//- 取出一个爆炸类(一个爆炸类可以控制三张图片的显示)
try {
Thread.sleep(50);//休眠50ms,不然执行速度太快,第一个坦克爆炸没有效果
} catch (InterruptedException e) {
e.printStackTrace();
}
Bomb bomb = bombs.get(i);
//- 因为run()中不断重绘,所以不停执行这个for循环,life是一直减少的
if (bomb.life > 6) {//- 不同的爆炸时期,画出不同的爆炸效果
//- bomb对象在hitTank()中初始化过了
g.drawImage(image1, bomb.x, bomb.y, 60, 60, this);
} else if (bomb.life > 3) {
g.drawImage(image2, bomb.x, bomb.y, 60, 60, this);
} else {
g.drawImage(image3, bomb.x, bomb.y, 60, 60, this);
}
bomb.lifeDown();//- 让图片显示的生命值减少
//- 在lifeDown()中设置了如果life=0,则isLive=false
//-- 当life=0时,爆炸效果显示完毕,那就把该bomb对象从集合中移除,下次就不会再遍历到他了
if (!(bomb.isLive)) {
bombs.remove(bomb);
}
}
}
public void drawTank(int x, int y, Graphics g, int direction, int type) {
//两种类型的坦克①我方②敌方 -> 不同类型的坦克,颜色不同
switch (type) {
case 0://我方
g.setColor(Color.CYAN);
break;
case 1://敌方
g.setColor(Color.YELLOW);
break;
}
//四种移动方向:不同移动方向使用画笔绘制的坦克是不同的
//direction:0:向上,1:向右,2:向下,3:向左
switch (direction) {
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, 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;
case 2://向下
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 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, y + 20);//画坦克的炮筒
break;
}
}
//判断是否击中,若击中则坦克消失(这里按判断我方击中敌方)
// - 什么时候判断我方坦克是否击中敌方坦克?=> 在MyPanel的run()中,每隔100ms判断一次。
// -- 开始时想到当按下J时判断,但是这样的只会判断一次,而且里面的内容(包括:子弹的位置和坦克的位置都只获取了一次),那这是永远判断不成功的
public void hitTank(Shoot s, EnemyTank enemyTank) {
// - 判断子弹s 是否击中坦克=>因为方向不同,坦克所在的范围也不同,所以这里用switch穿透来判断敌人坦克方向
switch (enemyTank.getDirection()) {
case 0://坦克向上
case 2://坦克向下
if (s.x > enemyTank.getX() && s.x < enemyTank.getX() + 40 &&
s.y > enemyTank.getY() && s.y < enemyTank.getY() + 60) {
s.isLive = false;//子弹消失
enemyTank.isLive = false;//敌人坦克消失
enemyTanks.remove(enemyTank);//从集合中移除被击中的坦克
//击中时,创建bomb对象,放入bombs集合中
Bomb bomb = new Bomb(enemyTank.getX(), enemyTank.getY());
bombs.add(bomb);
}
case 1://坦克向右
case 3://坦克向左
if (s.x > enemyTank.getX() && s.x < enemyTank.getX() + 60 &&
s.y > enemyTank.getY() && s.y < enemyTank.getY() + 40) {
s.isLive = false;//子弹消失
enemyTank.isLive = false;//敌人坦克消失
enemyTanks.remove(enemyTank);//从集合中移除被击中的坦克
//击中时,创建bomb对象,放入bombs集合中
Bomb bomb = new Bomb(enemyTank.getX(), enemyTank.getY());
bombs.add(bomb);
}
}
}
//事件处理方法(对按键进行监听)
@Override
public void keyPressed(KeyEvent e) {
//判断事件(当按下WDSA键时进行处理)
if (e.getKeyCode() == KeyEvent.VK_W) {//上
myTank.moveUp();//改变坦克的坐标(将改变坐标封装到父类的moveUp()方法中)
myTank.setDirection(0);//改变坦克的方向(将direction作为所有坦克的属性放到父类中)
} else if (e.getKeyCode() == KeyEvent.VK_D) {//右
myTank.moveRight();
myTank.setDirection(1);
} else if (e.getKeyCode() == KeyEvent.VK_S) {//下
myTank.moveDown();
myTank.setDirection(2);
} else if (e.getKeyCode() == KeyEvent.VK_A) {//左
myTank.moveLeft();
myTank.setDirection(3);
}
//按下J键时,我方坦克发射子弹
if (e.getKeyCode() == KeyEvent.VK_J) {
//- 出现的问题:
// -- 按一下J,只会调用一次keyPressed(),那面板只会重绘一次
// -- 因此按一下J,只能看到一个不会动小球
// -- 解决方法:让面板每隔100ms,自动重绘=>用多线程(面板实现Runnable接口)
myTank.shootEnemyTank();
}
//重绘
this.repaint();
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
//每隔100ms,重绘面板,使子弹移动
// - 要while不停的循环这些内容,不然线程只执行一次就退出了
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//- 判断是否击中敌人坦克
//-- 如果我方发射线程不为空且子弹存在,再判断是否击中
// 在调用run()之前,myTank已经进行了初始化,所以myTank不为空,但是shoot线程是在按下J键时创建的,
// 程序刚开始不按J键时,shoot为空,所以不写myTank.shoot != null这个条件,会报空指针异常
//- 遇到一个问题:子弹打到坦克时,子弹的isLive=false,所以子弹不再绘制,但是子弹的xy还在继续变化
// 这是因为在isLive=false时,发射线程并没有退出=>前面设置的是 子弹碰到墙壁后退出线程,所以要为退出线程增加一个条件
if (myTank.shoot != null && myTank.shoot.isLive) {
// -- 遍历敌方坦克,判断击中哪个
for (int i = 0; i < enemyTanks.size(); i++) {
EnemyTank enemyTank = enemyTanks.get(i);
hitTank(myTank.shoot, enemyTank);
}
}
this.repaint();
}
}
}
输出效果:
4. 敌方坦克自由移动
思路需求:让敌方坦克也可以自由随机的上下左右移动
体会线程的妙用!
思路补充一条:4. 在创建敌人坦克对象时,启动线程
### 实现
EnemyTank类和MyPanel类
- EnemyTank类:①实现Runnable接口=>敌人坦克类可以当作线程使用了 ②编写run()=>让坦克动起来
- MyPanel类中:初始化敌方坦克后,启动这个线程
package com.wpz.tankgame;
import java.util.Vector;
public class EnemyTank extends Tank implements Runnable {
//千万注意要初始化这个Vector...不然会报空指针
Vector shoots = new Vector();//一个敌方坦克可以发射多颗子弹,所以这里用集合
boolean isLive = true;//敌人坦克是否存在
public EnemyTank(int x, int y) {
super(x, y);
//每创建一个敌方坦克都分配一个子弹线程并启动=>所以把(创建+启动)线程 写到初始化敌人坦克里面写
// 不能在这个构造器中创建线程,因为初始化EnemyTank时,只给x,y赋值了,没给direction赋值,
// 而初始化子弹线程时,需要EnemyTank的方向,如果在这个类中创建线程,会报空指针异常
//也就是下面这两句:
//EnemyTank enemyTank = new EnemyTank(100 * (i + 1), 0);
//shoot = new Shoot(enemyTank.getX() + 20, enemyTank.getY() + 60, enemyTank.getDirection());
}
@Override
public void run() {
while (true) {
//思路:先向坦克的当前方向走30步=>然后转变方向
//- 向当前方向移动30步
switch (getDirection()) {
case 0://上
for (int i = 0; i < 30; i++) {
moveUp();
try {
Thread.sleep(50);//休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 1://右
for (int i = 0; i < 30; i++) {
moveRight();//使用父类中移动的方法
try {
Thread.sleep(50);//休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 2://下
for (int i = 0; i < 30; i++) {
moveDown();
try {
Thread.sleep(50);//休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 3://左
for (int i = 0; i < 30; i++) {
moveLeft();
try {
Thread.sleep(50);//休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
//- 转变方向(方向为0-3随机)
setDirection((int) (Math.random() * 4));
//- 注意!写并发程序,一定要考虑清楚,该线程什么时候结束
//-- 当坦克不再存在时,这个线程就结束了
if (!(isLive)) {
break;//退出线程
}
}
}
}
输出效果:
5. 控制坦克移动范围
实现需求:控制我方的坦克和敌人的坦克在规定的范围内移动
Tank类
- 因为我方坦克和敌方坦克都必须在窗口内移动,所以把判断条件放到move的方法中
- 每次移动时都判断坦克的坐标是否超出边界,若没有超,就可以继续移动
package com.wpz.tankgame;
public class Tank {
private int x;//坦克横坐标
private int y;//坦克纵坐标
//将坦克的方向写到父类中 这样任意坦克在不同方法中 都可以设置移动方向
private int direction;//坦克的方向(0:上,1:右,2:下,3:左)-->默认是0
private int speed = 2;//移动速度
public Tank(int x, int y) {
this.x = x;
this.y = y;
}
public int getDirection() {
return direction;
}
public void setDirection(int direction) {
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 getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
//将坦克移动时的坐标改变封装到 父类的方法中
//直接改变坐标的x/y值(取代myTank.setY(myTank.getY()-2);)
//先死后活:开始时坐标是加减固定值-->可以扩展成变化的--引入变量-->speed(可以在初始化坦克对象时赋值)
//- 让坦克(我方+敌方)都在规定的范围内(窗口内)移动
//-- 思路:先判断一下坦克当前的坐标是否超出边界,若没有超,就让坦克移动
public void moveUp() {
//判断坦克是否超出边界:若没超就移动
if (getY() > 0) {
y -= speed;
}
}
public void moveRight() {
if (getX() + 60 < 1000) {//本来是<1000的,如果坦克会超出一点边界就改成980
x += speed;
}
}
public void moveDown() {
if (getY() + 60 < 750) {//本来是750,如果会超改为710,把窗口放大就不会超了
y += speed;
}
}
public void moveLeft() {
if (getX() > 0) {
x -= speed;
}
}
}
版本2.1的全部代码 Tank类
package com.wpz.tankgame;
public class Tank {
private int x;//坦克横坐标
private int y;//坦克纵坐标
//将坦克的方向写到父类中 这样任意坦克在不同方法中 都可以设置移动方向
private int direction;//坦克的方向(0:上,1:右,2:下,3:左)-->默认是0
private int speed = 2;//移动速度
public Tank(int x, int y) {
this.x = x;
this.y = y;
}
public int getDirection() {
return direction;
}
public void setDirection(int direction) {
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 getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
//将坦克移动时的坐标改变封装到 父类的方法中
//直接改变坐标的x/y值(取代myTank.setY(myTank.getY()-2);)
//先死后活:开始时坐标是加减固定值-->可以扩展成变化的--引入变量-->speed(可以在初始化坦克对象时赋值)
//- 让坦克(我方+敌方)都在规定的范围内(窗口内)移动
//-- 思路:先判断一下坦克当前的坐标是否超出边界,若没有超,就让坦克移动
public void moveUp() {
//判断坦克是否超出边界:若没超就移动
if (getY() > 0) {
y -= speed;
}
}
public void moveRight() {
if (getX() + 60 < 1000) {//本来是<1000的,如果坦克会超出一点边界就改成980
x += speed;
}
}
public void moveDown() {
if (getY() + 60 < 750) {//本来是750,如果会超改为710,把窗口放大就不会超了
y += speed;
}
}
public void moveLeft() {
if (getX() > 0) {
x -= speed;
}
}
}
MyTank类
package com.wpz.tankgame;
public class MyTank extends Tank {
Shoot shoot;//子弹发射的线程
public MyTank(int x, int y) {
super(x, y);
}
public void shootEnemyTank() {//射击
//创建线程:根据坦克的位置和方向 来创建 子弹射击的线程
// - 判断坦克的方向,来创建线程
if (getDirection() == 0) {//上
shoot = new Shoot(getX() + 20, getY(), 0);
} else if (getDirection() == 1) {//右
shoot = new Shoot(getX() + 60, getY() + 20, 1);
} else if (getDirection() == 2) {//下
shoot = new Shoot(getX() + 20, getY() + 60, 2);
} else if (getDirection() == 3) {//左
shoot = new Shoot(getX(), getY() + 20, 3);
}
//启动线程
shoot.start();
}
}
EnemyTank类
package com.wpz.tankgame;
import java.util.Vector;
public class EnemyTank extends Tank implements Runnable {
//千万注意要初始化这个Vector...不然会报空指针
Vector shoots = new Vector();//一个敌方坦克可以发射多颗子弹,所以这里用集合
boolean isLive = true;//敌人坦克是否存在
public EnemyTank(int x, int y) {
super(x, y);
//每创建一个敌方坦克都分配一个子弹线程并启动=>所以把(创建+启动)线程 写到初始化敌人坦克里面写
// 不能在这个构造器中创建线程,因为初始化EnemyTank时,只给x,y赋值了,没给direction赋值,
// 而初始化子弹线程时,需要EnemyTank的方向,如果在这个类中创建线程,会报空指针异常
//也就是下面这两句:
//EnemyTank enemyTank = new EnemyTank(100 * (i + 1), 0);
//shoot = new Shoot(enemyTank.getX() + 20, enemyTank.getY() + 60, enemyTank.getDirection());
}
@Override
public void run() {
while (true) {
//思路:先向坦克的当前方向走30步=>然后转变方向
//- 向当前方向移动30步
switch (getDirection()) {
case 0://上
for (int i = 0; i < 30; i++) {
moveUp();//使用父类中移动的方法
try {
Thread.sleep(50);//休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 1://右
for (int i = 0; i < 30; i++) {
moveRight();
try {
Thread.sleep(50);//休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 2://下
for (int i = 0; i < 30; i++) {
moveDown();
try {
Thread.sleep(50);//休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 3://左
for (int i = 0; i < 30; i++) {
moveLeft();
try {
Thread.sleep(50);//休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
//- 转变方向(方向为0-3随机)
setDirection((int)(Math.random()*4));
//- 注意!写并发程序,一定要考虑清楚,该线程什么时候结束
//-- 当坦克不再存在时,这个线程就结束了
if (!(isLive)) {
break;//退出线程
}
}
}
}
MyPanel类(面板类)
package com.wpz.tankgame;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Vector;
//为了让面板不停重绘子弹,让面板实现Runnable接口,当作一个线程使用(在run()中写重绘)
public class MyPanel extends JPanel implements KeyListener, Runnable {
MyTank myTank = null;//定义一个自己的坦克
Vector enemyTanks = new Vector<>();//定义敌人的坦克,放到Vector中
int enemyTankSize = 3;//敌人坦克的数量
//定义炸弹,放到Vector中=>炸弹爆炸不属于坦克属性,属于面板的属性
//- 当子弹击中坦克时,加入一个Bomb对象到bombs:被击中的每个坦克都有一个Bomb对象,加入集合中统一管理
Vector bombs = new Vector<>();
//定义三张炸弹爆炸的图片,用于显示炸弹爆炸效果
Image image1 = null;
Image image2 = null;
Image image3 = null;
public MyPanel() {
myTank = new MyTank(100, 100);//初始化自己的坦克
myTank.setSpeed(5);//设置坦克移动的速度
//初始化敌人的坦克(注意:使用循环来添加。因为敌人坦克数量多,不要一个一个add)
for (int i = 0; i < enemyTankSize; i++) {
// - 创建一个敌人的坦克
EnemyTank enemyTank = new EnemyTank(100 * (i + 1), 0);
// - 设置方向
enemyTank.setDirection(2);
// - 创建敌方坦克时,就为这个坦克(创建+启动)一个子弹线程,并把这个子弹线程添加到自己的子弹集合中
Shoot shoot = new Shoot(enemyTank.getX() + 20, enemyTank.getY() + 60, enemyTank.getDirection());
shoot.start();
enemyTank.shoots.add(shoot);
// - 启动敌人坦克线程,让坦克动起来
new Thread(enemyTank).start();
// - 加入
enemyTanks.add(enemyTank);
}
//初始化三个爆炸的图片对象
image1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_1.gif"));
image2 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_2.gif"));
image3 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_3.gif"));
}
@Override
public void paint(Graphics g) {
super.paint(g);
g.fillRect(0, 0, 1000, 750);//绘图区域:填充矩形,默认是黑色
//画出自己的坦克->封装到画坦克的方法中
// - 对direction做了修改,把它放到了tank父类中(使用get()方法访问)
drawTank(myTank.getX(), myTank.getY(), g, myTank.getDirection(), 0);//画出我的坦克
//画敌人的坦克->遍历集合
for (int i = 0; i < enemyTanks.size(); i++) {
// - 取出坦克
EnemyTank enemyTank = enemyTanks.get(i);
// - 判断坦克是否存在,存在时才画这个坦克和它的子弹
if (enemyTank.isLive) {
drawTank(enemyTank.getX(), enemyTank.getY(), g, enemyTank.getDirection(), 1);
//画敌方坦克的子弹 => 画出敌方坦克时,也画出每个坦克的(多颗)子弹(遍历每隔坦克的子弹集合)
for (int j = 0; j < enemyTank.shoots.size(); j++) {
// - 取出子弹
Shoot shoot = enemyTank.shoots.get(j);
if (shoot.isLive) {// - 如果子弹存在就画
g.setColor(Color.white);
g.fillOval(shoot.x, shoot.y, 2, 2);
} else {
// - 如果子弹不存在,就把该子弹从Vector中移除
enemyTank.shoots.remove(j);
}
}
}
}
//画我方坦克的子弹=>如果子弹线程非空且子弹存在
if (myTank.shoot != null && myTank.shoot.isLive) {
g.setColor(Color.white);
g.fillOval(myTank.shoot.x, myTank.shoot.y, 2, 2);
}
//画子弹爆炸效果的图片
//- 遍历bombs集合,画出每个(不用判断是否为空,因为size=0时,循环0次)
for (int i = 0; i < bombs.size(); i++) {
//- 取出一个爆炸类(一个爆炸类可以控制三张图片的显示)
try {
Thread.sleep(50);//休眠50ms,不然执行速度太快,第一个坦克爆炸没有效果
} catch (InterruptedException e) {
e.printStackTrace();
}
Bomb bomb = bombs.get(i);
//- 因为run()中不断重绘,所以不停执行这个for循环,life是一直减少的
if (bomb.life > 6) {//- 不同的爆炸时期,画出不同的爆炸效果
//- bomb对象在hitTank()中初始化过了
g.drawImage(image1, bomb.x, bomb.y, 60, 60, this);
} else if (bomb.life > 3) {
g.drawImage(image2, bomb.x, bomb.y, 60, 60, this);
} else {
g.drawImage(image3, bomb.x, bomb.y, 60, 60, this);
}
bomb.lifeDown();//- 让图片显示的生命值减少
//- 在lifeDown()中设置了如果life=0,则isLive=false
//-- 当life=0时,爆炸效果显示完毕,那就把该bomb对象从集合中移除,下次就不会再遍历到他了
if (!(bomb.isLive)) {
bombs.remove(bomb);
}
}
}
public void drawTank(int x, int y, Graphics g, int direction, int type) {
//两种类型的坦克①我方②敌方 -> 不同类型的坦克,颜色不同
switch (type) {
case 0://我方
g.setColor(Color.CYAN);
break;
case 1://敌方
g.setColor(Color.YELLOW);
break;
}
//四种移动方向:不同移动方向使用画笔绘制的坦克是不同的
//direction:0:向上,1:向右,2:向下,3:向左
switch (direction) {
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, 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;
case 2://向下
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 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, y + 20);//画坦克的炮筒
break;
}
}
//判断是否击中,若击中则坦克消失(这里按判断我方击中敌方)
// - 什么时候判断我方坦克是否击中敌方坦克?=> 在MyPanel的run()中,每隔100ms判断一次。
// -- 开始时想到当按下J时判断,但是这样的只会判断一次,而且里面的内容(包括:子弹的位置和坦克的位置都只获取了一次),那这是永远判断不成功的
public void hitTank(Shoot s, EnemyTank enemyTank) {
// - 判断子弹s 是否击中坦克=>因为方向不同,坦克所在的范围也不同,所以这里用switch穿透来判断敌人坦克方向
switch (enemyTank.getDirection()) {
case 0://坦克向上
case 2://坦克向下
if (s.x > enemyTank.getX() && s.x < enemyTank.getX() + 40 &&
s.y > enemyTank.getY() && s.y < enemyTank.getY() + 60) {
s.isLive = false;//子弹消失
enemyTank.isLive = false;//敌人坦克消失
enemyTanks.remove(enemyTank);//从集合中移除被击中的坦克
//击中时,创建bomb对象,放入bombs集合中
Bomb bomb = new Bomb(enemyTank.getX(), enemyTank.getY());
bombs.add(bomb);
}
case 1://坦克向右
case 3://坦克向左
if (s.x > enemyTank.getX() && s.x < enemyTank.getX() + 60 &&
s.y > enemyTank.getY() && s.y < enemyTank.getY() + 40) {
s.isLive = false;//子弹消失
enemyTank.isLive = false;//敌人坦克消失
enemyTanks.remove(enemyTank);//从集合中移除被击中的坦克
//击中时,创建bomb对象,放入bombs集合中
Bomb bomb = new Bomb(enemyTank.getX(), enemyTank.getY());
bombs.add(bomb);
}
}
}
//事件处理方法(对按键进行监听)
@Override
public void keyPressed(KeyEvent e) {
//判断事件(当按下WDSA键时进行处理)
if (e.getKeyCode() == KeyEvent.VK_W) {//上
myTank.moveUp();//改变坦克的坐标(将改变坐标封装到父类的moveUp()方法中)
myTank.setDirection(0);//改变坦克的方向(将direction作为所有坦克的属性放到父类中)
} else if (e.getKeyCode() == KeyEvent.VK_D) {//右
myTank.moveRight();
myTank.setDirection(1);
} else if (e.getKeyCode() == KeyEvent.VK_S) {//下
myTank.moveDown();
myTank.setDirection(2);
} else if (e.getKeyCode() == KeyEvent.VK_A) {//左
myTank.moveLeft();
myTank.setDirection(3);
}
//按下J键时,我方坦克发射子弹
if (e.getKeyCode() == KeyEvent.VK_J) {
//- 出现的问题:
// -- 按一下J,只会调用一次keyPressed(),那面板只会重绘一次
// -- 因此按一下J,只能看到一个不会动小球
// -- 解决方法:让面板每隔100ms,自动重绘=>用多线程(面板实现Runnable接口)
myTank.shootEnemyTank();
}
//重绘
this.repaint();
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
//每隔100ms,重绘面板,使子弹移动
// - 要while不停的循环这些内容,不然线程只执行一次就退出了
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//- 判断是否击中敌人坦克
//-- 如果我方发射线程不为空且子弹存在,再判断是否击中
// 在调用run()之前,myTank已经进行了初始化,所以myTank不为空,但是shoot线程是在按下J键时创建的,
// 程序刚开始不按J键时,shoot为空,所以不写myTank.shoot != null这个条件,会报空指针异常
//- 遇到一个问题:子弹打到坦克时,子弹的isLive=false,所以子弹不再绘制,但是子弹的xy还在继续变化
// 这是因为在isLive=false时,发射线程并没有退出=>前面设置的是 子弹碰到墙壁后退出线程,所以要为退出线程增加一个条件
if (myTank.shoot != null && myTank.shoot.isLive) {
// -- 遍历敌方坦克,判断击中哪个
for (int i = 0; i < enemyTanks.size(); i++) {
EnemyTank enemyTank = enemyTanks.get(i);
hitTank(myTank.shoot, enemyTank);
}
}
this.repaint();
}
}
}
Shoot类(子弹类)
package com.wpz.tankgame;
public class Shoot extends Thread {
int x;//子弹的横坐标
int y;//子弹的纵坐标
int direction;//子弹的方向
int speed = 4;//子弹发射的速度
boolean isLive = true;//子弹是否存在=>线程创建后子弹就存在,所以默认为true
//构造器:需要这三个属性是因为 这几个需要根据我方坦克的x,y和direction来定
public Shoot(int x, int y, int direction) {
this.x = x;
this.y = y;
this.direction = direction;
}
@Override
public void run() {
//该线程的任务是:
// ①控制子弹的移动(根据子弹的方向去移动)
// ②控制子弹是否存在
while (true) {
try {
Thread.sleep(50);//休眠一下,不然子弹一下子就打到墙上了
} catch (InterruptedException e) {
e.printStackTrace();
}
// - 根据方向改变x,y坐标
if (direction == 0) {//上
y -= speed;
} else if (direction == 1) {//右
x += speed;
} else if (direction == 2) {//下
y += speed;
} else if (direction == 3) {//左
x -= speed;
}
// System.out.println("x = " + x + " y = " + y);//测试
//- 如果碰到墙壁,则线程结束(break),子弹消失(置false,面板中不绘制子弹)
//- 解决子弹打到坦克时,线程还未退出的问题:判断的条件增加isLive
//把正确条件取反 就得到了它反面的条件(正确条件为:没碰到墙壁且子弹存在)
if (!(x >= 0 && x <= 1000 && y >= 0 && y <= 750 && isLive)) {
isLive = false;//子弹消失
System.out.println(Thread.currentThread().getName()+"线程结束");
break;//线程结束
}
}
}
}
Bomb类(爆炸效果类)
package com.wpz.tankgame;
public class Bomb {
int x, y;//爆炸效果图片的坐标
int life = 9;//爆炸效果的生命周期
boolean isLive = true;//是否存在(生命周期是否结束(<=0时结束))
public Bomb(int x, int y) {
this.x = x;
this.y = y;
}
//减少生命值,配合出现图片的爆炸效果,
//- 当生命值减少为0时,爆炸效果显示完毕,控制让炸弹不再显示isLive=false(在paint()中控制)
public void lifeDown() {
if (life > 0) {
life--;
} else {
isLive = false;//
}
}
}
TankGame类(窗口类)
package com.wpz.tankgame;
import javax.swing.*;
public class TankGame extends JFrame {
private MyPanel mp = null;//定义面板
public static void main(String[] args) {
new TankGame();
}
public TankGame() {
this.mp = new MyPanel();
//启动线程mp的线程:重绘面板
Thread thread = new Thread(mp);
thread.start();
this.add(mp);
this.setSize(1000, 750);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
this.addKeyListener(mp);//为mp面板添加键盘监听器
}
}



