关于作者

姓名:方雨

性别:男

出生日期:--

地区:江南

联系电话:

QQ:447979601婚否:保密
用户名:j2mego
笔名:江南雨
地区: 江南
行业:其他

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言



访问统计:
文章个数:15
评论个数:4
留言条数:4




Powered by BlogDriver 2.1

江南烟雨

 

文章

J2ME游戏开发笔记
J2ME游戏开发笔记


---------------一般类问题------------------
1 J2ME中查表法使用三角函数

   CLDC和MIDP都没有提供三角函数,而且CLDC1.0中也没有浮点数,所以我们的选择是查表。使用8位定点数的sin和cos表。下面是wtk自带demo中的代码,只提供了有限的几个角度,实际使用时根据需要细化角度值。

JAVA手机网[www.cnjm.net]
// 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)
JAVA手机网[www.cnjm.net]
   {
       angle %= 36;    // 360 degrees
       if (angle <= 9)          // 0..90 degrees
JAVA手机网[www.cnjm.net]
       {
           return SINES[angle];
       }
       else if (angle <= 18)    // 90..180 degrees
       {
           return SINES[18-angle];
       }
JAVA手机网[www.cnjm.net]
       else if (angle <= 27)    // 180..270 degrees
       {
           return -SINES[angle-18];
       }
       else                     // 270..360 degrees
       {
JAVA手机网[www.cnjm.net]
           return -SINES[36-angle];
       }
   }

   // angle is in degrees/10, i.e. 0..36 for full circle
   private static int cosineTimes256(int angle)
JAVA手机网[www.cnjm.net]
   {
JAVA手机网[www.cnjm.net]
       return sineTimes256(angle + 9);     // i.e. add 90 degrees
   }


2 J2ME中使用随机数

 产生0~n之间的随机数:
(ran.nextInt()>>>1)%n

JAVA手机网[www.cnjm.net]
(ran.nextInt()&0x7FFFFFFF)%n

产生-n~0之间的随机数:
(ran.nextInt() | 0x80000000 )%n
JAVA手机网[www.cnjm.net]


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的机制才知道。
JAVA手机网[www.cnjm.net]
弄完速度的问题,我又觉得读取文件的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时间变长)


5 同时多处异常
程序出现exception时,在一个外包函数处捕获到了,显示为函数a出现异常,然后去a中捕获却没捕获成功,但是仍然发生了异常.
原来是外包函数中调用的另一个函数b也产生了同样的异常.
同时多处异常-小心!

----------------开发工具问题-----------------
1 Eclipse Tips
1.在工具条上有个文本形象的按钮"show source of selected element only".当编辑类的某个成员(方法或域)时,按下这个按钮,则当前窗口会只显示你正在编辑的类成员.再按一下则恢复.
JAVA手机网[www.cnjm.net]
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.
JAVA手机网[www.cnjm.net]

说明:这种方法的局限在于只能执行jar,所以每次运行前必须打包。实际使用前需要为没种机型配置一个run,由于使用了通配参数,所以所有的工程都可以使用一个配置
(05.12.31注:现在某些MotoSDK已经可以和Eclipse集成了!)

JAVA手机网[www.cnjm.net]
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中的名字
JAVA手机网[www.cnjm.net]
   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
JAVA手机网[www.cnjm.net]
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)
JAVA手机网[www.cnjm.net]
{
 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){
JAVA手机网[www.cnjm.net]
...
JAVA手机网[www.cnjm.net]
 if(...){
JAVA手机网[www.cnjm.net]
   exit = true ;
 }
...
}

destroyApp(true);

注:可在destroyApp内部调用notifyDestroyed

5 Nokia"不能运行应用程序"错误新解
   Nokia手机运行J2ME程序的时候出现“不能运行应用程序”的错误,一般都是内存不足引起的,但今天遇到这样的错误,却发现是另一个原因。即当使用nokia的UI API,DirectGraphics的drawImage时,如果旋转参数设置不当,也会出现“不能运行应用程序”的错误。

6 Nokia系统bug两则
JAVA手机网[www.cnjm.net]

(1) Nokia7650(V4.46)应用程序目录显示bug

 应用程序安装后,打开应用程序目录,显示错误提示:
 "程序已关闭 MidpUi USER9",应用程序目录无法进入。
 分析后发现,原来是新安装的应用程序没有在mainfest.mf中的
midlet-1属性中指定应用程序图标,导致程序目录无法显示图标。
JAVA手机网[www.cnjm.net]
 在我所见到NokiaS40机器上和NGageQD上,如果图标没指定或指定了但
不存在,将显示默认的图标。
 此bug对于其它版本的7650或者其他机型是否存在尚不得知。
JAVA手机网[www.cnjm.net]

解决方法:使用seleQ将7650c:\system\midp中刚安装的程序目录删掉,即可正常进入应用程序目录。
在应用中使用自己的应用程序图标,并正确设置,以避免让用户遭遇到此bug。

(2) Nokia3100(v3.10)游戏目录振动设置与应用程序中使用振动冲突的bug
 在Nokia3100等机型中,提供了一个游戏目录管理游戏类应用。该目录
可以设置目录中的游戏运行时是否发声,振动和使用网络。对于
Nokia3100(V3.10)如果将振动设置关掉,而在应用程序中使用了振动,则
会产生一个异常。此bug是在10个月之前发现的,记不清是哪个异常了。
 此bug对于其它版本的3100或者其他机型是否存在尚不得知。

解决方法:在应用程序中使用振动的地方增加异常处理。


7 Motorola手机J2ME应用问题

JAVA手机网[www.cnjm.net]
(1) 应用程序图标
 必须在jad 文件Midlet-Icon属性中指定图标文件,Midlet-1中指定的图标无效
JAVA手机网[www.cnjm.net]
 Moto V系列图标大小应为15*15,其他尺寸无法显示。

(2) 左右软键问题
Motorola手机操作系统设定是:右软键确认,左软键取消。所以,我们的程序应该和这个习惯保持一致。

JAVA手机网[www.cnjm.net]
(3) Key Code
Moto V的key code不同于其他Midp2.0机器
左软键:21
右软键: 22
中键: 20
up: 1
down: 6
left: 2
JAVA手机网[www.cnjm.net]
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 ;        
   }
JAVA手机网[www.cnjm.net]
但要注意,此事件并不是同步的,就是说如果你调用了setFullScreenMode(true)之后,立即使用新的width,height,有可能获得错误的结果。

(2) SE K700
JAVA手机网[www.cnjm.net]
调用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
JAVA手机网[www.cnjm.net]

几点开发经验:
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,所以需要获得机型信息。

JAVA手机网[www.cnjm.net]
下面是一段简单的代码
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")){
JAVA手机网[www.cnjm.net]
            is7650 = true ;
        }
       }
}
获得机型信息还包括版本号等等,所以要截取前面的几个字符比较。
不过得到的机型字符串有时并不保险,如早期的Nokia N-Gage获得得并不是N-Gage,不过3650和7650还是可以的

- 作者: 江南雨 2006年04月1日, 星期六 19:33  回复(0) |  引用(0) 加入博采

Java手机游戏编程之MIDP图形设计篇

Java手机游戏编程之MIDP图形设计篇
本文版权归原作者,中国JAVA手机网收录本文的目的是让更多人阅读到此文章。转载请注明出处为中国JAVA手机网
来自:http://www.cnjm.net/tech/article1051.html

[转载于java-cn]

  介绍
  本文通过一个简单的MIDlet游戏程序示例,简要介绍了MIDlet图形编程,以期能对开发者深入理解MIDP图形编程和开发复杂的移动游戏有所帮助。
  一. MIDLET图形
  1、MIDlet 图形简述
  移动信息设备描述(Mobile Information Device Profile ,MIDP)定义了一套应用编程接口(API),用于运行在MIDP容器中的MIDlet应用程序。这套API本身是建立在有限连接设备配置(Connected Limited Device Configuration ,CLDC)应用编程接口的基础上的。MIDP用户界面应用编程接口类并不是基于Java抽象窗口工具包(Abstract Window Toolkit ,AWT)设计。它们是专为手机和呼机这样的小型移动信息设备而设计的,这类设备的特点是只有很小的屏幕和键盘。当一个程序员在编写MIDP图形应用程序的时候,他可能只能使用MIDP或CLDC应用编程接口。
  MIDP的中心抽象是屏幕,这句话的含义是MIDP的用户界面设计是基于屏幕的(screen-based)。也就是说,Screen类封装了设备特定的图形和用户交互,所有的用户界面组件都位于屏幕上,并且一次只显示一个屏幕,并且只能浏览或使用这个屏幕上的条目。由屏幕来处理所有的用户界面事件。并只把高级事件传送给应用。之所以采取这种面向屏幕(screen-oriented) 的方式,主要是因为移动设备的显示屏幕和键盘实是种类太多了,几乎每个厂家都多多少少有所不同。图1是基于屏幕的MIDP图形用户界面的一些例子。

图1:基于屏幕的MIDP 图形用户界面
  MIDP 应用编程接口具有高级用户界面类和低级用户界面类。高级用户界面类(例如Form、List、TextBox、TextField、Alert,及Ticker)可被适配到设备上:支持图像、文本、文本输入域、单选按钮等。低级用户界面类(Canvas类)允许开发者根据需要绘制任意图形。MIDlet可以运行在各种不同尺寸的彩色、不同灰度等级或黑白屏幕的手机上。高级用户界面类是通用用户界面元素的抽象,它的用途在于提高MIDlet跨不同设备的移植性,并且可以使用本地设备的外观表现。低级应用编程接口则能够更直接地控制显示内容,但是MIDlet设计者应该确保其在不同设备(显示尺寸、键盘、色彩等)上的可移植性。上面的例子既用到了高级应用编程接口又用到了低级应用编程接口。
  所有的MIDP图形用户界面类都是javax.microedition.lcdui程序包的一部分。

  2、MIDlet屏幕
JAVA手机网[www.cnjm.net]
  MIDP有两种主要的屏幕类型:
  A 高级屏幕
   它包括简单的高级屏幕类,例如List和TextBox。用户不能添加额外的图形用户界面组件到这种类型的屏幕中。九宫格MIDlet示例程序使用的屏幕是继承于名为ChoosePieceScreen的List类,用于游戏者在游戏开始时选择棋子。
   一般的Form屏幕类和List类很相像,但是它允许使用额外的图形元素,例如:图像、只读文本域、可编辑文本域、可编辑数据域、标尺和选项组。Form条目可以任意地被添加或删除。九宫格例程中没有使用Form类。
  B 低级屏幕
   Canvas(画布)屏幕(和Graphics、Image类) 可以用来编写基于低级应用编程接口的用户界面。这些类给予MIDlet程序员很大程度的绘画灵活性。程序员可以绘制各种类型的图形元素,例如:线、弧、矩形、圆角矩形、圆、文字(不同颜色、字体、大小)、位图剪辑等等。大部分的游戏MIDlet是使用基于画布屏幕类的主图形用户界面元素编写的。
  一个MIDlet用户界面通常包含一个或多个屏幕。因为每次只能显示一个屏幕,因此MIDlet具有良好设计的结构是非常重要的进行,这样就能更加容易地处理屏幕之间内容的切换。
  下面的代码段说明了在一个MIDlet中切换屏幕的方法,基于屏幕类和对应的MIDlet回调。
  代码段1:
JAVA手机网[www.cnjm.net]


Class MyMIDlet extends MIDlet
{
private FirstScreen firstScreen;
private SecondScreen secondScreen;
public MyMIDlet()
{

}
public void startApp()
{
Displayable current = Display.getDisplay(this).getCurrent();
if (current == null)
{
firstScreen = new FirstScreen(this, …);
Display.getDisplay(this).setCurrent(firstScreen);
//显示应用程序的第一个用户界面屏幕
}
else
{
Display.getDisplay(this).setCurrent(current);
}
JAVA手机网[www.cnjm.net]
}
// FirstScreen 回调切换到下一个屏幕
public void firstScreenDone()
{

JAVA手机网[www.cnjm.net]
secondScreen = new SecondScreen(this, …);
display.getDisplay(this).setCurrent(secondScreen);
}
// SecondScreen回调终止应用程序
public void secondScreenQuit()
{

destroyApp(false);
notifyDestroyed();
}

}
  这个MIDlet使用了两个屏幕类(FirstScreen和SecondScreen)作为用户界面。当开始执行MIDlet的时候,它设置当前显示屏幕为FirstScreen。当需要从FirstScreen切换到SecondScreen的时候,FirstScreen 调用父MIDlet方法firstScreenDone(参见下面的代码)。firstScreenDone方法创建并设置SecondScreen为当前显示的屏幕。
JAVA手机网[www.cnjm.net]
  代码段2: 包含MIDlet回调的FirstScreen示例


Class FirstScreen extends Form implements CommandListener {
private MyMIDlet midlet;
public FirstScreen(MyMIDlet midlet)
{
this.midlet = midlet;

}
public void commandAction(Command c)
{
if (c == cmdQuit)
{
parent.firstScreenDone();
}

JAVA手机网[www.cnjm.net]
}

}
  3、MIDP用户界面应用编程接口
  保证基于高级应用编程接口类的用户界面对象的可移植性和适用性是MIDP设备的职责。
  另一方面,像Canvas和Graphics这样的低级类为程序员提供了更大的自由空间让其控制其用户界面的视觉表现,并且监听低级键盘事件。程序员还要负责确保应用程序在不同特性(例如显示尺寸、彩色或黑白,以及不同键盘类型)的移动设备上的可移植性。比如说,有可能需要使用getWidth()和getHeight()方法调节用户界面外观使其适应一个或更多设备的可用Canvas尺寸。
  下面的九宫格MIDlet例程将介绍:
   简单应用高级应用编程接口;
   使用低级应用编程接口来绘制线、弧、字符串和图像等图形;
   不同显示尺寸的移动设备之间的MIDlet移植问题
   键盘代码与游戏动作之间的映射
  本章概述了MIDP图形用户界面的设计,如果想得到更进一步的信息,请参阅
http://java.sun.com/products/midp/ 。
  二. 示例:九宫格(TICTACTOEMIDLET)
  1、设计
  概述
  这个示例应用程序是一个简单的MIDlet,允许游戏者与MIDlet程序之间玩一种称为九宫格的人机游戏。这个例程说明:
   使用高级和低级用户界面组件
JAVA手机网[www.cnjm.net]
   在多显示屏幕之间进行切换
   处理简单的命令
   动态适配显示尺寸
JAVA手机网[www.cnjm.net]
 
   处理键盘事件
  游戏者首先选择使用哪种棋子(用圆和叉表示),然后开始游戏。游戏者和MIDlet谁是先手是随机决定的。每走一步棋之后,程序都要检查游戏状态,判断游戏是否已经结束。游戏的几种可能结果是:游戏者赢,MIDlet程序赢,或者平局。在应用程序运行期间,双方的得分都能显示出来。游戏者可以随时开始新游戏或者退出游戏。
JAVA手机网[www.cnjm.net]
  图2:所示的屏幕快照是游戏中的MIDlet用户界面。

图2:游戏屏幕的先后顺序
  2、九宫格MIDlet
  下面是九宫格MIDlet的类模式图:
JAVA手机网[www.cnjm.net]

图3:九宫格MIDlet类图
  当MIDlet启动方法startApp()时,将创建闪烁屏幕和第一个游戏屏幕(ChoosePieceScreen)。闪烁屏幕显示4秒之后,第一个游戏屏幕开始显示。ChoosePieceScreen让游戏者选择使用哪种棋子(圆还是叉)。当游戏者做出选择之后,他可以使用OK键确认。这会使ChoosePieceScreen回调主MIDlet的choicePieceScreenDone()方法。
  ChoosePieceScreen是使用高级应用编程接口List类实现的。

图4:ChoosePieceScreen是一个高级用户界面List子类
JAVA手机网[www.cnjm.net]
  choosePieceScreenDone()回调创建并显示下一个屏幕,这个屏幕在此应用程序中作为游戏的主屏(GameScreen)。
  每当轮到游戏者下棋的时候,游戏者使用GameScreen的箭头键和Select按钮来选择想要走的空格。每一回合之后,应用程序都会检查游戏的状态,检查其是否符合游戏结束条件并显示游戏结果。游戏者通过点击GameScreen的Quit命令结束游戏,或使用New命令开始新一轮游戏。Quit(结束)命令调用TicTacToeMIDlet的quit()方法,然后MIDlet就会调用destroyApp()方法来终止整个MIDlet程序。
  游戏程序逻辑被封装在一个单独的Game类中。本文只关注MIDlet的图形设计,而对游戏程序逻辑不作深入探讨。如果要与现有的applet Java程序作比较,请参阅http://java.sun.com/applets/jdk/1.0/demo/TicTacToe/TicTacToe.java 和http://java.sun.com/products/jfc/tsc/articles/tictactoe/index.html 中的游戏程序逻辑。
GameScreen通过使用低级Canvas和Graphics类来实现。它使用Canvas、Image和Graphics对象来绘制图形。
GameScreen首先初始化基于画布尺寸的显示面板。这可让MIDlet能够运行在不同显示屏幕的移动设备上。在本例中还使用了一个Image对象用来表示游戏面板。然后GameScreen根据游戏者在ChoosePieceScreen中所做的选择为游戏者和MIDlet分配棋子。游戏然后进行初始化(包括随机决定谁是先手),然后游戏就开始了。
为了使GameScreen能够被移植,MIDlet的键盘代码必须被映射到游戏动作上,如:Up、Down、Left、Right和Fire,用于具有不同键盘的移动设备。每当一个键被按下的时候,keyPressed()方法就会判断这是一个方向键还是一个Fire/Select键。如果按下的键是方向键,光标就会相应地移动,帮助游戏者可视化地选择一个空格放入棋子。Select键用来选择一个空格放入棋子。如果探测到满足游戏结束的条件,就会显示一条信息宣布游戏的获胜者和本轮游戏的得分。(见下图)

图5: GameScreen 是一个低级 Canvas(画布)子类
  3、TicTacToeMIDlet.java
  TicTacToeMIDlet非常简单:它处理MIDlet的生命周期事件。它根据需要创建屏幕对象并且处理来自屏幕的回调。ChoosePieceScreenDone回调被用来创建GameScreen。quit方法则被GameScreen用来结束游戏。


package example.tictactoe;
import java.io.IOException;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
JAVA手机网[www.cnjm.net]
import javax.microedition.io.*;
public class TicTacToeMIDlet extends MIDlet {
JAVA手机网[www.cnjm.net]
private ChoosePieceScreen choosePieceScreen;
private GameScreen gameScreen;
public TicTacToeMIDlet()
{
}
public void startApp() {
Displayable current = Display.getDisplay(this).getCurrent();
if (current == null) {
// first time we've been called
// Get the logo image
Image logo = null;
try
{
logo = Image.createImage("/tictactoe.png");
}
catch (IOException e) {
// just use null image
}
Alert splashScreen = new Alert(null, "Tic-Tac-Toe Forum Nokia", logo, AlertType.INFO);

splashScreen.setTimeout(4000);
// 4 seconds
choosePieceScreen = new ChoosePieceScreen(this);
Display.getDisplay(this).setCurrent(splashScreen, choosePieceScreen);
}
else
{
Display.getDisplay(this).setCurrent(current);
JAVA手机网[www.cnjm.net]
}
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
public void quit()
{
destroyApp(false);
notifyDestroyed();
}
public void choosePieceScreenDone(boolean isPlayerCircle)
{
gameScreen = new GameScreen(this, isPlayerCircle);
Display.getDisplay(this).setCurrent(gameScreen);
}
JAVA手机网[www.cnjm.net]
}
  4、ChoosePieceScreen.java
  ChoosePieceScreen是一个基于高级应用编程接口窗体的屏幕,允许游戏者选择圆或叉作为棋子。当游戏者按下OK键时,它使用MIDlet的回调方法choosePieceScreenDone来处理游戏者的选择。


package example.tictactoe;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.io.*;
public class ChoosePieceScreen extends List implements CommandListener
{
private static final String CIRCLE_TEXT = "Circle";
private static final String CROSS_TEXT = "Cross";
private final TicTacToeMIDlet midlet;
private final Command quitCommand;
public ChoosePieceScreen(TicTacToeMIDlet midlet) {
super("Choose your piece", List.IMPLICIT);
JAVA手机网[www.cnjm.net]
this.midlet = midlet;
append(CIRCLE_TEXT, loadImage("/circle.png"));
JAVA手机网[www.cnjm.net]
append(CROSS_TEXT, loadImage("/cross.png"));
quitCommand = new Command("Quit", Command.EXIT, 2);
addCommand(quitCommand);
setCommandListener(this);
}
public void commandAction(Command c, Displayable d) {
boolean isPlayerCircle = getString(getSelectedIndex()).equals(CIRCLE_TEXT);
if (c == List.SELECT_COMMAND) {
midlet.choosePieceScreenDone(isPlayerCircle);
}
JAVA手机网[www.cnjm.net]
else
// quit Command
{
JAVA手机网[www.cnjm.net]
midlet.quit();
JAVA手机网[www.cnjm.net]
}
}
private Image loadImage(String imageFile)
{
Image image = null;
try
{
image = Image.createImage(imageFile);
}
catch (Exception e)
{
// Use a 'null' image in the choice list (i.e. text only choices).
}
return image;
}
}
  5、GameScreen.java
  GameScreen使用了一个低级应用编程接口Canvas屏幕,和Image、Graphics类来绘制游戏面板、棋子,以及游戏的最终结果状态。要获取更详细的信息,请参阅各种绘画方法和drawCircle、drawCross、drawPiece、drawPlayerCursor、drawBoard等方法。这个屏幕使用MIDlet的quit回调方法来指示游戏结束。
此屏幕可适应各种可用显示性能(高、宽、色彩等)。此外还要注意到可以使用四向导航键,也可以使用双向导航键来移动光标。
  它使用了封装了主游戏程序逻辑的Game类。


JAVA手机网[www.cnjm.net]
package example.tictactoe;
import java.util.Random;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
class GameScreen extends Canvas implements CommandListener {
private static final int BLACK = 0x00000000;
private static final int WHITE = 0x00FFFFFF;
private static final int RED = 0x00FF0000;
private static final int BLUE = 0x000000FF;
private static final int NO_MOVE = -1;
private final TicTacToeMIDlet midlet;
private final Game game;
private final Command exitCommand;
private final Command newGameCommand;
JAVA手机网[www.cnjm.net]
private final Random random = new Random();
private int screenWidth, screenHeight;
private int boardCellSize, boardSize, boardTop, boardLeft;
private boolean playerIsCircle;
private boolean computerIsCircle;
private int preCursorPosition, cursorPosition;
private int computerMove = NO_MOVE;
private int playerMove = NO_MOVE;
JAVA手机网[www.cnjm.net]
private int computerGamesWonTally = 0;
private int playerGamesWonTally = 0;
private boolean isRestart;
public GameScreen(TicTacToeMIDlet midlet, boolean playerIsCircle) {
this.midlet = midlet;
this.playerIsCircle = playerIsCircle;
computerIsCircle = !playerIsCircle;
game = new Game(random);
initializeBoard();
// configure Screen commands
exitCommand = new Command("Exit", Command.EXIT, 1);
newGameCommand = new Command("New", Command.SCREEN, 2);
addCommand(exitCommand);
addCommand(newGameCommand);
setCommandListener(this);
// begin the game play initialize();
}
// Initialize the Game and Game screen. Also used for game restarts.
private void initialize() {
JAVA手机网[www.cnjm.net]
game.initialize();
preCursorPosition = cursorPosition = 0;
playerMove = NO_MOVE;
boolean computerFirst = ((random.nextInt() & 1) == 0);
if (computerFirst) {
computerMove = game.makeComputerMove();
}
else
JAVA手机网[www.cnjm.net]
{
computerMove = NO_MOVE;
}
isRestart = true;
repaint();
}
public void paint(Graphics g) {
if (game.isGameOver()) {
paintGameOver(g);
}
else {
JAVA手机网[www.cnjm.net]
paintGame(g);
}
}
private void paintGame(Graphics g) {
if (isRestart) {
// clean the canvas
g.setColor(WHITE);
g.fillRect(0, 0, screenWidth, screenHeight);
drawBoard(g);
isRestart = false;
}
drawCursor(g);
if (playerMove != NO_MOVE) {
drawPiece(g, playerIsCircle, playerMove);
}
if (computerMove != NO_MOVE) {
drawPiece(g, computerIsCircle, computerMove);
}
}
private void paintGameOver(Graphics g)
{
String statusMsg = null;
if(game.isComputerWinner()) {
statusMsg = "I win !";
JAVA手机网[www.cnjm.net]
computerGamesWonTally++;
}
else if (game.isPlayerWinner()) {
statusMsg = "You win";
playerGamesWonTally++;
}
else {
statusMsg = "Stalemate";
}
String tallyMsg = "You:" + playerGamesWonTally + " Me:" + computerGamesWonTally;
JAVA手机网[www.cnjm.net]
Font font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_MEDIUM);
int strHeight = font.getHeight();
int statusMsgWidth = font.stringWidth(statusMsg);
int tallyMsgWidth = font.stringWidth(tallyMsg);
int strWidth = tallyMsgWidth;
if (statusMsgWidth > tallyMsgWidth)
{
strWidth = statusMsgWidth;
}
// Get the
{
x, y
}
position for painting the strings. int x = (screenWidth - strWidth) / 2;
x = x < 0 ? 0 : x;
JAVA手机网[www.cnjm.net]
int y = (screenHeight - 2 * strHeight) / 2;
y = y < 0 ? 0 : y;
// clean the canvas
g.setColor(WHITE);
g.fillRect(0, 0, screenWidth, screenHeight);
// paint the strings' text
g.setColor(BLACK);
g.drawString(statusMsg, x, y, (Graphics.TOP | Graphics.LEFT));
g.drawString(tallyMsg, x, (y + 1 + strHeight), (Graphics.TOP | Graphics.LEFT));
}

public void commandAction(Command c, Displayable d) {
if (c == exitCommand) {
midlet.quit();
}
else if (c == newGameCommand) {
initialize();
}
}
private void initializeBoard() {
JAVA手机网[www.cnjm.net]
screenWidth = getWidth();
screenHeight = getHeight();
if (screenWidth > screenHeight) {
boardCellSize = (screenHeight - 2) / 3;
boardLeft = (screenWidth - (boardCellSize * 3)) / 2;
JAVA手机网[www.cnjm.net]
boardTop = 1;
}
else {
boardCellSize = (screenWidth - 2) / 3;
boardLeft = 1;
JAVA手机网[www.cnjm.net]
boardTop = (screenHeight - boardCellSize * 3) / 2;
}
}
protected void keyPressed(int keyCode) {
JAVA手机网[www.cnjm.net]
// can't continue playing until the player restarts
if (game.isGameOver()) {
return;
}
int gameAction = getGameAction(keyCode);
switch (gameAction) {
case FIRE: doPlayerMove();

JAVA手机网[www.cnjm.net]
break;
case RIGHT: doMoveCursor(1, 0);
break;
case DOWN: doMoveCursor(0, 1);
break;
case LEFT: doMoveCursor(-1, 0);
break;
case UP: doMoveCursor(0, -1);
break;
default: break;
}
}
private void doPlayerMove() {
if (game.isFree(cursorPosition)) {
// player move game.
makePlayerMove(cursorPosition);
playerMove = cursorPosition;
// computer move
if (!game.isGameOver()) {
JAVA手机网[www.cnjm.net]
computerMove = game.makeComputerMove();
}
repaint();
}
}
private void doMoveCursor(int dx, int dy) {
int newCursorPosition = cursorPosition + dx + 3 * dy;
JAVA手机网[www.cnjm.net]
if ((newCursorPosition >= 0) && (newCursorPosition < 9))
{
preCursorPosition = cursorPosition;
cursorPosition = newCursorPosition;
JAVA手机网[www.cnjm.net]
repaint();
}
}
// Draw a CIRCLE or CROSS piece on the board
private void drawPiece(Graphics g, boolean isCircle, int pos) {
int x = ((pos % 3) * boardCellSize) + 3;
int y = ((pos / 3) * boardCellSize) + 3;
if (isCircle) {
drawCircle(g, x, y);
}
else {
drawCross(g, x, y);
}
}
// Draw blue CIRCLE onto the board image
private void drawCircle(Graphics g, int x, int y) {
g.setColor(BLUE);
g.fillArc(x + boardLeft, y + boardTop, boardCellSize - 4, boardCellSize - 4, 0, 360);
g.setColor(WHITE);
g.fillArc(x + 4 + boardLeft, y + 4 + boardTop, boardCellSize - 4 - 8, boardCellSize - 4 - 8, 0, 360);
}
// Draw red CROSS onto the board image
private void drawCross(Graphics g, int x, int y) {
g.setColor(RED);
for (int i = 0;
i < 4;
i++) {
g.drawLine(x + 1 + i + boardLeft, y + boardTop, x + boardCellSize - 4 - 4 + i + boardLeft, y + boardCellSize - 5 + boardTop);

g.drawLine(x + 1 + i + boardLeft, y + boardCellSize - 5 + boardTop, x + boardCellSize - 4 - 4 + i + boardLeft, y + boardTop);
JAVA手机网[www.cnjm.net]
}
}
// Visually indicates a Player selected square on the board image
private void drawCursor(Graphics g) {
// draw cursor at selected Player square.
g.setColor(WHITE);
g.drawRect(((preCursorPosition % 3) * boardCellSize) + 2 + boardLeft, ((preCursorPosition/3) * boardCellSize) + 2 + boardTop, boardCellSize - 3, boardCellSize - 3);
JAVA手机网[www.cnjm.net]
// draw cursor at selected Player square.
JAVA手机网[www.cnjm.net]
g.setColor(BLACK);
g.drawRect(((cursorPosition % 3) * boardCellSize) + 2 + boardLeft, ((cursorPosition/3) * boardCellSize) + 2 + boardTop, boardCellSize - 3, boardCellSize - 3);
}
JAVA手机网[www.cnjm.net]
private void drawBoard(Graphics g) {
JAVA手机网[www.cnjm.net]
// clean the board
g.setColor(WHITE);
g.fillRect(0, 0, screenWidth, screenHeight);
// draw the board
g.setColor(BLACK);
for (int i = 0;
i < 4;
i++) {
g.fillRect(boardLeft, boardCellSize * i + boardTop, (boardCellSize * 3) + 2, 2);
JAVA手机网[www.cnjm.net]
g.fillRect(boardCellSize * i + boardLeft, boardTop, 2, boardCellSize * 3);
}
}
}
  6、Game.java
JAVA手机网[www.cnjm.net]
  这个类封装了九宫格游戏的主要的游戏程序逻辑。前面我们也说过,游戏程序逻辑本身并不在本例程重点讨论的范围之内,本文主要是介绍MIDP图形编程的基础知识。游戏程序逻辑的WINS数组部分来自http://java.sun.com/applets/jdk/1.0/demo/TicTacToe/TicTacToe.java 这个经典例程。
  注意游戏程序逻辑是独立于游戏用户界面的(参见类GameScreen),并且可以使用其它实现方法替代。


package example.tictactoe;
import java.util.Random;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
// The game logic for TicTacToe
JAVA手机网[www.cnjm.net]
class Game {
private static final int[] WINS = {
// horizontals
bit(0) | bit(1) | bit(2),
bit(3) | bit(4) | bit(5),
bit(6) | bit(7) | bit(8),
// verticals
bit(0) | bit(3) | bit(6),
bit(1) | bit(4) | bit(7),
bit(2) | bit(5) | bit(8),
// diagonals
bit(0) | bit(4) | bit(8),
bit(2) | bit(4) | bit(6) }
;
private static final int DRAWN_GAME = bit(0) | bit(1) | bit(2) | bit(3) | bit(4) | bit(5) | bit(6) | bit(7) | bit(8);
private int playerState;
JAVA手机网[www.cnjm.net]
private int computerState;
private Random random;
Game(Random random) {
this.random = random;
initialize();
}
void initialize() {
playerState = 0;
computerState = 0;
}
boolean isFree(int position) {
int bit = bit(position);
JAVA手机网[www.cnjm.net]
return (((playerState & bit) == 0) && ((computerState & bit) == 0));
}
// The 'Contract' is that caller will always make valid moves.
// We don't check that it's the player's turn.
void makePlayerMove(int position) {
playerState |= bit(position);
}
// The 'Contract' is that we will be called only when there is still
// at least one free square.
int makeComputerMove() {
int move = getWinningComputerMove();
JAVA手机网[www.cnjm.net]
if (move == -1) {
// can't win
move = getRequiredBlockingComputerMove();
if (move == -1) {
// don't need to block
move = getRandomComputerMove();
}
}
computerState |= bit(move);
return move;
}

boolean isGameOver() {
return isPlayerWinner() | isComputerWinner() | isGameDrawn();
}
boolean isPlayerWinner() {
return isWin(playerState);
JAVA手机网[www.cnjm.net]
}
boolean isComputerWinner() {
return isWin(computerState);
JAVA手机网[www.cnjm.net]
}
boolean isGameDrawn() {
return (playerState | computerState) == DRAWN_GAME;
}
// Return a winning move if there is at least one, otherwise return -1
private int getWinningComputerMove() {
int move = -1;
for (int i = 0;
i < 9;
++i) {
if (isFree(i) && isWin(computerState | bit(i))) {
JAVA手机网[www.cnjm.net]
move = i;
break;
}
}
return move;
}
// Return a required blocking move if there is at least one (more
// than one and we've inevitably lost), otherwise return -1
private int getRequiredBlockingComputerMove() {
int move = -1;
for (int i = 0;
i < 9;
JAVA手机网[www.cnjm.net]
++i) {

if (isFree(i) && isWin(playerState | bit(i))) {
move = i;
break;
JAVA手机网[www.cnjm.net]
}
}
return move;
}
JAVA手机网[www.cnjm.net]
// Return a random move in a free square, // or return -1 if none are available private int getRandomComputerMove() {
int move = -1;
// determine how many possible moves there are int numFreeSquares = 0;
for (int i = 0;
i < 9;
JAVA手机网[www.cnjm.net]
++i) {
if (isFree(i)) {
numFreeSquares++;
JAVA手机网[www.cnjm.net]
}
}
// if there is at least one possible move, pick randomly
if (numFreeSquares > 0) {
// shift twice to get rid of sign bit, then modulo numFreeSquares
JAVA手机网[www.cnjm.net]
int pick = ((random.nextInt()<<1)>>>1) % numFreeSquares;
// now find the chosen free square by counting pick down to zero
JAVA手机网[www.cnjm.net]
for (int i = 0;
i < 9;
++i) {
if (isFree(i)) {
if (pick == 0) {
move = i;
JAVA手机网[www.cnjm.net]
break;
JAVA手机网[www.cnjm.net]
}
pick--;
}
}
}
return move;
}
private static boolean isWin(int state) {
boolean isWinner = false;
for (int i = 0;
i < WINS.length;
++i) {
JAVA手机网[www.cnjm.net]
if ((state & WINS[i]) == WINS[i]) {
isWinner = true;
break;
}
JAVA手机网[www.cnjm.net]
}
JAVA手机网[www.cnjm.net]
return isWinner;
}
private static int bit(int i) {
return 1 << i;
}
}
  7、TicTacToe.jad
  下面是九宫格MIDlet的应用程序描述文件。


MIDlet-Name: TicTacToe
MIDlet-Vendor: Forum Nokia MIDlet-Version: 1.1.1
MIDlet-Jar-Size: 11409
MIDlet-Jar-URL: TicTacToe.jar
MIDlet-1: TicTacToe, /tictactoe.png, example.tictactoe.TicTacToeMIDlet

- 作者: 江南雨 2006年03月30日, 星期四 22:52  回复(0) |  引用(0) 加入博采

Java教程_线程入门

线程是Java的内嵌特性,线程并不容易掌握,有专门介绍Java线程的书籍,读者可以参考。由此可见Java线程的重要性,本文将详细介绍线程的基本知识。

  有的时候你可能想写一个程序,每隔一段时间执行相关的任务,这个时候你可以使用Timer和TimerTask,非常方便。你可以参考这里。

  在Java中实现一个线程有两种方法,第一是实现Runnable接口实现它的run()方法,第二种是继承Thread类,覆盖它的run()方法。下面是代码示例:


   public class DoSomething implements Runnable {
  public void run(){
  // here is where you do something
  }
  }
  public class DoAnotherThing extends Thread {
  public void run(){
  // here is where you do something
  }
  }


  这两种方法的区别是,如果你的类已经继承了其它的类,那么你只能选择实现Runnable接口了,因为Java只允许单继承的。

  Java中的线程有四种状态分别是:运行、就绪、挂起、结束。如果一个线程结束了也就说明他是一个死线程了。当你调用一个线程实例的start()的方法的时候,这个时候线程进入就绪状态,注意并不是运行状态,当虚拟机开始分配给他CPU的运行时间片的时候线程开始进入运行状态,当线程进入等待状态,例如等待某个事件发生的时候,这时候线程处于挂起状态。

  启动一个线程你只需要调用start()方法,针对两种实现线程的方法也有两种启动线程的方法,分别如下:


   DoSomething doIt = new DoSomething();
  Thread myThread = new Thread( doIt );
  myThread.start();
  DoAnotherThing doIt = new DoAnotherThing();
  doIt.start();
 


  由于安全等因素Thread中的stop()方法已经不推荐使用了,因此如果你想要停止一个线程的时候可以通过设置一个信号量,例如:


   public class MyThread implements Runnable {
  private boolean quit = false;
  public void run(){
  while( !quit ){
  // do something
  }
  }

  public void quit(){
  quit = true;
  }
  }
 

  如果每个线程只做它自己的事情,那么就很简单了,但是有的时候几个线程可能要同时访问一个对象并可能对它进行修改,这个时候你必须使用线程的同步在方法或者代码块使用关键字synchronized,例如:


   public class Counter {
  private int counter;
  public synchronized int increment(){
  return ++counter;
  }

  public synchronized int decrement(){
  if( --counter < 0 ){
  counter = 0;
  }

  return counter;
  }
  }
 


  每个java对象都可以最为一个监视器,当线程访问它的synchronized方法的时候,他只允许在一个时间只有一个线程对他访问,让其他得线程排队等候。这样就可以避免多线程对共享数据造成破坏。记住synchronized是会耗费系统资源降低程序执行效率的,因此一定要在需要同步的时候才使用,尤其在J2ME的开发中要小心。

  如果你要是想让线程等待某个事件的发生然后继续执行的话,那么这就涉及到线程的调度了。在java中通过wait(),notify(),notifyAll()来实现,这三个方法是在Object类中定义的,当你想让线程挂起的时候调用obj.wait()方法,在同样的obj上调用notify()则让线程重新开始运行。 最后以SUN提供的Producer/Consumer的例子来结束这篇文章,内容是Producer产生一个数字而Consumer消费这个数字,这个小程序里面基本覆盖了本文所有的知识点。请详细研究一下代码


   public class Producer extends Thread {
  private CubbyHole cubbyhole;
  private int number;
  public Producer(CubbyHole c, int number) {
  cubbyhole = c;
  this.number = number;
  }

  public void run() {
  for (int i = 0; i < 10; i++) {
  cubbyhole.put(i);
  System.out.println("Producer #" + this.number
  + " put: " + i);
  try {
  sleep((int)(Math.random() * 100));
  } catch (InterruptedException e) { }
  }
  }
  }
 

  public class CubbyHole {
  private i

nt contents;
  private boolean available = false;

  public synchronized int get() {
  while (available == false) {
  try {
  wait();
  } catch (InterruptedException e) { }
  }
  available = false;
  notifyAll();
  return contents;
  }

  public synchronized void put(int value) {
  while (available == true) {
  try {
  wait();
  } catch (InterruptedException e) { }
  }
  contents = value;
  available = true;
  notifyAll();
  }
  }

  public class Consumer extends Thread {
  private CubbyHole cubbyhole;
  private int number;

  public Consumer(CubbyHole c, int number) {
  cubbyhole = c;
  this.number = number;
  }

  public void run() {
  int value = 0;
  for (int i = 0; i < 10; i++) {
  value = cubbyhole.get();
  System.out.println("Consumer #" + this.number
  + " got: " + value);
  }
  }
  }
  public class ProducerConsumerTest {
  public static void main(String[] args) {
  CubbyHole c = new CubbyHole();
  Producer p1 = new Producer(c, 1);
  Consumer c1 = new Consumer(c, 1);

  p1.start();
  c1.start();
  }
  }

  SUN说输出的结果应该是如下形式,但是在我的机器上却不是这样的,做了一些改动才正确,有兴趣的朋友可以运行一下看看结果,欢迎和我讨论一下!


   Producer #1 put: 0
  Consumer #1 got: 0
  Producer #1 put: 1
  Consumer #1 got: 1
  Producer #1 put: 2
  Consumer #1 got: 2
  Producer #1 put: 3
  Consumer #1 got: 3
  Producer #1 put: 4
  Consumer #1 got: 4
  Producer #1 put: 5
  Consumer #1 got: 5
  Producer #1 put: 6
  Consumer #1 got: 6
  Producer #1 put: 7
  Consumer #1 got: 7
  Producer #1 put: 8
  Consumer #1 got: 8
  Producer #1 put: 9
  Consumer #1 got: 9

- 作者: 江南雨 2006年03月5日, 星期日 16:11  回复(1) |  引用(0) 加入博采

Java代码编写30条建议
     (1) 类名首字母应该大写。字段、方法以及对象(句柄)的首字母应小写。对于所有标识符,其中包含的所有单词都应紧靠在一起,而且大写中间单词的首字母。

  例如:
  ThisIsAClassName
  thisIsMethodOrFieldName
  若在定义中出现了常数初始化字符,则大写static final基本类型标识符中的所有字母。这样便可标志出它们属于编译期的常数。
  Java包(Package)属于一种特殊情况:它们全都是小写字母,即便中间的单词亦是如此。对于域名扩展名称,如com,org,net或者edu等,全部都应小写(这也是Java 1.1和Java 1.2的区别之一)。

  (2) 为了常规用途而创建一个类时,请采取"经典形式",并包含对下述元素的定义:

  equals()
  hashCode()
  toString()
  clone()(implement Cloneable)
  implement Serializable

  (3) 对于自己创建的每一个类,都考虑置入一个main(),其中包含了用于测试那个类的代码。为使用一个项目中的类,我们没必要删除测试代码。若进行了任何形式的改动,可方便地返回测试。这些代码也可作为如何使用类的一个示例使用。

  (4) 应将方法设计成简要的、功能性单元,用它描述和实现一个不连续的类接口部分。理想情况下,方法应简明扼要。若长度很大,可考虑通过某种方式将其分割成较短的几个方法。这样做也便于类内代码的重复使用(有些时候,方法必须非常大,但它们仍应只做同样的一件事情)。

  (5) 设计一个类时,请设身处地为客户程序员考虑一下(类的使用方法应该是非常明确的)。然后,再设身处地为管理代码的人考虑一下(预计有可能进行哪些形式的修改,想想用什么方法可把它们变得更简单)。

  (6) 使类尽可能短小精悍,而且只解决一个特定的问题。下面是对类设计的一些建议:

  一个复杂的开关语句:考虑采用"多形"机制;
  数量众多的方法涉及到类型差别极大的操作:考虑用几个类来分别实现;
  许多成员变量在特征上有很大的差别:考虑使用几个类.

  (7) 让一切东西都尽可能地"私有"--private。可使库的某一部分"公共化"(一个方法、类或者一个字段等等),就永远不能把它拿出。若强行拿出,就可能破坏其他人现有的代码,使他们不得不重新编写和设计。若只公布自己必须公布的,就可放心大胆地改变其他任何东西。在多线程环境中,隐私是特别重要的一个因素--只有private字段才能在非同步使用的情况下受到保护。

  (8) 谨惕"巨大对象综合症"。对一些习惯于顺序编程思维、且初涉OOP领域的新手,往往喜欢先写一个顺序执行的程序,再把它嵌入一个或两个巨大的对象里。根据编程原理,对象表达的应该是应用程序的概念,而非应用程序本身。

  (9) 若不得已进行一些不太雅观的编程,至少应该把那些代码置于一个类的内部。

  (10) 任何时候只要发现类与类之间结合得非常紧密,就需要考虑是否采用内部类,从而改善编码及维护工作(参见第14章14.1.2小节的"用内部类改进代码")。

  (11) 尽可能细致地加上注释,并用javadoc注释文档语法生成自己的程序文档。

  (12) 避免使用"魔术数字",这些数字很难与代码很好地配合。如以后需要修改它,无疑会成为一场噩梦,因为根本不知道"100"到底是指"数组大小"还是"其他全然不同的东西"。所以,我们应创建一个常数,并为其使用具有说服力的描述性名称,并在整个程序中都采用常数标识符。这样可使程序更易理解以及更易维护。

  (13) 涉及构建器和异常的时候,通常希望重新丢弃在构建器中捕获的任何异常--如果它造成了那个对象的创建失败。这样一来,调用者就不会以为那个对象已正确地创建,从而盲目地继续。

  (14) 当客户程序员用完对象以后,若你的类要求进行任何清除工作,可考虑将清除代码置于一个良好定义的方法里,采用类似于cleanup()这样的名字,明确表明自己的用途。除此以外,可在类内放置一个boolean(布尔)标记,指出对象是否已被清除。在类的finalize()方法里,请确定对象已被清除,并已丢弃了从RuntimeException继承的一个类(如果还没有的话),从而指出一个编程错误。在采取象这样的方案之前,请确定finalize()能够在自己的系统中工作(可能需要调用System.runFinalizersOnExit(true),从而确保这一行为)。 (15) 在一个特定的作用域内,若一个对象必须清除(非由垃圾收集机制处理),请采用下述方法:初始化对象;若成功,则立即进入一个含有finally从句的try块,开始清除工作。

  (16) 若在初始化过程中需要覆盖(取消)finalize(),请记住调用super.finalize()(若Object属于我们的直接超类,则无此必要)。在对finalize()进行覆盖的过程中,对super.finalize()的调用应属于最后一个行动,而不应是第一个行动,这样可确保在需要基础类组件的时候它们依然有效。

  (17) 创建大小固定的对象集合时,请将它们传输至一个数组(若准备从一个方法里返回这个集合,更应如此操作)。这样一来,我们就可享受到数组在编译期进行类型检查的好处。此外,为使用它们,数组的接收者也许并不需要将对象"造型"到数组里。

  (18) 尽量使用interfaces,不要使用abstract类。若已知某样东西准备成为一个基础类,那么第一个选择应是将其变成一个interface(接口)。只有在不得不使用方法定义或者成员变量的时候,才需要将其变成一个abstract(抽象)类。接口主要描述了客户希望做什么事情,而一个类则致力于(或允许)具体的实施细节。

  (19) 在构建器内部,只进行那些将对象设为正确状态所需的工作。尽可能地避免调用其他方法,因为那些方法可能被其他人覆盖或取消,从而在构建过程中产生不可预知的

      (20) 对象不应只是简单地容纳一些数据;它们的行为也应得到良好的定义。

  (21) 在现成类的基础上创建新类时,请首先选择"新建"或"创作"。只有自己的设计要求必须继承时,才应考虑这方面的问题。若在本来允许新建的场合使用了继承,则整个设计会变得没有必要地复杂。

  (22) 用继承及方法覆盖来表示行为间的差异,而用字段表示状态间的区别。一个非常极端的例子是通过对不同类的继承来表示颜色,这是绝对应该避免的:应直接使用一个"颜色"字段。

  (23) 为避免编程时遇到麻烦,请保证在自己类路径指到的任何地方,每个名字都仅对应一个类。否则,编译器可能先找到同名的另一个类,并报告出错消息。若怀疑自己碰到了类路径问题,请试试在类路径的每一个起点,搜索一下同名的.class文件。

  (24) 在Java 1.1 AWT中使用事件"适配器"时,特别容易碰到一个陷阱。若覆盖了某个适配器方法,同时拼写方法没有特别讲究,最后的结果就是新添加一个方法,而不是覆盖现成方法。然而,由于这样做是完全合法的,所以不会从编译器或运行期系统获得任何出错提示--只不过代码的工作就变得不正常了。

  (25) 用合理的设计方案消除"伪功能"。也就是说,假若只需要创建类的一个对象,就不要提前限制自己使用应用程序,并加上一条"只生成其中一个"注释。请考虑将其封装成一个"独生子"的形式。若在主程序里有大量散乱的代码,用于创建自己的对象,请考虑采纳一种创造性的方案,将些代码封装起来。

  (26) 警惕"分析瘫痪"。请记住,无论如何都要提前了解整个项目的状况,再去考察其中的细节。由于把握了全局,可快速认识自己未知的一些因素,防止在考察细节的时候陷入"死逻辑"中。

  (27) 警惕"过早优化"。首先让它运行起来,再考虑变得更快--但只有在自己必须这样做、而且经证实在某部分代码中的确存在一个性能瓶颈的时候,才应进行优化。除非用专门的工具分析瓶颈,否则很有可能是在浪费自己的时间。性能提升的隐含代价是自己的代码变得难于理解,而且难于维护。

  (28) 请记住,阅读代码的时间比写代码的时间多得多。思路清晰的设计可获得易于理解的程序,但注释、细致的解释以及一些示例往往具有不可估量的价值。无论对你自己,还是对后来的人,它们都是相当重要的。如对此仍有怀疑,那么请试想自己试图从联机Java文档里找出有用信息时碰到的挫折,这样或许能将你说服。

  (29) 如认为自己已进行了良好的分析、设计或者实施,那么请稍微更换一下思维角度。试试邀请一些外来人士--并不一定是专家,但可以是来自本公司其他部门的人。请他们用完全新鲜的眼光考察你的工作,看看是否能找出你一度熟视无睹的问题。采取这种方式,往往能在最适合修改的阶段找出一些关键性的问题,避免产品发行后再解决问题而造成的金钱及精力方面的损失。

  (30) 良好的设计能带来最大的回报。简言之,对于一个特定的问题,通常会花较长的时间才能找到一种最恰当的解决方案。但一旦找到了正确的方法,以后的工作就轻松多了,再也不用经历数小时、数天或者数月的痛苦挣扎。我们的努力工作会带来最大的回报(甚至无可估量)。而且由于自己倾注了大量心血,最终获得一个出色的设计方案,成功的快感也是令人心动的。坚持抵制草草完工的诱惑--那样做往往得不偿失

- 作者: 江南雨 2006年03月5日, 星期日 16:07  回复(0) |  引用(0) 加入博采

J2ME开发的一些体会
 J2ME方面开发的资料,确实是少之又少,一般给新手推荐的都是王森先生的《PDA与手机开发入门》一书,然而该书其实并不适合新人阅读,该书的前几章花了很大篇幅来讲解命令行调试J2ME程序的方法和很多要点,其实这些东西,作为一个初级的J2ME开发者来说,完全没有必要学习和掌握,反而只会给自己平添很多负担。现在做J2ME开发,大多都有了IDE(集成开发环境),比如JBEclipse什么的,就算是用最简单的WTK,也不需要进行path的配置等操作,所以初学者完全可以跳过这些内容,直接上手。我的认为,做程序开发,重在实践经验,而不是理论研究,因此,新人完全可以直接从J2ME的程序流程入手,直接进入,而不需要去了解太多基本概念,比如什么是API,什么是MIDP,什么是JDK等等。这些东西我到现在也不是很清楚,但并不影响我进行开发,我是讲究实用主义的,因此,本文的基调就在于讲述一些实用的开发方法。另外首先要声明:本文中的一切开发调试都是在WTK环境下进行的,请读者注意。
一.学习J2ME需要的基础知识
J2ME本身是Java语言的一部分,因此,对Java语言有一定了解是必要的,而Java语言本身是面向对象编程的一种语言,因此,对类与对象的概念要有必要的了解,而这些内容在本文中就不进行介绍了。另外,本文行文中对API的介绍很简略,因此,读者应该自己多查阅API中的相关内容。
二.J2ME开发环境简介
J2ME的开发环境如前文所述,目前主要有JBEclipseWTK三种方式,其中前面两者都是集成开发环境,在编程上比较方便,有智能联想等功能,而WTK相对只提供了手动编译和打包,并且没有Debug功能。但WTK不需要多余的配置,对各种模拟器可以说是即插即用,而且在WTK下的程序目录简洁明了,方便查询。这几种开发环境各有长短处,所以大家可以自己选择,不过对新手来说,还是建议采用WTK,因为刚接触J2ME开发,与其把时间花在配置上,还不如花在编程上。
三.J2ME程序流程
我的感觉,每门语言都有自己的固定格式,就跟写文章的几要素一样,总有开头,结尾等等。而了解一门语言的格式,是学习这门语言最先要掌握的内容。
如果把程序流程比做是一个管道工程的话,那么每门语言里都有一个管道的入口,在J2ME里,这个入口就是MIDlet类,这个类一般也称为J2ME的主类,每个程序,都有一个或多个固定的主类,在这里,我们只谈一般的情况,即只有一个MIDlet主类的情况,大家看API即可知道,MIDlet类有几个固定方法,startApp(),pauseApp(),destroyApp(),这些方法就像是管道工程(以下的讲述里,我都把编程比作是管道工程)中的几个关键阀门,一个J2ME程序启动后,便先经历MIDlet的构造方法,然后进入startApp()方法,当有特殊情况挂起的时候,便会进入pauseApp方法,当程序结束时,便经由destroyApp方法退出。一个J2ME程序可以没有其他类,但不能没有MIDlet类,只要有一个完整的主类,便是一个完整的J2ME程序。比如非常精典的"HelloWorld",便只有一个MIDlet

我觉得,J2ME实际上是一个很标准的顺序流程语言,J2ME中没有真正的多线程,很多情况下,一个J2ME程序的运行,我们都可以对他的流程进行完全的跟踪。这在调试方面是非常方便的。

在这里还要解释一点,J2ME程序中,屏幕对象是一个比较重要的对象,你的所有操作,你所要表达的意思,都需要在屏幕上描绘出来,因此,对屏幕的操作也是最基本和最重要的一个操作,因此,J2ME程序的操作其实就是对DisplayDisplay上的Displayable(包括FormTextBoxListCanvas等等)对象进行控制。也就是说,让程序在合适的时候,把需要的Displayable显示在屏幕上。使用的方法,即是Display.setCurrent()方法。

在屏幕对象的操作上,我要解释一下,J2ME默认的屏幕只有一个,即是当前屏幕,所有操作都是对当前屏幕进行操作,因此,当你在主类中将一个Canvas对象设置为当前屏幕后,则主类已经处于脱屏状态,脱屏状态下的对象无法进行直接操作。这一点需要注意。所有外界的操作均只能作用于当前屏幕上显示的Displayable对象,无论是FormList,或者是Canvas。此时对脱屏类的操作只能通过静态方法或者是该类的一个实例来进行。

J2ME的游戏中最简单的一种游戏流程是这样,即一个主类和一个Canvas类,主类入口处定义一个Canvas对象,并且此Canvas对象继承一个Runnable接口,再定义一个基于此对象的线程,然后将此Canvas对象设为当前屏幕对象,并启动线程,即用线程的start()方法,启动线程后,Canvas里的run方法便开始运行,而run方法通常要用repaint()方法来刷屏,因此流程就是按paint()->run->paint().....这样的过程往复,其中如果还有键盘输入,则keyPressed()等方法就穿插在中间。基本上J2ME的程序就是按这样的流程来走,因此在开发和调试中只要对这个流程进行必要的跟踪就可以了。
四.J2ME中常用的类
J2ME中常用的类有MIDletFormListCanvasStringCommand,Graphics等等。
其中Graphics作为游戏开发中的核心类,自然需要熟练的掌握。
Graphics中如下的重要方法需要掌握:
setColor(int 设置的颜色)
setFont(Font 设置的字体)
drawRect(int 起点坐标x,int 起点坐标y,int ,int )
fillRect(int 起点坐标x,int 起点坐标y,int ,int )
drawImage(Image 要画的图形对象,int 起点坐标x,int起点坐标y,int 基点类型)
drawLine(int 起点坐标x,int起点坐标y,int 终点坐标x,int 终点坐标y)
drawString(String 要写的字符串, int 起点坐标x,int起点坐标y,int 基点类型)
在这里要说明一下什么是基点类型,基点是标志你设置的起点坐标在整个要绘制的对象(不论是图像还是文字)中的位置,如左上,右下,正中间等等。
常用的值有020:左上,17:正中间,24:右上,36:左下,40:右下。

除此之外,还有几个重要方法
addCommand,removeCommand,这两个方法是对Command的添加和取消的操作,很常用。
而谈到Command,自然就要涉及到commandListener这个接口,按钮的监听需要通过这个接口来进行,也就是要重载接口中的commandAction()方法。不过在使用这个接口的时候,不要忘了对当前的对象设置setCommandListener(this)。
另外,对于String类,substring方法应该掌握
而对于Form这些高级控件,可用方法不多,看看API即可。
五.J2ME中的线程
J2ME中的线程是Thread类,而实现线程有两种方法,一种是继承Runnable接口,一种是TimerTimerTask。而不管是哪种线程,其本质都是在运行其中的run方法。在这里要注意,Runnable接口的run方法和TimerTaskrun方法又是不同的,因为他们的运行机制不同。Runnable接口是靠线程的休眠或等待来实现线程的刷新,而TimerTask是按照事先给定的时间间隔来自动刷新。对Runnable接口,一旦run方法结束后,则线程就结束了,因此Runnable线程里的run方法要写成循环方式,一般是while(isRunning)的形式,要结束的时候把isRunning设为false,而TimerTask则不用,因为他是定时刷新。但这样也有个问题,就是如果TimerTaskrun方法写得过长,执行时间超过了设置的线程间隔时间,就会出现线程重叠的情况。线程重叠会造成诸如游戏速度突然加快等Bug
Runnable接口定义的线程里,有两种等待方法,一种是wait,一种是sleep,如果使用wait方法,则要注意在线程run方法里,用synchonized标志符将该线程设置为同步锁定状态,否则会出现java.lang.IllegalMonitorStateException异常,大致意思是线程当前请求的监视器对象不能响应,因为被别的线程占用了。

线程在J2ME里非常重要,一切动态效果(当然这是游戏的前提)都是通过线程来操作,因此,对线程的控制是值得研究的一个问题。如何优化代码,使线程能够在足够短的间隔时间内正常刷新,换言之,这就是大家讨论的FPS问题。程序优化做得好,则线程间隔时间可以足够短,使得FPS可以足够高,游戏的流畅度当然就越高。
六.J2ME开发中的好习惯
1.       多写注释,完备的注释不管是对你自己,还是对别人,都是很有好处的,在调试中也会起很大的帮助。
2.       在重要流程的各个环节设置一些监听点,其实很简单,就是用symstem.out.println()来输出一些标志符号就可以了,这样可以监听你的程序在运行的时候,走到哪一步。
3.       不要写太多的类,尽量整合资源,不用的变量或者方法,尽量去掉。
4.       熟练掌握各种常见的异常,不要太依赖于问别人和翻书,这些应该是一个合格的程序员应该熟练于心的东西。
5.       对不熟悉的类,应该多写小代码来实验其用法,别人讲的永远不如自己研究出来的结果牢靠。

- 作者: 江南雨 2006年03月4日, 星期六 21:02  回复(0) |  引用(0) 加入博采

游戏学院培训模式

游戏被称为“第九艺术”,游戏开发是集剧情、美术、音乐、动画、程序等为一体的复合技术,因此,需要多样化的游戏从业人员。其中高中学历人员可向游戏运营与管理、市场营销方面发展;女生、文科生、大专生在游戏策划、架构设计等领域占优势;美术特长生可在 3D游戏动画设计方面一展身手;应届毕业生先通过职业培训,掌握职业技能后,顺利进入游戏行业工作;而计算机及相关专业则从事手机游戏、网络游戏开发等职位。

     由北京汇众益智科技有限公司联合信息产业部电子教育中心、香港职业训练局举办的游戏学院项目,在教学模式上比较韩国、日本、美国等游戏培训课程的优势后,采用案例教学、实例教学、实践项目开发相结合的教学模式, 设立游戏运营管理、游戏架构设计、 3D 游戏设计、移动(手机)游戏开发、在线游戏设计与开发等多个方向的培养。

     游戏学院与国际游戏开发教育联合会合作推出 2005新版课程体系 是国内市场上率先推出的系统化游戏专业培训体系,避免了某些培训机构的单一课程使学员与游戏企业实际要求相脱节的问题。过硬的国际国内双重认证、极强的实际开发操作能力,使游戏学院培养出来的学员具备在游戏公司一年开发经验的综合实力,成为各大游戏开发公司争抢对象。同时, 为游戏企业定制培训急需的游戏人才, 8个月成为游戏开发高手,更有游戏工厂与创业孵化器助学员自主创业。


810个月成就游戏设计与开发高手,快到游戏学院来!

就读游戏学院,将获以下职业技能:

就业职位

薪资 (/)

入学条件

 游戏 运营与客服

2000 4000

喜爱游戏行业,熟悉计算机及互联网,对游戏行业有足够的激情与热情,致力投身中国民族游戏产业发展事业。

 游戏策划与架构设计师

4000 10000

 游戏动画设计师

4000 10000

 手机游戏开发工程师

5000 10000

 网络游戏开发工程师

5000 10000

主办机构: 北京汇众益智科技有限公司    信息产业部电子教育中心 香港职业训练局 

专家型师资: 全部教师均具有多年游戏经验,并持有信息产业部电子教育中心及香港职业训练局高级培训师资格证书

学习成果:
1
、学习期间完成多款手机、网络游戏设计与开发;
2
、合格学员将获得国家、国际双重权威认证。