我方坦克发射子弹

添加Shot 类,让其实现多线程,添加相应属性及方法

/**
 * @author 谢阳
 * @version 1.8.0_131
 */
public class Shot implements Runnable {
    private int x;//子弹x坐标
    private int y;//子弹y坐标
    private int speed = 6;//子弹速度
    private int direct;//子弹方向
    private boolean isLive = true;//子弹是否存活,用于后面判断可以消除子弹

    public Shot(int x, int y, int direct) {//Shot的构造器
        this.x = x;
        this.y = y;
        this.direct = direct;
    }

    @Override
    public void run() {//根据移动方向设置子弹移动方向
        while (true) {
            try {
                Thread.sleep(15);
            } 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;
            }
            if (!(x >= 0 && x <= 1000 && y >= 0 && y <= 750)) {//子弹超界退出循环
                isLive = false;
                break;
            }
        }

    }

    public boolean isLive() {
        return isLive;
    }

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

    public int getX() {

        return x;
    }

    public int getY() {
        return y;
    }
}

在Hero类中添加shotEnemy( )方法

import java.util.Vector;

/**
 * @author 谢阳
 * @version 1.8.0_131
 */
public class Hero extends Tank{
    Vector<Shot> shots = new Vector<>();//创建子弹集合,后续限制子弹个数
    private Shot shot = null;//子弹

    public Hero(int x, int y, int direct) {//构造器
        super(x, y, direct);
    }

    public void shotEnemy() {//根据方向设置子弹发射位置
        switch (getDirect()) {
            case 0:
                shot = new Shot(getX() + 20, getY(), 0);
                break;
            case 1:
                shot = new Shot(getX() + 40, getY() + 20, 1);
                break;
            case 2:
                shot = new Shot(getX() + 20, getY() + 40, 2);
                break;
            case 3:
                shot = new Shot(getX(), getY() + 20, 3);
                break;
        }
        new Thread(shot).start();//启动子弹线程
        shots.add(shot);//添加进子弹集合
    }
}

在Mypanel的监听中设置按J键启动shotEnemy( )方法

public void keyPressed(KeyEvent e) {
    if (e.getKeyCode() == KeyEvent.VK_W) {//上
        hero.moveUp();
        hero.setDirect(0);
    } else if (e.getKeyCode() == KeyEvent.VK_D) {//右
        hero.setDirect(1);
        hero.moveRight();
    } else if (e.getKeyCode() == KeyEvent.VK_S) {//下
        hero.setDirect(2);
        hero.moveDown();
    } else if (e.getKeyCode() == KeyEvent.VK_A) {//左
        hero.setDirect(3);
        hero.moveLeft();
    }
    if (e.getKeyCode() == KeyEvent.VK_J) {//按J发射子弹
        hero.shotEnemy();
    }
}

在Mypanel的paint( ) 中画出子弹

public void paint(Graphics g) {
    super.paint(g);
    g.fill3DRect(0, 0, 1000, 750, false);//设置背景大小
    drawTank(hero.getX(), hero.getY(), hero.getDirect(), 0, g);//画出我方坦克

    for (int i = 0; i < hero.shots.size(); i++) {//当子弹不为空时画出子弹
            Shot shot = hero.shots.get(i);
            g.fill3DRect(shot.getX() - 1, shot.getY() - 1, 3, 3, false);
        }

    for (int i = 0; i < enemies.size(); i++) {//遍历画出敌方坦克
        Enemy enemy = enemies.get(i);
        drawTank(enemy.getX(), enemy.getY(), enemy.getDirect(), 1, g);
    }
}

验证结果
图片.png
优化 :限制我方发射子弹数量,每次只能发射3颗子弹保证了游戏的公平性

在Hero类中shotEnemy()方法中添加限制条件

public void shotEnemy() {//根据方向设置子弹发射位置
        if (shots.size() == 3) {//如果子弹数量等于3就不添加子弹
            return;
        }
        switch (getDirect()) {
            case 0:
                shot = new Shot(getX() + 20, getY(), 0);
                break;
            case 1:
                shot = new Shot(getX() + 40, getY() + 20, 1);
                break;
            case 2:
                shot = new Shot(getX() + 20, getY() + 40, 2);
                break;
            case 3:
                shot = new Shot(getX(), getY() + 20, 3);
                break;
        }
        new Thread(shot).start();//启动子弹线程
        shots.add(shot);//添加进子弹集合
    }

在MyPanel的paint()方法中换子弹时判断

public void paint(Graphics g) {
    super.paint(g);
    g.fill3DRect(0, 0, 1000, 750, false);//设置背景大小
    drawTank(hero.getX(), hero.getY(), hero.getDirect(), 0, g);//画出我方坦克

    for (int i = 0; i < hero.shots.size(); i++) {//当子弹不为空时画出子弹
        Shot shot = hero.shots.get(i);
        if (shot.isLive()) {//判断子弹是否超界,未超界画出
            g.fill3DRect(shot.getX() - 1, shot.getY() - 1, 3, 3, false);//画出子弹大小
        } else {
            hero.shots.remove(shot);//超界移除
        }
    }

    for (int i = 0; i < enemies.size(); i++) {//遍历画出敌方坦克
        Enemy enemy = enemies.get(i);
        drawTank(enemy.getX(), enemy.getY(), enemy.getDirect(), 1, g);
    }
}

验证结果
图片.png

敌方坦克发射子弹

与我坦克坦克代码类似,Enemy是多线程,所以坦克可以通过run()方法自动发射子弹

Enemy的run( ) 中写出子弹代码

@Override
public void run() {//重写run方法
    while (true) {
        if (shots.size() < 2) {//设置敌方子弹个数为2
            switch (getDirect()) {
                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;
            }
            new Thread(shot).start();//启动子弹线程
            shots.add(shot);//添加进子弹集合
        }

        int random = (int) (Math.random() * 60);//获取随机移动距离
        switch (getDirect()) {//获取方向
            case 0://上
                for (int i = 0; i < random; i++) {
                    moveUp();//向上移动random步
                    try {
                        Thread.sleep(50);//休眠50ms
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                break;
            case 1://右
                for (int i = 0; i < random; i++) {
                    moveRight();//向右移动random步
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                break;
            case 2://下
                for (int i = 0; i < random; i++) {
                    moveDown();//向下移动random步
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                break;
            case 3://左
                for (int i = 0; i < random; i++) {
                    moveLeft();//向左移动random步
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                break;
        }
        setDirect((int) (Math.random() * 4));//设置随机方向
    }
}

同理,在在Mypanel的paint( ) 中画出敌人子弹

@Override
public void paint(Graphics g) {
    super.paint(g);
    g.fill3DRect(0, 0, 1000, 750, false);//设置背景大小

    //我方坦克
    drawTank(hero.getX(), hero.getY(), hero.getDirect(), 0, g);//画出我方坦克
    for (int i = 0; i < hero.shots.size(); i++) {//当子弹不为空时画出子弹
        Shot shot = hero.shots.get(i);
        if (shot.isLive()) {//判断子弹是否超界,未超界画出
            g.fill3DRect(shot.getX() - 1, shot.getY() - 1, 3, 3, false);
        } else {
            hero.shots.remove(shot);//超界移除
        }
    }

    //敌方坦克
    for (int i = 0; i < enemies.size(); i++) {//遍历画出敌方坦克
        Enemy enemy = enemies.get(i);
        drawTank(enemy.getX(), enemy.getY(), enemy.getDirect(), 1, g);
        for (int j = 0; j < enemy.shots.size(); j++) {//当子弹不为空时画出子弹
            Shot shot = enemy.shots.get(j);
            if (shot.isLive()) {//判断子弹是否超界,未超界画出
                g.fill3DRect(shot.getX() - 1, shot.getY() - 1, 3, 3, false);
            } else {
                enemy.shots.remove(shot);//超界移除
            }
        }
    }
}

验证结果
图片.png

限制游戏移动区域

在Tank类中添加限制即可

/**
 * @author 谢阳
 * @version 1.8.0_131
 */
public class Tank {
    private int x;//x坐标
    private int y;//有坐标
    private int direct;//方向
    private int speed = 1;//速度

    public void moveUp() {
        if (y >= 3) {
            y -= speed;
        }
    }

    public void moveDown() {
        if (y <= 707) {
            y += speed;
        }
    }

    public void moveRight() {
        if (x <= 957) {
            x += speed;
        }
    }

    public void moveLeft() {
        if (x >= 3 ) {
            x -= speed;
        }
    }

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

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

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public int getDirect() {
        return direct;
    }

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

验证结果
图片.png

我方坦克可以击中敌人

Tank类中添加isLive属性

public class Tank {
    private int x;//x坐标
    private int y;//有坐标
    private int direct;//方向
    private int speed = 1;//速度
    boolean isLive = true;//坦克存活

MyPanel中添加hit方法,判断是否击中敌人

public void hit(Shot shot, Enemy tank) {//hit方法,判断是否击中敌人
        int x = tank.getX();
        int y = tank.getY();
        if (shot.getX() > x && shot.getX() < x + 40 && shot.getY() > y && shot.getY() < y + 40){
            tank.isLive = false;//设置为不存活
            enemies.remove(tank);//击中就从敌方tank集合中移除
        }
    }

MyPanel的run()方法中遍历敌人和我方子弹并调用hit()方法

@Override
public void run() {
    while (true) {

        this.repaint();//刷新画板
        try {
            Thread.sleep(15);//每15ms刷新一次
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < enemies.size(); i++) {//遍历敌人坦克
            Enemy enemy = enemies.get(i);//得到敌人
            for (int j = 0; j < hero.shots.size(); j++) {//遍历我方子弹
                Shot shot = hero.shots.get(j);//得到我方子弹
                hit(shot,enemy);//调用方法判断
            }
        }
    }
}

在MyPanel的paint()方法中画敌人坦克时判断

for (int i = 0; i < enemies.size(); i++) {//遍历画出敌方坦克
    Enemy enemy = enemies.get(i);
    if (enemy.isLive) {//判断存活才画出坦克
        drawTank(enemy.getX(), enemy.getY(), enemy.getDirect(), 1, g);
    }
    for (int j = 0; j < enemy.shots.size(); j++) {//当子弹不为空时画出子弹
        Shot shot = enemy.shots.get(j);
        if (shot.getIsLive()) {//判断子弹是否超界,未超界画出
            g.fill3DRect(shot.getX() - 1, shot.getY() - 1, 3, 3, false);
        } else {
            enemy.shots.remove(shot);//超界移除
        }
    }
}

验证结果
QQ录屏20211014195339.gif
但子弹可以穿过敌人,可以优化一下,子弹击中敌人后消失

MyPanel类的hit()方法中涉资子弹击中敌人后状态

public void hit(Shot shot, Enemy tank) {//hit方法,判断是否击中敌人
    int x = tank.getX();
    int y = tank.getY();
    if (shot.getX() > x && shot.getX() < x + 40 && shot.getY() > y && shot.getY() < y + 40){
        tank.isLive = false;//击中坦克,坦克不存活
        shot.setIsLive(false);//击中敌人,子弹不存活
        enemies.remove(tank);//击中就从敌方tank集合中移除
    }
}

Shot类中添加子弹判断条件,打中敌人可以提前退出循环

if (!(x >= 0 && x <= 1000 && y >= 0 && y <= 750 && isLive)) {//子弹超界或则打中敌人退出循环
    isLive = false;
    break;
}

验证结果
QQ录屏20211015085619.gif

敌方坦克可以击中我方坦克

MyPanel中添加我方坦克集合(后续有兴趣的可以添加两个坦克),删除原有tank,并在构造器中初始化我方坦克数据

public class MyPanel extends JPanel implements KeyListener, Runnable {
    Vector<Hero> heroes = new Vector<>();//创建我方坦克集合
    int enemyNum = 10;//创建敌方坦克数量
    Vector<Enemy> enemies = new Vector<>();//创建敌方坦克集合

    public MyPanel() {//创建无参构造器
        Hero hero = new Hero(550, 500, 0);//初始化我方坦克
        hero.setSpeed(5);//设置我方移动速度
        heroes.add(hero);//添加我方坦克集合

        for (int i = 0; i < enemyNum; i++) {//初始化敌方坦克
            Enemy enemy = new Enemy(100 * (i + 0), 0, 2);
            enemy.setSpeed(2);//设置敌人移动速度
            enemies.add(enemy);//添加至敌人坦克集合
            new Thread(enemy).start();//启动敌人坦克线程
        }

    }

MyPanel的paint()中修改我方绘画条件

//我方坦克
for (int i = 0; i < heroes.size(); i++) {//如果坦克数量为零则不画出
    Hero hero = heroes.get(i);
    drawTank(hero.getX(), hero.getY(), hero.getDirect(), 0, g);//画出我方坦克
    for (int j = 0; j < hero.shots.size(); j++) {//当子弹不为空时画出子弹
        Shot shot = hero.shots.get(j);
        if (shot.getIsLive()) {//判断子弹是否超界,未超界画出
            g.fill3DRect(shot.getX() - 1, shot.getY() - 1, 3, 3, false);
        } else {
            hero.shots.remove(shot);//超界移除
        }
    }
}

MyPanel的监听中添加我方判断条件

@Override
public void keyPressed(KeyEvent e) {
    for (int i = 0; i < heroes.size(); i++) {
        Hero hero = heroes.get(i);
        if (e.getKeyCode() == KeyEvent.VK_W) {//上
            hero.moveUp();
            hero.setDirect(0);
        } else if (e.getKeyCode() == KeyEvent.VK_D) {//右
            hero.setDirect(1);
            hero.moveRight();
        } else if (e.getKeyCode() == KeyEvent.VK_S) {//下
            hero.setDirect(2);
            hero.moveDown();
        } else if (e.getKeyCode() == KeyEvent.VK_A) {//左
            hero.setDirect(3);
            hero.moveLeft();
        }
        if (e.getKeyCode() == KeyEvent.VK_J) {//按J发射子弹
            hero.shotEnemy();
        }
    }
}

MyPanel修改hit()方法,扩大其判断范围

public void hit(Shot shot, Tank tank) {//hit方法,判断是否击中坦克
    int x = tank.getX();
    int y = tank.getY();
    if (shot.getX() > x && shot.getX() < x + 40 && shot.getY() > y && shot.getY() < y + 40) {
        tank.isLive = false;//击中坦克,坦克不存活
        shot.setIsLive(false);//击中坦克,子弹不存活
        if (tank instanceof Enemy) {//判断是否为敌方坦克
            enemies.remove(tank);//是敌方tank就从集合中移除
        }
        if (tank instanceof Hero) {//如果是我方坦克移除我方坦克
            heroes.remove(tank);
        }
    }
}

MyPanel的run()方法中重新添加遍历条件并调用hit()方法

@Override
public void run() {
    while (true) {

        this.repaint();//刷新画板
        try {
            Thread.sleep(15);//每15ms刷新一次
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < enemies.size(); i++) {//遍历敌人坦克
            Enemy enemy = enemies.get(i);//得到敌人

            for (int j = 0; j < enemy.shots.size(); j++) {//遍历敌方子弹
                Shot shot = enemy.shots.get(j);//得到敌方子弹
                for (int k = 0; k < heroes.size(); k++) {
                    Hero hero = heroes.get(k);
                    hit(shot, hero);//调用方法判断
                }
            }
            for (int k = 0; k < heroes.size(); k++) {//遍历我方坦克
                Hero hero = heroes.get(k);//得到我方坦克
                for (int j = 0; j < hero.shots.size(); j++) {//遍历我方子弹
                    Shot shot = hero.shots.get(j);//得到我方子弹
                    hit(shot, enemy);//调用方法判断
                }
            }
        }
    }
}

验证结果
QQ录屏20211015100554.gif

显示游戏胜利失败

MyPane添加showInfo()方法

 public void showInfo(Graphics g){//显示游戏信息
        g.setColor(Color.BLACK);
        Font font = new Font("宋体", Font.BOLD, 25);
        g.setFont(font);
        g.drawString("累计击毁敌方坦克", 1050, 30);
        g.drawString( enemyNum -enemies.size()  + "", 1110, 80);
        drawTank(1050, 50, 0, 1, g);

        if (enemies.size() == 0) {//胜利信息
            g.setColor(Color.red);
            Font font1 = new Font("宋体", Font.BOLD, 80);
            g.setFont(font1);
            g.drawString("YOU WIN", 350, 350);
        }

        if (heroes.size() == 0) {//失败信息
            g.setColor(Color.red);
            Font font2 = new Font("宋体", Font.BOLD, 80);
            g.setFont(font2);
            g.drawString("Game Over", 350, 350);
        }
    }

MyPanel的paint()中调用方法并传参

@Override
public void paint(Graphics g) {
    super.paint(g);
    g.fill3DRect(0, 0, 1000, 750, false);//设置背景大小

    //我方坦克
    for (int i = 0; i < heroes.size(); i++) {//如果坦克数量为零则不画出
        Hero hero = heroes.get(i);
        drawTank(hero.getX(), hero.getY(), hero.getDirect(), 0, g);//画出我方坦克
        for (int j = 0; j < hero.shots.size(); j++) {//当子弹不为空时画出子弹
            Shot shot = hero.shots.get(j);
            if (shot.getIsLive()) {//判断子弹是否超界,未超界画出
                g.fill3DRect(shot.getX() - 1, shot.getY() - 1, 3, 3, false);
            } else {
                hero.shots.remove(shot);//超界移除
            }
        }
    }

    //敌方坦克
    for (int i = 0; i < enemies.size(); i++) {//遍历画出敌方坦克
        Enemy enemy = enemies.get(i);
        if (enemy.isLive) {//判断存活才画出坦克
            drawTank(enemy.getX(), enemy.getY(), enemy.getDirect(), 1, g);
        }
        for (int j = 0; j < enemy.shots.size(); j++) {//当子弹不为空时画出子弹
            Shot shot = enemy.shots.get(j);
            if (shot.getIsLive()) {//判断子弹是否超界,未超界画出
                g.fill3DRect(shot.getX() - 1, shot.getY() - 1, 3, 3, false);
            } else {
                enemy.shots.remove(shot);//超界移除
            }
        }
    }

    showInfo(g);//调用方法显示游戏信息
}

TankGame中修改构造器TankGame()中的界面大小

public TankGame() {//重写构造器
    mp = new MyPanel();//初始化属性
    new Thread(mp).start();//启动Mypanel的线程
    this.setSize(1300, 800);//设置界面大小
    this.add(mp);//添加mp
    this.setVisible(true);//设置可见
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置关闭窗口结束程序
    this.addKeyListener(mp);//监听键盘输入
}

显示结果
image.png
image.png