一、简单介绍

写在前面:
这是我通过学习网上的教程写出的代码,并不是简单的复制粘贴,发出此代码的目的主要是为了让大家学习编程的思路,希望可以通过此项目让你学习到新的内容。

实现的内容:
1、单人游戏
2、双人游戏
3、选择关卡
4、游戏帮助
5、游戏关于
6、退出游戏

主要内容

java 回合制卡牌游戏demo java回合制游戏源码_初始化


游戏截图

java 回合制卡牌游戏demo java回合制游戏源码_java 回合制卡牌游戏demo_02


java 回合制卡牌游戏demo java回合制游戏源码_java 回合制卡牌游戏demo_03

GameMain类

package c02.n02.app;

import c02.n02.game.GameFrame;

public class GameMain {
    public static void main(String[] args) {
        //窗口初始化
        new GameFrame();
    }
}

Bullet类

package c02.n02.game;

import c02.n02.tank.Tank;
import c02.n02.util.Constant;

import java.awt.*;

/**
 * 子弹类
 */
public class Bullet {
    public static final int DEFAULT_SPEED = 20;    //子弹默认速度
    public static final int RADIUS = 4;   //炮弹的半径

    private int x, y; //子弹坐标
    private int speed = DEFAULT_SPEED;  //子弹速度
    private int dir;    //方向
    private int atk;    //攻击力
    private Color color;    //颜色

    private boolean visible = true;    //子弹是否可见

    /**
     * 子弹类构造函数
     *
     * @param x     X坐标
     * @param y     Y坐标
     * @param dir   方向
     * @param atk   攻击力
     * @param color 颜色
     */
    public Bullet(int x, int y, int dir, int atk, Color color) {
        this.x = x;
        this.y = y;
        this.dir = dir;
        this.atk = atk;
        this.color = color;
    }

    /**
     * 无参构造函数
     * 给对象池使用的,参数全部默认,因为炮弹是哪个坦克发射的都不知道
     */
    public Bullet() {
    }

    /*====================START=============GETTER/SETTER========================================*/
    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getSpeed() {
        return speed;
    }

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

    public int getDir() {
        return dir;
    }

    public void setDir(int dir) {
        this.dir = dir;
    }

    public int getAtk() {
        return atk;
    }

    public void setAtk(int atk) {
        this.atk = atk;
    }

    public Color getColor() {
        return color;
    }

    public void setColor(Color color) {
        this.color = color;
    }

    public boolean isVisible() {
        return visible;
    }

    public void setVisible(boolean visible) {
        this.visible = visible;
    }

    /*====================END=============GETTER/SETTER========================================*/

    /**
     * 炮弹自身的绘制方法
     *
     * @param g
     */
    public void draw(Graphics g) {
        //如果子弹不可见就不画了
        if (!visible) return;

        logic();
        g.setColor(color);  //设置炮弹颜色
        //画实心炮弹
        g.fillOval(x - RADIUS, y - RADIUS, RADIUS * 2, RADIUS * 2);
    }

    /**
     * 炮弹的逻辑
     * 逻辑就是移动
     */
    private void logic() {
        move();
    }

    /**
     * 炮弹的移动方法
     */
    private void move() {
        switch (dir) {
            case Tank.DIR_UP:
                y -= speed;
                if (y <= 0) {
                    //如果子弹飞出屏幕外就为不可见状态
                    visible = false;
                }
                break;
            case Tank.DIR_DOWN:
                y += speed;
                if (y > Constant.FRAME_HIGH) {
                    visible = false;
                }
                break;
            case Tank.DIR_LEFT:
                x -= speed;
                if (x < 0) {
                    visible = false;
                }
                break;
            case Tank.DIR_RIGHT:
                x += speed;
                if (x > Constant.FRAME_WIDTH) {
                    visible = false;
                }
                break;
        }
    }


}

Explode类

package c02.n02.game;

import c02.n02.util.MyUtil;

import java.awt.*;

/**
 * 爆炸效果类
 */
public class Explode {
    //设置爆炸效果一共有几帧
    public static final int EXPLODE_FRAME_COUNT = 6;
    //导入爆炸图片
    private static Image[] img;

    //在静态代码块中进行初始化
    static {
        img = new Image[EXPLODE_FRAME_COUNT];
        for (int i = 0; i < img.length; i++) {
            img[i] = MyUtil.createImage("image/boom/b" + i + ".png");
        }
    }

    //爆炸效果的属性
    private int x;
    private int y;
    //当前播放的是第几帧的下标
    private int index;
    //是否可见
    private boolean visible = true;
    //爆炸图片宽度和高度的一半
    private static int explodeWidth = 60 / 2;
    private static int explodeHight = 60 / 2;


    //============START==========GETTER/SETTER===========================
    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    public boolean isVisible() {
        return visible;
    }

    public void setVisible(boolean visible) {
        this.visible = visible;
    }

    //============END==========GETTER/SETTER===========================

    /**
     * 爆炸类的无参构造函数
     * 给对象池使用的,参数全部默认,因为爆炸效果是哪个坦克的都不知道
     */
    public Explode() {
        //设置为从第0帧开始
        index = 0;
    }

    /**
     * 爆炸类的构造方法
     *
     * @param x X位置
     * @param y Y位置
     */
    public Explode(int x, int y) {
        this.x = x;
        this.y = y;
        //设置为从第0帧开始
        index = 0;
    }


    /**
     * 炮弹类的绘制方法
     *
     * @param g
     */
    public void draw(Graphics g) {
        //如果帧下标超出最大的话就重新设置为0
        //为了防止调用时没有手动设置为从第0帧开始播放导致下标越界
        if (index > EXPLODE_FRAME_COUNT - 1) {
            index = 0;
        }

        //只有可见的时候才绘制
        if (visible) {
            g.drawImage(img[index], x - explodeWidth, y - explodeHight, null);
            index++;
        }
        //播放完最后一帧变成不可见状态
        if (index >= EXPLODE_FRAME_COUNT) {
            visible = false;
        }
    }


}

GameFrame类

package c02.n02.game;

import c02.n02.map.GameMap;
import c02.n02.tank.EnemyTank;
import c02.n02.tank.MyTank;
import c02.n02.tank.Tank;
import c02.n02.util.Constant;
import c02.n02.util.EnemyTanksPool;
import c02.n02.util.MusicUtil;
import c02.n02.util.MyUtil;

import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import static c02.n02.util.Constant.*;

/*
 *游戏主窗口类
 *所有游戏显示的内容都在此窗口中实现
 *注意:为了方便维护所有的常量都在常量类中
 *继承了Frame类用于绘制
 *
 */
public class GameFrame extends Frame implements Runnable {
    // 定义一张和屏幕一样大的图片用于双缓冲
    private BufferedImage bufImg = new BufferedImage(FRAME_WIDTH, FRAME_HIGH, BufferedImage.TYPE_4BYTE_ABGR);
    // 菜单界面背景
    private Image menuImg = null;
    //游戏地图背景
    private Image mapImg = null;
    //选择关卡背景
    private Image levelImg = null;
    //帮助界面背景
    private Image helpImg = null;
    //关于界面背景
    private Image aboutImg = null;
    // 游戏结束的图片,第一次使用的时候加载,而不是类加载的时候加载,这样效率更高
    private Image overImg = null;
    // 游戏通关的图片,第一次使用的时候加载,而不是类加载的时候加载,这样效率更高
    private Image winImg = null;
    // 游戏状态
    private static int gameState;
    // 菜单指向
    public static int menuIndex;
    // 关卡指向
    public static int levelIndex;
    // 标题栏的高度
    public static int titleBarH;
    // 定义坦克对象
    private static Tank myTank;
    // 定义第二个坦克对象
    private static Tank myTank2;
    // 定义一个变量用来判断是否是双人模式
    private static boolean isTwoMode = true;
    // 是否开启坦克之间的碰撞
    private static boolean tankCollide = false;
    // 是否开启队友伤害
    private static boolean killMyTank = false;
    // 用来记录本关卡产生了多少个敌人
    private static int bornEnemyCount;
    // 用来记录本关消灭了多少敌人
    public static int killEnemyCount;
    // 定义敌人的坦克容器
    private static List<Tank> enemies = new ArrayList<>();
    // 定义并创建地图对象
    private static GameMap gameMap = new GameMap();
    // 获取总关卡数量
    private static int levlCount = GameInfo.getLevelCount();

    public static int getGameState() {
        return gameState;
    }

    public static void setGameState(int gameState) {
        GameFrame.gameState = gameState;
    }

    /*
     * 对窗口进行初始化
     */
    public GameFrame() {
        //初始化属性
        initFrame();
        // 初始化窗口按键监听
        initEventListener();
        // 启用用于刷新窗口的进程
        new Thread(this).start();
        // 播放背景音乐
        MusicUtil.playStart();
    }

    /**
     * 对游戏内容进行初始化
     */
    public void initGAME() {
        gameState = STATE_MENU;
    }

    /**
     * 初始化函数 对属性进行初始化
     */
    private void initFrame() {
        // 设置标题
        setTitle(GAME_TITLE);
        // 设置窗口大小
        setSize(FRAME_WIDTH, FRAME_HIGH);
        setLocation(FRAME_X, FRAME_Y);
        // 设置窗口大小不可变
        setResizable(false);
        // 设置窗口可见
        setVisible(true);
        // 求标题栏的高度
        titleBarH = getInsets().top;

    }

    /**
     * 该方法负责所有的绘制的内容,也就是说需要在屏幕中显示的内容都需要在该方法中调用 updata方法是继承的,自动调用,不用自己调用
     */
    public void update(Graphics g1) {
        // 得到图片的画笔,用于双缓冲
        Graphics g = bufImg.getGraphics();
        g.setFont(FONT);
        switch (gameState) {
            case STATE_MENU:
                drawMenu(g);
                break;
            case STATE_LEVEL:
                drawLevel(g);
                break;
            case STATE_HELP:
                drawHelp(g);
                break;
            case STATE_ABOUT:
                drawAbout(g);
                break;
            case STATE_RUN:
                drawRun(g);
                break;
            case STATE_OVER:
                drawOver(g);
                break;
            case STATE_WIN:
                drawWin(g);
                break;
        }
        // 使用系统画笔将图片绘制到frame上,用于双缓冲
        g1.drawImage(bufImg, 0, 0, null);
    }

    /* =================START==========绘制方法==================================== */

    /**
     * 绘制菜单的方法
     *
     * @param g
     */
    private void drawMenu(Graphics g) {
        // 设置画笔颜色
        g.setColor(Color.MAGENTA);
        // 画个矩形当做背景
        // g.fillRect(0, 0, FRAME_WIDTH, FRAME_HIGH);
        // 只有menuImg为空的时候才加载
        if (menuImg == null) {
            menuImg = MyUtil.createImage("image/Menu/background.png");
        }
        // 使用图片作为背景
        g.drawImage(menuImg, 0, 0, null);

        // 定位文字位置
        int x = 560;
        int y = 350;
        int dis = 60; // 行间距
        // g.setColor(Color.PINK);
        // 根据选择设置菜单颜色
        for (int i = 0; i < MENUS.length; i++) {
            if (i == menuIndex) {
                g.setColor(Color.cyan);
            } else {
                g.setColor(Color.BLUE);
            }
            g.drawString(MENUS[i], x, y + dis * i);
        }

    }

    /**
     * 绘制关卡界面方法
     *
     * @param g
     */
    private void drawLevel(Graphics g) {
        // 只有levelImg为空的时候才加载
        if (levelImg == null) {
            levelImg = MyUtil.createImage("image/Menu/level.png");
        }
        // 使用图片作为背景
        g.drawImage(levelImg, 0, 0, null);

        g.setColor(Color.BLACK);
        // 定位文字位置
        int x = 500;
        int y = 280;
        int dis = 60; // 行间距
//        g.fillRect(0, 0, FRAME_WIDTH, FRAME_HIGH);  //绘制纯色背景
        g.setFont(FONT);

        for (int i = 0; i < levlCount; i++) {
            if (i == levelIndex) {
                g.setColor(Color.RED);
            } else {
                g.setColor(Color.BLUE);
            }
            g.drawString(i + 1 + "", x + dis * i, y);
        }

    }

    /**
     * 游戏运行时的绘制方法
     *
     * @param g
     */
    private void drawRun(Graphics g) {
        //绘制地图背景
        g.setColor(Color.BLACK);
//        g.fillRect(0, 0, FRAME_WIDTH, FRAME_HIGH); // 单色背景
        if (mapImg == null) {
            mapImg = MyUtil.createImage("image/Map/background.png");
        }
        g.drawImage(mapImg, 0, 0, null);   //绘制地图背景

        // TODO 显示内测版本
        g.setColor(Color.CYAN);
        g.setFont(SMALL_FONT);
        g.drawString("版本 v2020-12-24 最终答辩版", 10, FRAME_HIGH - 10);

        // 绘制地图,先绘制没有遮挡的块
        gameMap.drawBK(g);
        // 绘制敌人的坦克
        drawEnemies(g);
        //如果己方两个坦克都死亡的话就切换到游戏结束状态
        if (myTank.isDie() && myTank2.isDie()) {
            GameFrame.setGameState(Constant.STATE_OVER);
        }
        // 绘制自己的坦克
        //只有坦克没有死的时候才绘制
        if (!myTank.myTankisDie()) {
            myTank.draw(g);
        } else {    //坦克死亡后就绘制到屏幕外
            myTank.draw(g);
            myTank.setX(1500);
            myTank.setY(1500);
        }
        if (!myTank2.myTankisDie()) {
            myTank2.draw(g);
        } else {
            myTank2.draw(g);
            myTank2.setX(1500);
            myTank2.setY(1500);
        }

        // 绘制地图的遮挡层
        gameMap.drawCover(g);
        // 子弹和坦克的碰撞方法
        bulletCollideTank();
        // 爆炸效果
        drawExplodes(g);

        // 坦克和子弹和所有地图块的碰撞
        bulletAndTanksCollideMapTile();

        //坦克之间的碰撞
        if (tankCollide) {
            tankCollideTank();
        }

    }

    /**
     * 绘制敌人坦克的方法,并且移除已经死亡的坦克
     *
     * @param g
     */
    private void drawEnemies(Graphics g) {
        for (int i = 0; i < enemies.size(); i++) {
            Tank enemy = enemies.get(i);
            // 如果坦克已经死亡就从容器中移除
            if (enemy.isDie()) {
                enemies.remove(i);
                i--;
                continue;
            }
            enemy.draw(g);
        }
    }

    /**
     * 绘制关于界面的方法
     *
     * @param g
     */
    private void drawAbout(Graphics g) {
        //绘制关于界面
        g.setColor(Color.BLACK);
//        g.fillRect(0, 0, FRAME_WIDTH, FRAME_HIGH); // 单色背景
        if (aboutImg == null) {
            aboutImg = MyUtil.createImage("image/Menu/about.png");
        }
        g.drawImage(aboutImg, 0, 0, null);   //绘制地图背景
    }

    /**
     * 绘制帮助界面的方法
     *
     * @param g
     */
    private void drawHelp(Graphics g) {
        //绘制帮助界面
        g.setColor(Color.BLACK);
//        g.fillRect(0, 0, FRAME_WIDTH, FRAME_HIGH); // 单色背景
        if (helpImg == null) {
            helpImg = MyUtil.createImage("image/Menu/help.png");
        }
        g.drawImage(helpImg, 0, 0, null);   //绘制地图背景
    }

    // 绘制游戏结束的方法
    private void drawOver(Graphics g) {
        // 只有overImg为空的时候才加载
        if (overImg == null) {
            overImg = MyUtil.createImage("image/gameover.png");
        }

        // 求图片宽和高,让图片居中显示
        int imgW = overImg.getWidth(null);
        int imgH = overImg.getHeight(null);

        g.drawImage(overImg, (FRAME_WIDTH - imgW) / 2, (FRAME_HIGH - imgH) / 2, null);

        // 添加按键提示信息
        g.setColor(Color.RED);
        g.drawString(OVER_STR[0], 120, FRAME_HIGH - 100);
        g.setColor(Color.BLUE);
        g.drawString(OVER_STR[1], FRAME_WIDTH - 500, FRAME_HIGH - 100);
    }

    // 绘制游戏胜利的方法
    private void drawWin(Graphics g) {
        // 只有winImg为空的时候才加载
        if (winImg == null) {
            winImg = MyUtil.createImage("image/gamewin.png");
        }

        // 求图片宽和高,让图片居中显示
        int imgW = winImg.getWidth(null);
        int imgH = winImg.getHeight(null);

        g.drawImage(winImg, (FRAME_WIDTH - imgW) / 2, (FRAME_HIGH - imgH) / 2, null);

        // 添加按键提示信息
        g.setColor(Color.RED);
        g.drawString(OVER_STR[0], 120, FRAME_HIGH - 100);
        g.setColor(Color.BLUE);
        g.drawString(OVER_STR[1], FRAME_WIDTH - 500, FRAME_HIGH - 100);
    }

    /* =================END==========绘制方法==================================== */

    /* =================START==========按键响应==================================== */

    /**
     * 菜单状态对按键的相应
     *
     * @param keyCode
     */
    private void keyPressEventMenu(int keyCode) {
        //每按一次就播放一次音乐
//        MusicUtil.playMenuCase();     //效果不佳,取消此选项

        switch (keyCode) {
            // 按了上键和W键
            case KeyEvent.VK_UP:
            case KeyEvent.VK_W:
                // 每按一次menuIndex就减1,小于0时变成最大
                menuIndex--;
                if (menuIndex < 0) {
                    menuIndex = MENUS.length - 1;
                }
                break;
            // 按了下键和S键
            case KeyEvent.VK_DOWN:
            case KeyEvent.VK_S:
                menuIndex++;
                if (menuIndex > MENUS.length - 1) {
                    menuIndex = 0;
                }
                break;
            // 如果按下了回车键,就说明开始新游戏
            case KeyEvent.VK_ENTER:
                switch (menuIndex) {
                    case 0: // 开始游戏
                        isTwoMode = false;    //设置为单人模式
                        newGame(1);
                        break;
                    case 1: //双人游戏
                        isTwoMode = true;     //设置为双人模式
                        newGame(1);
                        break;
                    case 2: // 选择关卡
                        setGameState(STATE_LEVEL);
                        break;
                    case 3: // 帮助
                        setGameState(STATE_HELP);
                        break;
                    case 4: // 关于
                        setGameState(STATE_ABOUT);
                        break;
                    case 5: // 退出游戏
                        System.exit(0);
                        break;
                }
                break;
        }
    }

    /**
     * 菜单状态对按键的相应
     *
     * @param keyCode
     */
    private void keyPressEventlevel(int keyCode) {
        switch (keyCode) {
            case KeyEvent.VK_LEFT:
                // 每按一次menuIndex就减1,小于0时变成最大
                levelIndex--;
                if (levelIndex < 0) {
                    levelIndex = levlCount - 1;
                }
                break;
            case KeyEvent.VK_RIGHT:
                levelIndex++;
                if (levelIndex > levlCount - 1) {
                    levelIndex = 0;
                }
                break;
            // 如果按下了回车键,就说明开始新游戏
            case KeyEvent.VK_ENTER:
                newGame(levelIndex + 1);
                break;

        }
    }

    /**
     * 游戏运行中的按键处理方法-移动
     *
     * @param keyCode
     */
    private void keyPressEventRun(int keyCode) {
        switch (keyCode) {
            case KeyEvent.VK_UP:
                myTank2.setDir(Tank.DIR_UP);
                myTank2.setState(Tank.STATE_MOVE); // 二号坦克移动
                break;
            case KeyEvent.VK_W:
                myTank.setDir(Tank.DIR_UP);
                myTank.setState(Tank.STATE_MOVE); // 坦克移动
                break;
            case KeyEvent.VK_DOWN:
                myTank2.setDir(Tank.DIR_DOWN);
                myTank2.setState(Tank.STATE_MOVE); // 二号坦克移动
                break;
            case KeyEvent.VK_S:
                myTank.setDir(Tank.DIR_DOWN);
                myTank.setState(Tank.STATE_MOVE); // 坦克移动
                break;
            case KeyEvent.VK_LEFT:
                myTank2.setDir(Tank.DIR_LEFT);
                myTank2.setState(Tank.STATE_MOVE); // 二号坦克移动
                break;
            case KeyEvent.VK_A:
                myTank.setDir(Tank.DIR_LEFT);
                myTank.setState(Tank.STATE_MOVE); // 坦克移动
                break;
            case KeyEvent.VK_RIGHT:
                myTank2.setDir(Tank.DIR_RIGHT);
                myTank2.setState(Tank.STATE_MOVE); // 二号坦克移动
                break;
            case KeyEvent.VK_D:
                myTank.setDir(Tank.DIR_RIGHT);
                myTank.setState(Tank.STATE_MOVE); // 坦克移动
                break;
            case KeyEvent.VK_SPACE:
                myTank.fire();
                break;
            case KeyEvent.VK_NUMPAD0:
                myTank2.fire();
                break;

        }
    }

    /**
     * 游戏运行中的按键处理方法-松开按键坦克停止
     *
     * @param keyCode
     */
    private void keyReleasedEventRun(int keyCode) {
        switch (keyCode) {
            case KeyEvent.VK_UP:
                myTank2.setState(Tank.STATE_STAND); // 二号坦克停止
                break;
            case KeyEvent.VK_W:
                myTank.setState(Tank.STATE_STAND); // 坦克停止
                break;
            case KeyEvent.VK_DOWN:
                myTank2.setState(Tank.STATE_STAND); // 二号坦克停止
                break;
            case KeyEvent.VK_S:
                myTank.setState(Tank.STATE_STAND); // 坦克停止
                break;
            case KeyEvent.VK_LEFT:
                myTank2.setState(Tank.STATE_STAND); // 二号坦克停止
                break;
            case KeyEvent.VK_A:
                myTank.setState(Tank.STATE_STAND); // 坦克停止
                break;
            case KeyEvent.VK_RIGHT:
                myTank2.setState(Tank.STATE_STAND); // 二号坦克停止
                break;
            case KeyEvent.VK_D:
                myTank.setState(Tank.STATE_STAND); // 坦克停止
                break;
        }
    }

    /**
     * 游戏关于界面按键处理
     *
     * @param keyCode
     */
    private void keyPressEventAbout(int keyCode) {
        setGameState(STATE_MENU);
    }

    /**
     * 游戏帮助按界面键处理
     *
     * @param keyCode
     */
    private void keyPressEventHelp(int keyCode) {
        setGameState(STATE_MENU);
    }

    /**
     * 游戏结束的按键处理
     *
     * @param keyCode
     */
    private void keyPressEventOver(int keyCode) {
        // 如果按了ESC就退出游戏
        if (keyCode == KeyEvent.VK_ESCAPE) {
            System.exit(0);
        } else if (keyCode == KeyEvent.VK_ENTER) {
            // 如果按了ENTER就回到主菜单,并且开始播放背景音乐
            setGameState(STATE_MENU);
            MusicUtil.playStart();
            // 还需要关闭很多游戏操作,重置某些属性
            resetGame();
        }
    }

    /**
     * 游戏通关按键的处理
     *
     * @param keyCode
     */
    private void keyPressEventWin(int keyCode) {
        keyPressEventOver(keyCode); // 直接调用游戏结束的按键处理
    }

    /* =================END==========按键响应==================================== */

    /**
     * 开始游戏方法
     *
     * @param level 关卡信息
     */
    private static void newGame(int level) {
        // 先清空敌人坦克容器
        enemies.clear();

        if (gameMap == null) {
            gameMap = new GameMap();
        }
        // 设置关卡信息
        gameMap.initMap(level);
        // 播放背景音乐
        // MusicUtil.playStart();
        MusicUtil.stopStart();
        // 先把产生的敌人数量设置为0
        bornEnemyCount = 0;
        // 初始化消灭的敌人
        killEnemyCount = 0;
        // 设置当前模式
        gameState = STATE_RUN;

        // 创建坦克对象,x y就是坦克出生的坐标,最后的属性是设置坦克默认朝向
        myTank = new MyTank(500, 670, Tank.DIR_UP);     //创建一号坦克对象
        myTank2 = new MyTank(780, 670, Tank.DIR_UP);    //创建二号坦克对象
        myTank2.setTwo(true);   //设置为二号坦克

        // 判断是否为单人模式,如果为单人模式那么二号坦克对象直接设置为0血
        if (!isTwoMode) {
            myTank2.setHp(0);
        }

        // 使用一个单独的线程用于创建敌人的坦克
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    // 如果没有超过本关最大坦克数量和屏幕最大坦克数量才新建坦克
                    if (LevelInof.getInstance().getEnemyCount() > bornEnemyCount && enemies.size() < ENEMY_MAX_COUNT) {
                        Tank enemy = EnemyTank.createEnemy();
                        enemies.add(enemy);
                        bornEnemyCount++; // 每创建一个敌人就加1
                    }
                    // 产生坦克生成间隔时间
                    try {
                        Thread.sleep(ENEMY_BORN_INTERVAL);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    // 如果游戏不在RUN状态就不再创建敌人
                    if (gameState != STATE_RUN) {
                        break;
                    }
                }
            }
        }.start();
    }

    /**
     * 初始化窗口按键监听
     */
    private void initEventListener() {
        // 注册监听事件
        addWindowListener(new WindowAdapter() {
            // 点击关闭按钮时自动调用此方法
            @Override
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });

        // 添加按键监听事件
        addKeyListener(new KeyAdapter() {
            // 按下按键
            @Override
            public void keyPressed(KeyEvent e) {
                // 获取按下的键
                int keyCode = e.getKeyCode();
                // 把获取到的键值传递给当前的状态
                switch (gameState) {
                    case STATE_MENU:
                        keyPressEventMenu(keyCode);
                        break;
                    case STATE_LEVEL:
                        keyPressEventlevel(keyCode);
                        break;
                    case STATE_HELP:
                        keyPressEventHelp(keyCode);
                        break;
                    case STATE_ABOUT:
                        keyPressEventAbout(keyCode);
                        break;
                    case STATE_RUN:
                        keyPressEventRun(keyCode);
                        break;
                    case STATE_OVER:
                        keyPressEventOver(keyCode);
                        break;
                    case STATE_WIN:
                        keyPressEventWin(keyCode);
                        break;
                }
            }

            // 按键松开时,游戏中的处理方法
            @Override
            public void keyReleased(KeyEvent e) {
                // 获取按下的键
                int keyCode = e.getKeyCode();
                // 把获取到的键值传递给当前的状态
                if (gameState == STATE_RUN) {
                    keyReleasedEventRun(keyCode);
                }
            }

        });
    }

    /**
     * 单独设置刷新率的线程
     */
    @Override
    public void run() {
        while (true) {
            // 在此调用repaint
            repaint();
            try {
                Thread.sleep(REPAINT_INTERVAL);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 子弹碰撞方法
     */
    private void bulletCollideTank() {
        // 我的子弹和敌人的坦克碰撞
        for (int i = 0; i < enemies.size(); i++) {
            Tank enemy = enemies.get(i);
            enemy.collideBullets(myTank.getBullets());
            enemy.collideBullets(myTank2.getBullets());
            //队友伤害
            if (killMyTank) {
                myTank2.collideBullets(myTank.getBullets());
                myTank.collideBullets(myTank2.getBullets());
            }
        }
        // 敌人的子弹和我的坦克碰撞
        for (int i = 0; i < enemies.size(); i++) {
            Tank enemy = enemies.get(i);
            myTank.collideBullets(enemy.getBullets());
            myTank2.collideBullets(enemy.getBullets());

        }
    }

    /**
     * 坦克和坦克的碰撞方法
     */
    private void tankCollideTank() {
        //敌方坦克和我方坦克的碰撞
        for (Tank enemy : enemies) {
            boolean collideTank = enemy.isCollideTank(myTank);
            boolean collideTank2 = enemy.isCollideTank(myTank2);
            if (collideTank || collideTank2) {
                enemy.back();
            }
        }
        //我方坦克和坦克的碰撞
        boolean collideTanks = myTank.isCollideTanks(enemies);   //一号坦克和敌方坦克的碰撞检测
        boolean collideTanks2 = myTank2.isCollideTanks(enemies); //二号坦克和敌方坦克的碰撞检测
        boolean collideTank = myTank.isCollideTank(myTank2);     //一号坦克和二号坦克的碰撞检测
        boolean collideTank2 = myTank2.isCollideTank(myTank);     //二号坦克和一号坦克的碰撞检测

        //如果发生了碰撞坦克就后退
        if (collideTank || collideTanks) {
            myTank.back();
        }
        if (collideTank2 || collideTanks2) {
            myTank2.back();
        }
    }

    /**
     * 子弹和地图块的碰撞方法 坦克和地图块的碰撞方法
     */
    private void bulletAndTanksCollideMapTile() {
        // 自己坦克子弹和所有地图块的碰
        myTank.bulletsCollideMapTiles(gameMap.getTiles());
        myTank2.bulletsCollideMapTiles(gameMap.getTiles());

        // 敌人坦克子弹和所有地图块的碰撞
        for (Tank enemy : enemies) {
            enemy.bulletsCollideMapTiles(gameMap.getTiles());
        }

        // 我的坦克和地图块的碰撞
        boolean collideTile = myTank.isCollideTile(gameMap.getTiles());
        boolean collideTile2 = myTank2.isCollideTile(gameMap.getTiles());

        //如果发生了碰撞坦克就后退
        if (collideTile) {
            myTank.back();
        }
        if (collideTile2) {
            myTank2.back();
        }

        // 敌人的坦克和地图块的碰撞
        for (Tank enemy : enemies) {
            boolean collideTile1 = enemy.isCollideTile(gameMap.getTiles());
            if (collideTile1) {
                enemy.back();
            }
        }

        // 清理所有的被销毁的地图块
        gameMap.clearDestoryTile();
    }

    // 绘制所有坦克上的爆炸效果
    private void drawExplodes(Graphics g) {
        // 绘制所有敌人坦克上的爆炸效果
        for (Tank enemy : enemies) {
            enemy.drawExplodes(g);
        }
        // 绘制自己坦克上的爆炸效果
        myTank.drawExplodes(g);
        myTank2.drawExplodes(g);
    }

    /**
     * 重置游戏状态方法
     */
    private void resetGame() {
        menuIndex = 0; // 重置菜单指向
        myTank.bulletsReturn(); // 还回所有子弹,并清空子弹容器
        myTank = null; // 销毁自己的坦克
        myTank2.bulletsReturn(); // 还回所有子弹,并清空子弹容器
        myTank2 = null; // 销毁自己的坦克
        // 还回敌人所有子弹,并且清空子弹容器,归还坦克
        for (Tank enemy : enemies) {
            enemy.bulletsReturn();
            EnemyTanksPool.theReturn(enemy);
        }
        enemies.clear(); // 清空敌人坦克容器
        gameMap.clearMap(); // 还回所有地图块
        gameMap = null; // 清空地图资源
    }

    /**
     * 判断游戏是否是最后一关
     *
     * @return true表示为最后一关
     */
    public static boolean isLastLevel() {
        // 获取当前关卡信息
        int currLevel = LevelInof.getInstance().getLevel();

        // int levlCount = GameInfo.getLevelCount();
        // 如果当前关卡是最后一关就返回true
        return currLevel == levlCount;
    }

    /**
     * 判断是否过关
     *
     * @return true为过关
     */
    public static boolean isCrossLevel() {
        // 如果杀敌数量等于关卡规定数量就说明过关了
        return killEnemyCount == LevelInof.getInstance().getEnemyCount();
    }

    /**
     * 进入下一关的方法
     */
    public static void nextLevel() {
        // 当前关卡加一
        newGame(LevelInof.getInstance().getLevel() + 1);
    }

}

GameInfo类

package c02.n02.game;

import java.io.FileInputStream;
import java.util.Properties;

/**
 * 游戏相关的信息类
 */
public class GameInfo {
    //关卡数量
    private static int levelCount;

    static {
        Properties prop = new Properties();
        try {
            //加载配置文件
            prop.load(new FileInputStream("level/gameinfo"));
            //获取关卡数量
            levelCount = Integer.parseInt(prop.getProperty("levelCount"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取关卡总数量
     *
     * @return 返回关卡数量
     */
    public static int getLevelCount() {
        return levelCount;
    }


}

LevelInfo类

package c02.n02.game;

import c02.n02.util.MyUtil;

/**
 * 用来管理当前关卡的信息的类
 */
public class LevelInof {
    //关卡编号
    private int level;
    //敌人的数量
    private int enemyCount;
    //通关的要求的时长,-1不限时
    private int crossTime = -1;
    //敌人类型信息
    private int[] enemyType;
    //游戏难度
    private int levelType;

    //===========START=================GETTER/SETTER===============
    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public int getEnemyCount() {
        return enemyCount;
    }

    public void setEnemyCount(int enemyCount) {
        this.enemyCount = enemyCount;
    }

    public int getCrossTime() {
        return crossTime;
    }

    public void setCrossTime(int crossTime) {
        this.crossTime = crossTime;
    }

    public int[] getEnemyType() {
        return enemyType;
    }

    public void setEnemyType(int[] enemyType) {
        this.enemyType = enemyType;
    }

    public int getLevelType() {
        if (levelType <= 0) {
            levelType = 1;
        }
        return levelType;
    }

    public void setLevelType(int leevelType) {
        this.levelType = leevelType;
    }

    //===========END=================GETTER/SETTER===============


    //构造方法私有化
    private LevelInof() {
    }

    //定义静态的本类类型的变量,用来指向唯一的实例
    private static LevelInof instance;

    /**
     * 懒汉模式的单例
     * 第一次使用该实例的时候创建唯一的实例
     *
     * @return
     */
    public static LevelInof getInstance() {
        if (instance == null) {
            //创建了唯一的实例
            instance = new LevelInof();
        }
        return instance;
    }

    /**
     * 获得一个随机的敌人类型
     *
     * @return 返回敌人类型
     */
    public int getRandomEnemyType() {
        int index = MyUtil.getRandomNumber(0, enemyType.length);
        return enemyType[index];
    }

}

GameMap类

package c02.n02.map;

import c02.n02.game.GameFrame;
import c02.n02.game.LevelInof;
import c02.n02.tank.Tank;
import c02.n02.util.Constant;
import c02.n02.util.MapTilePool;

import java.awt.*;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * 游戏地图类
 * 游戏中显示的地图都来自此类
 */
public class GameMap {
    //设置地图坐标,地图离边框1.5个坦克距离
    public static final int MAP_X = Tank.RADIUS * 3;
    public static final int MAP_Y = Tank.RADIUS * 3 + GameFrame.titleBarH;

    //设置地图宽高
    public static final int MAP_WIDTH = Constant.FRAME_WIDTH - Tank.RADIUS * 6;
    public static final int MAP_HEIGHT = Constant.FRAME_HIGH - Tank.RADIUS * 8 - GameFrame.titleBarH;

    //地图距离边框为0
//    public static final int MAP_X = 0;
//    public static final int MAP_Y = 30;
//    public static final int MAP_WIDTH = Constant.FRAME_WIDTH;
//    public static final int MAP_HEIGHT = Constant.FRAME_HIGH;

    private int width;  //地图宽
    private int height; //地图高

    //地图元素块的容器
    private List<MapTile> tiles = new ArrayList<>();

    //基地对象
    private TankHouse house;

    //===========START=================GETTER/SETTER===============

    public List<MapTile> getTiles() {
        return tiles;
    }

    public void setTiles(List<MapTile> tiles) {
        this.tiles = tiles;
    }

    //===========END=================GETTER/SETTER===============


    /**
     * 游戏地图类的无参构造函数
     */
    public GameMap() {
    }

    /**
     * 初始化地图元素块
     *
     * @param level 第几关
     */
    public void initMap(int level) {
        //初始化地图,先把里面原有的块清空掉重新加载
        tiles.clear();
        //从关卡文件里读取关卡信息
        try {
            loadLevel(level);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //初始化基地
        house = new TankHouse();
        addHouse();
    }

    /**
     * 加载关卡信息
     *
     * @param level 第几关
     */
    private void loadLevel(int level) throws Exception {
        //获得关卡信息类的唯一实例对象
        LevelInof levelInof = LevelInof.getInstance();

        //将所有的地图信息都加载进来
        Properties prop = new Properties();
        prop.load(new FileInputStream("level/lv_" + level));
        //从文件中读取指定的信息
        //敌人数量
        int enemyCount = Integer.parseInt(prop.getProperty("enemyCount"));
        //敌人类型,根据逗号分隔
        String[] enemyType = prop.getProperty("enemyType").split(",");
        int[] type = new int[enemyType.length];
        for (int i = 0; i < type.length; i++) {
            type[i] = Integer.parseInt(enemyType[i]);
        }
        //调用的方法
        String methodName = prop.getProperty("method");
        //方法调用的次数
        int invokeCount = Integer.parseInt(prop.getProperty("invokeCount"));
        //把参数读取到数组中
        String[] params = new String[invokeCount];
        for (int i = 1; i <= invokeCount; i++) {
            params[i - 1] = prop.getProperty("param" + i);
        }

        //获取关卡难度信息
        int levelType = Integer.parseInt(prop.getProperty("levelType"));

        //设置关卡信息
        levelInof.setEnemyCount(enemyCount);    //设置敌人数量
        levelInof.setLevel(level);      //设置关卡编号
        levelInof.setEnemyType(type);   //设置敌人类型
        levelInof.setLevelType(levelType);  //设置关卡难度

        //根据方法的名字和参数调用对应的方法
        invokeMethod(methodName, params);
    }

    /**
     * 根据方法的名字和参数调用对应的方法
     *
     * @param name   调用的方法名
     * @param params 参数
     */
    private void invokeMethod(String name, String[] params) {
        for (String param : params) {
            //获取每一行的参数,解析
            String[] split = param.split(",");
            //使用一个数组来保存解析后的内容
            int[] arr = new int[split.length];
            for (int i = 0; i < split.length; i++) {
                arr[i] = Integer.parseInt(split[i]);
            }
            //块之间的间隔是地图块的倍数
            final int DIS = MapTile.tileW;
            //根据配置信息生成块
            switch (name) {
                case "addRow":
                    addRow(MAP_X + arr[0] * DIS, MAP_Y + arr[1] * DIS, MAP_X + MAP_WIDTH - arr[2] * DIS, arr[3], arr[4] * DIS);
                    break;
                case "addCol":
                    addCol(MAP_X + arr[0] * DIS, MAP_Y + arr[1] * DIS, MAP_Y + MAP_HEIGHT - arr[2] * DIS, arr[3], DIS);
                    break;
                case "addRect":
                    addRect(MAP_X + arr[0] * DIS, MAP_Y + arr[1] * DIS, MAP_X + MAP_WIDTH - arr[2] * DIS, MAP_Y + MAP_HEIGHT - arr[2] * DIS, arr[4], DIS);
                    break;
                case "addCustom":
                    addCustom(params);
                    return;
            }
        }
    }

    /**
     * 将基地的块添加到地图容器中
     */
    private void addHouse() {
        tiles.addAll(house.getTiles());
    }

    /**
     * 判断某个点是否和tiles集合中的所有块发生了重叠
     *
     * @param tiles tiles集合
     * @param x     点x
     * @param y     点y
     * @return 有重叠返回true,否则返回false
     */
    private boolean isCollide(List<MapTile> tiles, int x, int y) {
        for (MapTile tile : tiles) {
            int tileX = tile.getX();
            int tileY = tile.getY();
            //如果点到块左上点的距离小于块的宽度就说明发生了重叠
            if (Math.abs(tileX - x) < MapTile.tileW && Math.abs(tileY - y) < MapTile.tileW) {
                return true;
            }
        }
        return false;
    }

    /**
     * 用来绘制没有遮挡效果的块
     * 比如 砖块、铁块
     *
     * @param g
     */
    public void drawBK(Graphics g) {
        for (MapTile tile : tiles) {
            if (tile.getType() != MapTile.TYPE_COVER) {
                tile.draw(g);
            }
        }
    }

    /**
     * 用来绘制有遮挡效果的块
     * 比如 草块
     *
     * @param g
     */
    public void drawCover(Graphics g) {
        for (MapTile tile : tiles) {
            if (tile.getType() == MapTile.TYPE_COVER) {
                tile.draw(g);
            }
        }
    }

    /**
     * 将所有不可见的地图块从容器中移除
     */
    public void clearDestoryTile() {
        for (int i = 0; i < tiles.size(); i++) {
            MapTile tile = tiles.get(i);
            if (!tile.isVisible()) {
                tiles.remove(i);
            }
        }
    }

    /**
     * 往地图容器中添加一行指定类型的地图块
     *
     * @param startX 起始x点坐标
     * @param startY 起始y点坐标
     * @param endX   结束点x坐标,因为是生成一行,所以不需要结束点Y坐标
     * @param type   地图块类型
     * @param DIS    地图块的间隔,如果是块的宽度就说明地图块是连续的
     *               如果大于块的宽度就说明是不连续的
     */
    public void addRow(int startX, int startY, int endX, int type, final int DIS) {
        //计算一行有多少个地图块
        int count = 0;
        count = (endX - startX) / (MapTile.tileW + DIS);
        for (int i = 0; i < count; i++) {
            MapTile tile = MapTilePool.get();
            tile.setType(type);
            tile.setX(startX + i * (MapTile.tileW + DIS));
            tile.setY(startY);
            tile.setVisible(true);
            tiles.add(tile);
        }
    }

    /**
     * 往地图容器中添加一列指定类型的地图块
     *
     * @param startX 起始点x坐标
     * @param startY 起始点y坐标
     * @param endY   结束点y坐标
     * @param type   地图块类型
     * @param DIS    地图块的间隔,如果是块的宽度就说明地图块是连续的
     *               如果大于块的宽度就说明是不连续的
     */
    public void addCol(int startX, int startY, int endY, int type, final int DIS) {
        //计算一列有多少个地图块
        int count = 0;
        count = (endY - startY) / (MapTile.tileW + DIS);
        for (int i = 0; i < count; i++) {
            MapTile tile = MapTilePool.get();
            tile.setType(type);
            tile.setX(startX);
            tile.setY(startY + i * (MapTile.tileW + DIS));
            tile.setVisible(true);
            tiles.add(tile);
        }
    }

    /**
     * 往地图容器中添加一个矩形地图块
     *
     * @param startX 起始点x
     * @param startY 起始点y
     * @param endX   结束点x
     * @param endY   结束点y
     * @param type   地图块类型
     * @param DIS    地图块水平和垂直间隔
     */
    public void addRect(int startX, int startY, int endX, int endY, int type, final int DIS) {
//        //计算一行应该填充多少地图块
//        int rows = (endX - startX) / (MapTile.tileW + DIS);
        //计算一列应该填充多少地图块
        int cols = (endY - startY) / (MapTile.tileW + DIS);
        //一个矩形区域由若干行组成
        for (int i = 0; i < cols; i++) {
            addRow(startX, startY + i * (MapTile.tileW + DIS), endX, type, DIS);
        }
    }

    /**
     * 往地图容器中添加自定义的地图块,通过配置文件里的数组来设置地图
     *
     * @param params
     */
    public void addCustom(String[] params) {
        for (int i = 0; i < params.length; i++) {
            String param = params[i];
            //获取每一行的参数,解析
            String[] split = param.split(",");
            //使用一个数组来保存解析后的内容
            int[] arr = new int[split.length];
            for (int j = 0; j < split.length; j++) {
                arr[j] = Integer.parseInt(split[j]);
            }

            //如果类型等于9那么就跳过本次循环
            for (int j = 0; j < arr.length; j++) {
                int a = arr[j];
                if (a == 9) {
                    continue;
                }

                //砖块对象池中拿出一个砖块
                MapTile tile = MapTilePool.get();
                tile.setType(a);
                tile.setX(MAP_X + j * 60);
                tile.setY(MAP_Y + i * 60);
                tile.setVisible(true);
                tiles.add(tile);

            }
        }
    }

    /**
     * 清空地图容器并且把所有地图块还回池中
     */
    public void clearMap() {
        for (MapTile tile : tiles) {
            tile.setVisible(false);
            MapTilePool.theReturn(tile);
        }
        tiles.clear();
    }

}

MapTile类

package c02.n02.map;

import c02.n02.game.Bullet;
import c02.n02.util.MyUtil;

import java.awt.*;
import java.util.List;

/**
 * 地图中的所有块的类
 */
public class MapTile {
    //地图块的类型
    public static final int TYPE_HOUSE = 0;     //基地块
    public static final int TYPE_NORMAL = 1;    //普通块(砖块)
    public static final int TYPE_HARD = 2;      //铁块
    public static final int TYPE_COVER = 3;     //遮挡块(草丛)

    //设置地图块类型,默认砖块
    private int type = TYPE_NORMAL;

    //设置图片宽,因为图片是正方形,所以只需要获取一个
    public static int tileW = 60;
    //设置半径
    public static int radius = tileW / 2;

    //创建图片数组
    private static Image[] tileImg;

    static {
        tileImg = new Image[4];
        tileImg[TYPE_NORMAL] = MyUtil.createImage("image/Map/tile.png");
        tileImg[TYPE_HOUSE] = MyUtil.createImage("image/Map/house.png");
        tileImg[TYPE_COVER] = MyUtil.createImage("image/Map/cover.png");
        tileImg[TYPE_HARD] = MyUtil.createImage("image/Map/hard.png");
    }

    //砖块左上角坐标
    private int x;
    private int y;

    //砖块是否可见
    private boolean visible = true;

    //===========START=================GETTER/SETTER===============
    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public boolean isVisible() {
        return visible;
    }

    public void setVisible(boolean visible) {
        this.visible = visible;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    //===========END=================GETTER/SETTER===============


    /**
     * 地图块类的无参构造函数
     */
    public MapTile() {
    }

    /**
     * 地图块类的构造函数
     *
     * @param x X坐标
     * @param y Y坐标
     */
    public MapTile(int x, int y) {
        this.x = x;
        this.y = y;
    }

    /**
     * 绘制方法
     * 根据不同的类型绘制不同的砖块
     *
     * @param g
     */
    public void draw(Graphics g) {
        //不可见就不绘制
        if (!visible) {
            return;
        }
        g.drawImage(tileImg[type], x, y, null);

    }

    /**
     * 判断块是否碰到了子弹(所有子弹)
     * 遮挡块不用判断
     *
     * @param bullets 所有子弹所在的列表
     * @return 碰撞了返回true否则返回false
     */
    public boolean isCollideBullet(List<Bullet> bullets) {
        //如果地图块不可见或者块类型为遮挡块,直接返回false
        if (!visible || type == TYPE_COVER) {
            return false;
        }
        for (Bullet bullet : bullets) {
            //获取子弹的坐标
            int bulletX = bullet.getX();
            int bulletY = bullet.getY();

            //判断是否碰撞,因为传入的是方块的中心点,所以x y需要加半径
            boolean collide = MyUtil.isCollide(x + radius, y + radius, radius, bulletX, bulletY);
            if (collide) {
                //销毁子弹
                bullet.setVisible(false);
                return true;
            }
        }
        return false;
    }

    /**
     * 判断当前的地图块是否是基地
     *
     * @return  如果为基地块就返回true
     */
    public boolean isHouse() {
        return type == TYPE_HOUSE;
    }

}

TankHouse类

package c02.n02.map;

import c02.n02.util.Constant;

import java.awt.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 我方基地
 * 大概就长这样
 * ■■■
 * ■☆■
 */
public class TankHouse {
    //坐标
    public static final int HOUSE_X = (Constant.FRAME_WIDTH - 3 * MapTile.tileW) / 2;
    public static final int HOUSE_Y = Constant.FRAME_HIGH - 2 * MapTile.tileW;

    //地图块容器
    private List<MapTile> tiles = new ArrayList<>();

    public TankHouse() {
        tiles.add(new MapTile(HOUSE_X + MapTile.tileW, HOUSE_Y + MapTile.tileW));    //中间,基地
        tiles.add(new MapTile(HOUSE_X, HOUSE_Y));    //左上
        tiles.add(new MapTile(HOUSE_X, HOUSE_Y + MapTile.tileW));    //左下
        tiles.add(new MapTile(HOUSE_X + MapTile.tileW, HOUSE_Y));    //上中
        tiles.add(new MapTile(HOUSE_X + 2 * MapTile.tileW, HOUSE_Y));    //右上
        tiles.add(new MapTile(HOUSE_X + 2 * MapTile.tileW, HOUSE_Y + MapTile.tileW));    //右下
        //设置基地块的类型
        tiles.get(0).setType(MapTile.TYPE_HOUSE);
    }

    /**
     * 获取我方基地块
     *
     * @return 返回一个地图块数组
     */
    public List<MapTile> getTiles() {
        return tiles;
    }

    /**
     * 设置我方基地块
     *
     * @param tiles 传入一个地图块数组
     */
    public void setTiles(List<MapTile> tiles) {
        this.tiles = tiles;
    }

    /**
     * 我方基地绘制方法
     *
     * @param g
     */
    public void draw(Graphics g) {
        for (MapTile tile : tiles) {
            tile.draw(g);
        }
    }
}

EnemyTank类

package c02.n02.tank;

import c02.n02.game.GameFrame;
import c02.n02.game.LevelInof;
import c02.n02.util.Constant;
import c02.n02.util.EnemyTanksPool;
import c02.n02.util.MyUtil;

import java.awt.*;

/**
 * 敌人坦克类
 * 继承了坦克类
 */
public class EnemyTank extends Tank {
    public static final int TYPE_GREEN = 0;   //绿坦克
    public static final int TYPE_YELLOW = 1;    //黄色坦克
    //敌人坦克类型,默认绿坦克
    private int type = TYPE_GREEN;

    //定义坦克图片数组
    private static Image[] greenImg;    //绿色坦克图片数组
    private static Image[] yellowImg;    //黄色图片数组


    //记录5秒开始的时间
    private long aiTime;


    //在静态代码块中进行初始化
    static {
        greenImg = new Image[4];
        greenImg[0] = MyUtil.createImage("image/TankImg/enemyTank/green/u.png");
        greenImg[1] = MyUtil.createImage("image/TankImg/enemyTank/green/d.png");
        greenImg[2] = MyUtil.createImage("image/TankImg/enemyTank/green/l.png");
        greenImg[3] = MyUtil.createImage("image/TankImg/enemyTank/green/r.png");
//todo 替换图片
        yellowImg = new Image[4];
        yellowImg[0] = MyUtil.createImage("image/TankImg/enemyTank/yellow/u.png");
        yellowImg[1] = MyUtil.createImage("image/TankImg/enemyTank/yellow/d.png");
        yellowImg[2] = MyUtil.createImage("image/TankImg/enemyTank/yellow/l.png");
        yellowImg[3] = MyUtil.createImage("image/TankImg/enemyTank/yellow/r.png");
    }

    /**
     * 获取敌人坦克类型
     *
     * @return 返回类型
     */
    public int getType() {
        return type;
    }

    /**
     * 设置坦克类型
     *
     * @param type 传入类型
     */
    public void setType(int type) {
        this.type = type;
    }

    /**
     * 敌人坦克类的无参构造方法
     */
    public EnemyTank() {
        //当敌人创建时获取系统时间
        aiTime = System.currentTimeMillis();
        //随机创建不同的坦克
//        type = MyUtil.getRandomNumber(0, 2);
    }

    /**
     * 敌人坦克类的构造方法
     *
     * @param x   X坐标
     * @param y   Y坐标
     * @param dir 方向
     */
    public EnemyTank(int x, int y, int dir) {
        super(x, y, dir);
        //当敌人创建时获取系统时间
        aiTime = System.currentTimeMillis();
        //随机创建不同的坦克
//        type = MyUtil.getRandomNumber(0, 2);
    }

    /**
     * 重写坦克类的绘制方法
     *
     * @param g
     */
    @Override
    public void drawImgTank(Graphics g) {
        ai();   //调用AI
        //根据不同的类型绘制不同的坦克
        if (type == TYPE_GREEN) {
            g.drawImage(greenImg[getDir()], getX() - RADIUS, getY() - RADIUS, null);
        } else if (type == TYPE_YELLOW) {
            g.drawImage(yellowImg[getDir()], getX() - RADIUS, getY() - RADIUS, null);
        }
    }

    /**
     * 创建一个敌人坦克
     *
     * @return 返回一个敌人坦克对象
     */
    public static Tank createEnemy() {
        int x = MyUtil.getRandomNumber(0, 2);  //生成0或1
        int y = GameFrame.titleBarH + RADIUS;   //定位在顶部
        int dir = DIR_DOWN;   //方向默认朝下

        //只在左上角和右上角生成坦克
        if (x == 0) {
            x = RADIUS;   //如果X=0就在左上角生成坦克
        } else {
            x = Constant.FRAME_WIDTH - RADIUS;
        }

        EnemyTank enemy = (EnemyTank) EnemyTanksPool.get();  //从坦克池里拿坦克,强制转换成敌人坦克类型
        enemy.setX(x);
        enemy.setY(y);
        enemy.setDir(dir);
        enemy.setEnemy(true); //设置为敌方坦克
        enemy.setState(STATE_MOVE);
        //为了避免拿到已经死亡的坦克就要重新设血量属性
        //根据关卡难度设置血量
        int hp = DEFAULT_HP * LevelInof.getInstance().getLevelType();
        enemy.setHp(hp);
        enemy.setMaxHp(hp);
        //通过关卡信息中的敌人类型来设置当前出生的敌人类型
        int enemyType = LevelInof.getInstance().getRandomEnemyType();
        enemy.setType(enemyType);

        return enemy;
    }


    /**
     * 坦克AI
     * 每隔一段时间让敌人坦克随机获得一个状态(站立,行走)
     * 游戏的每一帧都随机(概率可调)判断敌人是否发射子弹
     */
    private void ai() {
        //如果当前的时间减去创建坦克的时间大于2秒就切换状态
        if (System.currentTimeMillis() - aiTime > Constant.ENEMY_AI_INTERVAL) {
            //改变方向
            int dir = MyUtil.getRandomNumber(DIR_UP, DIR_RIGHT + 1);  //获取一个随机的方向
            int tankDir = getDir(); //获取目前坦克的方向
            //判断是否在边缘,如果在边缘并且方向还是往外,那么就立即重新随机获取一个方向
            if (super.getX() <= 30 && dir == DIR_LEFT) {
                dir = MyUtil.getRandomNumber(DIR_UP, DIR_RIGHT + 1);
            } else if (super.getX() >= Constant.FRAME_WIDTH - 70 && dir == DIR_RIGHT) {
                dir = MyUtil.getRandomNumber(DIR_UP, DIR_RIGHT + 1);
            } else if (super.getY() <= 50 && dir == DIR_UP) {
                dir = MyUtil.getRandomNumber(DIR_UP, DIR_RIGHT + 1);
            } else if (super.getX() >= Constant.FRAME_HIGH - 30 && dir == DIR_DOWN) {
                dir = MyUtil.getRandomNumber(DIR_UP, DIR_RIGHT + 1);
            }
            setDir(dir);
            //改变状态
            int i = MyUtil.getRandomNumber(0, 2);
            if (i == 0) {
                setState(STATE_STAND);
            } else {
                setState(STATE_MOVE);
            }
            //重新计时
            aiTime = System.currentTimeMillis();
        }
        //生成一个0-1的随机数,如果小于概率的话就发射炮弹
        if (Math.random() < Constant.ENEMY_FIRE_PERCENT) {
            fire();
        }
    }

}

MyTank类

package c02.n02.tank;

import c02.n02.util.MyUtil;

import java.awt.*;

/**
 * 自己坦克类
 * 继承了坦克类
 */
public class MyTank extends Tank {
    //坦克图片数组
    private static Image[] tankImg;
    //二号坦克图片数组
    private static Image[] tankImg2;

    //在静态代码块中进行初始化
    static {
        tankImg = new Image[4];
        tankImg[0] = MyUtil.createImage("image/TankImg/myTank/u.png");
        tankImg[1] = MyUtil.createImage("image/TankImg/myTank/d.png");
        tankImg[2] = MyUtil.createImage("image/TankImg/myTank/l.png");
        tankImg[3] = MyUtil.createImage("image/TankImg/myTank/r.png");

        tankImg2 = new Image[4];
        tankImg2[0] = MyUtil.createImage("image/TankImg/myTank/u2.png");
        tankImg2[1] = MyUtil.createImage("image/TankImg/myTank/d2.png");
        tankImg2[2] = MyUtil.createImage("image/TankImg/myTank/l2.png");
        tankImg2[3] = MyUtil.createImage("image/TankImg/myTank/r2.png");
    }

    /**
     * 自己坦克的构造方法
     *
     * @param x   X坐标
     * @param y   Y坐标
     * @param dir 方向
     */
    public MyTank(int x, int y, int dir) {
        super(x, y, dir);
    }

    /**
     * 重写draw方法
     *
     * @param g
     */
    @Override
    public void drawImgTank(Graphics g) {
        //如果为二号坦克就画二号坦克的图片
        if (!isTwo()) {
            g.drawImage(tankImg[getDir()], getX() - RADIUS, getY() - RADIUS, null);
        } else {
            g.drawImage(tankImg2[getDir()], getX() - RADIUS, getY() - RADIUS, null);
        }
    }

}

Tank类

package c02.n02.tank;

import c02.n02.game.Bullet;
import c02.n02.game.Explode;
import c02.n02.game.GameFrame;
import c02.n02.map.MapTile;
import c02.n02.util.*;

import java.awt.*;
import java.util.ArrayList;
import java.util.List;

/*
 * 坦克类
 * */
public abstract class Tank {

    //四个方向
    public static final int DIR_UP = 0;
    public static final int DIR_DOWN = 1;
    public static final int DIR_LEFT = 2;
    public static final int DIR_RIGHT = 3;
    //坦克的半径
    public static final int RADIUS = 30;
    //默认速度,每帧的速度
    public static final int DEFAULT_SPEED = 10;
    //坦克的状态
    public static final int STATE_STAND = 0; //静止
    public static final int STATE_MOVE = 1;  //移动
    public static final int STATE_DIE = 2;   //死亡
    //坦克的初始生命值
    public static final int DEFAULT_HP = 1000;
    //坦克的最大血量
    private int maxHp = DEFAULT_HP;

    private int x, y;       //坦克坐标
    private int oldX = -1;  //坦克旧坐标
    private int oldY = -1;
    private int hp = DEFAULT_HP;    //当前血量
    private String name;    //坦克名字
    private int atk;        //攻击力
    private boolean isTwo = false;    //设置是否为第二号坦克
    //攻击力范围
    public static final int ATK_MAX = 200;
    public static final int ATK_MIN = 150;

    //坦克上一次开火的时间,用于设置开火间隔
    private long fireTime;
    //开火间隔
    public static final int FIRE_INTERVAL = 200;

    private int speed = DEFAULT_SPEED;  //速度
    private int dir;        //方向
    private int state = STATE_STAND;    //坦克状态
    private Color color;    //坦克颜色
    private boolean isEnemy = false;    //是否是敌人

    //创建血条对象
    private BloodBar bar = new BloodBar();

    //炮弹容器
    private List<Bullet> bullets = new ArrayList();
    //爆炸效果容器
    private List<Explode> explodes = new ArrayList<>();

    //===========START=================GETTER/SETTER===============
    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getHp() {
        return hp;
    }

    public void setHp(int hp) {
        this.hp = hp;
    }

    public int getAtk() {
        return atk;
    }

    public void setAtk(int atk) {
        this.atk = atk;
    }

    public int getSpeed() {
        return speed;
    }

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

    public int getDir() {
        return dir;
    }

    public void setDir(int dir) {
        this.dir = dir;
    }

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
    }

    public Color getColor() {
        return color;
    }

    public void setColor(Color color) {
        this.color = color;
    }

    public List getBullets() {
        return bullets;
    }

    public void setBullets(List bullets) {
        this.bullets = bullets;
    }

    public boolean isEnemy() {
        return isEnemy;
    }

    public void setEnemy(boolean enemy) {
        isEnemy = enemy;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getMaxHp() {
        return maxHp;
    }

    public void setMaxHp(int maxHp) {
        this.maxHp = maxHp;
    }

    public boolean isTwo() {
        return isTwo;
    }

    public void setTwo(boolean isTwo) {
        this.isTwo = isTwo;
    }

    //==============END==============GETTER/SETTER==================


    //构造函数
    public Tank(int x, int y, int dir) {
        this.x = x;
        this.y = y;
        this.dir = dir;
        color = MyUtil.getRandomColor();
        name = MyUtil.getRandomName();
        atk = MyUtil.getRandomNumber(ATK_MIN, ATK_MAX);    //随机一个攻击力
    }

    //无参构造函数
    public Tank() {
        color = MyUtil.getRandomColor();
        name = MyUtil.getRandomName();
        atk = MyUtil.getRandomNumber(ATK_MIN, ATK_MAX);    //随机一个攻击力
    }

    /*
     * 全部的绘制方法
     * 绘制坦克
     *  绘制炮弹
     * */
    public void draw(Graphics g) {
        logic();
        drawImgTank(g);     //绘制坦克
        drawBullets(g);     //绘制炮弹
        drawName(g);    //绘制名字
        bar.draw(g);    //绘制血条
    }

    private void drawName(Graphics g) {
        g.setColor(color);
        g.setFont(Constant.SMALL_FONT);
        g.drawString(name, x - RADIUS, y - 50);
    }

    //绘制图片坦克
    public abstract void drawImgTank(Graphics g);
//    {
//        //敌我坦克图片不同,所以需要判断
//        if (isEnemy) {
//            g.drawImage(enemyImg[dir], x - RADIUS, y - RADIUS, null);
//        } else {
//            g.drawImage(tankImg[dir], x - RADIUS, y - RADIUS, null);
//        }
//    }


    /**
     * 绘制坦克方法
     * 已弃用!!!!
     *
     * @param g
     */
    public void drawTank(Graphics g) {
        g.setColor(color);
        //绘制坦克的圆
        g.fillOval(x - RADIUS, y - RADIUS, RADIUS * 2, RADIUS * 2);
        //绘制炮管
        int endX = x;
        int endY = y;
        switch (dir) {
            case DIR_UP:
                endY = y - RADIUS * 2;
                break;
            case DIR_DOWN:
                endY = y + RADIUS * 2;
                break;
            case DIR_LEFT:
                endX = x - RADIUS * 2;
                ;
                break;
            case DIR_RIGHT:
                endX = x + RADIUS * 2;
                break;
        }
        g.drawLine(x, y, endX, endY);
    }

    //坦克的逻辑处理
    private void logic() {
        switch (state) {
            case STATE_STAND:
                break;
            case STATE_MOVE://处于move状态时就调用move方法
                move();
                break;
            case STATE_DIE:
                break;
        }
    }

    //坦克移动的方法
    private void move() {
        //设置旧的坐标,用于坦克和墙碰撞
        oldX = x;
        oldY = y;

        switch (dir) {
            case DIR_UP:
                y -= speed;
                //0坐标是从左上角算的,所以要减去标题栏的高度
                if (y < RADIUS + GameFrame.titleBarH) {
                    y = RADIUS + GameFrame.titleBarH;
                }
                break;
            case DIR_DOWN:
                y += speed;
                if (y > Constant.FRAME_HIGH - RADIUS) {
                    y = Constant.FRAME_HIGH - RADIUS;
                }
                break;
            case DIR_LEFT:
                x -= speed;
                if (x < RADIUS) {
                    x = RADIUS;
                }
                break;
            case DIR_RIGHT:
                x += speed;
                if (x > Constant.FRAME_WIDTH - RADIUS) {
                    x = Constant.FRAME_WIDTH - RADIUS;
                }
                break;
        }
    }

    /**
     * 开火方法
     * 包括控制开火速度
     */
    public void fire() {
        //坦克上一次开火和这一次开火的时间间隔指定时间后才能再次开火
        if (System.currentTimeMillis() - fireTime > FIRE_INTERVAL) {
            //计算炮弹出现坐标(炮口处坐标)
            int bulletX = x;
            int bulletY = y;
            switch (dir) {
                case DIR_UP:
                    bulletY -= RADIUS;
                    break;
                case DIR_DOWN:
                    bulletY += RADIUS;
                    break;
                case DIR_LEFT:
                    bulletX -= RADIUS;
                    break;
                case DIR_RIGHT:
                    bulletX += RADIUS;
                    break;
            }

            //从池塘中拿子弹
            Bullet bullet = BulletsPool.get();
            //设置参数
            bullet.setX(bulletX);
            bullet.setY(bulletY);
            bullet.setDir(dir);
            bullet.setAtk(atk);
            bullet.setColor(color);

            bullet.setVisible(true);

            //放到炮弹容器中
            bullets.add(bullet);

            //记录本次发射时间
            fireTime = System.currentTimeMillis();

            //播放开火音效
            MusicUtil.playBoom();
        }
    }

    //将坦克发射的炮弹绘制出来
    private void drawBullets(Graphics g) {
        for (Bullet bullet : bullets) {
            bullet.draw(g);
        }
        //遍历全部的子弹,将不可见的子弹移除,并还原到对象池
        for (int i = 0; i < bullets.size(); i++) {
            Bullet bullet = bullets.get(i);
            if (!bullet.isVisible()) {
                //移除时会返回移除的对象,用remove接收
                Bullet remove = bullets.remove(i);  //移除
                BulletsPool.theReturn(remove);      //还原
                i--;    //ArrayList删除元素后,当前位置后面的元素下标会变成被当前位置,如果没有i--那么就会少判断一个元素
            }
        }
    }

    //坦克销毁时,还回所有子弹并清空子弹容器
    public void bulletsReturn() {
        for (Bullet bullet : bullets) {
            BulletsPool.theReturn(bullet);
        }
        bullets.clear();
    }

    //坦克和敌人子弹的碰撞方法
    public void collideBullets(List<Bullet> bullets) {
        //遍历所有子弹,和当前的坦克进行碰撞检测
        for (Bullet bullet : bullets) {
            //获取子弹坐标
            int bulletX = bullet.getX();
            int bulletY = bullet.getY();

            //如果子弹和坦克发生了碰撞
            if (MyUtil.isCollide(x, y, RADIUS, bulletX, bulletY)) {
                //子弹消失
                bullet.setVisible(false);
                //坦克受到伤害
                hurt(bullet);
                //添加爆炸效果
                addExplode(bulletX, bulletY);
            }
        }
    }

    //添加爆炸效果的方法,传入子弹坐标
    private void addExplode(int bulletX, int bulletY) {
        //添加爆炸效果,坐标是子弹的坐标
        Explode explode = ExplodePool.get();
        explode.setX(bulletX);
        explode.setY(bulletY);
        explode.setIndex(0);    //重新设置从第0帧开始播放
        explode.setVisible(true);
        explodes.add(explode);
    }

    /**
     * 坦克减血方法
     *
     * @param bullet 传入子弹,用于获取子弹的攻击力
     */
    private void hurt(Bullet bullet) {
        final int atk = bullet.getAtk();
        hp = hp - atk;
        if (hp < 0) {
            hp = 0;
            die();
        }
    }

    /**
     * 坦克死亡方法
     */
    private void die() {
        //判断是否为敌方坦克
        if (isEnemy) {
            //消灭的敌人加一
            GameFrame.killEnemyCount++;
            //绘制一个在坦克中间的爆炸效果
            addExplode(x, y);
            //坦克被消灭之后就归还对象池
            EnemyTanksPool.theReturn(this);
            //判断本关是否结束
            if (GameFrame.isCrossLevel()) {
                //判断是为最后一关
                if (GameFrame.isLastLevel()) {
                    //如果是最后一关就设置为胜利状态
                    GameFrame.setGameState(Constant.STATE_WIN);
                } else {
                    //TODO 否则进入下一关
                    GameFrame.nextLevel();
                }
            }
        } else {
//            //玩家死亡若干毫秒后进入游戏结束画面
//            delaySecondsToOver(0);

        }
    }

    /**
     * 判断当前坦克是否死亡
     *
     * @return
     */
    public boolean isDie() {
        //只有坦克血量小于或等于0并且坦克身上的爆炸效果绘制完毕才视为死亡,解决了坦克死亡后立即消失的问题
        if (hp <= 0 && explodes.size() == 0) {
            return true;
        } else {
            return false;
        }
    }

    public boolean myTankisDie() {
        if (hp <= 0) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 绘制当前坦克上的爆炸效果,和砖块上的爆炸效果
     *
     * @param g
     */
    public void drawExplodes(Graphics g) {
        for (Explode explode : explodes) {
            explode.draw(g);
        }
        //遍历全部的爆炸效果,将不可见的爆炸效果移除,并还原到对象池
        for (int i = 0; i < explodes.size(); i++) {
            Explode explode = explodes.get(i);
            if (!explode.isVisible()) {
                //移除时会返回移除的对象,用remove接收
                Explode remove = explodes.remove(i);  //移除
                ExplodePool.theReturn(remove);      //还原
                i--;
            }
        }
    }

    /**
     * 子弹和所有地图块的碰撞
     *
     * @param tiles 所有地图块所在的数组
     */
    public void bulletsCollideMapTiles(List<MapTile> tiles) {
        for (MapTile tile : tiles) {
            if (tile.isCollideBullet(bullets)) {
                //添加爆炸效果,在方块中心
                addExplode(tile.getX() + MapTile.radius, tile.getY() + MapTile.radius);
                //如果是铁块的话就直接从这结束
                if (tile.getType() == MapTile.TYPE_HARD) {
                    continue;
                }
                //设置地图块销毁
                tile.setVisible(false);
                //归还对象池
                MapTilePool.theReturn(tile);
                //当基地被击毁之后,若干毫秒切换到游戏结束的画面
                if (tile.isHouse()) {
                    delaySecondsToOver(500);
                }
            }
        }
    }

    /**
     * 延迟若干毫秒进入死亡状态
     *
     * @param millisSecond 延迟多少毫秒
     */
    private void delaySecondsToOver(int millisSecond) {
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(millisSecond);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                GameFrame.setGameState(Constant.STATE_OVER);
            }
        }.start();
    }

    /**
     * 地图块和当前坦克碰撞方法
     * 遮挡块没有碰撞效果
     * 从地图中提取8个点,如果坦克有任何一个点和这8个点发生了碰撞就说明坦克和点发生了碰撞
     * 8个点的顺序是从左上角开始顺时针一圈
     *
     * @param tiles 所有地图块所在的集合
     * @return 碰到返回true否则返回false
     */
    public boolean isCollideTile(List<MapTile> tiles) {
        for (MapTile tile : tiles) {
            //如果地图块不可见或者地图块类型为遮挡块就直接结束
            if (!tile.isVisible() || tile.getType() == MapTile.TYPE_COVER) {
                continue;
            }
            //第一个点,左上
            int tileX = tile.getX();
            int tileY = tile.getY();
            boolean collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
            //如果碰上了就直接返回,否则就判断下一个点
            if (collide) {
                return true;
            }
            //第二个点,上中
            tileX += MapTile.radius;
            collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
            //如果碰上了就直接返回,否则就判断下一个点
            if (collide) {
                return true;
            }
            //第三个点,右上
            //因为上面已经加过半径了,所以这里只用再加一次就行
            tileX += MapTile.radius;
            collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
            //如果碰上了就直接返回,否则就判断下一个点
            if (collide) {
                return true;
            }
            //第四个点,右中
            tileY += MapTile.radius;
            collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
            //如果碰上了就直接返回,否则就判断下一个点
            if (collide) {
                return true;
            }
            //第五个点,右下
            tileY += MapTile.radius;
            collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
            //如果碰上了就直接返回,否则就判断下一个点
            if (collide) {
                return true;
            }
            //第六个点,下中
            tileX -= MapTile.radius;
            collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
            //如果碰上了就直接返回,否则就判断下一个点
            if (collide) {
                return true;
            }
            //第七个点,左下
            tileX -= MapTile.radius;
            collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
            //如果碰上了就直接返回,否则就判断下一个点
            if (collide) {
                return true;
            }
            //第八个点,左中
            tileY -= MapTile.radius;
            collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);
            //如果碰上了就直接返回,否则就判断下一个点
            if (collide) {
                return true;
            }
        }
        //如果都没有碰到就返回false
        return false;
    }

    /**
     * 坦克和多个坦克发生碰撞的方法
     *
     * @param tanks 传入坦克数组
     * @return 如果发生了碰撞返回true
     */
    public boolean isCollideTanks(List<Tank> tanks) {
        for (Tank tank : tanks) {
            //第一个点,左上
            int tileX = tank.getX();
            int tileY = tank.getY();
            boolean collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
            //如果碰上了就直接返回,否则就判断下一个点
            if (collide) {
                return true;
            }
            //第二个点,上中
            tileX += Tank.RADIUS;
            collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
            //如果碰上了就直接返回,否则就判断下一个点
            if (collide) {
                return true;
            }
            //第三个点,右上
            //因为上面已经加过半径了,所以这里只用再加一次就行
            tileX += Tank.RADIUS;
            collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
            //如果碰上了就直接返回,否则就判断下一个点
            if (collide) {
                return true;
            }
            //第四个点,右中
            tileY += Tank.RADIUS;
            collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
            //如果碰上了就直接返回,否则就判断下一个点
            if (collide) {
                return true;
            }
            //第五个点,右下
            tileY += Tank.RADIUS;
            collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
            //如果碰上了就直接返回,否则就判断下一个点
            if (collide) {
                return true;
            }
            //第六个点,下中
            tileX -= Tank.RADIUS;
            collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
            //如果碰上了就直接返回,否则就判断下一个点
            if (collide) {
                return true;
            }
            //第七个点,左下
            tileX -= Tank.RADIUS;
            collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
            //如果碰上了就直接返回,否则就判断下一个点
            if (collide) {
                return true;
            }
            //第八个点,左中
            tileY -= Tank.RADIUS;
            collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
            //如果碰上了就直接返回,否则就判断下一个点
            if (collide) {
                return true;
            }
        }
        //如果都没有碰到就返回false
        return false;
    }

    /**
     * 坦克和单个坦克碰撞的方法
     *
     * @param tank 传入单个坦克
     * @return 如果发生了碰撞返回true
     */
    public boolean isCollideTank(Tank tank) {
        //第一个点,左上
        int tileX = tank.getX();
        int tileY = tank.getY();
        boolean collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
        //如果碰上了就直接返回,否则就判断下一个点
        if (collide) {
            return true;
        }
        //第二个点,上中
        tileX += Tank.RADIUS;
        collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
        //如果碰上了就直接返回,否则就判断下一个点
        if (collide) {
            return true;
        }
        //第三个点,右上
        //因为上面已经加过半径了,所以这里只用再加一次就行
        tileX += Tank.RADIUS;
        collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
        //如果碰上了就直接返回,否则就判断下一个点
        if (collide) {
            return true;
        }
        //第四个点,右中
        tileY += Tank.RADIUS;
        collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
        //如果碰上了就直接返回,否则就判断下一个点
        if (collide) {
            return true;
        }
        //第五个点,右下
        tileY += Tank.RADIUS;
        collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
        //如果碰上了就直接返回,否则就判断下一个点
        if (collide) {
            return true;
        }
        //第六个点,下中
        tileX -= Tank.RADIUS;
        collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
        //如果碰上了就直接返回,否则就判断下一个点
        if (collide) {
            return true;
        }
        //第七个点,左下
        tileX -= Tank.RADIUS;
        collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
        //如果碰上了就直接返回,否则就判断下一个点
        if (collide) {
            return true;
        }
        //第八个点,左中
        tileY -= Tank.RADIUS;
        collide = MyUtil.isCollide(x + 30, y + 30, RADIUS, tileX, tileY);
        //如果碰上了就直接返回,否则就判断下一个点
        if (collide) {
            return true;
        }
        //如果都没有碰到就返回false
        return false;
    }

    /**
     * 坦克回退方法
     */
    public void back() {
        //如果发生了碰撞那么坦克就回到上一步的坐标
        x = oldX;
        y = oldY;
    }


    /**
     * 血条类
     * 为了方便封装所以写成了内部类
     */
    class BloodBar {
        //血条宽度
        public static final int BAR_LENGTH = 50;
        //血条高度
        public static final int BAR_HEIGHT = 5;
        //血条离坦克的距离
        public static final int BAR_DISTANCE = 15;

        public void draw(Graphics g) {
            //设置底色
            g.setColor(Color.CYAN);
            //绘制矩形,当做血条
            //因为要显示在坦克上方,所以才要 y-RADIUS-20
            g.fillRect(x - RADIUS, y - RADIUS - BAR_DISTANCE, BAR_LENGTH, BAR_HEIGHT);

            //设置血量颜色
            g.setColor(Color.RED);
            //当前血量乘血条长度除以默认血量就是当前血条长度
            g.fillRect(x - RADIUS, y - RADIUS - BAR_DISTANCE, hp * BAR_LENGTH / maxHp, BAR_HEIGHT);

            //设置边框颜色并且绘制边框
            g.setColor(Color.GREEN);
            g.drawRect(x - RADIUS, y - RADIUS - BAR_DISTANCE, BAR_LENGTH, BAR_HEIGHT);

        }
    }
}

BulletsPool类

在这里插入代码片package c02.n02.util;

import c02.n02.game.Bullet;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

/**
 * 子弹对象池类
 */
public class BulletsPool {
    //设置默认的池大小可以容纳200个子弹对象
    public static final int DEFAULT_POOL_SIZE = 200;
    //设置池塘最多能放多少子弹对象
    public static final int POOL_MAX_SIZE = 300;
    //用于保存所有子弹的容器
    private static List<Bullet> pool = new ArrayList<>();
    private static HashSet<Bullet> poo=new HashSet<>();

    //在类加载的时候创建200个子弹对象
    static {
        for (int i = 0; i < DEFAULT_POOL_SIZE; i++) {
            pool.add(new Bullet());
        }
    }

    /**
     * 从池塘中获取一个子弹对象
     *
     * @return 返回一个子弹对象
     */
    public static Bullet get() {
        Bullet bullet = null;
        if (pool.size() == 0) {
            //如果池塘被拿空了就新建一个子弹
            bullet = new Bullet();
//            System.out.println("池空");
        } else {
            //取出一个子弹,并且在池塘中删除
            bullet = pool.remove(0);
//            System.out.println("从池中取出子弹,池中还剩"+pool.size());
        }
        return bullet;
    }

    /**
     * 归还子弹
     *
     * @param bullet 传入子弹对象
     */
    public static void theReturn(Bullet bullet) {
        if (pool.size() == POOL_MAX_SIZE) {
            //池塘中的子弹个数到达了最大值那就不再归还
//            System.out.println("池满");
            return;
        } else {
//            System.out.println("归还子弹");
            pool.add(bullet);
        }
    }

}

Constant类

package c02.n02.util;

import java.awt.*;

/**
 * 游戏常量类,所有的常量都在此类中
 */
public class Constant {
    /*=================START============游戏窗口相关============================*/
    //标题
    public static final String GAME_TITLE = "坦克大战";
    //游戏窗口大小
    public static final int FRAME_WIDTH = 1280;
    public static final int FRAME_HIGH = 720;
    //动态获取系统的宽和高
    public static final int SCREEN_W = Toolkit.getDefaultToolkit().getScreenSize().width;
    public static final int SCREEN_H = Toolkit.getDefaultToolkit().getScreenSize().height;
    //左上角位置,把游戏窗口定位到屏幕中间
    public static final int FRAME_X = (SCREEN_W - FRAME_WIDTH) / 2;
    public static final int FRAME_Y = (SCREEN_H - FRAME_HIGH) / 2;

    /*=================END============游戏窗口相关============================*/


    /*=================START============游戏菜单相关============================*/
    //七个状态
    public static final int STATE_MENU = 0;     //菜单
    public static final int STATE_HELP = 1;     //帮助
    public static final int STATE_ABOUT = 2;    //关于
    public static final int STATE_RUN = 3;      //开始游戏
    public static final int STATE_OVER = 4;     //死亡
    public static final int STATE_WIN = 5;      //胜利
    public static final int STATE_LEVEL = 6;    //选择关卡

    public static final String[] MENUS = {
            "单人游戏",
            "双人游戏",
            "选择关卡",
            "游戏帮助",
            "游戏关于",
            "退出游戏"
    };

    public static final String[] OVER_STR = {
            "ESC键退出游戏",
            "ENTER键返回主菜单"
    };

    //字体
    public static final Font FONT = new Font("迷你简菱心", Font.PLAIN, 36);
    //小号状态
    public static final Font SMALL_FONT = new Font("黑体", Font.PLAIN, 14);

    /*=================END============游戏菜单相关============================*/


    //刷新时间,毫秒
    public static final int REPAINT_INTERVAL = 30;
    //敌人坦克数量
    public static final int ENEMY_MAX_COUNT = 10;
    //敌人坦克产生间隔,毫秒
    public static final int ENEMY_BORN_INTERVAL = 2000;
    //坦克AI,敌人改变状态间隔
    public static final int ENEMY_AI_INTERVAL = 2000;
    //发射子弹概率
    public static final double ENEMY_FIRE_PERCENT = 0.05;

}

EnemyTanksPool类

package c02.n02.util;

import c02.n02.tank.EnemyTank;
import c02.n02.tank.Tank;

import java.util.ArrayList;
import java.util.List;

/**
 * 敌人坦克池
 */
public class EnemyTanksPool {
    //设置默认的池大小可以容纳20个坦克对象
    public static final int DEFAULT_POOL_SIZE = 20;
    //设置池塘最多能放多少坦克对象
    public static final int POOL_MAX_SIZE = 30;
    //用于保存所有坦克的容器
    private static List<Tank> pool = new ArrayList<>();

    //在类加载的时候创建20个坦克对象
    static {
        for (int i = 0; i < DEFAULT_POOL_SIZE; i++) {
            pool.add(new EnemyTank());
        }
    }

    /**
     * 从池塘中获取一个坦克对象
     *
     * @return 返回一个坦克对象
     */
    public static Tank get() {
        Tank tank = null;
        if (pool.size() == 0) {
            //如果池塘被拿空了就新建一个坦克
            tank = new EnemyTank();
//            System.out.println("池空");
        } else {
            //取出一个坦克,并且在池塘中删除
            tank = pool.remove(0);
//            System.out.println("从池中取出坦克,池中还剩"+pool.size());
        }
        return tank;
    }

    /**
     * 归还坦克
     *
     * @param tank 传入坦克对象
     */
    public static void theReturn(Tank tank) {
        if (pool.size() == POOL_MAX_SIZE) {
            //池塘中的坦克个数到达了最大值那就不再归还
//            System.out.println("池满");
            return;
        } else {
//            System.out.println("归还坦克");
            pool.add(tank);
        }
    }
}

ExplodePool类

package c02.n02.util;

import c02.n02.game.Explode;

import java.util.ArrayList;
import java.util.List;

/**
 * 爆炸效果对象池类
 */
public class ExplodePool {
    //设置默认的池大小可以容纳20个爆炸对象
    public static final int DEFAULT_POOL_SIZE = 20;
    //设置池塘最多能放多少爆炸对象
    public static final int POOL_MAX_SIZE = 30;
    //用于保存所有爆炸效果的容器
    private static List<Explode> pool = new ArrayList<>();

    //在类加载的时候创建20个爆炸对象
    static {
        for (int i = 0; i < DEFAULT_POOL_SIZE; i++) {
            pool.add(new Explode());
        }
    }

    /**
     * 从池塘中获取一个爆炸对象
     *
     * @return 返回一个爆炸对象
     */
    public static Explode get() {
        Explode explode = null;
        if (pool.size() == 0) {
            //如果池塘被拿空了就新建一个爆炸对象
            explode = new Explode();
//            System.out.println("池空");
        } else {
            //取出一个爆炸对象,并且在池塘中删除
            explode = pool.remove(0);
//            System.out.println("从池中取出爆炸效果,池中还剩"+pool.size());
        }
        return explode;
    }

    /**
     * 归还爆炸效果
     *
     * @param explode 传入爆炸效果对象
     */
    public static void theReturn(Explode explode) {
        if (pool.size() == POOL_MAX_SIZE) {
            //池塘中的爆炸对象到达了最大值那就不再归还
//            System.out.println("池满");
            return;
        } else {
//            System.out.println("归还爆炸对象");
            pool.add(explode);
        }
    }

}

MapTilePool类

package c02.n02.util;

import c02.n02.map.MapTile;

import java.util.ArrayList;
import java.util.List;

/**
 * 砖块对象池
 */
public class MapTilePool {
    //设置默认的池大小可以容纳100个砖块对象
    public static final int DEFAULT_POOL_SIZE = 100;
    //设置池塘最多能放多少砖块对象
    public static final int POOL_MAX_SIZE = 150;
    //用于保存所有砖块的容器
    private static List<MapTile> pool = new ArrayList<>();

    //在类加载的时候创建100个砖块对象
    static {
        for (int i = 0; i < DEFAULT_POOL_SIZE; i++) {
            pool.add(new MapTile());
        }
    }

    /**
     * 从池塘中获取一个砖块对象
     *
     * @return 返回一个砖块对象
     */
    public static MapTile get() {
        MapTile tile = null;
        if (pool.size() == 0) {
            //如果池塘被拿空了就新建一个砖块
            tile = new MapTile();
//            System.out.println("池空");
        } else {
            //取出一个砖块,并且在池塘中删除
            tile = pool.remove(0);
//            System.out.println("从池中取出砖块,池中还剩"+pool.size());
        }
        return tile;
    }

    /**
     * 归还砖块
     *
     * @param tile 传入砖块对象
     */
    public static void theReturn(MapTile tile) {
        if (pool.size() == POOL_MAX_SIZE) {
            //池塘中的砖块个数到达了最大值那就不再归还
//            System.out.println("池满");
            return;
        } else {
//            System.out.println("归还砖块,池中还剩"+pool.size());
            pool.add(tile);
        }
    }

}

MusicUtil类

package c02.n02.util;

import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import java.applet.Applet;
import java.applet.AudioClip;
import java.io.File;
import java.io.IOException;

/**
 * 音乐类
 */
public class MusicUtil {
    private static Clip start;
    private static AudioClip boom;
    private static AudioClip menuCase;


    //静态,大文件请使用底下的play方法
    static {
        //适用于小文件
        try {
            boom = Applet.newAudioClip(new File("music/boom.wav").toURL());    //加载爆炸的音乐
            menuCase = Applet.newAudioClip(new File("music/menuCase.wav").toURL());    //加载选择菜单时候的音乐
        } catch (IOException e) {
            //TODO
            System.out.println("音频接口异常");
            e.printStackTrace();
        }

        //适用于大文件
        try {
            start = AudioSystem.getClip();
            start.open(AudioSystem.getAudioInputStream(new File("music/F-777 - The 7 Seas.wav")));
        } catch (Exception exc) {
            exc.printStackTrace(System.out);
        }
    }


    /**
     * 播放开始音乐
     * 循环播放10次
     */
    public static void playStart() {
        start.loop(10);
    }

    /**
     * 结束播放音乐
     */
    public static void stopStart() {
        start.stop();
    }

    /**
     * 播放爆炸音乐
     */
    public static void playBoom() {
        boom.play();
    }

    /**
     * 播放选择菜单音乐
     */
    public static void playMenuCase() {
        menuCase.play();
    }


}

MyUtil类

package c02.n02.util;

import java.awt.*;

/*
 * 工具类
 * */
public class MyUtil {
    private MyUtil() {
    }

    /**
     * 得到指定区间内的随机数
     *
     * @param min 区间最小值,包含
     * @param max 区间最大值
     * @return 随机数
     */
    public static final int getRandomNumber(int min, int max) {
        return (int) (Math.random() * (max - min) + min);
    }

    /**
     * 返回随机颜色
     *
     * @return 返回一个颜色
     */
    public static final Color getRandomColor() {
        int r = getRandomNumber(0, 256);
        int g = getRandomNumber(0, 256);
        int b = getRandomNumber(0, 256);
        Color c = new Color(r, g, b);
        return c;
    }

    /**
     * 碰撞检测
     * 判断一个点是否在某一个正方形中
     *
     * @param rectX  正方形中心点X
     * @param rectY  正方形中心点Y
     * @param radius 正方形的半径
     * @param pointX 点X
     * @param pointY 点Y
     * @return 点在正方形中返回true,否则返回false
     */
    public static final boolean isCollide(int rectX, int rectY, int radius, int pointX, int pointY) {
        //正方形中心点和点的X轴Y轴的距离
        //因为可能为负数,所以要取绝对值
        int disX = Math.abs(rectX - pointX);
        int disY = Math.abs(rectY - pointY);
        //如果某个方向的距离小于正方形的半径就说明碰撞了
        if (disX < radius && disY < radius) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 根据图片的路径创建加载图片对象
     *
     * @param path 图片路径
     * @return 返回对象
     */
    public static final Image createImage(String path) {
        return Toolkit.getDefaultToolkit().createImage(path);
    }

    //形容词
    public static final String[] MODIFIY = {
            "一样", "喜欢", "美丽", "一定", "原来", "美好", "开心", "可能",
            "可爱", "明白", "所有", "后来", "重要", "经常", "自然", "真正",
            "害怕", "空中", "红色", "干净"
    };
    //名词
    public static final String[] NAMES = {
            "豹虎", "蜂猴", "熊猴", "叶猴", "紫貂", "貂熊", "熊狸", "云豹",
            "雪豹", "儒艮", "黑麂", "野马", "鼷鹿", "坡鹿", "豚鹿", "麋鹿",
            "野牛", "藏羚", "河狸", "蝎子"
    };

    /**
     * 返回一个随机的名字
     *
     * @return 返回一个名字
     */
    public static final String getRandomName() {
        String s = MODIFIY[getRandomNumber(0, MODIFIY.length)] + "的" + NAMES[getRandomNumber(0, NAMES.length)];
        return s;
    }
}