1、J2ME中文教程(6) GAME API第 6章 GAME API6.1 游戏 API简介6.2 GameCanvas的使用6.2.1 绘图6.2.2 键盘6.3 Sprite的使用6.3.1 Sprite帧6.3.2 帧序列6.3.3 Reference Pixel6.3.4 Sprite的变换6.3.5 绘制 Sprite6.3.6 碰撞检测6.4 Layer的使用6.4.1 TiledLayer6.4.2 LayerManager6.5 一个示例6.6 小结6.1 游戏 API简介MIDP 2.0相对于 1.0来说,最大的变化就是新添加了用于支持游戏的 API,它们被放在 javax.m
2、icroedition.lcdui.game包中。游戏 API包提供了一系列针对无线设备的游戏开发类。由于无线设备仅有有限的计算能力,因此许多 API的目的在于提高Java游戏的性能,并且把原来很多需要手动编写的代码如屏幕双缓冲、图像剪裁等都交给 API间接调用本地代码来实现。各厂家有相当大的自由来优化它们。游戏 API使用了 MIDP的低级图形类接口(Graphics,Image,等等)。整个 game包仅有 5个 Class:GameCanvas这个类是 LCDUI的 Canvas类的子类,为游戏提供了基本的“屏幕”功能。除了从Canvas继承下来的方法外,这个类还提供了游戏专用的功能,如
3、查询当前游戏键状态的能力,同步图像输出;这些功能简化了游戏开发并提高了性能。LayerLayer类代表游戏中的一个可视化元素,例如 Sprite或 TiledLayer是它的子类;这个抽象类搭好了层(Layer)的基本框架并提供了一些基本的属性,如位置,大小,可视与否。出于优化的考虑,不允许直接产生 Layer的子类(不能包外继承)。LayerManager对于有着许多 Layer的游戏而言,LayerManager 通过实现分层次的自动渲染,从而简化了游戏开发。它允许开发者设置一个可视窗口(View Window),表示用户在游戏中可见的窗口; LayerManager 自动渲染游戏中的 L
4、ayer,从而实现期望的视图效果。SpriteSprite又称“精灵”,也是一种 Layer,可以显示一帧或多帧的连续图像。但所有的帧都是相同大小的,并且由一个 Image对象提供。Sprite 通过循环显示每一帧,可以实现任意顺序的动画;Sprite 类还提供了许多变换(翻转和旋转)模式和碰撞检测方法,能大大简化游戏逻辑的实现。TiledLayerTiledLayer又称“砖块”,这个类允许开发者在不必使用非常大的 Image对象的情况下创建一个大的图像内容。TiledLayer 有许多单元格构成,每个单元格能显示由一个单一 Image对象提供的一组贴图中的某一个贴图。单元格也能被动画贴图填
5、充,动画贴图的内容能非常迅速地变化;这个功能对于动画显示非常大的一组单元格非常有用,例如一个充满水的动态区域。 在游戏中,某些方法如果改变了 Layer,LayerManager,Sprite 和 TiledLayer对象的状态,通常并不能立刻显示出视觉变化。因为这些状态仅仅存储在对象里,只有当随后调用我们自己的 paint()方法时才会更新显示。这种模式非常适合游戏程序,因为在一个游戏循环中,一些对象的状态会更新,在每个循环的最后,整个屏幕才会被重绘。基于轮询也是现在视频游戏的基本结构。6.2 GameCanvas的使用GameCanvas类提供了基本的游戏用户接口。除了从 Canvas继承
6、下来的特性(命令,输入事件等)以外,它还提供了专门针对游戏的功能,比如后备屏幕缓冲和键盘状态查询的能力。每个 GameCanvas实例都会有一个为之创建的专用的缓冲区。因为每个 GameCanvas实例都会有一个唯一的缓冲区。可以从 GameCanvas实例获得其对应的 Graphics对象,而且,只有对 Graphics对象操作,才会修改缓冲区的内容。外部资源如其他的 MIDlet或者系统级的通知都不会导致缓冲区内容被修改。该缓冲区在初始化时被填充为白色。缓冲区大小被设置为 GameCanvas的最大尺度。然而,当请求填充时,可被填充的区域大小会受限于当前 GameCanvas的尺度,一个存
7、在的 Ticker,Command 等等都会影响到 GameCanvas的大小。GameCanvas 的当前大小可以通过调用 getWidth和getHeight获得。一个游戏可能提供自己的线程来运行游戏循环。一个典型的循环将检查输入,实现游戏逻辑,然后渲染更新后的用户界面。以下代码演示了一个典型的游戏循环的结构:/ 从后备屏幕缓冲获得 Graphics对象Graphics g = getGraphics();while (true) / 检查用户输入并更新位置,如果有需要int keyState = getKeyStates();if (keyState else if (keyState
8、/ 将背景清除成白色g.setColor(0xFFFFFF);g.fillRect(0,0,getWidth(), getHeig ht(); / 绘制 Sprite(精灵)sprite.paint(g);/ 输出后备缓冲区的内容flushGraphics();6.2.1 绘图要创建一个新的 GameCanvas实例,只能通过继承并调用父类的构造函数:protected GameCanvas(boolean suppressKeyEvents),这将使为 GameCanvas准备的一个新的缓冲区也被创建并在初始化时被填充为白色。为了在 GameCanvas上绘图,首先要获得 Graphics对
9、象来渲染 GameCanvas:protected Graphics getGraphics()返回的 Graphics对象将用于渲染属于这个 GameCanvas的后备屏幕缓冲区(off-screen buffer)。 但是渲染结果不会立刻显示出来,直到调用 flushGraphics()方法;输出缓冲区的内容也不会改变缓冲区的内容,即输出操作不会清除缓冲区的像素。每次调用这个方法时,都会创建一个新的 Graphics对象;对于每个 GameCanvas实例,获得的多个 Graphics对象都将渲染同一个后备屏幕缓冲区。因此,有必要在游戏启动前获得并存储 Graphics对象,以便游戏运行时
10、能反复使用。刚创建的 Graphics对象有以下属性: 渲染目标是这个 GameCanvas的缓冲区; 渲染区域覆盖整个缓冲区; 当前颜色是黑色(black); 字体和调用 Font.getDefaultFont()返回的相同; 绘图模式为 SOLID; 坐标系统的原点定位在缓冲区的左上角。在完成了绘图操作后,可以使用 flushGraphics()方法将后备屏幕缓冲区的内容输出到显示屏上。输出区域的大小与 GameCanv as的大小相同。输出操作不会改变后备屏幕缓冲区的内容。这个方法会直到输出操作完成后才返回,因此,当这个方法返回时,应用程序可以立刻对缓冲区进行下一帧的渲染。 如果 Gam
11、eCanvas当前没有被显示,或者系统忙而不能执行输出请求,这个方法不做任何事就立刻返回。6.2.2 键盘如果需要,开发者可以随时调用 getKeyStates方法来查询键的状态。getKeyStates()获取游戏的物理键状态。返回值的每个比特位都表示设备上的一个特定的键。如果一个键对应的比特位的值为 1,表示该键当前被按下,或者自上次调用此方法后到现在,至少被按下过一次。如果一个键对应的比特位的值为 0,表示该键当前未被按下,并且自上次调用此方法后到现在从未被按下过。这种“闭锁行为(latching behavior)”保证一个快速的按键和释放总是能够在游戏循环中被捕获,不管循环有多慢。下
12、面是获取游戏按键的示例:/ 获得键的状态并存储int keyState = getKeyStates();if (keyState else if (keyState 调用这个方法的副作用是不能及时清除过期的状态。在一个 getKeyStates调用后如果紧接着另一个调用,键的当前状态将取决于系统是否已经清除了上一次调用后的结果。某些设备可能无法直接访问键盘硬件,因此,这个方法可能是通过监视键的按下和释放事件来实现的,这会导致 getKeyStates可能滞后于当前物理键的状态,因为时延取决于每个设备的性能。某些设备还可能没有探测多个键同时按下的能力。请注意,除非 GameCanvas当前可见
13、(通过调用 Displayable.isShown()方法) ,否则此方法返回 0。一旦 GameCanvas变为可见,将初始化所有键为未按下状态(0)。6.3 Sprite的使用Sprite是一个基本的可视元素,可以用存储在图像中的一帧或多帧来渲染它;轮流显示不同的帧可以令 Sprite实现动画。翻转和旋转等几种变换方式也能应用于Sprite使之外观改变。作为 Layer子类,Sprite 的位置可以改变,并且还能设置其可视与否。6.3.1 Sprite帧用于渲染 Sprite的原始帧由一个单独的 Image对象提供,此 Image可以是可变的,也可以是不可变的。如果使用多帧,图像将按照指定
14、的宽度和高度被切割成一系列相同大小的帧。正如下图所示,同一序列的帧可以以不同的排列存储,这取决于游戏开发者是否方便开发。每一帧都被赋予一个唯一的索引号。左上角的帧被赋予索引号 0。余下的帧按照行的顺序索引号依次递增(索引号从第一行开始,接着是第二行,以此类推) 。getRawFrameCount()方法返回所有原始帧的总数。 6.3.2 帧序列Sprite的帧序列定义了帧以什么样的顺序来显示。缺省的帧序列就是所有可用帧的顺序排列,因此,帧序列和对应的帧的索引号是一致的。这表示缺省的帧序列的长度和所有原始帧的总数是相等的。例如,如果一个 Sprite有 4帧,缺省的帧序列为0, 1, 2, 3。
15、可以使用 setFrameSequence(int sequence)来为 Sprite设置帧序列。当调用此方法时,将会复制 sequence数组;因此,随后对参数 sequence数组进行的任何更改均不会影响 Sprite的帧序列。传入 null将使 Sprite的帧序列重置为缺省值。开发者必须在帧序列中手动切换当前帧。可以调用 setFrame(int),prevFrame()或者 nextFrame()方法来完成。注意,这些方法是针对帧序列操作,而不是对帧的索引操作。如果使用缺省的帧序列,那么帧序列的索引和帧的索引是可互换的。如果愿意,可以为 Sprite定义任意的帧序列。帧序列必须至少
16、包含一个元素,并且每个元素都必须是一个有效的帧的索引号。通过定义新的帧序列,开发者可以方便地以任意想要的顺序来显示 Sprite的帧;帧可以重复,忽略,或者以相反的顺序显示,等等。例如,下图显示了一个特定的序列如何被用于动画显示一个蚊子。帧序列被设计为蚊子振动翅膀 3次,然后在下次循环前暂停一会儿。每次调用 nextFrame()方法就会更新显示,动画效果如下:要创建一个静态的 Sprite,可以调用 public Sprite(Image image),通过提供的图像创建一个新的 Sprite。如果要创建动态的 Sprite,就必须使用 public Sprite(Image image,i
17、nt frameWidth, int frameHeight)。帧的大小由 frameWidth和 frameHeight指定,所有帧的大小必须相等。可以在图像中水平、竖直或以方格形式排列。源图像的宽度必须是帧宽度的整数倍,高度必须是帧高度的整数倍。如果 image的宽度或高度不是 frameWidth或 frameHeight的整数倍,将会抛出 IllegalArgumentException异常。6.3.3 Reference Pixel作为 Layer的一个子类,Sprite 继承了很多方法来设置和获取位置,如setPosition(x,y),getX()和 getY()。这些方法定义的
18、位置均以 Sprite视图区域的左上角像素点为依据。然而,在某些情况下,根据 Sprite的其它像素点来定位 Sprite更加方便,尤其是在Sprite上应用某些转换。 因此,Sprite 包含一个参考像素点(reference pixel)的概念。参考像素点通过指定其在 Sprite未经变换的帧内的某一点来定义,使用 defineReferencePixel(x,y)方法。缺省的,参考像素点定义在帧的(0,0)像素点。如果有必要,参考像素点也可以定义在帧区域以外。在这个例子中,参考像素点被定义在猴子悬挂的手上:getRefPixelX()和 getRefPixelY()方法可用于查询参考像素
19、点在绘图坐标系统中的位置。开发者也可以调用 setRefPixelPosition(x,y)方法来定位 Sprite,使得参考像素点定义在绘图坐标系统中的指定位置。这些方法自动地适应任何应用在 Sprite上的变换。在这个例子中,参考像素点被定位在树枝末端的一点;Sprite 的位置也改变了,使得参考像素点定位在这一点上,猴子看起来像挂在树枝上。6.3.4 Sprite的变换几种变换可应用于 Sprite。可用的变换包括旋转几个 90度加上镜像(沿垂直轴) 。Sprite的变换通过调用 setTransform(transform)方法实现。当应用一个变换时,Sprite 被自动重新定位,使得
20、参考像素点在绘图坐标系统中看起来是静止的。因此,参考像素点即为变换操作的中心点。因为参考像素点并未移动,getRefPixelX()和 getRefPixelY()方法返回的值仍不变;但是,getX()和 getY()方法可能改变以便反映出 Sprite左上角位置的移动。再次回到猴子的例子上来,当应用一个 90度旋转后,参考像素点的位置仍然在(48, 22),因此使得猴子像是在沿着树枝飘着: 由于某些变换涉及到 90度或 270度旋转,其使用结果可能导致 Sprite的宽度和高度互换。因此,调用 Layer.getWidth()和 Layer.getHeight()方法的返回值可能改变。绘制
21、Sprite 可以在任何时候通过调用 paint(Graphics)方法来绘制 Sprite。 Sprite将被绘制在 Graphics对象上,根据 Sprite保持的当前状态信息(如位置,帧,可视与否) 。擦除 Sprite通常是 Sprite以外的类的责任。厂商可以使用任何希望使用的技术(如硬件加速可以用于所有 Sprite,或特定大小的 Sprite,或者根本不使用硬件加速)来实现 Sprite。对一些平台而言,特定大小的 Sprite可能对于其它大小的 Sprite更高效;厂商可以选择提供给开发者关于设备相关的这些特性。6.3.6 碰撞检测Sprite非常适合移动的物体,如游戏主角、敌
22、人等等,在游戏中,可以使用Sprite提供的碰撞检测功能来简化游戏逻辑。使用 defineCollisionRectangle()定义用于碰撞检测的 Sprite的矩形区域。此指定的矩形是相对于未经变换的 Sprite的左上角,该区域将用于检测碰撞。对于像素级的碰撞检测,仅仅在这个碰撞检测区内部的像素点会被检查。缺省的,Sprite 的碰撞检测区定位在(0,0),并与 Sprite尺度相同。碰撞检测区也可以指定为大于或小于缺省的碰撞检测区;如果大于缺省的碰撞检测区,在 Sprite之外的像素在像素级的碰撞检测时被认为是透明的。要判断两个 Sprite是否碰撞,或者与其他 Layer是否碰撞,可
23、以使用collidesWith()方法。如果使用像素级检测,仅当非透明像素重叠时,碰撞才被检测到。即第一个 Sprite中的非透明像素和第二个 Sprite中的非透明像素重叠时,碰撞才被检测到。仅仅那些包含在 Sprite的碰撞检测区内的像素会被检测。如果不使用像素级检测,这个方法就简单地检查两个 Sprite的碰撞检测区矩形是否有重合。 如果对 Sprite应用了变换,会进行相应的处理。注意,只有两个 Sprite都可见时,才能检测碰撞。6.4 Layer的使用Layer是一个抽象类,表示游戏中的一个可视元素。上节中讲述的 Sprite就是Layer的一种。每个 Layer都有位置(取决于它
24、的左上角在其容器中的位置) ,宽度,高度和可视与否。 Layer的子类必须实现一个 paint(Graphics)方法,使得它们能够被渲染。如果该 Layer可见。 Layer从它的左上角开始渲染,其当前坐标(x,y)是相对于原始的 Graphics对象。当渲染 Layer时,应用程序可以使用剪辑和坐标变换来控制并限制渲染的区域。实现此方法的子类有责任检查 Layer是否可见,如果不可见,这个方法应该不做任何事。此外,调用此方法不应该改变 Graphics对象的属性(剪辑区域,坐标变换,绘图颜色等等) 。Layer的位置坐标(x,y)通常都是相对于 Graphics对象的坐标系统,该对象通过
25、Layer的 paint()方法传递。这个坐标系统被称为绘图坐标系统。一个 Layer的初始位置是(0,0)。 6.4.1 TiledLayerTiledLayer由一系列单元格组成,单元格可被一组贴图填充。这个类允许不必使用特别大的图像来创建大的虚拟层。这个技术在 2D游戏中被广泛用于创建特别大的可卷动的背景。贴图(Tiles)贴图用于填充 TiledLayer的单元格,由一个单一的可变或不可变的 Image对象提供。图像被切割成一系列相同大小的贴图;贴图大小随 Image一同指定。如下图所示,相同的一系列贴图可以以不同的方式存储,取决于对游戏开发者而言方便与否。每个贴图都被赋予一个唯一的索
26、引号。位于图像最左上角的贴图被赋予索引号 1。剩下的贴图按照一行一行的顺序(首先是第一行,然后是第二行,以此类推)依次递增。这些贴图被视为静态贴图(static tiles),因为贴图和图像内容有固定的联系。 当实例化一个 TiledLayer时,一组静态贴图就被创建了;也可以在任何时候调用setStaticTileSet(javax.microedition.lcdui.Image, int, int)方法来更新它们。 除了静态贴图外,开发者同样能够定义一系列动态贴图(animated tiles)。一个动态贴图就是一个虚拟的贴图,它与一个静态贴图动态地联系在一起;一个动态贴图的外观就是当时
27、与之联系的静态贴图。动态贴图允许开发者能非常容易地改变一组单元格的外观。对于用动态贴图填充的单元格而言,改变它们的外观仅仅需要简单地改变与动态贴图关联的静态贴图即可。此技术对于动画显示大的重复性区域非常有用,因为不需要显式地改变大量单元格的内容。动态贴图可以通过调用 createAnimatedTile(int)方法来创建,该方法返回一个索引号,用于标记新创建的动态贴图。动态贴图的索引号总是负数,并且也是连续的,起始值为-1。一旦被创建,与之关联的静态贴图可以通过调用 setAnimatedTile(int, int)方法来改变。单元格(Cells)TiledLayer由相同大小的单元格组成;
28、每行和每列的单元格数目在构造方法中指定,实际大小取决于贴图的大小。每个单元格的内容由贴图索引号指定;一个正的贴图索引号代表一个静态贴图,一个负的贴图索引号代表一个动态贴图。索引号为 0的贴图表示该单元格为空;为空的单元格是完全透明的,并且不会被 TiledLayer绘制任何内容。缺省的,所有单元格都包含索引号为 0的贴图。 可以通过调用 setCell(int, int, int)和 fillCells(int, int, int, int, int)方法改变单元格的内容。很多单元格可以包含同一个贴图;然而,一个单元格仅能包含一个贴图。下面的例子演示了如何使用 TiledLayer来创建一个简
29、单的背景。在这个例子中,水的区域由动态贴图来填充,索引号为-1,该动态贴图在初始化时与一个索引号为 5的静态贴图关联。可以简单地通过调用 setAnimatedTile(-1, 7)方法来改变与之联系的静态贴图,从而实现整个水区域的动画效果。渲染一个 TiledLayer可以手动调用 paint()方法来渲染一个 TiledLayer;也可以使用 LayerManager对象自动渲染它。绘图方法将尝试渲染在 Graphics对象的剪裁区域内的整个 TiledLayer;从TiledLayer的左上角开始渲染,该点的当前坐标(x,y)相对于 Graphics对象的原点。渲染区域可以通过设置 Gr
30、aphics对象的剪裁区域来控制。 6.4.2 LayerManagerLayerManager管理一系列的 Layer。LayerManager 简化了渲染每个 Layer的过程,每个添加的 Layer都将在正确的区域并以正确的顺序被渲染。LayerManager维护一个顺序列表,以便管理如何追加、插入和删除 Layer。一个Layer的索引号关联了它的 Z轴位置(z-order);索引号为 0的 Layer最接近用户,索引号越大的 Layer离用户越远。索引号永远是连续的,即,如果一个 Layer被删除,后面的 Layer的索引号都将调整使得索引号保持连续。LayerManager类提供一
31、些用于控制游戏中如何在屏幕上渲染 Layer的功能。可视窗口(view window)控制着可视区域及其在 LayerManager的坐标系统中的位置。改变可视窗口的位置可以实现上下或左右滚动屏幕的效果。例如,如果想向右移动,简单地将可视窗口的位置右移。可视窗口的大小决定了用户的可视范围,通常它应该适合设备的屏幕大小。 在这个例子中,可视窗口被设置为 85x85像素大小,并定位在 LayerManager的坐标系统的(52, 11)点。每个 Layer的位置都是相对于 LayerManager的原点。paint(Graphics, int, int)方法包含一个(x,y)坐标,控制可视窗口在屏
32、幕中的显示位置。改变参数不会改变可视窗口的内容,仅仅简单地改变可视窗口在屏幕中被绘制的位置。注意到这个位置是相对于 Graphics对象的原点而言的,因此它服从Graphics对象的变换属性。例如,如果一个游戏在屏幕的最顶端显示分数,可视窗口可能在(17,17)点被渲染,确保有足够的空间来显示分数。为了添加一个 Layer,我们使用 append()方法向这个 LayerManager添加一个Layer。Layer 将被添加到现有 Layer列表的末尾,即有最大的索引号(离用户最远) 。如果此 Layer已存在,将在添加前首先被删除。insert()方法与 append()的区别在于可以指定
33、Layer的索引号。如果此 Layer已存在,将在添加前首先被删除。获得指定位置的 Layer可以调用 getLayerAt(int index)方法。 渲染LayerManager的 paint()方法以降序的顺序来渲染每一个 Layer,以保证实现正确的 Z轴次序。完全在可视窗口之外的 Layer将不被渲染。此方法的另外两个参数决定了 LayerManager的可视窗口相对于 Graphics对象的原点在何处渲染。例如,一个游戏可能在屏幕上方显示分数,因此游戏的 Layer就必须在这个区域下面,可视窗口可能在点(0, 20)处开始渲染。此位置相对于 Graphics对象的原点,因此 Gra
34、phics对象的坐标转换模式也会影响可视窗口在屏幕上渲染的位置。Graphics对象的剪裁区域被设置为与位于(x,y)处的可视窗口的区域一致。 LayerManager将转换 Graphics对象的坐标,使得点(x,y)与可视窗口在 LayerManager中的坐标系统的位置一致。然后,Layer 以一定的次序被渲染。在方法返回前,Graphics对象的坐标转换模式和剪裁区将重置为原先的值。 渲染会自动适应 Graphics对象的剪裁区域和变换方式。这样,如果剪裁区域不够大,可视窗口仅有部分被渲染。为了提升速度,这个方法可能忽略不可见的 Layer,或者全部在 Graphics对象剪裁区域以外
35、的 Layer。在调用 Layer的 paint()方法前,Graphics 对象并不会重置为一个确定的状态。剪裁区域可能大于 Layer的区域,因此,Layer 必须自己保证渲染操作在其范围内进行。6.5 一个示例这里给出一个示例游戏的核心代码。这是一个著名的潜艇游戏的手机版本。由黄晔开发。这是一个良好的游戏入门范本,其中涉及到精灵的使用、Tiled 的使用以及碰撞检测等运动类 2D-Tile-based游戏常见的问题。希望通过对他的学习给你一些启示。为了配合本章 API的讲解,我们省略的大部分的游戏周边代码,这里给出的仅仅是游戏的 GameCanvas子类。并且这个类同时包含了游戏的主循环
36、线程。用于教学是再好不过了。如你所见的,这不是一个产品质量的游戏。他教会你基本的内容,而不是全部。同样这里没有包括什么优化。一下是游戏的截图。另外本游戏所用的资源大多不属于作者,代码仅供非商业用途的学习参考。在此强调想要编译游戏你需要下载完整的源代码。请着重注意代码中的一下方法: paintCanvas方法用于渲染; run方法用于游戏的主循环; 关于输入捕获的的一点说明是,这个游戏并没有屏蔽键盘事件,他混合使用了主动轮询用于潜艇运动,而开火则采用捕获方式。/* * Author: Huang ye()* 代码开源, 引用请注明出处 * * 创建日期 2005-2-24*/package ne
37、t.hyweb;import javax.microedition.lcdui.*;import javax.microedition.lcdui.game.GameCanvas;import javax.microedition.lcdui.game.*;import javax.microedition.lcdui.game.LayerManager;import java.util.*;/* author Huang ye */public class SubCanvas extends GameCanvas implements Runnable, CommandListener /*
38、 * uml.property name=“subMIDlet“* uml.associationEnd multiplicity=“(0 1)“*/private Controller controller;private Graphics graphics;private Thread thread;private boolean threadAlive = false;private Command startCommand;private Command exitCommand;private Command pauseCommand;/图层数据private LayerManager
39、 layerManager;private TiledLayer layerSeaback;private Sprite spriteMap;public final int GAME_INIT = 0; /游戏初始状态public final int GAME_RUN = 1; /游戏运行状态public final int GAME_OVER = 4; /游戏结束状态public final int GAME_PAUSE = 5;public final int GAME_SUSPEND = 9; /暂停状态public boolean COMMAND_ADD_FLAG = false;
40、/是否已经添加Command标识public static final int TROOP_PLAYER = 0; /敌我标识public static final int TROOP_ENEMY = 1;public static int PLAYER_LEVEL = 1; /当前玩家水平public static int ENEMY_MAX = PLAYER_LEVEL * 10; /最大敌人数量public static int ENEMY_CURRENT = 0; /当前敌人数量public static int ENEMY_CURRENT_LIMIT = 0; /当前敌人数量限制pr
41、otected int TRIGGER_COUNT = 0; /拖延标识,避免敌人新潜艇同一时刻全部产生protected int TICK_COUNT = 0;public static int mainWidth; /屏幕宽度 public static int mainHeight; /屏幕高度public int gameState; /游戏状态public final static int TILE_WIDTH = 10; /背景 单元宽度 10pxpublic final static int TILE_HEIGHT = 10; /背景 单元高度 10px/* 动画变化频率(200
42、ms)* MILLIS_PER_TICK 的注释*/public final static int MILLIS_PER_TICK = 200;private final static int WIDTH_IN_TILES = 45; /游戏域宽度(以单元宽度计算) 16 Nprivate final static int HEIGHT_IN_TILES = 24; /游戏域高度(以单元高度计算)private final static int NUM_DENSITY_LAYERS = 4; /海面密度(背景图层)private int rotations = Sprite.TRANS_NON
43、E,Sprite.TRANS_MIRROR, Sprite.TRANS_MIRROR_ROT90, Sprite.TRANS_MIRROR_ROT180, Sprite.TRANS_MIRROR_ROT270, Sprite.TRANS_ROT90, Sprite.TRANS_ROT180, Sprite.TRANS_ROT270;/*整个游戏背景宽度(以象素计算 : 宽度单元数 * 宽度单元象素) * WORLD_WIDTH 的注释*/public final static int WORLD_WIDTH = WIDTH_IN_TILES * TILE_WIDTH; /* 整个游戏背景高度(
44、以象素计算 : 高度单元数 * 高度单元象素)* WORLD_HEIGHTH 的注释*/public final static int WORLD_HEIGHT = HEIGHT_IN_TILES * TILE_HEIGHT;private final static int NUM_DENSITY_LAYER_TILES = 4; /每一个密度层的 TILE单元数private final static int FRACT_DENSITY_LAYER_ANIMATE = 20;private int SEABACK_DENSITY; /游戏初始海水密度为 0/private final Vec
45、tor oceanLayersVector = new Vector();private Vector fishCollectionVector = new Vector();public Vector enemyCollectionVector = new Vector();public Vector tinfishCollectionVector = new Vector();/* * uml.property name=“mySub“* uml.associationEnd multiplicity=“(0 1)“*/private Sub mySub = null;private Ru
46、ntime rt = null;/初始化为不使用窗口区域视野private boolean userViewWindow = false;/创建不稳定的动画线程private volatile Thread animationThread = null;/LayerManager的偏移坐标private int xViewWindow;private int yViewWindow;private int wViewWindow;private int hViewWindow;public SubCanvas(Controller controller)/不屏蔽键盘事件(潜艇运动采用主动轮询,
47、而开火则采用捕获方式)super(false);this.controller = controller;this.graphics = getGraphics();this.layerManager = new LayerManager();/决定图层显示方式init();/画布构造即建立玩家潜艇/初始位置为屏幕的(1/3, 1/3)位置mySub = new Sub(this, SubMIDlet.createImage(“/res/sub.png“), mainWidth / 3, mainHeight / 3, layerManager);/监测运行时,以便及时运行垃圾回收rt = R
48、untime.getRuntime(); startCommand = new Command(“Start“, Command.OK, 1);pauseCommand = new Command(“Pause“, Command.OK, 1);exitCommand = new Command(“Exit“, Command.EXIT, 2);/初始化其它类及图层/初始化游戏状态this.gameState = this.GAME_INIT; /游戏处于 demo画面状态/启动应用程序threadAlive = true;thread = new Thread(this);thread.start();/* 初始化地图数据 和 地图窗口显示方式*/private void init()/清理数据this.clearData();mainWidth = getWidth();mainHeight = getHeight();/判断是否使用预览模式窗口/根据显示设备,设置合适的最大区和显示视野this.xViewWindow = 0; if(WORLD_WIDTH mainWidth)/现有设备不能容纳所有游戏区域userViewWindow =