线程 - 应用到坦克大战
一、坦克大战3.0版本
1、增强功能
用户按下J键,我方坦克发射一颗子弹
2、思路
3、实现代码
Shot类
① 发射相当于一个子功能,且需要不断发射,所以当作一个线程 --> 实现Runnable
② 在run方法中是实现子弹移动–>需要子弹方向direct
③ 且需要不停地自动移动–>while(true)控制 -->在碰壁时退出循环
④ 必须休眠,不然会导致还没有重绘,子弹就碰壁销毁
//把发射作为一个线程
public class Shot implements Runnable{
int x; //子弹x坐标
int y; //子弹y坐标
int direct = 0; //子弹方向
int speed = 5; //子弹速度
boolean isAlive = true; //子弹是否存活
public Shot(int x,int y,int direct) {
this.x = x;
this.y = y;
this.direct = direct;
}
@Override
public void run() { //射击功能
while(true) {
//休眠 --> 不休眠会导致子弹在还没有重绘画布的时候就已经到达终点了(移动很快很快)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
switch (direct) {
case 0:
y -= speed;
break;
case 1:
x -= speed;
break;
case 2:
y += speed;
break;
case 3:
x += speed;
break;
}
//测试:输出子弹坐标
//System.out.println("子弹坐标x=" + x + " y=" + y);
//当子弹移动到边界时会销毁(线程退出)
if(!(x >= 0 && x <= 1000 && y >= 0 && y <= 750)){
isAlive = false;
break;
}
}
//System.out.println("子弹线程退出");
}
}
Hero类
① 发射功能由坦克调用–>写shotEnemy()方法–>创建Shot,并启动线程
② 创建对象需要输入坐标 --> 子弹坐标画图,由 和hero坐标的关系得到
public class Hero extends Tank {//自己的坦克
//创建一个shot对象(发射是由坦克调用的)
Shot shot = null;
public Hero(int x, int y) {
super(x, y);
}
//MyPanel按下J键时-->调用该方法
public void shotEnemy() {
//根据子弹方向创建shot对象
switch (getDirect()){
case 0:
shot = new Shot(getX() + 19, getY(), 0);
break;
case 1:
shot = new Shot(getX(), getY() + 19, 1);
break;
case 2:
shot = new Shot(getX() + 19, getY() + 59, 2);
break;
case 3:
shot = new Shot(getX() + 59, getY() + 19, 3);
break;
}
//启动shot线程
new Thread(shot).start();
}
}
MyPanel类
① 按下J键就得产生一个子弹 --> 在keyPressed方法中,检测J键,调用hero.shotEnemy()
② 需要在画布上绘制子弹 --> paint方法中绘制,但需要检测shot对象是否为空、shot对象是否被销毁
④ 子弹移动需要不停地重绘,但由于子弹是自主移动,并不是按下一个键就移动一次,所以不能靠keyPressed中的repaint方法重绘 --> 让MyPanel自动重绘 --> 让MyPanel成为一个线程,实现每隔50ms就重绘一次的功能
public class MyPanel extends JPanel implements KeyListener,Runnable {
//定义我方坦克
Hero hero = null;
//定义敌人坦克
Vector<Enemy> enemys = new Vector<>(); //因为敌人多线程,所以用Vector
int enemySize = 3; //敌人坦克个数
//构造器
public MyPanel() {
//初始化我方坦克
hero = new Hero(470,650);
hero.setSpeed(3);
//初始化敌方坦克
for (int i = 0; i < enemySize; i++) {
enemys.add(new Enemy((100 * (i+1)),0,2));
}
}
//绘制画布、调用绘制坦克的方法drawTank()
@Override
public void paint(Graphics g) {
super.paint(g);
g.fillRect(0,0,1000,750);//填充矩形,默认黑色
//画出我方坦克(使用自己封装的方法)
drawTank(hero.getX(), hero.getY(), g, hero.getDirect(), 0); //我方tank1
//画出敌人坦克
for (int i = 0; i < enemySize; i++) {
Enemy enemy = enemys.get(i);
drawTank(enemy.getX(), enemy.getY(),g,enemy.getDirect(),1);
}
//画出子弹(需要在按下J键-->创建了子弹对象并且子弹对象还存活才能画)
if(hero.shot != null && hero.shot.isAlive){
//g.fill3DRect(hero.shot.x, hero.shot.y , 2,2,false);
g.setColor(Color.cyan);
g.drawOval(hero.shot.x, hero.shot.y , 2,2 );
}
}
/**
*
* @param x 坦克的左上角x坐标
* @param y 坦克的左上角y坐标
* @param g 画笔
* @param direct 坦克方向(上下左右)
* @param type 坦克类型(自己、敌人。。。)
*/
//编写方法,画出tank
public void drawTank(int x, int y, Graphics g, int direct, int type) {
switch (type) {
case 0://我们的坦克
g.setColor(Color.cyan);
break;
case 1://敌人的坦克
g.setColor(Color.yellow);
break;
}
//根据tank方向,来绘制对应形状的tank(朝哪边走,头就朝向哪边)
//0、1、2、3:分别代表向上、向下、向左、向右
switch (direct) {
case 0://向上
g.fill3DRect(x,y,10,60,false);//画出tank左边的轮子
g.fill3DRect(x+30,y,10,60,false);//画出tank右边的轮子
g.fill3DRect(x+10, y+10, 20, 40, false); //画出tank中间的长方形炮体
g.fillOval(x+10, y+20, 20, 20); //画出tank中间的圆台
g.drawLine(x+19, y+30, x+19,y);
g.drawLine(x+20, y+30, x+20,y);
g.drawLine(x+21, y+30, x+21,y);
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 + 19, x, y + 19);//画出炮筒
g.drawLine(x + 30, y + 20, x, y + 20);//画出炮筒
g.drawLine(x + 30, y + 21, x, y + 21);//画出炮筒
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 + 19, y + 30, x + 19, y + 60);//画出炮筒
g.drawLine(x + 20, y + 30, x + 20, y + 60);//变粗
g.drawLine(x + 21, y + 30, x + 21, 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 + 19, x + 60, y + 19);//画出炮筒
g.drawLine(x + 30, y + 20, x + 60, y + 20);//变粗
g.drawLine(x + 30, y + 21, x + 60, y + 21);//变粗
break;
default:
System.out.println("暂时没有处理");
}
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_W) { //向上
//改变我方tank1方向
hero.setDirect(0);
hero.moveUp();
} else if(e.getKeyCode() == KeyEvent.VK_A){//向左
hero.setDirect(1);
hero.moveLeft();
} else if(e.getKeyCode() == KeyEvent.VK_S){//向下
hero.setDirect(2);
hero.moveDown();
} else if(e.getKeyCode() == KeyEvent.VK_D){//向右
hero.setDirect(3);
hero.moveRight();
}
if(e.getKeyCode() == KeyEvent.VK_J) {
hero.shotEnemy();
}
//让面板重绘
this.repaint();
}
@Override
public void keyReleased(KeyEvent e) {
}
//为了让MyPanel不停地重绘,需要将MyPanel变成一个线程,每隔一段时间就自己重绘
@Override
public void run() {
while(true) {
//每隔50ms就重新paint一次
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.repaint();
}
}
}
TankGame03类
① MyPanel成为了一个线程,需要由创建该线程的类进行启动 --> 在TankGame03中,new MyPanel的后面启动mp线程
public class TankGame03 extends JFrame {
//定义MyPanel
MyPanel mp = null;
public static void main(String[] args) {
TankGame03 tankGame01 = new TankGame03();
}
public TankGame03() {
mp = new MyPanel(); //画板,就是游戏区域
new Thread(mp).start(); //启动mp线程,不停重绘
this.add(mp);
this.setSize(1000,750);
this.addKeyListener(mp);//让JFrame 监听 mp事件
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
}
Enemy类和Tank类没有修改
Enemy类
public class Enemy extends Tank {
public Enemy(int x, int y,int direct) {
super(x, y);
super.setDirect(direct);
}
}
Tank类
public class Tank {
private int x;
private int y;
private int direct = 0 ; //坦克方向 0上 1左 2下 3右,初始化为向上
private int speed = 3; //坦克速度,初始化为3
public Tank(int x, int y) {
this.x = x;
this.y = y;
}
//上下左右移动
public void moveUp() {
y -= speed;
}
public void moveRight() {
x += speed;
}
public void moveDown() {
y += speed;
}
public void moveLeft() {
x -= speed;
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public int getDirect() {
return direct;
}
public void setDirect(int direct) {
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;
}
}
二、坦克大战4.0版本
1、增强功能
① 让敌人的坦克也能够发射子弹(可以有多颗子弹)
② 当我方坦克击中敌人坦克时候,敌人的坦克就消失,如果能做出爆炸效果更好;
③ 让敌人的坦克也可以自由随机的上下左右移动;
④ 控制我方的坦克和敌人的坦克在规定的范围移动。
2、思路及实现代码
1)敌人自动发子弹
Enemy类
① 敌人自带多个子弹 —> Enemy类中存放一个Shot的Vector集合
public class Enemy extends Tank {
Vector<Shot> shots = new Vector<>();
public Enemy(int x, int y,int direct) {
super(x, y);
super.setDirect(direct);
}
}
MyPanel类
① 子弹自动发射 —> 需要随着敌人坦克的创建而初始化 —> 敌人子弹初始化 —> 创建Shot线程 —> 存进shots集合中 —> 启动线程
② 子弹画出来 —> 子弹是和敌人坦克绑定的,有敌人坦克才有子弹 —> paint中的画敌人坦克代码块里画敌人子弹 —> 遍历shots集合
public class MyPanel extends JPanel implements KeyListener,Runnable {
//定义我方坦克
Hero hero = null;
//定义敌人坦克
Vector<Enemy> enemys = new Vector<>(); //因为敌人多线程,所以用Vector
int enemySize = 3; //敌人坦克个数
//构造器
public MyPanel() {
//初始化我方坦克
hero = new Hero(470,650);
hero.setSpeed(3);
//初始化敌方坦克和子弹
for (int i = 0; i < enemySize; i++) {
//初始化敌方坦克
Enemy enemy = new Enemy((100 * (i+1)),0,2);
enemys.add(enemy);
//初始化敌方子弹
Shot shot = new Shot(enemy.getX() + 19,enemy.getY() + 59,enemy.getDirect());
//添加到shots集合
enemy.shots.add(shot);
//自动启动shot线程
new Thread(shot).start();
}
}
//绘制画布、调用绘制坦克的方法drawTank()
@Override
public void paint(Graphics g) {
super.paint(g);
g.fillRect(0,0,1000,750);//填充矩形,默认黑色
//画出我方坦克(使用自己封装的方法)
drawTank(hero.getX(), hero.getY(), g, hero.getDirect(), 0); //我方tank1
//画出敌人坦克
for (int i = 0; i < enemySize; i++) {
Enemy enemy = enemys.get(i);
drawTank(enemy.getX(), enemy.getY(),g,enemy.getDirect(),1);
//画出敌人子弹
for (int j = 0; j < enemy.shots.size(); j++) {
Shot shot = enemy.shots.get(j);
if(shot.isAlive) {
g.drawOval(shot.x, shot.y,2,2);
} else {
enemy.shots.remove(shot);
}
}
}
//画出子弹(需要在按下J键-->创建了子弹对象并且子弹对象还存活才能画)
if(hero.shot != null && hero.shot.isAlive){
//g.fill3DRect(hero.shot.x, hero.shot.y , 2,2,false);
g.setColor(Color.cyan);
g.drawOval(hero.shot.x, hero.shot.y , 2,2 );
}
}
//...省略一些没有变化的代码
//为了让MyPanel不停地重绘,需要将MyPanel变成一个线程,每隔一段时间就自己重绘
@Override
public void run() {
while(true) {
//每隔50ms就重新paint一次
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.repaint();
}
}
}
2)我方子弹打到敌人后敌人消失
Enemy类
public class Enemy extends Tank {
boolean isAlive = true;
Vector<Shot> shots = new Vector<>();
public Enemy(int x, int y,int direct) {
super(x, y);
super.setDirect(direct);
}
}
MyPanel类
public class MyPanel extends JPanel implements KeyListener,Runnable {
//声明和初始化...
//绘制画布、调用绘制坦克的方法drawTank()
@Override
public void paint(Graphics g) {
//....其他paint
//画出敌人坦克
for (int i = 0; i < enemys.size(); i++) {
Enemy enemy = enemys.get(i);
if(enemy.isAlive){
drawTank(enemy.getX(), enemy.getY(),g,enemy.getDirect(),1);
//画出敌人子弹
for (int j = 0; j < enemy.shots.size(); j++) {
Shot shot = enemy.shots.get(j);
if(shot.isAlive) {
g.drawOval(shot.x, shot.y,2,2);
} else {
enemy.shots.remove(shot);
}
}
}
// else { //在这里移除enemy会导致整个画布跳动(立马重绘一次,残留的坦克就会闪烁),
// //所以在enemy的isAlive变化的敌方将它移除
// enemys.remove(enemy);
// }
}
}
public void hitEnemy(Shot shot , Enemy enemy) {
switch (enemy.getDirect()) {
case 0:
case 2: //上下
if(shot.x >= enemy.getX() && shot.x <= enemy.getX() + 40
&& shot.y >= enemy.getY() && shot.y <= enemy.getY() + 60) {
enemy.isAlive = false;
shot.isAlive = false;
enemys.remove(enemy);
}
break;
case 1:
case 3: //1和3代表左和右,是同一种情况
if(shot.x >= enemy.getX() && shot.x <= enemy.getX() + 60
&& shot.y >= enemy.getY() && shot.y <= enemy.getY() + 40) {
enemy.isAlive = false;
shot.isAlive = false;
enemys.remove(enemy);
}
break;
}
}
//为了让MyPanel不停地重绘,需要将MyPanel变成一个线程,每隔一段时间就自己重绘
@Override
public void run() {
while(true) {
//每隔50ms就重新paint一次
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.repaint();
if(hero.shot != null && hero.shot.isAlive){
for (int i = 0; i < enemys.size(); i++) {
hitEnemy(hero.shot, enemys.get(i));
}
}
}
}
}
3)敌人被击中产生爆炸效果
Bomb类
public class Bomb {
int x;
int y;
int life = 9;
boolean isAlive = true;
public Bomb(int x, int y) {
this.x = x;
this.y = y;
}
public void lifeDown(){
if(life <= 0) {
isAlive = false;
} else {
life--;
}
}
}
MyPanel类
public class MyPanel extends JPanel implements KeyListener,Runnable {
//...
//定义爆炸
Vector<Bomb> bombs = new Vector<>();
Image image1 = null;
Image image2 = null;
Image image3 = null;
//构造器
public MyPanel() {
//...
//初始化爆炸图片
image1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/baozha1.png"));
image2 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/baozha2.png"));
image3 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/baozha3.png"));
}
//绘制画布、调用绘制坦克的方法drawTank()
@Override
public void paint(Graphics g) {
//...
//画爆炸
for (int i = 0; i < bombs.size(); i++) {
//不休眠第一个会爆炸不出来,取图片太慢了应该
try {
Thread.sleep(55);
} catch (InterruptedException e) {
e.printStackTrace();
}
Bomb bomb = bombs.get(i);
if(bomb.isAlive) {
if(bomb.life > 6) {
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 if(bomb.life > 0){
g.drawImage(image3,bomb.x,bomb.y,60,60,this);
}
bomb.lifeDown();
if(bomb.life == 0) {
bombs.remove(bomb);
}
}
}
}
public void hitEnemy(Shot shot , Enemy enemy) {
switch (enemy.getDirect()) {
case 0:
case 2: //上下
if(shot.x >= enemy.getX() && shot.x <= enemy.getX() + 40
&& shot.y >= enemy.getY() && shot.y <= enemy.getY() + 60) {
enemy.isAlive = false;
shot.isAlive = false;
bombs.add(new Bomb(enemy.getX(), enemy.getY())); //要放在enemy移除前面,不然enemy会被回收
enemys.remove(enemy);
}
break;
case 1:
case 3: //1和3代表左和右,是同一种情况
if(shot.x >= enemy.getX() && shot.x <= enemy.getX() + 60
&& shot.y >= enemy.getY() && shot.y <= enemy.getY() + 40) {
enemy.isAlive = false;
shot.isAlive = false;
bombs.add(new Bomb(enemy.getX(), enemy.getY()));
enemys.remove(enemy);
}
break;
}
}
//...
}
4)让敌人的坦克可以随机自由地上下移动
Enemy类
public class Enemy extends Tank implements Runnable{
boolean isAlive = true;
Vector<Shot> shots = new Vector<>();
public Enemy(int x, int y,int direct) {
super(x, y);
super.setDirect(direct);
}
@Override
public void run() {
while(true) {
switch (getDirect()) {
case 0: //向上
for (int i = 0; i < 30; i++) { //移动30步再变化方向
moveUp();
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 1: //向左
for (int i = 0; i < 30; i++) { //移动30步再变化方向
moveLeft();
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 2: //向下
for (int i = 0; i < 30; i++) { //移动30步再变化方向
moveDown();
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 3: //向右
for (int i = 0; i < 30; i++) { //移动30步再变化方向
moveRight();
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
setDirect((int)(Math.random() * 4)); //方向随机设置
if(!isAlive) { //坦克不存在,则退出坦克线程
break;
}
}
}
}
MyPanel类
public class MyPanel extends JPanel implements KeyListener,Runnable {
//...
//构造器
public MyPanel() {
//...
//初始化敌方坦克和子弹
for (int i = 0; i < enemySize; i++) {
//初始化敌方坦克
Enemy enemy = new Enemy((100 * (i+1)),0,2);
enemys.add(enemy);
//启动敌方线程
new Thread(enemy).start();
//...
}
//...
}
//...
}
5)控制我方坦克和敌人坦克在规定的范围内移动
Enemy类
public class Enemy extends Tank implements Runnable{
//...
@Override
public void run() {
while(true) {
switch (getDirect()) {
case 0: //向上
for (int i = 0; i < 30; i++) { //移动30步再变化方向
if(getY() > 0){ //在画布范围内移动
moveUp();
}
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 1: //向左
for (int i = 0; i < 30; i++) { //移动30步再变化方向
if(getX() > 0) {
moveLeft();
}
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 2: //向下
for (int i = 0; i < 30; i++) { //移动30步再变化方向
if (getY() + 60 < 750) {
moveDown();
}
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 3: //向右
for (int i = 0; i < 30; i++) { //移动30步再变化方向
if(getX() + 60 < 1000) {
moveRight();
}
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
setDirect((int)(Math.random() * 4));
if(!isAlive) { //坦克不存在,则退出坦克线程
break;
}
}
}
}
MyPanel类
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_W) { //向上
//改变我方tank1方向
hero.setDirect(0);
if(hero.getY() > 0) { //控制移动范围
hero.moveUp();
}
} else if(e.getKeyCode() == KeyEvent.VK_A){//向左
hero.setDirect(1);
if(hero.getX() > 0){
hero.moveLeft();
}
} else if(e.getKeyCode() == KeyEvent.VK_S){//向下
hero.setDirect(2);
if(hero.getY() + 60 < 750) {
hero.moveDown();
}
} else if(e.getKeyCode() == KeyEvent.VK_D){//向右
hero.setDirect(3);
if(hero.getX() + 60 < 1000) {
hero.moveRight();
}
}
if(e.getKeyCode() == KeyEvent.VK_J) {
hero.shotEnemy();
}
//让面板重绘
this.repaint();
}
6)4.0完整版
package com.rxli.tankgame5;
Tank类
public class Tank {
private int x;
private int y;
private int direct = 0 ; //坦克方向 0上 1左 2下 3右,初始化为向上
private int speed = 3; //坦克速度,初始化为3
public Tank(int x, int y) {
this.x = x;
this.y = y;
}
//上下左右移动
public void moveUp() {
y -= speed;
}
public void moveRight() {
x += speed;
}
public void moveDown() {
y += speed;
}
public void moveLeft() {
x -= speed;
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public int getDirect() {
return direct;
}
public void setDirect(int direct) {
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;
}
}
Hero类
public class Hero extends Tank {//自己的坦克
boolean isAlive = true;
//创建一个shot对象(发射是由坦克调用的)
Shot shot = null;
public Hero(int x, int y) {
super(x, y);
}
//MyPanel按下J键时-->调用该方法
public void shotEnemy() {
//根据子弹方向创建shot对象
switch (getDirect()){
case 0:
shot = new Shot(getX() + 19, getY(), 0);
break;
case 1:
shot = new Shot(getX(), getY() + 19, 1);
break;
case 2:
shot = new Shot(getX() + 19, getY() + 59, 2);
break;
case 3:
shot = new Shot(getX() + 59, getY() + 19, 3);
break;
}
//启动shot线程
new Thread(shot).start();
}
}
Enemy类
public class Enemy extends Tank implements Runnable{
boolean isAlive = true;
Vector<Shot> shots = new Vector<>();
public Enemy(int x, int y,int direct) {
super(x, y);
super.setDirect(direct);
}
@Override
public void run() {
while(true) {
switch (getDirect()) {
case 0: //向上
for (int i = 0; i < 30; i++) { //移动30步再变化方向
if(getY() > 0){ //在画布范围内移动
moveUp();
}
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 1: //向左
for (int i = 0; i < 100; i++) { //移动100步再变化方向
if(getX() > 0) {
moveLeft();
}
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 2: //向下
for (int i = 0; i < 100; i++) { //移动100步再变化方向
if (getY() + 60 < 750) {
moveDown();
}
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 3: //向右
for (int i = 0; i < 100; i++) { //移动100步再变化方向
if(getX() + 60 < 1000) {
moveRight();
}
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
setDirect((int)(Math.random() * 4));
if(!isAlive) { //坦克不存在,则退出坦克线程
break;
}
}
}
}
Shot类
//把发射作为一个线程
public class Shot implements Runnable{
int x; //子弹x坐标
int y; //子弹y坐标
int direct = 0; //子弹方向
int speed = 5; //子弹速度
boolean isAlive = true; //子弹是否存活
public Shot(int x,int y,int direct) {
this.x = x;
this.y = y;
this.direct = direct;
}
@Override
public void run() { //射击功能
while(true) {
//休眠 --> 不休眠会导致子弹在还没有重绘画布的时候就已经到达终点了(移动很快很快)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
switch (direct) {
case 0:
y -= speed;
break;
case 1:
x -= speed;
break;
case 2:
y += speed;
break;
case 3:
x += speed;
break;
}
//测试:输出子弹坐标
//System.out.println("子弹坐标x=" + x + " y=" + y);
//当子弹移动到边界时会销毁(线程退出)
//当子弹碰到坦克就销毁 --> isAlive
if(!(x >= 0 && x <= 1000 && y >= 0 && y <= 750 && isAlive)){
isAlive = false;
break;
}
}
//System.out.println("子弹线程退出");
}
}
Bomb类
public class Bomb {
int x;
int y;
int life = 9;
boolean isAlive = true;
public Bomb(int x, int y) {
this.x = x;
this.y = y;
}
public void lifeDown(){
if(life <= 0) {
isAlive = false;
} else {
life--;
}
}
}
MyPanel类
public class MyPanel extends JPanel implements KeyListener,Runnable {
//定义我方坦克
Hero hero = null;
//定义敌人坦克
Vector<Enemy> enemys = new Vector<>(); //因为敌人多线程,所以用Vector
//定义爆炸
Vector<Bomb> bombs = new Vector<>();
Image image1 = null;
Image image2 = null;
Image image3 = null;
//敌人坦克个数
int enemySize = 3;
//构造器
public MyPanel() {
//初始化我方坦克
hero = new Hero(100,100);
hero.setSpeed(3);
//初始化敌方坦克和子弹
for (int i = 0; i < enemySize; i++) {
//初始化敌方坦克
Enemy enemy = new Enemy((100 * (i+1)),0,2);
enemys.add(enemy);
//启动敌方线程
new Thread(enemy).start();
//初始化敌方子弹
Shot shot = new Shot(enemy.getX() + 19,enemy.getY() + 59,enemy.getDirect());
//添加到shots集合
enemy.shots.add(shot);
//自动启动shot线程
new Thread(shot).start();
}
//初始化爆炸图片
image1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/baozha1.png"));
image2 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/baozha2.png"));
image3 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/baozha3.png"));
}
//绘制画布、调用绘制坦克的方法drawTank()
@Override
public void paint(Graphics g) {
super.paint(g);
g.fillRect(0,0,1000,750);//填充矩形,默认黑色
//画出我方坦克(使用自己封装的方法)
drawTank(hero.getX(), hero.getY(), g, hero.getDirect(), 0); //我方tank1
//画出子弹(需要在按下J键-->创建了子弹对象并且子弹对象还存活才能画)
if(hero.shot != null && hero.shot.isAlive){
g.setColor(Color.cyan);
g.drawOval(hero.shot.x, hero.shot.y , 2,2 );
}
//画出敌人坦克
for (int i = 0; i < enemys.size(); i++) {
Enemy enemy = enemys.get(i);
if(enemy.isAlive){
drawTank(enemy.getX(), enemy.getY(),g,enemy.getDirect(),1);
//画出敌人子弹
for (int j = 0; j < enemy.shots.size(); j++) {
Shot shot = enemy.shots.get(j);
if(shot.isAlive) {
g.drawOval(shot.x, shot.y,2,2);
} else {
enemy.shots.remove(shot);
}
}
}
// else { //在这里移除enemy会导致整个画布跳动(立马重绘一次,残留的坦克就会闪烁),
// //所以在enemy的isAlive变化的敌方将它移除
// enemys.remove(enemy);
// }
}
//画爆炸
for (int i = 0; i < bombs.size(); i++) {
//不休眠第一个会爆炸不出来,取图片太慢了应该
try {
Thread.sleep(55);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始画bomb");
Bomb bomb = bombs.get(i);
if(bomb.isAlive) {
if(bomb.life > 6) {
System.out.println("开始画最大的bomb");
g.drawImage(image1, bomb.x, bomb.y,60,60,this);
} else if(bomb.life > 3) {
System.out.println("开始画中间的bomb");
g.drawImage(image2,bomb.x,bomb.y,60,60,this);
} else if(bomb.life > 0){
System.out.println("开始画最小的bomb");
g.drawImage(image3,bomb.x,bomb.y,60,60,this);
}
bomb.lifeDown();
if(bomb.life == 0) {
bombs.remove(bomb);
}
}
}
}
/**
*
* @param x 坦克的左上角x坐标
* @param y 坦克的左上角y坐标
* @param g 画笔
* @param direct 坦克方向(上下左右)
* @param type 坦克类型(自己、敌人。。。)
*/
//编写方法,画出tank
public void drawTank(int x, int y, Graphics g, int direct, int type) {
switch (type) {
case 0://我们的坦克
g.setColor(Color.cyan);
break;
case 1://敌人的坦克
g.setColor(Color.yellow);
break;
}
//根据tank方向,来绘制对应形状的tank(朝哪边走,头就朝向哪边)
//0、1、2、3:分别代表向上、向下、向左、向右
switch (direct) {
case 0://向上
g.fill3DRect(x,y,10,60,false);//画出tank左边的轮子
g.fill3DRect(x+30,y,10,60,false);//画出tank右边的轮子
g.fill3DRect(x+10, y+10, 20, 40, false); //画出tank中间的长方形炮体
g.fillOval(x+10, y+20, 20, 20); //画出tank中间的圆台
g.drawLine(x+19, y+30, x+19,y);
g.drawLine(x+20, y+30, x+20,y);
g.drawLine(x+21, y+30, x+21,y);
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 + 19, x, y + 19);//画出炮筒
g.drawLine(x + 30, y + 20, x, y + 20);//画出炮筒
g.drawLine(x + 30, y + 21, x, y + 21);//画出炮筒
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 + 19, y + 30, x + 19, y + 60);//画出炮筒
g.drawLine(x + 20, y + 30, x + 20, y + 60);//变粗
g.drawLine(x + 21, y + 30, x + 21, 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 + 19, x + 60, y + 19);//画出炮筒
g.drawLine(x + 30, y + 20, x + 60, y + 20);//变粗
g.drawLine(x + 30, y + 21, x + 60, y + 21);//变粗
break;
default:
System.out.println("暂时没有处理");
}
}
public void hitEnemy(Shot shot , Enemy enemy) {
switch (enemy.getDirect()) {
case 0:
case 2: //上下
if(shot.x >= enemy.getX() && shot.x <= enemy.getX() + 40
&& shot.y >= enemy.getY() && shot.y <= enemy.getY() + 60) {
enemy.isAlive = false;
shot.isAlive = false;
bombs.add(new Bomb(enemy.getX(), enemy.getY())); //要放在enemy移除前面,不然enemy会被回收
enemys.remove(enemy);
}
break;
case 1:
case 3: //1和3代表左和右,是同一种情况
if(shot.x >= enemy.getX() && shot.x <= enemy.getX() + 60
&& shot.y >= enemy.getY() && shot.y <= enemy.getY() + 40) {
enemy.isAlive = false;
shot.isAlive = false;
bombs.add(new Bomb(enemy.getX(), enemy.getY()));
enemys.remove(enemy);
}
break;
}
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_W) { //向上
//改变我方tank1方向
hero.setDirect(0);
if(hero.getY() > 0) { //控制移动范围
hero.moveUp();
}
} else if(e.getKeyCode() == KeyEvent.VK_A){//向左
hero.setDirect(1);
if(hero.getX() > 0){
hero.moveLeft();
}
} else if(e.getKeyCode() == KeyEvent.VK_S){//向下
hero.setDirect(2);
if(hero.getY() + 60 < 750) {
hero.moveDown();
}
} else if(e.getKeyCode() == KeyEvent.VK_D){//向右
hero.setDirect(3);
if(hero.getX() + 60 < 1000) {
hero.moveRight();
}
}
if(e.getKeyCode() == KeyEvent.VK_J) {
hero.shotEnemy();
}
//让面板重绘
this.repaint();
}
@Override
public void keyReleased(KeyEvent e) {
}
//为了让MyPanel不停地重绘,需要将MyPanel变成一个线程,每隔一段时间就自己重绘
@Override
public void run() {
while(true) {
//每隔50ms就重新paint一次
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.repaint();
if(hero.shot != null && hero.shot.isAlive){
for (int i = 0; i < enemys.size(); i++) {
hitEnemy(hero.shot, enemys.get(i));
}
}
}
}
}
TankGame05类
package com.rxli.tankgame5;
import javax.swing.*;
public class TankGame05 extends JFrame {
//定义MyPanel
MyPanel mp = null;
public static void main(String[] args) {
TankGame05 tankGame01 = new TankGame05();
}
public TankGame05() {
mp = new MyPanel(); //画板,就是游戏区域
new Thread(mp).start(); //启动mp线程,不停重绘
this.add(mp);
this.setSize(1200,800); //Jrame框比MyPanel画板稍大一点
this.addKeyListener(mp);//让JFrame 监听 mp事件
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
}
三、坦克大战5.0版本
1、增加功能
2、思路及实现代码
1)我方坦克在发射的子弹消亡后,才能发射新的子弹
MyPanel类
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_W) { //向上
//改变我方tank1方向
hero.setDirect(0);
if(hero.getY() > 0) { //控制移动范围
hero.moveUp();
}
} else if(e.getKeyCode() == KeyEvent.VK_A){//向左
hero.setDirect(1);
if(hero.getX() > 0){
hero.moveLeft();
}
} else if(e.getKeyCode() == KeyEvent.VK_S){//向下
hero.setDirect(2);
if(hero.getY() + 60 < 750) {
hero.moveDown();
}
} else if(e.getKeyCode() == KeyEvent.VK_D){//向右
hero.setDirect(3);
if(hero.getX() + 60 < 1000) {
hero.moveRight();
}
}
if(e.getKeyCode() == KeyEvent.VK_J) {
//发射一颗子弹
if(hero.shot == null || !hero.shot.isAlive){
//shotEnemy(): 创建hero.shot线程,并启动
hero.shotEnemy();
}
}
//让面板重绘
this.repaint();
}
2)我方发射多颗子弹,且画板上最多只有5颗子弹
Hero类
public class Hero extends Tank {//自己的坦克
boolean isAlive = true;
//一发子弹
Shot shot = null;
//多发子弹集合
Vector<Shot> shots = new Vector<>();
public Hero(int x, int y) {
super(x, y);
}
//MyPanel按下J键时-->调用该方法
public void shotEnemy() {
//面板上最多有5个子弹
if(shots.size() == 5) {
return;
}
//根据子弹方向创建shot对象
switch (getDirect()){
case 0:
shot = new Shot(getX() + 19, getY(), 0);
break;
case 1:
shot = new Shot(getX(), getY() + 19, 1);
break;
case 2:
shot = new Shot(getX() + 19, getY() + 59, 2);
break;
case 3:
shot = new Shot(getX() + 59, getY() + 19, 3);
break;
}
//将shot对象加入shots集合
shots.add(shot);
//启动shot线程
new Thread(shot).start();
}
}
MyPanel类
@Override
public void paint(Graphics g) {
//画出我方子弹
for (int i = 0; i < hero.shots.size(); i++) {
Shot shot = hero.shots.get(i);
//创建了子弹对象并且子弹对象还存活才能画
if(shot.isAlive){
g.setColor(Color.cyan);
g.drawOval(shot.x, shot.y , 2,2 );
} else {
hero.shots.remove(shot);
}
}
}
/**
* 遍历所有子弹,再遍历所有坦克,判断是否打中
*/
public void hitEnemy() {
for (int i = 0; i < hero.shots.size(); i++) {
Shot shot = hero.shots.get(i);
if(shot != null && shot.isAlive){
for (int j = 0; j < enemys.size(); j++) {
singleHitEnemy(shot, enemys.get(j));
}
}
}
}
/**
* 传入一个子弹和一个坦克,判断这颗子弹是否打中坦克
* @param shot 单科子弹
* @param enemy 单个坦克
*/
public void singleHitEnemy(Shot shot , Enemy enemy) {
switch (enemy.getDirect()) {
case 0:
case 2: //上下
if(shot.x >= enemy.getX() && shot.x <= enemy.getX() + 40
&& shot.y >= enemy.getY() && shot.y <= enemy.getY() + 60) {
enemy.isAlive = false;
shot.isAlive = false;
bombs.add(new Bomb(enemy.getX(), enemy.getY())); //要放在enemy移除前面,不然enemy会被回收
enemys.remove(enemy);
}
break;
case 1:
case 3: //1和3代表左和右,是同一种情况
if(shot.x >= enemy.getX() && shot.x <= enemy.getX() + 60
&& shot.y >= enemy.getY() && shot.y <= enemy.getY() + 40) {
enemy.isAlive = false;
shot.isAlive = false;
bombs.add(new Bomb(enemy.getX(), enemy.getY()));
enemys.remove(enemy);
}
break;
}
}
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_W) { //向上
//改变我方tank1方向
hero.setDirect(0);
if(hero.getY() > 0) { //控制移动范围
hero.moveUp();
}
} else if(e.getKeyCode() == KeyEvent.VK_A){//向左
hero.setDirect(1);
if(hero.getX() > 0){
hero.moveLeft();
}
} else if(e.getKeyCode() == KeyEvent.VK_S){//向下
hero.setDirect(2);
if(hero.getY() + 60 < 750) {
hero.moveDown();
}
} else if(e.getKeyCode() == KeyEvent.VK_D){//向右
hero.setDirect(3);
if(hero.getX() + 60 < 1000) {
hero.moveRight();
}
}
if(e.getKeyCode() == KeyEvent.VK_J) {
// //发射一颗子弹
// if(hero.shot == null || !hero.shot.isAlive){
// //创建hero.shot线程,并启动
// hero.shotEnemy();
// }
hero.shotEnemy();
}
//让面板重绘
this.repaint();
}
//为了让MyPanel不停地重绘,需要将MyPanel变成一个线程,每隔一段时间就自己重绘
@Override
public void run() {
while(true) {
//每隔50ms就重新paint一次
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.repaint();
//判断画布上是否有其中一颗子弹打中其中一个坦克的情况
hitEnemy();
}
}
3)每辆敌人坦克可以发射2颗子弹
Enemy类
public class Enemy extends Tank implements Runnable{
boolean isAlive = true;
Vector<Shot> shots = new Vector<>();
Shot shot = null;
public Enemy(int x, int y,int direct) {
super(x, y);
super.setDirect(direct);
}
public void shotHero() {
//Enemy自动发射子弹
if(shots.size() < 2){ //在画布上敌人最多只能有2个子弹
//创建一个子弹
switch (getDirect()){
case 0:
shot = new Shot(getX() + 19, getY(), 0);
break;
case 1:
shot = new Shot(getX(), getY() + 19, 1);
break;
case 2:
shot = new Shot(getX() + 19, getY() + 59, 2);
break;
case 3:
shot = new Shot(getX() + 59, getY() + 19, 3);
break;
}
//添加到shot集合中
shots.add(shot);
//启动子弹线程
new Thread(shot).start();
}
}
public void move() {
switch (getDirect()) {
case 0: //向上
for (int i = 0; i < 30; i++) { //移动30步再变化方向
if(getY() > 0){ //在画布范围内移动
moveUp();
}
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 1: //向左
for (int i = 0; i < 30; i++) { //移动30步再变化方向
if(getX() > 0) {
moveLeft();
}
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 2: //向下
for (int i = 0; i < 30; i++) { //移动30步再变化方向
if (getY() + 60 < 750) {
moveDown();
}
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 3: //向右
for (int i = 0; i < 30; i++) { //移动30步再变化方向
if(getX() + 60 < 1000) {
moveRight();
}
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
setDirect((int)(Math.random() * 4));
}
@Override
public void run() {
while(true) {
if(!isAlive) { //坦克不存在,则退出坦克线程
break;
}
//Enemy自动发射子弹 --> 由shots.size()控制敌人子弹数
shotHero();
//Enemy坦克自动移动 --> move包含休眠
move();
}
}
}
MyPanel类
//构造器
public MyPanel() {
//初始化敌方坦克
for (int i = 0; i < enemySize; i++) {
//初始化敌方坦克
Enemy enemy = new Enemy((100 * (i+1)),0,2);
enemys.add(enemy);
//启动敌方线程
new Thread(enemy).start();
}
//绘制画布、调用绘制坦克的方法drawTank()
@Override
public void paint(Graphics g) {
//画出敌人坦克
for (int i = 0; i < enemys.size(); i++) {
Enemy enemy = enemys.get(i);
//如果敌人存活
if(enemy.isAlive){
drawTank(enemy.getX(), enemy.getY(),g,enemy.getDirect(),1);
//画出敌人子弹
for (int j = 0; j < enemy.shots.size(); j++) {
Shot shot = enemy.shots.get(j);
if(shot.isAlive) {
g.drawOval(shot.x, shot.y,2,2);
} else {
enemy.shots.remove(shot);
}
}
}
}
}
4)当敌人的坦克击中我方坦克时,我方坦克消失,并出现爆炸效果
MyPanel类
@Override
public void paint(Graphics g) {
super.paint(g);
g.fillRect(0,0,1000,750);//填充矩形,默认黑色
//画出我方坦克(使用自己封装的方法)
if(hero != null && hero.isAlive){
drawTank(hero.getX(), hero.getY(), g, hero.getDirect(), 0); //我方tank1
}
}
/**
* 遍历每一辆坦克,再遍历每一辆坦克的每一颗子弹,判断这些子弹是否打中hero
*/
public void hitHero() {
for (int i = 0; i < enemys.size(); i++) {
Enemy enemy = enemys.get(i);
for (int j = 0; j < enemy.shots.size(); j++) {
Shot shot = enemy.shots.get(j);
if(shot.isAlive && enemy.isAlive) {
singleHit(shot , hero);
}
}
}
}
/**
* 传入一个子弹和一个坦克,判断这颗子弹是否打中坦克
* @param shot 单科子弹
* @param tank 单个坦克
*/
public void singleHit(Shot shot , Tank tank) {
switch (tank.getDirect()) {
case 0:
case 2: //上下
if(shot.x >= tank.getX() && shot.x <= tank.getX() + 40
&& shot.y >= tank.getY() && shot.y <= tank.getY() + 60) {
tank.setAlive(false); //属性没有动态绑定!不能用tank.isAlive = false; --> 这样只修改了Tank类的isAlive
//检验属性的绑定机制
System.out.println("被击中,tank.isAlive=" + tank.isAlive + " hero.isAlive=" + hero.isAlive);
shot.isAlive = false;
bombs.add(new Bomb(tank.getX(), tank.getY())); //要放在tank移除前面,不然tank会被回收
if(tank instanceof Enemy) { //被击中的是敌人,则移除敌人坦克
enemys.remove(tank);
}
}
break;
case 1:
case 3: //1和3代表左和右,是同一种情况
if(shot.x >= tank.getX() && shot.x <= tank.getX() + 60
&& shot.y >= tank.getY() && shot.y <= tank.getY() + 40) {
tank.setAlive(false);
shot.isAlive = false;
bombs.add(new Bomb(tank.getX(), tank.getY()));
if(tank instanceof Enemy) { //被击中的是敌人,则移除敌人坦克
enemys.remove(tank);
}
}
break;
}
}
@Override
public void run() {
while(true) {
//每隔50ms就重新paint一次
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.repaint();
//判断画布上是否有其中一颗子弹打中其中一个坦克的情况
hitEnemy();
//监测敌人是否打中我方
hitHero();
}
}
5)5.0完整版
Tank类
//Tank类
public class Tank {
private int x;
private int y;
private int direct = 0 ; //坦克方向 0上 1左 2下 3右,初始化为向上
private int speed = 3; //坦克速度,初始化为3
boolean isAlive = true;
public Tank(int x, int y) {
this.x = x;
this.y = y;
}
//上下左右移动
public void moveUp() {
y -= speed;
}
public void moveRight() {
x += speed;
}
public void moveDown() {
y += speed;
}
public void moveLeft() {
x -= speed;
}
//设置isAlive
public void setAlive(boolean isAlive) {
this.isAlive = isAlive;
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public int getDirect() {
return direct;
}
public void setDirect(int direct) {
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;
}
}
Hero类
public class Hero extends Tank {//自己的坦克
boolean isAlive = true; //是否存活
Shot shot = null;//一发子弹
Vector<Shot> shots = new Vector<>();//多发子弹集合
public Hero(int x, int y) {
super(x, y);
}
//设置isAlive
public void setAlive(boolean isAlive) {
this.isAlive = isAlive;
}
//MyPanel按下J键时-->调用该方法
public void shotEnemy() {
//面板上最多有5个子弹
if(shots.size() == 5) {
return;
}
//根据子弹方向创建shot对象
switch (getDirect()){
case 0:
shot = new Shot(getX() + 19, getY(), 0);
break;
case 1:
shot = new Shot(getX(), getY() + 19, 1);
break;
case 2:
shot = new Shot(getX() + 19, getY() + 59, 2);
break;
case 3:
shot = new Shot(getX() + 59, getY() + 19, 3);
break;
}
//将shot对象加入shots集合
shots.add(shot);
//启动shot线程
new Thread(shot).start();
}
}
Enemy类
public class Enemy extends Tank implements Runnable{
boolean isAlive = true;
Vector<Shot> shots = new Vector<>();
Shot shot = null;
public Enemy(int x, int y,int direct) {
super(x, y);
super.setDirect(direct);
}
//设置isAlive
public void setAlive(boolean isAlive) {
this.isAlive = isAlive;
}
public void shotHero() {
//Enemy自动发射子弹
if(shots.size() < 2){ //在画布上敌人最多只能有2个子弹
//创建一个子弹
switch (getDirect()){
case 0:
shot = new Shot(getX() + 19, getY(), 0);
break;
case 1:
shot = new Shot(getX(), getY() + 19, 1);
break;
case 2:
shot = new Shot(getX() + 19, getY() + 59, 2);
break;
case 3:
shot = new Shot(getX() + 59, getY() + 19, 3);
break;
}
//添加到shot集合中
shots.add(shot);
//启动子弹线程
new Thread(shot).start();
}
}
public void move() {
switch (getDirect()) {
case 0: //向上
for (int i = 0; i < 30; i++) { //移动30步再变化方向
if(getY() > 0){ //在画布范围内移动
moveUp();
}
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 1: //向左
for (int i = 0; i < 30; i++) { //移动30步再变化方向
if(getX() > 0) {
moveLeft();
}
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 2: //向下
for (int i = 0; i < 30; i++) { //移动30步再变化方向
if (getY() + 60 < 750) {
moveDown();
}
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 3: //向右
for (int i = 0; i < 30; i++) { //移动30步再变化方向
if(getX() + 60 < 1000) {
moveRight();
}
//休眠:每移动一次都休眠一下,让下一次移动能够显示在重绘后的画布上(画布每50ms重绘一次)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
setDirect((int)(Math.random() * 4));
}
@Override
public void run() {
while(true) {
if(!isAlive) { //坦克不存在,则退出坦克线程
break;
}
//Enemy自动发射子弹 --> 由shots.size()控制敌人子弹数
shotHero();
//Enemy坦克自动移动 --> move包含休眠
move();
}
}
}
Shot类
package com.rxli.tankgame5;
//把发射作为一个线程
public class Shot implements Runnable{
int x; //子弹x坐标
int y; //子弹y坐标
int direct = 0; //子弹方向
int speed = 5; //子弹速度
boolean isAlive = true; //子弹是否存活
public Shot(int x,int y,int direct) {
this.x = x;
this.y = y;
this.direct = direct;
}
@Override
public void run() { //射击功能
while(true) {
//休眠 --> 不休眠会导致子弹在还没有重绘画布的时候就已经到达终点了(移动很快很快)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
switch (direct) {
case 0:
y -= speed;
break;
case 1:
x -= speed;
break;
case 2:
y += speed;
break;
case 3:
x += speed;
break;
}
//测试:输出子弹坐标
//System.out.println("子弹坐标x=" + x + " y=" + y);
//当子弹移动到边界时会销毁(线程退出)
//当子弹碰到坦克就销毁 --> isAlive
if(!(x >= 0 && x <= 1000 && y >= 0 && y <= 750 && isAlive)){
isAlive = false;
break;
}
}
//System.out.println("子弹线程退出");
}
}
Bomb类
public class Bomb {
int x;
int y;
int life = 9;
boolean isAlive = true;
public Bomb(int x, int y) {
this.x = x;
this.y = y;
}
public void lifeDown(){
if(life <= 0) {
isAlive = false;
} else {
life--;
}
}
}
MyPanel类
public class MyPanel extends JPanel implements KeyListener,Runnable {
//定义我方坦克
Hero hero = null;
//定义敌人坦克
Vector<Enemy> enemys = new Vector<>(); //因为敌人多线程,所以用Vector
//定义爆炸
Vector<Bomb> bombs = new Vector<>();
Image image1 = null;
Image image2 = null;
Image image3 = null;
//敌人坦克个数
int enemySize = 3;
//构造器
public MyPanel() {
//初始化我方坦克
hero = new Hero(100,100);
hero.setSpeed(3);
//初始化敌方坦克
for (int i = 0; i < enemySize; i++) {
//初始化敌方坦克
Enemy enemy = new Enemy((100 * (i+1)),0,2);
enemys.add(enemy);
//启动敌方线程
new Thread(enemy).start();
}
//初始化爆炸图片
image1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/baozha1.png"));
image2 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/baozha2.png"));
image3 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/baozha3.png"));
}
//绘制画布、调用绘制坦克的方法drawTank()
@Override
public void paint(Graphics g) {
super.paint(g);
g.fillRect(0,0,1000,750);//填充矩形,默认黑色
//画出我方坦克(使用自己封装的方法)
if(hero != null && hero.isAlive){
drawTank(hero.getX(), hero.getY(), g, hero.getDirect(), 0); //我方tank1
}
//画出我方子弹
for (int i = 0; i < hero.shots.size(); i++) {
Shot shot = hero.shots.get(i);
//创建了子弹对象并且子弹对象还存活才能画
if(shot.isAlive){
g.setColor(Color.cyan);
g.drawOval(shot.x, shot.y , 2,2 );
} else {
hero.shots.remove(shot);
}
}
//画出敌人坦克
for (int i = 0; i < enemys.size(); i++) {
Enemy enemy = enemys.get(i);
if(enemy.isAlive){
drawTank(enemy.getX(), enemy.getY(),g,enemy.getDirect(),1);
//画出敌人子弹
for (int j = 0; j < enemy.shots.size(); j++) {
Shot shot = enemy.shots.get(j);
if(shot.isAlive) {
g.drawOval(shot.x, shot.y,2,2);
} else {
enemy.shots.remove(shot);
}
}
}
}
//画爆炸
for (int i = 0; i < bombs.size(); i++) {
//不休眠第一个会爆炸不出来,取图片太慢了应该
try {
Thread.sleep(55);
} catch (InterruptedException e) {
e.printStackTrace();
}
Bomb bomb = bombs.get(i);
if(bomb.isAlive) {
if(bomb.life > 6) {
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 if(bomb.life > 0){
g.drawImage(image3,bomb.x,bomb.y,60,60,this);
}
bomb.lifeDown();
if(bomb.life == 0) {
bombs.remove(bomb);
}
}
}
}
/**
*
* @param x 坦克的左上角x坐标
* @param y 坦克的左上角y坐标
* @param g 画笔
* @param direct 坦克方向(上下左右)
* @param type 坦克类型(自己、敌人。。。)
*/
//编写方法,画出tank
public void drawTank(int x, int y, Graphics g, int direct, int type) {
switch (type) {
case 0://我们的坦克
g.setColor(Color.cyan);
break;
case 1://敌人的坦克
g.setColor(Color.yellow);
break;
}
//根据tank方向,来绘制对应形状的tank(朝哪边走,头就朝向哪边)
//0、1、2、3:分别代表向上、向下、向左、向右
switch (direct) {
case 0://向上
g.fill3DRect(x,y,10,60,false);//画出tank左边的轮子
g.fill3DRect(x+30,y,10,60,false);//画出tank右边的轮子
g.fill3DRect(x+10, y+10, 20, 40, false); //画出tank中间的长方形炮体
g.fillOval(x+10, y+20, 20, 20); //画出tank中间的圆台
g.drawLine(x+19, y+30, x+19,y);
g.drawLine(x+20, y+30, x+20,y);
g.drawLine(x+21, y+30, x+21,y);
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 + 19, x, y + 19);//画出炮筒
g.drawLine(x + 30, y + 20, x, y + 20);//画出炮筒
g.drawLine(x + 30, y + 21, x, y + 21);//画出炮筒
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 + 19, y + 30, x + 19, y + 60);//画出炮筒
g.drawLine(x + 20, y + 30, x + 20, y + 60);//变粗
g.drawLine(x + 21, y + 30, x + 21, 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 + 19, x + 60, y + 19);//画出炮筒
g.drawLine(x + 30, y + 20, x + 60, y + 20);//变粗
g.drawLine(x + 30, y + 21, x + 60, y + 21);//变粗
break;
default:
System.out.println("暂时没有处理");
}
}
/**
* 遍历每一辆坦克,再遍历每一辆坦克的每一颗子弹,判断这些子弹是否打中hero
*/
public void hitHero() {
for (int i = 0; i < enemys.size(); i++) {
Enemy enemy = enemys.get(i);
for (int j = 0; j < enemy.shots.size(); j++) {
Shot shot = enemy.shots.get(j);
if(shot.isAlive && enemy.isAlive) {
singleHit(shot , hero);
}
}
}
}
/**
* 遍历所有子弹,再遍历所有坦克,判断是否打中
*/
public void hitEnemy() {
for (int i = 0; i < hero.shots.size(); i++) {
Shot shot = hero.shots.get(i);
if(shot != null && shot.isAlive){
for (int j = 0; j < enemys.size(); j++) {
singleHit(shot, enemys.get(j));
}
}
}
}
/**
* 传入一个子弹和一个坦克,判断这颗子弹是否打中坦克
* @param shot 单科子弹
* @param tank 单个坦克
*/
public void singleHit(Shot shot , Tank tank) {
switch (tank.getDirect()) {
case 0:
case 2: //上下
if(shot.x >= tank.getX() && shot.x <= tank.getX() + 40
&& shot.y >= tank.getY() && shot.y <= tank.getY() + 60) {
tank.setAlive(false); //属性没有动态绑定!不能用tank.isAlive = false; --> 这样只修改了Tank类的isAlive
//检验属性的绑定机制
System.out.println("被击中,tank.isAlive=" + tank.isAlive + " hero.isAlive=" + hero.isAlive);
shot.isAlive = false;
bombs.add(new Bomb(tank.getX(), tank.getY())); //要放在tank移除前面,不然tank会被回收
if(tank instanceof Enemy) { //被击中的是敌人,则移除敌人坦克
enemys.remove(tank);
}
}
break;
case 1:
case 3: //1和3代表左和右,是同一种情况
if(shot.x >= tank.getX() && shot.x <= tank.getX() + 60
&& shot.y >= tank.getY() && shot.y <= tank.getY() + 40) {
tank.setAlive(false);
shot.isAlive = false;
bombs.add(new Bomb(tank.getX(), tank.getY()));
if(tank instanceof Enemy) { //被击中的是敌人,则移除敌人坦克
enemys.remove(tank);
}
}
break;
}
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_W) { //向上
//改变我方tank1方向
hero.setDirect(0);
if(hero.getY() > 0) { //控制移动范围
hero.moveUp();
}
} else if(e.getKeyCode() == KeyEvent.VK_A){//向左
hero.setDirect(1);
if(hero.getX() > 0){
hero.moveLeft();
}
} else if(e.getKeyCode() == KeyEvent.VK_S){//向下
hero.setDirect(2);
if(hero.getY() + 60 < 750) {
hero.moveDown();
}
} else if(e.getKeyCode() == KeyEvent.VK_D){//向右
hero.setDirect(3);
if(hero.getX() + 60 < 1000) {
hero.moveRight();
}
}
if(e.getKeyCode() == KeyEvent.VK_J) {
// //发射一颗子弹
// if(hero.shot == null || !hero.shot.isAlive){
// //创建hero.shot线程,并启动
// hero.shotEnemy();
// }
hero.shotEnemy();
}
//让面板重绘
this.repaint();
}
@Override
public void keyReleased(KeyEvent e) {
}
//为了让MyPanel不停地重绘,需要将MyPanel变成一个线程,每隔一段时间就自己重绘
@Override
public void run() {
while(true) {
//每隔50ms就重新paint一次
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.repaint();
//判断画布上是否有其中一颗子弹打中其中一个坦克的情况
hitEnemy();
//监测敌人是否打中我方
hitHero();
}
}
}
TankGame05类
public class TankGame05 extends JFrame {
//定义MyPanel
MyPanel mp = null;
public static void main(String[] args) {
TankGame05 tankGame01 = new TankGame05();
}
public TankGame05() {
mp = new MyPanel(); //画板,就是游戏区域
new Thread(mp).start(); //启动mp线程,不停重绘
this.add(mp);
this.setSize(1200,800); //Jrame框比MyPanel画板稍大一点
this.addKeyListener(mp);//让JFrame 监听 mp事件
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
}