如何编写自己的手机游戏模拟器(翻译版) - 北风技术专栏

作者: 发布于:2010年06月14日 14:45

因为觉得本文还是可以起到抛砖引玉的作用的,就照着英文版自己翻译了,由于晚上时间伧俗所以难免有所纰漏,仅供参考。
如何去编写自己的手机游戏模拟器呢?
对于一个程序员来说,修改几行代码之后,等待它运行起来看效果时的编译等待时间显得特别漫长,而来回修改运行调试的无尽的等待应该算最操蛋事情了。而开发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),我们不需要任何xx的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倍。不过{zh0}你将他做成自定义的,以后想设置为多大都可以。

@键盘输入
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对话,所有的功能都可轻松实现。有些并不是很xx,比如对下划线的支持,但是如果你的游戏使用点阵字体,你xx不必操心字体的问题。

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

@音频
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。使用这个并不能xx让你在你的AWT模拟器里实现3D,但是他可以让你开发3D的midlet项目只需要短短几行代码。

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

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

@调试
用j2se的模拟器实际上你就是在一个纯净的java环境下编写你的midlet代码。我前面已经提过,你可以点击“运行”以运行它,也可以点击“调试”然后步进源代码。你开发j2me的游戏有多久没有进行源码调试了呢?这个特性也许是抛弃传统模拟器的{zd0}理由。我知道它们应该支持调试,并且一些新的做的也不差,但是在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手机开发游戏仅仅需要将你的模拟器设置为640×480的屏幕尺寸。你可以将你的游戏在网页上玩,在微型电脑上玩,在PDA上等等等等的设备。java的本机编译也可以实现让你的游戏在没有java虚拟机的环境下运行。(只需要将用到的库文件一并发布,用到的其实是很小的)

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

作者:
来源:
原文链接:

( 内容完 )

添加收藏到:

您可能还对这些文章感兴趣:

  • 郑重声明:资讯 【如何编写自己的手机游戏模拟器(翻译版) - 北风技术专栏】由 发布,版权归原作者及其所在单位,其原创性以及文中陈述文字和内容未经(企业库qiyeku.com)证实,请读者仅作参考,并请自行核实相关内容。若本文有侵犯到您的版权, 请你提供相关证明及申请并与我们联系(qiyeku # qq.com)或【在线投诉】,我们审核后将会尽快处理。
    —— 相关资讯 ——