前天不知道又在哪里见到这么一句话“谁能坚持超过50秒,我请他吃饭”(可以点击玩一玩原版游戏),其实这个不就是之前在同学QQ空间里面转来转去的一个网页游戏吗?什么“据说,美国空军的飞行员被强制达到2分钟。”于是,又来了一点点兴趣再玩一下下。游戏界面如下:
其实前段时间自己也尝试用 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、游戏开始状态
2、游戏进行中
3、碰壁了,游戏结束
代码实现:
GlobalVars.java
- //常量接口模式:把系统中相关的常量放在专门的常量接口中定义
- public interface GlobalVars {
- /**
- * 4个移动方向,L、R为左、右,U、D为上、下
- */
- public static final int LD = -1; //往左下方
- public static final int RD = 1; //往右下方
- public static final int RU = 2; //往右上方
- public static final int LU = -2; //往左上方
- /**
- * 游戏窗口大小
- */
- public static final int FRAME_WIDTH = 500;
- public static final int FRAME_HEIGTH = 500;
- /**
- * 面板大小
- */
- public static final int PANEL_WIDTH = 400;
- public static final int PANEL_HEIGTH = 400;
- /**
- * 玩家JLabel的大小
- */
- public static final int PLAYER_WIDTH = 50;
- public static final int PLAYER_HEIGTH = 50;
- }
ImpMove.java
- import javax.swing.JLabel;
- /**
- * 定义一个电脑方、玩家的JLabel都要实现的
- * 移动策略的抽象类 ImpMove ,该接口继承自
- * JLabel,所以可以获取 JLabel 类中常用 的方法
- * @author haolloyin
- */
- public abstract class ImpMove extends JLabel{
- // 移动
- protected abstract void move();
- // 开始
- protected abstract void begin();
- }
ComputerPanel.java
- /**
- * 电脑控制的 JLabel 方块,继承自 ImpMove
- * 抽象类,必须实现其 move()和 begin()方法,
- * 它可以在游戏中存在多个具体实例,由玩家
- * 确定,它使用了线程因此能够自动移动
- * @author haolloyin
- */
- public class ComputerLabel extends ImpMove{
- /* 碰到壁必须反弹,这里4个常量用于
- * 判断电脑方块碰到那一面壁
- */
- private static final int PL = -1;
- private static final int PR = 1;
- private static final int PU = 2;
- private static final int PD = -2;
- private int xx;
- private int yy;
- private int width;
- private int heigth;
- private Point p;
- private Rectangle r;
- private int direction; //移动方向
- private int speed = 5; //移动速度
- private Thread go; //驱动其移动的线程实例
- private boolean isLive = true; //游戏是否结束
- public ComputerLabel(int x, int y, int w, int h, int d) {
- this.width = w;
- this.heigth = h;
- init(x, y, w, h, d);
- }
- private void init(int x, int y, int w, int h, int direction) {
- setBounds(x, y, w, h);
- setOpaque(true);
- setBackground(Color.green);
- r = getBounds();
- p = r.getLocation();
- this.direction = direction;
- }
- /**
- * 实现了Runnable接口的私有内部类,用于驱动move()方法
- */
- private class MoveAble implements Runnable {
- public void run() {
- while (isLive) {
- try {
- Thread.sleep(speed);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- // System.out.println("speed = " + speed);
- move();
- }
- }
- }
- /**
- * 实现ImpMove抽象类中的move()
- */
- @Override
- protected void move() {
- isPengBi();
- p.x += xx;
- p.y += yy;
- setLocation(p.x, p.y);
- }
- /**
- * 测试是否碰到壁,若是则调
- * 用changeDirection()改变移动方向
- */
- private void isPengBi() {
- if (p.x < 0) {
- changeDirection(PL);
- } else if (p.x > GlobalVars.PANEL_WIDTH - width) {
- changeDirection(PR);
- } else if (p.y < 0) {
- changeDirection(PU);
- } else if (p.y > GlobalVars.PANEL_HEIGTH - heigth) {
- changeDirection(PD);
- }
- }
- /**
- * 碰到壁则反弹,即改变移动方向
- */
- private void changeDirection(int peng) {
- if (peng == PL && direction == LD) { // ↙碰左
- direction = RD;
- xx = 1;
- yy = 1;
- } else if (peng == PL && direction == LU) { // ↖碰左
- direction = RU;
- xx = 1;
- yy = -1;
- } else if (peng == PR && direction == RU) { // ↗碰右
- direction = LU;
- xx = -1;
- yy = -1;
- } else if (peng == PR && direction == RD) { // ↘碰右
- direction = LD;
- xx = -1;
- yy = 1;
- } else if (peng == PU && direction == RU) { // ↗碰上
- direction = RD;
- xx = 1;
- yy = 1;
- } else if (peng == PU && direction == LU) { // ↖碰上
- direction = LD;
- xx = -1;
- yy = 1;
- } else if (peng == PD && direction == LD) { // ↙碰下
- direction = LU;
- xx = -1;
- yy = -1;
- } else if (peng == PD && direction == RD) { // ↘碰下
- direction = RU;
- xx = 1;
- yy = -1;
- }
- }
- /**
- * 游戏开始,启动线程
- */
- @Override
- protected void begin() {
- go = new Thread(new MoveAble());
- toWhere();
- go.start();
- }
- /**
- * 确定方块的移动方向
- */
- private void toWhere() {
- if(direction == LD) {
- xx = -1;
- yy = 1;
- }else if(direction == LU) {
- xx = -1;
- yy = -1;
- }else if(direction == RD) {
- xx = 1;
- yy = 1;
- }else if(direction == RU) {
- xx = 1;
- yy = -1;
- }
- }
- /**
- * 游戏是否结束
- */
- public void isDead() {
- this.isLive = false;
- }
- /**
- * 设置移动速度
- */
- public void setSpeed(int speed) {
- this.speed = speed;
- }
- }
PlayerPanel.java
- /**
- * 供玩家控制的 JLabel 方块,它在整个游戏
- * 当中只会存在一个实例对象,继承自 ImpMove
- * 抽象类,必须实现其 move() 和 begin() 方法
- * @author haolloyin
- */
- public class PlayerLabel extends ImpMove{
- private Rectangle r; //方块的大小、位置
- private Point now; //方块的坐标x、y
- private int x; //原来的坐标 x
- private int y; //原来的坐标 y
- private int max_x = PANEL_WIDTH - PLAYER_WIDTH;
- private int max_y = PANEL_HEIGTH - PLAYER_HEIGTH;
- public PlayerLabel() {
- begin();
- }
- /**
- * 重写begin()方法,玩家鼠标一单击中间的
- * JLabel 方块,则游戏开始
- */
- @Override
- protected void begin() {
- setText("点击吧");
- setForeground(Color.black);
- setSize(PLAYER_WIDTH, PLAYER_HEIGTH);
- setLocation(150, 150);
- setOpaque(true);
- setBackground(Color.green);
- r = this.getBounds();
- now = new Point();
- /**
- * 为当前JLabel对象添加接收鼠标移动事件的抽象适配器类,
- * 用于当按下鼠标时获取当前坐标并开始游戏
- */
- addMouseMotionListener(new MouseMotionAdapter() {
- public void mouseDragged(MouseEvent e) {
- x = e.getX();
- y = e.getY();
- move();
- }
- });
- /**
- * 添加可以变换鼠标指针样式的事件监听器
- */
- addMouseListener(new MouseAdapter() {
- public void mouseEntered(MouseEvent e) {
- // 变成手型鼠标
- setBackground(Color.yellow);
- setCursor(new Cursor(Cursor.HAND_CURSOR));
- }
- public void mouseExited(MouseEvent e) {
- // 变成默认鼠标
- setBackground(Color.green);
- setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
- }
- });
- }
- /**
- * 游戏的主要算法:实现ImpMove抽象类中的move()
- */
- @Override
- protected void move() {
- now = MouseInfo.getPointerInfo().getLocation();
- if (now.x % 10 > 4)
- now.x = now.x / 10 + 1;
- else
- now.x /= 10;
- if (now.y % 10 > 4)
- now.y = now.y / 10 + 1;
- else
- now.y /= 10;
- r.x += (x - now.x);
- r.y += (y - now.y);
- /*
- * 如果玩家JLabel方块碰壁了,则保证其
- * 始终紧靠着壁,而不是结束游戏
- */
- if (r.x <= 0)
- r.x = 0;
- if (r.y <= 0)
- r.y = 0;
- if(r.x > max_x)
- r.x = max_x;
- if(r.y > max_y)
- r.y = max_y;
- now.x = x;
- now.y = y;
- setBackground(Color.cyan);
- setLocation(r.x, r.y);
- }
- /**
- * 测试
- */
- public static void main(String[] args) {
- JFrame jf = new JFrame();
- PlayerLabel p1 = new PlayerLabel();
- jf.setLayout(null); //布局设置为null
- jf.add(p1);
- jf.setLocation(400, 50);
- jf.setSize(500, 500);
- jf.setVisible(true);
- jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- }
- }
GamePanel.java
- /**
- * 游戏的面板,所有JLabel对象都被添加
- * 到该面板,由其来具体控制游戏的运行,
- * 类似于中介者模式中的 Mediator
- * @author haolloyin
- */
- public class GamePanel extends JPanel{
- // 保存自动移动的JLabel对象,即电脑控制的方块
- private LinkedList
labels; - // 游戏是否结束
- private boolean isLive = true;
- // 玩家JLabel
- private PlayerLabel player;
- //游戏进行中判断是否相碰的线程
- private Thread go;
- public GamePanel() {
- setBounds(50, 30, PANEL_WIDTH, PANEL_HEIGTH);
- setBackground(Color.yellow);
- setLayout(null);
- init();
- }
- private void init() {
- labels = new LinkedList
(); - player = new PlayerLabel();
- this.add(player);
- player.addMouseListener(new MouseAdapter() {
- public void mousePressed(MouseEvent me) {
- player.setText("我闪...");
- startGame();
- }
- });
- }
- private void startGame() {
- int num = labels.size();
- for (int i = 0; i < num; i++) {
- labels.get(i).begin();
- }
- if(this.go == null) {
- go = new Thread(new CheckGameIsOver());
- go.start();
- }
- }
- private void isOver() {
- Rectangle r_player = player.getBounds();
- int num = labels.size();
- for (int i = 0; i < num; i++) {
- if (labels.get(i).getBounds().intersects(r_player)) {
- System.out.println("Game Over ...");
- System.out.println("你碰到第--> " + i + " <--个方块 !!!");
- gameOver();
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.exit(0);
- }
- }
- }
- private void gameOver() {
- int num = labels.size();
- for (int i = 0; i < num; i++) {
- ComputerLabel l = (ComputerLabel)labels.get(i);
- l.isDead();
- }
- }
- private class CheckGameIsOver implements Runnable {
- public void run() {
- while (isLive) {
- isOver();
- }
- }
- }
- public void addLabel(ImpMove label) {
- this.labels.add(label);
- this.add(label);
- }
- }
Game.java
- /**
- * 最终游戏测试类,其实就是初始化所有
- * JLabel 对象并启动游戏
- * @author haolloyin
- */
- public class Game extends JFrame {
- public Game() {
- setTitle("躲避 游戏");
- setLayout(null);
- setBounds(300, 100, FRAME_WIDTH, FRAME_HEIGTH);
- setVisible(true);
- setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- }
- /*
- * 测试
- */
- public static void main(String[] args) {
- GamePanel gamePanel = new GamePanel();
- ComputerLabel p1 = new ComputerLabel(340, 320, 35, 59, RU);
- ComputerLabel p2 = new ComputerLabel(13, 30, 40, 20, LU);
- ComputerLabel p3 = new ComputerLabel(20, 200, 60, 40, RD);
- ComputerLabel p4 = new ComputerLabel(350, 60, 70, 60, LD);
- ComputerLabel p5 = new ComputerLabel(200, 20, 10, 15, LD);
- p1.setBackground(Color.black);
- p2.setBackground(Color.DARK_GRAY);
- p3.setBackground(Color.magenta);
- p4.setBackground(Color.red);
- p5.setBackground(Color.red);
- p1.setSpeed(4);
- p2.setSpeed(5);
- p3.setSpeed(6);
- p4.setSpeed(3);
- p5.setSpeed(2);
- gamePanel.addLabel(p1);
- gamePanel.addLabel(p2);
- gamePanel.addLabel(p3);
- gamePanel.addLabel(p4);
- gamePanel.addLabel(p5);
- Game game = new Game();
- game.add(gamePanel);
- }
- }
小结:
1、各个类的设计、结构感觉很乱;
2、上面的一点还体现在关于游戏实体(即 JLabel 方块)所选取的数据结构很乱;
3、重要方法如 move() 的代码过多且杂,本来这是整个游戏的重点算法实现,却这样子糟糕,+_+;
4、代码中没有注释,所以刚才在理解自己的代码时花了点时间,%>_<%…嗯,以后这要注意了。