在信息社会中,手机及其他无线设备越来越多的走进普通百姓的工作和生活,随着信息网络化的不断进展,手机及其他无线设备上网络势在必行。但是传统手机存在以下弊端:
1. 传统手机出厂时均由硬件厂商固化程序,程序不能增加、删除,有了错误也不能更新、修改,若要增加新功能必须另换一部手机。
2. 传统手机访问互联网是通过WAP(Wireless Application Protocal),所有网络资源必须接通网络才能在线访问,非常耗时、费用亦很高。
而Java技术在无线应用方面的优势非常明显:
1. 应用程序可按需下载,而不是购买由硬件商提供的套件,可升级空间大。
2. Java技术提供了一个类库,它使的应用开发商可以创建更为直觉、丰富的用户界面(GUI);
3. Java技术使网络带宽的应用更为有效,因为应用程序可以下载到器件上,并在本地运行,仅仅是在连接到服务器时才会占用网络带宽。
基于以上分析,Java手机将是未来手机的发展方向,是业界的热点。
虽然 Java 已经被用到许多企业级软体上,可是其实骨子里面还是非常适合用在嵌入式系统之中。Java平台演进到Java2后,Java平台分别针对不同领域的需求被分成四个版本,亦即J2EE、J2SE、J2ME以及JavaCard。其中J2ME定位在消费性电子产品的应用上。这个版本针对资源有限的电子消费产品的需求精简核心类库,并提供了模块化的架构让不同类型产品能够随时增加支持的能力。这个版本的应用层面相当广泛,会是未来Java平台发展的重点项目。
J2ME在1999年的JavaOne开发人员大会上初次亮相,它的目标是面向智能无线设备和小型计算机设备的开发人员。J2ME的一个关键优点是,J2ME与所有支持Java的设备都是兼容的。支持Java的设备就是任何运行Java虚拟机器的计算机。Motorola、Nokia等生产厂商都生产支持Java的设备。、
J2ME平台是由配置(Configuration)和简表(Profile)构成的。配置是提供给最大范围设备使用的最小类库集合,在配置中同时包含Java虚拟机。简表是针对一系列设备
提供的开发包集合。在J2ME中还有一个重要的概念是可选包(Optional Package),它是针对特定设备提供的类库,比如某些设备是支持蓝牙的,针对此功能J2ME中制定了JSR82(Bluetooth API)提供了对蓝牙的支持。
目前,J2ME中有两个最主要的配置,分别是Connected Limited Devices Configuration(CLDC)和Connected Devices Configuration(CDC)。
作为第一个面对小型设备的Java应用开发规范,CLDC是由包括Nokia,Motorola和Siemens在内的18家全球知名公司共同协商完成的。CLDC是J2ME核心配置中的一个,可以支持一个或多个profile。其目标主要面向小型的、网络连接速度慢、能源有限(主要是电池供电)且资源有限的设备,如手机、PDA等。
而CDC则是主要用于运算能力相对较佳、在电力供应上相对比较充足的嵌入式装置 (比方说冷气机、电冰箱、电视机机顶盒 (set-top box))
一个手机游戏应该具有以下特征:
易于学习: 既然手机游戏面向的是普通消费者而不是计算机专家,那么他们不可能深入的学习游戏技巧。消费者不会花几个小时去研究一个3元的手动操作的游戏。保持游戏的简单是最基本的要求。
可中断性: 多任务处理是手机生活方式的基本特征。手机用户常常在任务(如等一个电子邮件或者等车)之间有一小段时间。而游戏、日历管理、通讯和工作数据访问使用的是同一个设备。所以一个好的手机游戏应该提供短时间的娱乐功能,并且允许用户在游戏和工作模式之间顺利切换。
基于订阅:手机游戏的盈利成功取决于他们巨大的使用量。一开始开发和设计每个游戏都是昂贵的。如果一个手机游戏开发者要赢利的话,重要的是:同一个游戏引擎,多个标题,基本的故事情节类似。基于订阅的游戏是不断产生收入的最好方法。
丰富的社会交互: 不管一个游戏设计得多好,只要玩家找到了它的根本模式或者玩完了所有的游戏路径很快就会厌烦这个游戏。对于一个基于订阅的游戏,重要的是与别的玩家合作以增强所玩游戏的智力和随机性。在今天纷繁复杂的多玩家游戏中具有丰富社会交互的游戏证明是成功的。
利用手机技术的优点: 巨额的手机技术研发费用都花在提高设备和网络的可用性和可靠性上面。因此,手机设备硬件和网络协议与桌面/控制台世界(如全球定位系统(GPS)扩展、条形码扫描仪、和短消息服务(SMS)/多媒体信息服务(MMS)通讯)有着非常大的差别。好的手机游戏应该利用那些更新的设备特征和网络基础设备的优点。
在抗战中,由国民政府领导的中国空军是所有国民党军队中抗战最为彻底,也最为英勇的部队,虽然开战之初力量悬殊,但是面对穷凶极恶的日本侵略者,他们毫不畏惧,视死如归,全力以赴投身到民族救亡的伟业中去,用自己的鲜血和生命谱写了中华民族最为豪迈的诗篇。自一九三二年二月五日“一·二八”事件始,至一九四五年八月十四日止,抗战期间,共出动飞机一千一百二十八批,八千八百四十七架次,击落敌机五百二十九架,击伤敌机一百一十架,炸毁敌机二百二十七架。同时,中国空军空战中一共牺牲空勤人员六百六十一名。
我至今仍然清楚的记得,在抗日战争即世界反法西斯战争胜利50周年的时候,我在一本描述抗战空军的书里第一次看到阎海文烈士那年青的面孔时所带来的震撼,第一次看到高志航、沈崇海等空军烈士的事迹时所带来的感动,第一次听说碧山空战时的无奈。
时至今日,已经很少有人能够记得在中国的天空献身的抗日英雄们,我只能引用下面这句话来表达我的心情:“你们的名字无人知晓,你们的业绩与世长存! ”
我的这款游戏取名为《览桥风光》,以纪念从览桥中央航校走出的英雄们。
引言和第一章中介绍了手机在无线应用方向的当今概况,J2ME的相关内容,分析了J2ME在手机软件开发中起的重要作用,描述了本论文的相关背景。
2 开发环境及相关技术的介绍
操作系统:Microsoft Windows XP
程序语言:Java 2
开 发 包:Java(TM) 2 Standard Edition (5.0)
Sun Micro. J2ME Wireless Tool Kit 2.2
IDE: Eclipse 3.01
1. 平台无关性
Java引进虚拟机原理,并运行于虚拟机,实现不同平台之间的Java接口。Java的数据类型与机器无关。
2. 安全性
Java的编程类似C++,但舍弃了C++的指针对存储器地址的直接操作,程序运行时,内存由操作系统分配,这样可以避免病毒通过指针入侵系统。它提供了安全管理器,防止程序的非法访问。
3. 面向对象
Java吸收了C++面向对象的概念,将数据封装于类中,实现了程序的简洁性和便于维护性,使程序代码可以只需一次编译就可反复利用。
4. 分布式
Java建立在TCP/IP网络平台上,提供了用HTTP和FTP协议传送和接收信息的库函数,使用其相关技术可以十分方便的构建分布式应用系统。
5. 健壮性
Java致力与检查程序在编译和运行时的错误,并自动回收内存,减少了内存出错的可能性。Java取消了C语言的结构、指针、#define语句、多重继承、goto语句、操作符、重载等不易被掌握的特性,提供垃圾收集器自动回收不用的内存空间。
Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。幸运的是,Eclipse 附带了一个标准的插件集,包括 Java 开发工具(Java Development Tools,JDT)。
虽然大多数用户很乐于将 Eclipse 当作 Java IDE 来使用,但 Eclipse 的目标不仅限于此。Eclipse 还包括插件开发环境(Plug-in Development Environment,PDE),这个组件主要针对希望扩展 Eclipse 的软件开发人员,因为它允许他们构建与 Eclipse 环境无缝集成的工具。由于 Eclipse 中的每样东西都是插件,对于给 Eclipse 提供插件,以及给用户提供一致和统一的集成开发环境而言,所有工具开发人员都具有同等的发挥场所。
这种平等和一致性并不仅限于 Java 开发工具。尽管 Eclipse 是使用 Java 语言开发的,但它的用途并不限于 Java 语言;例如,支持诸如 C/C++、COBOL 和 Eiffel 等编程语言的插件已经可用,或预计会推出。Eclipse 框架还可用来作为与软件开发无关的其他应用程序类型的基础,比如内容管理系统。Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。
WTK(Wireless Tool Kit)是Sun公司针对J2ME推出的用于手机和Palm等移动设备的开发包,是除手机厂商的专用开发包外唯一的手机模拟器开发包。它通用性高,开发出的应用程序可保证能运行在大部分设备上,而不像专用厂商具有一定的不兼容性。虽然它没有强大的功能和完善的调试手段,但它提供运行模拟器的最基本组件,是其他IDE需集成采用的必备元素。
手机中负责调配程序运行资源的管理后台是Java Application Manager。它所使用的传输媒体可以是红外线、网络、以及其他可用来传输的媒体。Java Application Manager 会从网络上下载代表该Application Suite 的JAR 档,接着在手机上安裝此MIDlet Suite,然后在手机开始执行该应用程序。
第二章介绍了Java语言的特点、本程序的开发环境及其相关工具的原理和使用。
3 程序结构、思想和相关技术
1. 游戏程序是一项精度要求很高的程序系统,因为其代码利用率很高。一个实时运行的最终作品,每秒都会运行成千上万行程序,绘图事件、键盘事件都会以极高的频率在后台等待响应,若有丝毫的差别都将很容易导致程序在运行不久后可能出现严重错误,甚至死循环。因此,其逻辑设计应当相当严谨,需将所有可能发生的事件及意外情况考虑在设计中。
2. 游戏中为了美观,适用性强,可能需要采用外部文件引入的图片贴图,有关贴图,在MIDP2.0中提供了用于增强游戏功能的game包,使得解决静态或动态、画面背景、屏幕刷新的双缓冲等都有较好的解决方案。
3. 玩家飞机的运行可以通过键盘响应事件控制,但敌方则因为是自动运行,就需要有一定的智能性;敌人飞机的运行算法也要进行相关的设置,已免游戏过于简单。
4.对于双方发射的子弹应该赋予不同的速度,同时,程序应该设定敌人飞机的子弹不与敌人的飞机进行碰撞检测,已增加游戏的可玩性。
5. 双方的飞机在前进时也需要考虑到是否碰撞到对方飞机,以免重叠运行,造成许多物理上不可能的情况,缺乏真实感。每一次刷新页面、每前进一步都需要进行相关的碰撞检测。
6.为了增加界面的美观,在程序中添加了白云。由于手机屏幕大小有限,所以白云的数量和出现的位置要经过相关的设置,才能实现白云不规则出现的效果。
7. 游戏的地图不可能通过绘图来解决。否则,不仅难于控制和处理过多的元素,也会因过多的大型图片而不能限制程序的大小,失去手机上程序的原则和Java的优势。
8. Java是基于虚拟机的半解释型编译系统,其执行效率较C++等完全编译后的程序会低很多,程序如果不进行精简和优化,将可能导致运行的不流畅。除开发过程中对结构上的控制、变量的使用、算法的优化等优化外,还可以使用混淆器(Obfuscator)进行程序打包后的优化。
9. 游戏的结束、开始、动态信息画面作为构成一个程序都是必不可少的重要部分。良好的用户界面更是吸引用户的硬指标,相关的美术构图和人性化设置也需要有一定的考虑。
以上相关技术细节和整体流程将分别在以下小节阐述。
3.2 程序流程
消减状态 (Destroyed) |
停止状态 (Paused) |
运行状态 (Active) |
StartApp() |
DestroyApp() |
呼叫MIDlet的构造函数 |
DestroyApp() |
PauseApp() |
图3-1 MIDlet的流程 |
每个MIDlet都必须继承javax.microedition.midlet.MIDlet这个抽象类。在MIDP规范中定义了MIDlet的生命周期,以及可以存在的三种状态,包括Paused、Active以及Destroyed,每一个MIDlet在任何时刻只可能处于其中的一个状态。这三种状态的转换关系如图3-1所示:MIDlet有三个状态,分别是pause、active和destroyed。在启动一个MIDlet的时
候,应用管理软件会首先创建一个MIDlet实例并使得他处于pause状态,当startApp()方法被调用的时候MIDlet进入active状态,也就是所说的运行状态。在active状态调用destroyApp(boolean unconditional)或者pauseApp()方法可以使得MIDlet进入destroyed或者pause状态。值得一提的是destroyApp(boolean unconditional)方法,事实上,当destroyApp()方法被调用的时候,AMS通知MIDlet进入destroyed状态。在destroyed状态的MIDlet必须释放了所有的资源,并且保存了数据。如果unconditional为false的时候,MIDlet可以在接到通知后抛出MIDletStateChangeException而保持在当前状态,如果设置为true的话,则必须立即进入destroyed状态。
本程序采用面向对象的设计模式,对游戏中的所有物体赋予对象的概念和属性。运行程序后允许用户选择执行选项菜单,在开始游戏后将先从外部文件载入地图文件,对背景的所有物体进行绘图。在主程序运行的线程中,画面刷新将以一定的频率采用双缓冲技术对屏幕重绘,实时反映整个游戏的进行状态。
游戏开始后先绘制地图,并将各个对象实例化。在主程序运行的线程中,游戏中所有的对象都应该运行在同一个线程下。当敌人或者用户的子弹达到射程范围后,并不删除子弹对象,而是使用setVisable(false)使其不能显示,当用户或敌人在次发射子弹时,只需使用setVisable(true)设置成可以显示即可。在屏幕重绘的主程序中,将在每次的循环中判断若干事件,以便程序进入相关的分支执行相关的反应代码。如:玩家剩余飞机数是为0、敌人、玩家飞机是否被击中、屏幕上相关信息的绘制等。
程序为需要完成独立功能的模块设置了单独的类。lzhhdm类继承自Midlet,gameScrenn类、MenuScreen类继承自GameCanvas,mybullets继承自Sprite类。载入程序后首先启动的是程序介绍的信息画面。点击ok后调用MenuScreen类实现菜单。
如果选择进入游戏,则调用gameScreen类,并且中止MenuScreen类中的线程运行,已提高运行速度。
Mybullets类为玩家子弹类。
为了能有程序开发人员控制接口的外观和行为,需要使用大量的初级用户接口类,尤其在游戏程序中,几乎完全依赖的就是Canvas抽象类进行绘图。从程序开发的观点看,Canvas类可与高级Screen类交互,程序可在需要时在Canvas中掺入高级类的组件。Canvas提供了键盘事件、指点杆事件(如果设备支持),并定义了允许将键盘按键映射为游戏控制键的函数。键盘事件由键代码指定,但这样控制游戏会导致缺乏通用性,并不是每个设备的键盘布局都适合游戏的操作。应当将键代码转换为游戏键的代码,以便硬件开发商能定义他们自己的游戏键布局。
Graphics类提供了简单的2D绘图功能。它具有24位深度色彩的绘制能力,以三原色分别各占一个字节表示其颜色。程序只能在paint()函数中使用Graphics绘制,GameCanvas可调用getGraphics()函数直接绘制在缓冲区上,可以在任何时间请求传输到前台。其对象会被传给Canvas的paint()函数,以便最终显示。
在没有MIDP2.0前,进行游戏绘图一般需要手动编程使用双缓冲。需要在paint()方法内将所想要画的图形画在一张预先准备好的背景上,等所有绘图操作都完成后再将背景的数据拷贝到实际的屏幕上。Image类提供了一个建立背景的静态方法createImage(int width, int height),再利用getGraphics()方法取得属于这个背景的Graphics对象,所进行的绘图操作都会作用在背景上,等到全部的绘图操作完成后,再调用drawImage()方法将背景的数据复制到实际显示的屏幕上。
这样的技术在绘制动画时特别有用。绘制动画时经常需要不断地更新画面,而更新画面的操作就是先将屏幕以fillRect()的方式清除,再将下一张图片画在屏幕上,然而反复的清除及重绘会造成屏幕的闪烁现象(flicker),因此使用双重缓冲的好处就是在背景进行这个清除及重绘的操作,再将完成的绘图拷贝到屏幕上,由于用户看不到清除的操作,因此就不会出现闪烁的现象了。不过在某些MIDP的实现上已经加上了双重缓冲的支持,因此在处理前应先利用Canvas类的isDoubleBuffer()方法来判断。
J2ME的流行促进几个运营商和制造商开发了一些支持游戏的类,但是,这却造成了游戏缺乏可移植性的问题,例如,很难将使用Siemens的Sprite类的游戏移植到Nokia上。
在MIDP2.0版本发布后,这些游戏移植性问题初步得到了解决。MIDP2.0新加入了
GameCanvas、Sprite、Layer、LayerManager、TiledLayer五个与游戏开发相关的类。其中 Layer类一般不会直接用到。
Game类的出现不仅降低了错误出现的几率,也使游戏代码变的更小,因为开发者不需要自己编写象Sprite这种例子。下面将简要介绍Game类。
GameCanvas类继承自Canvas,所以具有Canvas所具有的功能,还额外增加了一些便于游戏设计的功能。比如: GameCanvas类直接提供了getKeyStates(),使程序员可以在同一个线程自己侦测按键的状态。GameCanvas类提供了flushGraphics()的功能,实现了双缓冲技术。
所谓的Sprite,就是画面上独立移动的图形。
Sprite类是继承自Layer的用于存储多桢的基本可视元素。不同的frame可交相显示,构成动态的效果。图片可翻转、颠倒、由一个主角图片就可以方便的得到所有方向的显示状态,相比原先只能使用Canvas绘图,需要将所有方向的主角图象都绘制在png图象中简化了许多。Sprite也可以从整合的图象中读图,读图时将把大图分解为若干等宽等高的小图。每个小图按照其排列顺序有相应的序号,在程序中调用其序号,就可以绘制出相应的图片。本程序中的双方飞机、子弹、白云都由Sprite继承得到。
LayerManager提供控制整体画面层的控制。它包括了一系列自动获取了代号和位置的层,简化了各层加入游戏画面的过程,提供了自动排序和绘制的能力。
LayerManager存储了一个层的列表,新的层可以用函数附加、删除和插入。层的序号相当于坐标的Z轴,0层表示最接近用户视觉,层数越高,离用户越远。层号总是连续的,即使有中间的层被移除,其他层的序号会作相应的调整以保持整体的完整性。LM中的View Window控制着与LM相对坐标的可视区域。改变View Window的位置可以制造出滚动屏幕的效果。
TiledLayer是有一组图象格元素组成的整块虚拟图象。该类使不需要高分辨率的图象就能创建大幅图面成为可能。这项技术通常应用在2D游戏平台的滚动背景的绘图。一块整图可被分割成等大小的图象格,每块格有其对应的序号,按照行列递增。多块小格可由大块同时替换组合而模拟动态的背景,这不需要逐块替换所有的静态图象格而显得非常方便。
PNG(Portable Network Graphics)格式是MIDlet唯一支持的图象格式,PNG具体格式由PNG Specification,Version 1.0定义的。PNG格式提供透明背景的图象,这对绘制游戏画面和被操纵主角极有帮助。飞机之间或与白云碰撞时就不会因为背景有特定的颜色,显示出的效果像贴上的图片而缺乏真实感,物体之间轻微重叠时最上层图片也不会覆盖超过其有效象素外的部分。
PNG格式图片中包含许多定义其图片特性的冗余部分(Chunks)。这些代码包含在每一个单独的png格式图象中,然而如果将多个png图象合并在一张幅面稍大一些的整图中,多个chunks就可以得到精简,图片的大小可以得到控制。使用Image类中的createImage函数可从整图中分割出所需要的元素。在Game包中的TiledLayer和Sprite类都整合了这样的功能。本程序中的地图元素都集成在一张beijing.png图片中,实现了方便的管理和程序体积的精简。
GameCanvas提供getKeyStates函数可获取当前键盘上的信息。将以位的形式返回键盘上所有键的按与释放的状态,当bit为1时,按键就是被按下的状态,为0时则为释放状态。只需要此一个函数的返回值就可以返回所有键的状态。这保证了快速的按键和释放也会被循环所捕捉。同时,这样的机制也可检测到几个键同时按下的状态,从而提供斜向运行等相应功能(本程序没有实现斜上运行功能)。
程序运行时应该对玩家飞机是否飞出屏幕的范围进行检测,如果飞出屏幕,就应该重新设定玩家飞机的位置。
玩家飞机被击中后,为了平衡游戏的可玩性,玩家飞机将有短暂时间无敌,即不进行碰撞检测,同时在屏幕右上角显示无敌时间。
根据游戏设定,敌人飞机。不能与玩家飞机重合,则他每走一步都需要检测一下是否与玩家飞机碰撞。Sprite类中提供了collidesWith函数,用于判断是否与某个Sprite、TiledLayer、Image的对象有图象上的重合(即游戏中的碰撞)。同理,还需要检测玩家子弹与敌机、敌机与玩家子弹是否碰撞。如果发生碰撞,将相关精灵图片替换为爆炸图片。
敌人飞机需要具有一定的智能性,以便对玩家攻击,使游戏具有一定的可玩性。敌人可以在适当时候转向或者开炮火,同时,程序应该检测敌机是否飞出了界外。
在普通敌机中,有一组敌机的其中一架具有跟踪功能,其原理为:当其进入屏幕后,根据玩家飞机的X、Y坐标不断调整自己的X、Y坐标,已达成跟踪的效果。由于线程的关系,敌机器的改变方向有时并不是实时的,这就可以使玩家有躲开撞击的可能,增强了游戏的可玩性。
在游戏进行中出现的大型飞机,由于其不可能立即被击落,所以应该设置其的运行方法,理论上讲还是根据玩家飞机的坐标,但是,在此设置一个标志位,使得敌人在取的玩家位置后即开始玩家方向运动,这期间,将不执行取得玩家飞机位置重设飞行方向的步骤。这样做,即防止了大飞机变成跟踪飞机,又使得大飞机的运行具有不确定性。
在关尾出现的BOSS,其在屏幕上方左右移动并发射子弹。实际上,此时BOSS应该通过玩家在游戏运行中的习惯性的运行方向,使用遗传算法,来动态判断玩家下一步的运行方向,并且指挥普通飞机出现在预测的位置上。可惜由于时间关系没有实现。
玩家的子弹是个精灵数组,有9个元素,表示玩家一次最多可以发射3组9发子弹,对于一个完整的游戏来讲,应该根据关卡的不同而给予玩家不同的飞机,飞机性能的差别在于子弹的射程不同。由于本游戏仅有一关,所以子弹速度设定的差别没有体现出来。
当玩家一次发射了3组子弹,而这3组子弹并没有消失时,玩家将无法发射子弹。
使用每组子弹的第一发作为与敌人进行碰撞检测的精灵,同时相关的标志位也设在第一发子弹中。如果玩家子弹与敌机相撞,则敌机消失时,子弹精灵的图片替换为爆炸图片,直到第二次发射该组子弹时,才将图片替换为子弹图片。
手机内存空间小,所以在程序设计时应该注意以下几点,以尽量减少内存的使用:
(1)尽量缩短命名的长度。在应用程序内,对于所建立的类、接口、方法及变量名而言,都需要赋予一个识别的名称,所命名的名称每多一个字符就会在类文件内多产生一个字节,对于一个较复杂的应用程序而言就会增加为数不小的数据量。所有这些可以借助混淆器来帮助实现
(2)所有代码写为一个类。
(3)只使用一个线程。
(4)尽量不使用静态变量。
(5)将PNG图片合并成一张,减少图形数据的大小。
将PNG格式的小分辨率图象合并在一张大的高分辨率图象中,由于减少了头文件的大小,将比合并前的总大小减少许多。
Wireless Tool Kit提供了许多在运行时监视运行状态的工具。 包括内存状况的检测(手机上的内存空间十分有限,必须时刻关注机载内存是否大于程序所能使用到的最大可能的内存空间),网络状况的检测,运行函数的跟踪等。 内存检测器是内存跟踪测试随时间变化的调试器。其中,允许强制垃圾回收(Garbage Collection)。由于Java语言中,不像许多其他的如C++语言,不需要指定回收函数中特定不使用的资源,资源回收机制将自动清空无效变量占用的空间。在程序运行中也可以调用System类的gc()函数手动收回废弃的内存。
Java 语言并没有完全编译成二进制可执行文件,编译出的.class文件是一种介于源程序和二进制之间的一中基于半解释的字节码,需要虚拟机来执行。它包括了所有的信息。然而这样会导致.class很容易被反编译为源代码,从而不能保护作者的知识成果。目前流行的如decode,JAD等反编译工具可以以很快的速度生成源文件。如果不加以施行有效的措施,将造成严重的后果。
由此引入混淆器的概念。混淆器将代码中的所有变量、函数、类的名称变为简短的英文字母代号,如果缺乏相应的函数名指示和程序注释,即使被反编译,也将难以阅读。
混淆器的作用不仅仅是保护代码,它也有精简编译后程序大小的作用。由于以上介绍的减少变量、函数的命名长度的关系,编译后也会从.class文件中减少这些冗余的信息。混淆后,体积大约能减少25%,这对当前费用较贵的无线网络传输是有一定意义的。
第三章中介绍了程序的流程、相关技术的思想及其在本程序中的应用。对游戏基本算法等做了详细叙述。具体算法的代码实现和详细流程将在下章介绍。
4 程序分析和具体实现
每个MIDlet程序都必须有一个主类,该类必须继承自MIDlet。它控制着整个程序的运行,并且可以通过相应函数从程序描述文件中获取相关的信息。该类中拥有可以管理程序的创建、开始、暂停(手机中很可能有正在运行程序却突然来电的情况,这时应进入暂停状态。)、结束的函数。本程序主类为lzhhdm,并实现接口CommandLIstener。
首先显示的是游戏的背景介绍(图4-1),为此,在类lzhhdm定义Form类对象a,在startApp()函数中判断isSplash是否为真,如果为真的话,将创建Form类的实例a,并且调用append()方法在表单上放置StringItem类的实例以显示游戏背景信息。使用语句ok=new Command("ok",Command.OK,1);实例化Command类对象ok。调用addCommand()命令建立ok命令与Form之间的关联,调用setCommandListener()命令使Form与CommandListener建立关联。调用Displayable的
图4-1游戏背景介绍 |
当玩家点击ok后将调用display.setCurrent(menuscreen)
以显示游戏菜单menuscreen(图4-2)。
类menuscreen继承自Canvas类,并实现接口Runnable
和CommandListener。在类menuscreen中定义了lowColor和
highColor、highBGColor三个整型变量及布尔型变量co。其
中lowColor赋值为0x000000FF,代表兰色,higColor赋值为
0x00FF0000,代表红色,highBGColor赋值为0x00CCCCCC,代表
|
(int code)中的整型变量menuIndex相应的减1或加1,相应的,在paint()函数中会根据menuIndex绘制选项是否被选中。在函数run()中,如果co为真,则不停的repaint(),设置co的意义在于,当进入游戏主画面后,co赋值为false,以终止绘制选项的repaint(),提高游戏速度。
当移动选项条到某项,并点击ok时,在commandAction()方法中根据 menuIndex
进入游戏 |
y1>-1550 pzbzover==0 |
显示敌人 |
玩家被击落了吗 ? |
玩家是否还有机会? |
继续游戏 |
是否过关 |
返回主界面 |
Y |
Y |
N |
|
Y |
Y |
图4-4 gameScreen类主要关系流程图 |
N |
|
在介绍游戏主类gameScreen类之前,应该先简要说明一下玩家子弹类mybullets类,实际上,mybullets类是应该删除的,其要实现的功能应该放在gameScreen类中,但是由于设计游戏的过程也是一个学习的过程,而在当时,我并没有意识到这一点。
Mybullets类继承自Sprite类,以实现玩家子弹的相关功能。首先,创建子弹状态数组private int[][] bullets,其中,[i][0]代表子弹的X坐标,[i][1]代表子弹的Y坐标,[i][2]代表子弹Y方向速度,[i][3]代表子弹存活状态(由于此类是在早期设计的,而之后子弹存活状态使用了子弹射程作为标志位,所以其并没有起到作用)。类中定义的方法setfirstposition()起到定义玩家子弹发射坐标的作用(此方法在设计时起到的作用是消除每按一次开火玩家子弹位置就重新定位这个BUG,但是,这个BUG完全可以用设置标志位的方法消除)。方法newposition()实现的功能为更新玩家的子弹位置,并且检测玩家子弹与普通敌人的碰撞及记录玩家战果(更新子弹位置的功能可以由使用move()加设置标志位的方法取代;由于设计这个类的时候并没有考虑到添加BOSS等,所以在此检测碰撞,但添加BOOS等功能后,此处的检测完全可以和飞机对飞机等的碰撞检测封装在同一个方法中)。
mybullets类在gameScreen中建立了对象数组huokebullet[9],代表玩家所能发射的9发子弹。
gameScreen类是游戏的主类,决定着敌人何时出现,控制着敌人出现的方法,判断敌人及玩家是否被击中等。它运行在独立的线程中,以恒定的频率刷新画面。本程序设置为1/20秒。其主逻辑如图4-4所示。
gameScreen类要实现地图的滚动、敌人飞机的相关属性、玩家的相关属性等功能。
gameScreen类包括了LayerManager,这样所有静态和动态的图象都不需要手动刷新,只需要在LayerManager中加入所有的需要控制的精灵,在统一由LayerManager刷新即可, 因此,在gameScreen中创建LayerManager的对象lm,并在构造函数中实例子化。
其他精灵类的对象如敌人飞机、玩家飞机、玩家飞机的子弹、敌人的子弹、BOSS及BOSS所属的子弹都需在gameScreen()类中建立相应的对象,并在构造喊数中实例化,且由lm.appned()方法添加到LayerManager类对象lm中。
由于手机存储空间的限制,不可能将整张地图完整地存储在手机中,为了节约空间,往往提出地图中相同的图片组成一张PNG格式的图片,然后象拼图一样拼出地图来,专业的游戏设计者往往自己写一个地图编辑器,以使拼图过程不是那么痛苦。
创建地图就需要使用TiledLayer。TiledLayer指的是由一块一块类似用瓷砖拼凑起来的画面。地图实际即为TiledLayer的一个对象。先利用TiledLayer的构造函数建立TiledLayer,根据构造函数的参数可以给定Cell数组 的大小,并且地图图片切割成等尺寸的画面,并调用setCell()设置具体的图象格内容。地图
|
|
因此,创建一个返回TtiledLayer的方法createBackGround(),以便在gameScreen()的构造函数中调用。在方法中,定义整型数组map1[]以存储Cell的索引值。并使用tiledLayer.setCell(column,row,map1[i])设定TtiledLayer的内容,以形成地图。其中i的值由循环for(int i=0;i<map1.length;i++)取得,column由语句column=i%15取得、行由row=(i-column)/15取得。
画出地图后,由lm.append()将地图添加到LayerManager类对象lm中。由于地图位于Layer的最低层,即离用户视线最远的层,所以Tiledlayer最后一个被添加到lm中。
根据游戏的设定,游戏中地图是向下移动的,实现此功能的方法如下:
首先,在使用createBackGround()函数创建地图数组时,用(row+1)*16-getHeight()
语句对整型变量row2赋值,其中row+1代表地图有多少列,16为地图片的高度,而减去getHeight()是因为要留出一个屏幕的可视区域,由于J2ME规定坐标系中下方向为正,所以使用语句y1=-row2将row2的数值变为负数。
|
使用lm.paint(g,0,y1)决定
View Window从屏幕的哪里画起。
在run()函数中的while(conti)中,
|
地图下移1个象素。(参考图4-6)
gameScreen类的构造函数要将游戏中出现的所有精灵都实例化,实际上,这种方法
严重的占用了内存,但在当时,我并没有意识到这一点。
由于敌人要求不停的出现,但是不可能设置过多的精灵,解决的办法是设定6个Sprite类对象j0、j1、j2及jbullet1、jbullet1、jbullet2,分别代表三架敌机及其配属的子弹。所以在gameScreen类的构造函数分配这6个Sprite类的存储空间,并且使用new Sprite(Image img, int width,int height)实例化这6个类变量。同关尾cboss与游戏进行中的大飞机其他的Sprite类对象都需要使用相同的方法实例化。同样处于节约内存的考虑,sboss与cboss同用3个Sprite类对象bossbullet0,1,2。
在构造函数中,定义boolean型变量conti=true。conti的作用在于控制是否进行游戏画面的重绘及其他需要在画面重绘前进行的运算。
在构造函数中,将mybullets类里的no和score初始化,现在看来no的初始化没有必要,但是score的初始化是必须的,因为这个变量存储着玩家每次游戏的成绩,如果不在此进行初始化,则玩家重新开始游戏后score并不归0。
每个创建Command实例的J2ME的应用程序也必须创建实现CommandListener接口的实例。每当用户通过commandAction()方法的方式与命令进行交互的时候,就会通过CommandLIstener.所以实现CommandListener的类必须实现commandAction()方法。
在commandAction()方法中,使用getabel()方法获取命令的标签。如果getLabel()=“暂停”时,表示玩家点击了暂停键,此时,conti赋值为false,游戏画面的绘制及游戏相关的运算暂停,并且,使用removeCommand(c)语句将“暂停”移除,使用addCommand(new Command("继续",Command.OK,2));将”继续“按纽”添加进来。当玩家点击“继续“时,conti赋值为true,并且,一定要调用start()方法,否则继续功能不可用。必须调用start()方法的原因是:J2ME的线程已stop()方法拿掉,如果想停止线程的运做,就必须依靠一个旗标(flag),在本程序中,flag就是boolean型变量conti。所以一旦此标识变量被设为false,那么while(conti)循环就会结束,线程也会跟着结束。当用户按下“继续”的时候,start()将重新产生一个线程继续执行相关的运算和画面绘制。
当玩家通关时或者任务失败时,将显示相应信息,并使用上面的方法将“暂停”键变为“返回”键盘,当点击“返回”键时,将返回主菜单选项,调用类lzhhdm里的方法menuscreensecond(),在此方法中,实例化一个MenuScreen类对象,并且使选项“新游戏”改变为”重新开始“(图4-7)。完成此项功能的语句子为MenuScreen.mainmenu[0] =”重新开始”。当选择”重新开始“时,使用gamescreen=new gameScreen(this)将使所有变量重新被初始化,如地图的绘制、敌人出现位置的重置、敌人的数量、玩家飞机的当前位置等。使用gamescreen.start()重新开始程序的循环。
|
机、子弹与敌机及敌机子弹与玩家飞机的碰撞检测,即使用函
数collidesWith(Sprite,boolean)。由于设计的问题,玩家发
射的子弹与普通敌机的碰撞检测被写在了mybullets类中,并
且只检测第一发子弹是否与敌人相碰撞,如果碰撞为真时,则
使用setVisible(false)函数将敌机隐藏,使用setImage()函
数将子弹精灵的三张图片置换为爆炸图片( 图4-8 )。当敌人剩
图4-7 |
setVisible(true)将敌机重新设置为可见。在按“开火”键时,使用setImage()函数将huokebullets重新设定为子弹图片(每次击落敌机后屏幕上的爆炸效果有3团,并且按“开火”后爆炸图片就消失了,实际上setVisavle(false)的应该是子弹数组,而对敌人精灵使用setImage(),这样效果会好一些)。
敌机与玩家的碰撞检测原理同上,都是使用的collidesWith()函数,遗憾的是,我在写这段代码的时候,并没有考虑设置玩家有4次机会,所以对敌人飞机setVisable(fasle)了,而将玩家的飞机换成了爆炸图片,之后,添加了玩家4次机会这个功能,由于玩家被击落后会重新从屏幕下方进入屏幕,所以爆炸的图片一闪而过,效果不是很好。
根据游戏设置,玩家在每关中有共四次机会,当玩家飞机被击中或撞击爆炸后,程序首先检测整型变量playerno的值,并根据playerno的值决定屏幕右上角所画玩家飞机标志的数量(参考图4-8),playerno的初始值设为3,因为碰撞后才减1,所以玩家共有4次机会,当playerno<0时,游戏结束,同时将变量pver赋值为1,render()或renderboss()函数中,over=1代表在屏幕上GAMEOVER等相关信息,同时,将整型变量inputno赋植为1,以使手机的方向键失效,以消除玩家可以控制爆炸图像移动这个BUG。同时整型变量pzbz赋植为1,以消除玩家爆炸图像继续与敌人进行碰撞检测这个BUG。
当playerno>0时,碰撞后,将变量planert赋值为1,在之后的if(planert==1)判断语句中,重新设定玩家飞机的图片和可视状态,同时使用setPosition()函数设定玩家非的位置在屏幕下方。设定pzbz=1,即不检测碰撞,玩家有短暂时间无敌,无敌时间由屏幕右上角进度条表示。设定inputno=1,即飞入屏幕的过程中手机键盘是不可以用的。设置planert=2,即以上这些设置只执行一便。
|
0,-2)使飞机自己向上运动,使用if(c1.getY()<(
planepo-24))判断飞机是否到达屏幕最下方(planepo
是屏幕下边缘的坐标),如条件为真,则将inputno
设置为1,表示键盘可用,将planert赋值为3,使
其不再执行以上各步。
input()函数的作用是检测用户的输入。
首先使用if(inputno==0)判断用户的输入是否
被禁止,如为真,则用户输入不被禁止。
|
当玩家按方向键时,玩家飞机就向不同的方向运行,这需要使用c1.move(int x,int y)函数,当玩家控制飞机向左或右飞时,需要使c1.setFrame()函数改变飞机的图形(参见图4-7,此时飞机右飞)。同时,还需要判断飞机是否飞出屏幕,如,当飞机右飞时,用if(c1.getX()>(getWidth()-c1.getWidth()))
语句判断(getWith()为屏幕的宽度,c1.getWidth()为玩家飞机c1的宽度),如果条件为真,则使用c1.setPosition((getWidth()-c1.getWidth()),c1.getY())语句将飞机设置在紧靠屏幕右边的位置。上、下、左的设置原理同上。
语句if((keystate&LEFT_PRESSED)==0)的作用是消除左、右飞后在上、下飞时飞机的形态不变的BUG。如果为真,则执行语句c1.setFrame(0),表示只要左键松开飞机的形态都是平飞。
根据游戏设定,玩家一次最多只能发三组子弹,并且子弹有射程限制(在类mybullets中使用整型变量no表示),而当玩家按下“开火“键时,即if((keystate&FIRE_PRESSED)!=0)中判断条件为真时,首先执行循环语句for(int i=0;i<=6;i=i+3),即检测3组子弹中每组的第一发,即0,3,6。其次,检测huokebullet[i].no是否等于1,当等于1时使用语句for( int z=i;z<i+3;z++)初始化该组子弹中的3发子弹,而设置子弹位置的函数应该在if(huokebullet[i].no==1)语句外设置,因为当初设计的时候mybullets类里的函数写成了一次设置三组子弹的形式。
huokebullet[z].no=huokebullet[z].bulletheight赋予子弹射程。当程序循环运行时no--,当一次发射了三组子弹后,只有某一组子弹消失,即no等于1后才能继续发射子弹。
现在看来,玩家发射子弹的设置是完全失败的,降低了效率。
在方法render()过程中,除了要重绘飞机、地图、子弹外还要在上方绘制关卡信息、战果、玩家飞机数、及无敌状态时的无敌时间、大飞机生命条等。
首先使用lm.setViewWindow()和lm.paint()设定可视范围ViewWindow和从哪里画起(见4.3.3)。
其次,使用g.drawString()绘制屏幕上方的关卡信息、战绩、玩家剩余生命标志。
drawString()中使用String.valueOf(huokebullet[0].rscore()+huokebullet[3].
rscore()+huokebullet[6].rscore())返回玩家成绩score的字符串表示。
其中根据playerno的值绘出玩家的飞机标志数(应该有更好方法,但是没有想到 )当每次刷新绘图页面时,应使用GameCanvas的flushGraphics()将屏幕后台的缓冲区内的图像刷新到前台来(flushGraphics()应该写在render(){ }的最后)。
renderboss()方法重绘的是关尾的精灵cboss、相关信息等,与render()的区别在与于函数lm.paint(g,0,0),起始坐标是不可变的,即,关尾的地图背景是不可变的。实际上,renderboss()是完全不需要的,只要在render()函数中设置相关标志位就可以解决关尾的绘图问题。
|
根据游戏设定,当y1=-1000时,会出现如图4-8
所示的飞机(sboss),当玩家击落他后,屏幕会显示“
援军到达“,并且玩家剩余飞机数加1。
使用 if((y1==-1000)&&(sbz==0)){ }设定sboss的
初始位置,根据游戏设定,sboos从屏幕上方倒飞入屏幕,
所以sboss设定的初始位置(50,planepoup-65),其中
planepoup为屏幕上边缘的标志位。
|
|
的if(sbz==2)语句。
|
行。首先,根据玩家飞机的位置对sbmove赋值,当c1在sboos的上、下、左、右时,其对应的值为1、2、3、4在这4个if语句中,要设置标志位(smovebz==0)。设置这个
标志位的目的是防止sboos根据c1的位置不停的改变运行状态,即防止sboos成为跟踪飞机。当sboos根据c1的位置改变一次运行方向后,smovebz赋值为1,即不检测c1
的位置。只有sboss运行到屏幕的边缘时,才将smovebz重新赋值为0,使其可以再次通过c1的位置决定sboos的运行方向。
当玩家子弹击中sboss后,使用sboss.setFrame(1),此时飞机变红,在本次repaint结束前,使用sboss.setFrame(0)使飞机变为本来颜色,而程序设定每1/20秒画一次,由此得到飞机被击中后变色的效果。(参见图4-10)。
sboos会根据玩家飞机的位置发射子弹,根据游戏设置,当玩家在其上方、左方、右方时,sboss一次发射1发子弹,而玩家飞机在其下方时,sboss一次发射3发子弹。
sboss与cboss共用3发子弹,因为当sboss出现时,离关尾还远,所以,为了提高效率,采用这种方法。
如果sboos被击落后,使用函数setVisable(false)将bossbullet0、bossbullet1、bossbullet2设置为不见,使用sboss.setImage()函数将sboos的图片设置为爆炸图片。同时,玩家生命标志playerno加1,sbz赋值为-1,使得sboos无法发射子弹,sbpzbz赋值为1,使得玩家的子弹不与sboos进行碰撞检测。
同时在屏幕中使用drawString()绘制“援军到达”四个字,随着屏幕的运动,爆炸图片逐渐进入屏幕下方,当sboos.getY()>palnepo,通过改变标志位的值使得drawSteing()不在执行,四字消失。
如果玩家被击落后并没有点“返回“,而此时,背景会一直运动到关尾,考虑到其与关尾BOSS共用3发子弹,如sboos不消失,将会出现子弹乱飞的情况。所以,如果判断语句if((sboss.getY()==getHeight()))为真,则表示离地图的终点只有一个屏幕的距离时,sbz赋值为-1、sbpzbz赋值为1(含义上面已经说明)。同时调用sboss.move(0,-3),使sboos快速飞出屏幕,直到判断语句if(sboss.getY()<-65)为真时,调用下面的函数setVisable(false),使得sboos不可见。
普通敌人是指游戏中不断出现的兰色飞机。
首先在程序中首先定义了aik、aip两个Random()类对象,ai和aipp两个整型变量。程序中使用switch(ai)语句判断下一次的飞机出现情况,为了达到不重复出现的效果,使用语句ai=aik.nextInt()%4(同样应该在构造函数中放置此语句和aipp=aip.nextInt() % 5,以使每次游戏开始的时候敌人飞机的出现顺序是不固定的),以随机出现0,1,2,3四个整数(代表着飞机的四种出现情况)。
情况1:使用 aipp=aip.nextInt()%5取得随机数aipp,根据下面三条语句设置飞机的出现位置:j0.setPosition(100-aipp*30,planepoup+24);
j1.setPosition(100,planepoup);
j2.setPosition(100+aipp*30,planepoup-24);;
将getHeight()/8赋给整型变量kkk,每次循环kkk-1,当kkk<=1时飞机转向,当aipp>0时,飞机向左下方运行,使用语句setFrame(0)、move(-3,3)达成向左下方运动的效果。当aipp<0时,向右下方运动,实现方法同上。
情况2:初始位置设置方法同情况1。当kkk〈0时,飞机掉头向上飞,其中setFrame(3),
move(-4,0)。
情况3:初始位置设置方法同情况1。当kkk〈0时,飞机只向左转。设置情况3的原因是在更多的随机位置出现敌机。
情况4:初始位置设置方法同情况1。但其中的飞机j1具有跟踪能力,其实现方法如下:使用2个if语句if(j1.getX()<c1.getX())、if(j1.getX()>c1.getX())判断J1在c1的左或右侧,并且实时根据判断情况使用setFrame()和move()改变飞机的形态,使用语句
if((j1.getX()<c1.getX())&&((j1.getX()+48)>c1.getX())&&(j1.getY()<c1.getY()))判断c1是否在j1的下方,当c1在j1的下方时,发射子弹jbullet1。
以上四种情况的最后,都将使用函数nextInt()产生ai,aipp的值。
设置整型数组jb[4],对应着4种出现情况的标志位。
如,执行情况1,首先执行判断语句if(jb[0]==1),在此语句中,首先使用setVisable (true)函数将敌人飞机设置为可见的,并根据上次运行的qipp的值设置敌人飞机的初始位置最后,jb[0]赋值为2。
接着执行判断语句if(jb[0]==2),在此语句中,首先使用move()函数使飞机向下运动,同时kkk减1,当kkk<=0时,飞机转向,此时,根据aipp的正负判断飞机向哪边转向。当飞机飞出屏幕时,jb[0]赋值为3。
需要注意的一点是,当取得ai的数值时,一定要写上这条语句:jb[ai]=1;因为当4种情况都出现一便的时候,标志位jb[]里的数值都将变为3,如果不将其重新赋值为1,敌人飞机将只能出现4次。
其他3种情况也大致如此。
普通敌人是否发射子弹由以下语句if(((j1.getX()<=c1.getX()-18)||((j2.getX() -6)>=c1.getX()))&(jbz==0)),即c1在j1左侧18象素范围内或j2左侧6象素范围时,j0、j1、j2一起发射子弹,jbz=1,表示在这组子弹消失前敌人不发射子弹。
如果jpb的值为0,则判断语句if(jpb==0)里的move()语句将一直执行下去。
之后,还需要对每发子弹于玩家飞机进行碰撞检测,如果碰撞,则将碰撞的这发子弹设置为不可见。
为了游戏界面更加美观,程序中设定了精灵数组cloud[i]来表示白云,由于白云应该在所有飞机的上方,即cloud[i]应该最早被append()到LayerManager中,或者使用insert (cloud[],0)在索引数值0处插入Layer,本程序采用了第一种方法,即在gameScreen类的构造函数中按游戏设置的顺序使用lm.append()加入到LayerManager之中。
程序中设定白云数为5。首先设置白云的初始位置,其语句如下:
cloud[0].setPosition(25,planepoup-(65));
cloud[1].setPosition(80,planepoup-(140));
cloud[2].setPosition(112,planepoup-(90));
cloud[3].setPosition(175,planepoup-(200));
cloud[4].setPosition(223,planepoup-(70));
其原理为:将屏幕的X数轴和Y数轴各分成5份,即在X轴的5个范围内每个范围出现一朵白云,Y轴的每个范围内也只能出现一朵。所以的白云的起始位置在每次游戏开始时是固定的。
白云位置设定后,使用move(0,1)使白云移动,由于白云初始位置设定在屏幕的不同区域内,故其移动出屏幕的先后顺序是不同的,使用if(cloud[].getY()>planepo)判断白云是否飞出屏幕。如果为真则使用cloud[].setPosition(cloudposition*40,planepoup)设置白云的位置,其中,cloudposition=aicloud.nextInt()%5,aicloud为Random()类对象。乘以40表示其在X轴出现的范围是多少,cloud[0]、cloud[1]、cloud[2]、cloud[3]、 cloud[4]乘以的值分别为40,30、55、15、22。以达成白云的随机出现效果。
关尾处飞机在屏幕上方横向移动,而背景地图不动,所以使用renderboss()重绘屏幕,其中,paint(g,0,0)表示屏幕绘制点在坐标轴(0,0)处。BOSS生命进度条由以下语句绘制:
g.setColor(255,0,0);
g.fillRect(2,2,60,5);//生命进度条背静红
g.setColor(255,255,255);
g.fillRect(2,2,bosslife,5);//生命进度条前景白
其中bosslife记录着BOSS的生命值,其初始值为0,当玩家每击中一次BOSS,其值加5,,即化出白色进度条,当bosslife==60时,表示过关,除玩家飞机与子弹外的其他Sprite均使用setVisable(false)使其不在显示,同样的pzbz要赋予1,以消除玩家飞机还能与敌人碰撞的BUG。
如果cboss.getX()<0,则表示其在屏幕左方出界,应改为右飞。同理如果cboss.getX()
>(getWidth()-cboss.getWidth()),则表示其在屏幕右方出界,应改为左飞。
在飞机横向飞行中,使用以下语句判断飞机是否开火:
|
当每发一组子弹后,jbsz=1,则飞机无法开火,知道子弹
飞出屏幕,jbsz才重新设定为0。而((cboss.getX()<=
c1.getX()-10)||(cboss.getX()<=c1.getX()+60)表示当
玩家飞机处于BOSS的左右各10个象素的范围内时。BOSS
开火。关尾参见图4-10。
第四章中按照相应的步骤描述了所有关键类的具体算
|
理稍复杂的函数做了详细的分析。对游戏有关的各运行面也做了展示。
5 测试
使用Eclipse完成代码的调试之后,需要使用WTK生成包(即jar和jad文件),其过程如下:首先,使用WTK的新建项目功能建立一个新项目,要求与Eclipse工作区下的项目名称、MIDlet类名相同(图5-1)。
图5-1
新建项目后,将Eclipse工作区下的.java文件拷入src文件夹,将.class文件拷入class文件夹(需要新建),将pic文件夹拷入res文件夹,点击生成,如一切正常,将如图片5-2所示。
图5-2
之后,选择项目-包-生成包。如图5-3所示。
图5-3
生成的jar和jad文件存储在lzhhdm/bin目录下。
运行Motorola SDK v4.2 for J2ME,使用MOTOA760手机模拟器进行测试。参考图5-4。点击Lanuch按纽,进入如图5-5的界面,即可以进行游戏测试了。
图5-4
测试的过程,实际上就是找不同的同学玩这款游戏,以期望发现BUG。实际上,几乎每一版本都会产生很多BUG。
|
几乎每一个计算机程序都会有这样
那样的不足,尤其是未经过精心维护的
非商业软件。即使是作为操作系统的各
种版本的Windows也时常会发生许多类
型的错误和漏洞。本游戏程序对于初涉
此领域的毕业设计课题来说,尤其在开
始初期,感觉逻辑复杂,难以控制,因
此产生了大量BUG,其中一些BUG还没有
解决,所有发现的BUG如下:
1.玩家子弹出现顺序问题;
2.玩家飞机爆炸后仍然可以控制爆
炸图片;
3.当过关后仍然会发生碰撞;
|
家被击落,并且此为玩家的最后
一架飞机,虽然显示援军到达,但
|
5.有某组飞机在飞机被击中后仍能发射子弹;
6.sboos左侧子弹发射后不停在原位置刷新问题;
7. 游戏运行一段时间后变的很卡;
其中,1、2、3、4条BUG已经解决(方法已在第4章相关位置进行了说明)。4、5、6没有解决,其中,4、5、6三条如果有足够的时间可以解决。第7条以现有水平无法解决,因为出现这种情况的原因估计是内存的问题,而由于水平的关系,现在的代码变量过多,并且效率低下,有很多重复的地方,解决的方法只能是在水平提高的情况下重写整个程序。
经过汇总测试人员的建议,本游戏应该完善的功能如下
1. 由于每按一次开火,就会对玩家子弹的精灵数组进行一次循环检查,使得本运行效率就不高的KVM运行异常缓慢。即使刷屏没有间隔也不会提高速度;
2. 最好有接宝物的设置,增强可玩性;
3. 由于没由合适的图片,使得敌人设置单一;
4. 地图本应由外部文件读入,但设计初期并没有掌握这项技术;
5. 手机游戏最好只有一个类,而我却有4个;
7. 没有添加成绩记录,即英雄榜;
6. 最大的遗憾,敌人不够智能化。
希望不远的将来,我能够有能力对这个游戏进行重写,以解决BUG、完善功能。
6 总结
本程序设计实现了手机上以J2ME为平台的飞行射击游戏的开发,敌人运行的方式由程序进行控制,具有一定的可玩性和复杂性。经过了细心的调试和排错解决了绝大部分的问题。
但是我的水平实在有限,在第5章列举的众多BUG和遗憾就可以看出这一点来,但我相信,随着时间的推移,个人水平的增长,我一定会重写这个程序,使其更加完善,
这款游戏作为我的毕业设计,是本人目前做过的最有意义的程序,这期间对J2ME的学习,使我又回到了初学PASCAL的时候。
本科期间做过很多课程设计,大多规模很小。在数据库、各种应用软件和游戏中,我仍觉得对游戏的设计有极大的热情。因为其各个模块间的联系十分紧密,代码重复执行率高,当经过无数次的调试、修改后,能最终看到成品,有无比自豪的心情。大学期间做过图书馆管理程序等简单的数据库管理系统的课程设计,思想大致相似,变化范围有限,没有太多自己可发挥的余地。大家作品的最终结果都离不开同一个模式。相比一些数据库软件,游戏的设计有很多人情色彩和艺术思想的发挥,正式商业的软件的人性化界面和各个游戏间迥异的结构每每会让人有去开发的冲动。
学习J2ME的困难远远超出了想象,在设计初期,为了解决地图的滚动、玩家飞机如何不飞出上下边界两个问题竟然花费了数天的时间。很多相关的技术,如需要使用到的线程、Game包的新功能、高级、低级图形界面的使用、贴图等,每一项都需要花一定的时间去掌握和熟悉。更为困难的是,J2ME为一种刚出现仅几年的新技术,国内的参考资料非常少,仅有的几本也停留在简单的介绍阶段。台湾的王森写了本较好的书,给了我很大的帮助,但对设计该游戏来说,仍只够入门。
幸运的是,互联网上有那么多无私的人,由Jason Lam著,Deaboway Chou译的电子书《J2ME&Gaming》给了我很大的帮助,作者、译者的辛勤劳动的成果免费放在网上由大家下载,是开源精神的完美体现。
[1]Joshua Bloce著. 潘爱民译. Effective Java中文版.2004年7月第4版. 机械工业出版社.
[2]Bruce Eckel.侯捷译.Java编程思想. 2005年3月第1版. 机械工业出版社出版社
[3]王森著.Java手机/PDA程序设计入门.2005年2月第3版.电子工业出版社.
[4]James Keogh著.潘颖 王磊译.J2ME开发大全.2004年2月第1版清华大学出版社.
[5]Ian Sommerville著.程成等译.软件工程.2003年1月第1版.机械工业出版社.
[6]Jason Lam著.Deabo way Chou译 J2ME&Gaming 2004年末 www.jasonlanm604.com
[7]陈立伟 张克非 黎秀红著.精通JAVA手机游戏与应用程序设计.中国青年出版社.
2005年5月
[8]飞思科技产品研发中心.精通Jbuilder9.电子工业出版社.2004年
[9]微型爪哇人.Java手机程序开发.中国铁道出版社,2003年
[10]Frand M.Carrano,Janet J.Prichard .著 韩志宏 译.数据抽象和问题求解——
JAVA语言描述.清华大学出版社2005年4月
[11]袁海燕 王文涛著.JAVA实用程序设计100例.人民邮电出版社.2005年5月
[12]Jonathan Knudsen.What's New in the J2ME Wireless Toolkit 2. 22004年4月www.sun.com
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
import javax.microedition.lcdui.*;
public class lzhhdm extends MIDlet implements CommandListener {
public Display display;
private Image splashLogo;
private boolean isSplash=true;
public Form a;
private Alert alert;
int length;
private MenuScreen menuscreen;
private gameScreen gamescreen;
private Command ok,back;
private byte[] byteInputData;
public lzhhdm()
{
}
protected void startApp() throws MIDletStateChangeException {
display=Display.getDisplay(this);
menuscreen=new MenuScreen(this);
if(isSplash)
{ a=null;
a=new Form("览桥风光V1.0");
ok=new Command("ok",Command.OK,1);
a.append(new StringItem(null,"....."));
a.addCommand(ok);
a.setCommandListener(this);
display.setCurrent(a);
}
}
protected void menuscreenShow()
{
display.setCurrent(menuscreen);
}
protected void menuscreensecond() {
menuscreen=new MenuScreen(this);
MenuScreen.mainmenu[0]="重新开始";//玩完一遍或挂了后在玩一遍 菜单第一项改为 重新开始
display.setCurrent(menuscreen);
}
protected void pauseApp() {}
protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
}
protected void helShow()
{ a=null;
a=new Form("览桥风光V1.0");
back=new Command("返回",Command.BACK,1);
a.append(new StringItem(null,"操作方式:上 2 下 8 左 4 右 6 开火 5"));
a.append(new StringItem(null,"弹药数:一次最多打三组"));
a.addCommand(ok);
a.setCommandListener(this);
display.setCurrent(a);
}
protected void renwuShow()
{
a=null;
a=new Form("览桥风光V1.0");
back=new Command("返回",Command.BACK,1);
a.append(new StringItem(null,"游戏名称:览桥风光"));
a.append(new StringItem(null,"版本号:V1.00"));
a.append(new StringItem(null,"制作者:信息电子技术学院01计算机5班 刘泽华 学号:7"));
a.addCommand(ok);
a.setCommandListener(this);
display.setCurrent(a);
}
protected void gameShow()
{ try{
gamescreen=null;
gamescreen=new gameScreen(this);
gamescreen.start();
display.setCurrent(gamescreen);
gamescreen.conti=true;
}catch(Exception exp)
{
System.out.println("dfg");
}
}
public void commandAction(Command arg0, Displayable arg1) {
a=null;
this.menuscreenShow();
}
}
import javax.microedition.lcdui.*;
public class MenuScreen extends Canvas implements Runnable,CommandListener
{
Font lowfont=Font.getFont(Font.FACE_MONOSPACE,Font.STYLE_PLAIN,Font.SIZE_MEDIUM);
Font highfont=Font.getFont(Font.FACE_MONOSPACE,Font.STYLE_BOLD,Font.SIZE_MEDIUM);
int lowColor=0x000000FF;
int highColor=0x00FF0000;
int highBGColor=0x00CCCCCC;
int width;
boolean co;
int height;
int startHeight;
int spacing=highfont.getHeight()/2;
public static String[] mainmenu={"ÐÂÓÎÏ·","°ïÖú","¹ØÓÚ"};
int menuIndex;
Thread menuThread;
private Command ok=new Command("ok",Command.OK,1);
private lzhhdm midlet;
public MenuScreen(lzhhdm midlet)
{ this.midlet=midlet;
width=getWidth();
height=getHeight();
startHeight=(highfont.getHeight()*mainmenu.length)+((mainmenu.length-1)*spacing);
startHeight=(height-startHeight)/2;
menuIndex=0;
addCommand(ok);
setCommandListener(this);
menuThread =new Thread(this);
menuThread.start();
co=true;
}
public void run()
{
while(co)
{
repaint();
}
}
public void paint(Graphics g)
{
g.setColor(0x00FFFFFF);
g.fillRect(0,0,width,height);
for(int i=0;i<mainmenu.length;i++)
{
if(i==menuIndex)
{
g.setColor(highBGColor);
g.fillRect(0,startHeight+(i*highfont.getHeight())+spacing,width,highfont.getHeight());
g.setFont(highfont);
g.setColor(highColor); g.drawString(mainmenu[i],(width-highfont.stringWidth(mainmenu[i]))/2,startHeight+(i*highfont.getHeight())+spacing,20);
}else
{
g.setFont(lowfont);
g.setColor(lowColor);
g.drawString(mainmenu[i],(width-lowfont.stringWidth(mainmenu[i]))/2,startHeight+(i*highfont.getHeight())+spacing,20);
}
}
}
public void keyPressed(int code)
{
if(getGameAction(code)==Canvas.UP&&menuIndex-1>=0)
{
menuIndex--;
}
else if(getGameAction(code)==Canvas.DOWN&&menuIndex+1<mainmenu.length)
{
menuIndex++;
}
}
public void commandAction(Command c,Displayable d)
{
if (c==ok)
{
switch(menuIndex)
{
case 0:
co=false;
midlet.gameShow();
break;
case 1:
midlet.helShow();
break;
case 2:
midlet.renwuShow();
break;
}
}
}
}
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
public class mybullets extends Sprite
{ public int score=0;
private int[][] bullets;
private int bulletstotal;
public int width,bulletheight;
public int no=0;
public mybullets(Image img,int picwidth,int picheight,int bulletstotal,int width,int height)
{
super(img,picwidth,picheight);
this.bulletstotal=bulletstotal;
bullets=new int[bulletstotal][4];
this.width=width;
this.bulletheight=height/7;
}
public void initBullets(int i)
{
bullets[i][3]=1;
bullets[i][2]=0;
}
public void updata(int i)
{
bullets[i][1]+=bullets[i][2];
}
public void setfirstposition(int x,int y,int nof,Sprite sprite[],Image img)
{
sprite[nof].setVisible(true);
sprite[nof+1].setVisible(true);
sprite[nof+2].setVisible(true);
bullets[nof][0]=x+10;
bullets[nof][1]=y-24;
sprite[nof].setImage(img,6,6);
bullets[nof+1][1]=bullets[nof][1]+10;
bullets[nof+1 ][0]=x+10;
sprite[nof+1].setImage(img,6,6);
bullets[nof+2][1]=bullets[nof+1][1]+10;
bullets[nof+2 ][0]=x+10;
sprite[nof+2].setImage(img,6,6);
}
public void newposition(Sprite sprite[],int i,int v,Sprite jp0,Sprite jp1,Sprite jp2,Sprite boss,Image img)
{
bullets[i][2]-=5;
sprite[i].setPosition(bullets[i][0],bullets[i][1]+bullets[i][2]);
sprite[i+1].setPosition(bullets[i][0],bullets[i+1][1]+bullets[i][2]);
sprite[i+2].setPosition(bullets[i][0],bullets[i+2][1]+bullets[i][2]);
if (sprite[i].collidesWith(jp0,true))
{
sprite[i].setImage(img,32,32);
sprite[i+1].setImage(img,32,32);
sprite[i+2].setImage(img,32,32);
sprite[i].setFrame(1);
sprite[i+1].setFrame(1);
sprite[i+2].setFrame(2);
bullets[i][3]=0;
bullets[i+1][3]=0;
bullets[i+2][3]=0;
jp0.setVisible(false);
no=1;
score=score+1;
}
if (sprite[i].collidesWith(jp1,true))
{
sprite[i].setImage(img,32,32);
sprite[i+1].setImage(img,32,32);
sprite[i+2].setImage(img,32,32);
sprite[i].setFrame(1);
sprite[i+1].setFrame(1);
sprite[i+2].setFrame(2);
bullets[i][3]=0;
bullets[i+1][3]=0;
bullets[i+2][3]=0;
jp1.setVisible(false);
no=1;
score=score+1;
}
if (sprite[i].collidesWith(jp2,true))
{
sprite[i].setImage(img,32,32);
sprite[i+1].setImage(img,32,32);
sprite[i+2].setImage(img,32,32);
sprite[i].setFrame(1);
sprite[i+1].setFrame(1);
sprite[i+2].setFrame(2);
bullets[i][3]=0;
bullets[i+1][3]=0;
bullets[i+2][3]=0;
jp2.setVisible(false);
no=1;
score=score+1;
}
}
public boolean isAlive(int i)
{
if (bullets[i][3]==1) return true;
else return false;
}
public void setAlive(int i)
{
for (int z=i;z<i+3;z++)
{
bullets[i][3]=0;
}
}
public int rscore()
{
return score;
}
public void clean(int i,Sprite sprite[])
{
for (int z=i;z<i+3;z++)
{
//sprite[z].setPosition(0,-1500);
sprite[z].setVisible(false);
no=1;
}}}
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
import java.util.*;
public class gameScreen extends GameCanvas implements Runnable,CommandListener
{ private Form al;
public LayerManager lm,lm1;
TiledLayer b1;
int height=getHeight();
int bosscolor=0;
int sbosscolor=0;
int sbz=0;
int sbmove=0;
int sfire=0;
int smovebz=0;
int sbpzbz=0;
int slife=0;
int sbo=0;
int drawslife=0;
int lr=0;
int drawadd=0;
int pzbz=0;
int pzbzover=0;
int playlife=-1;
int bosslife=0;
int inputno=0;
int position=0;
int j1b=0;
int jbsz=-1;
int sbsz0=-1;
int sbsz1=-1;
int sbsz2=-1;
int sbsz3=-1;
int planepo;
int planepoup;
int kkk;
int gz=0;
int gzks=0;
int jiangli=0;
int jplaneno;
int cloundno=0;
int right=0;
int left=1;
int boss=0;
int over=0;
int ai=0;
int planert=-1;
int supermen=0;
int overcmd=0;
int bossover=0;
int jpb=-12;
int aipp=0;
int jbz=0;
int cloudposition;
Random aik=new Random();
Random aip=new Random();
Random aicloud=new Random();
int jb[]=new int[5];
int playerno=3;
private mybullets[] huokebullet=new mybullets[9];
privateSpritejbullet0,jbullet1,jbullet2,bossbullet0,bossbullet1,bossbullet2;
private Sprite cloud[]=new Sprite[5];
private MenuScreen ms;
privateSpritec1,sboss,cboss,j0,j1,j2,boss1; private lzhhdm midlet;
int s1=0;int s2=65;int s3=0;int row2;
int row;int planecolor=0;
int planecoco=0;public int by1;
public int y1;
public gameScreen (lzhhdm midlet)
{ super(true);this.midlet=midlet;
addCommand(new Command("暂停",Command.BACK,1));
setCommandListener(this);
lm=new LayerManager();
c1=new Sprite(img("/pic/MyPlaneFrames.png"),24,24);
cboss=newSprite(img("/pic/boss.png"),65,50);jbullet0=newSprite(img("/pic/bullet.png"),6,6);
jbullet1=new Sprite(img("/pic/bullet.png"),6,6);
jbullet2=new Sprite(img("/pic/bullet.png"),6,6);
bossbullet0=new Sprite(img("/pic/bullet.png"),6,6);
bossbullet1=new Sprite(img("/pic/bullet.png"),6,6);
bossbullet2=new Sprite(img("/pic/bullet.png"),6,6);
sboss=new Sprite(img("/pic/smallboss.png"),65,50);
b1=createBackGround(); c1.setPosition(getWidth()/2,row2+getHeight()-25);
System.out.println("ffffffffffff");
planepoup=row2;
planepo=row2+getHeight();
j0=new Sprite(img("/pic/jplane2.png"),24,22);
j1=new Sprite(img("/pic/jplane2.png"),24,22);
j2=new Sprite(img("/pic/jplane2.png"),24,22);
kkk=getHeight()/8;
lm.append(j0);
lm.append(j1);
lm.append(j2);
cboss.setVisible(false);
aipp=aip.nextInt()%3;
if(aipp==0)
{aipp=aip.nextInt()%3;
}lm.append(cboss);
lm.append(bossbullet0);
lm.append(bossbullet1);
lm.append(bossbullet2);
lm.append(jbullet0);
lm.append(jbullet1);
lm.append(jbullet2);
lm.append(sboss);
jb[0]=1;jb[1]=1;jb[2]=1;jb[3]=1;
try{for(int i=0;i<=4;i++){
cloud[i]=new Sprite(img("/pic/cloud1.png"),16,16);
lm.append(cloud[i]);}
}catch(Exception e)
{System.out.println("cloud");}
try{for(int i=0;i<9;i++){
huokebullet[i]=playerbullet("/pic/bullet.png");}}catch(Exception e){}
try{for(int i=0;i<=8;i=i+3)
{huokebullet[i].no=1;//ok
huokebullet[i].score=0;}
}catch(Exception e){System.out.println("ffffffffffffff");}
for(int i=0;i<9;i++){
lm.append(huokebullet[i]);}
lm.append(c1);lm.append(b1);
}
private Image img (String pic)
{
Image img=null;
try{ img=Image.createImage(pic);
}catch(Exception exp)
{System.out.println(exp);
}return img;
}
private mybullets playerbullet(String pic)
{Image img=null;
try{ img=Image.createImage(pic);
}catch(Exception exp)
{System.out.println(exp);
}return new mybullets(img,6,6,21,getWidth(),getHeight());}
public TiledLayer createBackGround()
{Image img=null;
try{ img=Image.createImage("/pic/beijing.png");
}catch(Exception exp)
{System.out.println("layer create image");
}
TiledLayer tiledLayer=new TiledLayer(50,200,img,16,16);
int[] map1[]{ 地图};
for(int i=0;i<map1.length;i++)
{ int column=i%15;
row=(i-column)/15; tiledLayer.setCell(column,row,map1[i]);}
row2=(row+1)*16-getHeight();
y1=-row2;
return tiledLayer;}
boolean conti=true;
int rate=50;
public void run()
{ long st=0;
long et=0;
Graphics g=getGraphics();
int l=1350;
while(conti){
st=System.currentTimeMillis();
input();
if(huokebullet[0].isAlive(0)){
huokebullet[0].no--;
if(huokebullet[0].no>0){ huokebullet[0].newposition(huokebullet,0,3,j0,j1,j2,cboss,img("/pic/explosion.png")); }
if(huokebullet[0].no<=0){
huokebullet[0].setAlive(0); huokebullet[0].clean(0,huokebullet);
}} if(huokebullet[3].isAlive(3))
{huokebullet[3].no--;
if(huokebullet[3].no>0)
{ huokebullet[3].newposition(huokebullet,3,3,j0,j1,j2,cboss,img("/pic/explosion.png")); }
if(huokebullet[3].no<=0)
{huokebullet[3].setAlive(3);
huokebullet[3].clean(3,huokebullet);
}} if(huokebullet[6].isAlive(6))
{huokebullet[6].no--;
if(huokebullet[6].no>0){ huokebullet[6].newposition(huokebullet,6,3,j0,j1,j2,cboss,img("/pic/explosion.png")); }
if(huokebullet[6].no<=0){huokebullet[6].setAlive(6);
huokebullet[6].clean(6,huokebullet);} }if (huokebullet[0].collidesWith(cboss,true))
{huokebullet[0].setVisible(false); huokebullet[1].setVisible(false); huokebullet[2].setVisible(false);
cboss.setFrame(1);
if(bosslife<=55)
{bosslife=bosslife+5;}
huokebullet[0].setAlive(0); huokebullet[0].clean(0,huokebullet);} if(huokebullet[0].collidesWith(sboss,true)&&(sbpzbz==0)){ huokebullet[0].setVisible(false);
huokebullet[1].setVisible(false); huokebullet[2].setVisible(false);
sboss.setFrame(1);
if(slife<=75){slife=slife+5;}
huokebullet[0].setAlive(0); huokebullet[0].clean(0,huokebullet);if(huokebullet[3].collidesWith(cboss,true)){cboss.setFrame(1); huokebullet[3].setVisible(false);
huokebullet[4].setVisible(false); huokebullet[5].setVisible(false);
huokebullet[3].no=0;
if(bosslife<=55){
bosslife=bosslife+5;}
huokebullet[3].setAlive(3); huokebullet[3].clean(3,huokebullet);}
if(huokebullet[3].collidesWith(sboss,true)&&(sbpzbz==0)){sboss.setFrame(1);
huokebullet[3].setVisible(false);
huokebullet[4].setVisible(false);
huokebullet[5].setVisible(false);
huokebullet[3].no=1;
if(slife<=75){slife=slife+5;}
huokebullet[3].setAlive(3); huokebullet[3].clean(3,huokebullet);}
if(huokebullet[6].collidesWith(cboss,true)){ huokebullet[6].setVisible(false); huokebullet[7].setVisible(false);
huokebullet[8].setVisible(false);
cboss.setFrame(1);
if(bosslife<=55){ bosslife=bosslife+5;
}huokebullet[6].setAlive(6); huokebullet[6].clean(6,huokebullet);
}if(huokebullet[6].collidesWith(sboss,true)&&(sbpzbz==0)){sboss.setFrame(1);
huokebullet[6].setVisible(false); huokebullet[7].setVisible(false); huokebullet[8].setVisible(false);
if(slife<=75){slife=slife+5;}
huokebullet[6].setAlive(6); huokebullet[6].clean(6,huokebullet);;}if((y1>-1350)&&(pzbzover==0))
{
switch (ai){
case 0:
if(jb[0]==1){
jbz=0;
j0.setVisible(true);
j1.setVisible(true);
j2.setVisible(true);
j1.setFrame(2);
j0.setFrame(2);
j2.setFrame(2); j0.setPosition(100-aipp*30,planepoup+24); j1.setPosition(100,planepoup); j2.setPosition(100+aipp*30,planepoup-24);jb[0]=2;}
if (jb[0]==2){ j0.move(0,3);
j1.move(0,3);j2.move(0,3);
kkk=kkk-1; }
if(kkk<=0){jb[0]=3;
if(aipp>=0){
j1.setFrame(0); j0.setFrame(0);j2.setFrame(0); j0.move(-3,3);j1.move(-3,3); j2.move(-3,3);}else if(aipp<0)
{j1.setFrame(1); j0.setFrame(1); j2.setFrame(1);j0.move(3,3);j1.move(3,3);
j2.move(3,3);}
if(j2.getY()>planepo) {
j0.setVisible(false);j1.setVisible(false);
j2.setVisible(false);jbullet0.setVisible(false);jbullet1.setVisible(false); jbullet2.setVisible(false);jpb=-1;
ai=aik.nextInt()%4; aipp=aip.nextInt()%5;
if(aipp==0){aipp=aip.nextInt()%5;
}if(ai<0) ai=ai*(-1);
jb[ai]=1; kkk=getHeight()/8;}} if((j1.getX()<=c1.getX()-18)&(jbz==0))
{ jpb=0;if(j0.isVisible()){
jbullet0.setVisible(true);}
if(j1.isVisible()){
jbullet1.setVisible(true);}
if(j2.isVisible()){
jbullet2.setVisible(true);} jbullet0.setPosition(j0.getX()+12,j0.getY()+30);jbullet1.setPosition(j1.getX()+12,j1.getY()+30);jbullet2.setPosition(j2.getX()+12,j2.getY()+30);jbz=1;}break; case 1:
if(jb[1]==1){j0.setVisible(true);
j1.setVisible(true);j2.setVisible(true);
j1.setFrame(2); j0.setFrame(2);
j2.setFrame(2);j0.setPosition(100-aipp*30,planepoup+24);j1.setPosition(100,planepoup);j2.setPosition(100+aipp*30,planepoup-24);;jb[1]=2;}
if (jb[1]==2){ j0.move(0,3);
j1.move(0,3); j2.move(0,3);
kkk=kkk-1;}
if(kkk<0){jb[1]=3;}
if(jb[1]==3){ jpb=1;j1.setFrame(3);
j0.setFrame(3);j2.setFrame(3);
jb[1]=4;}
if(jb[1]==4){j0.move(0,-4);
j1.move(0,-4); j2.move(0,-4);
if(j2.getY()<planepoup) {j0.setVisible(false);
j1.setVisible(false);j2.setVisible(false);
jbullet0.setVisible(false); jbullet1.setVisible(false);jpb=-1; jbullet2.setVisible(false); ai=aik.nextInt()%4; if(ai<0) ai=ai*(-1);jb[ai]=1;
aipp=aip.nextInt()%5; if(aipp==0){ aipp=aip.nextInt()%5;}
kkk=getHeight()/8; }}break;
case 2: if(jb[2]==1){
jbz=0;j0.setVisible(true);
j1.setVisible(true);j2.setVisible(true);
j1.setFrame(2);j0.setFrame(2);j2.setFrame(2); j0.setPosition(100-aipp*30,planepoup+aipp*30);j1.setPosition(100,planepoup); j2.setPosition(100+aipp*30,planepoup+aipp*30);;
jb[2]=2;} if (jb[2]==2){
j0.move(0,3);j1.move(0,3);
j2.move(0,3);kkk=kkk-1;
}if(kkk<=0){jb[2]=3;
j1.setFrame(0);j0.setFrame(0);j2.setFrame(0);j0.move(-3,3);j1.move(-3,3);
j2.move(-3,3); if(j2.getY()>planepo) {jpb=-1;
jbullet0.setVisible(false);
jbullet1.setVisible(false);
jbullet2.setVisible(false);
j0.setVisible(false); j1.setVisible(false);
j2.setVisible(false); ai=aik.nextInt()%4;
if(ai<0) ai=ai*(-1); jb[ai]=1; aipp=aip.nextInt()%5; if(aipp==0)
{aipp=aip.nextInt()%5; }
kkk=getHeight()/8; }
} if(((j1.getX()<=c1.getX()-18)||((j2.getX()-6)>=c1.getX()))&(jbz==0))
{if(j0.isVisible()){jbullet0.setVisible(true);} if(j1.isVisible()){
jbullet1.setVisible(true);}
if(j2.isVisible()) {
jbullet2.setVisible(true);}
jpb=0; jbullet0.setPosition(j0.getX()+12,j0.getY()+30); jbullet1.setPosition(j1.getX()+12,j1.getY()+30);
jbullet2.setPosition(j2.getX()+12,j2.getY()+30);jbz=1;}
break;
case 3:
if(jb[3]==1){jbz=0; j0.setVisible(true);j1.setVisible(true);
j2.setVisible(true);j1.setFrame(2);
j0.setFrame(2);j2.setFrame(2); j0.setPosition(200-aipp*50,planepoup-aipp*10);
j1.setPosition(100,planepoup); j2.setPosition(100+aipp*50,planepoup-aipp*10); jb[3]=2; }
if (jb[3]==2){ j0.move(0,3);
j1.move(0,3);j2.move(0,3);
if(gzks==0){if(j1.getX()<c1.getX())
{j1.move(2,2);j1.setFrame(1);}
if(j1.getX()>c1.getX()){ j1.setFrame(0);
j1.move(-2,2);} if((j1.getX()<c1.getX())&&((j1.getX()+48)>c1.getX())&&(j1.getY()<c1.getY()))
{j1.setFrame(2);j1.move(0,2); if(j1b==0){ jbullet1.setPosition(j1.getX()+12,j1.getY()+30);j1b=1;}}
j0.move(0,3);jbullet1.move(0,3);
j1.move(0,3);j2.move(0,3);} if((j2.getY()>(planepo+22))&&(j0.getY()>(planepo+22))&&((j1.getY()<(planepoup-22))||(j1.getY()>(planepo+22)))){
jb[3]=1;
gz=0;gzks=0;jbullet1.setVisible(false);
jbullet2.setVisible(false); j0.setVisible(false);
j1.setVisible(false);
j2.setVisible(false);
ai=aik.nextInt()%4; if(ai<0)
ai=ai*(-1);
jb[ai]=1; aipp=aip.nextInt()%5;
if(aipp==0) {aipp=aip.nextInt()%5;}
kkk=getHeight()/8; }
if((j2.getX()<=c1.getX()-18)&(jbz==0))/
{ jpb=0; if(j0.isVisible())
{jbullet0.setVisible(true);}
if(j2.isVisible()){jbullet2.setVisible(true);
}jbullet0.setPosition(j0.getX()+12,j0.getY()+30);jbullet2.setPosition(j2.getX()+12,j2.getY()+30); jbz=1; }} break;}}if(jpb==0)
{jbullet0.move(0,5); jbullet1.move(0,5);
jbullet2.move(0,5); }
if((jbullet0.collidesWith(c1,true)||jbullet1.collidesWith(c1,true)||jbullet2.collidesWith(c1,true)||bossbullet0.collidesWith(c1,true)||bossbullet1.collidesWith(c1,true)||bossbullet2.collidesWith(c1,true))&&(pzbz==0)){
c1.setImage(img("/pic/explosion.png"),32,32); c1.setFrame(3); if((playerno>0))
{playerno=playerno-1; planert=1;
}else{pzbz=1; overcmd=1;
over=1;}} if((j0.collidesWith(c1,true)&&(pzbz==0))) {
c1.setImage(img("/pic/explosion.png"),32,32); c1.setFrame(3); if(playerno>0)
{playerno=playerno-1; planert=1;
playlife=0; }else{overcmd=1; playlife=1;
over=1;} j0.setVisible(false); pzbz=1;
}if((j1.collidesWith(c1,true)&&(pzbz==0))) {c1.setImage(img("/pic/explosion.png"),32,32); c1.setFrame(3); if(playerno>0)
{playerno=playerno-1; planert=1;
playlife=0; }else{overcmd=1;
playlife=1; over=1;} j1.setVisible(false);
pzbz=1; if((j2.collidesWith(c1,true)&&(pzbz==0)))
{c1.setImage(img("/pic/explosion.png"),32,32); c1.setFrame(3);
if(playerno>0) {
playerno=playerno-1; planert=1;
playlife=0; }else{playlife=1;
if(overcmd==0) {}overcmd=1;
} over=1;} j2.setVisible(false);
pzbz=1; }if(overcmd==1)
{addCommand(new Command("返回",Command.OK,1)); overcmd=2;
if(boss==1) {cboss.setVisible(true);
if(cboss.getY()<25) {
cboss.move(0,3);
}else lr=1;
if(lr==1)
{if(cboss.getX()<0)
{
right=0;
left=1; }
else if(cboss.getX()>getWidth()-cboss.getWidth())
{left=0; right=1; }
if(right==0) {cboss.move(3,0); }
else if(left==0) {cboss.move(-3,0);} }
if(((cboss.getX()<=c1.getX()-10)||(cboss.getX()<=c1.getX()+60))&&(jbsz==0)) {bossbullet0.setPosition(cboss.getX()+6,cboss.getY()+40); bossbullet1.setPosition(cboss.getX()+30,cboss.getY()+52); bossbullet2.setPosition(cboss.getX()+54,cboss.getY()+40); jbsz=1; }if(jbsz==1){
bossbullet0.setVisible(true);
bossbullet1.setVisible(true);
bossbullet2.setVisible(true);
bossbullet0.move(0,5);
bossbullet1.move(0,5);
bossbullet2.move(0,5); }
if(bossbullet2.getY()>getHeight()){jbsz=0;}}
if(bosslife==60){ cboss.setVisible(false);
j1.setVisible(false);j0.setVisible(false);
j2.setVisible(false);bossbullet0.setVisible(false);bossbullet1.setVisible(false);bossbullet2.setVisible(false);jbullet0.setVisible(false);jbullet1.setVisible(false);jbullet2.setVisible(false);
pzbz=1;bossover=1;boss=2;bosslife=65;
pzbzover=1; addCommand(new Command("返回",Command.OK,1));
}if(bosslife==45)
{bosscolor=1; }
if(slife==65)
{sbosscolor=1;}
if(y1<0){ render(g);
y1=y1+1;planepoup=planepoup-1;
planepo=planepo-1;c1.move(0,-1); }
if (y1>=0 ) { if(boss==0)
{jbsz=0; boss=1; planepoup=0;
planepo=getHeight();cboss.setPosition(80,-60); }renderboss(g); } if(bosscolor==0)
cboss.setFrame(0); else cboss.setFrame(1);
if(sbosscolor==0) sboss.setFrame(0);
else sboss.setFrame(1); if(cloundno==0)
{ cloud[0].setPosition(25,planepoup-(65));
cloud[1].setPosition(80,planepoup-(140);
cloud[2].setPosition(112,planepoup-(90)); cloud[3].setPosition(175,planepoup-(200)); cloud[4].setPosition(223,planepoup-(70));
cloundno=1; } cloud[0].move(0,1);
cloud[1].move(0,1); cloud[2].move(0,1);
cloud[3].move(0,1);
cloud[4].move(0,1);
if(cloud[0].getY()>planepo){
cloudposition=aicloud.nextInt()%5;
if(cloudposition<0) { cloudposition=cloudposition*(-1);
} cloudposition=cloudposition+1;
cloud[0].setPosition(cloudposition*40,planepoup); }if(cloud[1].getY()>planepo){
cloudposition=aicloud.nextInt()%5;
if(cloudposition<0) { cloudposition=cloudposition*(-1);
} cloudposition=cloudposition+1;
cloud[1].setPosition(cloudposition*30,planepoup); }if(cloud[2].getY()>planepo){
cloudposition=aicloud.nextInt()%5;
if(cloudposition<0) {coudposition=cloudposition*(-1);
}
cloudposition=cloudposition+1;
cloud[2].setPosition(cloudposition*55,planepoup); }if(cloud[3].getY()>planepo){
cloudposition=aicloud.nextInt()%5;
if(cloudposition<0) {cloudposition=cloudposition*(-1);
}cloudposition=cloudposition+1;
cloud[03].setPosition(cloudposition*15,planepoup); } if(cloud[4].getY()>planepo){
cloudposition=aicloud.nextInt()%5;
if(cloudposition<0) {cloudposition=cloudposition*(-1); }
cloudposition=cloudposition+1; cloud[4].setPosition(cloudposition*22,planepoup); }if((y1==-1000)&&(sbz==0))
{sbsz0=0; sbsz1=0; sbsz2=0; sbsz3=0;
drawslife=1; sboss.setVisible(true);
sboss.setPosition(50,planepoup-65);
sbz=1; }if(sbz==1) {sboss.move(0,3);
if(sboss.getY()>planepoup) {sbz=2;
}}
if(sbz==2) {if(((sboss.getY()-50)<c1.getY())&&(smovebz==0)) { sbmove=1;//上移}
if(((sboss.getX()+30)<c1.getX())&&(smovebz==0)) {sbmove=4;//右移}
if(((sboss.getY()+50)<c1.getY())&&(smovebz==0)) {sbmove=2; }
if(((sboss.getX()-30)>c1.getX())&&(smovebz==0)) {sbmove=3; }if(sbmove==1)//上移
{ smovebz=1; sboss.move(0,-2); }
if(sbmove==2){smovebz=1; sboss.move(0,2);
}if(sbmove==3) {sboss.move(-2,0); }
if(sbmove==4)
{smovebz=1; sboss.move(2,0); }
if(sboss.getY()<planepoup) {
sboss.setPosition(sboss.getX(),planepoup); smovebz=0; }
if(sboss.getY()>(planepo-65)){ sboss.setPosition(sboss.getX(),planepo-65);
smovebz=0; }if(sboss.getX()<0) {sboss.setPosition(0,sboss.getY());smovebz=0; }if(sboss.getX()>(getWidth()-65)) { sboss.setPosition(getWidth()-65,sboss.getY()); smovebz=0; } if(((sboss.getX()+40)<c1.getX())&&(sboss.getY()<c1.getY())&&((sboss.getY()+65)>c1.getY())&&(sbsz0==0))
{bossbullet0.setVisible(true);
bossbullet0.setPosition(sboss.getX()+45,sbos.
getY()+35); sbsz0=1;
}
if(sbsz0==1)
{bossbullet0.move(3,0); if(bossbullet0.getX()>getWidth()) {
sbsz0=0; } }if((sboss.getX()>c1.getX())&&((sboss.getY()+65)>c1.getY())&&(sbsz1==0))
{bossbullet1.setPosition(sboss.getX()+10,sboss.getY()+35); sbsz1=1; }
if(sbsz1==1) { bossbullet1.move(-3,0);
if(bossbullet1.getX()<0) {
bossbullet1.setVisible(false); sbsz1=0;}} if((sboss.getX()<c1.getX())&&((sboss.getX()+50)>(c1.getX()))&&(sboss.getY()>c1.getY())&&(sbsz2==0)) {
bossbullet2.setVisible(true);
bossbullet2.setPosition(sboss.getX()+25,sboss.getY());sbsz2=1; }if(sbsz2==1)
{bossbullet2.move(0,-4); if(bossbullet2.getY()<planepoup)
{bossbullet2.setVisible(false); sbsz2=0;
} }if((sboss.getX()<c1.getX())&&((sboss.getX()+50)>(c1.getX()))&&(sboss.getY()<c1.getY())&&(sbsz3==0))
{bossbullet0.setVisible(true); bossbullet1.setVisible(true); bossbullet2.setVisible(true);
bossbullet2.setPosition(sboss.getX()+10,sboss.getY()+25); bossbullet1.setPosition(sboss.getX()+30,sboss.getY()+50);
bossbullet0.setPosition(sboss.getX()+55,sboss.getY()+25); sbsz3=1; }
if(sbsz3==1) {bossbullet0.move(0,4);
bossbullet1.move(0,4); bossbullet2.move(0,4);
if(bossbullet0.getY()>planepo)
{
bossbullet0.setVisible(false);
bossbullet1.setVisible(false);
bossbullet2.setVisible(false); sbsz3=0;
}}} if((slife==80)) {
sboss.setImage(img("/pic/explosion.png"),32,32);sboss.setFrame(3);bossbullet0.setVisible(false); bossbullet1.setVisible(false);
bossbullet2.setVisible(false); jiangli=11;
slife=85; drawslife=0;
playerno=playerno+1; sbz=-1;//sbos sbpzbz=1; }
if((sboss.getY()==getHeight())){
sbz=-1; sbpzbz=1; drawadd=21;
}if(drawadd==21) {sboss.move(0,-3);
if(sboss.getY()<-65) {
sboss.setVisible(false); drawadd=31;
}}if(planert==1) {inputno=1;
pzbz=1; s2=65; c1.setImage(img("/pic/MyPlaneFrames.png"),24,24); c1.setFrame(0); c1.setVisible(true);
c1.setPosition(getWidth()/2,planepo+48);
planert=2;
}if(planert==2)
{c1.move(0,-2); if(c1.getY()<(planepo-24))
{System.out.println(c1.getY());
System.out.println(planepo-24);inputno=0;
s1=1;
planert=3; }if(c1.getY()>(planepo+24))
{ c1.move(0,-2); }}
et=System.currentTimeMillis();
if((et-st)<rate){try{ Thread.sleep(rate-(et-st));
}catch(Exception exp){} }
}}public void render(Graphics g)
{ g.setColor(255,255,255);
g.fillRect(0,0,getWidth(),getHeight());
lm.setViewWindow(0,0,getWidth(),getHeight()+10000); lm.paint(g,0,y1); if(over==1)
{ g.drawString("寒在杭州览桥上空因座机被击中,壮烈殉国,年",c1.getWidth()-24,60,0); g.drawString("1937年 8月14日,空军第4大队少尉飞行员刘思",c1.getWidth()-24,40,0); g.drawString("仅21岁",c1.getWidth()-24,80,0); inputno=1;
}g.drawString("37年8月14日 杭州览桥 战果:"+String.valueOf(huokebullet[0].rscore()+huokebullet[3].rscore()+huokebullet[6].rscore()),c1.getWidth()-24,c1.getHeight()-20,0);//
if(drawslife==1) {g.setColor(255,0,0);
g.fillRect(2,22,80,5);g.setColor(255,255,255);
g.fillRect(2,22,slife,5); }
if(playerno==3) {g.drawImage(img("/pic/playerbiaozhi.png"),170,4,0); g.drawImage(img("/pic/playerbiaozhi.png"),195,4,0); g.drawImage(img("/pic/playerbiaozhi.png"),220,4,0); }if(playerno==2)
{g.drawImage(img("/pic/playerbiaozhi.png"),195,4,0);
g.drawImage(img("/pic/playerbiaozhi.png"),220,4,0); }if(playerno==1) {
g.drawImage(img("/pic/playerbiaozhi.png"),220,4,0); }if(playerno==4)
{g.drawImage(img("/pic/playerbiaozhi.png"),145,4,0);
g.drawImage(img("/pic/playerbiaozhi.png"),170,4,0);
g.drawImage(img("/pic/playerbiaozhi.png"),195,4,0);
g.drawImage(img("/pic/playerbiaozhi.png"),220,4,0);g.setColor(255,0,0); }
if(jiangli==11){g.setColor(255,0,0);
g.drawString("援军到达",100,150,0);
if(sboss.getY()>planepo) {jiangli=20;
}} if(s1==1) {g.setColor(255,255,255);
g.fillRect(170,22,65,5); g.setColor(255,0,0);
g.fillRect(170,22,s2,5); g.drawString("无敌时间",124,18,0); drawadd=1; s2=s2-1;
if(s2==0) {pzbz=0; s1=2; }}
flushGraphics();}public void renderboss(Graphics g) {lm.setViewWindow(0,0,getWidth(),getHeight()); lm.paint(g,0,0); if(over==1)
{//c1.getHeight(),0); g.drawString("1937年 8月14日,空军第4大队少尉飞行员刘思",c1.getWidth()-24,40,0);
g.drawString("仅21岁",c1.getWidth()-24,80,0); inputno=1;
} g.setColor(255,0,0); g.fillRect(2,2,60,5);//
g.setColor(255,255,255); g.fillRect(2,2,bosslife,5);//
if((bossover==1)&&(c1.isVisible()))
{g.drawString("此处加览桥空战真实历史战果",c1.getWidth()-24,40,0); g.drawString("此处加览桥空战真实历史战果",c1.getWidth()-24,60,0); g.drawString("仅21岁",c1.getWidth()-24,80,0); }
if(playerno==4) {g.drawImage(img("/pic/playerbiaozhi.png"),145,4,0); g.drawImage(img("/pic/playerbiaozhi.png"),170,4,0); g.drawImage(img("/pic/playerbiaozhi.png"),195,4,0); g.drawImage(img("/pic/playerbiaozhi.png"),220,4,0);} if(playerno==3) {
g.drawImage(img("/pic/playerbiaozhi.png"),170,4,0);
g.drawImage(img("/pic/playerbiaozhi.png"),195,4,0); g.drawImage(img("/pic/playerbiaozhi.png"),220,4,0);}if(playerno==2){ g.drawImage(img("/pic/playerbiaozhi.png"),195,4,0);
g.drawImage(img("/pic/playerbiaozhi.png"),220,4,0);
}if(playerno==1){ g.drawImage(img("/pic/playerbiaozhi.png"),220,4,0); }
if(s1==1) { g.setColor(255,255,255);
g.fillRect(170,22,65,5);g.setColor(255,0,0);
g.fillRect(170,22,s2,5);g.drawString("无敌",124,18,0);s2=s2-1;if(s2==0)
{pzbz=0;s1=2;}}
flushGraphics();}
public void input(){ if(inputno==0)
{int keystate=getKeyStates();
if((keystate&UP_PRESSED)!=0){
moveUp();}
if((keystate&DOWN_PRESSED)!=0){
moveDown();}if((keystate&LEFT_PRESSED)!=0){ moveLeft();}
if((keystate&LEFT_PRESSED)==0){
c1.setFrame(0);}if((keystate&RIGHT_PRESSED)!=0){moveRight();}
if((keystate&FIRE_PRESSED)!=0){
for(int i=0;i<=6;i=i+3){
if(huokebullet[i].no==1){
for( int z=i;z<i+3;z++){
huokebullet[z].initBullets(z); huokebullet[z].no=huokebullet[z].bulletheight
}huokebullet[i].setfirstposition(c1.getX(),c1.getY(),i,huokebullet,img("/pic/bullet.png"));//
break;}}}}}private void moveDown() {
c1.move(0,4);
if((c1.getY()+c1.getHeight())>planepo)
{
c1.setPosition(c1.getX(),planepo-c1.getHeight());}}private void moveUp() {c1.move(0,-4);
if(c1.getY()<planepoup){ c1.setPosition(c1.getX(),planepoup);}}
private void moveRight() {
c1.setFrame(2);
c1.move(3,0);
if(c1.getX()>(getWidth()-c1.getWidth()))
{ c1.setPosition((getWidth()-c1.getWidth())c1.getY());
} }
private void moveLeft() {
c1.move(-3,0);
c1.setFrame(1);
if(c1.getX()<=0)
{
c1.setPosition(0,c1.getY());
}
}
public void start()
{
Thread t=new Thread(this);
t.start();
}
public void commandAction(Command c,Displayable d)
{
if(c.getLabel()=="暂停")
{ conti=false;
removeCommand(c);
addCommand(new Command("继续",Command.OK,1));
}
if(c.getLabel()=="继续")
{ conti=true;
start();//
removeCommand(c);
addCommand(new Command("暂停",Command.OK,2));
}
if(c.getLabel()=="返回")
{ conti=false;
midlet.menuscreensecond();
}
}
}
附录二 操作说明
在电脑中使用模拟器的步骤如下:
1 使用WTK的新建项目功能建立一个新项目,要求与Eclipse工作区下的项目名称MIDlet类名相同。
2 新建项目后,将Eclipse工作区下的.java文件拷入src文件夹,将.class文件拷入class文件夹(需要新建),将pic文件夹拷入res文件夹,点击生成,如一切正常。
3 选择项目-包-生成包。
4 生成的jar和jad文件存储在lzhhdm/bin目录下。
5 运行Motorola SDK v4.2 for J2ME,使用MOTOA760手机模拟器进行测试。点击Lanuc按纽,即可以进行游戏了。
电脑键盘的方向键上、下、左、右可以控制玩家飞机的运动,回车可以发射子弹。
可以将鼠标移动到相应的按纽处单击,以测试相应的按纽。
附录三 英文文献及其译文
What's New in the J2ME Wireless Toolkit 2.2
The award-winning J2ME Wireless Toolkit has become the de facto standard toolkit for developing applications for Java 2, Micro Edition (J2ME). Since its debut in 2000, the toolkit has evolved to keep pace with the rapidly changing face of mobile Java technology. Don't be misled by the small change in version number from 2.1. The new toolkit supports new optional APIs specified through the Java Community Process (JCP) in four vital areas: 3D graphics, multimedia messaging, Bluetooth networking, and data management. In this article I'll describe these new APIs and show you how they're implemented in the toolkit.
Before I do, take a moment to admire the updated DefaultColorPhone emulator skin. Since version 2.1, this skin has gotten a larger screen (320 x 240, or QVGA) and far more colors (4096 instead of 256).
Mobile 3D Graphics
JSR 184, the Mobile 3D Graphics API for J2ME (M3G), provides three-dimensional graphics functionality in a compact package that's appropriate for devices that support the most widely adopted J2ME software stack, the Connected, Limited Device Configuration (CLDC) and the Mobile Information Device Profile (MIDP). The API provides two APIs for displaying 3D content. The immediate mode API makes it possible for applications to create and manipulate 3D elements directly. On top of this layer is a scene graph API, also called retained mode, that makes it possible to load and display entire 3D scenes you've designed ahead of time. Applications may use whichever API is most appropriate, or a combination of retained mode and immediate mode. The JSR 184 specification also defines a file format for scene graphs, .m3g.
The toolkit's emulator fully supports JSR 184 and you'll find several MIDlets that show off the API's capabilities in the Demo3D project.
Life3D demonstrates the use of immediate mode:
PogoRoo is an interactive demonstration that features a bouncing kangaroo:
Finally, retainedmode shows how to load and render a scene graph file:
Multimedia Messaging
The J2ME Wireless Toolkit has long supported JSR 120, version 1.1 of the Wireless Messaging API (WMA); the new release supports WMA 2.0 (JSR 205), which expands WMA's capabilities to include Multimedia Messaging.
In the toolkit, messaging takes place in a simulated environment. If you run the emulator multiple times, applications on the emulators can communicate with each other, and you can use the toolkit's handy WMA Console utility to exchange messages with the applications in the same environment. Messages may conform to any of three standard protocols: Short Message Service (SMS), Cell Broadcast Service (CBS), or - new in version 2.2 of the toolkit - Multimedia Messaging Service (MMS).
You can address MMS messages and add any collection of files to the message.
Furthermore, the network monitor now displays MMS messages that are sent or received by the emulator.
Bluetooth and OBEX
The J2ME Wireless Toolkit emulator supports JSR 82, the Java APIs for Bluetooth, which includes two independent APIs:
The Bluetooth API provides an interface to Bluetooth wireless
networking, including device discovery and data exchange.
The OBEX API allows applications to use the Object Exchange (OBEX)
protocol over Bluetooth or other communication channels.
The toolkit allows you to develop and test applications that use Bluetooth technology even if you don't have any actual Bluetooth hardware. The toolkit simulates a Bluetooth environment, in which you can run multiple emulator instances that can discover each other and exchange data using the Bluetooth API.
An example application, BluetoothDemo, shows how to transfer imagesfrom one emulator to another.
The J2ME Wireless Toolkit implements OBEX transfer over simulated Bluetooth and infrared connections. The simulated infrared connection
follows the IrDA standard defined by the Infrared Data Association. The toolkit simulates infrared transfers between multiple running emulators.
You can configure the Bluetooth and OBEX environment using the Bluetooth/OBEX tab in the toolkit preferences. This controls such
parameters as how long the emulator attempts to discover other devices in the simulated Bluetooth environment and the maximum packet size. See the toolkit's User's Guide for more details.
Local Files and Personal Information
The J2ME Wireless Toolkit supports JSR 75, the PDA Optional Packages for
the J2ME Platform, which also includes two independent APIs:
The FileConnection API gives MIDlets access to a local file system on the device.
The Personal Information Management (PIM) optional package includes APIs for manipulating contact lists, calendars, and to-do lists.
A real device may have a local file system which can be accessed using the FileConnection API. In the J2ME Wireless Toolkit emulator, a simulated file system is maintained as a directory on your hard disk.
The files your application can access using FileConnection are stored in subdirectories of <toolkit>\appdb\<skin>\filesystem., where <toolkit> is the installation directory of the J2ME Wireless Toolkit and <skin> is the name of the emulator skin. For example, the DefaultColorPhone emulator skin comes with a root directory installed called root1, which contains a file called Readme. The file's full path is <toolkit>\appdb\DefaultColorPhone\filesystem\root1\Readme.
You can manage the root directories that are available by choosing MIDlet > External events from the emulator window's menu. You'll see a small utility window for adding and removing roots. These actions will also generate events for a registered listener object.
In a real device, personal information might be stored in a proprietary
format in memory. The emulator uses a directory on your hard disk to
contain this information, accessible using the PIM API. All information is stored in <toolkit>\appdb\pim. Lists are stored in subdirectories of the contacts, events, and todo directories. For example, a contact list called Contacts is contained in <toolkit>\ appdb\ pim\ contacts\ Contacts.
Inside the list directory, items are stored in standard formats specified by the Internet Mail Consortium. Contacts are stored in vCard format, while calendar and to-do items are both stored in vCalendar format.
Summary
The J2ME Wireless Toolkit 2.2 is much more than an incremental update of an excellent tool. It includes support for four exciting new
specifications that together define six new APIs. As the world of wireless Java technology rapidly evolves, the J2ME Wireless Toolkit keeps pace and contains all the tools you need for
About the Author: Jonathan Knudsen [e-mail] [home page] is the author of several books, including Wireless Java (second edition), The Unofficial Guide to LEGO MINDSTORMS Robots, Learning Java (second edition), and Java 2D Graphics. Jonathan has written extensively about Java and Lego robots, including articles for JavaWorld, EXE, NZZ Folio, and the O'Reilly Network. Jonathan holds a degree in mechanical engineering from Princeton University.
译文
J2ME Wireless Toolkit 2.2 新改进
作者:Jonathan Knudsen
2004 年 6 月
屡获殊荣的 J2ME Wireless Toolkit 已经成为 J2ME 开发应用程序的事实标准工具包。自从 2000 年初次亮相以来,该工具包一直在发展以赶上移动 Java 技术外观上快速改变的步伐。不要被从 2.1 版本以来的小改变所误导。新的工具包在四个重要的领域支持 Java Community Process (JCP) 所指定的新的可选 API:3D 图形、多媒体通信、蓝牙连网(Bluetooth networking)和数据管理。本文将描述这些新的 API 并且向您展示如何在工具包中实现他们。
在这之前,让我们赞美一下更新后的 DefaultColorPhone 模拟器皮肤,自从 2.1 版本以来,该皮肤已有了一个更大的屏幕(320 x 240, 或 QVGA)和更多的颜色(4096 色而不是 256 色)。
移动 3D 图形
JSR 184, 即 Mobile 3D 图形 API for J2ME (M3G) 在一个集成软件包中提供了三维图形功能,适用于支持最为广泛采用的 J2ME 软件栈、有限连接设备配置(CLDC)和移动信息设备描述(MIDP)的设备。API 为显示 3D 内容提供了两种 API。立即模式 API 使应用程序能够直接创建和操纵 3D 元素。这一层之上是一个场景图 API,也称为保留模式,能够提早加载并显示已设计的整个 3D 场景。应用程序可以采用最适合的 API,或保留模式与立即模式的组合。JSR 184 规范还为场景图定义了一种文件格式-.m3g。
该工具包的模拟器全面支持 JSR 184,并且有几个 MIDlet 展示了 API 在 Demo3D 项目中的功能。Life3D 演示了立即模式的使用。PogoRoo 是一个的交互的演示,特点是显示一只跳跃的袋鼠:
多媒体通信
J2ME Wireless Toolkit 长期支持 JSR 120、Wireless Messaging API(WMA)的1.1版本;新版本支持 WMA 2.0(JSR 205),其把 WMA 的功能扩展到包括多媒体通信。
在工具包中,通信发生于模拟的环境中。如果多次运行模拟器,模拟器上的应用程序能够相互通信,并且可以使用工具包方便的 WMA Console 实用程序在同一环境中与应用程序交换消息。通信可以遵照以下三种标准协议之一:短消息服务(Short Message Service,SMS)、蜂窝广播服务(Cell Broadcast Service,CBS)或工具包 2.2 版本中的新协议-多媒体通信服务(MMS)。
您可以标记 MMS 消息,并向消息添加任何文件集合。
而且,现在网络监视器能够显示模拟器发送或接收的 MMS 消息。
蓝牙和 OBEX
J2ME Wireless Toolkit 模拟器支持 JSR 82,即 Java API for Bluetooth,包括两个独立的 API:
蓝牙 API 为蓝牙无线网络提供了接口,包括设备恢复和数据交换。
OBEX API 允许应用程序在蓝牙或其他通信信道上使用对象交换(Object Exchange,OBEX)协议。
该工具包允许在即使没有实际蓝牙硬件的情况下,也可以开发和测试采用蓝牙技术的应用程序。该工具包模拟蓝牙环境,您可以在该环境中运行可相互发现的多个模拟器实例和使用蓝牙 API 交换数据。
应用程序实例 BluetoothDemo 显示了如何从一个模拟器将图像传输到另一个模拟器。
J2ME Wireless Toolkit 通过模拟的蓝牙和红外连接上实现 OBEX 传输。模拟的红外连接遵循 Infrared Data Association 定义的 IrDA 标准。工具包在多个运行的模拟器之间模拟红外传输。
您可以使用工具包参数中的 Bluetooth/OBEX 标签来配置蓝牙和 OBEX 环境。其控制了诸如模拟器在模拟的蓝牙环境中试图用多长时间去发现其他设备,以及最大数据包大小等参数。请参见工具包的用户指南获取更多的详细信息。
本地文件和个人信息
J2ME Wireless Toolkit 支持 JSR 75,即 PDA Optional Packages for the J2ME Platform,它也包括两个独立的 API:
FileConnection API 提供了 MIDlet 对设备上本地文件系统的访问。
Personal Information Management (PIM) 可选软件包包括用于操纵联系人列表、日程安排和计划列表的 API。
一台真实的设备可能有一个能通过使用 FileConnection API 访问的本地文件系统。在 J2ME Wireless Toolkit 模拟器中,模拟的文件系统作为硬盘上的一个目录存在。
应用程序使用 FileConnection 可访问的文件都存放在子目录 <toolkit>\appdb\<skin>\filesystem.中,此处 <toolkit> 是 J2ME Wireless Toolkit 的安装目录,<skin> 是模拟器皮肤的名称。例如,DefaultColorPhone 模拟器皮肤随安装根目录 root1 而产生,包含一个 Readme 文件。文件的完整路径为<toolkit>\appdb\DefaultColorPhone\filesystem\root1\Readme。
您可以从模拟器窗口的菜单种选择 MIDlet > External events 来管理可用的根目录。您将看到一个添加和删除根目录的小的实用程序窗口。这些操作也将为 registered listener 对象生成事件。
在真实的设备上,个人信息可能以专有格式存储在内存中。模拟器使用硬盘中的一个目录来存放这些信息,可使用 PIM API 来访问。所有的信息存储在 <toolkit>\appdb\pim 目录中。列表存储在联系人、事件和计划目录的子目录中。例如,联系人列表 Contacts 位于 <toolkit>\appdb\pim\contacts\Contacts 目录中。
在列表目录内,列表项以 Internet Mail Consortium 指定的标准格式存储。联系人以 vCard 格式存储,日历和计划项都以 vCalendar 格式存储。
结束语
J2ME Wireless Toolkit 2.2 不仅仅是一个出色工具的增量更新。它包括了对令人激动的四项新规范的支持,同时定义了六种新的 API。随着无线 Java 技术世界的快速发展,J2ME Wireless Toolkit 紧紧跟随发展的步伐并且包含您所需的全部工具。
关于作者
Jonathan Knudsen[电子邮件] [主页],有多本著作,包括:Wireless Java (second edition)、The Unofficial Guide to LEGO MINDSTORMS Robots、Learning Java (second edition) 和 Java 2D Graphics 。Jonathan 在 Java 和 Lego 机器人领域编写了大量的文章,包括 JavaWorld、 EXE、 NZZ Folio 和 the O'Reilly Network 方面的文章。作者拥有普林斯顿大学的机械工程学位。
目 录
2.5 Java Appication Manager. 5
3.6 MIDP2.0新增的GameCanvas包... 8
4.3.5 关于commandAction()方法... 17
4.3.6 Sprite类对象的碰撞检测及相关属性... 18
4.3.9 render()和renderboss() 20
摘 要
这款游戏的名字叫《览桥风光》。J2ME(Java 2 Micro Edition) 是近年来随着各种不同设备,尤其是移动通信设备的飞速发展而诞生的一项新的开发技术。它定位在消费性电子产品的应用上,对设备的智能化、多样化,提供了革命性的解决方案,并因其“Write Once, run anywhere”的Java特性而提高开发的效率。
随着手机的日益普及、Java功能在移动设备上的实现,Java应用程序产生的手机增值服务逐渐体现出其影响力,对丰富人们的生活内容、提供快捷的资讯起着不可忽视的作用。本论文着眼于J2ME技术的应用,开发一款可商用的手机游戏程序。本程序将老少皆宜的经典作品移植到手机上来,为更流行的硬件平台提供应用软件。
本论文介绍了J2ME的相关技术及本程序的结构分析和具体功能的实现。
关键字:J2ME;手机游戏;Java
ABSTRACT
The title of this game is Air Force of China in 1937. J2ME is a kind of fast developing technology implemented on various devices especially mobile communication equipments. It focuses on application for consumptive electronic products, providing revolutionary solution to the intelligentizetion and diversification of the equipment. It improve the efficiency of the development process thanks to its “Write Once, run anywhere” nature.
When cell phone is getting ever more popular nowadays, with the implementation of Java technology on mobile equipment, increment of capital on communication service exhibits its force on people’s everyday life, providing them ever fast information just in hand. This dissertation focuses on implementation of J2ME technology and has developed a commercial game suite run on mobile phones. This application transports the classical product to mobile phones, offering corresponding software for such more popular hardware platform.
This dissertation introduces J2ME and its relative technology as well as this program’s structure and analysis of implementation in details.
Key words:J2ME; mobile game; Java
参考资料
java毕业设计——JAVA打飞机游戏毕业设计(源代码+论文).zip
JAVA毕业设计-打飞机游戏设计与实现(论文+源代码).zip