J2ME Game开发笔记

一般类问题------------------
  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[angle];
   }
   else if (angle <= 18) // 90..180 degrees
   {
   return SINES[18-angle];
   }
   else if (angle <= 27) // 180..270 degrees
   {
   return -SINES[angle-18];
   }
   else // 270..360 degrees
   {
   return -SINES[36-angle];
   }
   }
   // 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->references打开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还是可以的

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值