基于J2ME的Java游戏--坦克大战的开发毕业设计论文_第1页
基于J2ME的Java游戏--坦克大战的开发毕业设计论文_第2页
基于J2ME的Java游戏--坦克大战的开发毕业设计论文_第3页
基于J2ME的Java游戏--坦克大战的开发毕业设计论文_第4页
基于J2ME的Java游戏--坦克大战的开发毕业设计论文_第5页
已阅读5页,还剩19页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、西 南 交 通 大 学Java游戏编程技术课程设计报告坦克大战游戏编程课程设计报告年 级: 2008级 学 号: 20082961 姓 名: 陈锦添 专 业: 信息管理与信息系统 分数:教师签名:二零一零年六月摘 要Java良好的跨平台特性在移动平台的开发中显示出了巨大的威力。Java语言面向对象的优势也使得开发游戏变得非常容易。随着手机的日益普及、Java功能在移动设备上的实现,Java应用程序产生的手机增值服务逐渐体现出其影响力,对丰富人们的生活内容、提供快捷的资讯起着不可忽视的作用。本论文着眼于J2ME技术的应用,开发一款可商用的手机游戏程序坦克大战。本程序的界面和运作方式继承于日本任天

2、堂公司在20世纪80年代开发的Battle City游戏,将老少皆宜的经典作品移植到手机上来,为更流行的硬件平台提供应用软件。本论文介绍了任天堂红白机的软硬件特性、J2ME的相关技术及本程序的结构分析和具体功能的实现。关键字:J2ME,手机游戏,Java,坦克大战手机中将Java语言引入,作为一种通用的开发标准,并将向市场推广普及仅仅短短几年,J2ME仍是一种新型的技术,中文资料除台湾出版过相关书籍外,国内相关介绍资源很有限,国内J2ME开发商也屈指可数,名声较响的Digital-Red公司也仅成立于1999年。本文可算是对新技术的一些尝试,代表对无线平台应用程序推广的一些努力。目 录摘 要-

3、 I -Abstract - I -第一章 程序结构、思想和相关技术- 3 -1.1 本程序需解决的有关技术问题- 3 -1.2 程序流程- 4 -1.3 绘图与MIDP2.0新增的GameCanvas包- 6 -1.3.1 提供低级绘制的Canvas类- 6 -1.3.2 Graphics类- 6 -1.3.3 PNG格式- 6 -1.3.4 Game包中的新功能- 7 -第二章 游戏的设计思路- 8 -2.1 坦克的控制和敌方的智能运行- 8 -2.2 子弹的运行和控制- 9 -2.3 RMS数据库系统- 10 -2.4 内存使用的最佳化- 11 -2.5 混淆器(Obfuscator)的

4、使用- 12 -第三章 程序分析和具体实现- 12 -3.1 游戏进入前的选择- 12 -3.2 主游戏逻辑及其涉及到的若干类- 13 -3.3 坦克的共同行为- 15 -3.4 玩家坦克的功能属性- 16 -3.5 敌人坦克的功能属性- 16 -3.6 子弹的运行和控制- 18 -3.7 记分系统- 19 -3.8 本章小结:- 21 -第四章 总 结- 21 -4.1 本程序的总结和展望- 21 -4.2 经验和感想- 22 -参考文献- 23 -第一章 程序结构、思想和相关技术1.1 本程序需解决的有关技术问题1. 游戏程序是一项精度要求很高的程序系统,因为其代码利用率很高。一个实时运行

5、的最终作品,每秒都会运行成千上万行程序,绘图事件、键盘事件都会以极高的频率在后台等待响应,若有丝毫的差别都将很容易导致程序在运行不久后可能出现严重错误,甚至死循环。因此,其逻辑设计应当相当严谨,需将所有可能发生的事件及意外情况考虑在设计中。2. 游戏中为了美观,适用性强,可能需要采用外部文件引入的图片贴图,有关贴图,在MIDP2.0中提供了用于增强游戏功能的game包,使得解决静态或动态、画面背景、屏幕刷新的双缓冲等都有较好的解决方案。3. 己方坦克的运行可以通过键盘响应事件控制,但敌方则因为是自动运行,就需要有一定其一定的智能性;同时,出现在屏幕上的敌方可能会有较多的数量,这需要为每个敌方开

6、辟一个线程以便能让其独立运行。Java的多线程能力为实现这样的游戏提供了可能。敌人坦克的运行算法也需要进行适当的设置,以免游戏过于简单,单调。4. 对于双方坦克发出的子弹的控制也需要对其跟踪控制,子弹也需要处在独立的线程中。敌方子弹仅需要扫描用户坦克,而用户坦克需要在每一步扫描所有的敌方坦克。这需要对所有的对象有较好的控制。另外,子弹在运行过程中也需要实时扫描是否碰撞到了相关障碍物或屏幕边界。如此过多的线程同时在本来效率就不高的KVM虚拟机上运行,也许会导致程序的缓慢。5. 双方的坦克在前进时也需要考虑到是否碰撞到相关物体或对方坦克,以免重叠运行,造成许多物理上不可能的情况,缺乏真实感。每一次

7、刷新页面、每前进一步都需要将所有的周围环境都进行扫描。6. 游戏的结束、开始、动态信息画面作为构成一个完美程序都是必不可少的重要部分。良好的用户界面更是吸引用户的硬指标,相关的美术构图也需要有一定的考虑。7. 游戏的地图不可能通过绘图来解决。否则,不仅难于控制和处理过多的元素,也会因过多的大型图片而不能限制程序的大小,失去手机上程序的原则和Java的优势。同时,地图关卡不宜保存在手机有限的内存中,而最好采取外部文件的读入读出方法。8. 用户运行游戏时需要有分数记录的可能。如何采用合理的记分标准,需要进行适当的设计。记录分数的存储方式也需要有较好的解决方案。手机中由于处理器和内存空间、存储空间都

8、十分有限,其数据库系统与普通PC大相径庭。其数据库结构较为简单,被称之为RMS系统。9. Java是基于虚拟机的半解释型编译系统,其执行效率较C+等完全编译后的程序会低很多,程序如果不进行精简和优化,将可能导致运行的不流畅。除开发过程中对结构上的控制、变量的使用、算法的优化等优化外,还可以使用混淆器(Obfuscator)进行程序打包后的优化。以上相关技术细节和整体流程将分别在以下小节阐述。1.2 程序流程MIDlet suite是MIDP应用程序的最小单位,JAM负责将手机内的MIDlet suite以图形化的方式呈现,让用户能够选取欲执行的MIDlet suite,一旦选取了某个MIDle

9、t suite,操作系统就会激活KVM执行里面的MIDlet。MIDlet及相关的支持类组成了MIDP应用程序的实际内容。消减状态(Destroyed)停止状态(Paused)运行状态(Active)StartApp()DestroyApp()呼叫MIDlet的构造函数DestroyApp()PauseApp()图1-1 MIDlet的流程每个MIDlet都必须继承javax.microedition.midlet.MIDlet这个抽象类。在MIDP规格中定义了MIDlet的生命周期,以及可以存在的三种状态,包括Paused、Active以及Destroyed,每一个MIDlet在任何时刻只可

10、能处于其中的一个状态。这三种状态的转换关系如图所示:本程序采用面向对象的设计模式,对游戏中的所有物体赋予对象的概念和属性。运行程序后允许用户选择执行选项菜单,在开始游戏后将先从外部文件载入地图文件,对背景的所有物体进行绘图。在主程序运行的线程中,画面刷新将以一定的频率采用双缓冲技术对屏幕重绘,实时反映整个游戏的进行状态。用户控制的坦克运行在主线程中,随屏幕刷新的频率而步进。敌方坦克将在游戏开始时逐渐新增线程,每增加一个敌方对象就新增加一条线程,一旦线程数满到最大值(本程序暂设置为6),就不允许敌人再继续出现。用户坦克自诞生之时起将拥有一发子弹,子弹虽然开在单独的线程中,但运行结束后(比如撞到相

11、关物体或敌方坦克时)并不结束子弹对象,只是将其线程终止。用户再次发射子弹时只是将终止的线程再次激活。在屏幕重绘的主程序中,将在每次的循环中判断若干事件。如:用户坦克的生命是否已完全用尽,敌方坦克数是否已经为零,屏幕上的坦克数量是否少于仍剩下的坦克数量等。以便程序进入相关的分支执行相关的反应代码,结束游戏或统计分数等。主程序流程如图3-2所示:Logo 画面选项画面主程序屏幕绘图本关记分统计显示GameOver显示历史记分表About开始敌方需要出坦克时,生成坦克初始化参数死亡时符合结束条件时图1-2 本程序的主流程图程序为需要完成独立功能的需显示的模块设置了单独的类。TankMain类是继承自

12、MIDlet的控制主程序启动的首先被载入系统的部分。载入程序后首先启动的是程序介绍的信息画面。闪过后载入StartChoice类,为用户提供可选择的选项。在选择开始后,将运行BattleCanvas类中的总流程控制。它决定了游戏何时该结束,何时分配敌人数量,GameOver字样的闪现规则,地图的绘制及整个游戏的调度。图1-3 与主程序相关的类的UML视图图1-3是程序中类之间的UML分析图。敌方坦克与用户坦克的相关功能和具体行为分别定义在EnemySprite和UserSprite类中,它们都继承自TankSprite公共类,以简化程序的代码、理清结构。在每关的结束或死亡后都将载入ScoreS

13、creen类,统计当前的分数。如果已死亡或完成所有的关数,程序将用户所得的分数记载到RMS数据库中,进行永久性保存。载入过程中将对所得分数与以往历史比较,放置到合适的位置中,形成排序。1.3 绘图与MIDP2.0新增的GameCanvas包1.3.1 提供低级绘制的Canvas类为了能有程序开发人员控制接口的外观和行为,需要使用大量的初级用户接口类,尤其在游戏程序中,几乎完全依赖的就是Canvas抽象类进行绘图。从程序开发的观点看,Canvas类可与高级Screen类交互,程序可在需要时在Canvas中掺入高级类的组件。Canvas提供了键盘事件、指点杆事件(如果设备支持),并定义了允许将键盘

14、按键映射为游戏控制键的函数。键盘事件由键代码指定,但这样控制游戏会导致缺乏通用性,并不是每个设备的键盘布局都适合游戏的操作。应当将键代码转换为游戏键的代码,以便硬件开发商能定义他们自己的游戏键布局。本程序中,操纵用户坦克运行的按键都定义为游戏控制键,这样便能适应所有的机器。1.3.2 Graphics类Graphics类提供了简单的2D绘图功能。它具有24位深度色彩的绘制能力,以三原色分别各占一个字节表示其颜色。程序只能在paint()函数中使用Graphics绘制,GameCanvas可调用getGraphics()函数直接绘制在缓冲区上,可以在任何时间请求传输到前台。其对象会被传给Canv

15、as的paint()函数,以便最终显示。1.3.3 PNG格式PNG(Portable Network Graphics)格式是MIDlet唯一支持的图象格式,PNG具体格式由PNG Specification,Version 1.0定义的。PNG格式提供透明背景的图象,这对绘制游戏画面和被操纵主角极有帮助。坦克之间或与障碍物碰撞时就不会因为背景有特定的颜色,显示出的效果像贴上的图片而缺乏真实感,物体之间轻微重叠时最上层图片也不会覆盖超过其有效象素外的部分。PNG格式图片中包含许多定义其图片特性的冗余部分(Chunks)。这些代码包含在每一个单独的png格式图象中,然而如果将多个png图象合并

16、在一张幅面稍大一些的整图中,多个chunks就可以得到精简,图片的大小可以得到控制。使用Image类中的createImage函数可从整图中分割出所需要的元素。在Game包中的TiledLayer和Sprite类都整合了这样的功能。本程序中的地图元素都集成在一张tile.png图片中,实现了方便的管理和程序体积的精简。1.3.4 Game包中的新功能 MIDP自2.0以后新增了Game包,为游戏的开发带来了极大的便利。地图绘制、主角的动态显示、按键的检测、图层的控制等游戏专属的特性都得到了在移动设备上最大的发挥。 LayerManager(以下简称LM)提供控制整体画面层的控制。它包括了一系列

17、自动获取了代号和位置的层,简化了各层加入游戏画面的过程,提供了自动排序和绘制的能力。LM存储了一个层的列表,新的层可以用append函数附加、删除和插入。层的序号相当于坐标的Z轴,0层表示最接近用户视觉,层数越高,离用户越远。层号总是连续的,即使有中间的层被移除,其他层的序号会作相应的调整以保持整体的完整性。LM中的View Window控制着与LM相对坐标的可视区域。改变View Window的位置可以制造出滚动屏幕的效果。本程序中所有的地图、坦克都采用LM控制,敌方坦克的生成由附加一个EnemySprite对象得到。唯有界面右侧的计分栏由Graphics类绘制。Sprite类是继承自Lay

18、er的用于存储多桢的基本可视元素。不同的frame可交相显示,构成动态的效果。图片可翻转、颠倒、由一个主角图片就可以方便的得到所有方向的显示状态,相比原先只能使用Canvas绘图,需要将所有方向的主角图象都绘制在png图象中简化了许多。Sprite也可以从整合的图象中读图,读图时将把大图分解为若干等宽等高的小图。每个小图按照其排列顺序有相应的序号,在程序中调用其序号,就可以绘制出相应的图片。本程序中的双方坦克、子弹都由Sprite继承得到。在有些情况下,控制主角的翻转,尤其是多幅图片配合显示的过程,如果将多图的共享定位点设置在通常的左上角,将很不容易控制,因为许多翻转都是以其他点为参考电的(比

19、如,中心点)。由此,引入参考点的概念。参考点由defineReferencePixel函数确定未翻转图片状态时的坐标。默认是(0,0)点,如果需要,可将参考点设置在画面边界之外。本程序中的坦克的参考点定义在图片正中,以便简便的实现转向等功能。子弹的参考点设置在子弹底部的中心,因为子弹一出炮筒的时候紧挨着坦克的象素就是其底部中心。TiledLayer是有一组图象格元素(grid of cells)组成的整块虚拟图象。该类使不需要高分辨率的图象就能创建大幅图面成为可能。这项技术通常应用在2D游戏平台的滚动背景的绘图。一块整图可被分割成等大小的图象格,每块格有其对应的序号,按照行列递增。多块格可由大

20、块同时替换组合而模拟动态的背景,这不需要逐块替换所有的静态图象格而显得非常方便。本程序中的地图即为游戏背景。每块障碍物都有其响应的代号,其中,用户需保护的总部因为体积稍大,使用了四块图象格显示。地图背景分为20*22个图象格,每个格使用一个字节表示其中的障碍物,图象文件存储在外部文件中,以16进制的整数串表示,因此每个地图的大小为固定的440字节。如果整块地图均由绘图产生,将导致体积迅速增加,且对坦克与障碍物的碰撞也难以检测。J2ME中并没有J2SE中的File类,获取外部文件的手段很有限,仅仅在Class类中提供了一个getResourceAsStream函数,将外部文件获取为输入流,再由I

21、nputStream的继承类读出。 第二章 游戏的设计思路2.1 坦克的控制和敌方的智能运行 GameCanvas中提供了与以往MIDP1.0不同的键盘采样功能。Canvas类中采取响应键盘事件的方法,每次执行周期时会读取keyPressed函数中需执行的代码。这样的机制并不适合某些游戏场合。在某些不支持keyRepeat功能的设备上,反复执行的按键,比如发射子弹,将不能因为长时间按压而自动重复,这样就需要用户高频率的手动击键,这在操纵空间非常有限的移动设备上是非常困难的。同时,事件的执行周期也并不一定适合游戏的场合,也许需要更高频率执行的按键却只能在指定的周期内规律的响应。对此,针对游戏的开

22、发,Game包提供的键盘状态功能将显得十分有效。 GameCanvas提供getKeyStates函数可获取当前键盘上的信息。将以位的形式返回键盘上所有键的按与释放的状态,当bit为1时,键就是被按下的状态,为0时则为释放状态。只需要此一个函数的返回值就可以返回所有键的状态。这保证了快速的按键和释放也会被循环所捕捉。同时,这样的机制也可检测到几个键同时按下的状态,从而提供斜向运行等相应功能。敌方按照规则不能和用户坦克重合,则它每行走一步就需要把用户坦克扫描一次,判断其是否碰撞到了用户的坦克。Sprite类中提供了collidesWith函数,用于判断是否与某个TiledLayer、Sprite

23、、Image的对象有图象上的重合(即游戏中的碰撞)。然而不能仅仅将用户坦克作为其Sprite参数传递给敌人的类进行判断。因为如果发生碰撞,collidesWith成立,则两辆坦克已经发生了图象重合,违反了规则,甚至若再进行collidesWith判断的话,其结果将永为真。为了提前预知碰撞,可以将所有坦克的碰撞范围设定为一个比坦克图片稍大一些的矩形,此矩形仅在坦克前方比坦克图形多出一个象素。在多出的11个象素中,按照每个象素依次检查此象素是否于外界发生碰撞,如果不是按照象素检查,则当坦克与障碍物错位并同时与两种物体接触时将有可能忽略检测其中的一样物体。这样,就可以提前一步判断。如果发生碰撞,则坦

24、克应当选择掉转方向,此时,两辆碰撞的坦克又因为其矩形区域不重合而不符合collidesWith的条件,就可以继续正常运行了。敌方坦克由于需要具有一定的智能性,以便对玩家攻击,使之具有一定的可玩性。敌人可以自动行走,但是应当在以下适当的情况下转向:首先是是否超出界面的边界,其次是是否与地图障碍物发生了碰撞,再次是是否与用户坦克发生了碰撞。需要指出的是,当发生阻碍不能在不变方向的情况下继续行走时,并不一定立即需要采取转向的对策。如果一定发生转向,试想,当敌方碰到玩家时,如果它立即转向,将不会对玩家发射射向他的子弹,就不构成任何威胁,当然,也不能永远不转向。本程序设置为:当碰撞到障碍物或边界时立即转

25、向,但碰到玩家坦克时需要有一个等待的时间,这个时间由碰撞前随机取得的在某方向上的持续行走步数决定,当发生坦克间碰撞时,此随机数将在下一次循环前减少为原来的2/3,这样就实现了加快转向的时间,避免死锁在一个方向上静止的停留过长的时间。另外,坦克的发炮间隔和转后的具体方向都由随机数决定。坦克之间由以上道理也不会发生重叠,但当某坦克正从上方生成而正巧有另一辆阻碍在其生成点处,这将导致不可避免的重合。这是允许的,但需要对他们标注状态,即当坦克刚出现时暂时允许重合,一旦在某个时间他们脱离了重合状态,就不能在允许重合,如果不设置这样的判断,刚出现的坦克将会因为受到阻塞而永远不能前进,坦克将混成一团。本程序

26、中并未使用过多复杂的人工智能算法,如有时间,将可能再此方面加以完善。2.2 子弹的运行和控制每一个坦克都有他自己的一颗子弹,这颗子弹在任何一辆坦克被构造时就一直存在,直至此坦克生命的结束,子弹的再次只是将屏幕上暂时掩盖的图象重新置于坦克炮筒才恰当位置,并使其显示出来,这与现实中每个子弹都是单独的个体有所不同。子弹所需要完成的任务有:它是一个继承了Runnable虚类的可运行单独线程的对象。在其出现在屏幕上的运行周期中,每一步都需要循环检测以下条件:是否与某坦克发生了碰撞,即击中了这辆坦克。子弹使用的是象素级的碰撞检测,因为子弹的图片形状不规则,如果使用矩形碰撞检测,将有可能在子弹尚未接触到物体

27、时就已返回碰撞的真值。分为两种情况,如果此子弹来自于敌方,将只检测玩家坦克,因为敌方之间的子弹必须允许可以透明的穿过,以保证不会在敌人之间发生子弹的消减。如果来自玩家,则每一步需扫描所有的敌方坦克,检查是否发生碰撞,这可能会花费不少的CPU时间。其次,子弹之间需要检测是否碰撞。敌人之间显然,如上已经提过,不需要检测,但敌人与玩家之间应当可以互相消除子弹,以便在狭窄的路口中仍有存活的机会。玩家的子弹需要在每一步检测所有敌人的子弹的运行状态。这样较多的运算也将不可避免的耗费大量CPU时间。子弹对不同障碍物将有不同的反映。对砖墙将有能力将其击毁,使之在画面上消失;对水泥钢筋将不能发生作用,子弹也不能

28、通过;对于河流,坦克不可以通过,但子弹可以;对于草丛,子弹和坦克都可以通过。2.3 RMS数据库系统MIDP为MIDlets提供了一种永久存储和后来读出数据的数据库解决方案,被称为Record Managerment System(RMS),是一种类简单的基于记录的数据库。很显然,手机上的数据库系统不可能有PC上的强大功能。微小的存储空间也限制了它们的结构不能过于复杂。RMS是专门针对移动设备的服务的。RMS包中包括RecordStore类。在一个MIDlet suite包里的所有MIDlet都允许创建多个记录集,只要它们赋有不同的名称。当MIDlet包从平台中被移除后,所有与该包有关的的记录

29、集都同时会被移除。同一个包内的MIDlets可以直接互相访问它们的记录集,不同包内也可产生共享,但这需要有包的授权属性决定。访问模式会在准备提供共享的RecordStore被建立时被创建。访问模式允许私有使用或访问。RecordStore的API采用了时间戳的概念,其长整型变量由System的currentTimeMillis()函数返回决定。Record store 每次被修改后都会自动在其属性上附加上时间戳,这为同步化引擎和程序的控制都极为有效。记录是字节数组。开发者可以利用InputStream的派生类DataInputStream、DataOutputStream以及ByteArray

30、InputStream、ByteArrayOutputStream将不同种类的数据类型打包,以字节流的形式发送和接收。区别记录的唯一标记是他们的ID值,作为记录集的主键。第一项记录的ID是1,其后的每个记录ID递增。Record是以字节为基本单位来存放的,所以所有要写入record的数据都必须先将其转为字节才能写入,从record所读出来的数据也是字节,必须将其转换为原先写入时的数据类型才有意义。然而读取或写入的字节数组都只能代表一个字段的信息,如果需要读取或写入多个字段就必须要将数据转换成字节信息,并且提供适当的机制来分隔这些信息。主要有两种方法:1. 标记法。将所有要存放的数据用字符串表示

31、,但是在字段和字段之间以一个特殊的符号作为分隔。符号不能和字段内的数据相同的字符。2利用输入/输出流这一种方法较上一种复杂,但是较为实用。方法一中所有的字段只能以字符串的形式存储,要对这些字段作进一步的处理非常麻烦。利用输入输出流可以写入及读取不同数据类型的数据,做法是在写入数据时先将一个DataOutputStream数据流对象串接到一个ByteArrayOutStream数据流对象,然后再依字段的数据类型用writeInt()、writeBoolean()等方法写入,最后把ByteArrayOutputStream内的元素数据写入record中。反之若要读取数据,则先要串接一个DataIn

32、putStream对象和ByteArrayInputStream,依字段的数据类用readInt()、readBoolean()等方法读取。 本程序中主要存放在永久区的内容为用户得到的最高分数的记录。一共可以存储10条最高分。每次有新的更高的记录就会插入进相应的位置,将最低一名排挤出记录。在输入记录前,要求用户在TextField框中写入他自己的名字。返回的getString可以将名字输送给字节流。因为每个记录包括用户名和分数,因此需要使用多字段的方式编入。打印到屏幕上时,记录ID号即为排名,因此将显示三项数据。2.4 内存使用的最佳化通常在MIDP应用程序的手机执行环境中,所牵涉的内存有下列

33、三种:应用程序存储内存RecordStore存储内存执行时期内存(Java Heap)其中前两种是持久性的内存,关闭电源后还能保持数据的正确性,通常这两种内存所能存储的容量是合并计算的,这个上限对每种手机都不一样,大部分在一两百KB内。在这样的情况下需要在不影响原有功能的情况下适当的缩减JAR文件的大小,除了可以克服内存空间的限制外,也能大幅度缩短下载的时间(费用也降低了),势必会有更多的人愿意下载所开发的程序。其方法有:第一,就是尽量缩短命名的长度。在应用程序内,对于所建立的类、接口、方法及变量名而言,都需要赋予一个识别的名称,所命名的名称每多一个字符就会在类文件内多产生一个字节,对于一个较

34、复杂的应用程序而言就会增加为数不小的数据量。所有这些可以借助混淆器来帮助实现。第二是减少复杂的程序结构,为一些共同的行为建立一个抽象类(Abstract Class) 来表示继承的子类的共通性。第三是减少图形数据的大小。将PNG格式的小分辨率图象合并在一张大的高分辨率图象中,由于减少了chunks,将比合并前的总大小减少许多。2.5 混淆器(Obfuscator)的使用Java 语言并没有完全编译成二进制可执行文件,编译出的.class文件是一种介于源程序和二进制之间的一中基于半解释的字节码,需要虚拟机来执行。它包括了所有的信息。然而这样会导致.class很容易被反编译为源代码,从而不能保护作

35、者的知识成果。目前流行的如decode,JAD等反编译工具可以以很快的速度生成源文件。如果不加以施行有效的措施,将造成严重的后果。由此引入混淆器的概念。混淆器将代码中的所有变量、函数、类的名称变为简短的英文字母代号,如果缺乏相应的函数名指示和程序注释,即使被反编译,也将难以阅读。 混淆器的作用不仅仅是保护代码,它也有精简编译后程序大小的作用。由于以上介绍的减少变量、函数的命名长度的关系,编译后也会从.class文件中减少这些冗余的信息。混淆后,体积大约能减少25%,这对当前费用较贵的无线网络传输是有一定意义的。为了能与各种IDE集成,就像Java2 SDK一样,混淆器采用命令行参数的形式,以便

36、可被其调用。目前流行的Obfuscator有RetroGuard等。第三章 程序分析和具体实现3.1 游戏进入前的选择图3-1 游戏前的选项画面每个MIDlet程序都必须有一个主类,该类必须继承自MIDlet。它控制着整个程序的运行,并且可以通过相应函数从程序描述文件中获取相关的信息。该类中拥有可以管理程序的创建、开始、暂停(手机中很可能有正在运行程序却突然来电的情况,这时应进入暂停状态。)、结束的函数。进入时,首先载入画面的不是游戏运行状态,而是提供选项,当再次选择Start Game时才正式运行。运行画面如图3-1所示。因此,在TankMain的构造函数中分配了StartChoice类,即

37、选项画面的内存空间。在startApp()函数中,随即调用了Displable的setCurrent()函数将当前屏幕设置为startChoice。在显示高级用户界面前,建造了一个Alert类。Alert对象用于显示提示、警告等告之用户信息的临时闪现的屏幕,它可作为setCurrent的参数,提前显示在最终需要显示的屏幕前。当将FOREVER作为Alert的参数时,将永久显示,直到用户点击相应按钮手动结束。但是当显示的画面元素超过一屏大小时,将自动转换为永久状态。在此,由于贴在Alert上的图片大小超出了其范围,故已成为永久状态。效果如图3-2所示。图4-3 使用说明画面startChoice

38、继承了接口commandListener,这样,就可以使用高级界面的Command按钮。继承了commandListener的类必须拥有commandAction(),以决定对按键采取什么样的行为。即按钮事件触发后需执行的函数。在设置好commandlistener后,需要调用setCommandListener()以将按钮事件激活。键盘事件中,可用getCommandType()返回的Command类型来确定选择的是什么按钮。startChoice继承了List类,用于显示列表选项,使用其append()函数可将选项加入到列表中。getSelectIndex()可检测到选择的项目的序号,序号

39、从0开始递增。其中,当选择第一项时将载入正式游戏画面BattleCanvas类,第二项将显示帮助信息(效果如图3-3),第三项则是重新显示与作品和作者相关的logo画面。开始敌人数量小于0吗?显示记分画面最后一关或死亡了吗?进入下一关敌人数量大于屏幕上的数量吗?增加一个敌人玩家是否死亡显示GameOver重绘屏幕内存回收结束YNYNNYYN图3-4 BattleCanvas类主要关系流程图3.2 主游戏逻辑及其涉及到的若干类 BattleCanvas主管着所有类之间的协调,决定何时死亡,何时分配新的敌人,及控制敌人出现处的闪光图标、游戏结束后的动态Gameover字样。它运行在独立的线程中,以

40、恒定的频率刷新画面。刷新速度需大于30/秒才能使画面显示因人眼的暂时停留效应流畅运行。本程序设置为20毫秒。其主逻辑如图4-4所示。程序中建立了另外的两个类,分别表述了敌人坦克和玩家坦克的功能。它们分别为:EnemySprite和UserSprite。这两个类均在BattleCanvas中建立了对象,以便进行统一调度。BattleCanvas包括了LayerManager,这样所有静态和动态的图象都不需要手动刷新,只需要在LayerManager中加入所有的需控制的元素,再统一由LayerManager刷新即可。因此,有必要在其中创立一个LayerManager的对象。 其他,如Sprite类

41、的gameover字样、记分统计画面也都需在此主逻辑中建立相应对象。还需保存的变量有,游戏开始时间、结束时间(用于统计分数)、敌人的总数、屏幕上敌人的数量、下一个敌人需要出现的位置(总共允许在三个不同的位置出现,分别位于屏幕的左、中、右方)、游戏是否已成功结束或是否已死亡。 构造函数中,需初始化地图。地图实际即为TiledLayer的一个对象,可调用setCell设置其具体的图象格内容。地图由外部文件读入。外部文件分别命名为level*.png,利用MIDP中唯一获取外部文件为程序内资源的getResourceAsStream()函数将地图文件读入程序。在创建了InputStream类的map

42、对象后,使用read()函数可将流中的下一个字节读出,并返回此字节代表的整数。每个整数代表一种障碍物。用二维循环将读出的每个整数,通过setCell()将整幅地图画出即可。地图文件可用十六进制的文本编辑器生成,如本程序使用的Ultraedit。绘出地图后,可用LayerManager的append()将地图放置在第一层。这是很有必要的。因为地图上的障碍物之一草,在坦克运行中时是必须处于坦克的上层的,否则将失去真实性。为此,地图必须首先载入。由于敌人将依次出现在屏幕上,同时出现的数量应当受到控制。本程序设置为6。所以在构造函数中,也应当分配6个EnemySprite对象的内存空间。构造坦克时,将

43、把坦克的png图片作为参数传递给EnemySprite和UserSprite,BattleCanvas中创建坦克仅调用createEnemy()和createUser()实现。在构造函数自己调用了线程的start后,程序将开始循环运行,直至跳出while的循环。每次循环中将检测是否死亡,屏幕上坦克的数量,是否该过关统计分数,检测用户输入的按键、重绘整个屏幕及回收垃圾内存(Garbage Collection)。当敌人坦克完全死亡时(enemyNum为0),需调用System类的currentTimeMillis()赋值给结果的时间。接着调用setCurrent()显示统计分数的画面,为了进入下

44、一关,统计画面只是停留四秒,就重新转回BattleCanvas画面。当然,如果当前已是最后一关,就不会再转回。进入下一关时,许多变量需要重新被初始化,如地图的绘制、敌人出现位置的重置、敌人的数量、玩家坦克的当前位置。图3-5 游戏结束的画面如果游戏未结束,则需判断屏上坦克是否已小于还剩坦克的总数,如果是这样,就需要再提供一辆坦克。提供新坦克之前,在屏幕上设置了一个专用指示的闪光符号,它继承了Sprite,运行在单独的线程中。以在两秒钟内反复闪现两次为一个生命周期。当它闪光完毕后,敌人就会从闪光位置出现。这样可提示玩家具体敌人将在什么时刻出现,以便做好准备。闪光位置设置了三处坐标,由于敌人不能同

45、时出现,便设置了enemyOutDelay的倒数计时,每次屏幕刷新会减少一次计数,直到为0时就准备一辆坦克。本程序设置的两次坦克出现的最小间隔为2秒。如果玩家已经死亡,就需要使用LayerManager的insert()将gameover字样插入到最上层,以免被其他物体覆盖。效果如图4-5所示。在检测用户输入的input()函数中,当按方向键时,玩家坦克就将向不同的方向运行,这调用了UserSprite的go()函数;当开炮时,就调用其fire()函数,作出相应的行为。在出现正式画面前设置了一个loading state*字样的单独屏幕,调用了loadinglevel()函数,并停滞了1500

46、毫秒,提示用户做好准备。效果如图4-6所示。在绘图的render()过程中,除了要重绘坦克、地图、子弹外,还会在右边空白处绘出一个生命统计栏。并反复使用Graphics的drawLine()、drawImage()绘画出一个三维的效果,增强视觉感。该三维栏的上方为白色,下方为黑色,就创造了立体感。在每次刷新绘图页面时,应使用GameCanvas的flushGraphics()将屏幕后台的缓冲区内的图象刷新到前台来。在允许敌人出现前,需要检测给即将出现的敌人分配一个数组序号。在程序中调用了getNullEnemyIndex()进行测试,当返回为-1时说明没有序号可以分配,否则,将返回空的序号。游

47、戏的最终运行状态如图4-7所示。3.3 坦克的共同行为 在TankSprite中定义了所有坦克(包括敌方坦克和玩家坦克)的共同行为和属性。EnemeySprite和UserSprite都继承了该类以简化结构。在transformDirection中定义了坦克四个方向分别应将原始图片旋转的角度,分别为TRANS_NONE,TRANS_ROT90,TRANS_ROT180,TRANS_ROT270,以便在后来的setTransform()中将这些常量代入。构造函数中创建了每个坦克必须拥有的一颗子弹,这些子弹就将只跟随自己的坦克调动。为了能提前预测碰撞,调用了defineCollisionRecta

48、ngle(0,-1,11,12)将碰撞矩形向前设置了一个象素,具体原理见第二章。在setBulletDirection()中,将根据坦克当前的方向确定子弹出膛后的方向,其中setRefPixelPosition()将子弹的参考点设置在其未变形状态的底部,setXY()将其放置到炮口的位置,setTransform()将其图片方向转到需要使用的位置。canPass()函数将返回坦克是否能够向前前进,考虑到的因素有边界、障碍物。它返回一个boolean值,提供给go()函数,做进一步的判断。getTileIndex()将检测传递来的象素处是什么类型的障碍物,它将象素除以8(即障碍物的象素宽度),取

49、整,再通过getCell()得到。在得到障碍物属性后,判断其序号是否与草相同,或是否为空(序号为0)。因为所有的障碍物中只有草不会阻碍坦克的向前运行。3.4 玩家坦克的功能属性 构造函数中需要将坦克方向设置为向上,因为刚出现时就是这样的状态。当开炮时,调用BulletSprite的setLayerManager()将子弹与layerManager联系起来。需要联系的还有自身坦克、地图。这些都由坦克传递给子弹。因为子弹是属于坦克的,它的属性需要跟当前的坦克保持一致。接着使用append()将子弹贴到layerManager上显示出来。最终调用其start()开始子弹自己的线程。子弹一旦开始运行,

50、就脱离了当前坦克的控制,直到其生命周期终止。无论子弹是属于敌人还是玩家的,它都必须记录自己的来源和攻击的对象。在玩家坦克发射的子弹中,就必须将攻击对象设置为所有的敌人。这样,它才能有扫描的目标。在setShootCheck()的参数中,传给子弹的是敌人的数组,子弹的对象就被确定了。die()、resetPosition()、getLife()都是很简短的函数,但却提供必不可少的功能。他们可被外部调用,以取得生命值、死亡记数、重置位置。在go()函数,每个方向在走前都须用if (canPass(UP)&&!collidesEnemy()检测是否可以行走。canPass()检测是否

51、有障碍物及是否到边界。collidesEnenmy检测是否前方有坦克阻碍行动。当可以行走时,就在当前方向的坐标上增加或减少一个象素。在collidesEnemy()函数中,将有一个for循环按照敌人数组的序号依次检测6次。有一点非常重要:在检测前,需要将敌人的检测矩形区域设置为与原来图片一样大小。否则,当玩家向上走,而有敌人从左方向右走,并且已经碰撞到玩家坦克时,玩家坦克会因为被判定已与敌人发生碰撞而不允许前进。事实上,敌人坦克此时并没有阻碍玩家前进。这样的判断必须排除在考虑范围外。当然,在设置完成后,必须将将检测区域设置回原先的状态,否则敌人在往后自己的检测中将发生错误。3.5 敌人坦克的功

52、能属性 由于和UserSrite同属于一个TankSprite的继承类,其功能就与UserSprite有很大的相似之处,但也有其自身的特别属性。其主要功能流程图见图4-8。 首先,EnemySprite继承了Runable接口。因为敌人的运行是自动的,需要有设定的程序让它可自己控制,而不像UserSprite完全通过每次输入的键盘信号来做出反映。因此,它可以运行在单独的线程中。 setEnemyShootCheck()函数与UserSprite中的一样,设置了攻击的对象,并且此函数将继续把参数传递给自身的子弹,以便子弹可以识别攻击对象。此函数由BattleCanvas调用。开始是否刚出现前进Y

53、可否开炮开炮可否前进换向、取随机移动步数、随机开炮倒数记步数结束是否已死亡是否碰撞NYNYNYNN图3-8 敌方坦克运行流程图getRandomDirection()以当前系统时间作为种子,调用了Random类的nextInt()产生一个随机的整数,此整数取除4的余数的绝对值作为随机的方向。Random random=new Random(System.currentTimeMillis(); return Math.abs(random.nextInt()%4+1;此时返回的值的范围就确定在14之间,正好对应四个方向。将他们代入需要使用方向的函数中就可以使用了。getRandomStep()

54、的原理类似: Random random=new Random(System.currentTimeMillis(); return (Math.abs(random.nextInt()%4)*50;只是需要乘以每秒会刷新的屏幕的次数。这样就相当于允许在某一个方向运行03秒钟的时间。每个敌人还需要拥有一个内部的所有敌人的数组元素。这样,它们才可以自动检测自己是否与同伴发生了碰撞,以便采取躲避、转向等行动。 collidesWithOtherTank()将检测是否与其他坦克(包括敌人和玩家)。一个循环将依据敌人的序号查找5次。if(i=number)break ;语句将避免检测到自己,永远返回真

55、。collidesInOtherTank()虽与上面的函数很相似,但仍有一些细微不同,那就是不需要在检测前设置被检测方的矩形区域。因为不需要进行预先检测。此函数用来检测是否在刚出现时就与其他坦克发生碰撞的。如果一出现,出口就被堵死,显然,不能永远不出现,那就应采取其他的办法,否则两辆坦克将因为都处在碰撞状态中而无法移出。在运行的线程中,需在每前进的一步骤中循环做下列事件:如果坦克已死亡,立刻退出。(由boolean值destroyed决定)。如果不是刚出现(由isBeginner决定),判断是否与将其他坦克发生碰撞,就向当前方向前进一步骤,否则,将需要循环检测的当前随机步数减少为原先的2/3(

56、为了加速离开的时间)。如果刚出现,就直接走一步,具体如何行走将在go()函数中决定,并且此go()与UserSprite中的有所区别。当随机发炮数减少到0时,就进行发炮的动作。发炮后应立即重新赋值给随机发炮数,以便重新倒数计算。当所有的步骤走完后,因为需要转动方向,于是,调用一次随机取得方向的函数再次获值。其他的随机值也应当重置。在go()函数中首先检测是否正处于碰撞状态中,如果不是,就需要取消Beginner的状态,因为不需要Beginner这样的特殊身份,让别的坦克不检测了。开始是否出界是否击中物体将可以消除的障碍物消除玩家的子弹吗是否与任何敌人的子弹碰撞是否击中任何敌人敌人的子弹吗?是否

57、与玩家的子弹碰撞是否击中玩家结束NYNYYN子弹抵消Y消除敌人YNNNNN消除玩家子弹抵消图4-9 子弹运行的主要功能流程图YYY在运行在某个方向上,当确定为canPass时,应再检测是否为Beginner。如果是,就不应该受到其他坦克的影响而直接改变坐标,但若不是,就应当远地不动。3.6 子弹的运行和控制 子弹继承了Runnable,运行在独立的线程中。它拥有一个很重要的变量,isFromEnemy。它标识了该子弹是属于玩家的,还是敌人的,这样可以控制子弹在脱离坦克管束后的运行状态中的行为。其主要功能流程图见图4-9。 checkHit(int x,int y)调用了getTileIndex(x,y)获取当前子弹击中的是什么障碍物,如果返回了false就表示没有击中任何东西。当击中了需要作出反映的物体时,就分别采取措施:击中草时,由于没有定义相关函数,就不会有任何反映,会重合在草上正

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论