Java实现坦克大战(1990有木有勾起童年回忆)
2014-08-05 15:15
344 查看
笔者最近一个星期又抽时间学习了一点点Java多线程编程,结合swing绘图做了一个特别简单版的坦克大战游戏。
功能说明:
1.仅仅有一关;英雄坦克(自己)只有一条命--不过可以很好的扩展,只要把MyPanel中的SuperTank做成跟敌人坦克一样的Vector即可;
2.右侧的调速面板仅可以调整我军坦克速度;子弹速度目前不可面板自动调整;
3.敌军总共20辆坦克,同时显示的有3辆;击毁一辆会在左上角自动增加一辆,直到上限20辆;
4.目前敌军坦克的移动完全是随机的,没有加入敌军向目标攻击的概率函数控制(后续优化);
代码说明:
1.我本机是编写了三个*.java文件,当然拷贝后,完全可以直接在一个文件中粘贴并编译;
游戏截图:
功能说明:
1.仅仅有一关;英雄坦克(自己)只有一条命--不过可以很好的扩展,只要把MyPanel中的SuperTank做成跟敌人坦克一样的Vector即可;
2.右侧的调速面板仅可以调整我军坦克速度;子弹速度目前不可面板自动调整;
3.敌军总共20辆坦克,同时显示的有3辆;击毁一辆会在左上角自动增加一辆,直到上限20辆;
4.目前敌军坦克的移动完全是随机的,没有加入敌军向目标攻击的概率函数控制(后续优化);
代码说明:
1.我本机是编写了三个*.java文件,当然拷贝后,完全可以直接在一个文件中粘贴并编译;
游戏截图:
/** * 坦克大战 * 1.Created by Light on 2014-8-1 画坦克 * 2.Modified by Light on 2014-8-1 让坦克按照键盘的方向键实现移动 * 3.Modified by Light on 2014-8-1 让坦克发子弹,并且让子弹飞 * 4.Modified by Light on 2014-8-4 让敌人坦克自由行动,让子弹打到坦克时,坦克和子弹都消失,让坦克少于三个时自动增加 * 5.Modified by Light on 2014-8-4 优化敌军坦克行动代码和让敌军坦克发射子弹 * 6.Modified by Light on 2014-8-5 让敌军的坦克发射子弹也能击毁英雄坦克 */ package com.firstversion; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.*; public class TankGame extends JFrame implements ActionListener{ /** * 不知道这是干什么用的,后续补充 */ private static final long serialVersionUID = 1L; MyPanel mp ; oprationPanel mep; JSplitPane jsplit; JButton friend, enemy,speedUp,speedDown; JLabel speedArea; //主函数 public static void main(String[] args) { // TODO Auto-generated method stub new TankGame(); } //坦克游戏的构造函数 public TankGame() { mp = new MyPanel(); mep = new oprationPanel(); jsplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); //暂时把分隔窗口收起功能注释掉,免的窗口发生变化导致坦克开出边界 //jsplit.setOneTouchExpandable(true); friend = new JButton("友军"); friend.addActionListener(mp); friend.setActionCommand("friend"); friend.addKeyListener(mp); //http://blog.csdn.net/hn1232/article/details/4224863 //因在另外一个Panel上添加button导致键盘监听失效,参考上面的博文将每个Button都加了键盘监听,功能恢复了,但是现在不明白原理 enemy = new JButton("敌军"); enemy.addActionListener(mp); enemy.setActionCommand("enemy"); enemy.addKeyListener(mp); speedUp = new JButton("加速"); speedUp.addActionListener(this); speedUp.addActionListener(mp); speedUp.setActionCommand("speedUp"); speedUp.addKeyListener(mp); speedDown = new JButton("减速"); speedDown.addActionListener(this); speedDown.addActionListener(mp); speedDown.setActionCommand("speedDown"); speedDown.addKeyListener(mp); speedArea = new JLabel(String.valueOf(mp.st.getSpeed())); //Layout mep.add(friend); mep.add(enemy); mep.add(speedUp); mep.add(speedDown); mep.add(speedArea); jsplit.setLeftComponent(mp); jsplit.setRightComponent(mep); this.add(jsplit); //Listener set this.addKeyListener(mp); //启动MyPanel Thread t = new Thread(mp); t.start(); //设置JFrame this.setSize(600, 450); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setLocation(350, 180); this.setVisible(true); jsplit.setDividerLocation(0.88); } //写一个方法用于刷新speedArea public void refreshSpeed() { speedArea.setText(String.valueOf(this.mp.st.getSpeed())); } @Override public void actionPerformed(ActionEvent e) { // TODO Auto-generated method stub this.refreshSpeed(); this.repaint(); } } package com.firstversion; import java.awt.Color; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.*; import javax.swing.JPanel; import java.lang.Thread; //个人Panel,用于重写paint方便自定义 class MyPanel extends JPanel implements KeyListener,ActionListener,Runnable { /** * 暂时不清楚这个是干什么用的 */ private static final long serialVersionUID = 1L; //英雄坦克 SuperTank st = null; //敌军坦克,使用Vector来装敌军坦克群 int enemyNum = 3; Vector<EnemyTank> enemyTanks = null; //个人Panel构造方法 public MyPanel() { //初始化英雄坦克-->位置在底层中央,方向朝上 st = new SuperTank(250,350); //初始化敌人坦克,并为每个坦克启动一个线程-->在上层,方向朝下 enemyTanks = new Vector<EnemyTank>(); for(int i=0;i<enemyNum;i++) { //初始化敌军坦克 EnemyTank et = new EnemyTank(i*100,0); //初始化敌军坦克的类型和方向 et.setDirection(3); et.setTankType(1); //将敌军坦克装载Vector中管理 enemyTanks.addElement(et); //System.out.println("装了"+(i+1)+"个"); //System.out.println("总共"+(enemyTanks.size())+"个"); Thread t = new Thread(this.enemyTanks.get(i)); t.start(); } } //重写paint方法 public void paint(Graphics g) { super.paint(g); //设置一下背景颜色 g.fillRect(0, 0, 600, 450); //画英雄坦克 if(this.st.isLive()) { this.createTank(this.st.getX(),this.st.getY(),g,this.st.getTankType(),this.st.getDirection()); } //画英雄坦克子弹,要先判断子弹是否存在(子弹集是否为空)并且是否存活 if(!(this.st.bulletVector.isEmpty()))//子弹集不为空 { for(int i=0;i<this.st.bulletVector.size();i++)//遍历子弹集 { Bullet b = this.st.bulletVector.get(i); if(b.isLive())//子弹为存活状态 { //画子弹,x和y是取得子弹当前的最新坐标参照点 g.fillOval(b.getX(), b.getY(), 4, 4); } } } //画敌军坦克 for for(int i=0;i<enemyTanks.size();i++) { //System.out.println("画了"+(i+1)+"个"); //System.out.println("总共"+(enemyTanks.size())+"个"); EnemyTank et = this.enemyTanks.get(i); if(et.isLive()) { this.createTank(et.getX(),et.getY(),g,et.getTankType(),et.getDirection()); } //System.out.println("画了"+(i+1)+"个"); //画敌军坦克子弹 for(int j=0;j<et.bulletVector.size();j++) { Bullet b = et.bulletVector.get(j); if(b.isLive()) { //画子弹,x和y是取得子弹当前的最新坐标参照点 g.fillOval(b.getX(), b.getY(), 4, 4); } } } } //专门写一个方法来创建坦克-->参数:初始横坐标/初始纵坐标/画笔/坦克类型/坦克方向 public void createTank(int x,int y,Graphics g,int tankType, int direction) { //先根据tankType判断坦克类型 switch(tankType) {//0-->自己; 1--> 敌军 case 0: g.setColor(Color.YELLOW); break; //暂时使用颜色来标识tankType case 1: g.setColor(Color.CYAN); break; } //接着根据direction判断坦克朝向,因为坦克朝不同的方向画法可能不一样,比如炮筒的画法和旗帜的画法肯定不一样 switch(direction) {//0-->right; 1-->left; 2-->up; 3-->down; case 0: //认为是朝右 //画两条履带 g.fill3DRect(x, y, 60, 10, false); g.fill3DRect(x, y+30, 60, 10, false); //画坦克体 g.fill3DRect(x+10, y+10, 30, 20, false); //画盖子 g.fillOval(x+18, y+13, 14, 14); //画炮筒+炮筒头(略粗一点点) g.drawLine(x+25, y+20, x+60, y+20); g.drawLine(x+55, y+19, x+60, y+19); g.drawLine(x+55, y+21, x+60, y+21); break; case 1://朝左-->由朝右掉头 //画两条履带 g.fill3DRect(x, y, 60, 10, false); g.fill3DRect(x, y+30, 60, 10, false); //画坦克体 g.fill3DRect(x+20, y+10, 30, 20, false); //画盖子 g.fillOval(x+28, y+13, 14, 14); //画炮筒+炮筒头(略粗一点点) g.drawLine(x+35, y+20, x, y+20); g.drawLine(x+5, y+19, x, y+19); g.drawLine(x+5, y+21, x, y+21); break; case 2://朝上-->由朝右转头 //画两条履带 g.fill3DRect(x, y, 10, 60, false); g.fill3DRect(x+30, y, 10, 60, false); //画坦克体 g.fill3DRect(x+10, y+20, 20, 30, false); //画盖子 g.fillOval(x+13, y+28, 14, 14); //画炮筒+炮筒头(略粗一点点) g.drawLine(x+20, y, x+20, y+35); g.drawLine(x+19, y, x+19, y+5); g.drawLine(x+21, y, x+21, y+5); break; case 3://朝下-->由朝上掉头 //画两条履带 g.fill3DRect(x, y, 10, 60, false); g.fill3DRect(x+30, y, 10, 60, false); //画坦克体 g.fill3DRect(x+10, y+10, 20, 30, false); //画盖子 g.fillOval(x+13, y+18, 14, 14); //画炮筒+炮筒头(略粗一点点) g.drawLine(x+20, y+10, x+20, y+60); g.drawLine(x+19, y+55, x+19, y+60); g.drawLine(x+21, y+55, x+21, y+60); break; } } //写一个方法或者当前Panel中存活的敌军坦克数 public int getAliveTanks() { int aliveTankNum = 0; for(int i=0;i<this.enemyTanks.size();i++) { EnemyTank et = this.enemyTanks.get(i); if(et.isLive()) { aliveTankNum++; } } return aliveTankNum ; } //写一个方法判断面板中的子弹是否与坦克遭遇,遭遇则将坦克置为isLive = false public void gotTarget(Bullet b,EnemyTank et) { //一定要判断子弹和坦克是否存活,不然会碰到幽灵坦克和幽灵子弹 if(b.isLive() && et.isLive()) { //需要根据坦克的方向来判断 switch(et.getDirection()) { case 0: case 1: if(b.getX()>et.getX() && b.getX() < et.getX()+56 && b.getY()>et.getY() && b.getY()<et.getY()+36) { b.setLive(false);//将子弹置为死亡 et.setLive(false);//将坦克置为死亡 } break; case 2: case 3: if(b.getX()>et.getX() && b.getX()<et.getX()+36 && b.getY()>et.getY() && b.getY()<et.getY()+56) { b.setLive(false);//将子弹置为死亡 et.setLive(false);//将坦克置为死亡 } break; } } } //写一个方法判断英雄坦克死亡 public void isSuperDead(Bullet b,SuperTank st) { //先判断敌军子弹是否存活 if(b.isLive() && st.isLive()) { //需要根据坦克的方向来判断 switch(st.getDirection()) { case 0: case 1: if(b.getX()>st.getX() && b.getX() < st.getX()+56 && b.getY()>st.getY() && b.getY()<st.getY()+36) { b.setLive(false);//将子弹置为死亡 st.setLive(false);//将坦克置为死亡 } break; case 2: case 3: if(b.getX()>st.getX() && b.getX()<st.getX()+36 && b.getY()>st.getY() && b.getY()<st.getY()+56) { b.setLive(false);//将子弹置为死亡 st.setLive(false);//将坦克置为死亡 } break; } } } //写一个方法加入坦克 public void addEnemyTank() { EnemyTank et = new EnemyTank(0,0); et.setDirection(3); et.setTankType(1); //一定记得把坦克加入到Vector中,不然尼玛取不到,也画不出来 this.enemyTanks.add(et); Thread t = new Thread(et); t.start(); } @Override //重写监听方法 public void actionPerformed(ActionEvent e) { // TODO Auto-generated method stub if(e.getActionCommand().equals("friend")) { this.st.setTankType(0); }else if(e.getActionCommand().equals("enemy")) { this.st.setTankType(1); }else if(e.getActionCommand().equals("speedUp")) { this.st.setSpeed(this.st.getSpeed()+1); }else if(e.getActionCommand().equals("speedDown")) { if((this.st.getSpeed()-1)>0) { this.st.setSpeed(this.st.getSpeed()-1); }else{ this.st.setSpeed(1); } }else{ System.out.println("真神奇,你竟然能让程序跑到这个逻辑来!"); } this.repaint(); } @Override public void keyPressed(KeyEvent e) { // TODO Auto-generated method stub if(e.getKeyCode()==KeyEvent.VK_S) { this.st.setDirection(3);//控制方向 this.st.move(this.st.getDirection()); }else if(e.getKeyCode()==KeyEvent.VK_W) { this.st.setDirection(2); this.st.move(this.st.getDirection()); }else if(e.getKeyCode()==KeyEvent.VK_A) { this.st.setDirection(1); this.st.move(this.st.getDirection()); }else if(e.getKeyCode()==KeyEvent.VK_D) { this.st.setDirection(0); this.st.move(this.st.getDirection()); } //判断发射子弹 if(e.getKeyCode()==KeyEvent.VK_J) { this.st.fair(); } this.repaint(); } @Override//释放键盘 public void keyReleased(KeyEvent ke) { // TODO Auto-generated method stub } @Override//打印字符 public void keyTyped(KeyEvent ke) { // TODO Auto-generated method stub } @Override public void run() { // 重写run()方法,刷新整个MyPanel while(true) { try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } //System.out.println("刷新了一次!");//测试使用 //判断子弹是否击中敌军坦克 for(int i=0;i<this.st.bulletVector.size();i++) { Bullet b = this.st.bulletVector.get(i); for(int j=0;j<this.enemyTanks.size();j++) { EnemyTank et = this.enemyTanks.get(j); this.gotTarget(b, et); } } //判断英雄坦克是否死亡-->对每辆坦克的每颗子弹进行判断 for(int i=0;i<this.enemyTanks.size();i++) { if(this.enemyTanks.get(i).isLive()) { EnemyTank et = this.enemyTanks.get(i); for(int j=0;j<et.bulletVector.size();j++) { if(et.bulletVector.get(j).isLive()) { Bullet b = et.bulletVector.get(j); this.isSuperDead(b, this.st); } } } } //判断敌军坦克是否需要增加,少于三个存活的坦克则增加到三个,总数达到20个则不再增加 if(this.getAliveTanks()<3 && this.enemyTanks.size()<20) { this.addEnemyTank(); } //敌军射击 for(int j=0;j<this.enemyTanks.size();j++) { if(this.enemyTanks.get(j).isLive()) { this.enemyTanks.get(j).fair(); } } //重新调用repaint() this.repaint(); } } } //操作面板类 class oprationPanel extends JPanel { /** * */ private static final long serialVersionUID = 1L; public oprationPanel() { } } package com.firstversion; import java.util.*; class Tank { private int x=100;//坦克初始位置的横坐标 private int y=100;//坦克初始位置的纵坐标 private int direction=2; //坦克的朝向 private int speed=4;//坦克的速度 private int tankType=0;//坦克类型 private boolean isLive = true ;//坦克默认存活 Vector<Bullet> bulletVector = null;//使用Vector是为了使每个打出去的子弹都是一个线程,同时每个坦克可以打出多个子弹 //成员属性访问和设置方法群 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 getDirection() { return direction; } public void setDirection(int direction) { this.direction = direction; } public int getSpeed() { return speed; } public void setSpeed(int speed) { this.speed = speed; } public int getTankType() { return tankType; } public void setTankType(int tankType) { this.tankType = tankType; } public boolean isLive() { return isLive; } public void setLive(boolean isLive) { this.isLive = isLive; } //坦克构造函数,很重要,对于需要在其他位置调用的成员属性,在此都需要初始化,不然会报空指针 public Tank(int x, int y) { this.x=x; this.y=y; //这句话初始化Vector一定要有的,不然会在MyPanel中调用出现空指针错误 bulletVector = new Vector<Bullet>(); } //坦克移动的能力方法,以方向为入参 public void move(int direction) {//0-->right; 1-->left; 2-->up; 3-->down; switch(direction) { case 0: if(x+speed<455) x+=speed; break; case 1: if(x-speed>0) x-=speed; break; case 2: if(y-speed>0) y-=speed; break; case 3: if(y+speed<362) y+=speed; break; default: break; } //System.out.println("跑了一次!"); } //判断当前坦克存活子弹数 public int getAliveBullets() { int aliveBulletNum =0 ; for(int i=0;i<this.bulletVector.size();i++) { Bullet b = this.bulletVector.get(i); if(b.isLive()) { aliveBulletNum++; } } return aliveBulletNum; } } class SuperTank extends Tank { public SuperTank(int x,int y) { super(x,y);//使用父类的能够匹配(参数个数与类型完全一致)的构造函数 } //fair public void fair() {//每次开火就产生一个子弹,也就是子弹类的实例对象,加入到bulletVector中的最后一位,再从最后一个取取出来启动线程 if(this.getAliveBullets()<3) { switch(this.getDirection()) { case 0: bulletVector.addElement(new Bullet(this.getX()+60,this.getY()+18,this.getDirection())); break; case 1: bulletVector.addElement(new Bullet(this.getX()-4,this.getY()+18,this.getDirection())); break; case 2: bulletVector.addElement(new Bullet(this.getX()+18,this.getY()-4,this.getDirection())); break; case 3: bulletVector.addElement(new Bullet(this.getX()+18,this.getY()+60,this.getDirection()));break; } //在每次发射动作之后,就要启动这个线程 Thread t = new Thread(bulletVector.lastElement()); t.start(); } } } //敌军坦克类,因为敌军坦克需要自己走动,所以实现Runnable接口,另每一个敌军坦克对象作为一个线程 class EnemyTank extends Tank implements Runnable { private boolean isMoving = false ; private int fairController = 25 ; public int getFairController() { return fairController; } public void setFairController(int fairController) { this.fairController = fairController; } public boolean isMoving() { return isMoving; } public void setMoving(boolean isMoving) { this.isMoving = isMoving; } public EnemyTank(int x, int y) { super(x, y); } //fair automatically -- fair controller //fair public void fair() {//每次开火就产生一个子弹,也就是子弹类的实例对象,加入到bulletVector中的最后一位,再从最后一个取取出来启动线程 if(this.fairController>0) { this.fairController--; //System.out.println("不发射子弹"); }else { //System.out.println("发射子弹"); switch(this.getDirection()) { case 0: bulletVector.addElement(new Bullet(this.getX()+60,this.getY()+18,this.getDirection())); break; case 1: bulletVector.addElement(new Bullet(this.getX()-4,this.getY()+18,this.getDirection())); break; case 2: bulletVector.addElement(new Bullet(this.getX()+18,this.getY()-4,this.getDirection())); break; case 3: bulletVector.addElement(new Bullet(this.getX()+18,this.getY()+60,this.getDirection()));break; } //在每次发射动作之后,就要启动这个线程 Thread t = new Thread(bulletVector.lastElement()); t.start(); this.setFairController(25); } } @Override public void run() { // TODO Auto-generated method stub int i =500; int j =3; while(true) { //休眠 try { Thread.sleep(i); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } //随机秒 i = (int)(Math.random()*5000); //开始计时毫秒级时间 Date d = new Date(); long longtime = d.getTime(); //测试打印代码 //System.out.println(longtime); //System.out.println(longtime+i); //System.out.println((new Date()).getTime()); //随机方向 j = ((int)(Math.random()*100))%4; //一定要按照随机的方向给敌军坦克设置方向,不能只定义移动方向,不然你会看到坦克横着开 this.setDirection(j); //休眠结束时,将坦克置为移动状态 this.setMoving(true); //在随机数期间保持运动 while(true) { try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } Date d2 = new Date(); //System.out.println("d2:"+d2.getTime()); if(longtime+i>d2.getTime()) { //调用移动方法 this.move(j); }else{ //停止时发射一个子弹 this.fair(); break; } } //退出循环时,将坦克置为静止 this.setMoving(false); //在坦克静止时,每隔2~3s发射一颗子弹 //判断线程退出 if(this.isLive()==false) { break; } } } } //子弹类,作为线程处理 class Bullet implements Runnable { private int x; private int y; private int speed = 5;//妈的这个地方坑死老子了,打了子弹就是不走,最后找到初始速度尼玛默认为0了 private int direction; private boolean isLive = 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 getSpeed() { return speed; } public void setSpeed(int speed) { this.speed = speed; } public int getDirection() { return direction; } public void setDirection(int direction) { this.direction = direction; } public boolean isLive() { return isLive; } public void setLive(boolean isLive) { this.isLive = isLive; } //Bullet构造方法 public Bullet(int x, int y, int direction) { this.x=x; this.y=y; this.direction=direction; } @Override//重写run()方法 public void run(){ //在fair()的方法中,会调用start(),进而似的run()得以执行,所以在run()中需要包含当前子弹线程结束的逻辑判断 //需要进行当前子弹的方向的判断 while(true) { try { Thread.sleep(100);//每100毫秒变化一次初始位置,这样在MyPanel中进行repaint()就可以实现让子弹飞了 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } switch(this.direction) { case 0: x+=this.speed; break; case 1: x-=this.speed; break; case 2: y-=this.speed; break; case 3: y+=this.speed; break; } //System.out.println("子弹坐标:["+this.x+","+this.y+"]");//测试使用 //判断子弹到边界自动退出线程 if(x<0||y<0||x>600||y>450) { this.setLive(false);//将子弹状态置为非激活,作为后面在画子弹时需要判断的条件 break; //这里不知道怎么在退出线程时,清除Vector中已经消亡的子弹;初步考虑应该在外部按照时间和子弹状态来清除子弹 }else if(this.isLive==false){ break; } } } }
相关文章推荐
- 回忆童年的电子游戏 俄罗斯方块的Java代码
- 射击类小游戏——坦克大战(java实现)
- 童年乡下野果,野花勾起的回忆
- java实现坦克大战3.1版
- java实现的坦克大战
- Java实现坦克大战【升级版】
- java swing实现坦克大战小游戏源码
- 《Java小游戏实现》:坦克大战(续四)
- 用java实现的坦克大战
- 《Java小游戏实现》:坦克大战(续一)
- 如何在Java应用程序中实现copy图像功能。
- 利用Java实现网络通信
- 用JAVA实现Undo、Redo,Copy、Paste、Cut
- 用Java实现的设计模式系列(2)-Prototype
- http断点续传简单实现(java)
- 用 Java 实现回调例程
- 用Java实现的设计模式系列(3)--Builder
- 使用 .NET实现JavaTM Pet Store J2EETM 蓝图应用程序
- 在Java程序,如何实现自适应位置的快捷菜单
- 用Java实现的设计模式系列(1)-Factory