经典再现 基于JAVA平台开发坦克大战游戏
作者:ReliaM
一、需求描述
1.功能性需求
在功能需求分析阶段,我们的主要任务是指定系统必须提供哪些服务,定义软件完成哪些功能,提供给那些人使用,功能需求是软件开发的一项基本需求,是需求分析必不可少的一部分。坦克大战是一款经典游戏了,本游戏学习了一些前辈们的经验,整体来说讲,游戏分为敌我双方,主要参与战斗的坦克有玩家控制,敌人坦克可以智能随机出现在屏幕上,并且移动,发射一定数量的子弹;玩家可以在规定的区域内随意移动坦克,当有子弹击中玩家时,玩家死亡,游戏结束;敌人坦克智能运行,敌方坦克由于需要具有一定智能性,随机出现在屏幕上,自由的转动例如:碰到边界知道转向;子弹的运行:坦克有玩家控制打出,根据不同的坦克位置发射子弹,如果击中目标,则会产生爆炸效果;在屏幕上面也将消失。
2.系统性能需求
游戏对于基于计算机系统的性能配置要求是保障能使程序快速,稳定的运行,运行时能够及时的响应,当游戏中玩家坦克被击毁,及时提示游戏失败的信息,能及时有所回应,以满足游戏的规则需求,另外还要保证玩游戏时候,主窗口的大小是不能够随意改动的,保证有可玩性。
3.功能解决的问题
游戏中需要代码利用率很好,毕竟是一个实时运行的作品,每毫秒都会有很多个子弹发射,以及很多个坦克的坐标移动,无数次的对比子弹是否击中坦克,对于键盘监听事件,以及实现线程这个接口,绘图的重绘刷新等问题;逻辑感需要较强,以及面向对象展现的淋漓尽致;甚至之间的关系搞不清楚,很容易出现意外的情况;游戏中为了使加入一下美观,增添了爆炸这一现象,那么这就需要当敌人坦克死亡的时候在外面的面板上实现对固定地方图片进行短暂快速的轮播,实现一个爆炸效果;
总的来说对于坦克大战所要完成的基本的功能有:图形化界面、敌我坦克样式区别、坦克可以发射子弹攻击对方,但却不能攻击队友,坦克设置一定的生命值;
二、主要功能分析
在坦克大战游戏开发过程中,实现的主要的功能;提供给用户所使用,所以首先要画出来个坦克。
1.画出玩家坦克: 需要在JPanel面板上面设置一张画纸paint()并用画笔draw出坦克的大致形状;
2.玩家的坦克可以移动: 如果要是坦克运动起来,需要改变坦克的坐标,并且不停的重绘面板,但什么时候让坦克移动,可以在面板的类中实现事件监听机制(键盘监听)的接口,当玩家摁下w/d/s/a键可以对应上下左右移动;
3.并在我的画纸paint()上面画出敌人坦克;由于敌人坦克与玩家坦克在同一界面,需要画出在同一画板上;
4.玩家的坦克可以发射子弹: 玩家要想发射子弹,要有事件源,便是当用户摁下J键,事件监听当立刻发射一发子弹,但有需要显示在用户的眼前看出效果,所以在面板上面paint()画出子弹;由于子弹发射出来需要朝着发射放不停的移动,所以不进要一直改变子弹的坐标,还要在子弹速度恰当,所以需要用线程实现,并让面板的不停重绘,看出效果;
5.玩家子弹可以连发并且最多发五颗: 玩家若要控制子弹的个数,需要在玩家摁下J键时候,来得出当前存活的子弹数量是否小于5,当符合条件,就创建一颗子弹,当玩家发射子弹大小超过五颗,则不再调用系统的开火函数;由于发射子弹不是只发射一颗,所以设置集合存放子弹,开火一次产生一个子弹,知道子弹碰到边界死亡即是溢出子弹集合;
6.玩家坦克击中敌人坦克则消失,并产生爆炸效果;首先要判断玩家坦克的子弹是否击中了敌人,所以要有不停判断击中敌人的函数,当子弹运行到敌人坦克上,坦克生命需要终结,并产生一颗炸弹,并在paint()画出炸弹;
7.敌人的坦克可以智能移动: 如果要是坦克只能移动,需要随机产生移动方向,由于不停的运动,所以将方法写入到线程的run函数里面,并控制线程休眠来实现移动速度问题;
8.敌人坦克以可以发射子弹: 在画出敌人的同时,在线程中设定一定休眠产生不同方向,不同数量坦克的子弹,并在面板上取出每颗子弹,并画出;
9.敌人子弹击中玩家,玩家消失: 不停的取出敌人的每颗子弹,与玩家坦克进行比对,当子弹触碰到玩家,玩家爆炸消失;
三、概要设计
•角色属性设置
坦克:坦克产生的位置,不同类型坦克的颜色,坦克生命标识符,坦克运动的速度,以及不同方向的坦克;
玩家坦克:继承坦克基本属性之后,在次基础上实现,玩家坦克的自由上下左右移动;以及玩家坦克拥有开火发射子弹这种功能;
敌人坦克:继承基本坦克属性之后,需要实现敌人坦克要智能的运动,同时也要在不同位置的坦克对其进行发射子弹线程;
子弹:子弹要有坐标位置,速度,生命值,以及子弹应该可以动,以及子弹也要死亡函数方法的实现;
炸弹:炸弹的坐标,生命,以及生命渐消失的效果方法函数;
•功能属性设置
画纸:paint()应该画出来玩家坦克,敌人坦克,以及显示出死亡坦克爆炸效果;
事件监听:当用户摁下wdsa,对应的玩家坦克应该进行方向的改变,当用户发射j则朝发射方向有颗子弹,并且应该一直运行到死亡,除非击中敌方;
击中坦克:当子弹击中到敌方坦克,产生爆炸添加到爆炸集中,并将爆炸信息画到面纸上;
敌人击中玩家:取出游戏里所以的敌人,以及每个敌人的子弹与我的坦克去匹配,判断是否击中;
玩家击中敌人:取出玩家的每颗子弹,与每一个敌人的坦克去匹配,判断是否击中;并将击中坦克相关的判断,放到线程run()里面进行不停并发的判断;
具体功能分析图如下:
•坦克角色属性分析图
功能画板分析图
坦克Xmind整体分析图
坦克角色属性Xmind图
坦克功能属性Xmind图
四、系统实现
•Members.java
//Members.java package mytank9; import java.util.*; class Bomb { // 定义炸弹坐标 int x, y; // 炸弹的生命 int life = 9; boolean isLive = true; public Bomb(int x, int y) { this.x = x; this.y = y; } // 减少生命值 public void lifeDown() { if(life > 0) { life--; } else{ this.isLive = false; } } } class Shot implements Runnable { int x; int y; int direct; int speed = 1; boolean isLive = true; public Shot(int x, int y, int direct) { this.x = x; this.y = y; this.direct = direct; } public void run() { while(true) { try{ Thread.sleep(50); }catch(Exception e){ e.printStackTrace(); } switch(direct) { case 0: y-=speed; break; case 1: x+=speed; break; case 2: y+=speed; break; case 3: x-=speed; break; } // 判断字段是否碰到边缘 if(x<0||x>400||y<0||y>300) { this.isLive = false; break; } } } } class Tank { // 坦克横坐标 int x = 0; // 克纵坐标 int y = 0; // 坦克方向 // 0 表示上,1表示右,2 表示下,3 表示左 int direct = 0; // 坦克速度 int speed = 1; // 坦克颜色 int Color; boolean isLive = true; public int getColor() { return Color; } public void setColor(int color) { Color = color; } public int getSpeed() { return speed; } public void setSpeed(int speed) { this.speed = speed; } public int getDirect() { return direct; } public void setDirect(int direct) { this.direct = direct; } public Tank(int x, int y){ this.x = x; this.y = y; } 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; } } // 敌人的坦克 class EnemyTank extends Tank implements Runnable { int times = 0; // 定义一个向量,可以存放敌人的子弹 Vector<Shot>ss = new Vector<Shot>(); // 敌人添加子弹应该在刚刚创建坦克和敌人的坦克子弹死亡之后 public EnemyTank(int x, int y) { super(x, y); } @Override public void run() { // TODO Auto-generated method stub while(true) { switch(this.direct) { case 0: // 说明坦克正在向上走 for(int i = 0; i < 30; i++) { // 敌人坦克在我的范围内移动 if(y>0) { y-=speed; } try { Thread.sleep(50); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } break; case 1: for(int i = 0; i < 30; i++) { if(x<400) { x+=speed; } try { Thread.sleep(50); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } break; case 2: for(int i = 0; i < 30; i++) { if(y<300) { y+=speed; } try { Thread.sleep(50); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } break; case 3: for(int i = 0; i < 30; i++) { if(x > 0) { x-=speed; } try { Thread.sleep(50); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } break; } // 判断是否需要给坦克加入新的子弹 this.times++; if(times%2==0) { if(isLive) { if(ss.size()<5) { Shot s =null; switch(direct) { case 0: // 创建一颗子弹 s = new Shot(x+10, y, 0); // 把子弹加入到向量 ss.add(s); break; case 1: s = new Shot(x+30, y+10, 1); ss.add(s); break; case 2: s = new Shot(x+10, y+30, 2); ss.add(s); break; case 3: s = new Shot(x, y+10, 3); ss.add(s); break; } // 启动子弹线程 Thread t = new Thread(s); t.start(); } } } // 让坦克随机产生一个新的方向 this.direct = (int)(Math.random()*4); // 判断敌人坦克是否死亡 if(this.isLive == false) { // 让坦克死亡后,退出进程 break; } } } } // 我的坦克 class Hero extends Tank { Vector<Shot> ss = new Vector<Shot>(); Shot s = null; public Hero(int x, int y) { super(x, y); } // 开火 public void shotEnemy() { switch(this.direct) { case 0: // 创建一颗子弹 s = new Shot(x+10, y, 0); // 把子弹加入到向量 ss.add(s); break; case 1: s = new Shot(x+30, y+10, 1); ss.add(s); break; case 2: s = new Shot(x+10, y+30, 2); ss.add(s); break; case 3: s = new Shot(x, y+10, 3); ss.add(s); break; } Thread t = new Thread(s); t.start(); } // 坦克向上移动 public void moveUP() { y-=speed; } // 坦克向右移动 public void moveRight() { x+=speed; } public void moveDown() { y+=speed; } public void moveLeft() { x-=speed; } }
•MyTankGame4.java
//MyTankGame4.java /* * 功能:坦克游戏2.0 * 1: 画出坦克 * 2:我的坦克可以上下移动 * 3: 画出敌人坦克 * 4: 我的坦克可以发子弹 * 5:子弹可以连发(最多可以连发五颗) * 6: 当我的坦克击中敌人坦克时候,敌人消失(爆炸 * 『 判断子弹是否击中坦克;什么时候调用;』 * 爆炸:1先准备三张图;2定义Bomb类;3在击中敌人坦克时放入炸弹Vector 4绘制 * 7: 敌人坦克在我规定范围移动 * 8:敌人坦克也能发子弹 * 9: 当敌人坦克击中我的坦克,我的坦克消失 */ package mytank9; import java.awt.*; import javax.imageio.ImageIO; import javax.swing.*; import java.awt.event.*; import java.io.File; import java.util.*; public class MyTankGame4 extends JFrame{ MyPanel mp = null; public static void main(String[] args) { // TODO Auto-generated method stub MyTankGame4 mytankgame1 = new MyTankGame4(); } public MyTankGame4(){ mp = new MyPanel(); Thread t = new Thread(mp); t.start(); this.add(mp); // 注册监听 this.addKeyListener(mp); this.setSize(400, 300); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } } class MyPanel extends JPanel implements KeyListener,Runnable{ // 定义一个我的坦克 Hero hero = null; // 定义敌人的坦克 Vector<EnemyTank> ets = new Vector<EnemyTank>(); // 定义一个炸弹的集合 Vector<Bomb> bombs = new Vector<Bomb>(); // 敌人坦克多少 int enSize = 3; // // 定义三张图片的图片的切换,才能组成一颗炸弹 Image image1 = null; Image image2 = null; Image image3 = null; // 构造 public MyPanel(){ hero = new Hero(100,100); // 敌人的坦克初始化 for(int i = 0; i <enSize; i++) { // 创建敌人的坦克对象 EnemyTank et = new EnemyTank((i+1)*50, 0); et.setColor(0); et.setDirect(2); // 启动敌人坦克 Thread t = new Thread(et); t.start(); // 给敌人坦克谈价一颗子弹 Shot s = new Shot(et.x+10,et.y+30,2); et.ss.add(s); Thread t2 = new Thread(s); t2.start(); // 加入 ets.add(et); } try{ image1 = ImageIO.read(new File("bomb_1.gif")); image2 = ImageIO.read(new File("bomb_2.gif")); image3 = ImageIO.read(new File("bomb_3.gif")); }catch(Exception e){ e.printStackTrace(); } // 初始化三张图片 // image1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_1.gif")); // image2 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_2.gif")); // image3 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_3.gif")); } //重新paint public void paint(Graphics g){ super.paint(g); g.fillRect(0, 0, 400, 300); // 画出自己的坦克 if(hero.isLive==true) { this.drawTank(hero.getX(), hero.getY(), g, this.hero.direct, 1); } // 从ss中取出每一颗子弹,并绘制 for(int i = 0; i <hero.ss.size(); i++) { // 取出子弹 Shot myShot = hero.ss.get(i); // 画出子弹 画出一颗子弹,怎么画出多子弹?遍历 if(myShot!=null&&myShot.isLive==true) { g.draw3DRect(myShot.x, myShot.y, 1, 1, false); } if(myShot.isLive==false) { // 从ss向量中删除该子弹 hero.ss.remove(myShot); } } // 画出炸弹 for(int i = 0; i < bombs.size();i++) { Bomb b = bombs.get(i); if(b.life > 6) { g.drawImage(image1, b.x, b.y,30,30, this); }else if(b.life>4) { g.drawImage(image2, b.x, b.y,30,30, this); }else{ g.drawImage(image3, b.x, b.y,30,30, this); } // 让b的生命值减少 b.lifeDown(); // 如果炸弹生命值==0踢出去 if(b.life == 0) { bombs.remove(b); } } // 画出敌人坦克 for(int i = 0 ; i < ets.size(); i++) { EnemyTank et = ets.get(i); if(et.isLive) { this.drawTank(et.getX(), et.getY(), g,et.getDirect(), 0); // 画出敌人子弹 for(int j = 0; j < et.ss.size();j++) { Shot enemyShot = et.ss.get(j); if(enemyShot.isLive) { g.draw3DRect(enemyShot.x, enemyShot.y, 1, 1, false); }else{ // 敌人坦死亡了 et.ss.remove(enemyShot); } } } } } // 敌人子弹是否击我 public void hitMe() { // 取出每一个敌人坦克 for(int i = 0; i < this.ets.size(); i++) { // 取出敌人坦克 EnemyTank et = ets.get(i); if(et.isLive==true) { for(int j = 0; j < et.ss.size(); j++) { // 取出子弹 Shot enemyShot = et.ss.get(j); if(enemyShot.isLive==true) { this.hitTank(enemyShot, hero); } } } } } // 我的子弹是否击中敌人坦克 public void hitEnemyTank() { // 判断是否击中敌人的坦克 for(int i = 0; i < hero.ss.size(); i++) { Shot myShot = hero.ss.get(i); // 判断子弹是否有效 if(myShot.isLive==true) { // 取出每一个坦克与它判断 for(int j = 0; j < ets.size(); j++) { EnemyTank et = ets.get(j); if(et.isLive==true) { this.hitTank(myShot,et); } } } } } // 写一个函数专门判断子弹是否击中坦克 public void hitTank(Shot s, Tank et) { switch(et.direct) { // 如果敌人坦克方向是上或者是下 case 0: case 2: if(s.x>et.x&&s.x<et.x+20&&s.y>et.y&&s.y<et.y+30) { // 击中死亡 s.isLive = false; // 坦克死亡 et.isLive = false; // 创建一颗炸弹,放入Vector Bomb b = new Bomb(et.x, et.y); bombs.add(b); } case 1: case 3: if(s.x>et.x&&s.x<et.x+30&&s.y>et.y&&s.y<et.y+20) { { // 击中死亡 s.isLive = false; // 敌人坦克死亡 et.isLive = false; Bomb b = new Bomb(et.x, et.y); bombs.add(b); } } } } // 画出坦克 public void drawTank(int x , int y, Graphics g, int direct, int type) { // 坦克类型 switch(type) { case 0: g.setColor(Color.cyan); break; case 1: g.setColor(Color.yellow); break; } // 坦克方向 switch(direct) { // 向上 case 0: // 画出左边坦克 g.fill3DRect(x, y, 5, 30, false); // 画出右边坦克 g.fill3DRect(x+15, y, 5, 30, false); // 画出中间矩形 g.fill3DRect(x+5, y+5, 10, 20, false); // 画出原形 g.fillOval(x+5, y+10, 10, 10); // 画出直线 g.drawLine(x+10, y+15, x+10, y); break; case 1: // 向右 // 画出上面矩形 g.fill3DRect(x, y, 30, 5, false); // 画出下面矩形 g.fill3DRect(x, y+15, 30, 5, false); // 画出中间矩形 g.fill3DRect(x+5, y+5, 20, 10, false); // 画出圆形 g.fillOval(x+10, y+5, 10, 10); // 画出线 g.drawLine(x+15, y+10, x+30, y+10); break; case 2: // 画出下边坦克 g.fill3DRect(x, y, 5, 30, false); // 画出右边坦克 g.fill3DRect(x+15, y, 5, 30, false); // 画出中间矩形 g.fill3DRect(x+5, y+5, 10, 20, false); // 画出原形 g.fillOval(x+5, y+10, 10, 10); // 画出直线 g.drawLine(x+10, y+15, x+10, y+30); break; case 3: // 向左边 // 画出上面矩形 g.fill3DRect(x, y, 30, 5, false); // 画出下面矩形 g.fill3DRect(x, y+15, 30, 5, false); // 画出中间矩形 g.fill3DRect(x+5, y+5, 20, 10, false); // 画出圆形 g.fillOval(x+10, y+5, 10, 10); // 画出线 g.drawLine(x+15, y+10, x, y+10); break; } } //对键摁下做出处理啊 a向左 s 向下 d向右 w向上 @Override public void keyTyped(KeyEvent e) { // TODO Auto-generated method stub } @Override public void keyPressed(KeyEvent e) { // TODO Auto-generated method stub if(e.getKeyCode()==KeyEvent.VK_W) { this.hero.setDirect(0); this.hero.moveUP(); }else if(e.getKeyCode()==KeyEvent.VK_D) { this.hero.setDirect(1); this.hero.moveRight(); }else if(e.getKeyCode()==KeyEvent.VK_S) { this.hero.setDirect(2); this.hero.moveDown(); } else if(e.getKeyCode()==KeyEvent.VK_A) { this.hero.setDirect(3); this.hero.moveLeft(); } if(e.getKeyCode()==KeyEvent.VK_J) { // 判断玩家是否摁下J // 开火 if(this.hero.ss.size()<=4&&this.hero.isLive==true) { this.hero.shotEnemy(); } } // 必须重新绘制Panel this.repaint(); } @Override public void keyReleased(KeyEvent e) { // TODO Auto-generated method stub } public void run(){ while(true) { try{ Thread.sleep(100); }catch(Exception e) { e.printStackTrace(); } this.hitEnemyTank(); // 函数判断敌人的子弹是否击中我 this.hitMe(); this.repaint(); } } }
五、测试效果
黄色为玩家,击中玩家
敌人发射子弹
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。