引言:

坦克大战也是小时一个比较经典的游戏了,我在网上也是参考了韩顺平老师写的坦克大战,并做了一下完善,编写出来作为儿时的回忆吧!

思路:

  1. 创建主窗口,加载菜单及游戏面板。

  1. 在游戏面板中初始化各种参数,并建立各种功能组件。

  1. 利用线程固定刷新游戏界面。

  1. 处理各种碰撞问题

  1. 游戏结束。

代码:

本游戏用的是JDK1.8,编码UTF-8;

我这里用的IDE是Intellij Idea,新建了一个game的空项目,tankwar作为其中的一个模块(当然这个不重要,个人喜好罢了)。类比较多,TankWar.java是游戏入口类。GameFrame.java是主窗口类。GamePanel.java是游戏面板类。GameLogic.java是游戏逻辑类。先一口气把所有的代码贴上来再说。

  1. TankWar.java 游戏入口类

package lag.game.tankwar;/** * 功能:坦克大战
* 作者:我是小木鱼(Lag) */public class TankWar{public static void main(String[] args){new GameFrame();}}
  1. GameFrame.java游戏窗口

package lag.game.tankwar;import java.awt.*;import javax.swing.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;/** * 游戏窗口 */public class GameFrame extends JFrame implements ActionListener{/** 游戏面板 */private GamePanel gamePanel;/** * 构造函数 */public GameFrame(){try{// 定制菜单JMenuBar mb_main = new JMenuBar();// 菜单栏JMenu m_game = new JMenu("游戏"); // 游戏菜单m_game.setFont(new Font("微软雅黑",Font.PLAIN,12));// 游戏菜单字体JMenuItem mi_new = new JMenuItem("新游戏");// 游戏菜单中的新游戏子菜单mi_new.setFont(new Font("微软雅黑",Font.PLAIN,12)); // 子菜单字体mi_new.addActionListener(this); // 为窗口增加新游戏菜单的事件监听mi_new.setActionCommand("new"); // 设置新游戏菜单的监听标识m_game.add(mi_new); // 将新游戏子菜单附加到游戏菜单上JMenuItem mi_grade = new JMenuItem("选关卡");mi_grade.setFont(new Font("微软雅黑",Font.PLAIN,12));mi_grade.addActionListener(this);mi_grade.setActionCommand("grade");m_game.add(mi_grade);m_game.addSeparator();// 菜单分隔符JMenuItem mi_exit = new JMenuItem("退出");mi_exit.setFont(new Font("微软雅黑",Font.PLAIN,12));mi_exit.addActionListener(this);mi_exit.setActionCommand("exit");m_game.add(mi_exit);mb_main.add(m_game);JMenu m_help = new JMenu("帮助");m_help.setFont(new Font("微软雅黑",Font.PLAIN,12));JMenuItem mi_about = new JMenuItem("关于");mi_about.setFont(new Font("微软雅黑",Font.PLAIN,12));mi_about.addActionListener(this);mi_about.setActionCommand("about");m_help.add(mi_about);mb_main.add(m_help);this.setJMenuBar(mb_main);// 定制面板this.gamePanel = new GamePanel();this.add(this.gamePanel);// 定制窗口this.setTitle("坦克大战"); // 标题this.setLayout(null); // 清空布局管理器this.setSize(this.gamePanel.getWidth() + 10,this.gamePanel.getHeight() + 60); // 根据游戏面板大小设置游戏窗口大小this.setResizable(false); // 程序运行时禁止改变窗口大小尺寸this.setLocationRelativeTo(null); // 窗口居中this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 点击窗口X按钮时默认关闭程序this.setVisible(true);// 显示窗口//音乐(用线程播放)//new Thread(()->{//GameMusic gameMusic = new GameMusic(this.getClass().getClassLoader().getResourceAsStream("audio/start.wav"));//gameMusic.play(false);//}).start();}catch (Exception e){e.printStackTrace();JOptionPane.showMessageDialog(this,"程序出现异常错误,即将退出!\r\n\r\n"+e.toString(),"提示",JOptionPane.ERROR_MESSAGE);System.exit(0);}}/** * 事件监听 */@Overridepublic void actionPerformed(ActionEvent e){// 监听事件的标识String command = e.getActionCommand();switch (command){case "new": // 开始新游戏this.gamePanel.newGame();break;case "grade":int gradeCount = GameMap.getGradeCount();String[] options = new String[gradeCount];for (int i = 0; i < gradeCount; i++){options[i] = i + 1 + "";}String grade = (String)JOptionPane.showInputDialog(this, "请选择你要进行的关卡:","选关", JOptionPane.INFORMATION_MESSAGE, null, options, options[0]);gamePanel.setGrade(Integer.parseInt(grade));gamePanel.newGame();break;case "exit":System.exit(0);break;case "about":JOptionPane.showMessageDialog(this,"我是小木鱼(Lag)","提示",JOptionPane.INFORMATION_MESSAGE);break;}}}
  1. GamePanel.java游戏面板

package lag.game.tankwar;import java.awt.*;import javax.swing.*;import java.util.List;import java.util.ArrayList;import java.util.Vector;import java.awt.event.KeyEvent;import java.awt.event.KeyListener;import java.awt.image.BufferedImage;/** * 游戏面板 */public class GamePanel extends JPanel implements KeyListener{/** 游戏状态常量 */private final static int GAME_STATE_READY = 0; // 游戏未开始private final static int GAME_STATE_RUNNING = 1; // 游戏运行中private final static int GAME_STATE_OVER = 9;// 游戏已结束/** 游戏运行场景范围常量 */public final static int GAME_ACTION_WIDTH = Block.BLOCK_WIDTH * 26; // 游戏运行场景宽度public final static int GAME_ACTION_HEIGHT = Block.BLOCK_HEIGHT * 26;// 游戏运行场景宽度/** 游戏面板范围常量 */public final static int GAME_PANEL_WIDTH = GAME_ACTION_WIDTH + 150; // 游戏面板宽度public final static int GAME_PANEL_HEIGHT = GAME_ACTION_HEIGHT; // 游戏面板高度/** 利用双缓冲机制防止画面闪烁(创建一张与面板画面一样大小的图片,所有的元素先绘制到该图片上,再将该图片一次性绘制到面板画面上) */private BufferedImage bufferedImage = new BufferedImage(GAME_ACTION_WIDTH,GAME_ACTION_HEIGHT,BufferedImage.TYPE_4BYTE_ABGR);/** 游戏刷新频率 */private int repaintInterval = 50;// 游戏每秒刷新(1000/repaintInterval)次/** 游戏状态 */private int gameState = GAME_STATE_READY;/** 我方的坦克 */private Hero hero;/** 我方坦克模型(显示在计分榜上) */private Hero heroModel = new Hero(GAME_ACTION_WIDTH + 30,200,Tank.TANK_DIRECTION_RIGHT);/** 敌方坦克池(考虑线程安全用Vector,没用ArrayList) */private Vector enemyPool = new Vector();/** 敌方坦克池最大数量 */private int enemyPoolMaxNum = 0;/** 敌方坦克死亡数量 */private int enemyDeadNum = 0;/** 敌方坦克模型(显示在计分榜上) */private Enemy enemyModel = new Enemy(GAME_ACTION_WIDTH + 30,20,Tank.TANK_DIRECTION_RIGHT);/** 创建敌方坦克线程是否运行标识 */private boolean createEnemyTankThreadRunning = false;/** 上次投放时间(用来设置投放坦克最小时间间隔) */private long lastCreateEnemyTankTime = System.currentTimeMillis();/** 块列表 */private List blockList = new ArrayList();/** 爆炸列表 */private List bombList = new ArrayList();/** 游戏关卡 */private int grade = 1;/** 游戏某关地图 */private byte[][] gameGradeMap;/** 大本营 */private Camp camp = new Camp(12 * Block.BLOCK_WIDTH,24 * Block.BLOCK_HEIGHT);/** * 构造函数 */public GamePanel(){// 设置面板大小this.setSize(GAME_PANEL_WIDTH,GAME_PANEL_HEIGHT);// 监听键盘事件this.setFocusable(true); // 先让面板得到焦点this.addKeyListener(this);// 将监听键盘事件附加到面板上// 创建游戏逻辑实例GameLogic.setInstance(new GameLogic(this));}/** * 开始新游戏 */public void newGame(){// 游戏清空重置gameReset();// 设置游戏状态为运行中this.gameState = GAME_STATE_RUNNING;// 加载游戏地图loadGameMap();// 设置大本营camp.setState(Camp.CAMP_STATE_RUNNING);// 创建我方坦克int heroX = 8 * Block.BLOCK_WIDTH;int heroY = (this.gameGradeMap.length - 2) * Block.BLOCK_HEIGHT;hero = new Hero(heroX,heroY,Tank.TANK_DIRECTION_UP);hero.work();// 创建敌方坦克createEnemyTank();// 启动线程来定时刷新画面refresh();}/** * 加载游戏地图 */private void loadGameMap(){// 得到本关地图this.gameGradeMap = GameMap.getGameMap(this.grade);// 得到地图位置(几行几列)int row = this.gameGradeMap.length;int column = this.gameGradeMap[0].length;// 开始循环for (int i = 0; i < row; i++){for (int j = 0; j < column; j++){int mapValue = this.gameGradeMap[i][j];switch (mapValue){case Block.BLOCK_KIND_BRICK:Brick brick = new Brick(Block.BLOCK_WIDTH * j,Block.BLOCK_HEIGHT * i);this.blockList.add(brick);break;case Block.BLOCK_KIND_IRON:Iron iron = new Iron(Block.BLOCK_WIDTH * j,Block.BLOCK_HEIGHT * i);this.blockList.add(iron);break;}}}this.enemyPoolMaxNum = GameMap.getEnemyTankNum(this.grade); // 本关敌方坦克数量}/** * 游戏重置 */private void gameReset(){// 停止上局正在运行的线程if(this.gameState == GAME_STATE_RUNNING){this.gameState = GAME_STATE_READY;}while (true){if(this.createEnemyTankThreadRunning){try{Thread.sleep(10);}catch (InterruptedException e){throw new RuntimeException(e);}}else{break;}}// 清空敌方坦克池中的所有坦克及其子弹for (int i = 0; i < enemyPool.size(); i++){Enemy enemy = enemyPool.get(i);enemy.reset();for (int j = 0; j < enemy.getBulletPool().size(); j++){Bullet bullet = enemy.getBulletPool().get(j);bullet.reset();}enemy.getBulletPool().clear();}enemyPool.clear();enemyDeadNum = 0;// 清空我方坦克及其子弹if(hero != null){hero.reset();for (int i = 0; i {//System.out.println("创建敌方坦克线程开始启动...");// 记录已投放的坦克数量int createEnemyTankNum = 0;try{while (this.gameState == GAME_STATE_RUNNING){// 休眠一会try{Thread.sleep(repaintInterval);// 休眠时间短主要是考虑可以迅速退出本线程}catch (InterruptedException e){e.printStackTrace();}// 控制连续投放间隔long curCreateEnemyTankTime = System.currentTimeMillis();if((curCreateEnemyTankTime - this.lastCreateEnemyTankTime) >= 3000)// 3秒投放{this.lastCreateEnemyTankTime = curCreateEnemyTankTime;}else{continue;}// 投放敌方坦克到达最大值后退出本线程if(createEnemyTankNum >= enemyPoolMaxNum){break;}// 在敌方坦克池中寻找空闲的坦克随机显示在左上角或右上角Enemy enemy = null;for (int i = 0; i < enemyPool.size(); i++){Enemy tmpEnemy = enemyPool.get(i);if(tmpEnemy.getState() == Tank.TANK_STATE_FREE){enemy = tmpEnemy;//System.out.println("找到空闲的敌方坦克了..................");break;}}// 没有就增加一个if(enemy == null){enemy = new Enemy(-100,-100,Tank.TANK_DIRECTION_DOWN);enemyPool.add(enemy);//System.out.println("新建敌方坦克了.................");}// 开始投放(随机投放到左上角与右上角,坦克不能重叠)int enemyX; // 坦克X坐标int enemyDirection = Tank.TANK_DIRECTION_DOWN;// 坦克方向默认向下int randomPos = GameLogic.getRandomInt(0,1);// 随机生成位置(0-左上角,1-右上角)if(randomPos == 0)// 左上角(方向下/右){enemyX = 0;if(GameLogic.getRandomInt(0,1) == 1){enemyDirection = Tank.TANK_DIRECTION_RIGHT;}}else// 右上角(方向下/左){enemyX = GAME_ACTION_WIDTH - Tank.TANK_WIDTH;if(GameLogic.getRandomInt(0,1) == 1){enemyDirection = Tank.TANK_DIRECTION_LEFT;}}enemy.setX(enemyX);enemy.setY(0);enemy.setDirection(enemyDirection);// 判断该范围内是否可以投放运行boolean workFlag = true;for (int i = 0; i < enemyPool.size(); i++){Enemy tmpEnemy = enemyPool.get(i);if(tmpEnemy.getState() == Tank.TANK_STATE_RUNNING && enemy != tmpEnemy){if(GameLogic.getInstance().tankCollideTank(enemy,tmpEnemy))// 敌方坦克占位,不能投放{workFlag = false;continue;}}}if(GameLogic.getInstance().tankCollideTank(enemy,hero))// 我方坦克占位,不能投放{workFlag = false;}// 不能投放准备下一次if(!workFlag){continue;}// 可以投放了enemy.work(); // 启动线程开始工作了createEnemyTankNum++;}}catch (Exception e){e.printStackTrace();}finally{this.createEnemyTankThreadRunning = false;// 线程结束}//System.out.println("创建敌方坦克线程退出历史舞台了......");}).start();}/** * 游戏结束 */private void gameOver(){// 设置游戏状态this.gameState = GAME_STATE_OVER;// 游戏清空重置gameReset();}/** * 游戏胜利 */private void gameWin(){if(enemyPoolMaxNum - enemyDeadNum <= 0){this.grade++;newGame();}}/** * 碰撞处理 */private void collide(){// 判断我方子弹是否与敌方坦克或块或大本营碰撞for (int i = 0; i < hero.getBulletPool().size(); i++){Bullet bullet = hero.getBulletPool().get(i);if(bullet.getState() == Bullet.BULLET_STATE_RUNNING)// 我方子弹飞行中{// 判断是否与敌方坦克碰撞for (int j = 0; j = 0; m--){Block block = blockList.get(m);// 开始判断if(GameLogic.getInstance().bulletCollideBlock(bullet,block)) // 这还真碰上了{// 记录块坐标int bombX = block.getX();int bombY = block.getY();// 子弹销毁bullet.reset();if(block.getBlockKind() == Block.BLOCK_KIND_BRICK){// 砖块销毁blockList.remove(m);// 砖块爆炸addBomb(bombX,bombY,Block.BLOCK_WIDTH,Block.BLOCK_HEIGHT);}else{addBomb(bombX,bombY,Block.BLOCK_WIDTH,Block.BLOCK_HEIGHT);}}}// 判断是否与大本营碰撞if(camp.getState() == Camp.CAMP_STATE_RUNNING){if(GameLogic.getInstance().bulletCollideCamp(bullet,camp)) // 完蛋了啊{// 子弹销毁bullet.reset();// 大本营销毁camp.setState(Camp.CAMP_STATE_FREE);// 大本营爆炸addBomb(camp.getX(),camp.getY(),camp.getWidth(),camp.getHeight());// 游戏结束gameOver();}}}}// 判断敌方子弹是否与我方坦克或块或大本营碰撞(不需要判断敌方坦克状态,因为即使坦克销毁子弹可能仍然在飞)for (int i = 0; i < enemyPool.size(); i++){// 判断是否与我方坦克碰撞Enemy enemy = enemyPool.get(i);for (int j = 0; j = 0; m--){Block block = blockList.get(m);// 开始判断if(GameLogic.getInstance().bulletCollideBlock(bullet,block)) // 这还真碰上了{// 记录块坐标int bombX = block.getX();int bombY = block.getY();// 子弹销毁bullet.reset();if(block.getBlockKind() == Block.BLOCK_KIND_BRICK){// 砖块销毁blockList.remove(m);// 砖块爆炸addBomb(bombX,bombY,Block.BLOCK_WIDTH,Block.BLOCK_HEIGHT);}else{addBomb(bombX,bombY,Block.BLOCK_WIDTH,Block.BLOCK_HEIGHT);}}}// 判断子弹是否与大本营碰撞if(camp.getState() == Camp.CAMP_STATE_RUNNING){if(GameLogic.getInstance().bulletCollideCamp(bullet,camp)) // 完蛋了啊{// 子弹销毁bullet.reset();// 大本营销毁camp.setState(Camp.CAMP_STATE_FREE);// 大本营爆炸addBomb(camp.getX(),camp.getY(),camp.getWidth(),camp.getHeight());// 游戏结束gameOver();}}}}}}/** * 添加爆炸 */private void addBomb(int x,int y,int w,int h){// 查找空闲的爆炸Bomb bomb = null;for (int i = 0; i < bombList.size(); i++){Bomb tmpBomb = bombList.get(i);if(tmpBomb.getState() == Bomb.BOMB_STATE_FREE){bomb = tmpBomb;//System.out.println("找到空闲的爆炸了..................");break;}}// 没有就增加一个if(bomb == null){bomb = new Bomb(-100,-100);bombList.add(bomb);//System.out.println("新建爆炸了.................");}bomb.setX(x);bomb.setY(y);bomb.setWidth(w);bomb.setHeight(h);bomb.work();}/** * 字符被输入 */@Overridepublic void keyTyped(KeyEvent e){}/** * 某键被按下 */@Overridepublic void keyPressed(KeyEvent e){// 游戏未开始或结束禁止按键if(this.gameState != GAME_STATE_RUNNING){return;}int keyCode = e.getKeyCode();switch (keyCode){case KeyEvent.VK_W:case KeyEvent.VK_UP:hero.move(Tank.TANK_DIRECTION_UP);break;case KeyEvent.VK_D:case KeyEvent.VK_RIGHT:hero.move(Tank.TANK_DIRECTION_RIGHT);break;case KeyEvent.VK_S:case KeyEvent.VK_DOWN:hero.move(Tank.TANK_DIRECTION_DOWN);break;case KeyEvent.VK_A:case KeyEvent.VK_LEFT:hero.move(Tank.TANK_DIRECTION_LEFT);break;case KeyEvent.VK_SPACE:hero.shoot();break;}}/** * 某键被释放 */@Overridepublic void keyReleased(KeyEvent e){}/** * 利用线程来定时刷新重绘游戏画面,适用于飞机类刷新频率较高的游戏。
* 动画原理就是在极短时间内连续显示多个图片,给人眼一种画面动起来的错觉(人眼的识别静态图时间为0.1秒)。
* 屏幕刷新频率(FPS->帧/每秒),单位赫兹(Hz)。
* 20Hz ->每秒刷新画面20次,即每隔(1000/20)毫秒刷新一次画面。
*/private void refresh(){// 利用Lambda表达式替代内部类更方便new Thread(() ->{//System.out.println("游戏自动刷新线程开始启动...");while (this.gameState == GAME_STATE_RUNNING){// 碰撞处理collide();// 开始刷新repaint();// 延时睡眠try{Thread.sleep(repaintInterval);}catch (InterruptedException e){e.printStackTrace();}}//System.out.println("游戏自动刷新线程退出历史舞台了......");}).start();}/** * 绘制游戏画面(利用双缓冲机制防止画面闪烁) */@Overridepublic void paint(Graphics g){// 完成初始化工作,该语句千万不要动super.paint(g);// 准备开始游戏if(this.gameState == GAME_STATE_READY){// 清一下背景g.setColor(Color.BLACK);g.fillRect(0,0,this.getWidth(),this.getHeight());// 显示Logog.setColor(Color.ORANGE);g.setFont(new Font("微软雅黑",Font.BOLD,60));g.drawString("坦克大战", 160,170);g.setColor(Color.CYAN);g.setFont(new Font("微软雅黑",Font.BOLD,40));g.drawString("2023", 280,280);g.setColor(Color.WHITE);g.setFont(new Font("宋体",Font.PLAIN,20));g.drawString("我是小木鱼(Lag)制作", 240,420);return;}// 游戏结束if(this.gameState == GAME_STATE_OVER){g.setColor(Color.BLACK);g.fillRect(0,0,this.getWidth(),this.getHeight());g.setColor(Color.RED);g.setFont(new Font("宋体",Font.BOLD,60));g.drawString("游戏结束", 200,170);g.drawString("大虾请重新来过吧!", 70,280);}// 游戏进行中if(this.gameState == GAME_STATE_RUNNING){// 设置游戏面板区域g.setColor(Color.LIGHT_GRAY);g.fillRect(0,0,this.getWidth(),this.getWidth());g.setColor(Color.WHITE);g.fill3DRect(GAME_ACTION_WIDTH,0,2,GAME_ACTION_HEIGHT,true);// 绘制分数// 敌方坦克信息enemyModel.draw(g);g.setColor(Tank.TANK_COLOR_ENEMY);g.setFont(new Font("微软雅黑",Font.BOLD,20));g.drawString("总数:" + enemyPoolMaxNum,enemyModel.getX() ,enemyModel.getY() + 70);g.drawString("死亡:" + enemyDeadNum,enemyModel.getX() ,enemyModel.getY() + 100);g.drawString("剩余:" + (enemyPoolMaxNum - enemyDeadNum),enemyModel.getX() ,enemyModel.getY() + 130);// 我方坦克信息heroModel.draw(g);g.setColor(Tank.TANK_COLOR_HERO);g.setFont(new Font("微软雅黑",Font.BOLD,20));g.drawString("总数:1" ,heroModel.getX() ,heroModel.getY() + 70);g.drawString("生命:" + hero.getHp() ,heroModel.getX() ,heroModel.getY() + 100);// 关口信息g.setColor(Color.BLACK);int gradeModelX = heroModel.getX();int gradeModelY = heroModel.getY() + 150;g.fillRect(gradeModelX,gradeModelY,3,40);Polygon triangle = new Polygon();triangle.addPoint(gradeModelX + 3, gradeModelY);triangle.addPoint(gradeModelX + 3, gradeModelY + 20);triangle.addPoint(gradeModelX+ 40, gradeModelY + 20);g.setColor(Color.RED);g.fillPolygon(triangle);g.setColor(Color.WHITE);g.setFont(new Font("微软雅黑",Font.BOLD,20));g.drawString("关数:" + this.grade ,gradeModelX ,gradeModelY + 70);// 利用双缓冲机制来重绘画面,防止画面闪烁(先把所有的元素绘制到缓冲图片上,再将该图片一次性绘制到画面上)Graphics ig = bufferedImage.getGraphics();// 得到缓冲图片的画笔// 设置游戏面板区域ig.setColor(Color.BLACK);ig.fillRect(0,0,GAME_ACTION_WIDTH,GAME_ACTION_HEIGHT);// 绘制地图for (Block block : this.blockList){block.draw(ig);}// 绘制大本营if(camp.getState() == Camp.CAMP_STATE_RUNNING){camp.draw(ig);}// 绘制我方坦克hero.draw(ig);// 绘制我方子弹for (Bullet bullet : hero.getBulletPool()){if(bullet.getState() == Bullet.BULLET_STATE_RUNNING){bullet.draw(ig);}}// 绘制敌方坦克、子弹for (int i = 0; i < enemyPool.size(); i++){Enemy enemy = enemyPool.get(i);// 绘制坦克if(enemy.getState() == Tank.TANK_STATE_RUNNING){enemy.draw(ig);}// 绘制子弹for (Bullet bullet : enemy.getBulletPool()){if(bullet.getState() == Bullet.BULLET_STATE_RUNNING){bullet.draw(ig);}}}// 绘制爆炸for (Bomb bomb : bombList){if(bomb.getState() == Bomb.BOMB_STATE_RUNNING){bomb.draw(ig);}}// 将缓冲图片一次性显示到画面上g.drawImage(bufferedImage,0,0,null);}}public Hero getHero(){return hero;}public Vector getEnemyPool(){return enemyPool;}public List getBlockList(){return blockList;}public Camp getCamp(){return camp;}public void setGrade(int grade){this.grade = grade;}}
  1. GameLogic.java游戏逻辑

package lag.game.tankwar;/** * 游戏逻辑(牛逼类) */public class GameLogic{/** 唯一实例 */private static GameLogic instance;/** 游戏面板 */private GamePanel gamePanel;/** * 构造函数 * @param gamePanel 游戏面板 */public GameLogic(GamePanel gamePanel){this.gamePanel = gamePanel;}/** * 得到唯一实例 */public static GameLogic getInstance(){return instance;}/** * 设置唯一实例 * @param gameLogic 游戏逻辑 */public static void setInstance(GameLogic gameLogic){instance = gameLogic;}/** * 判断矩形1是否碰撞到矩形2 * @param x1 矩形1左上角X坐标 * @param y1 矩形1左上角Y坐标 * @param w1 矩形1宽度 * @param h1 矩形1高度 * @param x2 矩形2左上角X坐标 * @param y2 矩形2左上角Y坐标 * @param w2 矩形2宽度 * @param h2 矩形2高度 */public boolean rectCollideRect(int x1,int y1,int w1,int h1,int x2,int y2,int w2,int h2){// 判断原理(1、矩形1的四个顶点是否落在矩形2里;2、矩形2的四个顶点是否落在矩形1里。若高度或宽阔相等时特殊判断)// 这个碰撞判断比较严,必须进入才算碰撞,挨着不算// 判断矩形1的四个顶点是否落在矩形2里if((x1 > x2 && x1  y2 && y1  x2 && x1 + w1  y2 && y1  x2 && x1  y2 && y1 + h1  x2 && x1 + w1  y2 && y1 + h1  x1 && x2  y1 && y2  x1 && x2 + w2  y1 && y2  x1 && x2  y1 && y2 + h2  x1 && x2 + w2  y1 && y2 + h2  y2 && y1  y2 && y1 + h1  x2 && x1  x2 && x1 + w1 < x2 + w2))){return true;}}// 2个矩形完全重合if(x1 == x2 && y1 == y2 && w1 == w2 && h1 == h2){return true;}return false;}/** * 判断坦克移动时的下一个位置是否碰撞到任何对象 */public boolean tankMoveCollide(Tank tank){int newX = tank.getX();int newY = tank.getY();// 得到移动后的新坐标switch (tank.getDirection()){case Tank.TANK_DIRECTION_UP:newY = tank.getY() - tank.getSpeed();break;case Tank.TANK_DIRECTION_RIGHT:newX = tank.getX() + tank.getSpeed();break;case Tank.TANK_DIRECTION_DOWN:newY = tank.getY() + tank.getSpeed();break;case Tank.TANK_DIRECTION_LEFT:newX = tank.getX() - tank.getSpeed();break;}// 判断是否碰撞到边界switch (tank.getDirection()){case Tank.TANK_DIRECTION_UP:if(newY  GamePanel.GAME_ACTION_WIDTH){return true;}break;case Tank.TANK_DIRECTION_DOWN:if(newY + Tank.TANK_HEIGHT > GamePanel.GAME_ACTION_HEIGHT){return true;}break;case Tank.TANK_DIRECTION_LEFT:if(newX < 0){return true;}break;}// 判断是否碰撞到敌方坦克for (int i = 0; i < gamePanel.getEnemyPool().size(); i++){Enemy enemy = gamePanel.getEnemyPool().get(i);if(enemy.getState() == Tank.TANK_STATE_RUNNING && tank != enemy){if(rectCollideRect(newX,newY,Tank.TANK_WIDTH,Tank.TANK_HEIGHT,enemy.getX(),enemy.getY(),Tank.TANK_WIDTH,Tank.TANK_HEIGHT)){return true;}}}// 判断是否碰撞到我方坦克if(tank.getTankKind() == Tank.TANK_KIND_ENEMY){if(rectCollideRect(newX,newY,Tank.TANK_WIDTH,Tank.TANK_HEIGHT,gamePanel.getHero().getX(),gamePanel.getHero().getY(),Tank.TANK_WIDTH,Tank.TANK_HEIGHT)){return true;}}// 判断是否碰撞到块for (Block block : gamePanel.getBlockList()){if(rectCollideRect(newX,newY,Tank.TANK_WIDTH,Tank.TANK_HEIGHT,block.getX(),block.getY(),Block.BLOCK_WIDTH,Block.BLOCK_HEIGHT)){return true;}}// 判断是否碰撞大本营if(gamePanel.getCamp().getState() == Camp.CAMP_STATE_RUNNING){if(rectCollideRect(newX,newY,Tank.TANK_WIDTH,Tank.TANK_HEIGHT,gamePanel.getCamp().getX(),gamePanel.getCamp().getY(),gamePanel.getCamp().getWidth(),gamePanel.getCamp().getHeight())){return true;}}return false;}/** * 判断坦克是否碰撞到坦克
* @param tank1 坦克1 * @param tank2 坦克2 */public boolean tankCollideTank(Tank tank1,Tank tank2){return rectCollideRect(tank1.getX(),tank1.getY(),Tank.TANK_WIDTH,Tank.TANK_HEIGHT,tank2.getX(),tank2.getY(),Tank.TANK_WIDTH,Tank.TANK_HEIGHT);}/** * 判断子弹是否碰撞到坦克
*/public boolean bulletCollideTank(Bullet bullet,Tank tank){return rectCollideRect(bullet.getX(),bullet.getY(),bullet.getWidth(),bullet.getHeight(),tank.getX(),tank.getY(),Tank.TANK_WIDTH,Tank.TANK_HEIGHT);}/** * 判断子弹是否碰撞到块 */public boolean bulletCollideBlock(Bullet bullet,Block block){return rectCollideRect(bullet.getX(),bullet.getY(),bullet.getWidth(),bullet.getHeight(),block.getX(),block.getY(),Block.BLOCK_WIDTH,Block.BLOCK_HEIGHT);}/** * 判断子弹是否碰撞到大本营 */public boolean bulletCollideCamp(Bullet bullet,Camp camp){return rectCollideRect(bullet.getX(),bullet.getY(),bullet.getWidth(),bullet.getHeight(),camp.getX(),camp.getY(),camp.getWidth(),camp.getHeight());}/** * 得到某范围内的随机整数 * @param min 最小值 * @param max 最大值 */public static int getRandomInt(int min,int max){return (int)(min + Math.random() * (max - min + 1));}}
  1. GameMap.java游戏地图

package lag.game.tankwar;/** * 游戏地图 */public class GameMap{/** 各关地图[26行26列](0-空地,1-砖块,2-铁块) */private static byte[][][] gradeMap ={// 第1关地图{{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},{0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,1,1,2,2,1,1,0,0,1,1,0,0,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,1,1,2,2,1,1,0,0,1,1,0,0,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0},{0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0},{1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,1,1},{2,2,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,2,2},{0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0},{0,0,1,1,0,0,1,1,0,0,1,1,1,1,1,1,0,0,1,1,0,0,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,0,1,1,1,1,0,0,0,1,1,0,0,1,1,0,0},{0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0}},// 第2关地图{{0,0,0,0,0,0,2,2,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,2,2,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0},{0,0,1,1,0,0,2,2,0,0,0,0,0,0,1,1,0,0,1,1,0,0,1,1,0,0},{0,0,1,1,0,0,2,2,0,0,0,0,0,0,1,1,0,0,1,1,0,0,1,1,0,0},{0,0,1,1,0,0,0,0,0,0,0,0,1,1,1,1,0,0,1,1,2,2,1,1,0,0},{0,0,1,1,0,0,0,0,0,0,0,0,1,1,1,1,0,0,1,1,2,2,1,1,0,0},{0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0},{0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0},{0,0,0,0,0,0,1,1,0,0,0,0,2,2,0,0,0,0,1,1,0,0,1,1,2,2},{0,0,0,0,0,0,1,1,0,0,0,0,2,2,0,0,0,0,1,1,0,0,1,1,2,2},{0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,2,2,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,2,2,0,0,0,0,0,0,0,0},{0,0,1,1,1,1,1,1,0,0,0,0,0,0,2,2,0,0,0,0,0,0,1,1,0,0},{0,0,1,1,1,1,1,1,0,0,0,0,0,0,2,2,0,0,0,0,0,0,1,1,0,0},{0,0,0,0,0,0,2,2,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0},{0,0,0,0,0,0,2,2,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0},{2,2,1,1,0,0,2,2,0,0,1,1,0,0,1,1,0,0,0,0,0,0,1,1,0,0},{2,2,1,1,0,0,2,2,0,0,1,1,0,0,1,1,0,0,0,0,0,0,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,1,1,1,1,1,1,0,0,1,1,2,2,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,1,1,1,1,1,1,0,0,1,1,2,2,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0},{0,0,1,1,0,0,1,1,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0},{0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0},{0,0,1,1,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,0,0,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,0,1,0,0,1,0,0,0,1,1,1,1,1,1,0,0},{0,0,1,1,0,0,1,1,0,0,0,1,0,0,1,0,0,0,1,1,1,1,1,1,0,0}}// 其他关地图...};/** 总关卡数 */private static int gradeCount = gradeMap.length;/** 各关敌方坦克数量 */private static int[] enemyTankNum = {10,20};/** * 返回某关地图 * @param grade 关数 */public static byte[][] getGameMap(int grade){// 由于数组是个对象,而原始地图是不允许被修改的,所以不能直接赋值(引用地址),得复制一个新的地图让游戏随便修改。byte[][] tempMap = null;if(grade > 0 && grade <= gradeCount){tempMap = gradeMap[grade - 1];}else{tempMap = gradeMap[0];}//开始复制int row = tempMap.length;int column = tempMap[0].length;byte[][] returnMap = new byte[row][column];for (int i = 0; i < row; i++){for (int j = 0; j < column; j++){returnMap[i][j] = tempMap[i][j];}}return returnMap;}/** * 功能:返回总关卡数
*/public static int getGradeCount(){return gradeCount;}/** * 返回某关敌方坦克数量 * @param grade 关数 */public static int getEnemyTankNum(int grade){if(grade gradeCount){grade = gradeCount;}return enemyTankNum[grade - 1];}}
  1. GameMusic.java

package lag.game.tankwar;import java.io.File;import java.io.InputStream;import javax.sound.sampled.Clip;import javax.sound.sampled.DataLine;import javax.sound.sampled.AudioFormat;import javax.sound.sampled.AudioSystem;import javax.sound.sampled.AudioInputStream;/** * 游戏音乐
* 支持音乐格式(AITF、AU、WAV),就是加载有点慢啊。 */public class GameMusic{/** 音频输入流 */private AudioInputStream stream;/** 音频格式 */private AudioFormat format;/** 音频剪辑 */private Clip clip;/** * 功能:构造函数
*/public GameMusic(String fileName){try{File file = new File(fileName);//将音乐文件转为音频输入流this.stream = AudioSystem.getAudioInputStream(file);//得到音频格式this.format = stream.getFormat();}catch(Exception e){e.printStackTrace();}}/** * 构造函数 * @param fileName URL文件名 */public GameMusic(InputStream fileName){try{//将音乐文件转为音频输入流this.stream = AudioSystem.getAudioInputStream(fileName);//得到音频格式this.format = stream.getFormat();}catch(Exception e){e.printStackTrace();}}/** * 功能:播放音乐
* 参数:boolean _loop -> 是否循环(True-无限循环,False-不循环)
*/public void play(boolean _loop){try{//设置音频行信息DataLine.Info info = new DataLine.Info(Clip.class,this.format);//建立音频行信息this.clip = (Clip)AudioSystem.getLine(info);this.clip.open(this.stream);if(_loop)//无限循环{this.clip.loop(Clip.LOOP_CONTINUOUSLY);}else{this.clip.loop(0);}this.clip.start();}catch(Exception e){e.printStackTrace();}}/** * 功能:停止音乐
*/public void stop(){try{if(this.clip.isRunning()){this.clip.stop();this.clip.close();}}catch(Exception e){e.printStackTrace();}}}
  1. Tank.java坦克父类

package lag.game.tankwar;import java.awt.*;import java.util.Vector;/** * 坦克父类 */public class Tank{/** 坦克类别常量 */public final static int TANK_KIND_HERO = 0; // 我方坦克public final static int TANK_KIND_ENEMY = 1;// 敌方坦克/** 坦克颜色常量 */public final static Color TANK_COLOR_HERO = Color.YELLOW; // 我方坦克颜色public final static Color TANK_COLOR_ENEMY = Color.CYAN;// 敌方坦克颜色/** 坦克方向常量 */public final static int TANK_DIRECTION_UP = 0;// 向上public final static int TANK_DIRECTION_RIGHT = 1; // 向右public final static int TANK_DIRECTION_DOWN = 2;// 向下public final static int TANK_DIRECTION_LEFT = 3;// 向左/** 坦克状态常量 */public final static int TANK_STATE_FREE = 0;// 空闲中public final static int TANK_STATE_RUNNING = 1; // 运行中/** 坦克最大血量值 */public final static int TANK_MAX_BLOOD = 100;/** 坦克尺寸常量 */public final static int TANK_WIDTH = 40;public final static int TANK_HEIGHT = 40;/** 坦克左上角X坐标 */private int x;/** 坦克左上角Y坐标 */private int y;/** 坦克速度 */private int speed = 8;/** 坦克血量 */private int hp = TANK_MAX_BLOOD;/** 坦克方向 */private int direction = TANK_DIRECTION_UP;/** 坦克类别 */private int tankKind = TANK_KIND_HERO;/** 坦克状态 */private int state = TANK_STATE_FREE;/** 子弹池(考虑线程安全用Vector,没用ArrayList) */private Vector bulletPool = new Vector();/** 上次射击时间(用来设置2次射击最小时间间隔,禁止连续射击) */private long lastShootTime = System.currentTimeMillis();/** * 构造函数 * @param tankKind 坦克类别 * @param x 坦克左上角X坐标 * @param y 坦克左上角Y坐标 * @param direction 坦克方向 */public Tank(int tankKind, int x, int y, int direction){this.tankKind = tankKind;this.x = x;this.y = y;this.direction = direction;}/** * 画坦克 */public void draw(Graphics g){// 绘制血条g.setColor(Color.YELLOW);g.fill3DRect(this.x,this.y,TANK_WIDTH,3,false); // 底色g.setColor(Color.RED);g.fill3DRect(this.x,this.y,(hp * TANK_WIDTH)/TANK_MAX_BLOOD,3,false); // 血量(计算宽度时由于都是int类型,因此除法是放到最后计算的,防止出现结果为0的问题【int取整了】)g.setColor(Color.WHITE);g.draw3DRect(this.x,this.y,TANK_WIDTH,3,false); // 边框int tankToBloodHeight = 5;// 坦克到血条的高度// 设置坦克颜色if(this.tankKind == TANK_KIND_HERO)// 我方坦克颜色{g.setColor(TANK_COLOR_HERO);}else// 敌方坦克颜色{g.setColor(TANK_COLOR_ENEMY);}// 绘制四周虚线for (int i = 0; i <= 10; i++){g.drawOval(x - 1,y + 4 * i - 1, 1,1);g.drawOval(x + TANK_WIDTH - 1,y + 4 * i - 1, 1,1);if(i = 500)// 2发/秒{this.lastShootTime = curShootTime;}else{return;}// 在子弹池中寻找空闲的子弹Bullet bullet = null;for (int i = 0; i < bulletPool.size(); i++){Bullet tmpBullet = bulletPool.get(i);if(tmpBullet.getState() == Bullet.BULLET_STATE_FREE){bullet = tmpBullet;//System.out.println("找到空闲的子弹了..................");break;}}// 没有就增加一个if(bullet == null){bullet = new Bullet(this);//System.out.println("新建子弹了.................");bulletPool.add(bullet);}// 设置子弹位置switch (this.direction){case TANK_DIRECTION_UP:bullet.setPosition(this.x + TANK_WIDTH/2 - bullet.getWidth()/2,this.y - bullet.getHeight(),this.direction);break;case TANK_DIRECTION_RIGHT:bullet.setPosition(this.x + TANK_WIDTH,this.y + TANK_HEIGHT/2 - bullet.getHeight()/2,this.direction);break;case TANK_DIRECTION_DOWN:bullet.setPosition(this.x + TANK_WIDTH/2 - bullet.getWidth()/2,this.y + TANK_HEIGHT,this.direction);break;case TANK_DIRECTION_LEFT:bullet.setPosition(this.x - bullet.getWidth(),this.y + TANK_HEIGHT/2 - bullet.getHeight()/2,this.direction);break;}// 让子弹飞一会bullet.fly();}/** 坦克受到伤害 */public void hurt(Bullet bullet){this.hp -= bullet.getAtk();if(this.hp < 0){this.hp = 0;}}/** * 坦克是否死亡 */public boolean isDead(){return this.hp <= 0;}/** * 坦克开始工作了 */public void work(){this.state = TANK_STATE_RUNNING;}/** 坦克重置 */public void reset(){this.setX(-100);this.setY(-100);this.setHp(Tank.TANK_MAX_BLOOD);this.setState(TANK_STATE_FREE);}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 getHp(){return hp;}public void setHp(int hp){this.hp = hp;}public int getDirection(){return direction;}public void setDirection(int direction){this.direction = direction;}public int getState(){return state;}public void setState(int state){this.state = state;}public int getTankKind(){return tankKind;}public Vector getBulletPool(){return bulletPool;}}
  1. Hero.java我方的坦克

package lag.game.tankwar;/** * 我方的坦克 */public class Hero extends Tank{/** * 构造函数 * @param x 坦克左上角X坐标 * @param y 坦克左上角Y坐标 * @param direction 坦克方向 */public Hero(int x, int y, int direction){super(TANK_KIND_HERO, x, y, direction);}}
  1. Enemy.java敌方的坦克

package lag.game.tankwar;/** * 敌方的坦克 */public class Enemy extends Tank{/** * 构造函数 * @param x 坦克左上角X坐标 * @param y 坦克左上角Y坐标 * @param direction 坦克方向 */public Enemy(int x, int y, int direction){super(TANK_KIND_ENEMY, x, y, direction);}/** * 起来干活了 */@Overridepublic void work(){super.work();// 启动线程让坦克跑(看看敌方坦克的AI)new Thread(()->{//System.out.println("坦克线程启动...");while (this.getState() == TANK_STATE_RUNNING){try{Thread.sleep(200);}catch (InterruptedException e){e.printStackTrace();}// 随机确认是原地不动还是移动(0-不动,其它-移动)if(GameLogic.getRandomInt(0,8) != 0) // 移动(概率设大点){// 随机确认是改变方向还是向前移动(0-改变方向,其它-向前移动)if(GameLogic.getRandomInt(0,12) == 0) // 改变方向(概率设小点){int newDirection = GameLogic.getRandomInt(TANK_DIRECTION_UP, TANK_DIRECTION_LEFT + 3);if(newDirection > TANK_DIRECTION_LEFT){newDirection = TANK_DIRECTION_DOWN;}// 向下概率设大点this.setDirection(newDirection);}else// 向前移动{// 如果到边界了就别顶牛了,赶紧换方向boolean toBorder = false; // 默认未到边界switch (this.getDirection()){case TANK_DIRECTION_UP:if((this.getY() - this.getSpeed()  GamePanel.GAME_ACTION_WIDTH)){toBorder = true;}break;case TANK_DIRECTION_DOWN:if((this.getY() + TANK_HEIGHT + this.getSpeed() > GamePanel.GAME_ACTION_HEIGHT)){toBorder = true;}break;case TANK_DIRECTION_LEFT:if((this.getX() - this.getSpeed() < 0)){toBorder = true;}break;}if(toBorder)// 到边界了赶紧换方向{int newDirection = GameLogic.getRandomInt(TANK_DIRECTION_UP, TANK_DIRECTION_LEFT);this.setDirection(newDirection);}else// 继续前进{this.move(this.getDirection());}}}// 随机确认是发射子弹还是省子弹(1-发射,其它-不发射)if(GameLogic.getRandomInt(0,8) == 1){this.shoot();}}//System.out.println("坦克线程退出历史舞台了...");}).start();}}
  1. Bullet.java子弹类

package lag.game.tankwar;import java.awt.*;/** * 子弹类
*/public class Bullet{/** 子弹状态常量 */public final static int BULLET_STATE_FREE = 0;// 空闲中public final static int BULLET_STATE_RUNNING = 1; // 运行中/** 子弹攻击力常量 */public final static int BULLET_MAX_ATTACK = 100;// 最大攻击力public final static int BULLET_MIN_ATTACK = 20; // 最小攻击力/** 子弹左上角X坐标 */private int x;/** 子弹左上角Y坐标 */private int y;/** 子弹宽度 */private int width = 10;/** 子弹高度 */private int height = 10;/** 子弹方向 */private int direction;/** 子弹速度 */private int speed = 16;/** 子弹状态 */private int state = BULLET_STATE_FREE;/** 所属坦克 */private Tank tank;/** 子弹攻击力 */private int atk;/** * 构造函数 * @param tank 所属坦克 */public Bullet(Tank tank){this.tank = tank;this.atk = GameLogic.getRandomInt(BULLET_MIN_ATTACK,BULLET_MAX_ATTACK);}/** * 设置子弹位置 */public void setPosition(int x,int y,int direction){this.x = x;this.y = y;this.direction = direction;}/** * 功能:子弹重置
*/public void reset(){this.x = -100;this.y = -100;this.atk = GameLogic.getRandomInt(BULLET_MIN_ATTACK,BULLET_MAX_ATTACK); // 重新随机生成攻击力this.state = BULLET_STATE_FREE;}/** * 让子弹飞一会 */public void fly(){// 设置子弹状态为运行中this.state = BULLET_STATE_RUNNING;// 启动线程让子弹飞new Thread(()->{//System.out.println(tank.getTankKind() + "子弹线程启动...");while (this.state == BULLET_STATE_RUNNING)//tank.getState() == Tank.TANK_STATE_RUNNING &&{try{Thread.sleep(50);}catch (InterruptedException e){e.printStackTrace();}switch (direction){case Tank.TANK_DIRECTION_UP:if(this.y - this.speed >= 0){this.y -= this.speed;}else{this.reset();}break;case Tank.TANK_DIRECTION_RIGHT:if(this.x + this.width + this.speed <= GamePanel.GAME_ACTION_WIDTH){this.x += this.speed;}else{this.reset();}break;case Tank.TANK_DIRECTION_DOWN:if(this.y + this.height + this.speed = this.speed){this.x -= this.speed;}else{this.reset();}break;}}//System.out.println("子弹线程退出历史舞台了......");}).start();}/** * 画子弹 */public void draw(Graphics g){// 开始画子弹if(tank.getTankKind() == Tank.TANK_KIND_HERO)// 我方子弹{g.setColor(Tank.TANK_COLOR_HERO);}else// 敌方子弹{g.setColor(Tank.TANK_COLOR_ENEMY);}g.fillOval(this.x,this.y,this.width,this.height);// 根据攻击力画加强子弹g.setColor(Color.RED);if(this.atk == 100){g.fillOval(this.x,this.y,this.width,this.height);}else if(this.atk >= 90){g.fillOval(this.x + 1,this.y + 1,this.width - 2,this.height - 2);}else if(this.atk >= 70){g.fillOval(this.x + 2,this.y + 2,this.width - 4,this.height - 4);}else if(this.atk >= 50){g.fillOval(this.x + 3,this.y + 3,this.width - 6,this.height - 6);}}public int getX(){return x;}public int getY(){return y;}public int getWidth(){return width;}public int getHeight(){return height;}public int getDirection(){return direction;}public int getSpeed(){return speed;}public int getState(){return state;}public int getAtk(){return atk;}}
  1. Bomb.java爆炸效果

package lag.game.tankwar;import java.awt.*;/** * 爆炸效果 */public class Bomb{/** 爆炸状态常量 */public final static int BOMB_STATE_FREE = 0;// 空闲中public final static int BOMB_STATE_RUNNING = 1; // 运行中/** 爆炸左上角X坐标 */private int x;/** 爆炸左上角Y坐标 */private int y;/** 爆炸宽度 */private int width = 20;/** 爆炸高度 */private int height = 20;/** 状态 */private int state = BOMB_STATE_FREE;/** 爆炸进度,根据它来决定显示哪种爆炸形状(-1-不显示任何爆炸形状,0-准备显示,1-显示第一种爆炸形状,2-显示第二种爆炸形状,3-显示第三种爆炸形状) */public int bombProgress = -1;/** * 构造函数 * @param x 爆炸左上角X坐标 * @param y 爆炸左上角Y坐标 */public Bomb(int x, int y){this.x = x;this.y = y;}/** * 起来干活,准备爆炸。 */public void work(){// 设置爆炸状态为运行中this.state = BOMB_STATE_RUNNING;// 爆炸进度,准备显示bombProgress = 0;}/** * 爆炸重置 */public void reset(){this.setX(-100);this.setY(-100);this.state = BOMB_STATE_FREE;this.bombProgress = -1;}/** * 画爆炸
* 每帧显示一种形状,共三种,即3帧结束爆炸
*/public void draw(Graphics g){// 进度走起this.bombProgress++;// 根据进度显示某种爆炸形状switch (this.bombProgress){case 1: // 第一种形状g.setColor(Color.WHITE);g.fillRoundRect(this.x + 5,this.y + 5,20,20,10,10);g.fillOval(this.x + 10,this.y,10,30);g.setColor(Color.YELLOW);g.drawOval(this.x + 10,this.y,10,30);g.setColor(Color.WHITE);g.fillOval(this.x,this.y + 10,30,10);g.setColor(Color.YELLOW);g.drawOval(this.x,this.y + 10,30,10);break;case 2: // 第二种形状g.setColor(Color.WHITE);g.fillOval(this.x + 12,this.y + 5,6,20);g.setColor(Color.YELLOW);g.drawOval(this.x + 12,this.y + 5,6,20);g.setColor(Color.WHITE);g.fillOval(this.x + 5,this.y + 12,20,6);g.setColor(Color.YELLOW);g.drawOval(this.x + 5,this.y + 12,20,6);break;case 3: // 第三种形状g.setColor(Color.WHITE);g.fillOval(this.x + 10,this.y + 10,10,10);g.setColor(Color.YELLOW);g.drawOval(this.x + 10,this.y + 10,10,10);break;default:this.reset();break;}}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 getWidth(){return width;}public void setWidth(int width){this.width = width;}public int getHeight(){return height;}public void setHeight(int height){this.height = height;}public int getState(){return state;}}
  1. Block.java砖块与铁块的父类

package lag.game.tankwar;import java.awt.*;/** * 砖块与铁块的父类 */public class Block{/** 块类别常量 */public final static int BLOCK_KIND_BRICK = 1; // 砖块public final static int BLOCK_KIND_IRON = 2;// 铁块/** 块尺寸常量(默认4个块=1坦克大小) */public final static int BLOCK_WIDTH = Tank.TANK_WIDTH / 2;public final static int BLOCK_HEIGHT = Tank.TANK_HEIGHT / 2;/** 块左上角X坐标 */private int x;/** 块左上角Y坐标 */private int y;/** 块类别 */private int blockKind = BLOCK_KIND_BRICK;/** * 构造函数 * @param blockKind 块类别 * @param x 块左上角X坐标 * @param y 块左上角Y坐标 */public Block(int blockKind,int x, int y){this.blockKind = blockKind;this.x = x;this.y = y;}/** * 画块 */public void draw(Graphics g){if(this.blockKind == BLOCK_KIND_BRICK) // 画砖块{g.setColor(new Color(210, 105, 30));g.fillRect(this.x,this.y,BLOCK_WIDTH,BLOCK_HEIGHT);g.setColor(new Color(244, 164, 96));g.drawLine(this.x,this.y,this.x + BLOCK_WIDTH - 1,this.y);g.drawLine(this.x,this.y + BLOCK_HEIGHT / 2,this.x + BLOCK_WIDTH - 1,this.y + BLOCK_HEIGHT / 2);g.drawLine(this.x,this.y,this.x,this.y + BLOCK_HEIGHT / 2);g.drawLine(this.x + BLOCK_WIDTH / 2,this.y + BLOCK_HEIGHT / 2, this.x + BLOCK_WIDTH / 2, this.y + BLOCK_HEIGHT - 1);}else if(this.blockKind == BLOCK_KIND_IRON)// 画铁块{g.setColor(new Color(190, 190, 190));g.fillRect(this.x,this.y,BLOCK_WIDTH,BLOCK_HEIGHT);g.setColor(Color.WHITE);g.fillRect(this.x + 3,this.y + 3,BLOCK_WIDTH - 6,BLOCK_HEIGHT - 6);g.draw3DRect(this.x + 3,this.y + 3,BLOCK_WIDTH - 6,BLOCK_HEIGHT - 6,true);g.drawLine(this.x + 1,this.y + 1,this.x + 3,this.y + 3);g.drawLine(this.x + BLOCK_WIDTH - 1,this.y + 1,this.x + BLOCK_WIDTH - 3,this.y + 3);g.drawLine(this.x + 1,this.y + BLOCK_HEIGHT - 1,this.x + 3,this.y + BLOCK_HEIGHT - 3);g.drawLine(this.x + BLOCK_WIDTH - 1,this.y + BLOCK_HEIGHT - 1,this.x + BLOCK_WIDTH - 3,this.y + BLOCK_HEIGHT - 3);}}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 getBlockKind(){return blockKind;}}
  1. Brick.java砖块

package lag.game.tankwar;/** * 砖块 */public class Brick extends Block{/** * 构造函数 * @param x 砖块左上角X坐标 * @param y 砖块左上角Y坐标 */public Brick(int x, int y){super(BLOCK_KIND_BRICK, x, y);}}
  1. Iron.java铁块

package lag.game.tankwar;/** * 铁块 */public class Iron extends Block{/** * 构造函数 * @param x 铁块左上角X坐标 * @param y 铁块左上角Y坐标 */public Iron(int x, int y){super(BLOCK_KIND_IRON, x, y);}}
  1. Camp.java营地

package lag.game.tankwar;import java.awt.*;/** * 营地 */public class Camp{/** 营地状态常量 */public final static int CAMP_STATE_FREE = 0;// 空闲中public final static int CAMP_STATE_RUNNING = 1; // 运行中/** 营地左上角X坐标 */private int x;/** 营地左上角Y坐标 */private int y;/** 营地宽度 */private int width = Tank.TANK_WIDTH;/** 营地高度 */private int height = Tank.TANK_HEIGHT;/** 营地状态 */private int state = CAMP_STATE_FREE;/** 元素宽度 */private int elementWidth = 2;/** 元素高度 */private int elementHeight = 2;/** 营地地图(20 * 20) */private static byte[][] campMap ={{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},{0,0,1,0,0,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0},{1,1,1,1,0,0,0,1,1,1,1,0,0,0,0,0,1,1,1,1},{0,2,1,1,1,0,0,0,1,0,0,1,1,0,0,1,1,1,2,0},{1,1,2,0,1,0,0,0,1,1,1,1,0,0,0,1,0,2,1,1},{0,0,1,2,1,1,0,0,1,1,1,1,0,0,1,1,2,1,0,0},{0,1,1,1,2,1,0,0,1,1,1,1,0,0,1,2,1,1,1,0},{0,0,1,1,1,2,1,1,1,1,1,1,1,1,2,1,1,1,0,0},{0,0,0,1,1,1,2,1,1,1,1,1,1,2,1,1,1,0,0,0},{0,0,1,1,1,1,1,1,2,1,1,1,2,1,1,1,1,1,0,0},{0,0,0,1,1,1,1,1,1,2,1,2,1,1,1,1,1,0,0,0},{0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0},{0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0},{0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0},{0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0},{0,0,0,0,0,0,1,0,1,1,1,0,1,0,0,0,0,0,0,0},{0,0,0,0,0,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0},{0,0,0,0,0,1,1,1,1,0,1,1,1,1,0,0,0,0,0,0},{0,0,0,0,0,1,1,0,1,0,1,0,1,1,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}};/** * 构造函数 */public Camp(int x, int y){this.x = x;this.y = y;}/** * 画营地 */public void draw(Graphics g){int row = campMap.length;int column = campMap[0].length;for (int i = 0; i < row; i++){for (int j = 0; j < column; j++){int mapValue = campMap[i][j];if(mapValue == 1){g.setColor(Color.WHITE);drawElement(g,this.x + elementWidth * j,this.y + elementHeight * i);}else if(mapValue == 2){g.setColor(Color.LIGHT_GRAY);drawElement(g,this.x + elementWidth * j,this.y + elementHeight * i);}}}}/** * 画元素 * @param x 元素左上角X坐标 * @param y 元素左上角Y坐标 */private void drawElement(Graphics g,int x,int y){g.fillRect(x,y,elementWidth,elementHeight);g.setColor(Color.WHITE);g.draw3DRect(x,y,elementWidth,elementHeight,true);}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 getWidth(){return width;}public int getHeight(){return height;}public int getState(){return state;}public void setState(int state){this.state = state;}}

下载:

链接:https://pan.baidu.com/s/1aqNjqATCjteiAdv90YDByw?pwd=mylx

提取码:mylx

感言:

本游戏并没有使用图片,都是用Java画的,有兴趣的朋友可以自己改用图片,图片素材我已经打包了。使用图片的朋友要注意一个问题,就是图片加载是需要时间的,第一次可能还未加载成功就直接刷新下一次界面了,这也是韩顺平老师那个第一次射击坦克不出现爆炸效果的原因。Toolkit.getDefaultToolkit().createImage这个方法有BUG,改用new ImageIcon(图片路径)).getImage()就可以了。