引言:
坦克大战也是小时一个比较经典的游戏了,我在网上也是参考了韩顺平老师写的坦克大战,并做了一下完善,编写出来作为儿时的回忆吧!
思路:
创建主窗口,加载菜单及游戏面板。
在游戏面板中初始化各种参数,并建立各种功能组件。
利用线程固定刷新游戏界面。
处理各种碰撞问题
游戏结束。
代码:
本游戏用的是JDK1.8,编码UTF-8;
我这里用的IDE是Intellij Idea,新建了一个game的空项目,tankwar作为其中的一个模块(当然这个不重要,个人喜好罢了)。类比较多,TankWar.java是游戏入口类。GameFrame.java是主窗口类。GamePanel.java是游戏面板类。GameLogic.java是游戏逻辑类。先一口气把所有的代码贴上来再说。
TankWar.java 游戏入口类
package lag.game.tankwar; /** * 功能:坦克大战
* 作者:我是小木鱼(Lag) */ public class TankWar { public static void main(String[] args) { new GameFrame(); } }
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); } } /** * 事件监听 */ @Override public 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; } } }
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 VectorenemyPool = 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 < hero.getBulletPool().size(); i++) { Bullet bullet = hero.getBulletPool().get(i); bullet.reset(); } hero.getBulletPool().clear(); } // 清空块池中的所有块 blockList.clear(); } /** * 创建敌方坦克 */ private void createEnemyTank() { this.createEnemyTankThreadRunning = true; // 线程运行中 new Thread(()->{ //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 < enemyPool.size(); j++) { Enemy enemy = enemyPool.get(j); if(enemy.getState() == Tank.TANK_STATE_RUNNING) // 敌方坦克爬行中 { // 开始判断 if(GameLogic.getInstance().bulletCollideTank(bullet,enemy)) // 这还真碰上了 { // 记录坦克坐标,因为坦克销毁时会改变其坐标 int bombX = enemy.getX(); int bombY = enemy.getY(); // 子弹销毁 bullet.reset(); // 坦克受到伤害 enemy.hurt(bullet); // 判断坦克是否死亡 if(enemy.isDead()) { // 坦克销毁 enemy.reset(); enemyDeadNum++; // 坦克爆炸 addBomb(bombX,bombY,Tank.TANK_WIDTH,Tank.TANK_HEIGHT); // 判断是否胜利 gameWin(); } } } } // 判断是否与块碰撞 for (int m = blockList.size() - 1; m >= 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 < enemy.getBulletPool().size(); j++) { Bullet bullet = enemy.getBulletPool().get(j); if(bullet.getState() == Bullet.BULLET_STATE_RUNNING) { // 判断子弹是否与坦克碰撞 if(GameLogic.getInstance().bulletCollideTank(bullet,hero)) // 完蛋了啊 { // 记录坦克坐标,因为坦克销毁时会改变其坐标 int bombX = hero.getX(); int bombY = hero.getY(); // 子弹销毁 bullet.reset(); // 坦克受到伤害 hero.hurt(bullet); // 判断坦克是否死亡 if(hero.isDead()) { // 坦克销毁 hero.reset(); // 坦克爆炸 addBomb(bombX,bombY,Tank.TANK_WIDTH,Tank.TANK_HEIGHT); // 游戏结束 gameOver(); } } // 判断子弹是否与块碰撞 for (int m = blockList.size() - 1; m >= 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(); } /** * 字符被输入 */ @Override public void keyTyped(KeyEvent e){} /** * 某键被按下 */ @Override public 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; } } /** * 某键被释放 */ @Override public 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(); } /** * 绘制游戏画面(利用双缓冲机制防止画面闪烁) */ @Override public 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()); // 显示Logo g.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 VectorgetEnemyPool(){return enemyPool;} public List getBlockList(){return blockList;} public Camp getCamp(){return camp;} public void setGrade(int grade){this.grade = grade;} }
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 < x2 + w2 && y1 > y2 && y1 < y2 + h2) || (x1 + w1 > x2 && x1 + w1 < x2 + w2 && y1 > y2 && y1 < y2 + h2) || (x1 > x2 && x1 < x2 + w2 && y1 + h1 > y2 && y1 + h1 < y2 + h2) || (x1 + w1 > x2 && x1 + w1 < x2 + w2 && y1 + h1 > y2 && y1 + h1 < y2 + h2)) {return true;} // 判断矩形2的四个顶点是否落在矩形1里 if((x2 > x1 && x2 < x1 + w1 && y2 > y1 && y2 < y1 + h1) || (x2 + w2 > x1 && x2 + w2 < x1 + w1 && y2 > y1 && y2 < y1 + h1) || (x2 > x1 && x2 < x1 + w1 && y2 + h2 > y1 && y2 + h2 < y1 + h1) || (x2 + w2 > x1 && x2 + w2 < x1 + w1 && y2 + h2 > y1 && y2 + h2 < y1 + h1)) {return true;} // 特殊情况处理 // 若2个矩形的宽度相等时 if(w1 == w2){if(x1 == x2 && x1 + w1 == x2 + w2 && ((y1 > y2 && y1 < y2 + h2) || (y1 + h1 > y2 && y1 + h1 < y2 + h2))){return true;}} // 若2个矩形的高度相等时 if(h1 == h2){if(y1 == y2 && y1 + h1 == y2 + h2 && ((x1 > x2 && x1 < x2 + w2) || (x1 + w1 > 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 < 0){return true;} break; case Tank.TANK_DIRECTION_RIGHT: if(newX + Tank.TANK_WIDTH > 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)); } }
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 < 1 || grade > gradeCount){grade = gradeCount;} return enemyTankNum[grade - 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(); } } }
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 VectorbulletPool = 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 < 10){g.drawOval(x + 4 * i - 1,y + TANK_HEIGHT - 1, 1,1);} } // 根据方向开始绘制坦克 switch (direction) { case TANK_DIRECTION_UP: g.fill3DRect(this.x + tankToBloodHeight,this.y + tankToBloodHeight,7,30,false); // 左边轮子 g.fill3DRect(this.x + 23 + tankToBloodHeight,this.y + tankToBloodHeight,7,30,false); // 右边轮子 g.fill3DRect(this.x + 7 + tankToBloodHeight,this.y + 5 + tankToBloodHeight,16,20,false); // 驾驶室 g.fillOval(this.x + 9 + tankToBloodHeight,this.y + 9 + tankToBloodHeight,12,12); // 炮台 g.fill3DRect(x + 14 + tankToBloodHeight,y + tankToBloodHeight,3,15,false); // 炮管 break; case TANK_DIRECTION_RIGHT: g.fill3DRect(this.x + tankToBloodHeight,this.y + tankToBloodHeight,30,7,false); // 上边轮子 g.fill3DRect(this.x + tankToBloodHeight,this.y + 23 + tankToBloodHeight,30,7,false); // 下边轮子 g.fill3DRect(this.x + 5 + tankToBloodHeight,this.y + 7 + tankToBloodHeight,20,16,false); // 驾驶室 g.fillOval(this.x + 9 + tankToBloodHeight,this.y + 9 + tankToBloodHeight,12,12); // 炮台 g.fill3DRect(x + 15 + tankToBloodHeight,y + 14 + tankToBloodHeight,15,3,false); // 炮管 break; case TANK_DIRECTION_DOWN: g.fill3DRect(this.x + tankToBloodHeight,this.y + tankToBloodHeight,7,30,false); // 左边轮子 g.fill3DRect(this.x + 23 + tankToBloodHeight,this.y + tankToBloodHeight,7,30,false); // 右边轮子 g.fill3DRect(this.x + 7 + tankToBloodHeight,this.y + 5 + tankToBloodHeight,16,20,false); // 驾驶室 g.fillOval(this.x + 9 + tankToBloodHeight,this.y + 9 + tankToBloodHeight,12,12); // 炮台 g.fill3DRect(x + 14 + tankToBloodHeight,y + 15 + tankToBloodHeight,3,15,false); // 炮管 break; case TANK_DIRECTION_LEFT: g.fill3DRect(this.x + tankToBloodHeight,this.y + tankToBloodHeight,30,7,false); // 上边轮子 g.fill3DRect(this.x + tankToBloodHeight,this.y + 23 + tankToBloodHeight,30,7,false); // 下边轮子 g.fill3DRect(this.x + 5 + tankToBloodHeight,this.y + 7 + tankToBloodHeight,20,16,false); // 驾驶室 g.fillOval(this.x + 9 + tankToBloodHeight,this.y + 9 + tankToBloodHeight,12,12); // 炮台 g.fill3DRect(x + tankToBloodHeight,y + 14 + tankToBloodHeight,15,3,false); // 炮管 break; } } /** * 坦克移动 * @param direction 移动方向 */ public void move(int direction) { if(this.direction == direction) // 方向相同,加速前进(要判断边界及是否碰撞到别的对象) { if(!GameLogic.getInstance().tankMoveCollide(this)) // 没有碰撞,可以移动 { switch (direction) { case TANK_DIRECTION_UP: this.y -= this.speed; break; case TANK_DIRECTION_RIGHT: this.x += this.speed; break; case TANK_DIRECTION_DOWN: this.y += this.speed; break; case TANK_DIRECTION_LEFT: this.x -= this.speed; break; } } } else // 方向不同,仅调整方向不前进 { this.direction = direction; } } /** * 坦克射击 */ public void shoot() { // 禁止连续射击 long curShootTime = System.currentTimeMillis(); if((curShootTime - this.lastShootTime) >= 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;} }
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); } }
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); } /** * 起来干活了 */ @Override public 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() < 0)) { toBorder = true; } break; case TANK_DIRECTION_RIGHT: if((this.getX() + TANK_WIDTH + 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(); } }
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 <= GamePanel.GAME_ACTION_HEIGHT) { this.y += this.speed; } else { this.reset(); } break; case Tank.TANK_DIRECTION_LEFT: if(this.x >= 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;} }
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;} }
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;} }
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); } }
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); } }
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()就可以了。
猜你喜欢
网友评论
- 搜索
- 最新文章
- 热门文章