基于J2ME的Java游戏坦克大战的开发毕业设计.docx
《基于J2ME的Java游戏坦克大战的开发毕业设计.docx》由会员分享,可在线阅读,更多相关《基于J2ME的Java游戏坦克大战的开发毕业设计.docx(18页珍藏版)》请在冰豆网上搜索。
基于J2ME的Java游戏坦克大战的开发毕业设计
(此文档为word格式,下载后您可任意编辑修改!
)
西南交通大学
Java游戏编程技术课程设计报告
坦克大战游戏编程课程设计报告
年级:
2008级
姓名:
陈锦添
专业:
信息管理与信息系统
分数:
教师签名:
二零一零年六月
摘要
Java良好的跨平台特性在移动平台的开发中显示出了巨大的威力。
Java语言面向对象的优势也使得开发游戏变得非常容易。
随着手机的日益普及、Java功能在移动设备上的实现,Java应用程序产生的手机增值服务逐渐体现出其影响力,对丰富人们的生活内容、提供快捷的资讯起着不可忽视的作用。
本论文着眼于J2ME技术的应用,开发一款可商用的手机游戏程序——坦克大战。
本程序的界面和运作方式继承于日本任天堂公司在20世纪80年代开发的BattleCity游戏,将老少皆宜的经典作品移植到手机上来,为更流行的硬件平台提供应用软件。
本论文介绍了任天堂红白机的软硬件特性、J2ME的相关技术及本程序的结构分析和具体功能的实现。
[关键字]:
J2ME,手机游戏,Java,坦克大战
手机中将Java语言引入,作为一种通用的开发标准,并将向市场推广普及仅仅短短几年,J2ME仍是一种新型的技术,中文资料除台湾出版过相关书籍外,国内相关介绍资源很有限,国内J2ME开发商也屈指可数,名声较响的Digital-Red公司也仅成立于1999年。
本文可算是对新技术的一些尝试,代表对无线平台应用程序推广的一些努力。
目录
摘要-I-
Abstract-I-
第一章程序结构、思想和相关技术-3-
1.1本程序需解决的有关技术问题-3-
1.2程序流程-4-
1.3绘图与MIDP2.0新增的GameCanvas包-6-
1.3.1提供低级绘制的Canvas类-6-
1.3.2Graphics类-6-
1.3.3PNG格式-6-
1.3.4Game包中的新功能-7-
第二章游戏的设计思路-8-
2.1坦克的控制和敌方的智能运行-8-
2.2子弹的运行和控制-9-
2.3RMS数据库系统-10-
2.4内存使用的最佳化-11-
2.5混淆器(Obfuscator)的使用-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.游戏程序是一项精度要求很高的程序系统,因为其代码利用率很高。
一个实时运行的最终作品,每秒都会运行成千上万行程序,绘图事件、键盘事件都会以极高的频率在后台等待响应,若有丝毫的差别都将很容易导致程序在运行不久后可能出现严重错误,甚至死循环。
因此,其逻辑设计应当相当严谨,需将所有可能发生的事件及意外情况考虑在设计中。
2.游戏中为了美观,适用性强,可能需要采用外部文件引入的图片贴图,有关贴图,在MIDP2.0中提供了用于增强游戏功能的game包,使得解决静态或动态、画面背景、屏幕刷新的双缓冲等都有较好的解决方案。
3.己方坦克的运行可以通过键盘响应事件控制,但敌方则因为是自动运行,就需要有一定其一定的智能性;同时,出现在屏幕上的敌方可能会有较多的数量,这需要为每个敌方开辟一个线程以便能让其独立运行。
Java的多线程能力为实现这样的游戏提供了可能。
敌人坦克的运行算法也需要进行适当的设置,以免游戏过于简单,单调。
4.对于双方坦克发出的子弹的控制也需要对其跟踪控制,子弹也需要处在独立的线程中。
敌方子弹仅需要扫描用户坦克,而用户坦克需要在每一步扫描所有的敌方坦克。
这需要对所有的对象有较好的控制。
另外,子弹在运行过程中也需要实时扫描是否碰撞到了相关障碍物或屏幕边界。
如此过多的线程同时在本来效率就不高的KVM虚拟机上运行,也许会导致程序的缓慢。
5.双方的坦克在前进时也需要考虑到是否碰撞到相关物体或对方坦克,以免重叠运行,造成许多物理上不可能的情况,缺乏真实感。
每一次刷新页面、每前进一步都需要将所有的周围环境都进行扫描。
6.游戏的结束、开始、动态信息画面作为构成一个完美程序都是必不可少的重要部分。
良好的用户界面更是吸引用户的硬指标,相关的美术构图也需要有一定的考虑。
7.游戏的地图不可能通过绘图来解决。
否则,不仅难于控制和处理过多的元素,也会因过多的大型图片而不能限制程序的大小,失去手机上程序的原则和Java的优势。
同时,地图关卡不宜保存在手机有限的内存中,而最好采取外部文件的读入读出方法。
8.用户运行游戏时需要有分数记录的可能。
如何采用合理的记分标准,需要进行适当的设计。
记录分数的存储方式也需要有较好的解决方案。
手机中由于处理器和内存空间、存储空间都十分有限,其数据库系统与普通PC大相径庭。
其数据库结构较为简单,被称之为RMS系统。
9.Java是基于虚拟机的半解释型编译系统,其执行效率较C++等完全编译后的程序会低很多,程序如果不进行精简和优化,将可能导致运行的不流畅。
除开发过程中对结构上的控制、变量的使用、算法的优化等优化外,还可以使用混淆器(Obfuscator)进行程序打包后的优化。
以上相关技术细节和整体流程将分别在以下小节阐述。
1.2程序流程
MIDletsuite是MIDP应用程序的最小单位,JAM负责将手机内的MIDletsuite以图形化的方式呈现,让用户能够选取欲执行的MIDletsuite,一旦选取了某个MIDletsuite,操作系统就会激活KVM执行里面的MIDlet。
MIDlet及相关的支持类组成了MIDP应用程序的实际内容。
每个MIDlet都必须继承javax.microedition.midlet.MIDlet这个抽象类。
在MIDP规格中定义了MIDlet的生命周期,以及可以存在的三种状态,包括Paused、Active以及Destroyed,每一个MIDlet在任何时刻只可能处于其中的一个状态。
这三种状态的转换关系如图所示:
本程序采用面向对象的设计模式,对游戏中的所有物体赋予对象的概念和属性。
运行程序后允许用户选择执行选项菜单,在开始游戏后将先从外部文件载入地图文件,对背景的所有物体进行绘图。
在主程序运行的线程中,画面刷新将以一定的频率采用双缓冲技术对屏幕重绘,实时反映整个游戏的进行状态。
用户控制的坦克运行在主线程中,随屏幕刷新的频率而步进。
敌方坦克将在游戏开始时逐渐新增线程,每增加一个敌方对象就新增加一条线程,一旦线程数满到最大值(本程序暂设置为6),就不允许敌人再继续出现。
用户坦克自诞生之时起将拥有一发子弹,子弹虽然开在单独的线程中,但运行结束后(比如撞到相关物体或敌方坦克时)并不结束子弹对象,只是将其线程终止。
用户再次发射子弹时只是将终止的线程再次激活。
在屏幕重绘的主程序中,将在每次的循环中判断若干事件。
如:
用户坦克的生命是否已完全用尽,敌方坦克数是否已经为零,屏幕上的坦克数量是否少于仍剩下的坦克数量等。
以便程序进入相关的分支执行相关的反应代码,结束游戏或统计分数等。
主程序流程如图3-2所示:
程序为需要完成独立功能的需显示的模块设置了单独的类。
TankMain类是继承自MIDlet的控制主程序启动的首先被载入系统的部分。
载入程序后首先启动的是程序介绍的信息画面。
闪过后载入StartChoice类,为用户提供可选择的选项。
在选择开始后,将运行BattleCanvas类中的总流程控制。
它决定了游戏何时该结束,何时分配敌人数量,GameOver字样的闪现规则,地图的绘制及整个游戏的调度。
图1-3是程序中类之间的UML分析图。
敌方坦克与用户坦克的相关功能和具体行为分别定义在EnemySprite和UserSprite类中,它们都继承自TankSprite公共类,以简化程序的代码、理清结构。
在每关的结束或死亡后都将载入ScoreScreen类,统计当前的分数。
如果已死亡或完成所有的关数,程序将用户所得的分数记载到RMS数据库中,进行永久性保存。
载入过程中将对所得分数与以往历史比较,放置到合适的位置中,形成排序。
1.3绘图与MIDP2.0新增的GameCanvas包
1.3.1提供低级绘制的Canvas类
为了能有程序开发人员控制接口的外观和行为,需要使用大量的初级用户接口类,尤其在游戏程序中,几乎完全依赖的就是Canvas抽象类进行绘图。
从程序开发的观点看,Canvas类可与高级Screen类交互,程序可在需要时在Canvas中掺入高级类的组件。
Canvas提供了键盘事件、指点杆事件(如果设备支持),并定义了允许将键盘按键映射为游戏控制键的函数。
键盘事件由键代码指定,但这样控制游戏会导致缺乏通用性,并不是每个设备的键盘布局都适合游戏的操作。
应当将键代码转换为游戏键的代码,以便硬件开发商能定义他们自己的游戏键布局。
本程序中,操纵用户坦克运行的按键都定义为游戏控制键,这样便能适应所有的机器。
1.3.2Graphics类
Graphics类提供了简单的2D绘图功能。
它具有24位深度色彩的绘制能力,以三原色分别各占一个字节表示其颜色。
程序只能在paint()函数中使用Graphics绘制,GameCanvas可调用getGraphics()函数直接绘制在缓冲区上,可以在任何时间请求传输到前台。
其对象会被传给Canvas的paint()函数,以便最终显示。
1.3.3PNG格式
PNG(PortableNetworkGraphics)格式是MIDlet唯一支持的图象格式,PNG具体格式由PNGSpecification,Version1.0定义的。
PNG格式提供透明背景的图象,这对绘制游戏画面和被操纵主角极有帮助。
坦克之间或与障碍物碰撞时就不会因为背景有特定的颜色,显示出的效果像贴上的图片而缺乏真实感,物体之间轻微重叠时最上层图片也不会覆盖超过其有效象素外的部分。
PNG格式图片中包含许多定义其图片特性的冗余部分(Chunks)。
这些代码包含在每一个单独的png格式图象中,然而如果将多个png图象合并在一张幅面稍大一些的整图中,多个chunks就可以得到精简,图片的大小可以得到控制。
使用Image类中的createImage函数可从整图中分割出所需要的元素。
在Game包中的TiledLayer和Sprite类都整合了这样的功能。
本程序中的地图元素都集成在一张tile.png图片中,实现了方便的管理和程序体积的精简。
1.3.4Game包中的新功能
MIDP自2.0以后新增了Game包,为游戏的开发带来了极大的便利。
地图绘制、主角的动态显示、按键的检测、图层的控制等游戏专属的特性都得到了在移动设备上最大的发挥。
LayerManager(以下简称LM)提供控制整体画面层的控制。
它包括了一系列自动获取了代号和位置的层,简化了各层加入游戏画面的过程,提供了自动排序和绘制的能力。
LM存储了一个层的列表,新的层可以用append函数附加、删除和插入。
层的序号相当于坐标的Z轴,0层表示最接近用户视觉,层数越高,离用户越远。
层号总是连续的,即使有中间的层被移除,其他层的序号会作相应的调整以保持整体的完整性。
LM中的ViewWindow控制着与LM相对坐标的可视区域。
改变ViewWindow的位置可以制造出滚动屏幕的效果。
本程序中所有的地图、坦克都采用LM控制,敌方坦克的生成由附加一个EnemySprite对象得到。
唯有界面右侧的计分栏由Graphics类绘制。
Sprite类是继承自Layer的用于存储多桢的基本可视元素。
不同的frame可交相显示,构成动态的效果。
图片可翻转、颠倒、由一个主角图片就可以方便的得到所有方向的显示状态,相比原先只能使用Canvas绘图,需要将所有方向的主角图象都绘制在png图象中简化了许多。
Sprite也可以从整合的图象中读图,读图时将把大图分解为若干等宽等高的小图。
每个小图按照其排列顺序有相应的序号,在程序中调用其序号,就可以绘制出相应的图片。
本程序中的双方坦克、子弹都由Sprite继承得到。
在有些情况下,控制主角的翻转,尤其是多幅图片配合显示的过程,如果将多图的共享定位点设置在通常的左上角,将很不容易控制,因为许多翻转都是以其他点为参考电的(比如,中心点)。
由此,引入参考点的概念。
参考点由defineReferencePixel函数确定未翻转图片状态时的坐标。
默认是(0,0)点,如果需要,可将参考点设置在画面边界之外。
本程序中的坦克的参考点定义在图片正中,以便简便的实现转向等功能。
子弹的参考点设置在子弹底部的中心,因为子弹一出炮筒的时候紧挨着坦克的象素就是其底部中心。
TiledLayer是有一组图象格元素(gridofcells)组成的整块虚拟图象。
该类使不需要高分辨率的图象就能创建大幅图面成为可能。
这项技术通常应用在2D游戏平台的滚动背景的绘图。
一块整图可被分割成等大小的图象格,每块格有其对应的序号,按照行列递增。
多块格可由大块同时替换组合而模拟动态的背景,这不需要逐块替换所有的静态图象格而显得非常方便。
本程序中的地图即为游戏背景。
每块障碍物都有其响应的代号,其中,用户需保护的总部因为体积稍大,使用了四块图象格显示。
地图背景分为20*22个图象格,每个格使用一个字节表示其中的障碍物,图象文件存储在外部文件中,以16进制的整数串表示,因此每个地图的大小为固定的440字节。
如果整块地图均由绘图产生,将导致体积迅速增加,且对坦克与障碍物的碰撞也难以检测。
J2ME中并没有J2SE中的File类,获取外部文件的手段很有限,仅仅在Class类中提供了一个getResourceAsStream函数,将外部文件获取为输入流,再由InputStream的继承类读出。
第二章游戏的设计思路
2.1坦克的控制和敌方的智能运行
GameCanvas中提供了与以往MIDP1.0不同的键盘采样功能。
Canvas类中采取响应键盘事件的方法,每次执行周期时会读取keyPressed函数中需执行的代码。
这样的机制并不适合某些游戏场合。
在某些不支持keyRepeat功能的设备上,反复执行的按键,比如发射子弹,将不能因为长时间按压而自动重复,这样就需要用户高频率的手动击键,这在操纵空间非常有限的移动设备上是非常困难的。
同时,事件的执行周期也并不一定适合游戏的场合,也许需要更高频率执行的按键却只能在指定的周期内规律的响应。
对此,针对游戏的开发,Game包提供的键盘状态功能将显得十分有效。
GameCanvas提供getKeyStates函数可获取当前键盘上的信息。
将以位的形式返回键盘上所有键的按与释放的状态,当bit为1时,键就是被按下的状态,为0时则为释放状态。
只需要此一个函数的返回值就可以返回所有键的状态。
这保证了快速的按键和释放也会被循环所捕捉。
同时,这样的机制也可检测到几个键同时按下的状态,从而提供斜向运行等相应功能。
敌方按照规则不能和用户坦克重合,则它每行走一步就需要把用户坦克扫描一次,判断其是否碰撞到了用户的坦克。
Sprite类中提供了collidesWith函数,用于判断是否与某个TiledLayer、Sprite、Image的对象有图象上的重合(即游戏中的碰撞)。
然而不能仅仅将用户坦克作为其Sprite参数传递给敌人的类进行判断。
因为如果发生碰撞,collidesWith成立,则两辆坦克已经发生了图象重合,违反了规则,甚至若再进行collidesWith判断的话,其结果将永为真。
为了提前预知碰撞,可以将所有坦克的碰撞范围设定为一个比坦克图片稍大一些的矩形,此矩形仅在坦克前方比坦克图形多出一个象素。
在多出的11个象素中,按照每个象素依次检查此象素是否于外界发生碰撞,如果不是按照象素检查,则当坦克与障碍物错位并同时与两种物体接触时将有可能忽略检测其中的一样物体。
这样,就可以提前一步判断。
如果发生碰撞,则坦克应当选择掉转方向,此时,两辆碰撞的坦克又因为其矩形区域不重合而不符合collidesWith的条件,就可以继续正常运行了。
敌方坦克由于需要具有一定的智能性,以便对玩家攻击,使之具有一定的可玩性。
敌人可以自动行走,但是应当在以下适当的情况下转向:
首先是是否超出界面的边界,其次是是否与地图障碍物发生了碰撞,再次是是否与用户坦克发生了碰撞。
需要指出的是,当发生阻碍不能在不变方向的情况下继续行走时,并不一定立即需要采取转向的对策。
如果一定发生转向,试想,当敌方碰到玩家时,如果它立即转向,将不会对玩家发射射向他的子弹,就不构成任何威胁,当然,也不能永远不转向。
本程序设置为:
当碰撞到障碍物或边界时立即转向,但碰到玩家坦克时需要有一个等待的时间,这个时间由碰撞前随机取得的在某方向上的持续行走步数决定,当发生坦克间碰撞时,此随机数将在下一次循环前减少为原来的23,这样就实现了加快转向的时间,避免死锁在一个方向上静止的停留过长的时间。
另外,坦克的发炮间隔和转后的具体方向都由随机数决定。
坦克之间由以上道理也不会发生重叠,但当某坦克正从上方生成而正巧有另一辆阻碍在其生成点处,这将导致不可避免的重合。
这是允许的,但需要对他们标注状态,即当坦克刚出现时暂时允许重合,一旦在某个时间他们脱离了重合状态,就不能在允许重合,如果不设置这样的判断,刚出现的坦克将会因为受到阻塞而永远不能前进,坦克将混成一团。
本程序中并未使用过多复杂的人工智能算法,如有时间,将可能再此方面加以完善。
2.2子弹的运行和控制
每一个坦克都有他自己的一颗子弹,这颗子弹在任何一辆坦克被构造时就一直存在,直至此坦克生命的结束,子弹的再次只是将屏幕上暂时掩盖的图象重新置于坦克炮筒才恰当位置,并使其显示出来,这与现实中每个子弹都是单独的个体有所不同。
子弹所需要完成的任务有:
它是一个继承了Runnable虚类的可运行单独线程的对象。
在其出现在屏幕上的运行周期中,每一步都需要循环检测以下条件:
是否与某坦克发生了碰撞,即击中了这辆坦克。
子弹使用的是象素级的碰撞检测,因为子弹的图片形状不规则,如果使用矩形碰撞检测,将有可能在子弹尚未接触到物体时就已返回碰撞的真值。
分为两种情况,如果此子弹来自于敌方,将只检测玩家坦克,因为敌方之间的子弹必须允许可以透明的穿过,以保证不会在敌人之间发生子弹的消减。
如果来自玩家,则每一步需扫描所有的敌方坦克,检查是否发生碰撞,这可能会花费不少的CPU时间。
其次,子弹之间需要检测是否碰撞。
敌人之间显然,如上已经提过,不需要检测,但敌人与玩家之间应当可以互相消除子弹,以便在狭窄的路口中仍有存活的机会。
玩家的子弹需要在每一步检测所有敌人的子弹的运行状态。
这样较多的运算也将不可避免的耗费大量CPU时间。
子弹对不同障碍物将有不同的反映。
对砖墙将有能力将其击毁,使之在画面上消失;对水泥钢筋将不能发生作用,子弹也不能通过;对于河流,坦克不可以通过,但子弹可以;对于草丛,子弹和坦克都可以通过。
2.3RMS数据库系统
MIDP为MIDlets提供了一种永久存储和后来读出数据的数据库解决方案,被称为RecordManagermentSystem(RMS),是一种类简单的基于记录的数据库。
很显然,手机上的数据库系统不可能有PC上的强大功能。
微小的存储空间也限制了它们的结构不能过于复杂。
RMS是专门针对移动设备的服务的。
RMS包中包括RecordStore类。
在一个MIDletsuite包里的所有MIDlet都允许创建多个记录集,只要它们赋有不同的名称。
当MIDlet包从平台中被移除后,所有与该包有关的的记录集都同时会被移除。
同一个包内的MIDlets可以直接互相访问它们的记录集,不同包内也可产生共享,但这需要有包的授权属性决定。
访问模式会在准备提供共享的RecordStore被建立时被创建。
访问模式允许私有使用或访问。
RecordStore的API采用了时间戳的概念,其长整型变量由System的currentTimeMillis()函数返回决定。
Recordstore每次被修改后都会自动在其属性上附加上时间戳,这为同步化引擎和程序的控制都极为有效。
记录是字节数组。
开发者可以利用InputStream的派生类DataInputStream、DataOutputStream以及ByteArrayInputStream、ByteArrayOutputStream将不同种类的数据类型打包,以字节流的形式发送和接收。
区别记录的唯一标记是他们的ID值,作为记录集的主键。
第一项记录的ID是1,其后的每个记录ID递增。
Record是以字节为基本单位来存放的,所以所有要写入record的数据都必须先将其转为字节才能写入,从record所读出来的数据也是字节,必须将其转换为原先写入时的数据类型才有意义。
然而读取或写入的字节数组都只能代表一个字段的信息,如果需要读取或写入多个字段就必须要将数据转换成字节信息,并且提供适当的机制来分隔这些信息。
主要有两种方法:
1.标记法。
将所有要存放的数据用字符串表示,但是在字段和字段之间以一个特殊的符号作为分隔。
符号不能和字段内的数据相同的字符。
2.利用输入输出流
这一种方法较上一种复杂,但是较为实用。
方法一中所有的字段只能以字符串的形式存储,要对这些字段作进一步的处理非常麻烦。
利用输入输出流可以写入及读取不同数据类型的数据,做法是在写入数据时先将一个DataOutputStream数据流对象串接到一个ByteArrayOutStream数据流对象,然后再依字段的数据类型用writeInt()、writeBoolean()等方法写入,最后把ByteArrayOutputStream内的元素数据写入record中。
反之若要读取数据,则先要串接一个DataInputStream对象和ByteArrayInputStream,依字段的数据类用readInt()、readBoolean()等方法读取。
本程序中主要存放在永久区的内容为用户得到的最高分数的记录。
一共可以存储10条最高分。
每次有新的更高的记录就会插入进相应的位置,将最低一名排挤出记录。
在输入记录前,要求用户在TextField框中写入他自己的名字。
返回的getString可以将名字输送给字节流。
因为每个记录包括用户名和分数,因此需要使用多字段的方式编入。
打印到屏幕上时,记录ID号即为排名,因此将显示三项数据。
2.4内存使用的最佳化
通常在MIDP应用程序的手机执行环境中,所牵涉的内存有下列三种:
﹡应用程序存储内存
﹡RecordStore存储内存
﹡执行时期内存(JavaHeap)
其中前两种是持久性的内存,关闭电源后还能保持数据的正确性,通常这两种内存所能存储的容量是合并计算的,这个上限对每种手机都不一样,大部分在一两百KB内。
在这样的情况下需要在不影响原有功能的情况下适当的缩减JAR文件的大小,除了可以克服内存空间的限制外,也能大幅度缩短下载的时间(费用也降低了),势必会有更多的人愿意下载所开发的程序。
其方法有:
第一,就是尽量缩短命名的长度。
在应用程序内,对于所建立的类、接口、方法及变量名而言,都需要赋予一个识别的名称,所命名的名称每多一个字符就会在类文件内多产