1、学习情境学习情境4 4 MIDP2.0MIDP2.0游戏开发游戏开发能力目标v 学会使用GameCvanvas类的按键响应方法;v 会熟练构造精灵类;v 学会使用精灵实现动画的自动播放;v 能熟练设置精灵出现的位置、显示帧、切换帧和绘制帧的方法。v 学会使用Vector类创建子弹、移动子弹和绘制子弹;v 学会自定义对象类的方法,构造一个游戏对象;v 学会使用变量来模拟计数器功能,实现游戏对象以不同的频率运动;v 熟练掌握使用setTransform()方法,实现精灵对象的旋转;v 熟练掌握使用精灵类提供的方法进行碰撞的检测;v 认知目标v 认识游戏的屏幕GameCanvas类;v 了解Game
2、Canvas类和Canvas类的区别;v 了解GameCvanvas类的自动双缓冲区和脱屏画笔;v 学习精灵类中常用方法的语法格式,并理解其中参数的含义。v 了解Vector类中的常用方法,并理解各参数的含义;v 理解计数器在游戏程序中的使用方法和重要性;v 理解setTransform()方法中参数rotating的8个取值的含义v 认知collidesWith()方法的语法格式,并理解其参数的含义;v 理解区域级和像素级碰撞区别;v 理解播放帧序列中的帧号码和帧序号的区别;v 4.1 学习情景描述 除棋牌类游戏之外,一款手机游戏,通常在一个特定的场景下展开,这个特定的场景,就是游戏的背景,
3、根据需要,游戏背景还可以滚动。在游戏背景下,通常有若干个游戏的对象。其中可以由玩家通过按键来控制的游戏对象,称为游戏的“主角”,其余不能由玩家来控制的角色称为“NPC”,即非玩家控制角色,或系统控制角色。这些游戏对象可以进行移动、跳跃、投掷、发射子弹、攻击等各种动作。并通过对这些游戏对象和动作进行HP(生命值)、MP(经验值)、攻击力等等的数值设计,来判断游戏的胜利或者失败。本学习情境分为五个学习单元来完成。单元一,游戏主角设计;单元二,子弹设计;单元三,NPC(非玩家控制角色)设计;单元四,游戏背景设计;单元五,滚屏设计。游戏主角是由玩家来控制的游戏对象,可以是一架飞机、一辆坦克、一个人物等
4、。玩家通过手机键盘上的按键,可以控制游戏主角在手机屏幕上进行移动、跳跃、投掷、格斗等各种动作。在本学习单元中,首先,通过控制一架飞机在屏幕上的移动,来认识GameCanvas,并学习在GameCanvas中的键盘处理;然后,通过在移动时,改变飞机的形状,初识精灵类;最后,通过控制一个主角人物在屏幕上的行走,来深入地学习精灵类的使用。4.2 游戏主角设计 4.2.2 知识准备:了解GameCanvas类 MIDP有1.0和2.0版本之分。在MIDP2.0中,新增加了一个javax.microedition.lcdui.game包,这个包中定义了专门用于游戏开发的5个新类:GameCanvas、L
5、ayer、LayerManager、Sprite和TiledLayer。这些类主要针对游戏开发的特点,提供了用于提高游戏性能的独立功能。其中,GameCanvas类继承自MIDP1.0中的Canvas类,为游戏提供了基本的“屏幕”功能。除了完全具备Canvas类的功能外,这个类还提供了游戏专用的功能,这些功能简化了游戏开发并提高了运行性能。比如:提供双缓冲绘制机制,能直接获得设备键盘的物理状态等等。4.2.3 任务一:控制主角飞机在屏幕上移动任务准备(1)声明变量(2)在构造方法中实现初始化(3)绘制屏幕(4)响应键盘(5)启动线程(6)控制线程(7)测试运行(1)新建MIDlet工程,工程名
6、为“Ch4_Unit1”。(2)新建MIDlet类,类名为“Ch4_Unit1_MIDlet”。(3)新建GameCanvas类。(4)将主角飞机图片资源文件plane.png,拷贝到工程的“res”文件夹下。实现过程知识提炼:MIDP2.0中的键盘处理 在MIDP2.0中,不需要等待键盘事件发生后才能获取键盘状态信息,通过调用GameCanvas类上定义的的getKeyStates()方法,可以随时获取键盘状态信息,并且可以获取多个按键状态信息。getKeyStates()方法的语法如下:public int getKeyStates()该方法返回一个int型数据,其中每一位代表一个按键的状
7、态。如果某个按键被按下,则对应的数据位为1,否则,数据位为0。000010000GAME DGAME CGAME BGAME AFIREDOWNUPRIGHTLEFT MIDP2.0在GameCanvas类上定义了九个静态属性常量来表示游戏按键状态码,分别为:UP_PRESSED 上功能键或数字2键DOWN_PRESSED 下功能键或数字8键LEFT_PRESSED 左功能键或数字4键RIGHT_PRESSED 右功能键或数字6键FIRE_PRESSED 开火功能键或数字5键GAME_A_PRESSED 数字1键GAME_B_PRESSED 数字3键GAME_C_PRESSED 数字7键GAM
8、E_D_PRESSED 数字9键检测按键状态方法int keyCode=this.getKeyStates();/获取键盘状态if(keyCode=GameCanvas.UP_PRESSED)/假如是按上键 else if(keyCode=GameCanvas.DOWN_PRESSED)/假如是按下键 else if(keyCode=GameCanvas.LEFT_PRESSED)/假如是按左键 else if(keyCode=GameCanvas.RIGHT_PRESSED)/假如是按右键当然,也可以使用位“与”的方法进行判断:int keyCode=this.getKeyStates();
9、/获取键盘状态if(keyCode&GameCanvas.UP_PRESSED)!=0)/假如是按向上键 else if(keyCode&GameCanvas.DOWN_PRESSED)!=0)/假如是按下键 else if(keyCode&GameCanvas.LEFT_PRESSED)!=0)/假如是按向左键 else if(keyCode&GameCanvas.RIGHT_PRESSED)!=0)/假如是按向右键 使用上述两种方法,都只能判断一个按键被按下的情况,无法判断多个按键被同时按下的情况。原因是,比如,当同时按下“上”和“右”按键的时候,得到的值既不是UP_PRESSED也不是R
10、IGHT_PRESSED。改进方法如下:if(keyCode&GameCanvas.UP_PRESSED)!=0)&(keyCode&GameCanvas.LEFT_PRESSED)!=0)/左、上两个按键被同时按下 else if(keyCode&GameCanvas.UP_PRESSED)!=0)&(keyCode&GameCanvas.RIGHT_PRESSED)!=0)/右、上两个按键被同时按下 else if(keyCode&GameCanvas.LEFT_PRESSED)!=0)/左键被按下 else if(keyCode&GameCanvas.RIGHT_PRESSED)!=0)
11、/右键被按下小提示:由于使用的是else if判断,因此对多按键被同时按下的判断应放在单按键被按下的判断之前。比如,当同时按下左、上按键时,会有如下三个条件同时成立:(keyCode&GameCanvas.UP_PRESSED)!=0(keyCode&GameCanvas.LEFT_PRESSED)!=0(keyCode&GameCanvas.UP_PRESSED)!=0)&(keyCode&GameCanvas.LEFT_PRESSED)!=0)如果单按键被按下的判断放在前面,那么后面的多按键被按下的情况就不再判断了。protected GameCanvas(boolean suppress
12、KeyEvents)参数suppressKeyEvents用于指定是否支持MIDP1.0中的键盘响应事件。当参数取值为true时,指明不支持键盘响应事件。当用户操作键盘时,不会引起keyPressed()、keyReleased()和keyRepeated()这三个事件方法被调用,这样可以提高速度和性能。如果参数取值为false,那么一旦按键被按下,就会调用上述三个传统的键盘事件处理方法。在GameCanvas类中定义的九个表示按键状态码的静态属性常量中,并不包含键盘左上角的“左软键”和键盘右上角的“右软键”。而这两个按键通常被用来响应“返回”、“暂停”、“退出”、“确定”等与用户交互的功能按
13、钮。因此,在MIDP2.0中保留了对MIDP1.0中的按键响应机制,通常用于对“左软键”和“右软键”的响应。要点提示1:GameCanvas类的构造方法要点提示1:绘制双缓冲区1、在MIDP2.0中,GameCanvas类为每个实例对象提供唯一的图形缓冲区,缓冲区的大小与屏幕大小一样。每一个GameCanvas对象所拥有的缓冲区都是独立的,因此最好在游戏中仅创建一个GameCanvas类对象,并且重复利用。2、调用GameCanvas类的getGraphics()方法,可以获得绘制缓冲区的Graphics对象。3、调用GameCanvas类的flushGraphics()方法,可以将缓冲区中的
14、内容绘制到屏幕上。4.2.4 任务二:飞机移动时,实现形状的改变 知识准备:了解精灵类精灵类(Sprite)被定义在javax.microedition.lcdui.game包中,专门用来代表游戏中的动画角色,可以显示一帧或多帧的连续图像。但所有的帧都必须大小相同,并且由一个Image对象提供。比如:飞机在飞行时有三种不同的形状,正向飞行,向左侧飞行和向右侧飞行:。将这三个形状不同,但大小相同(2524)的小图片合成到一个Image对象中。每个小图片为一帧,帧的序号从0开始,并且按从左到右,从上到下的顺序依次排列。通过精灵类(Sprite)对象,可以任意显示其中某一帧的静态图像,或者连续地、循
15、环显示任意顺序的某几帧,以实现动画效果。精灵类(Sprite)还提供了许多变换模式(翻转和旋转),以及碰撞检测的方法,能大大简化游戏逻辑的实现。知识提炼:精灵类构造方法(1)public Sprite(Image image)说明:通过图像创建一个只含有一帧图像的非动画Sprite类对象,即只能显示一幅静态图像。利用该方法创建精灵类对象的目的,主要是利用Sprite类提供的方法,实现图像各种角度的翻转、碰撞检测等功能。参数:image为只含有一帧的图像。(2)public Sprite(Image image,int frameWidth,int frameHeight)说明:通过图像创建一个
16、指定每帧图像大小的动画精灵类对象,该方法是最常用的。参数:image为包含多帧的图像。frameWidth和frameHeight分别为每一帧的宽度和高度。(3)public Sprite(Sprite sprite)说明:通过一个精灵类对象创建一个新的精灵类对象。参数:sprite是一个已创建好的精灵类。要点提示(1)单帧图像处理public void setFrame(int index)其中,参数index为当前精灵类对象某一帧的序号。(2)设置精灵位置并绘制精灵 public void setPosition(int x,int y)说明:将精灵类对象定位到屏幕上的任意位置。参数:x为
17、横坐标,y为纵坐标。public void paint(Graphics g)说明:绘制精灵类对象的当前帧。参数:g为绘制上下文。4.2.5 任务三:设计主角人物在屏幕上行走任务准备(1)声明变量(2)在构造方法中实现初始化(3)绘制屏幕(4)响应键盘(5)启动线程(6)控制线程(7)测试运行(1)新建MIDlet类,类名为“Ch4_Unit1_MIDlet2”。(2)新建GameCanvas类,类名为“Ch4_Unit1_GameCanvas2”(3)将主角人物图片资源文件man.png,拷贝到工程的“res”文件夹下。实现过程 要点提示(1)多帧动画处理public void setFra
18、meSequence(int sequence)public void nextFrame()public void prevFrame()小提示:使用getFrame()方法可以获取动画播放帧序列中当前正在播放的图像帧序号。在实际应用中,可以使用该方法判断某一次动画的播放帧是否已经播放完毕,比如:爆炸效果。(2)精灵的移动public void move(int dx,int dy)小提示:当多次使用move()方法移动精灵后,其在屏幕上的确切位置就有些模糊了。此时可以使用精灵类提供的getX()和getY()方法,来获取精灵的当前坐标位置,以此判断精灵是否移出了屏幕,即进行边界的处理。要点
19、提示int sequence=6,7,8,0,1,2,3,4,5,9,10,11 ;/上、下、左、右播放帧序列int direction=-1;/行走方向 0:上 1:下 2:左 3:右-1:无方向4.2.6 延伸任务:控制主角人物,按键行走,释放站立private void controlFrame(int direction)if(frameIndex sequencedirection.length-1)frameCounter-;if(frameCounter=0)frameIndex+;frameCounter=3;else frameIndex=1;/跳过0帧(站立帧)spMan.
20、setFrame(sequencedirectionframeIndex);/设置帧4.3 子弹设计 在射击类游戏中,除主角和NPC之外,子弹是一个很重要的游戏元素。他可以自动地、源源不断地发射,或者按键触发后发射。其实,子弹只是一个特殊的游戏元素,使用与设计子弹同样的方法,可以设计任何形状的、与游戏角色相关的游戏元素,比如:潜水艇扔下的水雷、武士发出的暗器、天上飘落的雪花、大富翁游戏中掉落的金子等等。在本学习单元中,首先,通过Vector类进行子弹的设计;然后,通过自定义类的方法设计子弹,并指出两种设计方法在实现手段和执行效率及执行效果上的差别;最后,通过实现散弹,进一步学习精灵类对象在运行
21、过程,进行图片的动态转换和对象的旋转。4.3.2 使用Vector类设计子弹 知识准备:了解Vector类v Vector类位于java.util包中。在J2ME MIDP2.0中,保留了J2SE中的Vector类。该类对用户隐藏了内存分配的细节问题,使用Vector可以作为需要动态生成对象的容器。而游戏中的子弹则是动态生成的最好例子。v Vector有“矢量、媒介、载体”的含义,很多书上称之为“向量”。但把他看成一个“容器”也许更形象一些,因为他能够像容器一样存放各种类型的对象。而且这个“容器”随着存放对象的多少,其长度可以动态地变化。从这个角度来说,Vector就是一个能够存放任意类型对象
22、的动态数组。任务准备(1)声明子弹相关变量(2)在构造方法中加载图片(3)自定义创建子弹的方(4)自定义移动子弹的方法(5)自定义绘制子弹的方(6)在绘制屏幕的show()方法中,添加对上述三个方法的调用语句,实现对子弹的绘制。(7)测试运行(1)新建MIDlet工程,工程名为“Ch4_Unit2”。(2)新建MIDlet类,类名为“Ch4_Unit2_MIDlet”。(3)新建GameCanvas类,类名为“Ch4_Unit2_GameCanvas”。(4)将飞机图片文件plane2.png,和子弹图片文件bullet.png,拷贝到工程的“res”文件夹下。实现过程知识提炼:Vector类
23、的常用方法(1)构造方法 public Vector(int initialCapacity,int capacityIncrement)说明:通过参数构造一个Vector的动态数组。能设置动态数组的初始容量和递增增量。参数:initialCapacity为Vector动态数组的初始容量。capacityIncrement为Vector动态数的递增增量。public Vector(int initialCapacity)说明:通过参数构造一个Vector的动态数组。只能设置动态数组的初始容量。一般是增长目前大小的一倍。参数:initialCapacity为Vector动态数组的初始容量。pub
24、lic Vector()说明:构造一个Vector动态数组。参数:无。(2)size方法语法:public int size()说明:返回当前Vector动态数组中的元素个数。参数:无。(3)addElement方法:语法:public void addElement(Object obj)说明:把组件加到向量尾部,同时大小加1,向量容量比以前大1。参数:Object obj是向动态数组中添加的对象。(4)removeElementAt方法:语法:public void removeElementAt(int i)说明:把第i个元素移出动态数组。同时,动态数组的大小减1。参数:int i 为要
25、移除的动态数组的第i个元素。(5)ElementAt方法:语法:public Object ElementAt(int i)说明:返回Vector动态数组中的第i个对象。参数:int i 为返回动态数组的第i个元素。返回值:返回Vector动态数组中的对象。注意,返回的对象要强制转换为自己所需要的类型。要点提示 private void createBullet()if(-distance=0)Sprite spBullet=new Sprite(imgBullet);spBullet.setPosition(spPlane.getX()+10,spPlane.getY();vecBullet
26、.addElement(spBullet);distance=6;4.3.3 任务五:使用自定义子弹(Bullet)类的方法设计子弹 任务四和任务五,采用不同的方法,实现了同样的效果。但要注意区别两者在实现方法和执行效率及执行效果上的差别。Vector类使用方便,但其对内存的频繁动态分配,在一定程度上会影响执行效率。并且放入Vector中的对象,一般都具有相同的属性和行为,比如,都按同一轨迹运行。而自定义子弹类的方法,则比较灵活。可以通过给定不同的子弹图片,定义各种不同形状的子弹,可以设计多种运行轨迹,使得不同的子弹可以按不同的轨迹运行。需要注意的是,Bullet类只是一个普通的JAVA类,并
27、没有继承自精灵类。主要原因是精灵类占用内存空间比较大一些,而且运行起来比较慢。因此,在本任务中只声明了一个子弹精灵spBullet,而不是十五个。并该子弹精灵看成是一个“精灵图章”,当需要显示15颗子弹中的某一颗时,只需在相应的位置上“盖”上对应的“精灵图章”即可,这样既节省了内存,又提高了运行效率。4.3.4 延伸任务:实现散弹 知识提炼(2)精灵对象的旋转使用setTransform()方法可以实现精灵对象的旋转,语法格式如下:public viod setTransform(int rotating)其中,参数Rotating是由8个静态属性常量来表示。TRANS_NONE:保持图像的原
28、始位置。TRANS_ROT90:顺时针旋转90度。TRANS_ROT180:顺时针旋转180度。TRANS_ROT270:顺时针旋转270度。TRANS_MIRROR:沿着图片垂直Y轴翻转。TRANS_MIRROR_ROT90:沿着图片垂直Y轴翻转后,再顺时针旋转90度。TRANS_MIRROR_ROT180:沿着图片垂直Y轴翻转后,再顺时针旋转180度。TRANS_MIRROR_ROT270:沿着图片垂直Y轴翻转后,再顺时针旋转270度。(1)精灵图片的动态转换使用setImage()方法可以在运行时,实现精灵图片的动态转换,语法格式如下:public void setImage(Image
29、 img,int frameWidth,int frameHeight)其中,参数img表示要动态改变的图片对象,frameWidth和frameHeight是图片帧的宽度和高度。4.4 NPC(非玩家控制的角色)设计 NPC,即非玩家控制的角色,比如:射击类游戏中的敌机。NPC的设计通常包括:形象设计、出现的时机、出现方式、运行轨迹、碰撞检测、生命值和得分等简单的数值设计、以及简单的AI设计等等。在本学习单元中,以射击类游戏中的敌机为NCP的特例。首先,对其的出现方式和运行轨迹进行设计;然后,通过添加时间轴,对其的出现时机进行了统一的安排和设计;接着,进行主角子弹和敌机的碰撞检测;最后,通过
30、出现BOSS,进行简单的数值设计和AI设计。4.4.2 任务六:出现敌机(队列)要点提示:敌机队形的设计 在本任务中,设计了四种队形,但实现方法都是类似的。要点是首先定位第一架敌机,后面的敌机位置分别是前一架敌机在x位置或y位置上间隔一定的距离。比如,斜线排列的队形。首先,定位第一架敌机的位置是(18,-10);然后,后面的敌机分别在前一架敌机的右上侧,x方向上距离36像素,y方向上距离35像素。代码如下:spNpc10.setPosition(18,-10);/设置队列中第一架敌机出现的位置for(int i=1;i spNpc1.length;i+)spNpc1i.setPosition(
31、spNpc1i-1.getX()+36,spNpc1i-1.getY()-35);水平排列和垂直排列的方法与此类似。根据这个方法还可以设计出很多其他的队形。4.4.3 延伸任务:添加时间轴,安排游戏情节 timing+;if(timing=20*1|timing=20*7)npcPlane.setNpc0Position(spNpc0,screenWidth);else if(timing=20*3|timing=20*9)npcPlane.setNpcQueue1(spNpc1);else if(timing=20*4|timing=20*14)npcPlane.setNpcQueue2(s
32、pNpc2,screenWidth,screenHeight);else if(timing=20*5|timing=20*15)npcPlane.setNpcQueue3(spNpc3,screenWidth);else if(timing=20*6|timing=20*18)npcPlane.setNpcQueue4(spNpc4);时间出现NPC1秒和7秒出现一架敌机3秒和9秒出现敌机队列14秒和14秒出现敌机队列25秒和15秒出现敌机队列36秒和20秒出现敌机队列4要点提示:时间轴 在射击类游戏中,敌机的出现都应该是事先安排好的,在什么时刻出现什么敌机,什么时刻出现什么状态。例如,在本
33、任务中,游戏开始1秒钟后,出现第一架敌机,过3秒钟后,出现敌机队列1,过4秒钟后,出现敌机队列2,。这类似于游戏的脚本,从游戏的开始到结束,都贯穿着时间轴的概念。在时间轴的设计中,使用了记录时间的一个长整型变量timing,在每个线程周期中,该变量值就会增加1。因为每个线程周期为50毫秒,当timing的值为201时,就意味着过了50201毫秒,即1秒钟。依次类推,当tming的值为202时,就意味着游戏开始后2秒钟,。时间轴的实现方法,可以使用如任务中的ifelse if结构,当然,也可以使用swtich结构。不管是什么游戏,都会有条“主线”,例如:对于射击游戏,主线就是时间。对于RPG(角
34、色扮演类)游戏,主线可能就是游戏的故事情节。而对于益智类游戏来说,主线则显得不是很明朗,例如五子棋游戏,那么他的主线就是根据玩家下的每一步棋子,来判断下一步应该怎么操作。4.4.4 任务七:实现主角子弹和敌机的碰撞检测 要点提示:关于爆炸效果 程序中,每一架敌机都对应地定义了一个爆炸精灵,这是因为爆炸帧的播放需要一个过程,因此两架甚至多架敌机的爆炸效果可能会同时出现在屏幕上,所以爆炸精灵一般不共用。爆炸效果的绘制是一个逐帧绘制的过程,当最后一帧绘制完毕的时候,就应该停止。否则,爆炸效果会反复出现。精灵类提供的getFrame()方法,可以获取当前正在播放的帧索引号。当帧索引号再次变为0的时候,
35、表示帧序列已经播放完毕,此时,应该再次设置当前的爆炸精灵为不可见。代码如下:if(spExplosion.getFrame()=0)/如果帧序列播放完毕(重新开始下一循环序列了)spExplosion.setVisible(false);知识提炼 public final boolean collidesWith(Sprite s,boolean pixelLevel)public final boolean collidesWith(Image image,int x,int y,boolean pixelLevel)public final boolean collidesWith(Til
36、edLayer t,boolean pixelLevel)每种碰撞的检测都存在两种级别的检测模式:区域级模式和像素级模式。4.4.5 任务八:出现BOSS要点提示(1)数值设计在本任务中,设计Boss的初始生命值为100,被子弹击中一次,生命值就减2,直至为0,Boss爆炸。在玩家得分的设计上,击中一架敌机,得分加10。当Boss被击爆时,得分加100。(2)Boss的AI设计在Boss完全移入屏幕之前,子弹击中Boss,其生命值是不减的。当Boss作水平移动时,碰到左右边界,会自动折返。当被子弹击中时,显示闪烁效果,并且移动速度变慢。4.5 游戏背景设计 游戏背景设计是游戏的一个亮点所在,当
37、拿到一款游戏的时候,这款游戏是否吸引人,由两个方面所决定,游戏的控制和游戏的背景设计。在MIDP1.0中没有提供专门负责背景设计的类,而在MIDP2.0中提供了一个TiledLayer类,该类专门负责对游戏背景进行管理和操作。由于移动设备的存储资源有限,因此不可能装载过大的图片来作为游戏的背景。本学习单元中,首先,通过一个图块(瓷片)“铺设”单一的游戏背景;然后,使用由一块一块图块(瓷片)合成的图片,来“铺设”丰富的游戏背景;最后,学习使用地图编辑器,进行大型地图的设计,并显示在手机屏幕上。4.5.2 任务九:使用一个图块,“铺设”单一的游戏背景知识提炼TiledLayer类被定义在javax
38、.microedition.lcdui.game包中。该类代表一个背景图层,将背景所占区域分成大小相等的一系列单元格,每个单元格可被一个图块填充,最终构成一个组合成的大图块背景。(1)背景的初步规划根据手机屏幕的大小,以及图块的宽、高,设计使用该图块铺满整个屏幕,横着需要多少个图块,竖着需要多少个图块。使用TiledLayer类的构造方法可以进行游戏背景的初步规划。TiledLayer类的构造方法语法如下:public TiledLayer(int columns,int rows,Image image,int tileWidth,int tileHeight)(2)背景的铺设为了形成背景,
39、必须将图块填充到单元格中,通过调用setCell()方法可以为单元格设置引用的图块。语法如下:public void setCell(int col,int row,int tileIndex)(3)背景的绘制背景形成后,需要通过paint(Graphics g)方法绘制出TiledLayer实例。要点提示 在本任务中,water.png文件中只有一个图块,该图块的大小为3030,整个屏幕都使用同一个图块进行填充,类似这种使用同一个图块填充一片连续区域的情况,也可以使用fillCells()方法来完成。语法如下:public void fillCells(int col,int row,int
40、 numCols,int numRows,int tileIndex)因此,程序中如下的相关代码:for(int i=0;i backCols;i+)for(int j=0;j backRows;j+)tlgBackground.setCell(i,j,1);可以使用如下这条语句进行替代:tlgBackground.fillCells(0,0,backCols,backRows,1);4.5.3任务十:使用合成图块,“铺设”丰富的游戏背景要点提示 本任务中,使用了由一个一个图块合成的图片文件进行地图的铺设,这些图块必须拥有相同的宽度和高度。每个图块按从左到右,从上到下的顺序分配一个索引号,索引
41、号从1开始。小提示:如果使用索引为0去填充单元格,则表示该区域不填充、绘制任何东西。4.5.4 任务十一:使用地图编辑器,设计大型地图 对于飞行射击类、角色扮演类等手机游戏,在设计游戏背景的时候,通常比手机屏幕要大很多,并且在游戏过程中,背景会自动地滚动,或者随着游戏主角的移动而移动,以增加游戏的动感。这样大的一个游戏背景,全凭想象来设计二维的地图数据,显然是不可行的。因此,在本任务中,通过借助于第三方开发的地图编辑器,来设计大型地图,将大大简化工作量和工作难度。比较常用的手机游戏地图编辑器有Mappy(mapwin)、Tiled、TILE STUDIO等等,这些地图编辑工具可以到相关网站免费
42、下载。(2)在手机屏幕上显示地图 首 先,将 导 出 并 修 改 的“m a p 1.p n g”文 件 拷 贝 到“Ch4_Unit4”工程的“res”文件夹下。在“Ch4_Unit4”工程中新建类,类名为“Map1”。打开“Map1”类,声明一个二维数组,并把map1.txt文件中的二维数组“map1_map0”的值拷贝过来。将 原 先 声 明 的 二 维 数 组“mapData”删除,并将代码中所有引用该二维数组的地方,全部用“Map1.map1Data”替代(1)使用mapwin编辑地图 新建地图 导入图块 铺设地图 保存地图文件 导出地图数据 实现过程小提示:单击菜单“MapTool
43、s”“Remove Unused or Duplicate”,可以在导出的图块文件中移除那些在编辑地图过程中没有用到的图块。4.5.5 延伸任务:铺设多层地图4.6 滚屏设计 4.5.4的任务十一和4.5.5的延伸任务中,铺设的地图都比手机屏幕大许多。默认情况下,手机屏幕上显示的仅仅是地图的左上角部分,其余部分由于位于屏幕之外,而无法显示。如果能让屏幕“滚动”起来,不仅能逐渐展示完整的地图,丰富玩家的视觉效果,而且还可以根据游戏情节需要,切换游戏场景,增加游戏的动感。在本学习单元中,首先,实现游戏屏幕的自动滚动,了解实现滚屏的基本原理;然后,与任务八中完成的成果相结合,实现一个简单的滚屏飞行射
44、击游戏;最后,实现RPG游戏中的滚屏效果,即屏幕随着游戏主角的移动而移动。4.6.2 知识准备:了解层管理器 事实上,Sprite类和TiledLayer类的父类均是定义在javax.microedition.lcdui.game包中的Layer类。Layer类是一个抽象类,表示游戏中的一个可视元素图层。图层是可以在游戏画布中单独绘制并能够移动的对象。每个Layer类实例都有一定的位置、宽度、高度和是否可见的状态。Layer类的子类必须实现一个piant(Graphics)方法,使得它们能够被绘制在屏幕上。游戏中不止一个图层存在,每个加入LayerManager中的图层都会被分配一个索引值,索
45、引值从0开始顺序分配。就想排队一样,先加入的排在前面,后加入的排在后面。但需要注意的是,图层的绘制过程却是相反的,排后面的(即索引值大的)先绘制,排前面的(即索引值小的)后绘制。显然,后绘制的图层将覆盖先绘制的图层。另外,LayerManager中的图层索引号永远是连续的,如果一个图层从LayerManager中被移除,那么后面图层的索引号都将相应地作调整,以便使得图层索引号保持连续性。4.6.3 任务十二:实现游戏屏幕的自动滚动实现滚屏的原理:只需要在线程周期中不断改变可视窗口的x坐标(实现横向滚动),或者改变y坐标(实现纵向滚动)。然后,将改变坐标位置后的可视窗口重新绘制在手机屏幕上即可。
46、X轴(0,0)Y轴(0,viewY)手机屏幕可视窗口(0,0)screenHeightscreenWidthsetViewWindow(0,viewY,screenWidth,screenHeight)paint(g,0,0)知识提炼:LayerManager类其他常用方法(1)LayerManager类构造方法(2)append方法(3)remove方法(4)getLayerAt方法(5)getSize方法小提示:综合使用上述三个方法,可以实现将加入图层管理器对象中的图层全部清空的功能。清空图层管理器对象在RPG游戏的场景转换中非常有用。参考代码如下:public void clearLay
47、erManager()if(lm!=null)while(lm.getSize()!=0)lm.remove(lm.getLayerAt(0);System.gc();/垃圾回收4.6.4 延伸任务:实现滚动背景下的飞行射击效果4.6.5 任务十三:根据主角人物的位置,重置地图视角要点提示(1)重置地图视角所谓“重置地图视角”就是根据主角的位置,重新设置可视窗口的位置。设置可视窗口的代码如下:lm.setViewWindow(viewX,viewY,screenWidth,screenHeight);其中,参数viewX和viewY是可视窗口在地图层中的左上角坐标,这个坐标是根据主角行走时,在
48、地图层中的位置来确定的,并保证主角在屏幕的正中间。就像舞台上的追光灯一样,追着舞台上的主角,使得主角始终位于灯光的中间。viewX=spPlayer.getX()+spPlayer.getWidth()/2-screenWidth/2;viewY=spPlayer.getY()+spPlayer.getHeight()/2-screenHeight/2;当然,如果主角的位置距离屏幕的上、下、左、右边缘小于屏幕高度和宽度的一半时,当主角继续按原方向行走时,屏幕就不再移动了。这时,viewX和viewY的值就始终是可视窗口在地图层中的边界值。viewX=0;/左边界值viewX=backCols*
49、16-screenWidth;/右边界值viewY=0;/上边界值viewY=backRows*16-screenHeight;/下边界值(2)精灵与地图层的碰撞 在本任务中,使用的地图有两层,0层是行走层,1层是碰撞层。主角在走动过程中,需要检测是否和碰撞层发生了碰撞,如果碰撞,则回退并站立。精灵和地图层的碰撞检测方法语法如下:public final boolean collidesWith(TiledLayer t,boolean pixelLevel)在碰撞检测过程中,为了使效果更逼真一些,可以自定义精灵对象的碰撞区域。默认情况下,精灵对象的碰撞检测区定位在左上角(0,0),并与精灵对
50、象的大小相同。自定义碰撞检测区域方法的语法如下:public void defineCollisionRectangle(int x,int y,int width,int height)比如,本任务中,在进行主角和地图碰撞层的碰撞检测之前,首先自定义了主角精灵的碰撞检测区域,代码如下:spPlayer.defineCollisionRectangle(0,spPlayer.getHeight()/2,spPlayer.getWidth(),spPlayer.getHeight()/2);这样,就将主角精灵的碰撞检测区域定位在了下半身,当主角精灵的上半身和碰撞层中的房屋、数木、石头等发生重叠的