<Java>18 项目(坦克大战)
本文最后更新于:2023年5月24日 下午
18 项目:坦克大战
还有很多细节、优化方法没实现,但困难的已经没有了。
先不打磨了,还是以学习 Java 为主。
二阶段毕业
关于 数量 的注释可能不对。那些注释是早些时候添加的。后来添加了新功能,这些注释没有及时更新
TankGame.java:程序的入口
MyPanel.java:窗口、图像的绘制。这是该项目的主干
Player.java、Enemy.java:己方、敌方的坦克类。Tank.java 是他们的父类
Vehicle.java:记录了所有坦克预设的枚举类。该包下还有另一个记录了所有子弹类型的枚举类 Bullet
Map.java:记录了地图数据的枚举类,包含不同关卡。但我没设计关卡
InputImage.java:所有导入的图片。是我准备的素材
Timing.java:一个计时器。当时能力有限,用这个作为某个计时功能的代替方案
-
TankGame.java(入口)
package com.melody.tank_game; import javax.swing.*; import java.io.*; import java.util.Scanner; /** * @author Melody * @version 1.0.ష * <p> * 小派蒙会开坦克吗? * <p> * ⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⠴⠒⠛⠲⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ * ⠀⠀⠀⠀ ⠀⢻⡄⣠⠶⣆⠀⣸⣀⣀⠀⠀ * ⠀⠀⠀ ⠀⠀⢀⡠⠬⠛⢓⣏⠉⣾⣉⣀⠉⢹⡀⠀⠀⠀⠀⠀⠀⠀ * ⠀⠀ ⠀⢀⡖⠋⣠⠴⠛⠙⢹⠞⢳⢀⣨⡵⠚⠀⠀⠀ * ⠀⠀⠀⣰⠋⡠⠎⠁⣀⠤⠒⠚⠛⠙⠒⠳⠤⣄⡀⠀ * ⠀⠀⠀⠘⠐⢼⠖⠋⠀⠀⢀⠀⠀⠀⠀⠀⠀⠘⣌⡒⠲⢹⠀⠀⠀⠀ * ⠀⠀ ⠀⡸⠁⠀⠀⠀⠀⡆⠀⠀⠐⠀⠢⣄⠀⡽⡙⡲⠑⠒⠒⡒⠁ * ⢀⡠⠴⠚⠀⠀⠀⠀⠀⣕⠝⣄⡀⢀⠀⠀⡇⠵⢍⠚⢾⡀⢠⠖⠁⠀ ___________________ * ⠈⠦⣄⣀⠀⡔⠀⠀⢁⡞⠀⠉⠲⣄⡀⢲⢼⠀⢀⠳⡄⠁⠀⢣⠀⠀ / 坦克?是新游戏喔。 \ * ⠀⠀⣠⠃⢐⠄⠀⠀⠴⠅⠠⡊⡢⠀⠉⠉⠁⠀⢆⠕⠹⡀⠀⠈⡆⠀ < 开玩笑,我超勇的好不好 \ * ⠀⠠⡇⠀⡸⠀⠀⠀⠨⡅⠀⠒⠈⠀⢄⠠⠠⠔⠀⠀⠀⢻⠀⠀⢣⠀ \ 我超会玩的啦 ~ / * ⠀⢸⠅⠀⡕⠀⠀⠀⠀⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡤⡏⠀⠀⢸⠀ \____________________/ * ⠀⠈⡇⠀⣣⠀⠀⠈⠀⠸⡦⠴⠲⢚⢚⠙⠝⠙⠍⠝⣱⠏⢠⠀⢸⠅ * ⠀⠀⠙⣆⠘⣄⠀⠠⣄⠀⠹⣌⠌⠀⠂⠐⢈⠄⡁⢌⠳⣺⠏⢀⡞⠀ * ⠀⠀⠀⠀⠙⠺⠛⣲⠜⠟⡓⡚⣏⣔⡀⡌⣀⢂⣔⠴⠋⢏⠒⠁⠀⠀ * <p> * 什么新游戏,比游戏还刺激!还可以教你 学 Ja va 哦 ~ */ public class TankGame extends JFrame { public static MyPanel mp; public static Thread thread; public static File savePath = new File("d:\\Program\\Melody\\TankGame\\saveData.sav"); public boolean abandoned; public static void main(String[] args) { int width = 1200; int height = 800; int enemyNum = 5; Scanner scanner = new Scanner(System.in); char inpChar = ' '; mp = new MyPanel(width, height, enemyNum); if (savePath.isFile()) { System.out.println("开始新游戏?(Y新游戏/N读档)"); while (true) { inpChar = scanner.next().charAt(0); try { if (inpChar == 'Y' || inpChar == 'y') { break; } else if (inpChar == 'N' || inpChar == 'n') { mp.pause = true; mp.toLoad = true; } else { throw new RuntimeException(); } break; } catch (Exception e) { System.out.println("错误!请重新输入(Y新游戏/N读档)"); } } } TankGame game = new TankGame(Math.abs(width), Math.abs(height), Math.abs(enemyNum)); thread = new Thread(game.mp); thread.start(); } public TankGame(int width, int height, int enemyNum) { width = Math.max(width, 800); height = Math.max(height, 600); this.add(mp); this.setSize(width + 20, height + 240); this.addKeyListener(mp); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } }
-
MyPanel.java
package com.melody.tank_game; import javax.swing.*; import java.awt.*; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.io.*; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Vector; /** * @author Melody * @version 1.0 * !!!!!!!!!!!!!!!该类需要重新整理!!!!!!!!!!!!!!!! */ class MyPanel extends JPanel implements KeyListener, Runnable, Serializable { //5 个静态变量依次是:允许的同屏最大敌人数量的上限、战斗区域宽度、战斗区域高度、玩家、是否通关 private static final int ENEMY_MAX = 20; public static int width = 1200; public static int height = 1000; private static Player player = new Player(640, 640); public static boolean gameSuspend = false; public static int[][][] mapData = new int[width][height][2]; //*****此处需要修改***** private static final long serialVersionUID = 1L; //12 个成员变量,依次是:所有敌人坦克、所有被摧毁的敌人坦克、同屏最大敌人数量(用户设定)、敌人初始位置集(含坐标、占用情况)、 // 敌人初始位置集(仅坐标)、关卡敌人总数、W键是否正被按下、A键是否正被按下、S键是否正被按下、D键是否正被按下、开火键是否正被按下、 // 导入图片媒介、地图集、关卡数 private Vector<Tank> enemy = new Vector<>(); private Vector<Tank> deadEnemy = new Vector<>(); private int enemyMax; private LinkedHashMap<int[], Boolean> enemyPositions = new LinkedHashMap<>(); private int[][] enemyPosition; private transient boolean isPressingW = false; private transient boolean isPressingA = false; private transient boolean isPressingS = false; private transient boolean isPressingD = false; private transient boolean isFiring = false; private transient Image image = null; private Map[] maps = Map.values(); private int level = -1; private Tank boss; private boolean bossActive = false; boolean pause = false; transient boolean saved = false; boolean loaded = false; transient boolean toLoad = false; private transient boolean toSave = false; transient boolean abandoned = false; //构造器 public MyPanel(int width, int height, int enemyNum) { this.width = (int) (width / 40) * 40; this.height = (int) (height / 40) * 40; //设置长宽 enemyNum = Math.max(enemyNum, 0); enemyMax = Math.min(enemyNum, ENEMY_MAX); //设置同屏最大敌人数量 enemyPosition = new int[enemyMax][2]; //已经确定了要生成的敌人位置数量,生成位置数组(空) for (int i = 0; i < enemyMax; i++) { //生成敌人初始位置。 int[] tempPosition = new int[2]; tempPosition[0] = i * 150 + 50; tempPosition[1] = 50; while (tempPosition[0] > (width - 100)) { tempPosition[1] += 120; tempPosition[0] -= (width - 150); if (tempPosition[1] > height - 100) { throw new RuntimeException(); } } enemyPosition[i][0] = tempPosition[0]; enemyPosition[i][1] = tempPosition[1]; } //此时 敌人初始位置数组 生成完毕。 int n = 0; for (int[] ints : enemyPosition) { enemyPositions.put(ints, false); //装满 敌人初始位置集 默认全部是未占用 } } @Override public void run() { nextLevel(); player.start(); //启动玩家线程 while (!abandoned) { creatEnemy(); gameSuspend = false;//创建敌人坦克。这个方法同时会启动敌人坦克线程 while (!gameSuspend) { repaint(); //刷新 checkLevelClear(); if(toSave){ saveData(); toSave = false; } else if(toLoad){ loadData(); toLoad = false; } } pauseBetweenLevels(1200 + (bossActive ? 2000 : 0)); while (!gameSuspend) { repaint(); } nextLevel(); } } @Override public void paint(Graphics g) { drawUI(g); //画出 UI drawPlayer(g); //画出 玩家坦克(包含子弹) drawEnemy(g); //画出 所有敌人坦克(包含子弹) drawDeadTank(g); //画出 被破坏坦克的残留动画和残留子弹 } //画出 UI private void drawUI(Graphics g) { g.setColor(Color.BLACK); g.fillRect(0, 0, width, height); g.setColor(Color.GRAY); g.fillRect(2, 2, width - 4, height - 4); //以上 4 行,画出了战斗场地 g.setColor(Color.WHITE); g.fillRect(0, height, width, 200); drawHp(g); //画 HP 槽 drawFireCD(g); //画 开火 槽 drawEnemyNum(g); //画 雷达 槽 drawStars(g); //画 经验值 星星 if (pause) { drawPause(g); } } //画 HP 槽 private void drawHp(Graphics g) { //下面画出 绿色 HP 槽。随着 HP 减少,槽会变空 g.setFont(new Font("等线", Font.ITALIC + Font.BOLD, 50)); g.setColor(Color.BLACK); g.fillRect(50, height + 60, (int) (20 * player.vehicle.LIFE) + 4, 30); g.setColor(player.lifeRemains / player.vehicle.LIFE > 0.2 ? Color.GREEN : Color.RED); g.fill3DRect(52, height + 62, (int) (20 * Math.max(player.lifeRemains, 0)), 26, true); g.fill3DRect(52, height + 80, (int) (20 * Math.max(player.lifeRemains, 0) - 1), 6, false); //下面画出一段说明文 g.setColor(Color.GRAY); if (player.vehicle == Vehicle.Basic) { g.drawString("防弹衣", 50, height + 140); } else { g.drawString(player.vehicle == Vehicle.Tank_Special ? "复合装甲" : "装甲", 50, height + 140); } } //画 开火 槽 private void drawFireCD(Graphics g) { //以下画出 橙色 开火槽。随着装弹,槽会填满 g.setFont(new Font("等线", Font.ITALIC + Font.BOLD, 50)); g.setColor(Color.BLACK); g.fillRect((int) ((width / 2) + 60), height + 60, 205, 30); g.setColor(player.vehicle == Vehicle.Tank_Special ? Color.cyan : Color.ORANGE); g.fill3DRect((int) ((width / 2) + 62), height + 62, (int) (player.getAtkSpeed() * (player.fireCD - Math.max(player.fireCount, 0)) + 1), 26, true); g.fill3DRect((int) ((width / 2) + 62), height + 80, (int) (player.getAtkSpeed() * (player.fireCD - Math.max(player.fireCount, 0))), 6, false); //以下画出一段说明文字 g.setColor(Color.GRAY); if (player.vehicle == Vehicle.Basic) { g.drawString(player.fireCount <= 10 ? "哒哒哒哒哒……" : player.fireCount == player.fireCD ? "加特林停了" : "加特林转啊!", width / 2 + 60, height + 140); } else { g.drawString(player.fireCount <= 0 ? "弹药装填完毕" : "装填中…", width / 2 + 60, height + 140); } } //画出 敌人数量(这个方法是UI的一部分,统计数量,而不是画坦克) private void drawEnemyNum(Graphics g) { g.setFont(new Font("等线", Font.ITALIC + Font.BOLD, 20)); g.setColor(Color.BLACK); g.drawString("雷达发现敌人数量:" + (maps[level].levelEnemyNum - deadEnemy.size()), (int) (width * 0.77), height + 22); } //画出 经验值。有几点就有几颗红星被点亮。 private void drawStars(Graphics g) { switch (player.vehicle.LEVEL_UP_REQUIRED) { case 10: g.drawImage(player.enhance >= 10 ? InputImage.star : InputImage.nStar, 280, height + 10, 30, 30, this); g.drawImage(player.enhance >= 9 ? InputImage.star : InputImage.nStar, 250, height + 10, 30, 30, this); g.drawImage(player.enhance >= 8 ? InputImage.star : InputImage.nStar, 220, height + 10, 30, 30, this); g.drawImage(player.enhance >= 7 ? InputImage.star : InputImage.nStar, 190, height + 10, 30, 30, this); g.drawImage(player.enhance >= 6 ? InputImage.star : InputImage.nStar, 160, height + 10, 30, 30, this); case 5: g.drawImage(player.enhance >= 5 ? InputImage.star : InputImage.nStar, 130, height + 10, 30, 30, this); case 4: g.drawImage(player.enhance >= 4 ? InputImage.star : InputImage.nStar, 100, height + 10, 30, 30, this); case 3: g.drawImage(player.enhance >= 3 ? InputImage.star : InputImage.nStar, 70, height + 10, 30, 30, this); case 2: g.drawImage(player.enhance >= 2 ? InputImage.star : InputImage.nStar, 40, height + 10, 30, 30, this); case 1: g.drawImage(player.enhance >= 1 ? InputImage.star : InputImage.nStar, 10, height + 10, 30, 30, this); break; default: g.setColor(Color.GRAY); //不能升级的坦克,画一个小小的圆 g.fillOval(40, height + 10, 10, 10); break; } } private void drawPause(Graphics g) { g.setFont(new Font("等线", Font.ITALIC + Font.BOLD, 70)); g.setColor(Color.DARK_GRAY); g.drawString("游戏暂停中,按 P 键继续游戏", (int) ((width / 2) * 0.2), (height / 2 - 100)); if (saved){ g.setFont(new Font("等线", Font.ITALIC + Font.BOLD, 50)); g.setColor(Color.DARK_GRAY); g.drawString("存档成功!", (int) ((width / 2) * 0.8), (height / 2)); } else if(loaded){ g.setFont(new Font("等线", Font.ITALIC + Font.BOLD, 50)); g.setColor(Color.DARK_GRAY); g.drawString("读档成功!", (int) ((width / 2) * 0.8), (height / 2)); } if (deadEnemy.size() >= maps[level].levelEnemyNum && (!bossActive || !boss.alive)){ g.setFont(new Font("黑体", Font.ITALIC + Font.BOLD, 70)); g.setColor(Color.ORANGE); g.drawString("下一波敌人接近中!", (int) ((width / 2) * 0.5), (int)(height * 0.8)); //*****此处需要修改***** } } //画出 玩家坦克 private void drawPlayer(Graphics g) { player.pause = this.pause; drawLivingTank(g, player); } //画出敌人坦克 private void drawEnemy(Graphics g) { Iterator<Tank> iterator = enemy.iterator(); int n = 0; while (iterator.hasNext()) { Tank temp = iterator.next(); if (!temp.alive) { //这个分支,坦克已被摧毁 temp.releaseCollisionSize(); //消除其碰撞体积 iterator.remove(); deadEnemy.add(temp); //从 敌人集 把该坦克删除,加入 破坏坦克集 player.enhanced(temp.vehicle); //给玩家结算经验 enemyPositions.put(enemyPosition[n], false);//该坦克占用的位置被解放 } n++; } creatEnemy(); //尝试生成新的坦克 iterator = enemy.iterator(); while (iterator.hasNext()) { Tank t = iterator.next(); t.pause = this.pause; //暂停设置 // if (t.specialCount > 0) { // t.specialCount--; //*****此处需要修改***** // } else { drawLivingTank(g, t); //画出所有(敌人的)坦克 // } } if (bossActive) { boss.pause = this.pause; drawBoss(g); } } private void drawBoss(Graphics g) { if (boss.alive) { drawTank(g, boss); } else { drawExplosion(g, boss); } drawBullet(g, boss); } //画出被摧毁的坦克的残留特效及其所有残留子弹 private void drawDeadTank(Graphics g) { Iterator<Tank> iterator = deadEnemy.iterator(); while (iterator.hasNext()) { Tank temp = iterator.next(); temp.pause = this.pause; if (temp.specialCount > 0) { //敌人的特殊计数残留时,画出其爆炸效果 temp.specialCount--; //*****此处需要修改***** drawExplosion(g, temp); } if (temp.bullets.size() > 0) { //敌人的子弹有残留时,画出该子弹 drawBullet(g, temp); } } } //画出存活的坦克及其所有子弹 private void drawLivingTank(Graphics g, Tank tank) { //以下部分画出坦克 if (tank.alive) { drawTank(g, tank); //存活的场合,画出坦克 drawBullet(g, tank); //画出子弹 } } //画坦克。这个方法能画出朝向正确的坦克。 private void drawTank(Graphics g, Tank tank) { boolean b = dirState2(tank.dir); switch (tank.vehicle) { case Tank_Lv1: image = dirState(tank.dir) ? InputImage.tank1 : InputImage.tank1r; break; case Tank_Lv2: image = dirState(tank.dir) ? InputImage.tank2 : InputImage.tank2r; break; case Tank_Lv3: image = dirState(tank.dir) ? InputImage.tank3 : InputImage.tank3r; break; case Tank_E1: image = dirState(tank.dir) ? InputImage.tankE1 : InputImage.tankE1r; break; case Tank_E2: image = dirState(tank.dir) ? InputImage.tankE2 : InputImage.tankE2r; break; case Tank_E3: image = dirState(tank.dir) ? InputImage.tankE3 : InputImage.tankE3r; break; case Tank_Special: image = dirState(tank.dir) ? InputImage.tankSp : InputImage.tankSpr; break; case Basic: if (tank.fireCount <= 3) { image = dirState(tank.dir) ? InputImage.basicL : InputImage.basicLR; } else { image = dirState(tank.dir) ? InputImage.basic : InputImage.basicR; } break; } g.drawImage(image, b ? tank.x + 80 : tank.x, b ? tank.y + 80 : tank.y, b ? -80 : 80, b ? -80 : 80, this); if (tank.invincible > 0) { switch (tank.vehicle) { //无敌持续中的场合,画出护盾 case Tank_Special: case Basic: image = dirState(tank.dir) ? InputImage.bubble : InputImage.bubbleR; break; default: image = dirState(tank.dir) ? InputImage.bubbleE : InputImage.bubbleER; break; } g.drawImage(image, tank.x - 5, tank.y - 5, 90, 90, this); } } //画出子弹。这个方法也会判断子弹的状态(是否存活) private void drawBullet(Graphics g, Tank tank) { Iterator<Bullet> iterator = tank.bullets.iterator(); while (iterator.hasNext()) { Bullet temp = iterator.next(); temp.pause = pause; if (!temp.active) { //子弹消亡的场合,移除子弹 iterator.remove(); } else { //走到这里,说明子弹存活。 switch (tank.party) { //看看是哪个阵营发出的子弹,去判定别的阵营的坦克 case 0: if (bossActive && checkHit(boss, temp, tank)) { break; } for (Tank t : enemy) { if (checkHit(t, temp, tank)) { //遍历敌坦克,看看打中了谁。打中则停止判定 break; } } break; case 1: checkHit(player, temp, tank); //看看有没有击中玩家 default: } drawBullet(g, tank.vehicle, temp.getDir(), temp.getX(), temp.getY()); //画出子弹 } } } //画出子弹。这个方法通常由上个方法调用,能画出朝向正确的子弹 private void drawBullet(Graphics g, Vehicle vehicle, char dir, int x, int y) { boolean b = dirState2(dir); switch (vehicle.BULLET) { case 'n': image = dirState(dir) ? InputImage.nBullet : InputImage.nBulletR; break; case 'e': image = dirState(dir) ? InputImage.eBullet : InputImage.eBulletR; break; case 'g': image = dirState(dir) ? InputImage.gBullet : InputImage.gBulletR; break; } g.drawImage(image, b ? x + 10 : x, b ? y + 10 : y, b ? -10 : 10, b ? -10 : 10, this); } //画出爆炸。爆炸分为几个阶段,而且不同坦克也有不同爆炸特效 private void drawExplosion(Graphics g, Tank tank) { if (tank.specialCount > 350) { drawTank(g, tank); } switch (tank.vehicle) { case Tank_E3: if (tank.specialCount < 600 && tank.specialCount >= 100) { if (tank.specialCount >= 500) { image = InputImage.explosion1; } else if (tank.specialCount >= 400) { image = InputImage.explosion2; } else if (tank.specialCount >= 300) { image = InputImage.explosion3; } else if (tank.specialCount >= 200) { image = InputImage.explosion4; } else { image = InputImage.explosion5; } g.drawImage(image, tank.x, tank.y, 80, 80, this); } break; case Tank_E1: case Tank_E2: default: if (tank.specialCount < 450 && tank.specialCount >= 50) { if (tank.specialCount >= 250) { image = InputImage.explosion1; } else if (tank.specialCount > 100) { image = InputImage.explosion2; } else { image = InputImage.explosion3; } g.drawImage(image, tank.x, tank.y, 80, 80, this); } } tank.specialCount = Math.max(tank.specialCount, 0); } //下面这两个方法可以帮助其他方法画出正确朝向的对象 private boolean dirState(char dir) { return dir == 'u' || dir == 'd'; } private boolean dirState2(char dir) { return dir == 'l' || dir == 'd'; } //检查子弹是否击中目标 private boolean checkHit(Tank tank, Bullet bullet, Tank from) { if (hit(tank, bullet) && !bullet.hasHit(tank)) { //检查是否击中目标 及 目标是否已被击中过 bullet.setHit(tank); if (tank.invincible > 0) { bullet.pierce = 0; //撞到无敌的坦克时,子弹直接报销 } bullet.active = bullet.pierce > 0; //子弹穿透降为 0 的场合,子弹消亡 if (!tank.takeDamage(from, bullet.getDir())) { //坦克伤害计数完后,在这里把 HP 归 0 的坦克记为已销毁 tank.alive = false; } return true; } return false; } //看看子弹是否击中坦克。由上个方法调用 private boolean hit(Tank tank, Bullet bullet) { if (!tank.alive) { return false; } if (tank.vehicle == Vehicle.Basic) { return pIn(tank.x + 30, 20, bullet.getX() + 5) && pIn(tank.y + 30, 20, bullet.getY() + 5); } switch (tank.getDir()) { case 'u': case 'd': return pIn(tank.x + 20, 40, bullet.getX() + 5) && pIn(tank.y + 3, 76, bullet.getY() + 5); case 'r': case 'l': return pIn(tank.x + 3, 76, bullet.getX() + 5) && pIn(tank.y + 20, 40, bullet.getY() + 5); } return false; } //看看某个坐标(横坐标或纵坐标)是否在[p, p + w]范围内 private boolean pIn(int p, int w, int toCheck) { return toCheck >= p && toCheck <= (p + w); } @Override public void keyTyped(KeyEvent e) { } //监视器。以下方式保证了动画的流畅度 @Override public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_W: isPressingW = true; break; case KeyEvent.VK_A: isPressingA = true; break; case KeyEvent.VK_D: isPressingD = true; break; case KeyEvent.VK_S: isPressingS = true; break; case KeyEvent.VK_J: //J 是开火键 isFiring = true; break; // //===================以下为测试用===================== // case KeyEvent.VK_Q: //该 case 仅测试用。 务必删除!务必删除! // for (Tank tank : enemy) { // tank.alive = false; // tank.releaseCollisionSize(); // } // break; // case KeyEvent.VK_E: //该 case 仅测试用。 务必删除!务必删除! // deadEnemy.clear(); // break; // case KeyEvent.VK_1: //该 case 仅测试用。 务必删除!务必删除! // player.setVehicle(Vehicle.Tank_Lv1); // player.releaseCollisionSize(); // break; // case KeyEvent.VK_2: //该 case 仅测试用。 务必删除!务必删除! // player.setVehicle(Vehicle.Tank_Lv2); // player.releaseCollisionSize(); // break; // case KeyEvent.VK_3: //该 case 仅测试用。 务必删除!务必删除! // player.setVehicle(Vehicle.Tank_Lv3); // player.releaseCollisionSize(); // break; // case KeyEvent.VK_4: //该 case 仅测试用。 务必删除!务必删除! // player.setVehicle(Vehicle.Tank_Special); // player.releaseCollisionSize(); // break; // case KeyEvent.VK_5: //该 case 仅测试用。 务必删除!务必删除! // player.setVehicle(Vehicle.Basic); // player.releaseCollisionSize(); // break; // case KeyEvent.VK_Z: //该 case 仅测试用。 务必删除!务必删除! // player.buffedDef = 100; // break; // case KeyEvent.VK_X: //该 case 仅测试用。 务必删除!务必删除! // player.buffedDef = 0; // break; // case KeyEvent.VK_C: //该 case 仅测试用。 务必删除!务必删除! // player.invincible = 1000000; // break; // case KeyEvent.VK_V: //该 case 仅测试用。 务必删除!务必删除! // player.invincible = 0; // break; //===================以上为测试用===================== } isGoing(); //看看玩家是否正在移动 isFiring(); //看看玩家是否正在开火 } //同上 @Override public void keyReleased(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_W: //向上 isPressingW = false; break; case KeyEvent.VK_A: //向左 isPressingA = false; break; case KeyEvent.VK_D: //向右 isPressingD = false; break; case KeyEvent.VK_S: //向下 isPressingS = false; break; case KeyEvent.VK_J: //开火 isFiring = false; break; case KeyEvent.VK_P: //暂停 gamePause(); saved = false; loaded = false; break; case KeyEvent.VK_I: //读档(需要暂停) toLoad = true; break; case KeyEvent.VK_O: //存档(需要暂停) toSave = true; break; } isGoing(); isFiring(); } //更新玩家的移动状态 private void isGoing() { if (!player.pause) { if (isPressingW) { player.dir = 'u'; } else if (isPressingS) { player.dir = 'd'; } else if (isPressingA) { player.dir = 'l'; } else if (isPressingD) { player.dir = 'r'; } } player.isMoving = isPressingA || isPressingS || isPressingD || isPressingW; } //更新玩家的开火状态 private void isFiring() { player.isFiring = isFiring; } //生成敌人,直到允许的最大数量 private void creatEnemy() { while (enemy.size() < enemyMax && enemy.size() < maps[level].levelEnemyNum - deadEnemy.size()) { int[] temp = null; for (int[] ints : enemyPosition) { if (!enemyPositions.get(ints)) { //为坦克分配初始位置 temp = ints; enemyPositions.put(ints, true); break; } } if (temp == null) { //应该不会抛出异常。但,以防万一,加上这句 throw new RuntimeException(); } Tank tempTank = new Enemy(temp[0], temp[1], maps[level]); if (deadEnemy.size() == 0) { tempTank.specialCount = 0; //坦克有延迟显示。这里取消了第一组坦克(初始敌人坦克)的延迟显示 } enemy.add(tempTank); //在 敌人集 中添加新的坦克 tempTank.start(); //启动该新坦克线程 } } //这个方法有两个作用: // 1. 其他敌人全灭时,激活 BOSS // 2. 所有敌人全灭时,进入下一关 private void checkLevelClear() { if (deadEnemy.size() == maps[level].levelEnemyNum && !bossActive && boss != null && boss.alive) { boss.start(); bossActive = true; } else if (deadEnemy.size() >= maps[level].levelEnemyNum && (boss == null || !boss.alive)) { gameSuspend = true; } } //关卡间设置短暂停顿 private void pauseBetweenLevels(int time) { gameSuspend = false; new Thread(new Timing(time)).start(); } //每次调用会切换 暂停状态。 private void gamePause() { this.pause = !this.pause; } //进入下一关时,加载下一关数据 private void nextLevel() { player.releaseCollisionSize(); player.x = 200; player.y = height - 200; level++; for (int i = 0; i < maps[level].mapData.length; i++) { for (int j = 0; j < maps[level].mapData[i].length; j++) { inputMapData(i, j); } } for (int[] ints : enemyPosition) { enemyPositions.put(ints, false); } for (Tank tank : deadEnemy) { tank.releaseCollisionSize(); } enemy.clear(); deadEnemy.clear(); player.lifeRemains = player.vehicle.LIFE; player.invincible = 0; gameSuspend = false; boss = maps[level].boss; if (boss != null) { boss.x = Math.max(width / 2 - 40, 0); boss.y = 70; } bossActive = false; } //从 Map 类中 加载既定的地图数据 private void inputMapData(int x, int y) { for (int k = 0; k < 40; k++) { for (int l = 0; l < 40; l++) { mapData[(x * 40) + k][(y * 40) + l][1] = maps[level].mapData[x][y][1]; mapData[(x * 40) + k][(y * 40) + l][0] = maps[level].mapData[x][y][0]; } } //*****此处需要修改***** //这里需要对 地图逻辑、坦克行进逻辑 都进行修改。 // 现行方法是:由 mapData[][] 记录每个像素的数据。每个像素点的占用情况实时更新。 // 改成:由 mapData[][] 记录所有 40 * 40 方格的数据。 // 相信这个办法应该能解决现行方法偶发的空气墙问题,扩展性也会更好。存档大小也能缩小,因而也有机会优化存档逻辑。 // 目前的代码基本兼容这个新方法。Map 类的数据本来就是 40 * 40 方格模式。地图大小、各类模型尺寸也都是 40 的整数倍 } //读档 void loadData(){ if (pause) { ObjectInputStream ois = null; try { if (!TankGame.savePath.exists()) { TankGame.savePath.createNewFile(); } ois = new ObjectInputStream(new FileInputStream(TankGame.savePath)); if((MyPanel)ois.readObject() != null){ player = (Player) ois.readObject(); // mapData = (int[][][]) ois.readObject(); enemy = (Vector<Tank>) ois.readObject(); deadEnemy = (Vector<Tank>) ois.readObject(); enemyPositions = (LinkedHashMap<int[], Boolean>) ois.readObject(); enemyPosition = (int[][]) ois.readObject(); maps = (Map[]) ois.readObject(); boss = (Tank) ois.readObject(); level = ois.readInt(); bossActive = ois.readBoolean(); } } catch (IOException | ClassNotFoundException ex) { ex.printStackTrace(); } finally { try { ois.close(); } catch (IOException ex) { ex.printStackTrace(); } } pause = true; loaded = true; for (int i = 0; i < maps[level].mapData.length; i++) { for (int j = 0; j < maps[level].mapData[i].length; j++) { inputMapData(i, j); } } for (Tank tank : enemy) { tank.start(); for (Bullet bullet : tank.bullets) { bullet.start(); } } for (Tank tank : deadEnemy) { for (Bullet bullet : tank.bullets) { bullet.start(); } } if(bossActive && boss.alive){ boss.start(); } player.start(); } } //存档 private void saveData(){ if (pause) { ObjectOutputStream oos = null; try { if (!TankGame.savePath.exists()) { TankGame.savePath.createNewFile(); } oos = new ObjectOutputStream(new FileOutputStream(TankGame.savePath)); oos.writeObject(TankGame.mp); oos.writeObject(player); // oos.writeObject(mapData); //占用太大 oos.writeObject(enemy); oos.writeObject(deadEnemy); oos.writeObject(enemyPositions); oos.writeObject(enemyPosition); oos.writeObject(maps); oos.writeObject(boss); oos.writeInt(level); oos.writeBoolean(bossActive); saved = true; } catch (IOException ex) { ex.printStackTrace(); } finally { try { oos.close(); } catch (IOException ex) { ex.printStackTrace(); } } } } }
-
Tank.java
package com.melody.tank_game; import java.io.Serializable; import java.util.Vector; /** * @author Melody * @version 1.0 */ //坦克类。这是一个抽象类,Player 和 Enemy 类会继承此类 abstract public class Tank extends Thread implements Serializable { //为什么是 继承Thread 而不是 实现Runnable?因为我写这些代码的时候还没学到那里…… /*从上到下依次是:横坐标、纵坐标、坦克类型、方向、当前生命值、速度增幅、攻速增幅、攻击范围增幅、攻击力增幅、防御力增幅、 开火间隔、开火间隔计时、子弹集、是否存活、无敌状态持续时间、阵营、死亡计时 */ protected int x; protected int y; protected Vehicle vehicle = Vehicle.Basic; protected char dir = 'u'; protected double lifeRemains; protected int buffedSpeed = 0; protected int buffedAtkSpeed = 0; protected int buffedAtkRange = 0; protected int buffedAtk = 0; protected int buffedDef = 0; protected int fireCD = 1; public int fireCount = 0; public final Vector<Bullet> bullets = new Vector<>(); //所有活动中的子弹会被存放在里面 protected boolean alive = true; public int invincible = 0; public int party; public boolean pause = false; public int specialCount = 800; //坦克死亡后,该计时才开始计数。这个计数是为了播放爆炸动画。计数完毕后该对象会被消除。 public Tank(int x, int y) { this.x = x; this.y = y; setVehicle(Vehicle.Basic); } //设置坦克种类 public void setVehicle(Vehicle vehicle) { this.vehicle = vehicle; this.lifeRemains = vehicle.LIFE; this.fireCD = 200 / getAtkSpeed(); //开火间隔由这个公式算出 } public char getDir() { return dir; } //受到伤害。这个方法由子类实现 abstract protected boolean takeDamage(Tank tank, char dir); //移动。 // abstract void move(char move); protected abstract void move(); protected int[] tryMove(){ int[] temp = new int[2]; temp[0] = x; temp[1] = y; switch (dir) { case 'u': temp[1]--; break; case 'd': temp[1]++; break; case 'l': temp[0]--; break; case 'r': temp[0]++; break; } return temp; } protected boolean outOfArea(int x, int y) { return x < 0 || y < 0 || x > MyPanel.width - 80 || y > MyPanel.height - 80; } protected boolean runIntoSth(int x, int y) { boolean checking = false; switch (dir) { case 'u': checking = MyPanel.mapData[x + 40 - (vehicle.SIZE / 2)][y + 40 - (vehicle.SIZE / 2)][1] == 0 && MyPanel.mapData[x + 40][y + 40 - (vehicle.SIZE / 2)][1] == 0 && MyPanel.mapData[x + 39 + (vehicle.SIZE / 2)][y + 40 - (vehicle.SIZE / 2)][1] == 0; break; case 'd': checking = MyPanel.mapData[x + 40 - (vehicle.SIZE / 2)][y + 39 + (vehicle.SIZE / 2)][1] == 0 && MyPanel.mapData[x + 40][y + 40 + (vehicle.SIZE / 2)][1] == 0 && MyPanel.mapData[x + 39 + (vehicle.SIZE / 2)][y + 39 + (vehicle.SIZE / 2)][1] == 0; break; case 'r': checking = MyPanel.mapData[x + 39 + (vehicle.SIZE / 2)][y + 40 - (vehicle.SIZE / 2)][1] == 0 && MyPanel.mapData[x + 39 + (vehicle.SIZE / 2)][y + 40][1] == 0 && MyPanel.mapData[x + 39 + (vehicle.SIZE / 2)][y + 39 + (vehicle.SIZE / 2)][1] == 0; break; case 'l': checking = MyPanel.mapData[x + 40 - (vehicle.SIZE / 2)][y + 40 - (vehicle.SIZE / 2)][1] == 0 && MyPanel.mapData[x + 40 - (vehicle.SIZE / 2)][y + 40][1] == 0 && MyPanel.mapData[x + 40 - (vehicle.SIZE / 2)][y + 39 + (vehicle.SIZE / 2)][1] == 0; break; } return !checking; } protected void setCollisionSize(int tempX, int tempY, int type, int hp) { for (int i = 0; i < vehicle.SIZE; i++) { for (int j = 0; j < vehicle.SIZE; j++) { MyPanel.mapData[tempX + 40 - (vehicle.SIZE / 2) + i][tempY + 40 - (vehicle.SIZE / 2) + j][1] = type; MyPanel.mapData[tempX + 40 - (vehicle.SIZE / 2) + i][tempY + 40 - (vehicle.SIZE / 2) + j][0] = hp; } } } protected void releaseCollisionSize() { for (int i = 0; i < vehicle.SIZE; i++) { for (int j = 0; j < vehicle.SIZE; j++) { MyPanel.mapData[x + 40 - (vehicle.SIZE / 2) + i][y + 40 - (vehicle.SIZE / 2) + j][1] = 0; MyPanel.mapData[x + 40 - (vehicle.SIZE / 2) + i][y + 40 - (vehicle.SIZE / 2) + j][0] = 0; } } } //开火 public void fire() { if (fireCount > 0) { //fireCount > 0 说明开火CD 还没好,直接 return return; } if (vehicle == Vehicle.Basic) { //Basic 体积和一般坦克不同,故而发射子弹的位置也不同 fireCount = 10; //其武器也不同,所以 开火CD 的逻辑也不同。这里使其每发子弹的间隔很短 switch (dir) { case 'u': fire(x + 39, y + 17, dir, getAtkRange()); break; case 'd': fire(x + 31, y + 53, dir, getAtkRange()); break; case 'l': fire(x + 17, y + 31, dir, getAtkRange()); break; case 'r': fire(x + 53, y + 39, dir, getAtkRange()); break; } } else { fireCount = fireCD; switch (dir) { case 'u': fire(x + 35, y - 10, dir, getAtkRange()); break; case 'd': fire(x + 35, y + 80, dir, getAtkRange()); break; case 'l': fire(x - 10, y + 35, dir, getAtkRange()); break; case 'r': fire(x + 80, y + 35, dir, getAtkRange()); break; } } } public void fire(int x, int y, char dir, int range) { Bullet tempBullet = new Bullet(vehicle.BULLET, x, y, dir, range, this); bullets.add(tempBullet); //创建一个新的 子弹 实例,并让其运行 tempBullet.start(); } protected boolean isFront(char dir) { //判断是否被从正面击中。如若是,坦克的防御力生效 switch (dir) { case 'u': return this.dir == 'd'; case 'd': return this.dir == 'u'; case 'r': return this.dir == 'l'; case 'l': return this.dir == 'r'; default: return false; } } public void pause(boolean pause){ this.pause = pause; } public int getAtk() { return vehicle.ATK + buffedAtk; } public int getDef() { return vehicle.DEF + buffedDef; } public int getAtkSpeed() { return vehicle.ATK_SPEED + buffedAtkSpeed; } public int getSpeed() { return vehicle.SPEED + buffedSpeed; } public int getAtkRange() { return vehicle.ATK_RANGE + buffedAtkRange; } }
-
Player.java
package com.melody.tank_game; /** * @author Melody * @version 1.0 */ public class Player extends Tank { //三个属性依次是:经验值、是否正在移动、是否正在开火 public int enhance = 0; //这两个变量由 MyPanel 的 监视器决定。按下移动/开火键时,变为 true。松开时,变为 false public boolean isMoving = false; public boolean isFiring = false; public Player(int x, int y) { super(x, y); party = 0; //阵营为 0 setVehicle(Vehicle.Basic); //设置初始坦克。 可以随意修改 } @Override public void run() { while (alive) { try { Thread.sleep(100 / getSpeed()); //速度越快、行动越快 } catch (InterruptedException e) { e.printStackTrace(); } if (!pause) { if (invincible > 0) { //无敌冷却减少 invincible--; } if (isMoving) { //正在移动的场合,使其移动 move(); } if (isFiring) { //正在开火的场合,使其开火 fire(); } else if (vehicle == Vehicle.Basic) { //Basic 是特别的。按住开火键的场合,开火CD才减少,不然会增加。 // 这样做是为了模拟 加特林 的手感 fireCount += fireCount >= fireCD ? 0 : 1; } else { fireCount--; //开火CD 自然减少 } } } } @Override protected void move() { int[] temp = tryMove(); if (!outOfArea(temp[0], temp[1]) && !runIntoSth(temp[0], temp[1])) { releaseCollisionSize(); setCollisionSize(temp[0], temp[1], -2, -1); x = temp[0]; y = temp[1]; } } @Override public void fire() { if (fireCount > 0) { //如果 开火CD 没到,让其自然减少,然后 return fireCount--; return; } super.fire(); //真正的开火 } //获得经验值。每次消灭敌人坦克会调用该方法 public void enhanced(Vehicle vehicle) { if (this.vehicle == Vehicle.Basic && Math.random() > 0.9) { //特殊设定:Basic 的场合,消灭敌人有概率夺取坦克 switch (vehicle) { case Tank_E1: setVehicle(Vehicle.Tank_Lv1); break; case Tank_E2: setVehicle(Vehicle.Tank_Lv2); break; case Tank_E3: setVehicle(Vehicle.Tank_Lv3); break; case Tank_Special: setVehicle(Vehicle.Tank_Special); break; } } else { switch (vehicle) { case Tank_E1: enhance += 1; break; case Tank_E2: enhance += 2; break; case Tank_E3: enhance += 3; break; case Tank_Special: enhance += 10; break; } enhanced(); //看看能不能升级 } } //查看是否升级 private void enhanced() { if (vehicle.LEVEL_UP_REQUIRED > 0 && enhance >= vehicle.LEVEL_UP_REQUIRED) { enhance -= vehicle.LEVEL_UP_REQUIRED; releaseCollisionSize(); switch (vehicle) { case Basic: setVehicle(Vehicle.Tank_Lv1); break; case Tank_Lv1: setVehicle(Vehicle.Tank_Lv2); break; case Tank_Lv2: setVehicle(Vehicle.Tank_Lv3); break; default: } setCollisionSize(x, y, -2, -1); } } //设置坦克。每次设置会重置经验值 @Override public void setVehicle(Vehicle vehicle) { super.setVehicle(vehicle); this.enhance = 0; if (vehicle == Vehicle.Basic) { fireCount = fireCD; } } //受到伤害。传入的是造成伤害的坦克,和击中的子弹方向 @Override protected boolean takeDamage(Tank tank, char dir) { double damage = tank.getAtk() * vehicle.DEF_BY_PERCENT - (isFront(dir) ? this.getDef() : 0); //侧后方中弹,防御力不生效 if (invincible > 0) { //无敌的场合,不受伤害 return true; } else if ((lifeRemains -= Math.max(damage, 0.1)) <= 0) { //这里扣除了生命值。最少扣除 0.1。 invincible += 200; //坦克被打爆,给一段无敌时间 releaseCollisionSize(); switch (vehicle) { //坦克降级。Basic 的场合,游戏结束 case Tank_Lv3: case Tank_Lv2: case Tank_Lv1: case Tank_Special: setVehicle(Vehicle.Basic); break; case Basic: return false; } setCollisionSize(x, y, -2, -1); } invincible += 100; //只要受到了伤害,产生短暂无敌时间 return true; } }
-
Enemy.java
package com.melody.tank_game; /** * @author Melody * @version 1.0 */ //敌人的坦克 public class Enemy extends Tank { //step:坦克的前进次数。归 0 时会转向 private int step = 0; private final double FIRE_PROBABILITY; public Enemy(Vehicle vehicle) { super(0, 0); setVehicle(vehicle); party = 1; dir = 'd'; invincible = 300; FIRE_PROBABILITY = 0.95; } public Enemy(int x, int y, Map map) { this(x, y); giveVehicle(map.probability_E1, map.probability_E2); } public Enemy(int x, int y) { super(x, y); party = 1; //敌方坦克阵营为 1 // randomDir(); // giveVehicle(); dir = 'd'; invincible = 130; //敌方坦克出生时,赋予短时间的无敌 FIRE_PROBABILITY = 0.985; } @Override public void run() { try { Thread.sleep(700 + specialCount); //坦克出生时,使其短暂休眠。 } catch (InterruptedException e) { e.printStackTrace(); } specialCount = 200 + (int) (80 * lifeRemains); //坦克的 死亡计数,和坦克种类有关 while (alive) { try { Thread.sleep((100 / getSpeed())); //坦克速度越快,其所有行动速度越快 } catch (InterruptedException e) { e.printStackTrace(); } if (!pause) { if (invincible > 0) { //无敌时间减少 invincible--; } if (fireCount > 0) { //开火CD计数减少 fireCount--; } move(); //移动 } } releaseCollisionSize(); while (specialCount >= 0) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } if (!pause) { specialCount--; //*****此处需要修改***** } } } private void giveVehicle(double probabilityE1, double probabilityE2) { double result = Math.random(); if (result <= probabilityE1) { setVehicle(Vehicle.Tank_E1); } else if (result <= probabilityE2) { setVehicle(Vehicle.Tank_E2); } else { setVehicle(Vehicle.Tank_E3); } } @Override public void move() { //传入的字符没用。 if (step <= 0) { randomDir(); //步数归 0 时,赋予其随机方向 realMove(); step = (int) (Math.random() * 200 + 60 - 3 * getSpeed()); //赋予其随机的步数 } else if (step <= 60 - 2 * getSpeed()) { //最后几步的时候,让坦克在原地稍作停留(不移动) step--; } else { realMove(); step--; } if (Math.random() > FIRE_PROBABILITY) { //使敌方坦克有 1.5% 的概率开火。这个概率经过测试,比较合适 这里可以更改 fire(); } } private void realMove() { int[] temp = tryMove(); if (!outOfArea(temp[0], temp[1]) && !runIntoSth(temp[0], temp[1])) { releaseCollisionSize(); setCollisionSize(temp[0], temp[1], -1, -1); x = temp[0]; y = temp[1]; } else { step -= 10; } } //赋予随机方向(概率均匀) private void randomDir() { switch ((int) (Math.random() * 4)) { case 0: dir = 'u'; break; case 1: dir = 'r'; break; case 2: dir = 'd'; break; case 3: dir = 'l'; break; } } //受伤。传入的是造成伤害的坦克(以计算伤害),及子弹方向(用于判断是否是正面中弹) @Override protected boolean takeDamage(Tank tank, char dir) { double damage = tank.getAtk() * vehicle.DEF_BY_PERCENT - (isFront(dir) ? getDef() : 0); //侧后方中弹的场合,防御力不生效 if (invincible > 0) { //如果坦克无敌,不会受到伤害 return true; } else { return (this.lifeRemains -= Math.max(damage, 0.1)) > 0; //在这里进行了生命值的扣除,最少扣 0.1。返回是否生命值有剩 } } }
-
Vehicle.java
package com.melody.tank_game; import java.io.Serializable; /** * @author Melody * @version 1.0 */ //该枚举类存放所有可能的坦克种类及其参数 public enum Vehicle implements Serializable{ //Basic 是特别的。设定上,Basic 是离开载具,扛着加特林的驾驶员。其体积和一般坦克不同 Basic(1, 2, 0, 20, 3, 400, 5, 10, 'g', 10), Tank_Lv1(5, 11, 1, 12, 4, 1000, 5, 1, 'n'), Tank_Lv2(7, 15, 2, 12, 4, 1000, 10, 0.9, 'n'), Tank_Lv3(10, 17, 3, 12, 4, 1600, -1, 0.8, 'n'), Tank_E1(5, 5, 1, 11, 4, 1000, -1, 1, 'n'), Tank_E2(7, 10, 2, 10, 4, 1000, -1, 0.8, 'n'), Tank_E3(10, 18, 3, 9, 4, 1600, -1, 0.6, 'n'), Tank_Special(25, 13, 4, 18, 1, 100000, -1, 0.4, 'e'); //从上到下依次是:攻击力、生命值、防御力、速度、攻击速度、攻击范围、升级经验、减伤、子弹种类 public final int ATK; public final double LIFE; public final int DEF; public final int SPEED; public final int ATK_SPEED; public final int ATK_RANGE; public final int LEVEL_UP_REQUIRED; public final double DEF_BY_PERCENT; public final char BULLET; public final int SIZE; Vehicle(int atk, double life, int def, int speed, int atkSpeed, int atkRange, int levelUpRequired, double defByPercent, char bullet, int size) { this.ATK = atk; this.LIFE = life; this.DEF = def; this.SPEED = speed; this.ATK_SPEED = atkSpeed; this.ATK_RANGE = atkRange; this.LEVEL_UP_REQUIRED = levelUpRequired; this.DEF_BY_PERCENT = defByPercent; this.BULLET = bullet; this.SIZE = size; } Vehicle(int atk, double life, int def, int speed, int atkSpeed, int atkRange, int levelUpRequired, double defByPercent, char bullet) { this(atk, life, def, speed, atkSpeed, atkRange, levelUpRequired, defByPercent, bullet, 80); } } //子弹类 class Bullet extends Thread implements Serializable { //从上到下依次是:横坐标、纵坐标、方向、子弹速度、子弹是否存在、最大飞行范围、穿透数量、已命中的坦克 private int x; private int y; private char dir; private final int BULLET_SPEED; public boolean active = true; private int range; public int pierce; private Tank master; private Tank[] hit; public boolean pause = false; public Bullet(char type, int x, int y, char dir, int range, Tank master) { switch (type) { case 'e': this.BULLET_SPEED = 3; this.pierce = 3; break; case 'g': this.BULLET_SPEED = 2; this.pierce = 1; break; case 'n': default: this.BULLET_SPEED = 1; this.pierce = 1; break; } hit = new Tank[pierce]; this.x = x; this.y = y; this.dir = dir; this.range = range; this.master = master; } public void move() { switch (dir) { case 'u': y -= BULLET_SPEED; break; case 'd': y += BULLET_SPEED; break; case 'r': x += BULLET_SPEED; break; case 'l': x -= BULLET_SPEED; break; } if ((range -= BULLET_SPEED) <= 0 || outOfArea(x, y)) { //如果达到最大飞行范围,则销毁该子弹 active = false; } } private boolean outOfArea(int x, int y) { return x < 0 || y < 0 || x > MyPanel.width - 10 || y > MyPanel.height - 10; } public int getX() { return x; } public int getY() { return y; } public char getDir() { return dir; } public int getBULLET_SPEED() { return BULLET_SPEED; } public void setHit(Tank tank) { //当有坦克被该子弹击中后,该方法会被调用。一般由 MyPanel 类中的 checkHit 方法调用 hit[hit.length - pierce--] = tank; } public boolean hasHit(Tank tank) { //判断一辆坦克是否已被击中过。一般由 MyPanel 类中的 checkHit 方法调用 for (Tank t : hit) { if (t == tank) { return true; } } return false; } public void pause(boolean pause) { this.pause = pause; } @Override public void run() { while (active) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } if (!pause) { move(); } } } }
-
Map.java
package com.melody.tank_game; import java.io.Serializable; /** * @author Melody * @version 1.0 */ public enum Map implements Serializable { Level1(10, 1, 1, null, 0, creatNewMap1()), Level2(15, 0.5, 1, Vehicle.Tank_E2, 10, creatNewMap2()), Level3(20, 0.33, 0.66, Vehicle.Tank_E3, 15, creatNewMap3()), Level4(20, 0.2, 0.5, Vehicle.Tank_Special, 20, creatNewMap4()); public final int levelEnemyNum; public final double probability_E1; public final double probability_E2; public final Tank boss; public int[][][] mapData; //[0]:种类。-1 坦克;0 道路;1 墙;2 草丛;3 水池 //[1]:HP。-1 不可破坏;0 可以通行;1 basic以外任意子弹击中1次可以破坏;2、3 同 1 Map(int levelEnemyNum, double probability_E1, double probability_E2, Vehicle boss, int bossBuff, int[][][] map) { this.levelEnemyNum = levelEnemyNum; this.probability_E1 = probability_E1; this.probability_E2 = probability_E2; if (boss == null) { this.boss = null; } else { this.boss = new Enemy(boss); this.boss.lifeRemains += bossBuff; } mapData = map; } private static int[][][] creatNewMap1() { int[][][] newMap = creatNewMap(); return newMap; } private static int[][][] creatNewMap2() { int[][][] newMap = creatNewMap(); return newMap; } private static int[][][] creatNewMap3() { int[][][] newMap = creatNewMap(); return newMap; } private static int[][][] creatNewMap4() { int[][][] newMap = creatNewMap(); return newMap; } private static int[][][] creatNewMap() { int[][][] newMap = new int[30][20][2]; newMap = creatBasicMap(newMap); return newMap; } private static int[][][] creatBasicMap(int[][][] map){ for (int i = 0; i < map.length; i++) { for (int j = 0; j < map[i].length; j++) { map[i][j][1] = 0; map[i][j][0] = 0; } } return map; } private static void addWall(int[][][] map, int x, int y) { map[x][y][0] = 1; map[x][y][1] = -1; } private static void addArea(int[][][] map, int x, int y, int kind, int hp) { map[x][y][0] = kind; map[x][y][1] = hp; } private void removeTankCollisionSize(Tank tank) { } private void setTankCollisionSize(Tank tank) { } }
-
InputImage.java
package com.melody.tank_game; import java.awt.*; import java.io.Serializable; /** * @author Melody * @version 1.0 */ /* 该类中存放所有需要的图片。 包含:坦克图片(tank/basic:4 * 2 + 1 = 9 种); 子弹图片(bullet:2 * 2 = 4 种); 护盾图片(bubble:2 * 2 = 4种); 星星图片(star:2种)、爆炸图片(explosion:5种); 地形图片(4种) */ public class InputImage implements Serializable { public static final Image tank1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_Lv1.png")); public static final Image tank2 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_Lv2.png")); public static final Image tank3 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_Lv3.png")); public static final Image basic = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/P.png")); public static final Image basicL = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Pl.png")); public static final Image basicR = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Pr.png")); public static final Image basicLR = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Plr.png")); public static final Image tankE1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_E1.png")); public static final Image tankE2 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_E2.png")); public static final Image tankE3 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_E3.png")); public static final Image tank1r = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_Lv1r.png")); public static final Image tank2r = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_Lv2r.png")); public static final Image tank3r = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_Lv3r.png")); public static final Image tankE1r = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_E1r.png")); public static final Image tankE2r = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_E2r.png")); public static final Image tankE3r = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_E3r.png")); public static final Image tankSp = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_Sp.png")); public static final Image tankSpr = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Tank_Spr.png")); public static final Image bubble = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Bubble_red.png")); public static final Image bubbleR = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Bubble_red_r.png")); public static final Image bubbleE = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Bubble.png")); public static final Image bubbleER = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Bubble_r.png")); public static final Image eBullet = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/E_bullet.png")); public static final Image eBulletR = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/E_bullet_r.png")); public static final Image nBullet = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/N_bullet.png")); public static final Image nBulletR = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/N_bullet_r.png")); public static final Image gBullet = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/G_bullet.png")); public static final Image gBulletR = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/G_bullet_r.png")); public static final Image star = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Star.png")); public static final Image nStar = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Star_n.png")); public static final Image explosion1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Explosion/Explosion1.png")); public static final Image explosion2 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Explosion/Explosion2.png")); public static final Image explosion3 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Explosion/Explosion3.png")); public static final Image explosion4 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Explosion/Explosion4.png")); public static final Image explosion5 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Explosion/Explosion5.png")); public static final Image iron = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Landform/Iron.png")); public static final Image wall = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Landform/Wall.png")); public static final Image water = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Landform/Water.png")); public static final Image trees = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/pic/Landform/Trees.png")); }
-
Timing.java
package com.melody.tank_game; /** * @author Melody * @version 1.0 */ public class Timing implements Runnable{ private int time; public Timing(int time){ this.time = time; } @Override public void run() { try { Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } MyPanel.gameSuspend = true; } }
-
导入的图片
<Java>18 项目(坦克大战)
https://i-melody.github.io/2022/01/11/Java/入门阶段/18 项目:坦克大战/