J2ME游戏开发从理论到实践

作者:潇潇  出处:www.j2mefans.com

Java火了很多年了,但似乎更多的应用集中在web开发上。直到近几年java在手机中的应用即j2me的势头才真正的火了起来。看看现在的各大手机厂
商的产品,支持java游戏已经成为了其产品的一个主要的亮点。在手机功能越发强大的今天,你是否有兴趣自己开发一些小的游戏程序,在自己的手机中体验一
下成功的愉悦呢?
我们这篇教程的目的就是带领大家最大限度的使用我们的手机,体会“玩”的快乐。
对于什么是java以及关于java的基础知识,由于篇幅所限,我们不能讲解了。有兴趣的读者可以在网上或者书本中很容易得找到相关的内容。
那么开发手机游戏究竟要使用什么工具呢?有这样使用和操作呢?
一、平台设置
制作游戏时需要一定的工具的。这个工具当然就是java了,更具体地说就是J2ME Wireless Toolkit。这套工具是由Sun公司提供的一套开发工具,人们把它和Java 2 SDK一起使用就可以进行手机开发了。读者可以到http://java.sun.com/products/j2mewtoolkit/获得J2ME Wireless Toolkit最新的版本。
该工具中包括下面一些工具:字节码验证器;J2ME模拟器;Ktoolbar;预配置服务器。
对于他的安装简单我们不做讲解。
我们主要以一个小而简单的游戏实例贯穿讲解j2me游戏开发的相关知识。
二、创建项目
因为是一篇短小的文章,所以我们的实例不能太复杂。但又得尽量涉及到游戏开发的方方面面,所以我们选一个和经典的青蛙过河的游戏类似的游戏小鸡过马路为例进行讲解。
首先我们要建立一个工程。
启动Ktoolbar,界面如下:
 
我们这里不使用其他的可视化的开发工具。而是使用Ktoolbar和windows中最常用记事本来完成我们的所有工作。
Ktoolbar提供了一种直接的方法来管理MIDlet项目并生成设置。他更关心的是管理源代码文件与自动生成和测试过程。使用Ktoolbar应用程序,我们可以在一个单一的环境中执行编译、验证和模拟我们的工程,而且速度很快。
为了生成一个新的项目,单击工具栏上的“New Project”按钮。在弹出的“New Project”对话框中输入一个项目名称和一个MIDlet类名称。我们使用“jiguohe”作为工程名,类名为:jiguoheMIDlet。如下图所示:
 
点击“Create Project”按钮,该项目自动创建到J2ME Wireless Toolkit默认安装路径下的apps文件夹中。

接着我们要对我们的工程进行设置。我们使用MIDP 2.0框架,所以在Profiles中显示 MIDP 2.0 ,MIDP也就是
 Mobile Information Device Profile (移动信息设备框架)。他是建立在CLDC基础上来描述手机的无线移动设备的框
架。
我们还使用CLDC 1.0 。所以设置comfigurations为CLDC 1.0 。 CLDC也就是
Connected Limited Device Configuration (连接限制设备配置)。他描述了所有无线移动设备所需要的一个最小级别
的功能集合。如下图所示:
 
三、项目分析
青蛙过河的游戏是玩家控制一只青蛙,让他安全的从屏幕的最底端通过公路和河流到达屏幕的最顶端。当然在游戏中有许多的障碍,包括马路上高速行驶的汽车,湍急的河流和鳄鱼。青蛙必须躲过汽车避开鳄鱼,通过马路,乘坐木头通过河流。
细分析的话我们可以看到整个游戏中有这么几个元素:青蛙,汽车,鳄鱼,木头。青蛙由玩家控制上下移动。汽车和鳄鱼木头等则是左右移动。
并且木头是可以踩得,而汽车、鳄鱼是不能碰的。
为了简单我们制作一款小鸡躲避公路上高速行驶的汽车的简单游戏,汽车在上下走向的公路上移动,而小鸡要从屏幕的左边移动到屏幕的右边。每次成功穿过,成绩增加;相反在穿马路时被撞死,则减掉一次机会,每次游戏共有3次机会。看谁在这3次机会中,成绩最高。

四、代码编写

(一)在写代码之前,我们还是先了解一下有关j2me程序的结构吧。
J2me程序的基本框架,其实就是MIDlet
类的架构。MIDlet是j2me的一个java类,它扩展自java . microedition . midlet . MIDlet抽象类。它类
似于c语言的main函数,属于j2me程序的主程序。编写j2me程序简单的说也就是实现MIDlet类的startApp ( ) ,
 pauseApp ( ) ,  destroyApp ( )函数。这些函数类似于j2se的java . applet .Applet类中的
start ( ) , stop ( ) 和destroy ( )函数。
具体的说,所有的j2me程序和一般的程序一样都要使用系统所提供的类库,只是不同的工具提供的类库大小不同罢了。对于j2me程序主要使用下面两个库,所以将他们写在程序的开头位置:

//j2me程序主要使用下面两个库
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

然后我们可以构造一个继承自MIDlet类的类,当然很多时候还要继承命令监听接口。如下所示:
public class myMIDlet extends MIDlet implements CommandListener { }

我们要在他的构造函数里面添加上面提到的函数:
    
  public void startApp ( ) {        }
    
  public void pauseApp ( ) {  }
  
  public void destroyApp ( boolean unconditional ) { }
  

中startApp ( ) , pauseApp ( ) ,  destroyApp ( )函数都是必须实现的从MIDlet类中继承的接口函数,
实现具体功能的代码就添加在这几个函数中。其中startApp ( ) 函数负责程序的初始化功能, pauseApp ( )函数是当一个程序处于暂
停状态的时候自动执行的函数 ,  destroyApp ( )函数则是当一个程序要退出时才会执行的函数。
并且在下面实现对键盘按键的监听:这主要是通过java . microedition . lcdui . CommandListener类中的commandAction ( )方法来实现的。如下所示:
  public void commandAction(Command c, Displayable s) {
    if (c.getCommandType() == Command.EXIT) {
      destroyApp(true);
      notifyDestroyed();
    }

 所以我们的程序当然也就是这样的一个结构了:

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

public class jiguoheMIDlet extends MIDlet implements CommandListener { 

  //这里负责游戏的开始
public void startApp ( ) {        }
    
  public void pauseApp ( ) {  }
  //游戏结束时调用此函数
  public void destroyApp ( boolean unconditional ) { }
  
  //下面的函数处理命令接口
public void commandAction(Command c, Displayable s) {
     
    }

}
 

定对Graphics类很熟悉,他能够绘制图形,如点线面等,文本和图像,还可以用来显示或进行屏幕外内存缓冲。而在j2me开发时,Graphics类
的一个对象作为MIDlet的paint ( ) 方法的一个参数被传递给paint ( )方法,然后用来执行图形输出到MIDlet屏幕或者一个屏幕
外缓冲区。由于Graphics对象是自动的传递给paint ( )的,所以你不必显示的创建一个Graphics对象。
Graphics类所提供的大多数的图形操作包括下面三个领域:绘制图形基元,绘制文本,绘制图像。当然这是所有的游戏的基础。所以我们还是详细的学习一下吧。
1、设置颜色
在绘制图形之前,我们当然也的知道关于颜色的设置,因为它是很重要的一部分。

们可以使用Graphics类中定义的setColor ( )方法决定在绘制线条等图形操作中所使用的颜色。该方法接受3个整形参数,他们分别表示3种
基色(红,绿,兰)。其中两头setColor ( 255 , 255 , 255 )设定为白色,setColor ( 0 , 0 , 0 )设定
为黑色。

2、绘制线条
对于线条的绘制,我们使用Graphics类的drawLine ( )方法。他的定义如下:
void drawLine ( int x1 , int y1 , int x2 , int y2 )
两点确定一条直线,所以( x1 , y1 ) , ( x2 , y2 ) 分别为线段的起点和终点。

3、绘制矩形
对于矩形的绘制最简单的就是确定左上角和宽高了,所以他的函数原型定义如下:
viod drawRect ( int x , int y , int width , int height )

4、绘制弧形
弧形比线条和矩形要复杂。弧形是椭圆的一部分,是擦去椭圆的一部分后剩下的部分。绘制一个弧形的方法如下:
void drawArc ( int x , int y , int width , int height , int startAngle , int arcAngle )
drawArc ( )方法的前两个参数确定绘制弧形所在椭圆的位置坐标,后两个参数决定椭圆的宽高,startAngle表示弧形开始的绘制的角度,arcAngle表示沿逆时针旋转的角度。

5、绘制文本
尽管文本通常并不是游戏的重点,但在游戏中的应用还是很广泛的。大多数游戏要使用文本显示积分,即使绘制图形的时候也要多少使用文本做辅助的说明。

制文本我们可以使用定义在Graphics类中的drawString ( String str , int x , int y ,
 int anchor ),该方法接受一个String对象作为第一个参数,接下来的两个参数x和y指定了字符串绘制的位置。Anchor参数则定义绘
制文本的位置,主要分为水平位置和垂直位置两种。

6、绘制图像
推向对于游戏的重要性是不言而喻的,抛开图像的的基础知识,我们还是看看在j2me中我们怎样载入一个图像并进行绘制吧。
先来学习如何载入图像。由于图像通常存储在一个外部文件中,在绘制他们之前,必须从文件中载入他们。Image类的一个静态方法createImage ( ) 用来载入和创建图像:
publie static Image createImage (  String name ) throw IOException
我们只要把图像文件的名字指定为上面createImage ( )方法的参数即可。该方法传回Image类的一个实例对象。
这样我们就可以使用Graphic类提供的drawImage ( )方法来绘制图像:
Boolean drawImage ( Image img , int x , int y , int anchor ) 
看到他的第一个参数了吗?我们只要把刚才传回的Image类的实例传递给这个参数,并设置好它的位置就可以在屏幕上看到我们的图像了。

好了,有了这门多的预备知识,我们已经有能力做一个启动界面了。下面我们开始了!

7、构建一个游戏的启动界面

先构建一个MIDlet类文件。
添加下面两行代码,为程序编译运行提供相应的库。
import javax . microedition . midlet . * ;
import javax . microedition . lcdui . * ;
然后定义一个jiguoheMIDlet的类结构,要求继承自MIDlet类,并扩展CommandListener接口。
public class jiguoheMIDlet extends MIDlet implements CommandListener {

}
在类中添加下面代码:
先定义一个我们要创建的启动界面类的实例,该类我们命名为Ocanvas。
  private OCanvas canvas;
  这样我们就可以在后面将他启动并显示出来。
紧接着,我们要分别实现MIDlet类的几个函数。下面是startApp ( )
  public void startApp ( ) {
    if ( canvas = = null ) {
//将负责现实的类通过我们自定义的Ocanvas类的构造函数,传递给Ocanvas类
      canvas = new Ocanvas ( Display . getDisplay ( this ) ) ;
       //定义了一个Command控件变量并创建一个新的Command实例
      Command exitCommand = new Command ( " Exit " ,  Command . EXIT ,  0 ) ;
      //将命令添加到界面中
canvas . addCommand ( exitCommand ) ;
//设定对该命令的监听
      canvas . setCommandListener ( this ) ;
    }
    
    //创建成功后启动画布
    canvas . start ( ) ;
  }
  另外两个函数为空
  public void pauseApp ( ) { }
  
  public void destroyApp ( boolean unconditional ) { }
  
  //定义命令处理函数,当被监听的命令被触发时, 执行该代码
  public void commandAction ( Command c , Displayable s ) {
    if  ( c . getCommandType ( ) = = Command . EXIT ) {
      destroyApp ( true ) ;
      notifyDestroyed ( ) ;
    }
  }
有了MIDlet类我们程序就可以运行了,下面我们来添加程序的核心,就是有关界面绘制和显示的部分。
仍然实现添加必需的库文件:
import javax.microedition.lcdui.*;
//因为有错误处理,所以添加下面的库
import java.io.*;

然后定义我们前面提到的界面类Ocanvas,他继承自Canvas类
public class OCanvas extends Canvas { }
在类中添加下面的代码:
  //因为从MIDlet类的子类中传递过来了显示类Display,所以得到他的一个实例来绘制图像
  private Display display;
  //这里定义一个图像类,用来显示启动界面
private Image    title;
然后,构造显示类的构造函数:
  public Ocanvas ( Display d ) {
    //继承父类的功能
super ( ) ;
//存储显示类的实例
    display = d ;
  }
定义显示类的成员函数strat ( ) :
  void start ( ) {
    display . setCurrent ( this ) ;
    repaint ( ) ;
  }
最后实现图像显示函数,该函数命名为paint ( ) ,在每次图像更新时自动调用:
  public void paint (Graphics g ) {
     //清除屏幕
     g . setColor ( 255 , 255 , 255 ) ; // 设置白色
     g . fillRect ( 0 , 0 , getWidth ( ) , getHeight ( ) ) ; //用白色填充屏幕

     // 创建并载入图像
    try {
      title = Image . createImage ( " / title . png " ) ;
      
    }
    catch ( IOException e ) {
      System . err . println ( " Failed loading images ! " ) ;
    }
    
    //绘制图像
    g . drawImage ( title , 0 , 0 , Graphics . TOP | Graphics . LEFT ) ;
  }

是否有些激动!别着急,接下来我们还用这些知识,创造更令人兴奋的动画和游戏呢!

 

     
  }
}

怎样将一个静态的画面变成动画呢?这就是游戏循环。简单的说就是不断的重画物体。比如在我们的游戏中汽车在来回的移动,我们就要通过计算得到每一个汽车的位置,在新的位置上不断的绘制,只要速度足够的快,那么给人的感觉就是汽车移动了。
为了实现游戏循环,我们可以创建一个单独的线程磊实现,这样就用到了一个叫做Thread的类。在类中加入这个很简单,首先修改OCanvas类,让它实现Runable接口。对于线程来说,Runable是必需的:
public class OCanvas extends Canvas implements Runable
然后在OCanvas的构造函数中加入线程的初始化代码:
public OCanvas ( ) {

……
//创建游戏线程
Thread t = new Thread ( this );
//线程开始工作
t . start ( ) ;

}

当然这还不够,为了满足Runable接口的要求,还要增加一个run ( )方法。这个方法在调用start ( )方法后马上被调用执行。
private Boolean running = true ;
public void run ( ) {
    while ( running ) {
          //做一些事情,形成动画          
     }
}

了帮助理解我们加一个简单的例子,就是计算并显示帧频(也就是每秒显示几遍画面)。计算帧频比较简单:只需要在每次游戏循环中,计数器增加一个计数即可。
为了检查每秒钟多少循环,需要检查是否过去了一秒钟,如果超过1秒,则开始计算帧频,否则计数器加一。首先需要在OCanvas类中加入下面代码:
private int  cps = 0 ;
private int cyclesThisSecond = 0 ;
private long lastCPSTime = 0 ;
接下来就可以在游戏中使用这些值:
public void run ( ) {
            while ( running ) {
                  //做一些事情,形成动画
            while ( running ) {
                repaint (  ) ;

                     // 更新帧频
                     if ( System . currentTimeMillis ( ) – lastCPSTime > 1000 )
                     {
                            lastCPSTime = System . currentTimeMillis ( ) ;
                            cps = cyclesThisSecond ;
                            cyclesThisSecond = 0 ;
                     } else
                            cyclesThisSecond ++ ;

            }
          
             }
    }
现在可以修改paint ( )方法来绘制帧频了。
public void paint ( Graphics g ) {
     g . setColor ( 0 , 0 , 0  ) ;
    g . fillRect ( 0 , 0 , getWidth ( ) , getHeight ( )  );
    g . setColor ( 255 , 255 , 255 ) ;
    g . drawstring ( " cps = " + cps , 0 , 0 , Graphics . LEFT | Graphics . TOP ) ;
    
  }

连个变量的初始化代码:
XSpeed = YSpeed = 0 ;  //这样小孩方块就不会一启动就来回乱动了
更多的我们要修改update ( )方法:
private void update ( ) {
    //得到键盘状态
int keyState = getKeyStates ( ) ;
//与常量进行比较
    if ( ( keyState & LEFT_PRESSED )  ! = 0 )  //左键按下
         Xspeed = – 2 ;  //修改变量,向左移动
    else if ( ( keyState & RIGHT_PRESSED ) ! = 0 ) //右键按下
         XSpeed = 2 ;  //修改变量,向右移动
    else if ( ( keyState & UP_PRESSED ) ! = 0 ) //上键按下
         YSpeed = – 2 ;  //修改变量,向上移动
    else if ( ( keyState & DOWN_PRESSED ) ! = 0 ) //下键按下
         YSpeed = 2 ;  //修改变量,向下移动
     //计算小黑方快的位置
x = XSpeed + x ;
     y = YSpeed + y ;
    if ( x < 0 )
              x = getWidth ( ) ;
    if ( x > getWidth ( ) )
                x = 0 ;
     if ( y < 0 )
              y = getHeight ( ) ;
    if ( y > getHeight ( ) )
                y = 0 ;
}

最后,我们将draw ( )方法中的g . fillRect ( x ++ , y ++, 20 , 20 ) ;改为g . fillRect ( x , y , 20 , 20 ) ; 既可。

这样我们就可以使用按键来控制小黑方快移动的方向了。

 

paint ( )   再派生自精灵的子类中重写。
怎么样这个类还不错吧。除了这些Sprite类所提供的额外的功能主要包括:
(1)基于图形的精灵,以及支持多帧的图像(也就是说,我们得精灵不用再是一个丑陋的小黑方快,而可以是一个绘制精美动画)
(2)可以对精灵图像进行变形(旋转、反射等等)
(3)可以定义一个参考像素,作为精灵变形和定位的基础
(4)对于带有多个帧图像的精灵,图像显示的顺序可以精确定位
(5)精灵之间的冲突可以使用矩形、缩小矩形或者图像数据来进行冲突检测。
好了,让我们将我们的游戏升级一下吧!
先在游戏中添加一个根据按键移动的小鸡,并且小鸡中包含两帧,一个为迈步,一个为站立。如下图所示:
 

要根据一个图像来创建一个精灵,只要把一个新创建的Image对象传递到Sprite的构造函数中就可以了:
我们现在画布类的类体中定义一个精灵类的变量:
private Sprite   chickenSprite;
并去除private int     XSpeed , YSpeed ;
然后我们修改start ( )方法:
public void start ( ) {
    
    display . setCurrent ( this ) ;
    
    x = getWidth ( ) / 2 – 10 ;
    y = getHeight ( ) / 2 – 10 ; 
    // 去掉下面一行中原有的代码
    //XSpeed = YSpeed = 0 ;
    //在这里添加相应的精灵图像
    try {
      //创建精灵图像,并设定他的大小和位置
      chickenSprite = new Sprite ( Image . createImage ( " / Chicken . png " ) ,  22 , 22 ) ;
      chickenSprite . setPosition ( x ,  y ) ;
      }
    catch (IOException e) {
      System . err . println ( " Failed loading images ! " ) ;
    }

    sleeping = false ;
    Thread t = new Thread ( this ) ;
    t . start ( ) ;
  }

接着修改update ( )方法:
private void update ( ) {
    int keyState = getKeyStates ( ) ;
    if ( ( keyState & LEFT_PRESSED ) ! =0 ) {
         //这里我们不再需要计算x,y了,而直接使用精灵类的move ( )方法
chickenSprite . move ( -6 ,  0 ) ;
//显示下一帧动画
        chickenSprite . nextFrame ( ) ;
 } else if  ( ( keyState & RIGHT_PRESSED ) ! = 0 ) {
         chickenSprite . move ( 6 , 0 ) ;
         chickenSprite . nextFrame ( ) ;
} else if ( ( keyState & UP_PRESSED ) ! = 0 ) {
         chickenSprite . move ( 0 , -6 ) ;
         chickenSprite . nextFrame ( ) ; 
} else if  ( ( keyState & DOWN_PRESSED )  ! = 0 ) {
         chickenSprite . move ( 0 ,  6 ) ;
         chickenSprite . nextFrame ( ) ;
}
     //下面代码控制它的移动
     if ( chickenSprite . getX ( ) < – chickenSprite . getWidth ( ) )
        chickenSprite . setPosition ( getWidth ( ) , chickenSprite . getY ( ) ) ;
      else if ( chickenSprite . getX ( ) > getWidth ( ) )
        chickenSprite . setPosition ( – chickenSprite . getWidth ( ) , chickenSprite . getY ( ) ) ;
      if ( chickenSprite . getY ( ) <  – chickenSprite . getHeight ( ) )
        chickenSprite . setPosition ( chickenSprite . getX ( ) , getHeight ( ) ) ;
      else if ( chickenSprite . getY ( ) > getHeight ( ) )
        chickenSprite . setPosition ( chickenSprite . getX ( ) , – chickenSprite . getHeight ( ) ) ;
}

最后将draw ( )方法作如下修改:
private void draw ( Graphics g ) {
    // 清除屏幕
    g . setColor ( 0xffffffff ) ;
    g . fillRect ( 0 , 0 , getWidth ( ) , getHeight ( ) ) ;
    
    //将下面的原有代码去掉
    // g . setColor ( 0 , 0 , 0 ) ;
    // g . fillRect ( x , y , 20 , 20 ) ;
    //添加下面的代码
    chickenSprite . paint ( g ) ;
    // 刷新屏幕缓冲区
    flushGraphics ( ) ;
  }

 

    g . drawImage ( background , 0 , 0 , Graphics . TOP | Graphics . LEFT ) ;
    ……
}

(b)再来添加汽车,我们这里有四种汽车,所以分别定义四个汽车精灵:
在类体中添加下面的变量定义:
private Sprite [ ] carSprite = new Sprite [ 4 ] ;
然后在start ( )方法中添加下面代码:
public void start ( ) {
        ……
     try {   
          ……
        //添加下面的代码,绘制汽车,并设定他的位置
      carSprite[0] = new Sprite(Image.createImage("/Car1.png"));
      carSprite[0].setPosition(27, 0);
      
      carSprite[1] = new Sprite(Image.createImage("/Car2.png"));
      carSprite[1].setPosition(62, 0);
     
      carSprite[2] = new Sprite(Image.createImage("/Car3.png"));
      carSprite[2].setPosition(93, 67);
 
      carSprite[3] = new Sprite(Image.createImage("/Car4.png"));
      carSprite[3].setPosition(128, 64);
     
     }
    catch ( IOException e) {
      System . err . println ( " Failed loading images ! " ) ;
    }
}
对于汽车精灵的绘制我们修改draw ( )方法:
private void draw (Graphics g ) {
    ……
     //绘制汽车精灵
    for (int i = 0; i < 4; i++)
      carSprite[i].paint(g);
}
让汽车动起来:
在类体中添加下面的变量定义,这样我们就能分别存储每辆汽车的速度了:
private int[]    carYSpeed = new int[4];
然后在start ( )方法中添加下面代码:
public void start ( ) {
        ……
carYSpeed[0] = 3;  //第一辆汽车从上往下走,速度为3
carYSpeed[1] = 1;   //第二辆汽车从上往下走,速度为1
carYSpeed[2] = -2;    //第三辆汽车从下往上走,速度为-2 (符号表示方向)
carYSpeed[3] = -5; //第一辆汽车从下往上走,速度为-5

}
这样我们就剩最后一步就可以让车移动了,就是修改update ( )方法,在其中添加下面的代码:
// 更新汽车精灵
    for (int i = 0; i < 4; i++) {
      // 移动汽车精灵
      carSprite[i].move(0, carYSpeed[i]);

(c)添加碰撞检查,和成绩,生命值
先在类体中添加下面的变量定义:
private boolean  gameOver;//游戏结束的标志
private int      numLives; //一般的游戏都有三条命,我们这里也这样
private int      score;  //记录玩家的成绩
在start ( )方法中我们对上面的变量进行初始化:
gameOver = false;
numLives = 3;
    score = 0;

对于碰撞检查很简单,前面我们介绍精灵的时候,我们就提到过,精灵类为我们提供了碰撞检查的方法:
collidesWith ( sprite s , boolean pixelLevel ) ;
defineCollisionRectangle () ;
collidesWith ( Image image , int x , int y , boolean pixelLevel ) ;
collidesWith ( TiledLayer , Boolean pixelLevel ) ;
将它添加到update ( )方法中就可以了:
private void update() {
    // 检查游戏是否从新开始
    if (gameOver) {  
      int keyState = getKeyStates();
      if ((keyState & FIRE_PRESSED) != 0) {
        // 开始一个新游戏,初始化各种变量
        chickenSprite.setPosition(2, 77);
        gameOver = false;
        score = 0;
        numLives = 3;
      }

      // 游戏结束不做任何更改
      return;
    }

    // 按键控制小鸡的移动,这一部分没有变化,所以省略
    if (++inputDelay > 2) {
      int keyState = getKeyStates();
      if ((keyState & LEFT_PRESSED) != 0) {
        chickenSprite.move(-6, 0);
        chickenSprite.nextFrame();
      }
……

      // 重设延迟
      inputDelay = 0;
    }

    // 检查小鸡的位置,如果过了马路,记录加25
    if (chickenSprite.getX() > 154) {
      // 并且让消极重新回到开始处
      chickenSprite.setPosition(2, 77);
      score += 25;
    }

    // 更新汽车精灵
    for (int i = 0; i < 4; i++) {
      //伊东汽车精灵
      carSprite[i].move(0, carYSpeed[i]);
      checkBounds(carSprite[i], true);

      // 检查小鸡和汽车精灵是否碰撞
      if (chickenSprite.collidesWith(carSprite[i], true)) {

        // 碰撞后,减掉生命值,如果生命值没了,游戏结束
        if (–numLives == 0) {
          gameOver = true;
        } else {
          // 否则,从新过马路
          chickenSprite.setPosition(2, 77);
        }
        
        break;
      }
    }
  }

最后,在draw ( )方法中添加代码,显示游戏结束等信息:
private void draw(Graphics g) {
     ……
    if (gameOver) {
      // 游戏结束,显示“game over”和成绩
      g.setColor(255, 255, 255); // white
      g.setFont(Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_LARGE));
      g.drawString("GAME OVER", 90, 40, Graphics.TOP | Graphics.HCENTER);
      g.setFont(Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_MEDIUM));
      g.drawString("You scored " + score + " points.", 90, 70, Graphics.TOP |
        Graphics.HCENTER);
    }

    flushGraphics();
  }

urceAsStream("Music.mid");
      //通过Manager.createPlayer()方法创建一个播放器
musicPlayer = Manager.createPlayer(is, "audio/midi");
      //调用prefetch()方法开始获取音频并减少滞后
musicPlayer.prefetch();
      
    }
    catch (IOException ioe) {
    }
    catch (MediaException me) {
    }
这样我们紧接着就可以播放他了,当然如果你要在其他的地方播放不同的声音,就要在不同的地方不同的时间控制对声音的播放:
// 开始播放背景音乐
    try {
      //设定循环次数,-1表示无限次数的重复
      musicPlayer.setLoopCount(-1);
      //调用start()方法开始播放
      musicPlayer.start();
    }
    catch (MediaException me) {
    }

最后当调用stop ( )方法停止游戏时,我们应将声音关闭:
musicPlayer . close ( ) ;

好了,这里我们虽然只添加了一个背景音乐,但是相信你一定会在其他的地方添加不同的生效了。
至此,我们的游戏探索也告一段落了,在你的手机上试一试我们的游戏吧!