前天不知道又在哪里见到这么一句话“谁能坚持超过50秒,我请他吃饭”(可以点击玩一玩原版游戏),其实这个不就是之前在同学QQ空间里面转来转去的一个网页游戏吗?什么“据说,美国空军的飞行员被强制达到2分钟。”于是,又来了一点点兴趣再玩一下下。游戏界面如下:
其实前段时间自己也尝试用 Java 实现了这游戏,具体是按照自己对这游戏的简单理解使用 Java GUI 进行编程。电脑装了 JRE 的朋友可以下载附件直接双击 .bat 文件试玩一下哦!!!(*^__^*) 嘻嘻……
我的思路是这样子的(游戏中的方块用 JLabel 实现):
1、游戏中4个自动移动并且会慢慢加速的方块是碰到固定的“壁”就按照光的反射特性改变移动的方向,这个我们可以通过条件判断来改变方块的具体坐标 x 和 y 来实现;
2、自动移动则用 Java 线程来实现,具体是在线程类的 run() 方法中用死循环不断改变 x 和 y 坐标,然后不断地擦除、重绘即可,而碰壁之后反射的算法也是包含在这里的;
3、玩家能够通过按住鼠标左键来移动自己的方块,这个应该用 java.awt.event 包中的 MouseMotionAdapter 这个鼠标事件监听器类,Java 文档中的描述是“接收鼠标移动事件的抽象适配器类”,它包含() 和() 方法,可以监听鼠标时间按,具体的可以查阅一下该类;对于玩家控制的方块,要每时每刻都获取鼠标当前在面板上的坐标 x 、y,以此为 JLabel 方块的中心进行不断擦除、重绘,也就达到了跟着鼠标移动的效果了;
4、判断游戏是否结束,就是遍历4个自动移动的 JLabel 方块,看它们在游戏面板中的覆盖面是否与玩家控制的 JLabel 方块有一点点重合,重合则说明相碰了,游戏结束;
5、关于游戏计时,这个可以用到 Java 线程中的 java.util.TimerTask 类来实现,不过这里我的游戏中没实现,偷懒了一下;
6、至于方块的移动速度,用一定的策略对 Thead.sleep() 方法中的睡眠时间进行减少即可。
我的游戏界面:
1、游戏开始状态
2、游戏进行中
3、碰壁了,游戏结束
代码实现:
GlobalVars.java
//常量接口模式:把系统中相关的常量放在专门的常量接口中定义
publicinterfaceGlobalVars {
/**
* 4个移动方向,L、R为左、右,U、D为上、下
*/
publicstaticfinalintLD = -1;//往左下方
publicstaticfinalintRD =1;//往右下方
publicstaticfinalintRU =2;//往右上方
publicstaticfinalintLU = -2;//往左上方
/**
* 游戏窗口大小
*/
publicstaticfinalintFRAME_WIDTH =500;
publicstaticfinalintFRAME_HEIGTH =500;
/**
* 面板大小
*/
publicstaticfinalintPANEL_WIDTH =400;
publicstaticfinalintPANEL_HEIGTH =400;
/**
* 玩家JLabel的大小
*/
publicstaticfinalintPLAYER_WIDTH =50;
publicstaticfinalintPLAYER_HEIGTH =50;
}
ImpMove.java
importjavax.swing.JLabel;
/**
* 定义一个电脑方、玩家的JLabel都要实现的
* 移动策略的抽象类 ImpMove ,该接口继承自
* JLabel,所以可以获取 JLabel 类中常用 的方法
* @author haolloyin
*/
publicabstractclassImpMoveextendsJLabel{
// 移动
protectedabstractvoidmove();
// 开始
protectedabstractvoidbegin();
}
ComputerPanel.java
/**
* 电脑控制的 JLabel 方块,继承自 ImpMove
* 抽象类,必须实现其 move()和 begin()方法,
* 它可以在游戏中存在多个具体实例,由玩家
* 确定,它使用了线程因此能够自动移动
* @author haolloyin
*/
publicclassComputerLabelextendsImpMove{
/* 碰到壁必须反弹,这里4个常量用于
* 判断电脑方块碰到那一面壁
*/
privatestaticfinalintPL = -1;
privatestaticfinalintPR =1;
privatestaticfinalintPU =2;
privatestaticfinalintPD = -2;
privateintxx;
privateintyy;
privateintwidth;
privateintheigth;
privatePoint p;
privateRectangle r;
privateintdirection;//移动方向
privateintspeed =5;//移动速度
privateThread go;//驱动其移动的线程实例
privatebooleanisLive =true;//游戏是否结束
publicComputerLabel(intx,inty,intw,inth,intd) {
this.width = w;
this.heigth = h;
init(x, y, w, h, d);
}
privatevoidinit(intx,inty,intw,inth,intdirection) {
setBounds(x, y, w, h);
setOpaque(true);
setBackground(Color.green);
r = getBounds();
p = r.getLocation();
this.direction = direction;
}
/**
* 实现了Runnable接口的私有内部类,用于驱动move()方法
*/
privateclassMoveAbleimplementsRunnable {
publicvoidrun() {
while(isLive) {
try{
Thread.sleep(speed);
}catch(InterruptedException e) {
e.printStackTrace();
}
// System.out.println("speed = " + speed);
move();
}
}
}
/**
* 实现ImpMove抽象类中的move()
*/
@Override
protectedvoidmove() {
isPengBi();
p.x += xx;
p.y += yy;
setLocation(p.x, p.y);
}
/**
* 测试是否碰到壁,若是则调
* 用changeDirection()改变移动方向
*/
privatevoidisPengBi() {
if(p.x <0) {
changeDirection(PL);
}elseif(p.x > GlobalVars.PANEL_WIDTH - width) {
changeDirection(PR);
}elseif(p.y <0) {
changeDirection(PU);
}elseif(p.y > GlobalVars.PANEL_HEIGTH - heigth) {
changeDirection(PD);
}
}
/**
* 碰到壁则反弹,即改变移动方向
*/
privatevoidchangeDirection(intpeng) {
if(peng == PL && direction == LD) {// ↙碰左
direction = RD;
xx =1;
yy =1;
}elseif(peng == PL && direction == LU) {// ↖碰左
direction = RU;
xx =1;
yy = -1;
}elseif(peng == PR && direction == RU) {// ↗碰右
direction = LU;
xx = -1;
yy = -1;
}elseif(peng == PR && direction == RD) {// ↘碰右
direction = LD;
xx = -1;
yy =1;
}elseif(peng == PU && direction == RU) {// ↗碰上
direction = RD;
xx =1;
yy =1;
}elseif(peng == PU && direction == LU) {// ↖碰上
direction = LD;
xx = -1;
yy =1;
}elseif(peng == PD && direction == LD) {// ↙碰下
direction = LU;
xx = -1;
yy = -1;
}elseif(peng == PD && direction == RD) {// ↘碰下
direction = RU;
xx =1;
yy = -1;
}
}
/**
* 游戏开始,启动线程
*/
@Override
protectedvoidbegin() {
go =newThread(newMoveAble());
toWhere();
go.start();
}
/**
* 确定方块的移动方向
*/
privatevoidtoWhere() {
if(direction == LD) {
xx = -1;
yy =1;
}elseif(direction == LU) {
xx = -1;
yy = -1;
}elseif(direction == RD) {
xx =1;
yy =1;
}elseif(direction == RU) {
xx =1;
yy = -1;
}
}
/**
* 游戏是否结束
*/
publicvoidisDead() {
this.isLive =false;
}
/**
* 设置移动速度
*/
publicvoidsetSpeed(intspeed) {
this.speed = speed;
}
}
PlayerPanel.java
/**
* 供玩家控制的 JLabel 方块,它在整个游戏
* 当中只会存在一个实例对象,继承自 ImpMove
* 抽象类,必须实现其 move() 和 begin() 方法
* @author haolloyin
*/
publicclassPlayerLabelextendsImpMove{
privateRectangle r;//方块的大小、位置
privatePoint now;//方块的坐标x、y
privateintx;//原来的坐标 x
privateinty;//原来的坐标 y
privateintmax_x = PANEL_WIDTH - PLAYER_WIDTH;
privateintmax_y = PANEL_HEIGTH - PLAYER_HEIGTH;
publicPlayerLabel() {
begin();
}
/**
* 重写begin()方法,玩家鼠标一单击中间的
* JLabel 方块,则游戏开始
*/
@Override
protectedvoidbegin() {
setText("点击吧");
setForeground(Color.black);
setSize(PLAYER_WIDTH, PLAYER_HEIGTH);
setLocation(150,150);
setOpaque(true);
setBackground(Color.green);
r =this.getBounds();
now =newPoint();
/**
* 为当前JLabel对象添加接收鼠标移动事件的抽象适配器类,
* 用于当按下鼠标时获取当前坐标并开始游戏
*/
addMouseMotionListener(newMouseMotionAdapter() {
publicvoidmouseDragged(MouseEvent e) {
x = e.getX();
y = e.getY();
move();
}
});
/**
* 添加可以变换鼠标指针样式的事件监听器
*/
addMouseListener(newMouseAdapter() {
publicvoidmouseEntered(MouseEvent e) {
// 变成手型鼠标
setBackground(Color.yellow);
setCursor(newCursor(Cursor.HAND_CURSOR));
}
publicvoidmouseExited(MouseEvent e) {
// 变成默认鼠标
setBackground(Color.green);
setCursor(newCursor(Cursor.DEFAULT_CURSOR));
}
});
}
/**
* 游戏的主要算法:实现ImpMove抽象类中的move()
*/
@Override
protectedvoidmove() {
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);
}
/**
* 测试
*/
publicstaticvoidmain(String[] args) {
JFrame jf =newJFrame();
PlayerLabel p1 =newPlayerLabel();
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
*/
publicclassGamePanelextendsJPanel{
// 保存自动移动的JLabel对象,即电脑控制的方块
privateLinkedList labels;
// 游戏是否结束
privatebooleanisLive =true;
// 玩家JLabel
privatePlayerLabel player;
//游戏进行中判断是否相碰的线程
privateThread go;
publicGamePanel() {
setBounds(50,30, PANEL_WIDTH, PANEL_HEIGTH);
setBackground(Color.yellow);
setLayout(null);
init();
}
privatevoidinit() {
labels =newLinkedList();
player =newPlayerLabel();
this.add(player);
player.addMouseListener(newMouseAdapter() {
publicvoidmousePressed(MouseEvent me) {
player.setText("我闪...");
startGame();
}
});
}
privatevoidstartGame() {
intnum = labels.size();
for(inti =0; i
labels.get(i).begin();
}
if(this.go ==null) {
go =newThread(newCheckGameIsOver());
go.start();
}
}
privatevoidisOver() {
Rectangle r_player = player.getBounds();
intnum = labels.size();
for(inti =0; 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);
}
}
}
privatevoidgameOver() {
intnum = labels.size();
for(inti =0; i
ComputerLabel l = (ComputerLabel)labels.get(i);
l.isDead();
}
}
privateclassCheckGameIsOverimplementsRunnable {
publicvoidrun() {
while(isLive) {
isOver();
}
}
}
publicvoidaddLabel(ImpMove label) {
this.labels.add(label);
this.add(label);
}
}
Game.java
/**
* 最终游戏测试类,其实就是初始化所有
* JLabel 对象并启动游戏
* @author haolloyin
*/
publicclassGameextendsJFrame {
publicGame() {
setTitle("躲避 游戏");
setLayout(null);
setBounds(300,100, FRAME_WIDTH, FRAME_HEIGTH);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
/*
* 测试
*/
publicstaticvoidmain(String[] args) {
GamePanel gamePanel =newGamePanel();
ComputerLabel p1 =newComputerLabel(340,320,35,59, RU);
ComputerLabel p2 =newComputerLabel(13,30,40,20, LU);
ComputerLabel p3 =newComputerLabel(20,200,60,40, RD);
ComputerLabel p4 =newComputerLabel(350,60,70,60, LD);
ComputerLabel p5 =newComputerLabel(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 =newGame();
game.add(gamePanel);
}
}
小结:
1、各个类的设计、结构感觉很乱;
2、上面的一点还体现在关于游戏实体(即 JLabel 方块)所选取的数据结构很乱;
3、重要方法如 move() 的代码过多且杂,本来这是整个游戏的重点算法实现,却这样子糟糕,+_+;
4、代码中没有注释,所以刚才在理解自己的代码时花了点时间,%>_<%…嗯,以后这要注意了。