标题：J2ME小游戏
简介
一个J2ME的2D游戏技术DEMO源码  
计划了很久了，准备作为J2ME的Game APIs的例子贴出来，无奈一直不得空，直到最近才简单地整理了一下，把它共享出来。
游戏特色
严格地讲，这个不能算作一个完整的游戏，没有自己的创意，只是简单地模仿了一个Flash的小游戏，当时是为了自己练习用的，Game Play也很简单，但包含了J2ME的Game包中所有的东西，作为一个Demo来讲，内容还是很充实的。
Gaming API分类
在介绍之前，先简单地提一下Gaming API中的几个类，包括Sprite，TiledLayer,LayerManager,Media.Player等。Sprite：偶尔看到过有些人把它翻译成精灵，我偷懒一下，就不翻译了，这个类用来表达游戏中的一个活动的角色，包括玩家控制的Player角色和非玩家控制的角色（NPC）。TiledLayer：这个是用来表达背景的类，其实从绘图的角度来看，Sprite和TiledLayer没有本质差别，只是将要画在屏幕上的一幅图像而已，因此在Game包中它们都是Layer类的子类。并且都能够从一幅图像方便地构造。LayerManager：这个是用来管理所有图像对象的类，通过把Sprite和TiledLayer加入其中，J2ME设备就知道如何来绘制它们了。GameCanvas：最后要提到的是画布，这是所有可视对象最终要表演的舞台。其实跟以前的Canvas没有本质的不同，同样是提供了一个Graphics接口来供把一些内容画上去而已，但增加了对玩家输入的处理，能够通过按键状态来直接读入玩家按键操作，相对更加简便了。
还利用到了Media包中的Player和ToneControl来播放音乐，没有音乐和声音的游戏是不可能出现的，呵呵。
操作指南
先简单地介绍一下这个游戏，玩家只能控制角色（一个端着网兜的小人）水平地移动，接住自然下落的小球就得分，积分到一定程度后，小球下落速度将加快，直到最高速为止；如果没接到小球，也有相应的惩罚，最终游戏会Game Over。
为了避免过于单调，玩家角色不是简单地平移，而是利用了Sprite的简单帧动画来让角色看上去有些动作。其实很方便的，只是在移动位置时更换一下图像就行了，Sprite提供有几个方法NextFrame()，PrevFrame()用来切换。
声音部分就更简单了，只是重复地播放一段预先写进去的音乐，来自Sun的WTK中的一个例子。
不过既然提到了它，就还是先简单地说一下吧，免得后面介绍其他部分时有些疑问。
J2ME中的声音部分非常简单，当然效果也不太好，所需的基本元素只有如下几个，一个内容部分的Byte系列；一个是播放器Player；还有一个是控制部分的ToneControl。代码示例如下：
tonePlayer = Manager.createPlayer(Manager.TONE_DEVICE_LOCATOR);
tonePlayer.setLoopCount(-1);
tonePlayer.realize();
ToneControl tc = (ToneControl)tonePlayer.getControl("javax.microedition.media.control.ToneControl");
tc.setSequence(mySequence);
当然还需要些初始化工作和异常处理，然后就可以通过tonePlayer.start()/close()方法来控制声音的播放和停止了。
具体请参见源码中CanvasGetBall.java中的createTonePlayer()方法。
锣鼓响了半天，主角也该出场了。这个游戏里的主角只有一个接球小人，不过角色还有一个跟它演对手戏的NPC，就是那只从天而降的小球了。这两个类都是Sprite的子类，小球因为有些自己的动作，同时实现了Runnable接口，能自主活动。不过也很简单，NPC嘛，一般来说都是相对弱智些，不然也没法玩了，谁的反应速度跟得上机器啊，再说了，NPC知道的信息也要多些:)。
先来看看主角吧，BallPlayer类就是我们的主角，其实非常简单，在所有的6个类中，除了记分用的Score外就数它最小了。提供一幅图像把它实例化后，就只能通过左右移动来控制了，额外的几个方法都是跟记分有关系的，先略过不提。
先来看看它的构造方法：
public BallPlayer(Image img, int fw, int fh) {
super(img, fw, fh);
this.step = fw / 2;
}
主要工作都由它的父类Sprite做了，给出一幅图像，这个图像是用PNG格式提供的，大家可能留意到不是一幅单一的图像，而是有点象帧动画中的几个关键帧，不错，的确如此，构造方法中的后两个参数就是告诉Sprite如何分割这幅图像的。这里整个Player共有6个关键帧，比较粗糙，呵呵，自己动手截屏做的:)
Field step是用来控制主角的移动步伐的，为了快一点，取了它身宽的一半。
接下来我们看看如何移动它，就是通过这样两个方法来左移和右移。
public void left() {
prevFrame();
if( (getX() - step) >= -12 ) {
move( -1 * step, 0);
}
}
public void right () {
nextFrame();
if((getX() + step) < canvas.getWidth()) {
move(step, 0);
}
}
留意一下，这里只管相对位移，主角的开始位置通过setPosition来设定，在运动过程中最好就不要直接设置位置了，增大计算量，要不就看起来动作不自然了。
接下来简单说一下配角－－球。球的构造跟主角类似，只是为了节省构造销毁对象带来的开销，这个对象是一直存在的，也就是让它掉下去了又自己起来，并根据记分来确定下落速度，简单地用线程实现的，没怎么仔细设计，大家看看代码就清楚了。
再来看看CanvasGetBall这个类，它从GameCanvas继承，并实现了CommandListener和Runnable两个接口，是整个游戏中最复杂的一个类了，主要工作有如下几个部分，实例化主角，配角对象，还有背景对象，音乐等，并在适当的时候画出这些对象，在顶部画出些状态信息，并根据玩家操作开始和暂停游戏，并显示相应画面。并通过进行碰撞检测来判定玩家是否接到了小球。
检测方法
检测方法如下：
private boolean notMiss( ) {
//    return player.collidesWith(ball,false);
int ballCX = ball.getX() + ball.getWidth()/2;
int ballCY = ball.getY() + ball.getHeight()/2;
int playerCX = player.getX() + player.getWidth()/2;
int playerCY = player.getY() + player.getHeight()/2;
return ((Math.abs(playerCX - ballCX)< ball.getWidth()/2) &&
(Math.abs(ballCY - playerCY) < 5));
}
被注释掉的一行是直接用Sprite的碰撞检测，下面的部分是自己计算两幅图像有没有重叠，效果差不多。其中collidesWith()的第二个参数是告诉内部方法是否要用像素级别的检测，通常答案是千万不要，这很慢的，而且没有必要这么精确。
为了说明整个游戏的控制逻辑，我们先来看看MIDletGetBall这个类，跟通常的MIDlet略有不同，因为我把主线程放在了CanvasGetBall中，MIDletGetBall只是简单地控制主线程就行了。
public void startMainThread() {
Display.getDisplay(this).setCurrent(displayable);
if(mainThread != null) {
mainThread = null;
Runtime.getRuntime().gc();
}
mainThread = new Thread(displayable);
mainThread.start();
}
其中第一行就是设置当前显示页面；也就是显示CanvasGetBall。
回到CanvasGetBall，整个游戏分几个阶段，相应有不同的画面和命令接口，详细说明如下：
1. 等待开始，对应在方法ready(): 
public void ready() {
cover.setTitle(TIPS[2]);
cover.addCommand(playCommand);
Display.getDisplay(MIDletGetBall.instance).setCurrent(cover);
}
为了绘制方便，这里单独用了个GameCanvas来绘制提示信息和响应命令，并根据玩家操作在CanvasCover和CanvasGetBall两个画面之间来回切换。
2. 游戏画面，包括启动和结束两个方法：
public void start() {
if(!playing) {
strTip = TIPS[0];
playing = true;
MIDletGetBall.instance.startMainThread();
removeCommand(playCommand);
removeCommand(resumeCommand);
addCommand(pauseCommand);
ball.start();
try {
if(tonePlayer != null) {
tonePlayer.start();
}
}
catch (MediaException ex) {
tonePlayer.close();
tonePlayer = null;
}
}
}
public void stop () {
if(playing) {
ball.stop();
strTip = TIPS[1];
try {
Thread.sleep(300);
}
catch (InterruptedException ex) {
}
playing = false;
removeCommand(pauseCommand);
addCommand(resumeCommand);
try {
tonePlayer.stop();
}
catch (MediaException ex1) {
tonePlayer.close();
tonePlayer = null;
}
}
}
并对应设置相应的命令来让玩家能够继续下去，构成一个简单的封闭控制环路。
3.游戏结束，对应方法gameover()
public void gameover() {
this.stop();
cover.setTitle(TIPS[3]);
cover.removeCommand(playCommand);
cover.addCommand(restartCommand);
Display.getDisplay(MIDletGetBall.instance).setCurrent(cover);
}
说到这里，基本上也就把它讲完了，具体内容请详细研究源码，其实没必要看太多书，深入地研究一个问题并根据自己的理解来改进或者是修正它，实践才是最好的老师，希望大家能够有所收获。
存在的问题
总结一下，这个游戏存在的问题有如下几个：
1. 没有好的Game Play，画面很差；
2. 可玩性不强，控制比较单调；
3. 游戏声音过于单调；
4. 运行速度有些慢。
但作为一个技术Demo，它涵盖了Game包中的所有内容，并提供了一个利用线程方式实现简单游戏的方法，很简单，但不适合真实的游戏，比较费时。
背景处理很差，可以通过一个Map来分割组合处理背景小片，能让游戏场景变得生动些，可以实现类似于卷轴游戏的效果，自己试试吧！
附：源代码和工程，在JBuilderX下编译，同时需要WTK2.0或以上版本。好久没有用Jbuilder了，买不起正版:),现在主要开发工具是Eclipse和EclipseME,感觉非常爽，免费的也有好货。