(接上文)
游戏场景就像洋葱头
典型的2D动作游戏包括一个背景和一些活动的人物.尽管你可以自己来画这样的场景,但Game API能让你用layers 来创建场景. 你可以用一个layer来做城市的背景,另一个来做一辆汽车. 将汽车层放到背景层上方就完成了整个的场景. 将汽车做为一个独立于背景和场景中的其它层的层会使对它的操作变得简单.
Game API 通过下面的四个类提供了灵活的对层的支持:
- Layer 是所有层的抽象父类. 它定义了层的基本属性,包括位置,大小以及是否可见. 每个Layer 的子类必须定义paint() 方法用来在Graphics 上绘制该层. 它的两个具体的子类, TiledLayer 和 Sprite, 应该会满足对2D游戏的需求.
- TiledLayer 在创建背景图时会被用到. 你可以用一组小块儿的图片来高效地创建大幅的背景图.
- Sprite 是会动的层. 你提供每个动作(帧)的图片然后对它的活动进行完全的控制. Sprite 同时还提供对每个帧的镜像翻转和90°倍数旋转的功能.
- LayerManager 是一个非常顺手的类,它记录你的场景中所有的层. 只需调用LayerManager的paint() 方法就可以绘制它所包括的所有层.
使用 TiledLayer
TiledLayer 很简单, 尽管乍一看有些深入的地方不好理解. 最主要的思想就是一个图片源提供一组tiles (切片)用于组成一个大的场景. 例如, 下面的图片尺寸为64 x 48.
|
这个图片可以分为12个切片,每个大小为16 x 16 像素. TiledLayer 为每个切片分配一个编号, 从左上角开始为1. 图片源中的切片编号如下所示:
|
编码来创建一个TiledLayer 是想当容易的. 你需要指定行数和列数, 图片源, 以及每个切片的大小. 这段代码说明了如何加载图片源并创建一个TiledLayer.
Image image = Image.createImage(“/board.png”); TiledLayer tiledLayer = new TiledLayer(10, 10, image, 16, 16); |
在例子中, 新创建的 TiledLayer 有10 行10 列. 每个切片大小为16像素正方.
引人入胜的部分在于如何用这些切片创建场景. 要想为场景中的一个格分配一个切片,应使用setCell()方法. 你需要提供格的行和列位置以及切片的编号. 例如,你想将切片5分配给第2行第3列的格子,就应该使用setCell(2, 1, 5). 如果你看着这几个参数别扭,请注意切片编号从1开始,而格子的行和列编号从0开始. 新建的TiledLayer 中每个格的默认的切片编号为0, 这表明它们是空的.
下面的代码段演示了一种使用int数组创建TiledLayer的方法.在真正的游戏中, TiledLayers 应该由资源文件定义,这样可以更灵活地定义背景以及为游戏扩展新的地图或者级别等.
private TiledLayer createBoard() { Image image = null; try { image = Image.createImage(“/board.png”); } catch (IOException ioe) { return null; } TiledLayer tiledLayer = new TiledLayer(10, 10, image, 16, 16); int[] map = { 1, 1, 1, 1, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 11, 0, 0, 0, 0, 0, 0, 0, 7, 6, 0, 0, 0, 0, 0, 0, 0, 7, 6, 0, 0, 0 }; for (int i = 0; i < map.length; i++) { int column = i % 10; int row = (i – column) / 10; tiledLayer.setCell(column, row, map[i]); } return tiledLayer; } |
你需要在paint()方法中获得Graphics 对象来将这个TiledLayer 画在屏幕上.
TiledLayer 同样支持动态的切片,这使得按一定顺序变化一组切片变得很容易实现. 更多细节请参考 TiledLayer的JavaDoc.
用 Sprites 实现人物动画
Game API 提供的Layer的另一个的子类是Sprite. 某种意义上来说, Sprite 是TileLayer概念上的反义词. TiledLayer 用图片源的一堆切片来构成一个场景,而 Sprite 是用一个图片源序列来实现动画.
创建一个Sprite 的所有工作需要提供一个图片源以及每一frame(帧) 的图片大小. TiledLayer中, 图片源是被分成相同大小的切片; Sprite中, 每一个子图片则被称作一个帧. 下面的例子中, 图片源tank.png 被用来创建每帧大小32 x 32像素的Sprite.
private MicroTankSprite createTank() { Image image = null; try { image = Image.createImage(“/tank.png”); } catch (IOException ioe) { return null; }
return new MicroTankSprite(image, 32, 32); } |
图片源的每一个帧由一个编号,从0开始依次编号. (这儿别晕; 切片编号从1开始.) Sprite 有一个frame sequence(帧序列) 用于决定每个帧按什么顺序来显示. 新建的 Sprite 默认帧序列为从0开始依次排列所有的帧.
在帧序列中前后变换应使用Sprite的 nextFrame() 和 prevFrame() 方法. 这两个方法会自动判断边界处理. 比如, 如果Sprite 正显示它的帧序列中的最后一帧,调用 nextFrame() 则会显示帧序列的第一帧.
指定与默认的不同的序列,将新的序列表示为int数组并传递给setFrameSequence().
[不解] You can jump to a particular point in the current frame sequence by calling setFrame(). There is no way to jump to a specific frame number. You can only jump to a certain point in the frame sequence..
帧的变化只有在Sprite下次重绘的时候,也就是从Layer 类继承的paint()方法被调用的时候才可见.
Sprite 还可以对帧进行旋转变化. 每个帧可以旋转90°的倍数、进行镜像旋转, 或者两者的结合. Sprite 类里的常量列举了所有可能性. 可以通过这些常量使用setTransform()方法设置Sprite当前的旋转情况.下面的列子将当前帧以纵向中心为轴做镜面旋转并旋转90°(没看到过效果不知道翻译的对不对):
// Sprite sprite = … sprite.setTransform(Sprite.TRANS_MIRROR_ROT90); |
图像旋转了但是Sprite的reference pixel(参考点) 并未移动. 默认地, Sprite 的参考点位于它的0,0,坐标也就是左上角. 当图像旋转时,参考点的位置也跟着转,但相对于图像来说参考点的位置还在原处(太蹩脚了,看不懂到底是什么意思).
你可以用defineReferencePixel() 方法改变参考点的位置. 在许多动画中你可能需要将参考点设在sprite的中心.
最后, Sprite 提供一些collidesWith() 方法用于与其它Sprites, TiledLayers, 或 Images之间的碰撞检测. 你可以用矩形检测(方便但是不准)或者在像素这个级别上进行检测(麻烦但是准确). 这些方法之间的差别不是一句两句说得明白的,你还是去看JavaDoc吧.
clapton_xpAThotmailDOTcom
〔后面的懒得翻了,不如直接看代码喽〕
The muTank Example
The muTank example demonstrates the use of TiledLayer, Sprite, and LayerManager.
The important classes are MicroTankCanvas, which contains most of the code, and MicroTankSprite, which encapsulates the behavior of the tank.
MicroTankSprite makes extensive use of transformations. Using a source image with only three frames, MicroTankSprite can show the tank pointing in any of 16 different directions. Two exposed public methods, turn() and forward(), make the tank easy to control.
MicroTankCanvas is a GameCanvas subclass and contains an animation loop in run() that should look familiar to you. The tick() method checks to see if the tank has collided with the board. If so, its last movement is reversed using MicroTankSprite‘s undo() method. The input() method simply checks for key presses and adjusts the direction or position of the tank accordingly. The render() method uses a LayerManager to handle painting. The LayerManager contains two layers, one for the tank, one for the board.
The debug() method, called from the game loop, compares the elapsed time through the game loop with the desired loop time (80 milliseconds) and displays the percentage of time used on the screen. It is for diagnostic purposes only, and would be removed before the game was shipped to customers.
The timing of the game loop is more sophisticated than in the previous SimpleGameCanvas. To try to perform one iteration of the game loop every 80 milliseconds accurately, MicroTankCanvas measures the time it takes to perform tick(), input(), and render(). It then sleeps for the remainder of the 80-millisecond cycle, keeping the total time through each loop as close as possible to 80 milliseconds.
Summary
MIDP 2.0’s Game API provides a framework that simplifies developing 2D action games. First, the GameCanvas class provides painting and input methods that make a tight game loop possible. Next, a framework of layers makes it possible to create complex scenes. TiledLayer assembles a large background or scene from a palette of source image tiles. Sprite is appropriate for animated characters and can detect collisions with other objects in the game. LayerManager is the glue that holds layers together. The muTank example provides a foundation of working code to demonstrate the Game API.
|
About the Author: Jonathan Knudsen [e-mail] [home page] is the author of several books, including Wireless Java (second edition), The Unofficial Guide to LEGO MINDSTORMS Robots, Learning Java (second edition), and Java 2D Graphics. Jonathan has written extensively about Java and Lego robots, including articles for JavaWorld, EXE, NZZ Folio, and the O’Reilly Network. Jonathan holds a degree in mechanical engineering from
Princeton
University
clapton_xpAThotmailDOTcom