当前位置:文档之家› 基于Java+Me的手机游戏毕业论文

基于Java+Me的手机游戏毕业论文

1.绪论

1.1手机软件现状

在信息社会中,手机及其他无线设备越来越多的走进普通百姓的工作和生活,随着信息网络化的不断进展,手机及其他无线设备上网络势在必行。

1.1.1传统手机存在以下弊端:

1. 传统手机出厂时均由硬件厂商固化程序,程序不能增加、删除,有了错误也不能更新、修改,若要增加新功能必须另换一部手机。

2. 传统手机访问互联网是通过WAP(Wireless Application Protocal),所有网络资源必须接通网络才能在线访问,非常耗时、费用亦很高。

1.1.2Java技术在无线应用方面的优势非常明显:

1. 应用程序可按需下载,而不是购买由硬件商提供的套件,可升级空间大。

2. Java技术提供了一个类库,它使的应用开发商可以创建更为直觉、丰富的用户界面(GUI);

3. Java技术使网络带宽的应用更为有效,因为应用程序可以下载到器件上,并在本地运行,仅仅是在连接到服务器时才会占用网络带宽。

基于以上分析,Java手机将是未来手机的发展方向,是业界的热点。

1.2 J2ME介绍

虽然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))

1.3 手机游戏应具有的特征

一个成功的手机游戏大多具有以下特征:

易于学习: 既然手机游戏面向的是普通消费者而不是计算机专家,那么他们不可能深入的学习游戏技巧。消费者不会花几个小时去研究一个3元的手动操作的游戏。保持游戏的简单是最基本的要求。

可中断性: 多任务处理是手机生活方式的基本特征。手机用户常常在任务(如等一个电子邮件或者等车)之间有一小段时间。而游戏、日历管理、通讯和工作数据访问使用的是同一个设备。所以一个好的手机游戏应该提供短时间的娱乐功能,并且允许用户在游戏和工作模式之间顺利切换。

基于订阅:手机游戏的盈利成功取决于他们巨大的使用量。一开始开发和设计每个游戏都是昂贵的。如果一个手机游戏开发者要赢利的话,重要的是:同一个游戏引擎,多个标题,基本的故事情节类似。基于订阅的游戏是不断产生收入的最好方法。

丰富的社会交互: 不管一个游戏设计得多好,只要玩家找到了它的根本模式或者玩完了所有的游戏路径很快就会厌烦这个游戏。对于一个基于订阅的游戏,重要的是与别的玩家合作以增强所玩游戏的智力和随机性。在今天纷繁复杂的多玩家游戏中具有丰富社会交互的游戏证明是成功的。

利用手机技术的优点:巨额的手机技术研发费用都花在提高设备和网络的可用性和可靠性上面。因此,手机设备硬件和网络协议与桌面/控制台世界(如全球定位系统(GPS)扩展、条形码扫描仪、和短消息服务(SMS)/多媒体信息服务(MMS)通讯)有着非常大的差别。好的手机游戏应该利用那些更新的设备特征和网络基础设备的优点。

第一章中介绍了手机在无线应用方向的当今概况,并介绍了游戏业务在当前社会的发展潜力。分析了J2ME在手机软件开发中起的重要作用,描述了本论文的相关背景。

2.开发环境及相关技术的介绍

2.1 开发环境

操作系统:Microsoft Windows XP

程序语言:Java 2

开发包:Java(TM) 2 Standard Edition (1.5.0)

Sun Micro. J2ME Wireless Tool Kit 2.2

IDE: Eclipse 3.0.1

EclipseMe 1.7.9

混淆器: Proguard 4.4

2.2 Java语言特点

2.2.1. 平台无关性

Java引进虚拟机原理,并运行于虚拟机,实现不同平台之间的Java接口。Java的数据类型与机器无关。

2.2.2. 安全性

Java的编程类似C++,但舍弃了C++的指针对存储器地址的直接操作,程序运行时,内存由操作系统分配,这样可以避免病毒通过指针入侵系统。它提供了安全管理器,防止程序的非法访问。

2.2.

3. 面向对象

Java吸收了C++面向对象的概念,将数据封装于类中,实现了程序的简洁性和便于维护性,使程序代码可以只需一次编译就可反复利用。

2.2.4. 分布式

Java建立在TCP/IP网络平台上,提供了用HTTP和FTP协议传送和接收信息的库函数,使用其相关技术可以十分方便的构建分布式应用系统。

2.2.5. 健壮性

Java致力与检查程序在编译和运行时的错误,并自动回收内存,减少了内存出错的可能性。Java取消了C语言的结构、指针、#define语句、多重继承、goto语句、操作符、重载等不易被掌握的特性,提供垃圾收集器自动回收不用的内存空间。

2.3 关于ECLIPSE

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的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。

2.4 关于Wireless Tool Kit

WTK(Wireless Tool Kit)是Sun公司针对J2ME推出的用于手机和Palm等移动设备的开发包,是除手机厂商的专用开发包外唯一的手机模拟器开发包。它通用性高,开发出的应用程序可保证能运行在大部分设备上,而不像专用厂商具有一定的不兼容性。虽然它没有强大的功能和完善的调试手段,但它提供运行模拟器的最基本组件,是其他IDE需集成采用的必备元素。

2.5 Java Appication Manager

手机中负责调配程序运行资源的管理后台是Java Application Manager。它所使用的传输媒体可以是红外线、网络、以及其他可用来传输的媒体。Java Application Manager会从网络上下载代表该Application Suite的JAR档,接着在手机上安裝此MIDlet Suite,然后在手机开始执行该应用程序。

2.6 本章小结:

第二章介绍了Java语言的特点、本程序的开发环境及其相关工具的原理和使用。

3 程序结构、思想和相关技术

3.1 本程序需要解决的主要技术问题

3.1.1代码利用率

游戏程序是一项精度要求很高的程序系统,因为其代码利用率很高。

一个实时运行的最终作品,每秒都会运行成千上万行程序,绘图事件、键盘事件都会以极高的频率在后台等待响应,若有丝毫的差别都将很容易导致程序在运行不久后可能出现严重错误,甚至死循环。因此,其逻辑设计应当相当严谨,需将所有可能发生的事件及意外情况考虑在设计中。

3.1.2图片问题

游戏中为了美观,适用性强,可能需要采用外部文件引入的图片贴图,有关贴图,在MIDP2.0中提供了用于增强游戏功能的game包,使得解决静态或动态、画面背景、屏幕刷新的双缓冲等都有较好的解决方案。

3.1.3控制问题

玩家坦克的运行可以通过键盘响应事件控制,但敌方则因为是自动运行,就需要有一定的智能性;敌军的运行算法也要进行相关的设置,已免游戏过于简单。

3.1.4可玩性

对于双方发射的子弹应该赋予不同的速度,同时,程序应该设定敌军的子弹不与敌人的坦克进行碰撞检测,已增加游戏的可玩性。

3.1.5碰撞问题

双方的坦克在前进时也需要考虑到是否碰撞到对方坦克,以免重叠运行,造成许多物理上不可能的情况,缺乏真实感。每一次刷新页面、每前进一步都需要进行相关的碰撞检测。

3.1.6界面问题

为了增加界面的美观,在程序中添加了白云。由于手机屏幕大小有限,所以白云的数量和出现的位置要经过相关的设置,才能实现白云不规则出现的效果。

3.1.7地图问题

游戏的地图不可能通过绘图来解决。否则,不仅难于控制和处理过多的元素,也会因过多的大型图片而不能限制程序的大小,失去手机上程序的原则和Java的优势。

3.1.8执行效率

Java是基于虚拟机的半解释型编译系统,其执行效率较C++等完全编译后的程序会低很多,程序如果不进行精简和优化,将可能导致运行的不流畅。除开发过程中对结构上的控制、变量的使用、算法的优化等优化外,还可以使用混淆器(Obfuscator)进行程序打包后的优化。

3.1.9人性化

游戏的结束、开始、动态信息画面作为构成一个程序都是必不可少的重要部分。良好的用户界面更是吸引用户的硬指标,相关的美术构图和人性化设置也需要有一定的考虑。

以上相关技术细节和整体流程将分别在以下小节阐述。

3.2 程序流程

MIDlet suite是MIDP应用程序的最小单位,JAM负责将手机内的MIDlet suite以图形化的方式呈现,让用户能够选取欲执行的MIDlet suite,一旦选取了某个MIDlet suite,操作系统就会激活KVM执行里面的MIDlet。MIDlet及相关的支持类组成了MIDP应用程序的实际内容。而每个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类为玩家子弹类。

3.3 Canvas类

为了能有程序开发人员控制接口的外观和行为,需要使用大量的初级用户接口类,尤其在游戏程序中,几乎完全依赖的就是Canvas抽象类进行绘图。从程序开发的观点看,Canvas类可与高级Screen类交互,程序可在需要时在Canvas中掺入高级类的组件。Canvas 提供了键盘事件、指点杆事件(如果设备支持),并定义了允许将键盘按键映射为游戏控制键的函数。键盘事件由键代码指定,但这样控制游戏会导致缺乏通用性,并不是每个设备的键盘布局都适合游戏的操作。应当将键代码转换为游戏键的代码,以便硬件开发商能定义他们自己的游戏键布局。

3.4 Graphics类

Graphics类提供了简单的2D绘图功能。它具有24位深度色彩的绘制能力,以三原色分别各占一个字节表示其颜色。程序只能在paint()函数中使用Graphics绘制,GameCanvas 可调用getGraphics()函数直接绘制在缓冲区上,可以在任何时间请求传输到前台。其对象会被传给Canvas的paint()函数,以便最终显示。

3.5 MIDP1.0技术下的绘制背景技术

在没有MIDP2.0前,进行游戏绘图一般需要手动编程使用双缓冲。需要在paint()方法内将所想要画的图形画在一张预先准备好的背景上,等所有绘图操作都完成后再将背景的数据拷贝到实际的屏幕上。Image类提供了一个建立背景的静态方法createImage(int width, int height),再利用getGraphics()方法取得属于这个背景的Graphics对象,所进行的绘图操作都会作用在背景上,等到全部的绘图操作完成后,再调用drawImage()方法将背景的数据复制到实际显示的屏幕上。

这样的技术在绘制动画时特别有用。绘制动画时经常需要不断地更新画面,而更新画面的操作就是先将屏幕以fillRect()的方式清除,再将下一张图片画在屏幕上,然而反复的清除及重绘会造成屏幕的闪烁现象(flicker),因此使用双重缓冲的好处就是在背景进行这个清除及重绘的操作,再将完成的绘图拷贝到屏幕上,由于用户看不到清除的操作,因此就不会出现闪烁的现象了。不过在某些MIDP的实现上已经加上了双重缓冲的支持,因此在处理前应先利用Canvas类的isDoubleBuffer()方法来判断。

3.6 MIDP2.0新增的GameCanvas包

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游戏平台的滚动背景的绘图。一块整图可被分割成等大小的图象格,每块格有其对应的序号,按照行列递增。多块小格可由大块同时替换组合而模拟动态的背景,这不需要逐块替换所有的静态图象格而显得非常方便。

3.7 PNG图片格式

PNG(Portable Network Graphics)格式是MIDlet唯一支持的图象格式,PNG具体格式由PNG Specification,Version 1.0定义的。PNG格式提供透明背景的图象,这对绘制游戏画面和被操纵主角极有帮助。坦克之间或与白云碰撞时就不会因为背景有特定的颜色,显示出的效果像贴上的图片而缺乏真实感,物体之间轻微重叠时最上层图片也不会覆盖超过其有效象素外的部分。

PNG格式图片中包含许多定义其图片特性的冗余部分(Chunks)。这些代码包含在每一个单独的png格式图象中,然而如果将多个png图象合并在一张幅面稍大一些的整图中,多个chunks就可以得到精简,图片的大小可以得到控制。使用Image类中的createImage 函数可从整图中分割出所需要的元素。在Game包中的TiledLayer和Sprite类都整合了这样的功能。本程序中的地图元素都集成在一张beijing.png图片中,实现了方便的管理和程序体积的精简。

3.8 玩家的控制方式和敌人方的智能运行

GameCanvas提供getKeyStates函数可获取当前键盘上的信息。将以位的形式返回键盘上所有键的按与释放的状态,当bit为1时,按键就是被按下的状态,为0时则为释放状态。只需要此一个函数的返回值就可以返回所有键的状态。这保证了快速的按键和释放也会被循环所捕捉。同时,这样的机制也可检测到几个键同时按下的状态,从而提供斜向运行等相应功能(本程序没有实现斜上运行功能)。

程序运行时应该对玩家是否开出屏幕的范围进行检测,如果开出屏幕,就应该重新设定玩家的位置。

玩家坦克被击中后,为了平衡游戏的可玩性,玩家将有短暂时间无敌,即不进行碰撞

检测,同时在屏幕右上角显示无敌时间。

根据游戏设定,敌人不能与玩家坦克重合,则他每走一步都需要检测一下是否与玩家碰撞。Sprite类中提供了collidesWith函数,用于判断是否与某个Sprite、TiledLayer、Image 的对象有图象上的重合(即游戏中的碰撞)。同理,还需要检测玩家子弹与敌军、敌军与玩家子弹是否碰撞。如果发生碰撞,将相关精灵图片替换为爆炸图片。

敌人需要具有一定的智能性,以便对玩家攻击,使游戏具有一定的可玩性。敌人可以在适当时候转向或者开炮火,同时,程序应该检测敌军是否开出了界外。在普通敌军中,有一组敌军的其中一辆具有跟踪功能,其原理为:当其进入屏幕后,根据玩家坦克的X、Y坐标不断调整自己的X、Y坐标,已达成跟踪的效果。由于线程的关系,敌军的改变方向有时并不是实时的,这就可以使玩家有躲开撞击的可能,增强了游戏的可玩性。

在游戏进行中出现的大型飞机为BOSS,由于其不可能立即被击落,所以应该设置其的运行方法,理论上讲还是根据玩家坦克的坐标,但是,在此设置一个标志位,使得敌人在取得玩家位置后即开始玩家方向运动,这期间,将不执行取得玩家位置重设方向的步骤。这样做,使得大BOSS飞机的运行具有不确定性。

3.9 子弹的运行和控制

玩家的子弹是个精灵数组,有9个元素,表示玩家一次最多可以发射3组9发子弹,对于一个完整的游戏来讲,应该根据关卡的不同而给予玩家不同的坦克,坦克性能的差别在于子弹的射程不同。由于本游戏仅有一关,所以子弹速度设定的差别没有体现出来。当玩家一次发射了3组子弹,而这3组子弹并没有消失时,玩家将无法发射子弹。

使用每组子弹的第一发作为与敌人进行碰撞检测的精灵,同时相关的标志位也设在第一发子弹中。如果玩家子弹与敌机相撞,则敌机消失时,子弹精灵的图片替换为爆炸图片,直到第二次发射该组子弹时,才将图片替换为子弹图片。

3.10 内存的优化

手机内存空间小,所以在程序设计时应该注意以下几点,以尽量减少内存的使用:

(1)尽量缩短命名的长度。在应用程序内,对于所建立的类、接口、方法及变量名而言,都需要赋予一个识别的名称,所命名的名称每多一个字符就会在类文件内多产生一个字节,对于一个较复杂的应用程序而言就会增加为数不小的数据量。所有这些可以借助混淆器来帮助实现

(2)所有代码写为一个类。

(3)只使用一个线程。

(4)尽量不使用静态变量。

(5)将PNG图片合并成一张,减少图形数据的大小。将PNG格式的小分辨率图象合并在一张大的高分辨率图象中,由于减少了头文件的大小,将比合并前的总大小减少许多。

3.11 内存检测器

Wireless Tool Kit提供了许多在运行时监视运行状态的工具。包括内存状况的检测(手机上的内存空间十分有限,必须时刻关注机载内存是否大于程序所能使用到的最大可能的内存空间),网络状况的检测,运行函数的跟踪等。内存检测器是内存跟踪测试随时间变化的调试器。其中,允许强制垃圾回收(Garbage Collection)。由于Java语言中,不像许多其他的如C++语言,不需要指定回收函数中特定不使用的资源,资源回收机制将自动清空无效变量占用的空间。在程序运行中也可以调用System类的gc()函数手动收回废弃的内存。

3.12 关于混淆器

Java语言并没有完全编译成二进制可执行文件,编译出的.class文件是一种介于源程序和二进制之间的一中基于半解释的字节码,需要虚拟机来执行。它包括了所有的信息。然而这样会导致.class很容易被反编译为源代码,从而不能保护作者的知识成果。目前流行的如decode,JAD等反编译工具可以以很快的速度生成源文件。如果不加以施行有效的措施,将造成严重的后果。由此引入混淆器的概念。混淆器将代码中的所有变量、函数、类的名称变为简短的英文字母代号,如果缺乏相应的函数名指示和程序注释,即使被反编译,也将难以阅读。

混淆器的作用不仅仅是保护代码,它也有精简编译后程序大小的作用。由于以上介绍的减少变量、函数的命名长度的关系,编译后也会从.class文件中减少这些冗余的信息。混淆后,体积大约能减少25%,这对当前费用较贵的无线网络传输是有一定意义的。

3.13 本章小结

第三章中介绍了程序的流程、相关技术的思想及其在本程序中的应用。对游戏基本算法等做了详细叙述。具体算法的代码实现和详细流程将在下章介绍。

4 程序分析和具体实现

4.1 游戏进入前的选择

每个MIDlet 程序都必须有一个主类,该类必须继承自MIDlet 。它控制着整个程序的运

行,并且可以通过相应函数从程序描述文件中获

取相关的信息。该类中拥有可以管理程序的创建、

开始、暂停(手机中很可能有正在运行程序却突

然来电的情况,这时应进入暂停状态。)、结束的

函数。本程序主类为tkdz ,并实现接口

CommandLIstener 。

首先显示的是游戏的背景介绍(图4-1),为

此,在类tkdz 定义Form 类对象a ,在startApp ()

函数中判断isSplash 是否为真,如果为真的话,

将创建Form 类的实例a ,并且调用append ()方法

在表单上放置StringItem 类的实例以显示游戏背

景信息。使用语句ok =new Command ("ok ",Command .OK ,1);实例化用

addCommand ()命令建立ok 命令与

Form 之间的关联,调用

setCommandListener ()命令使Form 与CommandListener 建立关联。调用Displayable

seturrent ()

函数显示背景介绍窗口。

图4-4gameScreen 类主要关系流程图

图4-1 图4-2

当玩家点击ok后将调用display.setCurrent(menuscreen)以显示游戏菜单menuscreen (图4-2)类menuscreen继承自Canvas类,并实现接口Runnable和CommandListener。在类menuscreen中定义了lowColor和highColor、highBGColor三个整型变量及布尔型变量co。其中lowColor赋值为0x000000FF,代表兰色,higColor赋值为0x00FF0000,代表红色,highBGColor赋值为0x00CCCCCC,代表兰灰色,即背景条。当玩家按住上或下键时,在函数keyPressed(int code)中的整型变量menuIndex相应的减1或加1,相应的,在paint()函数中会根据menuIndex绘制选项是否被选中。在函数run()中,如果co为真,则不停的repaint(),设置co的意义在于,当进入游戏主画面后,co赋值为false,以终止绘制选项的repaint(),提高游戏速度。当移动选项条到某项,并点击ok时,在commandAction()方法中根据menuIndex的值判断选择了哪个选项,列如当选择“关于”时,将调用tkdz类中的renwuShow()方法以显示”关于”界面(图4-4),在renwuShow()方法中,Form类对象a=null,表示清空Form,并重新调用用append()方法在表单上放置StringItem类的实例以显示游戏关于信息,“帮助”界面(图4-3)的显示与”关于“界面相同,只不过调用的是helpShow()方法。

图4-3 图4-4

4.2 mybullets类

在介绍游戏主类gameScreen类之前,应该先简要说明一下玩家子弹类mybullets类,实际上,mybullets类是应该删除的,其要实现的功能应该放在gameScreen类中,但是由于设计游戏的过程也是一个学习的过程,而在当时,我并没有意识到这一点。

Mybullets类继承自Sprite类,以实现玩家子弹的相关功能。首先,创建子弹状态数组private int[][] bullets,其中,[i][0]代表子弹的X坐标,[i][1]代表子弹的Y坐标,[i][2]代表子弹Y方向速度,[i][3]代表子弹存活状态(由于此类是在早期设计的,而之后子弹存活状态使用了子弹射程作为标志位,所以其并没有起到作用。

mybullets类在gameScreen中建立了对象数组huokebullet[9],代表玩家所能发射的9发子弹。

4.3 游戏逻辑及gameScreen类

gameScreen类是游戏的主类,决定着敌人何时出现,控制着敌人出现的方法,判断敌人及玩家是否被击中等。它运行在独立的线程中,以恒定的频率刷新画面。本程序设置为1/20秒。其主逻辑如图4-4所示。

4.3.1 gameScreen类所实现的功能

gameScreen类要实现地图的滚动、敌军的相关属性、玩家的相关属性等功能。gameScreen类包括了LayerManager,这样所有静态和动态的图象都不需要手动刷新,只需要在LayerManager中加入所有的需要控制的精灵,在统一由LayerManager刷新即可, 因此,在gameScreen中创建LayerManager的对象lm,并在构造函数中实例子化。

其他精灵类的对象如敌军、玩家、玩家的子弹、敌人的子弹、BOSS及BOSS所属的子弹都需在gameScreen()类中建立相应的对象,并在构造喊数中实例化,且由lm.appned()方法添加到LayerManager类对象lm中。

4.3.2 地图的创建

由于手机存储空间的限制,不可能将整张地图完整地存储在手机中,为了节约空间,往往提出地图中相同的图片组成一张PNG格式的图片,然后象拼图一样拼出地图来,专业的游戏设计者往往自己写一个地图编辑器,以使拼图过程不是那么痛苦。

创建地图就需要使用TiledLayer。TiledLayer指的是由一块一块类似用瓷砖拼凑起来的画面。地图实际即为TiledLayer的一个对象。先利用TiledLayer的构造函数建立TiledLayer,根据构造函数的参数可以给定Cell数组的大小,并且地图图片切割成等尺寸的画面,并调用setCell()设置具体的图象格内容,地图图片如图4-5所示。

(图4-5)

因此,创建一个返回TtiledLayer的方法createBackGround(),以便在gameScreen()的构造函数中调用。在方法中,定义整型数组map1[]以存储Cell的索引值。并使用

tiledLayer.setCell(column,row,map1[i])设定TtiledLayer的内容,以形成地图。其中i的值由循环for(int i=0;i

画出地图后,由lm.append()将地图添加到LayerManager类对象lm中。由于地图位于Layer的最低层,即离用户视线最远的层,所以Tiledlayer最后一个被添加到lm中。4.3.3地图的移动

根据游戏的设定,游戏中地图是向下移动的,实现此功能的方法如下:首先,在使用createBackGround()函数创建地图数组时,用(row+1)*16-getHeight()语句对整型变量row2赋值,其中row+1代表地图有多少列,16为地图片的高度,而减去getHeight()是因为要留出一个屏幕的可视区域,由于J2ME规定坐标系中下方向为正,所以使用语句y1=-row2将row2的数值变为负数。其次render()函数中,使用lm.setViewWindow(0,0,getWidth(),getHeight()+10000)设定可视区域的范围,(0,0)表示View Window的起始坐标,(getWidth() ,getHeight()+10000)使用lm.paint(g,0,y1)决定View Window从屏幕的哪里画起。在run()函数中的while(conti)中,使用语句y1=y1+1使得每次绘图都使地图下移1个象素。

4.3.4 gameScreen类的构造函数

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。

4.3.5 关于commandAction()方法

每个创建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()将重新产生一个线程继续执行相关的运算和画面绘制。

当玩家通关时或者任务失败时,将显示相应信息,并使用上面的方法将“暂停”键变为“返回”键盘,当点击“返回”键时,将返回主菜单选项,调用类tkdz里的方法menuscreensecond(),在此方法中,实例化一个MenuScreen类对象,并且使选项“新游戏”改变为”重新开始“(图4-6)。完成此项功能的语句子为MenuScreen.mainmenu[0] =”重新开始”。当选择”重新开始“时,使用gamescreen=new gameScreen(this)将使所有变量重新被初始化,如地图的绘制、敌人出现位置的重置、敌人的数量、玩家的当前位置等。使用gamescreen.start()重新开始程序的循环。

图4-6 图4-7

图4-7 图4-8

4.3.6 Sprite类对象的碰撞检测及相关属性

游戏进行中,即在while(conti){ }中,需要进行玩家、子弹与敌军及敌军子弹与玩家的碰撞检测,即使用函数collidesWith(Sprite,boolean)。由于设计的问题,玩家发射的子弹与普通敌机的碰撞检测被写在了mybullets类中,并且只检测第一发子弹是否与敌人相碰撞,如果碰撞为真时,则使用setVisible(false)函数将敌机隐藏,使用setImage()函数将子弹精灵的三张图片置换为爆炸图片。当敌人剩余敌军消失后,即所有的敌人都开出了屏幕后,使用函数setVisible(true)将敌机重新设置为可见。在按“开火”键时,使用setImage()函数将huokebullets重新设定为子弹图片。

敌军与玩家的碰撞检测原理同上,都是使用的collidesWith()函数,遗憾的是,我在写这段代码的时候,并没有考虑设置玩家有4次机会,所以对敌军setVisable(fasle)了,而将玩家的坦克换成了爆炸图片,之后,添加了玩家4次机会这个功能,由于玩家被击落后会重新从屏幕下方进入屏幕,所以爆炸的图片一闪而过,效果不是很好。

4.3.7 玩家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,即以上这些设置只执行一便。

在if(planert==2)判断语句中,使用语句move(0,-2)使坦克自己向上运动,使用

if(c1.getY()<(planepo-24))判断坦克是否到达屏幕最下方(planepo是屏幕下边缘的坐标),如条件为真,则将inputno设置为1,表示键盘可用,将planert赋值为3,使其不再执行以上各步。

4.3.8 input()

input()函数的作用是检测用户的输入。首先使用if(inputno==0)判断用户的输入是否被禁止,如为真,则用户输入不被禁止。其次,调用getKeyStates()查询按键的状态。当玩家按方向键时,玩家就向不同的方向运行,这需要使用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

程序循环运行时no--,当一次发射了三组子弹后,只有某一组子弹消失,即no等于1后才能继续发射子弹。现在看来,玩家发射子弹的设置是完全失败的,降低了效率。

4.3.9 render()和renderboss()

在方法render()过程中,除了要重绘坦克、地图、子弹外还要在上方绘制关卡信息、战果、玩家坦克数、及无敌状态时的无敌时间、大BOSS生命条等。首先使用lm.setViewWindow()和lm.paint()设定可视范围ViewWindow和从哪里画起(见4.3.3)。

其次,使用g.drawString()绘制屏幕上方的关卡信息、战绩、玩家剩余生命标志。其中根据playerno的值绘出玩家的坦克标志数(应该有更好方法,但是没有想到 )当每次刷新绘图页面时,应使用GameCanvas的flushGraphics()将屏幕后台的缓冲区内的图像刷新到前台来(flushGraphics()应该写在render(){ }的最后)。renderboss()方法重绘的是关尾的精灵cboss、相关信息等,与render()的区别在与于函数lm.paint(g,0,0),起始坐标是不可变的,即,关尾的地图背景是不可变的。实际上,renderboss()是完全不需要的,只要在render()函数中设置相关标志位就可以解决关尾的绘图问题。玩家坦克的生命标志使用drawImage()就可以绘制在屏幕上了。

4.4 游戏中的奖励及相关坦克的行为

根据游戏设定,当y1=-1000时,会出现如图4-8所示的飞机(sboss),当玩家击落他后,屏幕会显示“援军到达“,并且玩家剩余坦克数加1。使用if((y1==-1000)&&(sbz==0)){ }设定sboss的初始位置,根据游戏设定,sboos从屏幕上方倒飞入屏幕,所以sboss设定的初始位置(50,planepoup-65),其中planepoup为屏幕上边缘的标志位。最后,要将sbz赋值为1,消除sboos不停设置初始位置的BUG。当sboss飞入屏幕后,将sbz赋值为2,以执行下面

的if(sbz==2)语句。

在判断语句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),此时boss变红,在本次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不可见。

图4-9 图4-10

相关主题
相关文档 最新文档