J2ME游戏开发笔记整编版
1 J2ME中查表法使用三角函数CLDC和MIDP都没有提供三角函数,而且CLDC1.0中也没有浮点数,所以我们的选择是查表。使用8位定点数的sin和cos表。下面是wtk自带demo中的代码,只提供了有限的几个角度,实际使用时根据需要细化角度值。
// sines of angles 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, all *256
private static final int[] SINES =
{ 0, 44, 88, 128, 165, 196, 222, 241, 252, 256 };
// angle is in degrees/10, i.e. 0..36 for full circle
private static int sineTimes256(int angle)
{
angle %= 36; // 360 degrees
if (angle <= 9) // 0..90 degrees
{
return SINES;
}
else if (angle <= 18) // 90..180 degrees
{
return SINES;
}
else if (angle <= 27) // 180..270 degrees
{
return -SINES;
}
else // 270..360 degrees
{
return -SINES;
}
}
// angle is in degrees/10, i.e. 0..36 for full circle
private static int cosineTimes256(int angle)
{
return sineTimes256(angle + 9); // i.e. add 90 degrees
}
(2006.5 注:有一些算法可以生成三角函数值,这样只要在游戏载入时生成一下函数表即可,节省一些数据)
2 J2ME中使用随机数
产生0~n之间的随机数:
(ran.nextInt()>>>1)%n
或
(ran.nextInt()&0x7FFFFFFF)%n
产生-n~0之间的随机数:
(ran.nextInt() | 0x80000000 )%n
3 尝试IO优化
正在开发的一个游戏,由于读地图的时候做了图片切割,所以速度比较慢。(在我开发上一个游戏的时候,读取地图时没有装载切割图片,速度非常快,看来IO操作的速度和createImage,drawImage相比是微不足道的)对于IO的优化也许根本不会明显的提高速度,但我还是试了一下。
分析了一下代码,在最初的代码中为了比较方便的读取各种类型的数据,使用DataInputStream套接InputStream。可是我仔细看了一下我读取得数据,居然都是byte,唯一的一个char也是被我用两个byte手工组装起来的。这下,DataInputStream看来是不需要了。于是我做了个实验,没改动之前读取地图耗时1242ms,将DataInputStream去掉直接使用InputStream耗时1065ms,虽然每次试验的结果都稍有不同,但大概还是节约了200ms左右。
还能再加快点吗?再观察一下代码,我发现数据是通过多次的read操作读取进来的。太过频繁的io操作会不会降低速度呢?如果用一个字节数组作缓冲一次性将数据都读进来会不会快点?嗯,试一试才知道。但是我怎么知道一个流的大小呢?InputStream的avaliable方法总是返回-1啊!打开两次流,第一次先计算大小?对了,还有一个方法。直接将文件大小写到文件前面。地图文件是用自己的编辑器生成的,知道大小很容易。于是我在文件前面用两个byte纪录了文件的大小,先从流中读取2个byte,得到文件大小后,再用read(byte[],int,int)方法将整个流读取到缓冲中。然后,我的所有数据操作都从缓冲中读取。好,试验一下,结果是:1154ms。阿? 慢了近100ms。事实证明了这个猜想是错误的。原因?也许只有了解KVM的机制才知道。
弄完速度的问题,我又觉得读取文件的try块太大了,因为是边读边处理数据,所以try块变得很大。try块太大会增加class文件的大小。于是我用一个方法将读取byte的操作封装起来,当然这个方法是声明为private static的,但究竟能不能内联,只有编译器和kvm才知道。在这个方法内部从流中读取一个字节的时候采用了try,catch结构,这就使一个大try块分散成若干小try块。试验了一下,耗时1089ms,诶,还是慢了点。现在对于速度的要求比空间更高,更何况减小try块节省的10几个字节打包后基本忽略不计了。所以这个优化又失败了。
小结:能使用简单流的时候就不要使用复杂流,不要太相信理论上的说法,只有试了才知道。
注:试验数据是Nokia3100手机的实机测试数据,在Nokia 3300上这个数据更小些,最快约800多ms
4 压缩还是不压缩
做J2ME的都知道Midlet Suite的容量实在太小了,于是不免想做点压缩。前些天,我就尝试了一次压缩。我自己定义的地图文件里有3层数据,其中2,3层有大片连续分布的相同的值。 唉?我一琢磨,使用一个简单的行长编码压缩,仅对这个值进行行长编码,算法很简单速度又不慢,却可以大大减小地图文件的大小。看起来真的很不错诶!说干就干,忙了半天,又改地图编辑器,又改游戏中读地图的代码。总算搞定,试了一下,原来2.23k的一个文件被压缩到900多字节。好像很不错啊,接着我打了个jar包,却突然发现这个jar文件好像并没有比原来小阿!似乎还大了点。我连忙找出备份的代码,果然原来的jar更小点!怎么回事啊??我突然想到,jar本身就是压缩格式的。难道。。。我赶快用winrar打开两次的jar文件观察。~~~~~原来如此!原来的jar中,2.23k的文件的包大小为185字节,而我现在的jar中,900多字节的文件的包大小为216字节。也就是说,我自己先压缩一遍的文件打包后还不如不压缩的小!
看来自己做压缩之前,一定要先看看你想压缩的文件在包里面的大小。还有对于png文件,使用某些工具优化后,在包里面的大小却变大了。这个还真是要注意阿~!
(05.12.31注:某些压缩算法确实比zip压缩效率要高,可以使用,不过副作用是解压导致loading时间变长)
(2006.5注:有些时候,需要节省一下内存,可以将数据打包压缩一下,package & compress模式,存在内存中,需要时解压)
5 同时多处异常
程序出现exception时,在一个外包函数处捕获到了,显示为函数a出现异常,然后去a中捕获却没捕获成功,但是仍然发生了异常.
原来是外包函数中调用的另一个函数b也产生了同样的异常.
同时多处异常-小心!
----------------开发工具问题-----------------
1 Eclipse Tips
1.在工具条上有个文本形象的按钮"show source of selected element only".当编辑类的某个成员(方法或域)时,按下这个按钮,则当前窗口会只显示你正在编辑的类成员.再按一下则恢复.
2.显示java文件行号.菜单中选择Window->;Preferences打开Preferences窗口后选择Java->Editor,在右边的选项中选中Show line numbers.
显示非java文件行号.在Preferences窗口中选择Workbench->Editors->Text Editor,同样右边的选项中选中Show line numbers.
3.编辑代码时,按ctrl+/可以注释当前行或选中的多行代码;按Atrl+/可以显示自动完成代码的提示。
4.选中代码,按 ctrl+shift+F 格式化代码
5.输入syso,按atrl+/可出来 System.out.println("") ;
2 运行Nokia模拟器的一个注意事项
这是一个老问题了,原来用WTK的时候就有,在WTK中启动Nokia的模拟器,如果先前已经打了包,那么运行的是打包的程序,想当年经常会很郁闷为什么改动了没效果,后来养成一个习惯,将jar装到手机测试后随手删除。
今天用JBuilder的时候又碰到了这个问题,也是Nokia的模拟器,如果已经建立了一个archive,那么Nokia模拟器运行的总是包,呵呵,所以要么将archive从project中remove,要么每次都rebuilder这个archive。
3 Eclipse集成Motorola模拟器
在Eclipse的菜单/工具条中选择Run->External Tools,打开面板后,选择program,然后new一个新的配置
1 在Location中填入Moto模拟器的路径,如:C:\Program Files\Motorola\SDK v4.2 for J2ME\EmulatorA.1\bin\emujava.exe,Moto的不同模拟器支持n种不同机型,需要看moto sdk的文档才知道。
2 在Arguments里填入执行的参数,包括jad路径,模拟器使用的机型。如:"${project_loc}\deployed\${project_name}.jad" -deviceFile Resources\V600.props
我是让模拟器执行deployed里面的jad/jar,${project_loc}是工程路径,${project_name}是工程名。这里选择的机型是V600.
说明:这种方法的局限在于只能执行jar,所以每次运行前必须打包。实际使用前需要为没种机型配置一个run,由于使用了通配参数,所以所有的工程都可以使用一个配置
(05.12.31注:现在某些MotoSDK已经可以和Eclipse集成了!)
4 初次使用JBuilder 7-若干小问题
(1) MobileSet问题
JBuilder7需另外安装MobileSet, Mobileset自带了一个WTK. 如果不安装MobileSet,JB7配置JDK时不能自动识别WTK,安装MobileSet后,可以通过配置JDK的方法加入新的WTK
(2) 资源文件问题
JBuilder的所有源文件都应该放在source path中,可以在工程属性中设置source path,资源文件也一样。既可以和源文件放在一个source path(即文件夹)中,也可以放在另外的source path中。需要注意的是,JBuilder只默认识别一定数量的后缀,如png,如果你使用了其他后缀的资源文件,如dat,bin,需要先把该文件通过add files加入到工程中,选择文件属性,设置为copy,这样该后缀的文件就被识别为资源文件了。
(3) 光标不对问题
最简单的办法-改字体,我改成了第一种字体(JB7中),感觉和默认字体没什么不同。至于这个问题的根本解决方法网上有文论述。
(4) 鼠标滚轮无效问题
据说这个问题只在JB7和以下版本中存在,原因是只有J2SDK1.4以上才支持滚轮,所以需要将JB7的JDK改成1.4的. 方法是修改JBuilder7\bin\jdk.config文件,将javapath和addpath两行修改,例如:
# javapath ../jdk1.3.1/jre/bin/hotspot/jvm.dll
javapath Y:\j2sdk1.4.2\jre\bin\server\jvm.dll
# addpath ../jdk1.3.1/lib/tools.jar
addpath Y:\j2sdk1.4.2\lib\tools.jar
5 百宝箱应用编译打包事宜
1 编译时,设置javac 的target vm为1.1即可通过移动检测。wtk中无法实现。在Eclipse中可以在java-compiler-Compliance and Classfiles中做以下设置:
Compiler compliance level: 1.4
Generated .class files compatibility: 1.1
Source compatibility: 1.3
(2005.12.31注:JBuilder中也有类似的选项,如果使用命令行或Ant,都只要将javac的targetVm参数设置为 target 1.1)
2 用eclispe打混淆包。但eclipse编写jad中文会出现乱码,所以用wtk编写正确的jad,然后用wtk打包(注意不能覆盖eclispe打的包),这是为了用wtk获得正确的jad和manifest文件。将elcipse打包出的jar解压,用wtk生成的mainifest代替原jar中的mainifest文件,然后用winrar打包(zip格式,可选最大压缩,注意要选择所有的文件后打包,不要将外面的整个目录打包).最后将jad中的jar size改为这个最新的jar的字节数。
(2005.12.31注:我不用eclipse很多年,据说现在的eclipse me新版没这问题了,我当时用的时候eclipse me的版本才0.4.6)
另:1. Nokia S60,SE k700机器中显示的游戏名字为MIDlet-1中的名字,而Nokia40为MIDlet-Name中的名字
2. 根据sp提供的资料Nokia 7650 游戏不能用中文名(其实NGageQD可以)
----------------机型相关问题-----------------
1 Nokia S60 IO操作内存泄漏不可不察
Nokia7650,3650
游戏运行过程中,有时会出现“存储已满”的对话框,出现的位置不固定
游戏运行过程中,有时出现“应用程序错误NullPointerExcept”,“程序已关闭MidpUi”的对话框
游戏运行过程中,有时会出现“程序已关闭 MidpUi ViewSrv 9”的对话框,出现的位置不固定
其实这个问题是由S60的getResourceAsStream方法内存泄漏的bug引起的,由于每次切换地图时io操作都要读取大量数据,内存泄漏积累到一定程度就引起了“存储已满”,白屏,死机,进而会引起null pointer异常等。解决方法是尽量减少io操作的次数。如果内存够大就一次将资源读入。
2 NokiaS60模拟器异常退出
症状:模拟器自动关闭,没提示任何错误
原因:使用了Nokia UI API中的灯光或振动控制,而Nokia S60部分机型和对应的模拟器不支持这两个特性.
3 NokiaS60 UI API bug
1 旋转后,并以clip的方式向缓冲上贴图,clip无效
2 无法创建透明muttable Image
此两点,致命伤,带来许多不变
4 Nokia S60的几个问题
(1) 不能每帧调用 System.gc(),否则严重降低fps
(2) Nokia S60机器的不同机型对于translate 和 setClip的处理不一样。在Nokia N-Gage QD等机型中,setClip是相对于translate以后的坐标计算的,而在Nokia 6600,6670等机型中,setClip不受translate的影响,永远只相对于屏幕左上角(0,0)点计算。所以如果在Nokia6670中,使用先translate再setClip的方法画子图,则会出现错误。为了统一代码,在Nokia S60中不要使用translate,即使用,两次translate之间不要进行setClip.修改后的画子图函数为:
public static void drawSubImg(Graphics g,Image img,int x,int y,int sx,int sy,int swidth,int sheight)
{
g.setClip(x,y,swidth,sheight);
g.drawImage(img,x-sx,y-sy,GLT) ;
g.setClip(0,0,width,height) ;
}
(3) 部分Nokia机型(6600,6670等)退出后报错null pointer exception的解决方法
不要在在主while循环中调用destroyApp,而改成检测一个标志,退出主循环后再调用destroyApp
boolean exit ;
...
while(!exit){
...
if(...){
exit = true ;
}
...
}
destroyApp(true);
注:可在destroyApp内部调用notifyDestroyed
5 Nokia"不能运行应用程序"错误新解
Nokia手机运行J2ME程序的时候出现“不能运行应用程序”的错误,一般都是内存不足引起的,但今天遇到这样的错误,却发现是另一个原因。即当使用nokia的UI API,DirectGraphics的drawImage时,如果旋转参数设置不当,也会出现“不能运行应用程序”的错误。
6 Nokia系统bug两则
(1) Nokia7650(V4.46)应用程序目录显示bug
应用程序安装后,打开应用程序目录,显示错误提示:
"程序已关闭 MidpUi USER9",应用程序目录无法进入。
分析后发现,原来是新安装的应用程序没有在mainfest.mf中的
midlet-1属性中指定应用程序图标,导致程序目录无法显示图标。
在我所见到NokiaS40机器上和NGageQD上,如果图标没指定或指定了但
不存在,将显示默认的图标。
此bug对于其它版本的7650或者其他机型是否存在尚不得知。
解决方法:使用seleQ将7650c:\system\midp中刚安装的程序目录删掉,即可正常进入应用程序目录。
在应用中使用自己的应用程序图标,并正确设置,以避免让用户遭遇到此bug。
(2) Nokia3100(v3.10)游戏目录振动设置与应用程序中使用振动冲突的bug
在Nokia3100等机型中,提供了一个游戏目录管理游戏类应用。该目录
可以设置目录中的游戏运行时是否发声,振动和使用网络。对于
Nokia3100(V3.10)如果将振动设置关掉,而在应用程序中使用了振动,则
会产生一个异常。此bug是在10个月之前发现的,记不清是哪个异常了。
此bug对于其它版本的3100或者其他机型是否存在尚不得知。
解决方法:在应用程序中使用振动的地方增加异常处理。
7 Motorola手机J2ME应用问题
(1) 应用程序图标
必须在jad 文件Midlet-Icon属性中指定图标文件,Midlet-1中指定的图标无效
Moto V系列图标大小应为15*15,其他尺寸无法显示。
(2) 左右软键问题
Motorola手机操作系统设定是:右软键确认,左软键取消。所以,我们的程序应该和这个习惯保持一致。
(3) Key Code
Moto V的key code不同于其他Midp2.0机器
左软键:21
右软键: 22
中键: 20
up: 1
down: 6
left: 2
right: 5
(2005.12.31注:在遇到新机型时,先测试一下keyCode比较好)
8 MIDP2.0 Canvas全屏问题
MIDP2.0 Canvas可以调用setFullScreenMode(true)将Canvas设置成全屏,但设置成全屏后新的Canvas width & height的获得对于不同手机却并不一样。
(1) MotoV系列
调用setFullScreenMode(true)后,将触发sizeChanged事件,此事件从系统接受两个参数,即为Canvas全屏后的width & height,通过这个事件可以获得新的宽高。
protected void sizeChanged(int w, int h)
{
width = w ;
height = h ;
}
但要注意,此事件并不是同步的,就是说如果你调用了setFullScreenMode(true)之后,立即使用新的width,height,有可能获得错误的结果。
(2) SE K700
调用setFullScreenMode(true)后,不会触发sizeChanged,而是通过getWidth和getHeight获得新的宽高。SE的setFullScreenMode调用后是立即返回的,所以可以获得正确的width & height
对于其他机型暂时还不了解
----------------移植问题-------------------
1 键盘响应
不同的机型对于键盘事件的响应不一样。经过我的测试,Nokia 7210,3100一次只能接受一个按键信息。(我写了个测试程序,发现如果一个键被按下后没有松开,则KeyPressed事件不会再产生,即其他键的按下操作无效)所以,用缓冲处理控制精灵运动时,如果规定只能四方向运动。如果up已按下,再按下left,精灵的运动方向并不会改变。不过将按键缓冲。按下up,按下left不释放,松开up---精灵就会向左运动。(在松开up后产生了left的KeyPressed事件!奇怪吗?松开up后我并没有进行"按下"left这个动作--left键在up松开前就被按下了且没有松开。似乎机器一直在监测键盘上各键的状态,并且有一个等待队列。)
在wtk的标准模拟器上就不同了。它可以接受多个按键“同时”按下的事件。所以如果用四个并列的if处理,精灵是可以斜着运动的。如果用if else处理,则如果已经按下一个方向键,然后再按下另一个,是否能改变方向受到if else 语句中顺序的影响。即,如果是 if(up) else if(left),则会先检查up键,所以如果已经按下了left,再按up是可以向上运动的,反过来就不行了。(这个自然:)
其它的机型由于手头没有机器,我也没试过。应该也是如此吧。
2 多机型移植经验谈
开发的时候平台是Nokia 40,然后移植到Nokia 60, Moto V, SE等,总结一下大概需要几个版本。
1。 Nokia 40版, 使用Midp1.0+Nokia UI API
2。 Nokia 60版, 使用Midp1.0+NOkia UI API
3.Nokia Midp2.0版,如6600,7610,使用Midp2.0
4。Moto V版,使用Midp2.0
5。 SE版,使用Midp2.0
6. 三星s100,s200,c100,使用Midp2.0
几点开发经验:
1。各机型之间最大的差别就是屏幕大小不同。所以游戏中要能自适应屏幕大小
2。不使用Midp2.0的GameAPI会比较方便移植,只要自己封装切图,旋转等函数即可。NokiaUI API和Midp2。0都支持图片选转。2.0支持的更好。注意Nokia 60不支持创建可变的透明图片,所以要用其他方法代替。
3。NOkia 6600,7610的UI API有问题(图片旋转),所以用了Midp2.0代替
4。支持MIDP2。0的机器程序大致相同,其中MOto,SE,SX都差不多。但也有细微差别。如SE不支持全屏。所以screenSizeChanged方法无效。
5。说说声音播放。NOkia s40上我坚决不用声音,一是容量限制,二是太难听。其他机型都可以支持midi和wav.不过没有发现可以同时播放2个midi的机型,moto v和se都可以同时播放midi和wav,nokia则不行。
3 移植一法
近日观察某些游戏的源代码(反编译后的),发现有个方法挺方便游戏的移植的。定义一个接口(比如stringTable)将游戏中所用到的静态字符串都定义为接口的常量。然后,让使用到这些字符串的类实现stringTable接口。这样移植的时候只要修改接口里面的字符串就行了。当然,对于游戏中坐标的定位,最好使用getWidth(),getHeight()还有Font类的方法stringWidth,不要定死了。这样的话,移植工作就比较轻松了。
4 检测机型
在J2ME开发中,往往遇到根据不同机型做不同事情的情况,比如Nokia3650的键盘比较特殊,Nokia7650不支持mmapi,所以需要获得机型信息。
下面是一段简单的代码
public static void checkPlatform()
{
String platform = System.getProperty("microedition.platform") ;
String tmp = null ;
if(platform.length()==9)
tmp = platform ;
else if(platform.length()>9){
tmp = platform.substring(0,9) ;
}
if(tmp!=null){
if(tmp.equals("Nokia3650")){
is3650 = true ;
}
else if(tmp.equals("Nokia7650")){
is7650 = true ;
}
}
}
获得机型信息还包括版本号等等,所以要截取前面的几个字符比较。
不过得到的机型字符串有时并不保险,如早期的Nokia N-Gage获得得并不是N-Gage,不过3650和7650还是可以的
页:
[1]