坦克大战进阶–发射子弹
1. 坦克大战0.3
1.1 分析
利用线程基础的知识,把坦克大战再次进阶一下:当我们按下J键,坦克就能够发射一颗子弹。
1.2 思路
- 当发射一颗子弹后,就相当于启动一个线程
- Mytank 有子弹的对象,当按下J时,我们就启动一个发射行为(线程),让子弹不停的移动,形成一个射击的效果
- 我们MyPanel需要不停的重绘子弹,才能出现该效果
- 当子弹移动到面板的边界时,就应该销毁(把启动的子弹的线程销毁)
1.3 代码实现
- 父类 Tank
public class Tank {
private int x;//坦克的横坐标
private int y;//坦克的纵坐标
//坦克的方向 0向上 1向右 2向下 3向左
private int direction;
//坦克的速度
private int speed = 1;
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
//添加上下左右移动方法
//向上
public void moveUp(){
y-=speed;
}
//向下
public void moveDown(){
y+=speed;
}
//向左
public void moveLeft(){
x-=speed;
}
//向右
public void moveRight(){
x+=speed;
}
public int getDirection() {
return direction;
}
public void setDirection(int direction) {
this.direction = direction;
}
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;
}
}
- 敌人坦克 EnemyTank
public class EnemyTank extends Tank{
public EnemyTank(int x, int y) {
super(x, y);
}
}
- 我们的坦克 MyTank
public class MyTank extends Tank {
//定义一个shot对象,表示一个射击行为(线程)
Shot shot = null;
public MyTank(int x, int y) {
super(x, y);
}
public void shotEnemyTank(){
//创建shot对象
switch (getDirection()){
case 0://向上
shot = new Shot(getX()+20,getY(),0);
break;
case 1://向右
shot = new Shot(getX() + 60, getY() + 20,1);
break;
case 2://向下
shot = new Shot(getX() + 20, getY() + 60,2);
break;
case 3://向左
shot = new Shot(getX(), getY() + 20,3);
break;
}
//启动我们的shot线程
new Thread(shot).start();
}
}
- 子弹 Shot
public class Shot implements Runnable {
//射击子弹
int x;//子弹x坐标
int y;//子弹y坐标
int direction;//子弹方向
int speed = 2;//子弹速度
boolean isLive = true;//子弹是否还存活
public Shot(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) {
throw new RuntimeException(e);
}
//根据方向来改变x,y坐标
switch (direction) {
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)) {
isLive = false;
break;
}
}
}
}
- 面板显示 MyPanel
//为了监听键盘事件,实现 KeyListener
//为了让panel不停的重绘,实现 Runnable,当做一个线程使用
public class MyPanel extends JPanel implements KeyListener, Runnable {
//定义我的坦克
MyTank myTank = null;
//定义敌人坦克,放入到 Vector
Vector<EnemyTank> enemyTanks = new Vector<>();
int enemyTanksize = 3;
public MyPanel() {
//初始化自己的坦克
myTank = new MyTank(100, 100);
//初始化敌人的坦克
for (int i = 0; i < enemyTanksize; i++) {
EnemyTank enemyTank = new EnemyTank((100 * (i + 1)), 0);
enemyTank.setDirection(2);
enemyTanks.add(enemyTank);
}
}
@Override
public void paint(Graphics g) {
super.paint(g);
//设置填充矩形,默认黑色
g.fillRect(0, 0, 1000, 750);
myTank.setSpeed(5);
//画出坦克-封装方法
//自己的坦克
drawTank(myTank.getX(), myTank.getY(), g, myTank.getDirection(), 1);
//画出自己的子弹
if (myTank.shot != null && myTank.shot.isLive == true) {
g.draw3DRect(myTank.shot.x, myTank.shot.y, 5, 5, true);
}
//敌人的坦克
for (int i = 0; i < enemyTanksize; i++) {
EnemyTank enemyTank = enemyTanks.get(i);
drawTank(enemyTank.getX(), enemyTank.getY(), g, enemyTank.getDirection(), 0);
}
}
//编写方法,画出坦克
/**
* @param x 坦克的左上角x坐标
* @param y 坦克的左上角y坐标
* @param g 画笔
* @param direction 坦克的方向
* @param type 坦克的类型
*/
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;
}
//根据坦克的方向,来绘制坦克
switch (direction) {
//0向上 1向右 2向下 3向左
case 0://默认方向向上
// 先画第一个矩形 大小 10*60
//坦克左边轮子
// 定点(x,y)
g.fill3DRect(x, y, 10, 60, false);
// 第二个矩形 大小 20*40
//坦克身体
// 定点(x+10,y+10)
g.fill3DRect(x + 10, y + 10, 20, 40, false);
// 第三个矩形 大小 10*60
//坦克右边轮子
// 定点(x+30,y)
g.fill3DRect(x + 30, y, 10, 60, false);
// 上面的圆盖子 大小 (20,20)
// 定点(x+10,y+20)
g.fillOval(x + 10, y + 20, 20, 20);
// 最后的炮管
// 定点1 (x+20,y)
// 定点2 (x+20,y+30)
g.drawLine(x + 20, y, x + 20, y + 30);
// 画出子弹
g.drawLine(x + 20, y, x + 20, y + 5);
break;
case 1://默认方向向右
// 先画第一个矩形 大小 60*10
//坦克上边轮子
g.fill3DRect(x, y, 60, 10, false);
// 第二个矩形 大小 40*20
//坦克身体
g.fill3DRect(x + 10, y + 10, 40, 20, false);
// 第三个矩形 大小 10*60
//坦克下边轮子
g.fill3DRect(x, y + 30, 60, 10, false);
// 上面的圆盖子 大小 (20,20)
g.fillOval(x + 20, y + 10, 20, 20);
// 最后的炮管
g.drawLine(x + 60, y + 20, x + 30, y + 20);
// 画出子弹
g.drawLine(x + 60, y + 20, x + 55, y + 20);
break;
case 2://默认方向向下
// 先画第一个矩形 大小 10*60
//坦克左边轮子
g.fill3DRect(x, y, 10, 60, false);
// 第二个矩形 大小 20*40
//坦克身体
g.fill3DRect(x + 10, y + 10, 20, 40, false);
// 第三个矩形 大小 10*60
//坦克右边轮子
g.fill3DRect(x + 30, y, 10, 60, false);
// 上面的圆盖子 大小 (20,20)
g.fillOval(x + 10, y + 20, 20, 20);
// 最后的炮管
g.drawLine(x + 20, y + 60, x + 20, y + 30);
// 画出子弹
g.drawLine(x + 20, y + 60, x + 20, y + 55);
break;
case 3://默认方向向左
// 先画第一个矩形 大小 60*10
//坦克上边轮子
g.fill3DRect(x, y, 60, 10, false);
// 第二个矩形 大小 40*20
//坦克身体
g.fill3DRect(x + 10, y + 10, 40, 20, false);
// 第三个矩形 大小 10*60
//坦克下边轮子
g.fill3DRect(x, y + 30, 60, 10, false);
// 上面的圆盖子 大小 (20,20)
g.fillOval(x + 20, y + 10, 20, 20);
// 最后的炮管
g.drawLine(x, y + 20, x + 30, y + 20);
// 画出子弹
g.drawLine(x, y + 20, x + 5, y + 20);
break;
default:
System.out.println("暂时不作处理");
}
}
@Override
public void keyTyped(KeyEvent e) {
}
//处理 wsad 按下的情况
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_W) {
//改变坦克的方向
myTank.setDirection(0);
myTank.moveUp();
} else if (e.getKeyCode() == KeyEvent.VK_S) {
myTank.setDirection(2);
myTank.moveDown();
} else if (e.getKeyCode() == KeyEvent.VK_A) {
myTank.setDirection(3);
myTank.moveLeft();
} else if (e.getKeyCode() == KeyEvent.VK_D) {
myTank.setDirection(1);
myTank.moveRight();
}
//如果用户按下J键,就是发射子弹
if (e.getKeyCode() == KeyEvent.VK_J) {
myTank.shotEnemyTank();//发射子弹
}
this.repaint();
}
@Override
public void keyReleased(KeyEvent e) {
}
//让子弹不停的重绘
@Override
public void run() {
while (true) {
try {
//每隔200毫秒,重绘
Thread.sleep(100);
}