原文发布时间为:2008-07-30 —— 来源于本人的百度文章 [由搬家工具导入]
Java手机游戏开发实例简明教程
(SunJava开发者认证程序员 郎锐)
一、手机游戏编写基础
1.手机游戏设计的基本原则
目前,市面上的手机无论在处理能力还是在存储容量上都不足与PC机相提并论,但也足以支持一个设计优化的微型游戏程序的运行。加上它的网络通信能力,甚至还可以支持有联机对战能力的网络游戏。正是由于硬件设备的极大差异,才直接导致了手机游戏开发与传统游戏开发的极大差别。
鉴于手机游戏与传统游戏开发的巨大差别,并考虑到作为其运行载体的手机的实际局限性,在进行手机游戏的设计时也有必须遵循的原则,即有限的游戏时间;缩短等待时间;精干的程序;寻求最大的兼容性。
手机作为一种典型的MIDP设备,其能量供应是有限度的,在设计游戏时应当为用户提供一个有限的游戏时间,以免游戏时间过长而大量消耗有限的能源。例如,可以把游戏时间限定在几分种之内完成或是将其划分为若干阶段分次进行。
鉴于手机游戏的处理速度不可能太快的客观事实,为保持游戏的顺畅进行,就必须尽量缩短游戏中的等待时间,尤其是在设计多人联机游戏时一定要注意这个问题。而且由于存储空间的限制,也要求设计精干的程序,否则将导致开发的游戏因过于臃肿而无法在手机执行。
为手机游戏寻求最大的兼容性也是有必要的。由于不同型号,不同款式的手机不仅支持的J2ME SDK(Java 2 Micro Edition SDK)不同,屏幕尺寸及按键等也都存在差异。一款好的游戏如果因为底层的不兼容而局限于某一款机型显然是一种浪费,其实游戏的剧本、流程设计等完全不用更改,只需针对其他系列的手机更换必要的低层处理就可以把游戏的市场拓展到其他机型。
2.手机游戏的实现技术
虽然J2ME对手机游戏的开发已经成为主流,但并不是说只有J2ME能够开发手机游戏,除此之外还有嵌入式和短信息等其他几种手机游戏实现技术:
嵌入式游戏是指在手机出厂时就已经固化在芯片中的游戏。早期手机提供的游戏大多是这一类游戏。由于这种技术不允许用户自由更新游戏,所以这类游戏很快便被淘汰了。
短信息游戏是基于手机短信息服务(SMS)的手机游戏,通过向游戏服务商的服务器发送简短的文字信息来获取从游戏服务器反馈的结果信息。由于这种游戏是纯文本交互形式,因此通常较乏味且输入烦琐,游戏成本较高(通常1条短信0.1元)。
随着Java技术的发展和Java手机的推广应用,使手机游戏进入J2ME时代。这种简化版本的Java极大地提高了手机对游戏的支持能力,它拥有比嵌入式和短信息手机游戏更为完美的界面,而且允许使用子图形动画。J2ME手机程序已经成为目前最佳的移动游戏开发环境,本文也正是围绕着J2ME技术展开对手机游戏开发过程的介绍。
3.《赛车》游戏的剧本设计
本文将向大家介绍一款赛车游戏的制作过程。这是一款典型的体育竞技类游戏,它以公路作为赛车场地,选手为一辆赛车,障碍物为在公路放置的炸弹。由于只安排了一辆赛车,所以制定的游戏规则不以速度取胜,而是按玩家规避障碍物的灵巧程度来积分。这可用玩家安全驾车的时长来度量,因为玩家玩得越熟练,其规避障碍物的手法也就更灵巧,安全驾驶的时间也就越长。
由于手机屏幕狭小,表现能力有限,而且在手机上也没有提供任何软硬件图像处理加速,所以在手机游戏中不可能追求PC机的表现效果。在追求视觉效果的同时,应尽量做到简单。
本游戏实例采用的是二维图形表现形式,以笔直无转弯的公路作为赛道。玩家将可以通过手机方向键控制赛车的左右移动,以规避随机布置在赛道上的炸弹,炸弹在随机布置后将不再移动。如果赛车的任意部分与炸弹相接触即被判定为触弹爆炸,本局游戏结束。
二、配置Eclipse开发环境
在使用任何一种语言进行编程时都离不开开发环境,Java语言也不例外,这里将向大家介绍一种非常著名的开发环境——Eclipse。
1.Eclipse基础
(1)认识Eclipse
Eclipse是一个开放源代码的、与NetBeans、Sun ONE Studio和Borland Jbuilder类似的一种基于Java的整合型可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。幸运的是,Eclipse 附带了一个标准的插件集,包括Java开发工具(JDT)。其未来的目标不仅仅是成为专门开发Java程序的IDE环境,根据Eclipse的体系结构,通过开发插件,它能扩展到任何语言的开发,甚至能成为图片绘制的工具。
难能可贵的是,Eclipse是一个开放源代码的项目,任何人都可以下载Eclipse的源代码,并且在此基础上开发自己的功能插件。也就是说未来只要有人需要,就会有建立在Eclipse之上的COBOL、Perl、Python等语言的开发插件出现。同时可以通过开发新的插件扩展现有插件的功能,例如,为了进行手机应用程序的开发,本文所涉及到的《赛车》游戏就是通过J2ME插件的扩展来加以实现的。
(2)Eclipse的组织结构
Eclipse是一个开放源代码的软件开发项目,它专注于为高度集成的工具开发提供一个全功能的、具有商业品质的工业平台。它主要由Eclipse项目、Eclipse工具项目和Eclipse技术项目等组成,具体包括Eclipse Platform、JDT、CDT和PDE等4个部分。JDT支持Java开发、CDT支持C开发、PDE用来支持插件开发,Eclipse Platform则是一个开放的可扩展IDE,提供了一个通用的开发平台。它提供建造块和构造并运行集成软件开发工具的基础。Eclipse Platform允许工具建造者独立开发与他人工具无缝集成的工具从而无须分辨一个工具功能在哪里结束,而另一个工具功能在哪里开始。
Eclipse SDK(软件开发者包)是Eclipse Platform、JDT和PDE所生产的组件合并,提供了一个具有丰富特性的开发环境,允许开发者有效地建造可以无缝集成到Eclipse Platform中的工具。Eclipse SDK由Eclipse项目生产的工具和来自其他开放源代码的第三方软件组合而成。Eclipse项目生产的软件以CPL发布,第三方组件有各自自身的许可协议。
2.Eclipse的下载与安装
通过前面的介绍,我们了解到Eclipse是一款非常出色和著名的开源项目。你只需登陆Eclipse官方网站(www.eclipse.org)就可以免费获得这款优秀的开发环境。
进入主页后单击“Downloads”链接,将出现镜像列表页面,从中选择较近的镜像点并进入下载页面,目前最新版本为3.0.1)。一般情况下,Eclipse同时提供了Release、Stable Build、Integration Build和Nightly Build等多个下载版本,建议下载Release或Stable版本。这里选择Release版本。
进入该版本的下载页面,单击“eclipse-SDK-3.0.1-win32.zip”链接进行安装包下载(针对Windows平台)。同时,单击“eclipse3.0.1-SDK-win-LanguagePackFeature.zip”链接下载对应的多国语言包插件以实现软件的本地化。
安装Eclipse的步骤非常简单:你只需将下载的安装压缩包按原路径直接解压即可。之后将多国语言包解压缩,并将解压得到的“plugins”和“features”文件夹去覆盖解压到Eclipse安装目录下“eclipse”文件夹下的同名文件夹即可。如果当前操作系统的JRE环境安装正确无误,运行Eclipse.exe将进入其默认界面。
注意:这里的前提是JRE环境的安装正确无误,由于Eclipse本身是用Java语言编写的,而下载的安装压缩包中并不包含Java运行环境,因此需要用户自己另行安装JRE,并且需要在操作系统的环境变量中指明JRE中bin的路径。如果上述设置不正确,Eclipse将无法正常运行。另外,由于Eclipse版本升级较快,如果有更新版本,需先删除旧版本重新安装,而不能直接解压到原来的路径覆盖旧版本。
3.一些必要的配置
(1)配置JRE
为了保证Eclipse的正常运行,我们需要配置JRE。你可以安装Sun的JDK或IBM的JDK,推荐使用1.4以上版本。因为只有使用1.4以上版本的JDK才可以享受到新增的HotSwap功能对于调试带来的方便。这里,我们使用Sun公司的1.5.0版本JDK,你可以从Sun公司官方网站http://java.sun.com免费下载。
(2)安装J2ME SDK
为能够保证手机应用程序的开发,你还必须安装J2ME SDK。Sun公司的J2ME Wireless Toolkit(WTK)便是常用的一款J2ME SDK,它提供了运行J2ME应用程序所需要的库以及模拟器等,通过它可以进行程序的编译、校验、运行。有关WTK的信息可查询http://java.sun.com/products/j2mewtoolkit。
目前,J2ME Wireless Toolkit共分3个版本:1.0.4、2.0和2.1。其中,1.0.4版只能开发MIDP 1.0程序,2.0版可以开发MIDP 2.0应用程序,2.1版则可以同时开发MIDP 1.0、JTWI、自定义等3种环境。需要注意的是,并非版本越高越好,必须视需求不同而选择适当的版本,才能开发出可以在真机上运行的MIDP应用程序。这里选用的是WTK 2.1,你可从Sun公司官方网站免费下载,按默认方式安装该工具包并记下其安装路径以便以后在安装EclipseMe插件时使用。
这里之所以选用Sun公司的J2ME Wireless Toolkit产品,是因为这样开发出来的手机软件可以具有更大的通用性。如果你只是出于为自己的爱机DIY应用软件的目的,则完全可以根据自己使用手机的型号从相应厂商网站下载与之对应的J2ME SDK,这样开发出来的手机软件能够以更优的方式在真机运行。目前一些大的手机厂商开发的特定J2ME SDK主要有:Nokia的Nokia DEveloper's Suite与Nokia各款手机专属SDK;SonyEricsson的SonyEricsson J2ME SDK;Siemens的Siemens Mobility Toolkits等。
4.J2ME插件EclipseMe的安装
通过上面的介绍,你现在应当理解:对手机程序的开发实际也就是等于对J2ME项目的开发。虽然上面我们对环境进行了配置,使其能够支持J2ME开发,而且Ecilpse使用起来非常方便,但这对J2ME开发的支持还是远远不够。下面,我们将通过为Eclipse安装一个开发J2ME程序的插件EclipseMe来完善手机应用程序开发的最后一项准备工作。
目前,EclipseMe的最新版本为0.5.5(eclipseme.feature_0.5.5_site.zip),你可以登陆SourceForge网站http://eclipseme.sourceforge.net/免费下载。
进入SourceForge网站后单击“Downloads”链接进入产品下载页面,该页面列有全部版本的EclipseMe插件及部分版本的源程序代码。单击需要下载的eclipseme.feature_0.5.5_site.zip,将出现下载镜像列表页面,你可以从中选取距离较近的镜像站点进行下载。
对于EclipseMe 0.5.0版本及更早版本的安装,可以下载后直接将其解压到Eclipse安装目录下的“plugin”文件夹下即可很方便地完成对插件的安装。但是到了0.5.5版本以后,EclipseMe的安装方式发生了较大变化,再用以前的方法将不能成功安装插件。下面将给出EclipseMe这一版本插件的具体安装过程:
启动Eclipse,单击“帮助→软件更新→查找并安装……”菜单命令,打开“安装/更新”对话框,选中“搜索要安装的新功能部件”选项。单击“下一步”按钮。
在出现的对话框中单击“新建本地站点……”按钮,在打开的对话框中指定EclipseMe压缩包的当前解压路径,然后将会在“要包括在搜索中的站点”列表中出现以当前指定目录为名称的站点项目。选中该项目,展开其树型结构,可以看见其子项EclipseME也被同时选中。单击“下一步”按钮。
提示:最好将EclipseMe压缩包解压到一个路径名中不包含汉字的文件夹下,例如,E:\EclipsMe\下,否则可能会在安装过程中出现一些奇怪现象而妨碍安装过程的顺利的进行。
在出现的对话框中选中“EclipseME”按钮。单击“下一步”按钮。在出现的对话框中单击“我接受许可协议中的条款”选项。单击“下一步”按钮。在出现的对话框中指定Eclipse插件要安装到的路径,你可以单击“添加站点”按钮,在出现的对话框中选择其他路径(最好还是安装到Eclipse的安装目录下),单击“确定”按钮完成即可。
为查看EclipseMe插件是否成功安装,你可以在重启Eclipse后单击“窗口→首选项”菜单命令,打开“首选项”对话框。在左侧窗口看到J2ME项即标明EclipseMe插件已经成功安装。
在“首选项”对话框中进行配置
5.对EclipseMe插件的配置
为了使新安装的EclipseMe插件能够正常工作,需要对其进行配置。展开J2ME项,选择“Platform Components”子项,在右侧窗口中将同步显示其详细配置。右键单击“Wireless Toolkits”选项,选择“Add Wireless Toolkit”命令,在打开的对话框中单击“浏览”按钮,在出现的对话框中指定先前安装的Wireless Toolkit路径。如果路径指定正确且Wireless Toolkit也安装正确,此时EclipseMe将自动检测出该目录所安装的Wireless Toolkit的版本,并显示在编辑框的下方。单击“完成”按钮,在“首选项”对话框右侧的配置窗口中将显示该Wireless Toolkit所支持的一些特性。如果你是针对某一机型的手机进行开发,可以重复上面的步骤将其他厂商的无线开发工具包添加其中。
三、搭建游戏框架
1.创建J2ME项目
在Eclipse中首先创建J2ME MIDlet Suite项目“Racing”,所有后续的编码、调试和运行都是在这个工程中进行。
启动Eclipse,单击“文件→新建→项目”菜单命令,在打开的对话框中展开“J2ME”选项,选中“J2ME Midlet Suite”子项后单击“下一步”按钮。在出现的对话框中设置项目名称和项目存放路径,一般保持默认路径即可。单击“下一步”按钮。
在出现的对话框中对应用程序所支持的MIDP版本进行指定。如果考虑兼容性可以选择“J2ME Wireless Toolkit 2.1 MIDP 1.0 platform”选项。当然,你也可以选择“MIDP 2.0”选项。单击“下一步”按钮。
在出现的对话框中对Java构建设置进行定义,通常保持默认值即可。单击“完成”按钮,EclipseMe将自动设置好项目的编辑及运行环境。你可以在导航器视图中单击刚才创建的项目,在右侧编辑视图中将可以查看EclipseMe生成的项目结构。
查看创建的项目
2.创建J2ME应用程序
上面创建的MIDlet Suite,一般也称作MIDlet应用程序套件,它可以包含一个或多个MIDlet,只是在发布时是以MIDlet Suite为单位进行,我们的一些实质性的工作都是在MIDlet中完成的。因此,需要继续添加J2ME MIDlet项“RacingMIDlet”到项目中。
在导航器上单击鼠标右键,选择“新建→其他”菜单命令,在打开的对话框中展开“J2ME”选项,选中“J2ME Midlet”子项后单击“下一步”按钮。在出现的对话框中指定包(也可以保持默认值)和名称,单击“完成”按钮后,EclipseMe将自动生成框架代码,并将新创建的RacingMIDlet类按如下代码进行编辑:
public class RacingMIDlet extends MIDlet {
public Display display;
public GameCtrl game;
public RacingMIDlet() {
super();
}
protected void startApp() throws MIDletStateChangeException {
// 获得Display
display = Display.getDisplay(this);
// 获得Displayable
Displayable current = display.getCurrent();
if (current == null) {
// 装载logo图像
Image logo = null;
try{
logo = Image.createImage("/logo.png");
}catch (IOException e) { }
// 显示logo
Alert splashScreen = new Alert(null, "郎锐2005年作\n版权所有(c)\n2005--2006", logo, AlertType.INFO);
// 延迟4秒
splashScreen.setTimeout(2000);
//新建ChooseDemo对象
game = new GameCtrl(this);
// 显示闪屏界面
display.setCurrent(splashScreen, game);
}else {
// 显示当前界面
display.setCurrent(current);
}
}
protected void pauseApp() {
}
protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
}
public void quit() throws MIDletStateChangeException {
// 退出程序
destroyApp(false);
notifyDestroyed();
}
}
这里通过一个Alert信息框完成对闪屏界面的显示,所使用的位图logo.png在开始可以暂用临时图像代替,最后再由美工完成的正式图像替换。
因为游戏主题为赛车,因此本例使用的logo位图最好显示一些赛车的图标,并辅以具有艺术字效果的游戏名称。图标可以很方便地下载得到,而游戏名称的艺术字效果通常要由开发人员自己完成。除了可以使用专业的设计软件外,还可使用两种不同颜色书写同字体、同大小的游戏名称,然后将深色文字置后,浅色文字错位少许后放置在前台的方法通过视觉错觉来实现立体字的效果。最后再与图标合成到一张PNG格式的位图,即可完成一个简单logo位图的制作。
Logo制作过程示意
编辑完毕后,Eclipse将提示有多处错误存在,这主要是由GameCtrl类还没有添加造成的。接下来,向工程添加从Canvas类继承的GameCtrl类,并编辑其类为如下代码,以保存从RacingMIDlet类传入的MIDlet对象和对命令按键、方向按键的添加与响应:
public class GameCtrl extends Canvas implements CommandListener{
private final Command startCommand;
private final Command quitCommand;
private final RacingMIDlet midlet;
public GameCtrl(RacingMIDlet midlet) {
super();
// 保存MIDlet类对象
this.midlet = midlet;
// 添加命令按键
quitCommand = new Command("退出", Command.EXIT, 2);
addCommand(quitCommand);
startCommand = new Command("开始", Command.OK, 1);
addCommand(startCommand);
// 侦听按键响应
setCommandListener(this);
}
protected void paint(Graphics g) {
}
public void commandAction(Command arg0, Displayable arg1) {
if (arg0 == startCommand){
// 用户开始游戏
initialize();
}
if (arg0 == quitCommand){
// 用户退出游戏
try{
midlet.quit();
}
catch(MIDletStateChangeException e){}
}
}
private void initialize() {
}
protected void keyPressed(int keyCode) {
// 得到按键动作
int gameAction = getGameAction(keyCode);
switch (gameAction) {
case RIGHT:
break;
case LEFT:
break;
default:
break;
}
}
}
这里除了新增的keyPressed()方法外,与以前介绍过的程序框架并没有太大的差别。这个keyPressed()方法主要用来捕获用户的手机按键,由于在赛车驾驶时只需控制其左右移动方向即可,因此这里只对getGameAction ()方法返回的键值与RIGHT和LEFT进行比较。稍后将完成这两个分支的功能实现。
四、实现游戏界面
由于将赛车游戏抽象为场地、选手和障碍物,因此在游戏的界面实现时也主要围绕这3个中心展开。
1.实现场地界面
由于这里的公路是平直无转弯无路口的,因此展现到二维平面上就是一个矩形。可以想象,如果只在手机屏幕显示一个矩形,无论填充什么颜色,都很难令人联想到是一条公路。为了表现出公路的特征,不妨在公路中间绘制一条隔离车道用的斑点线。由于在比赛进行时,赛车与公路之间是相互运动的,而作为游戏的主角,赛车一定是不能超出玩家视线的。也就是说,赛车应当始终处于屏幕显示范围之内,这样一来,为了表现赛车的运动,就必须让公路动起来。一个简单的办法是,在绘制公路中间的斑点线时对同一位置的斑点进行交替绘制。
简单展示公路特征
可能从纸面上很难理解这样简单的处理怎么能够产生公路想后移动的效果,不妨将上图的公路中线看作13段连续的LED灯管,开始时为奇数的灯管点亮,为偶数的熄灭。接下来点亮的灯管熄灭,熄灭的点亮,如此反复,正如街头的霓虹灯,可以通过对灯管点亮、熄灭的控制来实现一种移动的视觉错觉。在代码实现上可在paint()方法中添加如下代码来实现对公路中线的绘制:
// 绘制动态公路中线
g.setColor(0, 0, 255);
if (lineMode == true){
lineMode = false;
for (int i = 0; i < height; i += 20)
g.drawLine(width / 2, i, width / 2, i + 10);
}else{
lineMode = true;
for (int i = 10; i < height; i += 20)
g.drawLine(width / 2, i, width / 2, i + 10);
}
其中,通过lineMode变量对虚线的绘制模式进行控制,width和height分别为屏幕的宽度和高度。在构造函数中分别通过getWidth()和getHeight()方法获得。为了能够以固定时间间隔周期性地调用paint()函数,可在程序初始化方法initialize()中创建一个定时器,并在定时器的执行方法run()中添加repaint()方法以完成对paint()函数的周期性调用。有关定时器的创建,可先向工程添加一个基于TimerTask类的新类NextFrame,用于周期性定时执行任务。并在GameCtrl类添加其类声明和另外一个Timer类的对象:
private Timer timer = new Timer();
private NextFrame nextFrame;
在需要开启定时器时,以nextFrame对象为参数去调用Timer类的schedule ()方法并指定时间间隔即可:
nextFrame = new NextFrame(this);
timer.schedule(nextFrame, 300, 300);
2.实现炸弹与赛车界面
炸弹与赛车的界面实现,可以通过调用低级类Graphics中的相关绘图方法来绘制,也可以通过显示位图的方式来实现。前者几乎不占用内存,但绘制过程烦琐,后者的显示方法相比之下要简单许多但却要占用少量的内存。这里,我们选用后者。
炸弹与赛车示意
首先准备上图所示的两张PNG格式位图并将其添加到工程中。炸弹和赛车作为本游戏的主角,其图像在游戏过程中将被频繁使用,因此,为方便使用可将与这两位图相关的Image对象定义为公有型的类成员变量。并在类构造函数中完成对位图文件的装载:
try{
// 装载炸弹图像
bombImage = Image.createImage("/bomb.png");
// 装载赛车图像
carImage = Image.createImage("/car.png");
}catch(Exception e) {}
对于这种Image对象的绘制,可使用Graphics类成员函数drawImage()方法,该方法的使用方法在前文已有过详细介绍。由于赛车需要左右移动,炸弹也要在被随机放置后不断从屏幕滑过,因此在绘制Image对象时,其位置最好能通过公有型的类成员变量来设置。这样就可以在一个专门的控制方法中对赛车和炸弹的显示位置进行调整,而在paint()方法中只负责对图形进行绘制,从而实现对游戏功能的模块化划分。下面这段赛车和炸弹图像的绘制代码将以各自图像的中心位置为基准点进行绘制,共完成一辆赛车和四棵炸弹的屏幕绘制:
// 赛车水平位置
public int carPos = 0;
// 炸弹位置
public int[] bombPosX = {0, 0, 0, 0};
public int[] bombPosY = {0, 0, 0, 0};
// 炸弹是否出界
public boolean[] bombCanUse = {false, false, false, false};
……
// 绘制赛车
g.drawImage(carImage, carPos, height - carImage.getHeight(), Graphics.HCENTER | Graphics.VCENTER);
// 绘制炸弹
for (int i = 0; i < 4; i++){
if (bombCanUse[i] == true)
g.drawImage(bombImage, bombPosX[i], bombPosY[i], Graphics.HCENTER | Graphics.VCENTER);
}
这里的bombCanUse数组用来对当前炸弹是否出界予以标识,这里所说的炸弹出界并不是指炸弹当前位置超出屏幕范围,而是在真实屏幕上方另设一连续的与真实屏幕等大的、可用来随机放置炸弹的虚拟屏幕。此虚拟屏幕和真实屏幕的并集才是炸弹的有效区域。凡是落在此区域内的炸弹均不予销毁,如果有炸弹移出有效区域,则在炸弹放置区重新随机放置一枚,始终保持炸弹有效区域内的炸弹总数为4个。
炸弹有效区域示意
最后,一定要记得在paint()方法开始绘制下一场景之前通过下面的清屏语句擦除当前场景的显示。否则,在游戏开始后随着各种角色的移动,屏幕将变成一个“大花脸”。
// 白色清空画布
g.setColor(255, 255, 255);
g.fillRect(0, 0, width, height);
五、角色移动与碰撞检测
前面对游戏的骨架和界面都已经实现,但此时的游戏仍然毫无生气,因为我们还没有实现其灵魂。接下来将要进行的角色移动与碰撞检测将能够使游戏中的主角动起来,并能够通过对当前游戏状态的检测来自行判断游戏是否结束。
1.角色移动
(1)移动赛车
由于道路的移动是通过交替绘制公路中线所引起的错觉来实现的。因此,实际真正需要移动的角色只有炸弹和赛车。本例将以赛车为基准,它始终处于屏幕下方,玩家可以通过手机左右方向键实现对赛车的左右移动。显然,对赛车的移动控制全部由玩家掌握,其控制代码只能在keyPressed()方法对左、右按键的分支处理中实现。由于在先前的设计中实现了显示与控制的分离,因此在这里只需对表示赛车水平位置的carPos变量取值进行修改即可。为防止赛车移出屏幕,需要添加必要的越界保护代码:
// 得到按键动作
int gameAction = getGameAction(keyCode) switch (gameAction) {
case RIGHT:
// 右移赛车
carPos += 5;
// 防止越界
if (carPos > width - carImage.getWidth() / 2)
carPos = width - carImage.getWidth() / 2;
break;
case LEFT:
// 左移赛车
carPos -= 5;
// 防止越界
if (carPos < carImage.getWidth() / 2)
carPos = carImage.getWidth() / 2;
break;
default:
break;
}
// 重绘屏幕
repaint();
(2)移动炸弹
与赛车不同,炸弹的移动控制由手机全权负责。只要游戏在运行,炸弹就不停的从屏幕上方滑落,对于类似的处理应当交由定时器任务类完成相关操作。NextFrame类成员方法run()将在定时器每次被触发时调用。
在处理炸弹移动时,首先对炸弹是否超出有效区域进行检测。由于初始运行时屏幕没有炸弹,所以可以将其当作超出有效区域进行处理。对于这种情况,首先通过Random类的nextLong()方法得到两个随机数,然后分别以屏幕宽度和高度为模进行取模运算,其结果经越界限制后将作为炸弹的初始设置坐标。在每设置一棵炸弹后要及时对该炸弹所对应的bombCanUse元素设置炸弹有效值,以在后续的处理中执行下落显示。
与赛车的左右移动类似,炸弹的下落可以通过不断增加其纵坐标来实现。只是在下落过程中不必进行越界保护处理,如果炸弹越过有效区域就通过bombCanUse将其标记为无效。将在下一定时周期重复前面的过程:
for (int i = 0; i < 4; i++){
// 炸弹没显示时设置初始位置,显示后水平位置固定
if (gameCtrl.bombCanUse[i] == false){
// 随机设置炸弹初始位置
int x = (int)(rand.nextLong());
x = (int)(x % gameCtrl.width);
gameCtrl.bombPosX[i] = x;
int y = (int)(rand.nextLong());
y = (int)(y % gameCtrl.height);
// 将炸弹设置在虚拟屏幕
gameCtrl.bombPosY[i] = -y;
// 限制炸弹水平位置
if (x < bombWidth)
x = bombWidth;
if (x > gameCtrl.width - bombWidth)
x = gameCtrl.width - bombWidth;
// 标志第i个炸弹进入可视区
gameCtrl.bombCanUse[i] = true;
}else{
// 炸弹下移
gameCtrl.bombPosY[i] += 3;
// 炸弹出界检测
if (gameCtrl.bombPosY[i] > gameCtrl.height + bombHeight / 2)
gameCtrl.bombCanUse[i] = false;
}
}
如果此时运行程序,虽然可以实现赛车在布有炸弹的公路上行驶的效果,而且玩家也可以控制赛车的前进方向,但却存在一个致命的缺陷——当赛车和炸弹相撞时,炸弹依旧下落,赛车依旧行驶,好像什么都没有发生一样。这是由于没有为游戏添加碰撞检测代码所致。
2.碰撞检测
碰撞检测是游戏中经常使用的一种重要算法。用于检测游戏中的两物体是否发生表面接触,例如,子弹是否命中歹徒,炮弹是否击中目标等等。只有进行了碰撞检测才有能在出现上述情况后实现歹徒被击毙、目标被摧毁等结果。
炸弹与赛车发生碰撞示意
上图展示了炸弹与赛车发生碰撞时的所有可能情形。从图中可以看出,只要炸弹完全进入图中的虚线矩形框就必然与赛车发生了碰撞。对本游戏而言,发生了碰撞即意味着赛车被炸毁,游戏结束。所以,在炸弹下落过程中不难写出如下碰撞检测代码:
// 碰撞检测
if (gameCtrl.bombPosY[i] + bombHeight / 2 >= gameCtrl.height - carHeight * 3 / 2 && gameCtrl.bombPosY[i] - bombHeight / 2 <= gameCtrl.height - carHeight / 2 && gameCtrl.bombPosX[i] + bombWidth / 2 >= gameCtrl.carPos - carWidth / 2 && gameCtrl.bombPosX[i] - bombWidth / 2 <= gameCtrl.carPos + carWidth / 2){
// 结束游戏
gameCtrl.isGameOver = true;
// 关闭定时器
cancel();
}
其中,carWidth、carHeight、bombWidth和bombHeight变量分别是通过carImage和bombImage对象调用各自的getWidth()和getHeight()方法而返回得到的赛车图像尺寸和炸弹图像尺寸。如果发生碰撞,将通过公有型的boolean类型变量isGameOver来指示游戏的结束。同时调用TimerTask类成员方法cancel()关闭先前设置的定时器。
对于非胜负类游戏,由于在游戏结束时并没有胜出方,所以一般都是以积分的形式反馈给玩家当前局所取得的分数。对于本例,分数显然是与安全驾驶时间成正比的,在程序实现时,可以在定时器每次触发run()方法时对积分进行累加,并在游戏结束时通过下述代码以信息框的形式将当前积分反馈给玩家:
isGameRun = false;
// 显示logo
Alert result = new Alert("本局积分", String.valueOf(score), null, AlertType.INFO);
// 延迟4秒
result.setTimeout(2000);
// 显示闪屏界面
midlet.display.setCurrent(result, this);
经过上述辛勤工作,一款真正的J2ME手机游戏程序已经完成!
六、测试与发布游戏
单击“运行→运行”菜单命令,在打开的对话框左侧配置窗口中右键单击“Wireless Toolkit Emulator”选项,选择“新建”命令,在对话框右侧将显示相应的运行配置选项。一般可保持默认设置,也可以在“Emulation”选项卡中对默认设备进行指定。通常选择“DefaultClolorPhone”选项即可,其模拟的是在彩屏手机上的运行效果。由于目前支持Java的手机一般配置都不是很低,绝大多数也都是彩屏配置,因此“DefaultClolorPhone”选项应该是比较大众。当然,你也可以设置“DefaultGrayPhone”等其他选项以模拟在单色或其他配置的手机上的运行效果。
最后,单击“运行”按钮执行我们创建的赛车游戏。
当然,本例作为一款教学性质的游戏实例,没有做太多的完善与优化。给你留下了较大的游戏扩展空间。你可以在本游戏基础上尝试进一步的扩展和修改,例如,增加前后方向键以模拟加大油门和刹车;另外增加一些参赛车辆;根据玩家游戏进行时间的长短动态调整游戏难度,包括加快车速、增加障碍物等等。代码编写无误后可以先在PC上的模拟器中运行调试,并对出现的问题进行修改,一切无误后再打包发布。