前天不知道又在哪里见到这么一句话“谁能坚持超过50秒,我请他吃饭”(可以点击玩一玩原版游戏),其实这个不就是之前在同学QQ空间里面转来转去的一个网页游戏吗?什么“据说,美国空军的飞行员被强制达到2分钟。”于是,又来了一点点兴趣再玩一下下。游戏界面如下:

game01

其实前段时间自己也尝试用 Java 实现了这游戏,具体是按照自己对这游戏的简单理解使用 Java GUI 进行编程。电脑装了 JRE 的朋友可以下载附件直接双击 .bat 文件试玩一下哦!!!(*^__^*) 嘻嘻……

我的思路是这样子的(游戏中的方块用 JLabel 实现):

1、游戏中4个自动移动并且会慢慢加速的方块是碰到固定的“壁”就按照光的反射特性改变移动的方向,这个我们可以通过条件判断来改变方块的具体坐标 x 和 y 来实现;

2、自动移动则用 Java 线程来实现,具体是在线程类的 run() 方法中用死循环不断改变 x 和 y 坐标,然后不断地擦除、重绘即可,而碰壁之后反射的算法也是包含在这里的;

3、玩家能够通过按住鼠标左键来移动自己的方块,这个应该用 java.awt.event 包中的 MouseMotionAdapter 这个鼠标事件监听器类,Java 文档中的描述是“接收鼠标移动事件的抽象适配器类”,它包含 mouseDragged() 和 mouseMoved() 方法,可以监听鼠标时间按,具体的可以查阅一下该类;对于玩家控制的方块,要每时每刻都获取鼠标当前在面板上的坐标 x 、y ,以此为 JLabel 方块的中心进行不断擦除、重绘,也就达到了跟着鼠标移动的效果了;

4、判断游戏是否结束,就是遍历4个自动移动的 JLabel 方块,看它们在游戏面板中的覆盖面是否与玩家控制的 JLabel 方块有一点点重合,重合则说明相碰了,游戏结束;

5、关于游戏计时,这个可以用到 Java 线程中的 java.util.TimerTask 类来实现,不过这里我的游戏中没实现,偷懒了一下;

6、至于方块的移动速度,用一定的策略对 Thead.sleep() 方法中的睡眠时间进行减少即可。

 

我的游戏界面:

1、游戏开始状态

game02

2、游戏进行中

 

game03

3、碰壁了,游戏结束

game04

代码实现:

 GlobalVars.java

  1. //常量接口模式:把系统中相关的常量放在专门的常量接口中定义  
  2. public interface GlobalVars {  
  3.  
  4.     /**  
  5.      * 4个移动方向,L、R为左、右,U、D为上、下  
  6.      */ 
  7.     public static final int LD = -1;    //往左下方  
  8.     public static final int RD = 1;     //往右下方  
  9.     public static final int RU = 2;     //往右上方  
  10.     public static final int LU = -2;    //往左上方  
  11.       
  12.     /**  
  13.      * 游戏窗口大小  
  14.      */ 
  15.     public static final int FRAME_WIDTH = 500;  
  16.     public static final int FRAME_HEIGTH = 500;  
  17.       
  18.     /**  
  19.      * 面板大小  
  20.      */ 
  21.     public static final int PANEL_WIDTH = 400;  
  22.     public static final int PANEL_HEIGTH = 400;  
  23.       
  24.     /**  
  25.      * 玩家JLabel的大小  
  26.      */ 
  27.     public static final int PLAYER_WIDTH = 50;  
  28.     public static final int PLAYER_HEIGTH = 50;  

 ImpMove.java

  1. import javax.swing.JLabel;  
  2.  
  3. /**  
  4.  * 定义一个电脑方、玩家的JLabel都要实现的  
  5.  * 移动策略的抽象类 ImpMove ,该接口继承自   
  6.  * JLabel,所以可以获取 JLabel 类中常用 的方法  
  7.  * @author haolloyin  
  8.  */ 
  9. public abstract class ImpMove extends JLabel{  
  10.     // 移动  
  11.     protected abstract void move();  
  12.     // 开始  
  13.     protected abstract void begin();  

 ComputerPanel.java

  1. /**  
  2.  * 电脑控制的 JLabel 方块,继承自 ImpMove  
  3.  * 抽象类,必须实现其 move()和 begin()方法,  
  4.  * 它可以在游戏中存在多个具体实例,由玩家  
  5.  * 确定,它使用了线程因此能够自动移动  
  6.  * @author haolloyin  
  7.  */ 
  8. public class ComputerLabel extends ImpMove{  
  9.     /* 碰到壁必须反弹,这里4个常量用于  
  10.      * 判断电脑方块碰到那一面壁  
  11.      */ 
  12.     private static final int PL = -1;  
  13.     private static final int PR = 1;  
  14.     private static final int PU = 2;  
  15.     private static final int PD = -2;  
  16.  
  17.     private int xx;  
  18.     private int yy;  
  19.       
  20.     private int width;  
  21.     private int heigth;  
  22.  
  23.     private Point p;  
  24.     private Rectangle r;  
  25.  
  26.     private int direction;  //移动方向  
  27.     private int speed = 5;  //移动速度  
  28.     private Thread go;      //驱动其移动的线程实例  
  29.     private boolean isLive = true;  //游戏是否结束  
  30.  
  31.     public ComputerLabel(int x, int y, int w, int h, int d) {  
  32.         this.width = w;  
  33.         this.heigth = h;  
  34.         init(x, y, w, h, d);  
  35.     }  
  36.  
  37.     private void init(int x, int y, int w, int h, int direction) {  
  38.         setBounds(x, y, w, h);  
  39.         setOpaque(true);  
  40.         setBackground(Color.green);  
  41.  
  42.         r = getBounds();  
  43.         p = r.getLocation();  
  44.         this.direction = direction;  
  45.     }  
  46.       
  47.     /**  
  48.      *  实现了Runnable接口的私有内部类,用于驱动move()方法  
  49.      */ 
  50.     private class MoveAble implements Runnable {  
  51.         public void run() {  
  52.             while (isLive) {  
  53.                 try {  
  54.                     Thread.sleep(speed);  
  55.                 } catch (InterruptedException e) {  
  56.                     e.printStackTrace();  
  57.                 }  
  58. //              System.out.println("speed = " + speed);  
  59.                 move();  
  60.             }  
  61.         }  
  62.     }  
  63.       
  64.     /**  
  65.      * 实现ImpMove抽象类中的move()  
  66.      */ 
  67.     @Override 
  68.     protected void move() {  
  69.         isPengBi();  
  70.         p.x += xx;  
  71.         p.y += yy;  
  72.         setLocation(p.x, p.y);  
  73.     }  
  74.       
  75.     /**  
  76.      * 测试是否碰到壁,若是则调  
  77.      * 用changeDirection()改变移动方向  
  78.      */ 
  79.     private void isPengBi() {  
  80.         if (p.x < 0) {  
  81.             changeDirection(PL);  
  82.         } else if (p.x > GlobalVars.PANEL_WIDTH - width) {  
  83.             changeDirection(PR);  
  84.         } else if (p.y < 0) {  
  85.             changeDirection(PU);  
  86.         } else if (p.y > GlobalVars.PANEL_HEIGTH - heigth) {  
  87.             changeDirection(PD);  
  88.         }  
  89.     }  
  90.       
  91.     /**  
  92.      * 碰到壁则反弹,即改变移动方向  
  93.      */ 
  94.     private void changeDirection(int peng) {  
  95.         if (peng == PL && direction == LD) { // ↙碰左  
  96.             direction = RD;  
  97.             xx = 1;  
  98.             yy = 1;  
  99.         } else if (peng == PL && direction == LU) { // ↖碰左  
  100.             direction = RU;  
  101.             xx = 1;  
  102.             yy = -1;  
  103.         } else if (peng == PR && direction == RU) { // ↗碰右  
  104.             direction = LU;  
  105.             xx = -1;  
  106.             yy = -1;  
  107.         } else if (peng == PR && direction == RD) { // ↘碰右  
  108.             direction = LD;  
  109.             xx = -1;  
  110.             yy = 1;  
  111.         } else if (peng == PU && direction == RU) { // ↗碰上  
  112.             direction = RD;  
  113.             xx = 1;  
  114.             yy = 1;  
  115.         } else if (peng == PU && direction == LU) { // ↖碰上  
  116.             direction = LD;  
  117.             xx = -1;  
  118.             yy = 1;  
  119.         } else if (peng == PD && direction == LD) { // ↙碰下  
  120.             direction = LU;  
  121.             xx = -1;  
  122.             yy = -1;  
  123.         } else if (peng == PD && direction == RD) { // ↘碰下  
  124.             direction = RU;  
  125.             xx = 1;  
  126.             yy = -1;  
  127.         }  
  128.     }  
  129.       
  130.     /**  
  131.      * 游戏开始,启动线程  
  132.      */ 
  133.     @Override 
  134.     protected void begin() {  
  135.         go = new Thread(new MoveAble());  
  136.         toWhere();  
  137.         go.start();  
  138.     }  
  139.       
  140.     /**  
  141.      * 确定方块的移动方向  
  142.      */ 
  143.     private void toWhere() {  
  144.         if(direction == LD) {  
  145.             xx = -1;  
  146.             yy = 1;  
  147.         }else if(direction == LU) {  
  148.             xx = -1;  
  149.             yy = -1;  
  150.         }else if(direction == RD) {  
  151.             xx = 1;  
  152.             yy = 1;  
  153.         }else if(direction == RU) {  
  154.             xx = 1;  
  155.             yy = -1;  
  156.         }  
  157.     }  
  158.       
  159.     /**  
  160.      * 游戏是否结束  
  161.      */ 
  162.     public void isDead() {  
  163.         this.isLive = false;  
  164.     }  
  165.       
  166.     /**  
  167.      * 设置移动速度  
  168.      */ 
  169.     public void setSpeed(int speed) {  
  170.         this.speed = speed;  
  171.     }  

PlayerPanel.java

  1. /**  
  2.  * 供玩家控制的 JLabel 方块,它在整个游戏  
  3.  * 当中只会存在一个实例对象,继承自 ImpMove  
  4.  * 抽象类,必须实现其 move() 和 begin() 方法  
  5.  * @author haolloyin  
  6.  */ 
  7. public class PlayerLabel extends ImpMove{  
  8.  
  9.     private Rectangle r;    //方块的大小、位置  
  10.     private Point now;      //方块的坐标x、y  
  11.     private int x;          //原来的坐标 x  
  12.     private int y;          //原来的坐标 y  
  13.     private int max_x = PANEL_WIDTH - PLAYER_WIDTH;  
  14.     private int max_y = PANEL_HEIGTH - PLAYER_HEIGTH;  
  15.       
  16.     public PlayerLabel() {  
  17.         begin();  
  18.     }  
  19.       
  20.     /**  
  21.      * 重写begin()方法,玩家鼠标一单击中间的  
  22.      * JLabel 方块,则游戏开始  
  23.      */ 
  24.     @Override 
  25.     protected void begin() {  
  26.         setText("点击吧");  
  27.         setForeground(Color.black);  
  28.         setSize(PLAYER_WIDTH, PLAYER_HEIGTH);  
  29.         setLocation(150150);  
  30.         setOpaque(true);  
  31.         setBackground(Color.green);       
  32.         r = this.getBounds();  
  33.         now = new Point();  
  34.           
  35.         /**  
  36.          * 为当前JLabel对象添加接收鼠标移动事件的抽象适配器类,  
  37.          * 用于当按下鼠标时获取当前坐标并开始游戏  
  38.          */ 
  39.         addMouseMotionListener(new MouseMotionAdapter() {  
  40.             public void mouseDragged(MouseEvent e) {  
  41.                 x = e.getX();  
  42.                 y = e.getY();  
  43.                 move();  
  44.             }  
  45.         });  
  46.  
  47.         /**  
  48.          * 添加可以变换鼠标指针样式的事件监听器  
  49.          */ 
  50.         addMouseListener(new MouseAdapter() {  
  51.             public void mouseEntered(MouseEvent e) {  
  52.                 // 变成手型鼠标  
  53.                 setBackground(Color.yellow);  
  54.                 setCursor(new Cursor(Cursor.HAND_CURSOR));  
  55.             }  
  56.  
  57.             public void mouseExited(MouseEvent e) {  
  58.                 // 变成默认鼠标  
  59.                 setBackground(Color.green);  
  60.                 setCursor(new Cursor(Cursor.DEFAULT_CURSOR));  
  61.             }  
  62.         });  
  63.     }  
  64.       
  65.     /**  
  66.      * 游戏的主要算法:实现ImpMove抽象类中的move()  
  67.      */ 
  68.     @Override 
  69.     protected void move() {  
  70.         now = MouseInfo.getPointerInfo().getLocation();  
  71.         if (now.x % 10 > 4)  
  72.             now.x = now.x / 10 + 1;  
  73.         else 
  74.             now.x /= 10;  
  75.           
  76.         if (now.y % 10 > 4)  
  77.             now.y = now.y / 10 + 1;  
  78.         else 
  79.             now.y /= 10;  
  80.           
  81.         r.x += (x - now.x);  
  82.         r.y += (y - now.y);           
  83.           
  84.         /*   
  85.          * 如果玩家JLabel方块碰壁了,则保证其  
  86.          * 始终紧靠着壁,而不是结束游戏  
  87.          */ 
  88.         if (r.x <= 0)  
  89.             r.x = 0;  
  90.         if (r.y <= 0)  
  91.             r.y = 0;  
  92.         if(r.x > max_x)  
  93.             r.x = max_x;  
  94.         if(r.y > max_y)  
  95.             r.y = max_y;  
  96.           
  97.         now.x = x;  
  98.         now.y = y;                
  99.         setBackground(Color.cyan);  
  100.         setLocation(r.x, r.y);  
  101.     }  
  102.       
  103.     /**  
  104.      * 测试  
  105.      */ 
  106.     public static void main(String[] args) {  
  107.         JFrame jf = new JFrame();  
  108.           
  109.         PlayerLabel p1 = new PlayerLabel();  
  110.         jf.setLayout(null); //布局设置为null  
  111.         jf.add(p1);  
  112.         jf.setLocation(40050);  
  113.         jf.setSize(500500);  
  114.         jf.setVisible(true);  
  115.         jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
  116.     }  

GamePanel.java

  1. /**  
  2.  *  游戏的面板,所有JLabel对象都被添加  
  3.  *  到该面板,由其来具体控制游戏的运行,  
  4.  *  类似于中介者模式中的 Mediator  
  5.  * @author haolloyin  
  6.  */ 
  7. public class GamePanel extends JPanel{  
  8.  
  9.     // 保存自动移动的JLabel对象,即电脑控制的方块  
  10.     private LinkedList<ImpMove> labels;  
  11.  
  12.     // 游戏是否结束  
  13.     private boolean isLive = true;  
  14.       
  15.     // 玩家JLabel  
  16.     private PlayerLabel player;  
  17.       
  18.     //游戏进行中判断是否相碰的线程  
  19.     private Thread go;  
  20.  
  21.     public GamePanel() {  
  22.         setBounds(5030, PANEL_WIDTH, PANEL_HEIGTH);  
  23.         setBackground(Color.yellow);  
  24.         setLayout(null);  
  25.           
  26.         init();  
  27.     }  
  28.       
  29.     private void init() {         
  30.         labels = new LinkedList<ImpMove>();  
  31.         player = new PlayerLabel();  
  32.         this.add(player);  
  33.         player.addMouseListener(new MouseAdapter() {  
  34.             public void mousePressed(MouseEvent me) {  
  35.                 player.setText("我闪...");  
  36.                 startGame();  
  37.             }  
  38.         });  
  39.     }  
  40.       
  41.     private void startGame() {  
  42.         int num = labels.size();  
  43.         for (int i = 0; i < num; i++) {  
  44.             labels.get(i).begin();  
  45.         }         
  46.         if(this.go == null) {  
  47.             go = new Thread(new CheckGameIsOver());  
  48.             go.start();  
  49.         }  
  50.     }  
  51.       
  52.     private void isOver() {  
  53.         Rectangle r_player = player.getBounds();  
  54.         int num = labels.size();  
  55.         for (int i = 0; i < num; i++) {  
  56.             if (labels.get(i).getBounds().intersects(r_player)) {  
  57.                 System.out.println("Game Over ...");  
  58.                 System.out.println("你碰到第--> " + i + " <--个方块 !!!");  
  59.                 gameOver();  
  60.                 try {  
  61.                     Thread.sleep(2000);  
  62.                 } catch (InterruptedException e) {  
  63.                     e.printStackTrace();  
  64.                 }  
  65.                 System.exit(0);  
  66.             }  
  67.         }  
  68.     }  
  69.       
  70.     private void gameOver() {  
  71.         int num = labels.size();  
  72.         for (int i = 0; i < num; i++) {  
  73.             ComputerLabel l = (ComputerLabel)labels.get(i);  
  74.             l.isDead();  
  75.         }  
  76.     }  
  77.       
  78.     private class CheckGameIsOver implements Runnable {  
  79.         public void run() {  
  80.             while (isLive) {  
  81.                 isOver();  
  82.             }  
  83.         }  
  84.     }  
  85.       
  86.     public void addLabel(ImpMove label) {  
  87.         this.labels.add(label);  
  88.         this.add(label);  
  89.     }     

Game.java

  1. /**  
  2.  * 最终游戏测试类,其实就是初始化所有  
  3.  * JLabel 对象并启动游戏  
  4.  * @author haolloyin  
  5.  */ 
  6. public class Game extends JFrame {  
  7.  
  8.     public Game() {  
  9.         setTitle("躲避 游戏");  
  10.         setLayout(null);  
  11.         setBounds(300100, FRAME_WIDTH, FRAME_HEIGTH);  
  12.         setVisible(true);  
  13.         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
  14.     }  
  15.  
  16.     /*  
  17.      * 测试  
  18.      */ 
  19.     public static void main(String[] args) {  
  20.  
  21.         GamePanel gamePanel = new GamePanel();  
  22.  
  23.         ComputerLabel p1 = new ComputerLabel(3403203559, RU);  
  24.         ComputerLabel p2 = new ComputerLabel(13304020, LU);  
  25.         ComputerLabel p3 = new ComputerLabel(202006040, RD);  
  26.         ComputerLabel p4 = new ComputerLabel(350607060, LD);  
  27.         ComputerLabel p5 = new ComputerLabel(200201015, LD);  
  28.  
  29.         p1.setBackground(Color.black);  
  30.         p2.setBackground(Color.DARK_GRAY);  
  31.         p3.setBackground(Color.magenta);  
  32.         p4.setBackground(Color.red);  
  33.         p5.setBackground(Color.red);  
  34.  
  35.         p1.setSpeed(4);  
  36.         p2.setSpeed(5);  
  37.         p3.setSpeed(6);  
  38.         p4.setSpeed(3);  
  39.         p5.setSpeed(2);  
  40.  
  41.         gamePanel.addLabel(p1);  
  42.         gamePanel.addLabel(p2);  
  43.         gamePanel.addLabel(p3);  
  44.         gamePanel.addLabel(p4);  
  45.         gamePanel.addLabel(p5);  
  46.  
  47.         Game game = new Game();  
  48.         game.add(gamePanel);  
  49.     }  

小结:

1、各个类的设计、结构感觉很乱;

2、上面的一点还体现在关于游戏实体(即 JLabel 方块)所选取的数据结构很乱;

3、重要方法如 move() 的代码过多且杂,本来这是整个游戏的重点算法实现,却这样子糟糕,+_+;

4、代码中没有注释,所以刚才在理解自己的代码时花了点时间,%&gt;_&lt;%…嗯,以后这要注意了。