<Java>18 项目(坦克大战)

本文最后更新于:2023年5月24日 下午

18 项目:坦克大战

还有很多细节、优化方法没实现,但困难的已经没有了。

先不打磨了,还是以学习 Java 为主。

二阶段毕业

关于 数量 的注释可能不对。那些注释是早些时候添加的。后来添加了新功能,这些注释没有及时更新

TankGame.java:程序的入口

MyPanel.java:窗口、图像的绘制。这是该项目的主干

Player.java、Enemy.java:己方、敌方的坦克类。Tank.java 是他们的父类

Vehicle.java:记录了所有坦克预设的枚举类。该包下还有另一个记录了所有子弹类型的枚举类 Bullet

Map.java:记录了地图数据的枚举类,包含不同关卡。但我没设计关卡

InputImage.java:所有导入的图片。是我准备的素材

Timing.java:一个计时器。当时能力有限,用这个作为某个计时功能的代替方案

  • TankGame.java(入口)

    package com.melody.tank_game;
    
    import javax.swing.*;
    import java.io.*;
    import java.util.Scanner;
    
    /**
     * @author Melody
     * @version 1.0.ష
     * <p>
     * 小派蒙会开坦克吗?
     * <p>
     * ⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⠴⠒⠛⠲⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
     * ⠀⠀⠀⠀   ⠀⢻⡄⣠⠶⣆⠀⣸⣀⣀⠀⠀
     * ⠀⠀⠀ ⠀⠀⢀⡠⠬⠛⢓⣏⠉⣾⣉⣀⠉⢹⡀⠀⠀⠀⠀⠀⠀⠀
     * ⠀⠀ ⠀⢀⡖⠋⣠⠴⠛⠙⢹⠞⢳⢀⣨⡵⠚⠀⠀⠀
     * ⠀⠀⠀⣰⠋⡠⠎⠁⣀⠤⠒⠚⠛⠙⠒⠳⠤⣄⡀⠀
     * ⠀⠀⠀⠘⠐⢼⠖⠋⠀⠀⢀⠀⠀⠀⠀⠀⠀⠘⣌⡒⠲⢹⠀⠀⠀⠀
     * ⠀⠀ ⠀⡸⠁⠀⠀⠀⠀⡆⠀⠀⠐⠀⠢⣄⠀⡽⡙⡲⠑⠒⠒⡒⠁
     * ⢀⡠⠴⠚⠀⠀⠀⠀⠀⣕⠝⣄⡀⢀⠀⠀⡇⠵⢍⠚⢾⡀⢠⠖⠁⠀       ___________________
     * ⠈⠦⣄⣀⠀⡔⠀⠀⢁⡞⠀⠉⠲⣄⡀⢲⢼⠀⢀⠳⡄⠁⠀⢣⠀⠀     /  坦克?是新游戏喔。   \
     * ⠀⠀⣠⠃⢐⠄⠀⠀⠴⠅⠠⡊⡢⠀⠉⠉⠁⠀⢆⠕⠹⡀⠀⠈⡆⠀  <   开玩笑,我超勇的好不好   \
     * ⠀⠠⡇⠀⡸⠀⠀⠀⠨⡅⠀⠒⠈⠀⢄⠠⠠⠔⠀⠀⠀⢻⠀⠀⢣⠀     \     我超会玩的啦 ~     /
     * ⠀⢸⠅⠀⡕⠀⠀⠀⠀⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡤⡏⠀⠀⢸⠀       \____________________/
     * ⠀⠈⡇⠀⣣⠀⠀⠈⠀⠸⡦⠴⠲⢚⢚⠙⠝⠙⠍⠝⣱⠏⢠⠀⢸⠅
     * ⠀⠀⠙⣆⠘⣄⠀⠠⣄⠀⠹⣌⠌⠀⠂⠐⢈⠄⡁⢌⠳⣺⠏⢀⡞⠀
     * ⠀⠀⠀⠀⠙⠺⠛⣲⠜⠟⡓⡚⣏⣔⡀⡌⣀⢂⣔⠴⠋⢏⠒⠁⠀⠀
     * <p>
     * 什么新游戏,比游戏还刺激!还可以教你 学 Ja va 哦 ~
     */
    
    public class TankGame extends JFrame {
    
        public static MyPanel mp;
        public static Thread thread;
        public static File savePath = new File("d:\\Program\\Melody\\TankGame\\saveData.sav");
        public boolean abandoned;
    
        public static void main(String[] args) {
            int width = 1200;
            int height = 800;
            int enemyNum = 5;
            Scanner scanner = new Scanner(System.in);
            char inpChar = ' ';
            mp = new MyPanel(width, height, enemyNum);
            if (savePath.isFile()) {
                System.out.println("开始新游戏?(Y新游戏/N读档)");
                while (true) {
                    inpChar = scanner.next().charAt(0);
                    try {
                        if (inpChar == 'Y' || inpChar == 'y') {
                            break;
                        } else if (inpChar == 'N' || inpChar == 'n') {
                            mp.pause = true;
                            mp.toLoad = true;
                        } else {
                            throw new RuntimeException();
                        }
                        break;
                    } catch (Exception e) {
                        System.out.println("错误!请重新输入(Y新游戏/N读档)");
                    }
                }
            }
            TankGame game = new TankGame(Math.abs(width), Math.abs(height), Math.abs(enemyNum));
            thread = new Thread(game.mp);
            thread.start();
        }
    
        public TankGame(int width, int height, int enemyNum) {
            width = Math.max(width, 800);
            height = Math.max(height, 600);
            this.add(mp);
            this.setSize(width + 20, height + 240);
            this.addKeyListener(mp);
            this.setVisible(true);
            this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        }
    
    }
  • MyPanel.java

    package com.melody.tank_game;
    
    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.KeyEvent;
    import java.awt.event.KeyListener;
    import java.io.*;
    import java.util.Iterator;
    import java.util.LinkedHashMap;
    import java.util.Vector;
    
    /**
     * @author Melody
     * @version 1.0
     * !!!!!!!!!!!!!!!该类需要重新整理!!!!!!!!!!!!!!!!
     */
    class MyPanel extends JPanel implements KeyListener, Runnable, Serializable {
        //5 个静态变量依次是:允许的同屏最大敌人数量的上限、战斗区域宽度、战斗区域高度、玩家、是否通关
        private static final int ENEMY_MAX = 20;
        public static int width = 1200;
        public static int height = 1000;
        private static Player player = new Player(640, 640);
        public static boolean gameSuspend = false;
        public static int[][][] mapData = new int[width][height][2];                        //*****此处需要修改*****
        private static final long serialVersionUID = 1L;
    
    
        //12 个成员变量,依次是:所有敌人坦克、所有被摧毁的敌人坦克、同屏最大敌人数量(用户设定)、敌人初始位置集(含坐标、占用情况)、
        //          敌人初始位置集(仅坐标)、关卡敌人总数、W键是否正被按下、A键是否正被按下、S键是否正被按下、D键是否正被按下、开火键是否正被按下、
        //          导入图片媒介、地图集、关卡数
        private Vector<Tank> enemy = new Vector<>();
        private Vector<Tank> deadEnemy = new Vector<>();
        private int enemyMax;
        private LinkedHashMap<int[], Boolean> enemyPositions = new LinkedHashMap<>();
        private int[][] enemyPosition;
        private transient boolean isPressingW = false;
        private transient boolean isPressingA = false;
        private transient boolean isPressingS = false;
        private transient boolean isPressingD = false;
        private transient boolean isFiring = false;
        private transient Image image = null;
        private Map[] maps = Map.values();
        private int level = -1;
        private Tank boss;
        private boolean bossActive = false;
        boolean pause = false;
        transient boolean saved = false;
        boolean loaded = false;
        transient boolean toLoad = false;
        private transient boolean toSave = false;
        transient boolean abandoned = false;
    
        //构造器
        public MyPanel(int width, int height, int enemyNum) {
            this.width = (int) (width / 40) * 40;
            this.height = (int) (height / 40) * 40;               //设置长宽
            enemyNum = Math.max(enemyNum, 0);
            enemyMax = Math.min(enemyNum, ENEMY_MAX);           //设置同屏最大敌人数量
            enemyPosition = new int[enemyMax][2];                //已经确定了要生成的敌人位置数量,生成位置数组(空)
            for (int i = 0; i < enemyMax; i++) {                //生成敌人初始位置。
                int[] tempPosition = new int[2];
                tempPosition[0] = i * 150 + 50;
                tempPosition[1] = 50;
                while (tempPosition[0] > (width - 100)) {
                    tempPosition[1] += 120;
                    tempPosition[0] -= (width - 150);
                    if (tempPosition[1] > height - 100) {
                        throw new RuntimeException();
                    }
                }
                enemyPosition[i][0] = tempPosition[0];
                enemyPosition[i][1] = tempPosition[1];
            }                                                    //此时 敌人初始位置数组 生成完毕。
            int n = 0;
            for (int[] ints : enemyPosition) {
                enemyPositions.put(ints, false);                 //装满 敌人初始位置集 默认全部是未占用
            }
    
        }
    
    
        @Override
        public void run() {
            nextLevel();
            player.start();             //启动玩家线程
            while (!abandoned) {
                creatEnemy();
                gameSuspend = false;//创建敌人坦克。这个方法同时会启动敌人坦克线程
                while (!gameSuspend) {
                    repaint();              //刷新
                    checkLevelClear();
                    if(toSave){
                        saveData();
                        toSave  = false;
                    } else if(toLoad){
                        loadData();
                        toLoad  = false;
                    }
                }
                pauseBetweenLevels(1200 + (bossActive ? 2000 : 0));
                while (!gameSuspend) {
                    repaint();
                }
                nextLevel();
            }
        }
    
    
        @Override
        public void paint(Graphics g) {
            drawUI(g);                      //画出 UI
            drawPlayer(g);                  //画出 玩家坦克(包含子弹)
            drawEnemy(g);                   //画出 所有敌人坦克(包含子弹)
            drawDeadTank(g);                //画出 被破坏坦克的残留动画和残留子弹
        }
    
        //画出 UI
        private void drawUI(Graphics g) {
            g.setColor(Color.BLACK);
            g.fillRect(0, 0, width, height);
            g.setColor(Color.GRAY);
            g.fillRect(2, 2, width - 4, height - 4);        //以上 4 行,画出了战斗场地
            g.setColor(Color.WHITE);
            g.fillRect(0, height, width, 200);
            drawHp(g);                                                      //画 HP 槽
            drawFireCD(g);                                                  //画 开火 槽
            drawEnemyNum(g);                                                //画 雷达 槽
            drawStars(g);                                                   //画 经验值 星星
            if (pause) {
                drawPause(g);
            }
        }
    
        //画 HP 槽
        private void drawHp(Graphics g) {
            //下面画出 绿色 HP 槽。随着 HP 减少,槽会变空
            g.setFont(new Font("等线", Font.ITALIC + Font.BOLD, 50));
            g.setColor(Color.BLACK);
            g.fillRect(50, height + 60, (int) (20 * player.vehicle.LIFE) + 4, 30);
            g.setColor(player.lifeRemains / player.vehicle.LIFE > 0.2 ? Color.GREEN : Color.RED);
            g.fill3DRect(52, height + 62, (int) (20 * Math.max(player.lifeRemains, 0)), 26, true);
            g.fill3DRect(52, height + 80, (int) (20 * Math.max(player.lifeRemains, 0) - 1), 6, false);
            //下面画出一段说明文
            g.setColor(Color.GRAY);
            if (player.vehicle == Vehicle.Basic) {
                g.drawString("防弹衣", 50, height + 140);
            } else {
                g.drawString(player.vehicle == Vehicle.Tank_Special ? "复合装甲" : "装甲", 50, height + 140);
            }
        }
    
        //画 开火 槽
        private void drawFireCD(Graphics g) {
            //以下画出 橙色 开火槽。随着装弹,槽会填满
            g.setFont(new Font("等线", Font.ITALIC + Font.BOLD, 50));
            g.setColor(Color.BLACK);
            g.fillRect((int) ((width / 2) + 60), height + 60, 205, 30);
            g.setColor(player.vehicle == Vehicle.Tank_Special ? Color.cyan : Color.ORANGE);
            g.fill3DRect((int) ((width / 2) + 62), height + 62,
                    (int) (player.getAtkSpeed() * (player.fireCD - Math.max(player.fireCount, 0)) + 1), 26, true);
            g.fill3DRect((int) ((width / 2) + 62), height + 80,
                    (int) (player.getAtkSpeed() * (player.fireCD - Math.max(player.fireCount, 0))), 6, false);
            //以下画出一段说明文字
            g.setColor(Color.GRAY);
            if (player.vehicle == Vehicle.Basic) {
                g.drawString(player.fireCount <= 10 ? "哒哒哒哒哒……" : player.fireCount == player.fireCD ? "加特林停了" : "加特林转啊!",
                        width / 2 + 60, height + 140);
            } else {
                g.drawString(player.fireCount <= 0 ? "弹药装填完毕" : "装填中…", width / 2 + 60, height + 140);
            }
        }
    
        //画出 敌人数量(这个方法是UI的一部分,统计数量,而不是画坦克)
        private void drawEnemyNum(Graphics g) {
            g.setFont(new Font("等线", Font.ITALIC + Font.BOLD, 20));
            g.setColor(Color.BLACK);
            g.drawString("雷达发现敌人数量:" + (maps[level].levelEnemyNum - deadEnemy.size()), (int) (width * 0.77), height + 22);
        }
    
        //画出 经验值。有几点就有几颗红星被点亮。
        private void drawStars(Graphics g) {
            switch (player.vehicle.LEVEL_UP_REQUIRED) {
                case 10:
                    g.drawImage(player.enhance >= 10 ? InputImage.star : InputImage.nStar,
                            280, height + 10, 30, 30, this);
                    g.drawImage(player.enhance >= 9 ? InputImage.star : InputImage.nStar,
                            250, height + 10, 30, 30, this);
                    g.drawImage(player.enhance >= 8 ? InputImage.star : InputImage.nStar,
                            220, height + 10, 30, 30, this);
                    g.drawImage(player.enhance >= 7 ? InputImage.star : InputImage.nStar,
                            190, height + 10, 30, 30, this);
                    g.drawImage(player.enhance >= 6 ? InputImage.star : InputImage.nStar,
                            160, height + 10, 30, 30, this);
                case 5:
                    g.drawImage(player.enhance >= 5 ? InputImage.star : InputImage.nStar,
                            130, height + 10, 30, 30, this);
                case 4:
                    g.drawImage(player.enhance >= 4 ? InputImage.star : InputImage.nStar,
                            100, height + 10, 30, 30, this);
                case 3:
                    g.drawImage(player.enhance >= 3 ? InputImage.star : InputImage.nStar,
                            70, height + 10, 30, 30, this);
                case 2:
                    g.drawImage(player.enhance >= 2 ? InputImage.star : InputImage.nStar,
                            40, height + 10, 30, 30, this);
                case 1:
                    g.drawImage(player.enhance >= 1 ? InputImage.star : InputImage.nStar,
                            10, height + 10, 30, 30, this);
                    break;
                default:
                    g.setColor(Color.GRAY);             //不能升级的坦克,画一个小小的圆
                    g.fillOval(40, height + 10, 10, 10);
                    break;
            }
        }
    
        private void drawPause(Graphics g) {
            g.setFont(new Font("等线", Font.ITALIC + Font.BOLD, 70));
            g.setColor(Color.DARK_GRAY);
            g.drawString("游戏暂停中,按 P 键继续游戏", (int) ((width / 2) * 0.2), (height / 2 - 100));
            if (saved){
                g.setFont(new Font("等线", Font.ITALIC + Font.BOLD, 50));
                g.setColor(Color.DARK_GRAY);
                g.drawString("存档成功!", (int) ((width / 2) * 0.8), (height / 2));
            } else if(loaded){
                g.setFont(new Font("等线", Font.ITALIC + Font.BOLD, 50));
                g.setColor(Color.DARK_GRAY);
                g.drawString("读档成功!", (int) ((width / 2) * 0.8), (height / 2));
            }
            if (deadEnemy.size() >= maps[level].levelEnemyNum && (!bossActive || !boss.alive)){
                g.setFont(new Font("黑体", Font.ITALIC + Font.BOLD, 70));
                g.setColor(Color.ORANGE);
                g.drawString("下一波敌人接近中!", (int) ((width / 2) * 0.5), (int)(height * 0.8));
                                                                                            //*****此处需要修改*****
            }
        }
    
        //画出 玩家坦克
        private void drawPlayer(Graphics g) {
            player.pause = this.pause;
            drawLivingTank(g, player);
        }
    
        //画出敌人坦克
        private void drawEnemy(Graphics g) {
            Iterator<Tank> iterator = enemy.iterator();
            int n = 0;
            while (iterator.hasNext()) {
                Tank temp = iterator.next();
                if (!temp.alive) {                              //这个分支,坦克已被摧毁
                    temp.releaseCollisionSize();                //消除其碰撞体积
                    iterator.remove();
                    deadEnemy.add(temp);                        //从 敌人集 把该坦克删除,加入 破坏坦克集
                    player.enhanced(temp.vehicle);              //给玩家结算经验
                    enemyPositions.put(enemyPosition[n], false);//该坦克占用的位置被解放
                }
                n++;
            }
            creatEnemy();                       //尝试生成新的坦克
            iterator = enemy.iterator();
            while (iterator.hasNext()) {
                Tank t = iterator.next();
                t.pause = this.pause;                //暂停设置
    //            if (t.specialCount > 0) {
    //                t.specialCount--;                                     //*****此处需要修改*****
    //            } else {
                drawLivingTank(g, t);       //画出所有(敌人的)坦克
    //            }
            }
            if (bossActive) {
                boss.pause = this.pause;
                drawBoss(g);
            }
        }
    
        private void drawBoss(Graphics g) {
            if (boss.alive) {
                drawTank(g, boss);
            } else {
                drawExplosion(g, boss);
            }
            drawBullet(g, boss);
        }
    
    
        //画出被摧毁的坦克的残留特效及其所有残留子弹
        private void drawDeadTank(Graphics g) {
            Iterator<Tank> iterator = deadEnemy.iterator();
            while (iterator.hasNext()) {
                Tank temp = iterator.next();
                temp.pause = this.pause;
                if (temp.specialCount > 0) {          //敌人的特殊计数残留时,画出其爆炸效果
                    temp.specialCount--;                                        //*****此处需要修改*****
                    drawExplosion(g, temp);
                }
                if (temp.bullets.size() > 0) {          //敌人的子弹有残留时,画出该子弹
                    drawBullet(g, temp);
                }
            }
        }
    
    
        //画出存活的坦克及其所有子弹
        private void drawLivingTank(Graphics g, Tank tank) {
            //以下部分画出坦克
            if (tank.alive) {
                drawTank(g, tank);       //存活的场合,画出坦克
                drawBullet(g, tank);                //画出子弹
            }
        }
    
    
        //画坦克。这个方法能画出朝向正确的坦克。
        private void drawTank(Graphics g, Tank tank) {
            boolean b = dirState2(tank.dir);
            switch (tank.vehicle) {
                case Tank_Lv1:
                    image = dirState(tank.dir) ? InputImage.tank1 : InputImage.tank1r;
                    break;
                case Tank_Lv2:
                    image = dirState(tank.dir) ? InputImage.tank2 : InputImage.tank2r;
                    break;
                case Tank_Lv3:
                    image = dirState(tank.dir) ? InputImage.tank3 : InputImage.tank3r;
                    break;
                case Tank_E1:
                    image = dirState(tank.dir) ? InputImage.tankE1 : InputImage.tankE1r;
                    break;
                case Tank_E2:
                    image = dirState(tank.dir) ? InputImage.tankE2 : InputImage.tankE2r;
                    break;
                case Tank_E3:
                    image = dirState(tank.dir) ? InputImage.tankE3 : InputImage.tankE3r;
                    break;
                case Tank_Special:
                    image = dirState(tank.dir) ? InputImage.tankSp : InputImage.tankSpr;
                    break;
                case Basic:
                    if (tank.fireCount <= 3) {
                        image = dirState(tank.dir) ? InputImage.basicL : InputImage.basicLR;
                    } else {
                        image = dirState(tank.dir) ? InputImage.basic : InputImage.basicR;
                    }
                    break;
            }
            g.drawImage(image, b ? tank.x + 80 : tank.x, b ? tank.y + 80 : tank.y, b ? -80 : 80, b ? -80 : 80, this);
            if (tank.invincible > 0) {
                switch (tank.vehicle) {             //无敌持续中的场合,画出护盾
                    case Tank_Special:
                    case Basic:
                        image = dirState(tank.dir) ? InputImage.bubble : InputImage.bubbleR;
                        break;
                    default:
                        image = dirState(tank.dir) ? InputImage.bubbleE : InputImage.bubbleER;
                        break;
                }
                g.drawImage(image, tank.x - 5, tank.y - 5, 90, 90, this);
            }
        }
    
    
        //画出子弹。这个方法也会判断子弹的状态(是否存活)
        private void drawBullet(Graphics g, Tank tank) {
            Iterator<Bullet> iterator = tank.bullets.iterator();
            while (iterator.hasNext()) {
                Bullet temp = iterator.next();
                temp.pause = pause;
                if (!temp.active) {                 //子弹消亡的场合,移除子弹
                    iterator.remove();
                } else {                            //走到这里,说明子弹存活。
                    switch (tank.party) {           //看看是哪个阵营发出的子弹,去判定别的阵营的坦克
                        case 0:
                            if (bossActive && checkHit(boss, temp, tank)) {
                                break;
                            }
                            for (Tank t : enemy) {
                                if (checkHit(t, temp, tank)) {      //遍历敌坦克,看看打中了谁。打中则停止判定
                                    break;
                                }
                            }
                            break;
                        case 1:
                            checkHit(player, temp, tank);           //看看有没有击中玩家
                        default:
                    }
                    drawBullet(g, tank.vehicle, temp.getDir(), temp.getX(), temp.getY());       //画出子弹
                }
            }
        }
    
        //画出子弹。这个方法通常由上个方法调用,能画出朝向正确的子弹
        private void drawBullet(Graphics g, Vehicle vehicle, char dir, int x, int y) {
            boolean b = dirState2(dir);
            switch (vehicle.BULLET) {
                case 'n':
                    image = dirState(dir) ? InputImage.nBullet : InputImage.nBulletR;
                    break;
                case 'e':
                    image = dirState(dir) ? InputImage.eBullet : InputImage.eBulletR;
                    break;
                case 'g':
                    image = dirState(dir) ? InputImage.gBullet : InputImage.gBulletR;
                    break;
            }
            g.drawImage(image, b ? x + 10 : x, b ? y + 10 : y, b ? -10 : 10, b ? -10 : 10, this);
        }
    
    
        //画出爆炸。爆炸分为几个阶段,而且不同坦克也有不同爆炸特效
        private void drawExplosion(Graphics g, Tank tank) {
            if (tank.specialCount > 350) {
                drawTank(g, tank);
            }
            switch (tank.vehicle) {
                case Tank_E3:
                    if (tank.specialCount < 600 && tank.specialCount >= 100) {
                        if (tank.specialCount >= 500) {
                            image = InputImage.explosion1;
                        } else if (tank.specialCount >= 400) {
                            image = InputImage.explosion2;
                        } else if (tank.specialCount >= 300) {
                            image = InputImage.explosion3;
                        } else if (tank.specialCount >= 200) {
                            image = InputImage.explosion4;
                        } else {
                            image = InputImage.explosion5;
                        }
                        g.drawImage(image, tank.x, tank.y, 80, 80, this);
                    }
                    break;
                case Tank_E1:
                case Tank_E2:
                default:
                    if (tank.specialCount < 450 && tank.specialCount >= 50) {
                        if (tank.specialCount >= 250) {
                            image = InputImage.explosion1;
                        } else if (tank.specialCount > 100) {
                            image = InputImage.explosion2;
                        } else {
                            image = InputImage.explosion3;
                        }
                        g.drawImage(image, tank.x, tank.y, 80, 80, this);
                    }
            }
            tank.specialCount = Math.max(tank.specialCount, 0);
        }
    
    
        //下面这两个方法可以帮助其他方法画出正确朝向的对象
        private boolean dirState(char dir) {
            return dir == 'u' || dir == 'd';
        }
        private boolean dirState2(char dir) {
            return dir == 'l' || dir == 'd';
        }
    
    
        //检查子弹是否击中目标
        private boolean checkHit(Tank tank, Bullet bullet, Tank from) {
            if (hit(tank, bullet) && !bullet.hasHit(tank)) {        //检查是否击中目标 及 目标是否已被击中过
                bullet.setHit(tank);
                if (tank.invincible > 0) {
                    bullet.pierce = 0;              //撞到无敌的坦克时,子弹直接报销
                }
                bullet.active = bullet.pierce > 0;          //子弹穿透降为 0 的场合,子弹消亡
                if (!tank.takeDamage(from, bullet.getDir())) {                      //坦克伤害计数完后,在这里把 HP 归 0 的坦克记为已销毁
                    tank.alive = false;
                }
                return true;
            }
            return false;
        }
    
        //看看子弹是否击中坦克。由上个方法调用
        private boolean hit(Tank tank, Bullet bullet) {
            if (!tank.alive) {
                return false;
            }
            if (tank.vehicle == Vehicle.Basic) {
                return pIn(tank.x + 30, 20, bullet.getX() + 5) && pIn(tank.y + 30, 20, bullet.getY() + 5);
            }
            switch (tank.getDir()) {
                case 'u':
                case 'd':
                    return pIn(tank.x + 20, 40, bullet.getX() + 5) && pIn(tank.y + 3, 76, bullet.getY() + 5);
                case 'r':
                case 'l':
                    return pIn(tank.x + 3, 76, bullet.getX() + 5) && pIn(tank.y + 20, 40, bullet.getY() + 5);
            }
            return false;
        }
    
        //看看某个坐标(横坐标或纵坐标)是否在[p, p + w]范围内
        private boolean pIn(int p, int w, int toCheck) {
            return toCheck >= p && toCheck <= (p + w);
        }
    
        @Override
        public void keyTyped(KeyEvent e) {
    
        }
    
    
        //监视器。以下方式保证了动画的流畅度
        @Override
        public void keyPressed(KeyEvent e) {
            switch (e.getKeyCode()) {
                case KeyEvent.VK_W:
                    isPressingW = true;
                    break;
                case KeyEvent.VK_A:
                    isPressingA = true;
                    break;
                case KeyEvent.VK_D:
                    isPressingD = true;
                    break;
                case KeyEvent.VK_S:
                    isPressingS = true;
                    break;
                case KeyEvent.VK_J:         //J 是开火键
                    isFiring = true;
                    break;
    //            //===================以下为测试用=====================
    //            case KeyEvent.VK_Q:                                     //该 case 仅测试用。  务必删除!务必删除!
    //                for (Tank tank : enemy) {
    //                    tank.alive = false;
    //                    tank.releaseCollisionSize();
    //                }
    //                break;
    //            case KeyEvent.VK_E:                                     //该 case 仅测试用。  务必删除!务必删除!
    //                deadEnemy.clear();
    //                break;
    //            case KeyEvent.VK_1:                                     //该 case 仅测试用。  务必删除!务必删除!
    //                player.setVehicle(Vehicle.Tank_Lv1);
    //                player.releaseCollisionSize();
    //                break;
    //            case KeyEvent.VK_2:                                     //该 case 仅测试用。  务必删除!务必删除!
    //                player.setVehicle(Vehicle.Tank_Lv2);
    //                player.releaseCollisionSize();
    //                break;
    //            case KeyEvent.VK_3:                                     //该 case 仅测试用。  务必删除!务必删除!
    //                player.setVehicle(Vehicle.Tank_Lv3);
    //                player.releaseCollisionSize();
    //                break;
    //            case KeyEvent.VK_4:                                     //该 case 仅测试用。  务必删除!务必删除!
    //                player.setVehicle(Vehicle.Tank_Special);
    //                player.releaseCollisionSize();
    //                break;
    //            case KeyEvent.VK_5:                                     //该 case 仅测试用。  务必删除!务必删除!
    //                player.setVehicle(Vehicle.Basic);
    //                player.releaseCollisionSize();
    //                break;
    //            case KeyEvent.VK_Z:                                     //该 case 仅测试用。  务必删除!务必删除!
    //                player.buffedDef = 100;
    //                break;
    //            case KeyEvent.VK_X:                                     //该 case 仅测试用。  务必删除!务必删除!
    //                player.buffedDef = 0;
    //                break;
    //            case KeyEvent.VK_C:                                     //该 case 仅测试用。  务必删除!务必删除!
    //                player.invincible = 1000000;
    //                break;
    //            case KeyEvent.VK_V:                                     //该 case 仅测试用。  务必删除!务必删除!
    //                player.invincible = 0;
    //                break;
                //===================以上为测试用=====================
            }
            isGoing();          //看看玩家是否正在移动
            isFiring();         //看看玩家是否正在开火
        }
    
    
        //同上
        @Override
        public void keyReleased(KeyEvent e) {
            switch (e.getKeyCode()) {
                case KeyEvent.VK_W:             //向上
                    isPressingW = false;
                    break;
                case KeyEvent.VK_A:             //向左
                    isPressingA = false;
                    break;
                case KeyEvent.VK_D:             //向右
                    isPressingD = false;
                    break;
                case KeyEvent.VK_S:             //向下
                    isPressingS = false;
                    break;
                case KeyEvent.VK_J:             //开火
                    isFiring = false;
                    break;
                case KeyEvent.VK_P:             //暂停
                    gamePause();
                    saved = false;
                    loaded = false;
                    break;
                case KeyEvent.VK_I:             //读档(需要暂停)
                    toLoad = true;
                    break;
                case KeyEvent.VK_O:             //存档(需要暂停)
                    toSave = true;
                    break;
            }
            isGoing();
            isFiring();
        }
    
    
        //更新玩家的移动状态
        private void isGoing() {
            if (!player.pause) {
                if (isPressingW) {
                    player.dir = 'u';
                } else if (isPressingS) {
                    player.dir = 'd';
                } else if (isPressingA) {
                    player.dir = 'l';
                } else if (isPressingD) {
                    player.dir = 'r';
                }
            }
            player.isMoving = isPressingA || isPressingS || isPressingD || isPressingW;
        }
    
        //更新玩家的开火状态
        private void isFiring() {
            player.isFiring = isFiring;
        }
    
        //生成敌人,直到允许的最大数量
        private void creatEnemy() {
            while (enemy.size() < enemyMax && enemy.size() < maps[level].levelEnemyNum - deadEnemy.size()) {
                int[] temp = null;
                for (int[] ints : enemyPosition) {
                    if (!enemyPositions.get(ints)) {            //为坦克分配初始位置
                        temp = ints;
                        enemyPositions.put(ints, true);
                        break;
                    }
                }
                if (temp == null) {                 //应该不会抛出异常。但,以防万一,加上这句
                    throw new RuntimeException();
                }
                Tank tempTank = new Enemy(temp[0], temp[1], maps[level]);
                if (deadEnemy.size() == 0) {
                    tempTank.specialCount = 0;          //坦克有延迟显示。这里取消了第一组坦克(初始敌人坦克)的延迟显示
                }
                enemy.add(tempTank);         //在 敌人集 中添加新的坦克
                tempTank.start();            //启动该新坦克线程
            }
        }
    
        //这个方法有两个作用:
        //  1. 其他敌人全灭时,激活 BOSS
        //  2. 所有敌人全灭时,进入下一关
        private void checkLevelClear() {
            if (deadEnemy.size() == maps[level].levelEnemyNum && !bossActive && boss != null && boss.alive) {
                boss.start();
                bossActive = true;
            } else if (deadEnemy.size() >= maps[level].levelEnemyNum && (boss == null || !boss.alive)) {
                gameSuspend = true;
            }
        }
    
        //关卡间设置短暂停顿
        private void pauseBetweenLevels(int time) {
            gameSuspend = false;
            new Thread(new Timing(time)).start();
        }
    
        //每次调用会切换 暂停状态。
        private void gamePause() {
            this.pause = !this.pause;
        }
    
        //进入下一关时,加载下一关数据
        private void nextLevel() {
            player.releaseCollisionSize();
            player.x = 200;
            player.y = height - 200;
            level++;
            for (int i = 0; i < maps[level].mapData.length; i++) {
                for (int j = 0; j < maps[level].mapData[i].length; j++) {
                    inputMapData(i, j);
                }
            }
            for (int[] ints : enemyPosition) {
                enemyPositions.put(ints, false);
            }
            for (Tank tank : deadEnemy) {
                tank.releaseCollisionSize();
            }
            enemy.clear();
            deadEnemy.clear();
            player.lifeRemains = player.vehicle.LIFE;
            player.invincible = 0;
            gameSuspend = false;
            boss = maps[level].boss;
            if (boss != null) {
                boss.x = Math.max(width / 2 - 40, 0);
                boss.y = 70;
            }
            bossActive = false;
        }
    
        //从 Map 类中 加载既定的地图数据
        private void inputMapData(int x, int y) {
            for (int k = 0; k < 40; k++) {
                for (int l = 0; l < 40; l++) {
                    mapData[(x * 40) + k][(y * 40) + l][1] = maps[level].mapData[x][y][1];
                    mapData[(x * 40) + k][(y * 40) + l][0] = maps[level].mapData[x][y][0];
                }
            }                                                                           //*****此处需要修改*****
            //这里需要对 地图逻辑、坦克行进逻辑 都进行修改。
            // 现行方法是:由 mapData[][] 记录每个像素的数据。每个像素点的占用情况实时更新。
            // 改成:由 mapData[][] 记录所有 40 * 40 方格的数据。
            //      相信这个办法应该能解决现行方法偶发的空气墙问题,扩展性也会更好。存档大小也能缩小,因而也有机会优化存档逻辑。
            //      目前的代码基本兼容这个新方法。Map 类的数据本来就是 40 * 40 方格模式。地图大小、各类模型尺寸也都是 40 的整数倍
        }
    
        //读档
        void loadData(){
            if (pause) {
                ObjectInputStream ois = null;
                try {
                    if (!TankGame.savePath.exists()) {
                        TankGame.savePath.createNewFile();
                    }
                    ois = new ObjectInputStream(new FileInputStream(TankGame.savePath));
                    if((MyPanel)ois.readObject() != null){
                        player = (Player) ois.readObject();
    //                    mapData = (int[][][]) ois.readObject();
                        enemy = (Vector<Tank>) ois.readObject();
                        deadEnemy = (Vector<Tank>) ois.readObject();
                        enemyPositions = (LinkedHashMap<int[], Boolean>) ois.readObject();
                        enemyPosition = (int[][]) ois.readObject();
                        maps = (Map[]) ois.readObject();
                        boss = (Tank) ois.readObject();
                        level = ois.readInt();
                        bossActive = ois.readBoolean();
                    }
                } catch (IOException | ClassNotFoundException ex) {
                    ex.printStackTrace();
                } finally {
                    try {
                        ois.close();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
                pause = true;
                loaded = true;
                for (int i = 0; i < maps[level].mapData.length; i++) {
                    for (int j = 0; j < maps[level].mapData[i].length; j++) {
                        inputMapData(i, j);
                    }
                }
                for (Tank tank : enemy) {
                    tank.start();
                    for (Bullet bullet : tank.bullets) {
                        bullet.start();
                    }
                }
                for (Tank tank : deadEnemy) {
                    for (Bullet bullet : tank.bullets) {
                        bullet.start();
                    }
                }
                if(bossActive && boss.alive){
                    boss.start();
                }
                player.start();
            }
        }
    
        //存档
        private void saveData(){
            if (pause) {
                ObjectOutputStream oos = null;
                try {
                    if (!TankGame.savePath.exists()) {
                        TankGame.savePath.createNewFile();
                    }
                    oos = new ObjectOutputStream(new FileOutputStream(TankGame.savePath));
                    oos.writeObject(TankGame.mp);
                    oos.writeObject(player);
    //                oos.writeObject(mapData);         //占用太大
                    oos.writeObject(enemy);
                    oos.writeObject(deadEnemy);
                    oos.writeObject(enemyPositions);
                    oos.writeObject(enemyPosition);
                    oos.writeObject(maps);
                    oos.writeObject(boss);
                    oos.writeInt(level);
                    oos.writeBoolean(bossActive);
                    saved = true;
                } catch (IOException ex) {
                    ex.printStackTrace();
                } finally {
                    try {
                        oos.close();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }
    }
  • Tank.java

    package com.melody.tank_game;
    
    import java.io.Serializable;
    import java.util.Vector;
    
    /**
     * @author Melody
     * @version 1.0
     */
    //坦克类。这是一个抽象类,Player 和 Enemy 类会继承此类
    abstract public class Tank extends Thread implements Serializable {             //为什么是 继承Thread 而不是 实现Runnable?因为我写这些代码的时候还没学到那里……
        /*从上到下依次是:横坐标、纵坐标、坦克类型、方向、当前生命值、速度增幅、攻速增幅、攻击范围增幅、攻击力增幅、防御力增幅、
                        开火间隔、开火间隔计时、子弹集、是否存活、无敌状态持续时间、阵营、死亡计时
         */
        protected int x;
        protected int y;
        protected Vehicle vehicle = Vehicle.Basic;
        protected char dir = 'u';
        protected double lifeRemains;
        protected int buffedSpeed = 0;
        protected int buffedAtkSpeed = 0;
        protected int buffedAtkRange = 0;
        protected int buffedAtk = 0;
        protected int buffedDef = 0;
        protected int fireCD = 1;
        public int fireCount = 0;
        public final Vector<Bullet> bullets = new Vector<>();   //所有活动中的子弹会被存放在里面
        protected boolean alive = true;
        public int invincible = 0;
        public int party;
        public boolean pause = false;
        public int specialCount = 800;             //坦克死亡后,该计时才开始计数。这个计数是为了播放爆炸动画。计数完毕后该对象会被消除。
    
        public Tank(int x, int y) {
            this.x = x;
            this.y = y;
            setVehicle(Vehicle.Basic);
        }
    
    
        //设置坦克种类
        public void setVehicle(Vehicle vehicle) {
            this.vehicle = vehicle;
            this.lifeRemains = vehicle.LIFE;
            this.fireCD = 200 / getAtkSpeed();      //开火间隔由这个公式算出
        }
    
        public char getDir() {
            return dir;
        }
    
        //受到伤害。这个方法由子类实现
        abstract protected boolean takeDamage(Tank tank, char dir);
    
    
        //移动。
    //    abstract void move(char move);
    
        protected abstract void move();
    
    
        protected int[] tryMove(){
            int[] temp = new int[2];
            temp[0] = x;
            temp[1] = y;
            switch (dir) {
                case 'u':
                    temp[1]--;
                    break;
                case 'd':
                    temp[1]++;
                    break;
                case 'l':
                    temp[0]--;
                    break;
                case 'r':
                    temp[0]++;
                    break;
            }
            return temp;
        }
    
        protected boolean outOfArea(int x, int y) {
            return x < 0 || y < 0 || x > MyPanel.width - 80 || y > MyPanel.height - 80;
        }
    
        protected boolean runIntoSth(int x, int y) {
            boolean checking = false;
            switch (dir) {
                case 'u':
                    checking = MyPanel.mapData[x + 40 - (vehicle.SIZE / 2)][y + 40 - (vehicle.SIZE / 2)][1] == 0 &&
                            MyPanel.mapData[x + 40][y + 40 - (vehicle.SIZE / 2)][1] == 0 &&
                            MyPanel.mapData[x + 39 + (vehicle.SIZE / 2)][y + 40 - (vehicle.SIZE / 2)][1] == 0;
                    break;
                case 'd':
                    checking = MyPanel.mapData[x + 40 - (vehicle.SIZE / 2)][y + 39 + (vehicle.SIZE / 2)][1] == 0 &&
                            MyPanel.mapData[x + 40][y + 40 + (vehicle.SIZE / 2)][1] == 0 &&
                            MyPanel.mapData[x + 39 + (vehicle.SIZE / 2)][y + 39 + (vehicle.SIZE / 2)][1] == 0;
                    break;
                case 'r':
                    checking = MyPanel.mapData[x + 39 + (vehicle.SIZE / 2)][y + 40 - (vehicle.SIZE / 2)][1] == 0 &&
                            MyPanel.mapData[x + 39 + (vehicle.SIZE / 2)][y + 40][1] == 0 &&
                            MyPanel.mapData[x + 39 + (vehicle.SIZE / 2)][y + 39 + (vehicle.SIZE / 2)][1] == 0;
                    break;
                case 'l':
                    checking = MyPanel.mapData[x + 40 - (vehicle.SIZE / 2)][y + 40 - (vehicle.SIZE / 2)][1] == 0 &&
                            MyPanel.mapData[x + 40 - (vehicle.SIZE / 2)][y + 40][1] == 0 &&
                            MyPanel.mapData[x + 40 - (vehicle.SIZE / 2)][y + 39 + (vehicle.SIZE / 2)][1] == 0;
                    break;
            }
            return !checking;
        }
    
        protected void setCollisionSize(int tempX, int tempY, int type, int hp) {
            for (int i = 0; i < vehicle.SIZE; i++) {
                for (int j = 0; j < vehicle.SIZE; j++) {
                    MyPanel.mapData[tempX + 40 - (vehicle.SIZE / 2) + i][tempY + 40 - (vehicle.SIZE / 2) + j][1] = type;
                    MyPanel.mapData[tempX + 40 - (vehicle.SIZE / 2) + i][tempY + 40 - (vehicle.SIZE / 2) + j][0] = hp;
                }
            }
        }
    
        protected void releaseCollisionSize() {
            for (int i = 0; i < vehicle.SIZE; i++) {
                for (int j = 0; j < vehicle.SIZE; j++) {
                    MyPanel.mapData[x + 40 - (vehicle.SIZE / 2) + i][y + 40 - (vehicle.SIZE / 2) + j][1] = 0;
                    MyPanel.mapData[x + 40 - (vehicle.SIZE / 2) + i][y + 40 - (vehicle.SIZE / 2) + j][0] = 0;
                }
            }
        }
    
        //开火
        public void fire() {
            if (fireCount > 0) {        //fireCount > 0 说明开火CD 还没好,直接 return
                return;
            }
            if (vehicle == Vehicle.Basic) {     //Basic 体积和一般坦克不同,故而发射子弹的位置也不同
                fireCount = 10;                 //其武器也不同,所以 开火CD 的逻辑也不同。这里使其每发子弹的间隔很短
                switch (dir) {
                    case 'u':
                        fire(x + 39, y + 17, dir, getAtkRange());
                        break;
                    case 'd':
                        fire(x + 31, y + 53, dir, getAtkRange());
                        break;
                    case 'l':
                        fire(x + 17, y + 31, dir, getAtkRange());
                        break;
                    case 'r':
                        fire(x + 53, y + 39, dir, getAtkRange());
                        break;
                }
            } else {
                fireCount = fireCD;
                switch (dir) {
                    case 'u':
                        fire(x + 35, y - 10, dir, getAtkRange());
                        break;
                    case 'd':
                        fire(x + 35, y + 80, dir, getAtkRange());
                        break;
                    case 'l':
                        fire(x - 10, y + 35, dir, getAtkRange());
                        break;
                    case 'r':
                        fire(x + 80, y + 35, dir, getAtkRange());
                        break;
                }
            }
        }
    
        public void fire(int x, int y, char dir, int range) {
            Bullet tempBullet = new Bullet(vehicle.BULLET, x, y, dir, range, this);
            bullets.add(tempBullet);  //创建一个新的 子弹 实例,并让其运行
            tempBullet.start();
        }
    
        protected boolean isFront(char dir) {               //判断是否被从正面击中。如若是,坦克的防御力生效
            switch (dir) {
                case 'u':
                    return this.dir == 'd';
                case 'd':
                    return this.dir == 'u';
                case 'r':
                    return this.dir == 'l';
                case 'l':
                    return this.dir == 'r';
                default:
                    return false;
            }
        }
    
        public void pause(boolean pause){
            this.pause = pause;
        }
    
    
        public int getAtk() {
            return vehicle.ATK + buffedAtk;
        }
    
        public int getDef() {
            return vehicle.DEF + buffedDef;
        }
    
        public int getAtkSpeed() {
            return vehicle.ATK_SPEED + buffedAtkSpeed;
        }
    
        public int getSpeed() {
            return vehicle.SPEED + buffedSpeed;
        }
    
        public int getAtkRange() {
            return vehicle.ATK_RANGE + buffedAtkRange;
        }
    
    }
  • Player.java

    package com.melody.tank_game;
    
    /**
     * @author Melody
     * @version 1.0
     */
    
    
    public class Player extends Tank {
        //三个属性依次是:经验值、是否正在移动、是否正在开火
        public int enhance = 0;
        //这两个变量由 MyPanel 的 监视器决定。按下移动/开火键时,变为 true。松开时,变为 false
        public boolean isMoving = false;
        public boolean isFiring = false;
    
        public Player(int x, int y) {
            super(x, y);
            party = 0;              //阵营为 0
            setVehicle(Vehicle.Basic);          //设置初始坦克。               可以随意修改
        }
    
    
        @Override
        public void run() {
            while (alive) {
                try {
                    Thread.sleep(100 / getSpeed());         //速度越快、行动越快
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (!pause) {
                    if (invincible > 0) {       //无敌冷却减少
                        invincible--;
                    }
                    if (isMoving) {             //正在移动的场合,使其移动
                        move();
                    }
                    if (isFiring) {             //正在开火的场合,使其开火
                        fire();
                    } else if (vehicle == Vehicle.Basic) {          //Basic 是特别的。按住开火键的场合,开火CD才减少,不然会增加。
                        // 这样做是为了模拟 加特林 的手感
                        fireCount += fireCount >= fireCD ? 0 : 1;
                    } else {
                        fireCount--;            //开火CD 自然减少
                    }
                }
            }
        }
    
    
        @Override
        protected void move() {
            int[] temp = tryMove();
            if (!outOfArea(temp[0], temp[1]) && !runIntoSth(temp[0], temp[1])) {
                releaseCollisionSize();
                setCollisionSize(temp[0], temp[1], -2, -1);
                x = temp[0];
                y = temp[1];
            }
        }
    
        @Override
        public void fire() {
            if (fireCount > 0) {            //如果 开火CD 没到,让其自然减少,然后 return
                fireCount--;
                return;
            }
            super.fire();               //真正的开火
        }
    
    
        //获得经验值。每次消灭敌人坦克会调用该方法
        public void enhanced(Vehicle vehicle) {
            if (this.vehicle == Vehicle.Basic && Math.random() > 0.9) {         //特殊设定:Basic 的场合,消灭敌人有概率夺取坦克
                switch (vehicle) {
                    case Tank_E1:
                        setVehicle(Vehicle.Tank_Lv1);
                        break;
                    case Tank_E2:
                        setVehicle(Vehicle.Tank_Lv2);
                        break;
                    case Tank_E3:
                        setVehicle(Vehicle.Tank_Lv3);
                        break;
                    case Tank_Special:
                        setVehicle(Vehicle.Tank_Special);
                        break;
                }
            } else {
                switch (vehicle) {
                    case Tank_E1:
                        enhance += 1;
                        break;
                    case Tank_E2:
                        enhance += 2;
                        break;
                    case Tank_E3:
                        enhance += 3;
                        break;
                    case Tank_Special:
                        enhance += 10;
                        break;
                }
                enhanced();         //看看能不能升级
            }
        }
    
    
    
        //查看是否升级
        private void enhanced() {
            if (vehicle.LEVEL_UP_REQUIRED > 0 && enhance >= vehicle.LEVEL_UP_REQUIRED) {
                enhance -= vehicle.LEVEL_UP_REQUIRED;
                releaseCollisionSize();
                switch (vehicle) {
                    case Basic:
                        setVehicle(Vehicle.Tank_Lv1);
                        break;
                    case Tank_Lv1:
                        setVehicle(Vehicle.Tank_Lv2);
                        break;
                    case Tank_Lv2:
                        setVehicle(Vehicle.Tank_Lv3);
                        break;
                    default:
                }
                setCollisionSize(x, y, -2, -1);
            }
        }
    
    
        //设置坦克。每次设置会重置经验值
        @Override
        public void setVehicle(Vehicle vehicle) {
            super.setVehicle(vehicle);
            this.enhance = 0;
            if (vehicle == Vehicle.Basic) {
                fireCount = fireCD;
            }
        }
    
    
        //受到伤害。传入的是造成伤害的坦克,和击中的子弹方向
        @Override
        protected boolean takeDamage(Tank tank, char dir) {
            double damage = tank.getAtk() * vehicle.DEF_BY_PERCENT - (isFront(dir) ? this.getDef() : 0);    //侧后方中弹,防御力不生效
            if (invincible > 0) {               //无敌的场合,不受伤害
                return true;
            } else if ((lifeRemains -= Math.max(damage, 0.1)) <= 0) {           //这里扣除了生命值。最少扣除 0.1。
                invincible += 200;              //坦克被打爆,给一段无敌时间
                releaseCollisionSize();
                switch (vehicle) {              //坦克降级。Basic 的场合,游戏结束
                    case Tank_Lv3:
                    case Tank_Lv2:
                    case Tank_Lv1:
                    case Tank_Special:
                        setVehicle(Vehicle.Basic);
                        break;
                    case Basic:
                        return false;
                }
               
                setCollisionSize(x, y, -2, -1);
            }
            invincible += 100;      //只要受到了伤害,产生短暂无敌时间
            return true;
        }
    }
  • Enemy.java

    package com.melody.tank_game;
    
    /**
     * @author Melody
     * @version 1.0
     */
    
    //敌人的坦克
    public class Enemy extends Tank {
        //step:坦克的前进次数。归 0 时会转向
        private int step = 0;
        private final double FIRE_PROBABILITY;
    
        public Enemy(Vehicle vehicle) {
            super(0, 0);
            setVehicle(vehicle);
            party = 1;
            dir = 'd';
            invincible = 300;
            FIRE_PROBABILITY = 0.95;
        }
    
        public Enemy(int x, int y, Map map) {
            this(x, y);
            giveVehicle(map.probability_E1, map.probability_E2);
        }
    
        public Enemy(int x, int y) {
            super(x, y);
            party = 1;              //敌方坦克阵营为 1
    //        randomDir();
    //        giveVehicle();
            dir = 'd';
            invincible = 130;       //敌方坦克出生时,赋予短时间的无敌
            FIRE_PROBABILITY = 0.985;
        }
    
        @Override
        public void run() {
            try {
                Thread.sleep(700 + specialCount);          //坦克出生时,使其短暂休眠。
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            specialCount = 200 + (int) (80 * lifeRemains);         //坦克的 死亡计数,和坦克种类有关
            while (alive) {
                try {
                    Thread.sleep((100 / getSpeed()));       //坦克速度越快,其所有行动速度越快
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (!pause) {
                    if (invincible > 0) {           //无敌时间减少
                        invincible--;
                    }
                    if (fireCount > 0) {            //开火CD计数减少
                        fireCount--;
                    }
                    move();               //移动
                }
            }
            releaseCollisionSize();
            while (specialCount >= 0) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (!pause) {
                    specialCount--;                                    //*****此处需要修改*****
                }
            }
        }
    
    
        private void giveVehicle(double probabilityE1, double probabilityE2) {
            double result = Math.random();
            if (result <= probabilityE1) {
                setVehicle(Vehicle.Tank_E1);
            } else if (result <= probabilityE2) {
                setVehicle(Vehicle.Tank_E2);
            } else {
                setVehicle(Vehicle.Tank_E3);
            }
        }
    
        @Override
        public void move() {            //传入的字符没用。
            if (step <= 0) {
                randomDir();                        //步数归 0 时,赋予其随机方向
                realMove();
                step = (int) (Math.random() * 200 + 60 - 3 * getSpeed());       //赋予其随机的步数
            } else if (step <= 60 - 2 * getSpeed()) {       //最后几步的时候,让坦克在原地稍作停留(不移动)
                step--;
            } else {
                realMove();
                step--;
            }
            if (Math.random() > FIRE_PROBABILITY) {            //使敌方坦克有 1.5% 的概率开火。这个概率经过测试,比较合适           这里可以更改
                fire();
            }
        }
    
        private void realMove() {
            int[] temp = tryMove();
            if (!outOfArea(temp[0], temp[1]) && !runIntoSth(temp[0], temp[1])) {
                releaseCollisionSize();
                setCollisionSize(temp[0], temp[1], -1, -1);
                x = temp[0];
                y = temp[1];
            } else {
                step -= 10;
            }
        }
    
        //赋予随机方向(概率均匀)
        private void randomDir() {
            switch ((int) (Math.random() * 4)) {
                case 0:
                    dir = 'u';
                    break;
                case 1:
                    dir = 'r';
                    break;
                case 2:
                    dir = 'd';
                    break;
                case 3:
                    dir = 'l';
                    break;
            }
        }
    
    
        //受伤。传入的是造成伤害的坦克(以计算伤害),及子弹方向(用于判断是否是正面中弹)
        @Override
        protected boolean takeDamage(Tank tank, char dir) {
            double damage = tank.getAtk() * vehicle.DEF_BY_PERCENT - (isFront(dir) ? getDef() : 0);     //侧后方中弹的场合,防御力不生效
            if (invincible > 0) {               //如果坦克无敌,不会受到伤害
                return true;
            } else {
                return (this.lifeRemains -= Math.max(damage, 0.1)) > 0;        //在这里进行了生命值的扣除,最少扣 0.1。返回是否生命值有剩
            }
        }
    }
  • Vehicle.java

    package com.melody.tank_game;
    
    import java.io.Serializable;
    
    /**
     * @author Melody
     * @version 1.0
     */
    
    
    //该枚举类存放所有可能的坦克种类及其参数
    public enum Vehicle implements Serializable{
        //Basic 是特别的。设定上,Basic 是离开载具,扛着加特林的驾驶员。其体积和一般坦克不同
        Basic(1, 2, 0, 20, 3, 400, 5, 10, 'g', 10),
        Tank_Lv1(5, 11, 1, 12, 4, 1000, 5, 1, 'n'),
        Tank_Lv2(7, 15, 2, 12, 4, 1000, 10, 0.9, 'n'),
        Tank_Lv3(10, 17, 3, 12, 4, 1600, -1, 0.8, 'n'),
        Tank_E1(5, 5, 1, 11, 4, 1000, -1, 1, 'n'),
        Tank_E2(7, 10, 2, 10, 4, 1000, -1, 0.8, 'n'),
        Tank_E3(10, 18, 3, 9, 4, 1600, -1, 0.6, 'n'),
        Tank_Special(25, 13, 4, 18, 1, 100000, -1, 0.4, 'e');
    
        //从上到下依次是:攻击力、生命值、防御力、速度、攻击速度、攻击范围、升级经验、减伤、子弹种类
        public final int ATK;
        public final double LIFE;
        public final int DEF;
        public final int SPEED;
        public final int ATK_SPEED;
        public final int ATK_RANGE;
        public final int LEVEL_UP_REQUIRED;
        public final double DEF_BY_PERCENT;
        public final char BULLET;
        public final int SIZE;
    
    
        Vehicle(int atk, double life, int def, int speed, int atkSpeed, int atkRange,
                int levelUpRequired, double defByPercent, char bullet, int size) {
            this.ATK = atk;
            this.LIFE = life;
            this.DEF = def;
            this.SPEED = speed;
            this.ATK_SPEED = atkSpeed;
            this.ATK_RANGE = atkRange;
            this.LEVEL_UP_REQUIRED = levelUpRequired;
            this.DEF_BY_PERCENT = defByPercent;
            this.BULLET = bullet;
            this.SIZE = size;
        }
    
        Vehicle(int atk, double life, int def, int speed, int atkSpeed, int atkRange,
                int levelUpRequired, double defByPercent, char bullet) {
            this(atk, life, def, speed, atkSpeed, atkRange, levelUpRequired, defByPercent, bullet, 80);
        }
    }
    
    
    //子弹类
    class Bullet extends Thread implements Serializable {
        //从上到下依次是:横坐标、纵坐标、方向、子弹速度、子弹是否存在、最大飞行范围、穿透数量、已命中的坦克
        private int x;
        private int y;
        private char dir;
        private final int BULLET_SPEED;
        public boolean active = true;
        private int range;
        public int pierce;
        private Tank master;
        private Tank[] hit;
        public boolean pause = false;
    
    
        public Bullet(char type, int x, int y, char dir, int range, Tank master) {
            switch (type) {
                case 'e':
                    this.BULLET_SPEED = 3;
                    this.pierce = 3;
                    break;
                case 'g':
                    this.BULLET_SPEED = 2;
                    this.pierce = 1;
                    break;
                case 'n':
                default:
                    this.BULLET_SPEED = 1;
                    this.pierce = 1;
                    break;
    
            }
            hit = new Tank[pierce];
            this.x = x;
            this.y = y;
            this.dir = dir;
            this.range = range;
            this.master = master;
        }
    
        public void move() {
            switch (dir) {
                case 'u':
                    y -= BULLET_SPEED;
                    break;
                case 'd':
                    y += BULLET_SPEED;
                    break;
                case 'r':
                    x += BULLET_SPEED;
                    break;
                case 'l':
                    x -= BULLET_SPEED;
                    break;
            }
            if ((range -= BULLET_SPEED) <= 0 || outOfArea(x, y)) {     //如果达到最大飞行范围,则销毁该子弹
                active = false;
            }
        }
    
        private boolean outOfArea(int x, int y) {
            return x < 0 || y < 0 || x > MyPanel.width - 10 || y > MyPanel.height - 10;
        }
    
        public int getX() {
            return x;
        }
    
        public int getY() {
            return y;
        }
    
        public char getDir() {
            return dir;
        }
    
        public int getBULLET_SPEED() {
            return BULLET_SPEED;
        }
    
        public void setHit(Tank tank) {             //当有坦克被该子弹击中后,该方法会被调用。一般由 MyPanel 类中的 checkHit 方法调用
            hit[hit.length - pierce--] = tank;
        }
    
        public boolean hasHit(Tank tank) {          //判断一辆坦克是否已被击中过。一般由 MyPanel 类中的 checkHit 方法调用
            for (Tank t : hit) {
                if (t == tank) {
                    return true;
                }
            }
            return false;
        }
    
        public void pause(boolean pause) {
            this.pause = pause;
        }
    
    
        @Override
        public void run() {
            while (active) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (!pause) {
                    move();
                }
            }
        }
    }
  • Map.java

    package com.melody.tank_game;
    
    import java.io.Serializable;
    
    /**
     * @author Melody
     * @version 1.0
     */
    public enum Map implements Serializable {
        Level1(10, 1, 1, null, 0, creatNewMap1()),
        Level2(15, 0.5, 1, Vehicle.Tank_E2, 10, creatNewMap2()),
        Level3(20, 0.33, 0.66, Vehicle.Tank_E3, 15, creatNewMap3()),
        Level4(20, 0.2, 0.5, Vehicle.Tank_Special, 20, creatNewMap4());
        public final int levelEnemyNum;
        public final double probability_E1;
        public final double probability_E2;
        public final Tank boss;
        public int[][][] mapData;
        //[0]:种类。-1 坦克;0 道路;1 墙;2 草丛;3 水池
        //[1]:HP。-1 不可破坏;0 可以通行;1 basic以外任意子弹击中1次可以破坏;2、3 同 1
    
        Map(int levelEnemyNum, double probability_E1, double probability_E2, Vehicle boss, int bossBuff, int[][][] map) {
            this.levelEnemyNum = levelEnemyNum;
            this.probability_E1 = probability_E1;
            this.probability_E2 = probability_E2;
            if (boss == null) {
                this.boss = null;
            } else {
                this.boss = new Enemy(boss);
                this.boss.lifeRemains += bossBuff;
            }
            mapData = map;
        }
    
    
        private static int[][][] creatNewMap1() {
            int[][][] newMap = creatNewMap();
            return newMap;
        }
        private static int[][][] creatNewMap2() {
            int[][][] newMap = creatNewMap();
            return newMap;
        }
        private static int[][][] creatNewMap3() {
            int[][][] newMap = creatNewMap();
            return newMap;
        }
        private static int[][][] creatNewMap4() {
            int[][][] newMap = creatNewMap();
            return newMap;
        }
    
        private static int[][][] creatNewMap() {
            int[][][] newMap = new int[30][20][2];
            newMap = creatBasicMap(newMap);
            return newMap;
        }
    
        private static int[][][] creatBasicMap(int[][][] map){
            for (int i = 0; i < map.length; i++) {
                for (int j = 0; j < map[i].length; j++) {
                    map[i][j][1] = 0;
                    map[i][j][0] = 0;
                }
            }
            return map;
        }
    
        private static void addWall(int[][][] map, int x, int y) {
            map[x][y][0] = 1;
            map[x][y][1] = -1;
        }
    
        private static void addArea(int[][][] map, int x, int y, int kind, int hp) {
            map[x][y][0] = kind;
            map[x][y][1] = hp;
        }
    
        private void removeTankCollisionSize(Tank tank) {
        }
    
        private void setTankCollisionSize(Tank tank) {
        }
    }
  • InputImage.java

    package com.melody.tank_game;
    
    import java.awt.*;
    import java.io.Serializable;
    
    /**
     * @author Melody
     * @version 1.0
     */
    
    
    /*
    该类中存放所有需要的图片。
    包含:坦克图片(tank/basic:4 * 2 + 1 = 9 种);
         子弹图片(bullet:2 * 2 = 4 种);
         护盾图片(bubble:2 * 2 = 4种);
         星星图片(star:2种)、爆炸图片(explosion:5种);
         地形图片(4种)
     */
    public class InputImage implements Serializable {
        public static final Image tank1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_Lv1.png"));
        public static final Image tank2 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_Lv2.png"));
        public static final Image tank3 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_Lv3.png"));
        public static final Image basic = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/P.png"));
        public static final Image basicL = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Pl.png"));
        public static final Image basicR = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Pr.png"));
        public static final Image basicLR = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Plr.png"));
        public static final Image tankE1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_E1.png"));
        public static final Image tankE2 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_E2.png"));
        public static final Image tankE3 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_E3.png"));
        public static final Image tank1r = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_Lv1r.png"));
        public static final Image tank2r = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_Lv2r.png"));
        public static final Image tank3r = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_Lv3r.png"));
        public static final Image tankE1r = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_E1r.png"));
        public static final Image tankE2r = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_E2r.png"));
        public static final Image tankE3r = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_E3r.png"));
        public static final Image tankSp = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_Sp.png"));
        public static final Image tankSpr = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_Spr.png"));
        public static final Image bubble = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Bubble_red.png"));
        public static final Image bubbleR = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Bubble_red_r.png"));
        public static final Image bubbleE = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Bubble.png"));
        public static final Image bubbleER = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Bubble_r.png"));
        public static final Image eBullet = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/E_bullet.png"));
        public static final Image eBulletR = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/E_bullet_r.png"));
        public static final Image nBullet = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/N_bullet.png"));
        public static final Image nBulletR = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/N_bullet_r.png"));
        public static final Image gBullet = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/G_bullet.png"));
        public static final Image gBulletR = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/G_bullet_r.png"));
        public static final Image star = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Star.png"));
        public static final Image nStar = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Star_n.png"));
        public static final Image explosion1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Explosion/Explosion1.png"));
        public static final Image explosion2 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Explosion/Explosion2.png"));
        public static final Image explosion3 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Explosion/Explosion3.png"));
        public static final Image explosion4 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Explosion/Explosion4.png"));
        public static final Image explosion5 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Explosion/Explosion5.png"));
        public static final Image iron = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Landform/Iron.png"));
        public static final Image wall = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Landform/Wall.png"));
        public static final Image water = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Landform/Water.png"));
        public static final Image trees = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Landform/Trees.png"));
    }
  • Timing.java

    package com.melody.tank_game;
    
    /**
     * @author Melody
     * @version 1.0
     */
    public class Timing implements Runnable{
        private int time;
    
        public Timing(int time){
            this.time = time;
        }
        @Override
        public void run() {
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            MyPanel.gameSuspend = true;
        }
    }
  • 导入的图片

    Bubble_r

    Bubble_red

    Bubble_red_r

    E_bullet

    E_bullet_r

    G_Bullet

    G_Bullet_r

    N_bullet

    N_bullet_r

    p

    pl

    plr

    Pr

    Star

    Star_n

    Tank_E1

    Tank_E1r

    Tank_E2

    Tank_E2r

    Tank_E3

    Tank_E3r

    Tank_Lv1

    Tank_Lv1r

    Tank_Lv2

    Tank_Lv2r

    Tank_Lv3

    Tank_Lv3r

    Tank_Sp

    Tank_Spr

    Trees

    Wall

    Water

    Explosion2

    Explosion3

    Explosion4

    Explosion5


<Java>18 项目(坦克大战)
https://i-melody.github.io/2022/01/11/Java/入门阶段/18 项目:坦克大战/
作者
Melody
发布于
2022年1月11日
许可协议