midp 2.0里面包括一个用来简化编写二维游戏的api函数。这个api函数是非常简凑的,只包括javax.microedition.lcdui.game包里的五个类。这五个类主要提供了两个重要的功能:
新的gamecanvas类使得在一个游戏循环体内画一个screen和响应键盘输入成为可能,而不需要调用系统的paint和input线程。
功能强大而复杂的图层(layer)api函数可以轻松高效地建立复杂的场景。
mutank example
利用gamecanvas类创建一个游戏循环(game loop)
gamecanvas类是附加了功能的canvas类,它提供了立即重画和检查设备按键状态的方法。这些新的方法把一个游戏的所有函数(功能)封装在一个循环体内,并由一个单线程进行控制。为什么这样做就非常吸引人阿?先让我们考虑一下你是如何执行一个使用了canvas类的典型游戏的:
public void microtankcanvas
extends canvas
implements runnable {
public void run() {
while (true) {
// update the game state.
repaint();
// delay one time step.
} }
public void paint(graphics g) {
// painting code goes here.
}
protected void keypressed(int keycode) {
// respond to key presses here.
} }
这不是一个美丽的画面 。运行在应用程序线程中的run()方法,每一个时间段都会刷新游戏。典型的任务是刷新小球或飞行物的位置,绘制人物或飞行器动画。每一次通过循环体,repaint()方法被用来刷新屏幕。系统把按键事件传送给keypressed(),它能适当地刷新游戏状态。
问题是,每样东西都在不同的线程里,游戏代码在以上三种不同方法里传递很容易混淆。当run()方法里的主动画循环体调用repaint()方法时,将没有办法确切知道系统什么时候调用paint()方法。当系统调用keypressed()时,也没有办法知道程序的另一部分正在进行什么。如果你keypressed()中的代码将要刷新游戏的状态,而同一时刻paint()方法将表现屏幕,这时屏幕将会持续非常奇怪的状态。如果表现屏幕所用时间超过一个单时间段,动画会看起来颠簸不定或是很奇怪。
gamecanvas类允许你避开常用绘画(painting)和按键消息(key-event)机制,所以所有的游戏逻辑都可以被包括在一个单循环中。首先,gamecanvas类允许你用getgraphics()方法直接访问graphics对象。对于所返回的graphics对象的任何表现(rendering)都可以通过屏幕外缓冲区(offscreen buffer)来实现。你可以用flushgraphics()复制缓冲区到屏幕上,直到屏幕被刷新才会返回。这种方式给你提供比调用repaint()方法更完善的控制。repaint()方法会立即返回值,以至于你的应用程序不能确定系统什么时候会调用paint()来刷新屏幕。
gamecanvas类也包含一个用来获得设备按键当前状态的方法,即所谓得polling技术。你可以通过调用gamecanvas类的getkeystates()方法,马上确定哪一个按键被按下,从而取代了等待系统调用keypressed()方法。
下面是一个使用gamecanvas类的典型的游戏循环体:
public void microtankcanvas
extends gamecanvas
implements runnable {
public void run() {
graphics g = getgraphics();
while (true) {
// update the game state.
int keystate = getkeystates();
// respond to key presses here.
// painting code goes here.
flushgraphics();
// delay one time step.
} }
}
接下来的例子描述了一个基本的游戏循环体。它向你展现了一个旋转的“x”,你可以用方向键在屏幕上移动它。这里的run()方法特别的瘦小,这要多亏了gamecanvas。
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
public class simplegamecanvas
extends gamecanvas
implements runnable {
private boolean mtrucking;
private long mframedelay;
private int mx, my;
private int mstate;
public simplegamecanvas() {
super(true);
mx = getwidth() / 2;
my = getheight() / 2;
mstate = 0;
mframedelay = 20;
}
public void start() {
mtrucking = true;
thread t = new thread(this);
t.start();
}
public void stop() { mtrucking = false; }
public void run() { graphics g = getgraphics();
while (mtrucking == true) { tick();
input();
render(g);
try { thread.sleep(mframedelay); }
catch (interruptedexception ie) {}
}
}
private void tick() {
mstate = (mstate + 1) % 20;
}
private void input() {
int keystates = getkeystates();
if ((keystates & left_pressed) != 0)
mx = math.max(0, mx - 1);
if ((keystates & right_pressed) != 0)
mx = math.min(getwidth(), mx + 1);
if ((keystates & up_pressed) != 0)
my = math.max(0, my - 1);
if ((keystates & down_pressed) != 0)
my = math.min(getheight(), my + 1);
}
private void render(graphics g) {
g.setcolor(0xffffff);
g.fillrect(0, 0, getwidth(), getheight());
g.setcolor(0x0000ff);
g.drawline(mx, my, mx - 10 + mstate, my - 10);
g.drawline(mx, my, mx + 10, my - 10 + mstate);
g.drawline(mx, my, mx + 10 - mstate, my + 10);
g.drawline(mx, my, mx - 10, my + 10 - mstate);
flushgraphics();
} }
本文所举示例的代码包括一个使用了这个canvas的midlet。你可以尝试着运行simplegamemidlet这个小程序,看看它是怎样工作的。你将会看到一个像正在做健身操的海星的东西(或许它正在寻找自己失掉的腿)。
simplegamemidlet screen shot
游戏场景就像是洋葱(有层次)
典型的二维动作游戏常包含一个背景和若干动画人物。尽管你可以自己来描绘出这种场景,不过game api函数使你能够用图层来建立场景。你可以做一个城市的背景图层,另外再做一个含有一辆小汽车的图层。将小汽车图层放在背景上,你就创造出了一个完整的场景。把小汽车放在一个单独的图层中,可以很容易的熟练操控它,而不受背景和其他图层的影响。
game api函数使用以下四个类为图层提供灵活的支持
layer类是所有图层类对象的抽象基类。它定义了一个图层的基本属性,包括位置,尺寸,和此图层是否可见。layer类的每个子类必须定义一个paint()方法,用来把这个图层表现在一个图象上,这个图象将会被描画到屏幕表面上。两个确切的子类tiledlayer和sprite应该能满足你的二维游戏的需要了。
tiledlayer类用来建立背景图像。你可以用一个小的源图像贴的集合来高效的制作大的图像。
sprite类是一个动画层。你提供源帧就可以对整个动画进行完全的控制。sprite类也提供镜像,并可对源帧作90度旋转。
layermanager类是一个非常有用的类,用来保存你的场景中的所有图层的动作轨迹。layermanager类 paint()方法的一个简单调用就足以控制所包含的所有图层。
使用tiledlayer类
尽管包含一些不是显而易见的微妙不同,tiledlayer类还是很容易理解。这个类的基本思想就是,用一个源图像提供一组图像贴片,这些贴片可以组合成一幅大的场景。
source image
这个图像被分成了12块16*16的图像贴片。tiledlayer类分配给每个图像贴片编号,左上角的图片规定为1,以此类推。
tile numbering
用代码创建一个tiledlayer类是非常简单的。你需要确定行数和列数,源图像以及这个源图像里每个贴片的像素大小。下面的代码片断告诉你如何装载图像和创建tiledlayer类。
image image = image.createimage("/board.png");
tiledlayer tiledlayer = new tiledlayer(10, 10, image, 16, 16);
在例子中,新的tiledlayer类有10行,10列。这些来自image的图像贴片大小是16*16像素。
有趣的部分还是用这些图像贴片来创建一幕场景。利用setcell()方法可以把一个图像贴片分配到一个数组元胞里。你需要提供这个数组元胞所在行列数以及图像贴片的编号。例如,你可以通过调用setcelll(2,1,5)方法把编号为5的图像贴片分配到第2行中的第3个数组元胞里。如果你觉得这些参数看起来不对,请注意,图像贴片编号是从1开始计数,而行和列的编号是从0开始的。参数缺省情况下,新的tiledlayer类对象中的所有数组元胞的图像贴片标号为0,这就意味着它们是空的。
下面的代码片断向你说明一种使用整数数组来填充tiledlayer类对象。在实际图像中,tiledlayer类可以从资源文件里定义,这就使得定义背景时可以有更多的灵活性,并能提供新的背景和级别来增强游戏的可玩性。
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;
}
为了把这个tiledlayer类对象显示在屏幕上,你需要调用一个graphics对象的paint()方法。tiledlayer类还支持动画图像帖子,这样就使得通过一系列贴片来移动元胞集合很容易了。若想得到更详细的说明,参看tiledlayer类相关的api文档。
使用sprite类实现人物动画
game api函数里提供的另一个具体的layer类是sprite类。一方面,sprite类是tilelayer类的概念化的逆转.tiledlayer类使用源图像贴片的调色板来创建一幅大场景,而sprite类则使用一系列源图像帧来产生动画。
你创建一个sprite类所需要的只是源图像和每个帧的尺寸。在tiledlayer类里,源图像被分为相同大小的图像贴片;在sprite类里,子图像被称为帧。在下面的例子里,源图像tank.png用来创建帧大小为32*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类有一个帧序列,它决定了帧显示的顺序。一个新sprite类对象的缺省帧序列简单地依照可用帧,从0开始累加。
使用sprite类的nextframe()方法和prevframe()方法,可以把帧在帧序列中向前或向后移动。这些方法把帧序列的头尾连接起来了。例如,如果sprite类对象已经把位于帧序列末尾的帧显示出来了,若在调用nextframe()方法将会显示帧序列的头帧。
调用setframesequence()方法,可以通过整型数组所指定的序列来确定不同于缺省时的帧序列。
你还可以调用setframe()方法跳至当前帧序列中的某一帧。你不能跳至特定的帧编号处,只能跳至帧序列的特定点。
利用从layer类继承下来的paint()方法时,只有在sprite类在下一个时间段内被表现的时候,帧的变化才真正实现。
sprite类还可以变换源帧。可以把帧旋转90度,或做镜像变换,或两者皆有。在sprite类里的常数枚举了这些可能性。sprite类的当前变换方式可以通过向settransform()方法传递这些常数之一进行设定。下面的例子是当前帧绕垂直中心做镜像变换,并旋转90度:
// sprite sprite = ...
sprite.settransform(sprite.trans_mirror_rot90);
应用了变换方式,从而使得sprite类的参考像素并没有移动。缺省下,sprite类的参考像素位于sprite类坐标系里的(0,0)点处,即左上角。当应用了变换方式,参考像素的位置也变换了。sprite类的位置被调整了,从而参考像素仍然在原位置上。
你可以通过调用definereferencepixel()方法来改变参考像素点的位置。对于大多数类型的动画,你可以把参考像素点定义在sprite的中心上。
最后,sprite类提供几个collideswith()方法来检测与其他sprites,itledlayers,或images类对象的碰撞。你可以使用检测矩形(快但粗糙)或者像素级别(慢但精确)来检测碰撞。这些方法的微妙不同是难以描述的;若详细资料,可参看api 文档。
mutank例子
mutank例子向你说明tiledlayer,sprite和layermanager类的用法。最重要的类是包含大部分代码的microtankcanvas类和封装了坦克行为的microtanksprite类。
microtanksprite类制作了大量的变换方式。它使用了一个只含3帧的源图像来显示指向16种方向的坦克。turn()和forward()两个公用方法使得坦克很容易控制。
microtankcanvas类是gamecanvas类的子类,它在run()方法里包含一个你应该很熟悉的动画循环体.tick()方法用来检测坦克是否碰到隔板上了。如果碰到了,就调用microtankcanvas类的undo()方法使它最近一次的运动倒退。input()方法简单地检测按键是否被按下,并同时调整坦克的方向或位置。render()方法使用一个layermanager类对象来对绘画进行处理。layermanager类包含两个图层,坦克图层和隔板图层。
从游戏循环体中调用的debug()方法,用来比较通过游戏循环体所用时间和期望的循环时间(80毫秒),并在屏幕上显示时间的百分比。它仅仅是用作调试诊断目的的,在游戏被发送给用户之前将会被删除。
游戏循环体的计时比前面的simplegamecanvas类更加复杂。为了更精确的每80毫秒就重复一次游戏循环体,microtankcanvas类对tick(),input()和render()方法所花费时间进行测量。然后停下来花费完80毫秒中的剩余时间,以使得通过每次循环所用的总共时间尽可能的接近于80毫秒。
总结
midp 2.0的game api函数提供了一个简化二维动作游戏开发的构架。首先,gamecanvas提供了使得游戏循环体紧凑的绘画和输入方法。其次,图层的架构使得创建复杂的场景成为可能。tiledlayer从源图像簇的调色盘中组合了一个大背景或场景。sprite适合于动画人物,并能检测到在游戏中与其他对象的碰撞。layermanager把所有的图层粘合在一起。mutank例子提供了一组基本的工作代码,用来说明game api函数的使用。
原作者:jonathan knudsen
翻译者:阿麦 hua
原文出处: http://wireless.java.sun.com
Java Asp PHP .Net XML C/C++ CGI VB Jsp J2ee J2se J2me EJB Servlet Tomcat Resin Struts Weblogic Eclipse ANT GUI JMS Web servise IDEA Webphere Hibernate Spring Jboss Applet Swing Socket Javamail Perl Ajax P2P 安全 模式 框架 测试 开源 游戏
Windows XP Windows 2000 Windows 2003 Windows Me Windows 9.x Linux UNIX 注册表 操作系统 服务器 应用服务器