如何编写自己的手机游戏模拟器(翻译版)

如何编写自己的手机游戏模拟器(翻译版)

因为觉得本文还是可以起到抛砖引玉的作用的,就照着英文版自己翻译了,由于晚上时间伧俗所以难免有所纰漏,仅供参考。

 

如何去编写自己的手机游戏模拟器呢?
对于一个程序员来说,修改几行代码之后,等待它运行起来看效果时的编译等待时间显得特别漫长,而来回修改运行调试的无尽的等待应该算最操蛋事情了。而开发j2me游戏那频繁的编译混淆以及导出jar包令之前说的操蛋事更加突出。而操蛋加操蛋的就是那既启动速度很慢而按键响应很烂且调试功能很差的种多不同机型的模拟器。通过用j2se代码去模拟实现j2me的API函数也许我们可以使得手机上的java程序得到加速甚至不再操蛋。想要一个加速的、方便调试的、生成版本快速的、与机型无关的独立的模拟器?那么这篇文章将赐予你这个模拟器然后把你从繁琐的编译导出中解救出来~


@模拟器有什么问题么?
我不打算将我对那些手机生产商所提供的模拟器的厌恶一一列出。我确信如果你有j2me的开发经验那你肯定觉得他们的并不是最理想的方案。首先,很明显安装、维护、学习然后把那么多厂商的不同的SDK加入你的项目里是一件很操蛋的费力不讨好的事情。这些厂商的模拟器本应该让你省去在真机测试的麻烦,但是他们运行缓慢,而且对于程序错误基本没有什么提示,对于调试的支持甚少而且与真机的性能相差甚远。


@那么,你通常如何做呢?
一个好的模拟器必不可少的要能得到你的游戏现在使用了多少内存。这让你能确信你的游戏如果能在诺基亚的7210模拟器上跑起来,那一定能在7210的真机上跑。然而这个内存数字在每个模拟器上是不一样的,这个问题就让我们陷入了操蛋的内存溢出的异常里。
如果你是一个知道如何编写结构清晰的代码、能有效利用内存的有经验的j2me程序员的话,我建议你先做到让你的一套游戏程序能够在任何尺寸的屏幕下跑,能够在一个仅仅支持你用到的API函数的虚拟机里跑。你可以在一个简单的工具里做到这些,这个工具在眨眼的功夫就可以运行项目而且方便调试。


@那么,你打算怎么做呢?
我们之前说的操蛋事,也许你已经在j2me开发里经历了成千上万次。怎么办呢?
@在桌面的java中模拟j2me
用最简单有效的代码去实现j2me必须的API函数其实是很简单的。在CLDC1.0和1.1中,就是java.io, java.lang 和 java.util
这几个包里面的类都是由j2se为基础编写的,所以将库函数重写成单纯简单的j2se代码是必须的。javax.microedition.midlet包是很容易实现的。javax.microedition.lcdui包里面包含的主要的类在j2se的AWT里都有对应的类似的,所以你根本不用费心去在j2se里实现。虽然javax.microedition.rms你觉得肯定有用,但它不过是文件的保存读取而已,很简单就可以实现了。


@模拟器的类概述
我们下面将概述一下模拟器所有的内容除了MIDletStateChangeException这个类,因为这个类的实现需要建立一个手机j2me环境。j2meAPI的类MIDlet、Display、Displayable、Canvas对应我们模拟器里的Main、FrameAWT、CanvasAWT,处理了主要的框架、入口点、程序显示、键盘输入。而j2me里的类Image、Graphics、Font在AWT里的实现分别是AWT Image、AWT Graphics、AWT Font。


@那么,开始编写代码吧
在包javax.microedition.midlet里的MIDlet类是所有midlet程序的启动入口,显而易见的我们先来看看MIDP程序的运行环境。所有的midlet程序都要有一个继承了MIDlet并且重写了MIDlet的3个抽象方法的类,MIDlet的3个抽象方法的功能正如其名:startApp、pauseApp、destroyApp。
为了顺利开始,我们的MIDP模拟器需要通过几个public方法实现类MIDlet来调用他控制生命周期的抽象方法。由于这些并不能实现MIDletStateChangeException类,那么我们也要考虑如何实现MIDletStateChangeException。
那么我们的模拟器需要一个“main”类来用几句代码加载midlet类然后用public方法去调用他的startApp。下面的代码将加载一个midlet类。
// 创建我们的AWT主窗口 (后面会改的多几行)
frame = new FrameAWT (midlet_class_name);
// 加载并且实例midlet
midlet = (MIDlet) Class.forName(midlet_class_name).newInstance();
// 调用midlet的startApp方法
midlet.amsStartApp ();
主类可能在一个包中也可能在缺省包里,并且有一个名字。我选择“caodan”(操蛋)作为我的j2me模拟器的名字。这是个言简意赅的名字。仅仅在正确的地方用上面这三句我们就可以使用“操蛋”去运行一个midlet程序。如果你仅仅想运行一个简单的midlet那么你可以通过下面代码去调用我们的“操蛋”:
java -classpath bin;BasicMidlet_v1.0.0.jar com.longsteve.caodan.Main BasicMIDlet


@资源加载
j2me使用Class.getResourceAsStream()方法来加载资源,这点和j2se是一样的。如果你正在运行jar包的里一个midlet,通过在classpath路径上的jar(classpath:设置Classpath的目的,在于告诉Java执行环境,在哪些目录下可以找到您所要执行的Java程序),任何midlet程序调用getResourceAsStream()方法都会运行在虚拟机环境下。不一定你就非要从一个jar包去运行你的游戏代码。一个很快速的方法是用一个模拟器去跳过打包这步。你的classpath路径可以包含你编译好的类的路径,你的游戏资源的路径(比如png图片)。你调试的过程也简化了,不需要一次次的打包了。


@添加一个display
现在可以简单运行一个midlet了,但是我们还需要添加一个display作为显示器。我们现在在用j2se写模拟器,那么我们来用一个j2se的桌面开发工具包试试。我们可以选择Swing的JFrame以及它的相关类,但是我总是在使用Swing时因线程问题困扰不已。用了它我们的midlet模拟器也会变得很困扰,我们只需要一个窗口(window)一个画布(canvas),我们不需要任何奢侈的Swing控件,所以我会让它简单又干净,我们使用AWT。
在j2me中,midlet使用javax.microedition.lcdui包里的类来实现display。Display类提供静态方法setCurrent()来让midlet将一个可显示的对象显示在屏幕上。在游戏中常用的Displayable的子类主要是Canvas类。midlet控制一个继承了Canvas的类去调用它的绘图方法(paint(Graphics graphics))来绘制游戏内容。
我们要创建javax.microedition.lcdui包然后添加Displayable、Canvas、Graphics到我们的模拟器“操蛋”。这意味着我们要添加AWT的Frame和Canvas类,并且让它们和MIDP的类联系起来。稍后我们会把MIDP里面的Image和Font类也加入到我们的测试midlet里,不久它会看起来像是一个游戏。


@主窗口
FrameAWT继承AWT里的Frame类并且提供显示midlet的display的主要窗口。为了获知窗口大小,我们暂时写一行代码定义模拟器要显示的midlet画布尺寸,因为除了从手机设备的详细资料或者真机调试数据,我们无从获知不同手机的屏幕尺寸。将屏幕尺寸做成自定义,恩暂时这样吧。


@画布
CanvasAWT继承自AWT的Canvas类处理屏幕的绘制以及键盘和鼠标的输入。CanvasAWT与一般的midlet的Displayable对象差不多,就是参考Displayable类的。在Displayable类的方法invokePaint()实际上被用来调用midlet的Canvas的paint()方法。CanvasAWT类包含一个MIDP的图片(Image)对象,用在midlet的屏幕上。每次CanvasAWT里的绘制方法被调用,它会从屏幕图片获取一只画笔以此去调用midlet的Canvas的paint()方法。当MIDP的Canvas的paint()方法返回时,屏幕图片会通过CanvasAWT的paint()方法被以AWT的画笔重绘。
下面的代码写在CanvasAWT.paint()方法里。invokePaint()方法是我们实现MIDP的Displayable类的一部分。当一个midlet调用Display.setCurrent()方法时,我们的Display类实际上将现在的显示对象设置到CanvasAWT上,然后窗口开始绘制来自midlet的paint请求。
public javax.microedition.lcdui.Displayable current;
public javax.microedition.lcdui.Image midp_screen;
public void paint (java.awt.Graphics g)
{
 javax.microedition.lcdui.Graphics midp_graphics =
  midp_screen.getGraphics ();
 // 用j2me的画笔去调用midlet的paint方法
 current.invokePaint (midp_graphics);
 // 将midlet的屏幕画到我们的canvas上 缩放
 g.setClip(0,0,awt_canvas_width,awt_canvas_height);
 g.drawImage(midp_screen._image,
  0,0,getWidth(),getHeight(),
  0,0,midp_screen.getWidth(),midp_screen.getHeight(),
  this);
}

AWT的drawImage()方法也可以很轻松的实现缩放,所以我们可以随意调整想把midlet显示为多大尺寸。我讨厌眯着眼看我桌面上的模拟器,有些小屏幕的设备甚至放大一倍还是显得不爽,所以我将“操蛋”的默认大小设置为midlet的尺寸的3倍。不过最好你将他做成自定义的,以后想设置为多大都可以。


@键盘输入
CanvasAWT提供的键盘输入类似Displayable类。CanvasAWT实现了AWT的KeyListener接口,并且提供键盘事件,通过j2se的键值直接映射到Displayable的invokeKeyPressed()方法。
我们的Displayable类包含了j2se的键值表并且映射到了MIDP的键值以及游戏动作(game actions)。在这里使用j2se代码比使用j2me的代码要简单的多。然而j2me的Canvas类定义实际上已经指定了键值为不变的,比如KEY_NUM0 = 48,所以使用相同的键值使得模拟器方便的结合于midlet。


@图片和画笔
MIDP的Image对象可以用java.awt.BufferedImage很简单的实现。BufferedImage对象可以以宽度和高度创建,就像MIDP的Image对象一样也可以以一个输入流(inputStream)或者任何MIDP的Image所支持的参数创建。在java5的ImageIO提供了对PNG图片的支持,超级Image包(javax.media.jai),但是我发现现在这些读PNG数据的方法还不靠谱。我至今用过的最快捷的最强大的PNG处理函数库是sixlegs.com。库的jar包在50k以下,他在许可GPL协议(GPL,自己去google查是啥)之外,相比java在png图片的优化和压缩技术上获得了成功。
MIDP的Graphics画笔是从Image图片对象直接创建的,我们使用从java.awt.Image对象获取的java.awt.Graphics对象可以轻松实现。大多数的j2me的功能就是通过方法简单的调用了AWT的Graphics对象,比如drawLine()。不过还是有一些不同的,j2me的方法比如drawImage()和drawString()是需要锚点的。越来越多的强大复杂的功能已经实现,如果你开始使用MIDP2.0的话你一定接触过drawRegion(),他可以实现图片旋转啊镜像啊,这些绝不是不可能做到的,以后想到的都可以实现。
public void drawImage(Image img, int x, int y, int anchor)
{
 // 默认锚点
 if (anchor == 0)
 {
  anchor = TOP | LEFT;
 }
 // 计算x和y的偏移根据给出的锚点
 switch (anchor & (TOP|BOTTOM|BASELINE|VCENTER))
 {
  case BASELINE:
  case BOTTOM:
  y -= img.getHeight();
  break;
  case VCENTER:
  y -= img.getHeight() >> 1;
  break;
  case TOP:
  default:
  break;
 }
 switch (anchor & (LEFT|RIGHT|HCENTER))
 {
  case RIGHT:
  x -= img.getWidth();
  break;
  case HCENTER:
  x -= img.getWidth() >> 1;
  break;
  case LEFT:
  default:
  break;
 }
 // 用MIDP的图片画到我们AWT
 _graphics.drawImage(img._image,x,y,null);
}


@字体
跟MIDP的Font类非常相似,如果没有它那么将不会完成对画笔的任何操作。通过使用java.awt.Font和java.awt.FontMetrics对话,所有的功能都可轻松实现。有些并不是很完美,比如对下划线的支持,但是如果你的游戏使用点阵字体,你完全不必操心字体的问题。


@多余的话
实现8个j2me的类以及3个应用程序类足够可以使你的模拟器得以启动开发的游戏。有了你需要的所有功能,你或许也可以用它来玩一些你有的j2me游戏。不过,如果你尝试运行一些复杂的游戏,你会发现它会提示类或者方法没有找到的异常(class and method not found exceptions)。没啥大不了的,你可以马上根据需要将缺少的东西添加到你的模拟器里。
如果你在使用MIDP1.0开发游戏你一定会发现它需要javax.microedition.rms来给游戏存档。用一个模拟的代码就可以马上解决这个问题,你会惊讶的发现它马上又可以跑了。使用java的优秀的文件类和IO流来实现实际的文件存储器完全没有困难。
然后,或许在javax.microedition.lcdui里的一些类也是需要添加进去的。Command和CommandListener类应该是首先被需求的。与form相关的类你也许根本不必考虑,因为大多数游戏根本没有用到它。
在MIDP1.0的功能基础上,MIDP2.0填补了很多空白,比如我们之前说到的绘图的变化。如果你想在现在的游戏创作上做出较大的改变,那么也可以使用诺基亚的界面(nokiaUI),如果你在开发MIDP2.0,那么诺基亚的界面将会是你的首选。


@音频
javax.microedition.media包对音频做了支持。如果你使用的是java5或者更高的版本你可以使用javax.sound.midi和javax.sound.sampled的j2se包。通过调试你要运行的项目你可以实现正确的播放方法。


@蓝牙
在没有j2me模拟器可以支持真正的蓝牙传输前开发蓝牙的midlet程序是很麻烦的。它们只能通过传递实例去模拟蓝牙。真正的蓝牙设备使用程序都需要到真机上进行测试,虽然开发和调试都会很慢。现在在j2se里有了javax.bluetooth (JSR-82)包。GNU LGPL执行蓝牙通信并且可以被添加到classpath便于开发。jsr-82可以免费试用,它的购买也是很便宜的。


@3D
当你体验到使用j2se的模拟器开发之后,你会希望将你要到的所有API都添加进去。手机3D API(JSR-184)现在被越来越多的用在手机游戏中。这个复杂的画笔有一个叫做Rasteroid的产品是基于j2se实现的JSR-184。使用这个并不能完全让你在你的AWT模拟器里实现3D,但是他可以让你开发3D的midlet项目只需要短短几行代码。


@好处
你也许会思考放弃现有的各个厂商的设备模拟器而自己去努力制作一个模拟器是否值得。我自己的模拟器现在已经是我开发游戏的工具了。我希望我的java游戏引擎能够快速的反馈给我信息。最简单的方法就是在我的工具里面包含j2me必要的类和方法使得游戏引擎可以在源码丝毫不变的情况下随时运行。在编写工具的时候,java开发的立即运行的速度(编译运行)让我觉得它也适合游戏之外的别的j2me代码。
当我开始思考我的工具的设计需求时,我已经知道Mpowerplayer。我与其使用这个不咋地的工具,何不自己写一个呢。Mpowerplayer 是个比较强大的工具但是丫挺的是收费的。有你自己的工具是有好处的,虽然很多时候这个好处不是马上就显现出来。


@真正的敏捷开发
一个ant build程序,编译混淆打包等步骤也许要花费一点时间。你可以在你的IDE里创建一个项目,然后加入你自己的模拟器,游戏的类和资源路径。然后点击“运行”可以马上看到你的代码在运行。如果你使用的是Eclipse那你甚至开始都不用写,它类似开发java的桌面项目。你可以修改我们之前说的模拟器屏幕尺寸,不需要选择模拟器就可以让一个游戏在不同尺寸的屏幕下运行。


@调试
用j2se的模拟器实际上你就是在一个纯净的java环境下编写你的midlet代码。我前面已经提过,你可以点击“运行”以运行它,也可以点击“调试”然后步进源代码。你开发j2me的游戏有多久没有进行源码调试了呢?这个特性也许是抛弃传统模拟器的最大理由。我知道它们应该支持调试,并且一些新的做的也不差,但是在IDE以及调试器里对java程序的调试还需要更加成熟。你也可以尝试更多的java调试器。


@目的
有了我们的强大的工具,你可以快速的开发出你的产品。而且我们的工具可以给策划、测试等等在电脑上玩手机游戏的人。他们只需要在电脑上安装一个java虚拟机即可。不需要多个模拟器,这一个模拟器就可以满足我们的所有需求了。
使用自己的模拟器运行你的游戏是什么感觉的?策划可以拿这个编写关卡,美术可以换了图马上就可以看到效果。你可以添加另一个AWT窗口调整游戏引擎,这样测试可以实时调整不同的参数。美术可以自己将png图片放到资源目录里就可以实时的查看变化,总好过重新打包吧。


@开发
掌握你的游戏运行环境在游戏完成后好处才显现出来。很多项目在真机要得到设备的许可(比如发短信,录制视频保存图片),保存截图是很简单的只需要把MIDP的屏幕缓冲写成一个png图片就可以了。
java.io.File f = new File ("screenshot.png");
javax.imageio.ImageIO.write((BufferedImage)midp_screen._image,
 "png", f);
有时将游戏录制成视频保存下来也是个不错的功能,这样对于质检是很有帮助的,测试可以将一个bug以视频的方式录制下来。java的Media框架包含了写AVI文件的功能,并且能够和CanvasAWT的paint方法挂钩输出,就像截屏的代码那样简单。
通过修改自己的模拟器的设置可以实现所有的可能。你可以在任何平台比如笔记本电脑全屏玩你的手机游戏,这比拿着手机眯着眼玩要强得多。你可以很轻松的为VGA手机开发游戏仅仅需要将你的模拟器设置为640x480的屏幕尺寸。你可以将你的游戏在网页上玩,在微型电脑上玩,在PDA上等等等等的设备。java的本机编译也可以实现让你的游戏在没有java虚拟机的环境下运行。(只需要将用到的库文件一并发布,用到的其实是很小的)


@尾声
我希望这篇文章可以说明用纯净的j2se写一个你自己的j2me模拟器是件多么简单的事情。通过抛弃各个厂商的设备模拟器,你的游戏开发流程将会得到极大的简化。稳定舒服的调试可以让j2me的程序员飞奔起来!~这个“操蛋”模拟器也许会令你的手机游戏开发之路豁然开朗~



下面是英文原文

How to Code Your Own J2ME Simulator

There are few things worse for a programmer than the delay between altering some lines of code and seeing those changes working in game. Developing J2ME games is especially prone to build process delay, with many steps required between compilation and final jar file output. Add to this the complication of many different device emulators with their slow start up times, poor keyboard response and limited debugging support. Mobile Java programming can be sped up and even made a pleasurable experience with the help of some J2SE code which simulates the J2ME APIs. This article will lead programmers through the creation of a deceptively simple J2ME simulator which speeds up programming, improves debugging, facilitates rapid prototyping, aids the creation of device independent code and frees you from build processes that may take minutes to complete.

What's wrong with emulators?

I'm not going to list point by point all the things I personally dislike about using device manufacturer's emulators for J2ME development. I'm sure if you've spent any time developing J2ME applications you've felt there must be something better. Firstly, there are just so many SDKs and emulators available. Installing, maintaining, learning and integrating them into your development process is a time consuming and cumbersome task. Secondly, actually using them is all too often frustrating and slow, when they should be saving you from the even slower task of testing your game on a real device. They are slow to invoke, offer little or no feedback when errors occur, and debugging support is usually unreliable or functionally limited.


Figure 1. What you usually do.

The one area where a good emulator is essential is gauging the memory usage of your game. You can be pretty much assured that if your game runs in the Nokia 7210 MIDP SDK v1.0, it will work on a real 7210. However, you can't say the same for all emulators, and quite often you're left in the dark regarding memory errors.

If you are an experienced J2ME developer who knows how to write memory efficient, jar size and performance conscious code, I would suggest that your primary need for the majority of the programming work on a project is simply a J2ME environment that supports arbitrary screen sizes and all the relevant APIs your code utilises. You can get this in a tool that can be invoked in the blink of an eye both for normal execution and source level debugging, much more simply than you might think.


Figure 2. What you could do.

Compare the steps in figures 1 and 2, which you would potentially perform thousands of times during the programming of a J2ME game. I'd take the three steps over five any day.

Simulating J2ME with Desktop Java

Implementing the necessary J2ME APIs using pure Java turns out to be quite simple. The classes in CLDC 1.0 and 1.1 (java.io, java.lang and java.util) are all derived from their J2SE counterparts, so literally no wrapping or re-writing is required for the base standard library. The javax.microedition.midlet package is simple, well defined and easy to implement. The javax.microedition.lcdui package contains classes that (for the main part) have AWT counterparts, and those that don't, you probably never use anyway, so there's no need to implement them! You can probably do without the rest too, although javax.microedition.rms comes in handy, it's just file storage and again, easy to implement.


Figure 3. Simulator Class Overview

Figure 3 shows all but one class (MIDletStateChangeException) that needs to be implemented to create a functioning J2ME environment. The boxes in red are the main framework of the application and provide the entry point, window to display the midlet upon and keyboard input. The boxes in grey are the J2ME API classes that midlets use. These hook into each other and the simulator classes. Image, Graphics and Font are implemented using their AWT equivalent classes, shown in blue.

 

Start Coding

The MIDlet class contained within the javax.microedition.midlet package is the starting point of all midlet development and is the obvious place to look first when creating a MIDP execution environment. All midlets have to provide a class that extends MIDlet, and the 3 abstract methods that need to be overridden are the almost completely self explanatory startApp, pauseApp and destroyApp.

To get off the ground, our MIDP simulator needs an implementation of the MIDlet class with some public methods that call the abstract midlet lifecycle methods. Since these refer to the trivial MIDletStateChangeException, we should add this too.

Then we'll need a ‘main' application class for command line invocation that loads the concrete midlet class and invokes the public method that calls startApp. The following shows the code needed to load and invoke a midlet.

// Create our AWT main frame (more on this later)
frame = new FrameAWT (midlet_class_name);

// Load and instantiate the midlet
midlet = (MIDlet) Class.forName(midlet_class_name).newInstance();

// Call the midlet startApp method
midlet.amsStartApp ();

The main class should probably be in a package of its own, with a suitable name. I've chosen Jammy as the name of my J2ME simulator. I prefer single words over compound words or complex names that aren't very catchy. With just these 3 classes in place we are able to compile a midlet and execute it by invoking it with Jammy. If you have simple midlet built into a jar, you can invoke the simulator using a command line like Figure 5.

java -classpath bin;BasicMidlet_v1.0.0.jar com.longsteve.jammy.Main BasicMIDlet

Resource loading

J2ME uses the same mechanism to load resources as J2SE, the Class.getResourceAsStream() method. If you are running a midlet from a jar file, with the jar on the classpath, any midlet calls to getResourceAsStream will simply work in the simulated environment. You don't need to run your game code from a jar file however. One of the speed improvements of using a simulator comes from having to avoid the jar packaging step. Your classpath can simply include the directory containing your compiled classes, and your game resource directory (with PNG images in for example). Your compile - run cycle is then reduced to simply that, without the need for preverification and jar packaging.

Adding a display

Just running a basic midlet is ok, but we need to add a display. Since we're writing our simulator in Java, we can use one of the windowing toolkits available with J2SE. We could choose Swing and use JFrame and its companion classes, but I've often had trouble with threads when using Swing. As far as our midlets are concerned too, all we need is a window and a canvas, we don't need all the fancy widgets of Swing, so I'm going to keep it simple and use the AWT.

In J2ME, midlets use classes in the javax.microedition.lcdui package to interface with a display. The Display class provides the static setCurrent method, which a midlet uses to set a Displayable object visible on the screen. Canvas is the primary Displayable subclass a game will use. The midlet is coded with its own subclass of Canvas, which draws the game frames in its paint method, using a Graphics context.

We're going to need to create the javax.microedition.lcdui package and add Displayable, Canvas and Graphics to the simulator. This will mean adding AWT Frame and Canvas classes, and interfacing these with the MIDP classes. Later we'll add the MIDP Image and Font classes and expand the basic midlet even further into something resembling a game.

Main window

FrameAWT extends the AWT Frame class and provides the main window for the midlet display. In order to know how big the window should be, there's a command line argument to the main class that specifies the dimensions for the midlet canvas size. There's nothing to stop these dimensions from being cleverly worked out by referring to known device details, or by inferring the size from some metadata left over from a build process. Command line arguments are convenient for now though.

The canvas

CanvasAWT extends the AWT Canvas class and handles the screen drawing and keyboard/mouse input. CanvasAWT is linked to the current midlet Displayable object by containing a reference to it. A public method on Displayable called invokePaint() is used to actually invoke the midlets Canvas.paint() method. CanvasAWT maintains a MIDP Image object, which is used as the midlet screen. Each time the AWT paint method in CanvasAWT is called, it calls the midlet Canvas paint() method with a MIDP Graphics object derived from the screen Image. When the MIDP Canvas paint() method returns, the screen image is painted to the AWT Graphics context passed to the CanvasAWT paint() method.

The following summarises the code needed in the CanvasAWT.paint() method. The invokePaint() method is part of our implementation of the MIDP Displayable class. When a midlet calls Display.setCurrent(), our Display class actually sets the ‘current' object in CanvasAWT, so all windows paint requests get forwarded to the midlet.

public javax.microedition.lcdui.Displayable current;
public javax.microedition.lcdui.Image midp_screen;

public void paint (java.awt.Graphics g)
{
 javax.microedition.lcdui.Graphics midp_graphics =
  midp_screen.getGraphics ();

 // Call the midlet paint method with a J2ME graphics context
 current.invokePaint (midp_graphics);

 // Draw the midlet screen image to this canvas, scaled up
 g.setClip(0,0,awt_canvas_width,awt_canvas_height);
 g.drawImage(midp_screen._image,
  0,0,getWidth(),getHeight(),
  0,0,midp_screen.getWidth(),midp_screen.getHeight(),
  this);
}

The AWT drawImage() method also handily scales up the MIDP screen image to the required size, so you can resize the main window and see a zoomed in view of the midlet. I hate squinting at tiny emulator windows on my desktop, even the 2x zoom offered by some device emulators isn't enough. By default, Jammy creates its canvas 3x the required size, but a command line parameter could be used to set this as you like.

Keyboard input

Keyboard input is handled by CanvasAWT in combination with the Displayable class. CanvasAWT implements the AWT KeyListener interface, and handles keyboard events, passing the J2SE key code straight to the invokeKeyPressed() method of the current Displayable. 

Our Displayable class contains a table of J2SE key codes and their mappings to MIDP key codes and game actions. It would have been easy to simply re-use the J2SE key codes here, instead of inventing (or borrowing) J2ME codes. However, the J2ME Canvas class definition actually specifies the constant values for the codes (e.g. KEY_NUM0 is 48), so using the same ones will make the simulator directly compatible with existing midlets in this respect.

 

Image and Graphics

MIDP Image objects are implemented fairly simply by using a java.awt.BufferedImage. BufferedImage objects can be created with a width and height, like MIDP Image objects, or created from input streams using one of a number of image support methods or libraries. The Java 5 ImageIO class contains support for PNG images, as does the Advanced Imaging (javax.media.jai) package. I've found these methods of reading PNG data unreliable though. By far the fastest and most robust PNG library for Java (that I've used) is the library from sixlegs.com. The library jar is under 50k, licensed with a library exception to the GPL and succeeds in reading optimised and crushed PNGs where the built in Java support fails.

MIDP Graphics objects are created directly from Image objects and are implemented using a reference to a java.awt.Graphics object, created from the java.awt.Image. Most of the J2ME functionality is simply forwarding on method calls to the underlying AWT Graphics object, e.g. drawLine(). There are some differences though, which include the image anchors commonly used with J2ME for the drawImage() and drawString() methods, see below for an example. Things do get more complex if you start implementing any enhanced MIDP 2.0 methods like drawRegion(), which includes image transformations like rotation and mirroring. These are by no means impossible though, and some investigation into the AWT Graphics2D and AffineTransform classes should provide all the methods required.

public void drawImage(Image img, int x, int y, int anchor)
{
 // default anchor
 if (anchor == 0)
 {
  anchor = TOP | LEFT;
 }

 // Work out the x and y offsets given specific anchors
 switch (anchor & (TOP|BOTTOM|BASELINE|VCENTER))
 {
  case BASELINE:
  case BOTTOM:
  y -= img.getHeight();
  break;

  case VCENTER:
  y -= img.getHeight() >> 1;
  break;

  case TOP:
  default:
  break;

 }

 switch (anchor & (LEFT|RIGHT|HCENTER))
 {
  case RIGHT:
  x -= img.getWidth();
  break;

  case HCENTER:
  x -= img.getWidth() >> 1;
  break;

  case LEFT:
  default:
  break;
 }

 // Draw the AWT image within the MIDP image to our AWT
 // graphics context
 _graphics.drawImage(img._image,x,y,null);
}

Font

Closely related to the Graphics class is the MIDP Font class, and any implementation of Graphics cannot be completed without it. By using java.awt.Font and java.awt.FontMetrics objects, all of the functionality is fairly simple to code. Some elements aren't quite perfect, like underlined text support, but if your game uses a bitmap font, you're not going to worry too about any text limitations.

Extras

Implementing just eight J2ME classes and three application classes is enough to start developing games using your simulator. You have all the components you need, and you may even find that you can run some existing games you already have. However, if you try running anything complex, you're likely to run into class and method not found exceptions. What's great though, is you can immediately see any components you need to add in order to flesh out the simulator.

Assuming you try a MIDP 1.0 compliant game you'll almost certainly find it needs an implementation of the javax.microedition.rms record store package. Stub this out with some dummy code and you'll be surprised at how much will now run. Adding actual file storage isn't hard either using Java's excellent file and stream IO classes.

Following that, it will probably be the additional classes in the javax.microedition.lcdui package that need adding. Command, and CommandListener should be the first, allowing access to device ‘softkeys'. You'll probably not need any of the forms classes, since most games don't use these. Figure 4 shows Jammy running our old X-Change game.


Figure 4. Jammy running X-Change

Following on from the basic MIDP 1.0 functionality, MIDP 2.0 additions slot in fairly obviously, I already mentioned the graphics transformations. If you really want to get a wide range of existing games working, then adding the Nokia UI would be good too. If you do MIDP 2.0 first, then the Nokia UI can be implemented completely on top of it.

Audio

The javax.microedition.media package can be added for audio support. You can use the javax.sound.midi and javax.sound.sampled J2SE packages if you're using Java 5 or above. Again, stub out the classes first so that any midlet you are attempting to run actually works without crashing, then you can implement the functions and get real sound playing.

Bluetooth

Developing midlets with Bluetooth can be awkward since none of the manufactures J2ME emulators support actual wireless transmission. They all simulate it between running instances of emulators. Any actual integration with a real Bluetooth device is going to need testing on a phone, which slows the development and debug cycle immensely. There are javax.bluetooth (JSR-82) packages for J2SE available today. One GNU LGPL implementation is called BlueCove and can be dropped into your classpath for development. Another JSR-82 implementation is called avetana and is available for free trial, with a very reasonable fee for continued use.

3D

Once you've experienced the benefits of developing with a J2SE simulator, you'll want it to support all the common APIs your games utilize. The Mobile 3D Graphics API (JSR-184) is increasingly important to J2ME games. Hybrid Graphics have a development product called Rasteroid which is a J2SE implementation of JSR-184. Adding this to your simulator won't quite provide binary compatibility with midlets due to it's direct dependency on AWT classes, but it will allow you to develop 3D midlets with only a couple of minor source code tweaks.

 

The Benefits

You might wonder if programming your own J2ME simulator is worth the effort, in the face of existing emulators from device manufacturers. My own simulator application grew out of a level design tool for a J2ME game. I wanted to use my game engine within the Java based design tool to provide rapid feedback to the designer. The simplest way to do this was to include the necessary J2ME support classes and methods within my design tool so the game engine class could be dropped in without any source code changes. While coding the design tool, the speed of standard Java development (compile and run) along with instant source code debugging made me wish I could carry on doing it for the rest of the game code.
In the end, I re-factored the J2ME support code into a stand alone application that ran my complete midlet. Then, over new projects, I've gradually implementing more supporting J2ME code as it was needed.

When I first thought about my design tool requirements, had I known of Mpowerplayer I probably would have used it, rather than write my own tool. Mpowerplayer is a great development tool and the company have built a quality service offering around it. There are additional benefits to owning your own in house tool however, many of which weren't immediately obvious.

Really Rapid Development

An ant build process, with its compile, obfuscate, preverify and jar steps will take several seconds. You can set up a project in your IDE that includes your simulator classes, game classes and resource path, hit the ‘Run' button and see you code running immediately. If you use Eclipse you won't even need to compile first, it's like developing any desktop Java application. If your standard studio project environment includes any sort of meta data about your current device target, you can build this into the simulator and have it start automatically at the correct screen dimensions. No need to switch emulators to perform the bulk of the porting work, retargeting a game at a different screen size.

Debugging

With a J2SE simulator you're essentially coding your midlets in a pure Java development environment. As well as hitting the ‘Run' button in your IDE (did I mention that already?), you can hit the ‘Debug' button, and step through the source code. How long have you been developing J2ME games without source code debugging? This feature alone is probably the single best reason to ditch traditional emulators. I know they are supposed to support debugging, and some of the modern ones don't do a bad job, but source level Java debugging in your IDE and debugger of choice is much more mature. You can also take advantage of numerous Java profiler tools.

Game Design/Prototyping

Having nailed the rapid development cycle, you're going to be able to knock up prototypes faster. These builds can be given to designers, testers and anyone else to play with on their PCs, and all they need is a Java Virtual Machine installed. No need for numerous emulators, all to run different builds of the game.

How about a design tool that incorporates a build of your game using your simulator as the runtime? Designers can code levels, or artists can change tile sets and instantly see the results. You could add another AWT window to a special build with some sliders or checkboxes that allow game engine adjustments, so testers can tune difficulty parameters in real time. Artists can drop PNG images directly into the resource directory to see real time changes in the game, rather than fiddling with zip files and updating the JAD files.

Post Development

Having control of the environment your games run in brings further benefits after a game is complete. A lot of post development work is involved in obtaining submission material for operators, screenshots and video footage. Adding a screenshot facility is simply a matter of taking the MIDP screen buffer and writing it to a PNG file (inside CanvasAWT):

java.io.File f = new File ("screenshot.png");
javax.imageio.ImageIO.write((BufferedImage)midp_screen._image,
 "png", f);

Another potential time saver would be the ability to output video files of a game session. This would benefit QA too, with testers being able to record details of a bug. The Java Media Framework contains functionality to write AVI files, and could be hooked into the paint output of CanvasAWT, like the screenshot code.

Having a set of code not constrained to a J2ME execution environment opens up countless possibilities for running your games. You can demonstrate J2ME games to potential clients using a laptop and a projector, rather than hand them a phone to squint at, they get a full screen presentation. You can plan for future developments like VGA screen size phones simply by setting your simulator display to 640x480. Running your games as applets on the web is another obvious possibility, as are Pocket PC and PDA type devices. Java native compilers might also offer a quick route to alternative executable versions of your games for non-Java platforms.

Conclusion

I hope this article has shown how simple it is to write your own J2ME simulator using pure J2SE Java. By becoming (mostly) free of manufacturers device emulators, your full game development cycle can be positively enhanced. Stable source level debugging alone should have J2ME programmers running! Taking control of your midlet execution environment might also open up surprising new avenues for your games.



你可能感兴趣的:(如何编写自己的手机游戏模拟器(翻译版))