Java游戏开发项目及游戏源码_第1页
Java游戏开发项目及游戏源码_第2页
Java游戏开发项目及游戏源码_第3页
Java游戏开发项目及游戏源码_第4页
Java游戏开发项目及游戏源码_第5页
已阅读5页,还剩50页未读 继续免费阅读

下载本文档

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

文档简介

-.z.Java程序设计工程1.工程背景为了提高学生动手能力,增加学生实践的时机,*软件公司实习单位要求学生5-6人组成开发团队进展Java程序工程开发,参考自己团队的实力与客户的要求,使用Java语言开发*一工程,此工程可以是应用程序,也可以是游戏开发等,工程名称自拟。2.硬件资源和软件资源(1)每组配备联网PC机5-6台,智能手机一部〔带有手机数据线〕HP打印机一台。(2)eclipse-SDK--win32软件开发程序(含有eclipseme插件)、SUNWTK无线开发包或Symbian_3_SDK_v0_9等手机开发包。3.实验室功能需求(1)实现学生机和教师机器(效劳器)进展通信,相互共享资源。(2)教师机器(效劳器)上安装一台打印机,实现学生机可以共享打印。(3)实现教师机器(效劳器)连接和访问Internet。(4)实现每一台学生机器访问Internet。(5)要求为每一工程组学生机和教师机器分配一个标识,即学生机器的机器名为ST1~ST10,教师机器名为Server。4.工程实施要求(1)要求各工程小组拿出详细的**Java程序设计工程报告书〔具体工程规划书格式参考附表一〕,关键步骤要有相应的截图。(2)要求工程组长向各小组讲解与演示自己小组的工程成果,组长小组成员要熟练的解释与分析自己负责编写的Java代码或工程分工成果。(3)最后提交用小组命名的文件夹,其中包括开发所用的所有资料与工程成果。附表一:雷霆战机工程报告书一、工程概述本工程为一个Java游戏,是一个基于J2ME平台的飞机射击类游戏。二、工程开发团队三、所用Java相关知识点用了java的MIDP2.0的游戏类、GameCanvas类、游戏中的多线程应用、游戏引擎框架构造等等。四、工程需求分析1.游戏程序是一项精度要求很高的程序系统,因为其代码利用率很高。一个实时运行的最终作品,每秒都会运行成千上万行程序,绘图事件、键盘事件都会以极高的频率在后台等待响应,假设有丝毫的差异都将很容易导致程序在运行不久后可能出现严重错误,甚至死循环。因此,其逻辑设计应当相当严谨,需将所有可能发生的事件及意外情况考虑在设计中。2.游戏中为了美观,适用性强,可能需要采用外部文件引入的图片贴图,有关贴图,在MIDP2.0中提供了用于增强游戏功能的game包,使得解决静态或动态、画面背景、屏幕刷新的双缓冲等都有较好的解决方案。3.玩家飞机的运行可以通过键盘响应事件控制,但敌方则因为是自动运行,就需要有一定的智能性;敌人飞机的运行算法也要进展相关的设置,已免游戏过于简单。4.对于双方发射的子弹应该赋予不同的速度,同时,程序应该设定敌人飞机的子弹不与敌人的飞机进展碰撞检测,已增加游戏的可玩性。5.双方的飞机在前进时也需要考虑到是否碰撞到对方飞机,以免重叠运行,造成许多物理上不可能的情况,缺乏真实感。每一次刷新页面、每前进一步都需要进展相关的碰撞检测。6.为了增加界面的美观,在程序中添加了白云。由于手机屏幕大小有限,所以白云的数量和出现的位置要经过相关的设置,才能实现白云不规则出现的效果。7.游戏的地图不可能通过绘图来解决。否则,不仅难于控制和处理过多的元素,也会因过多的大型图片而不能限制程序的大小,失去手机上程序的原则和Java的优势。8.Java是基于虚拟机的半解释型编译系统,其执行效率较C++等完全编译后的程序会低很多,程序如果不进展精简和优化,将可能导致运行的不流畅。除开发过程中对构造上的控制、变量的使用、算法的优化等优化外,还可以使用混淆器(Obfuscator)进展程序打包后的优化。9.游戏的完毕、开场、动态信息画面作为构成一个程序都是必不可少的重要局部。良好的用户界面更是吸引用户的硬指标,相关的美术构图和人性化设置也需要有一定的考虑。五、类设计〔包含构造图〕5.1游戏进入前的选择每个MIDlet程序都必须有一个主类,该类必须继承自MIDlet。它控制着整个程序的运行,并且可以通过相应函数从程序描述文件中获取相关的信息。该类中拥有可以管理程序的创立、开场、暂停〔手机中很可能有正在运行程序却突然来电的情况,这时应进入暂停状态。〕、完毕的函数。本程序主类为lzhhdm,并实现接口mandLIstener。图5-1游戏背景介绍图5-1游戏背景介绍首先显示的是游戏的背景介绍〔图5-1〕,为此,在类lzhhdm定义Form类对象a,在startApp()函数中判断isSplash是否为真,如果为真的话,将创立Form类的实例a,并且调用append()方法在表单上放置StringItem类的实例以显示游戏背景信息。使用语句ok=newmand("ok",mand.OK,1);实例化mand类对象ok。调用addmand()命令建立ok命令与Form之间的关联,调用setmandListener()命令使Form与mandListener建立关联。调用Displayable的seturrent()函数显示背景介绍窗口。当玩家点击ok后将调用display.setCurrent(menuscreen) 以显示游戏菜单menuscreen〔图5-2〕。图5-2游戏的菜单图5-2游戏的菜单类menuscreen继承自Canvas类,并实现接口Runnable和mandListener。在类menuscreen中定义了lowColor和highColor、highBGColor三个整型变量及布尔型变量co。其中lowColor赋值为0*000000FF,代表兰色,higColor赋值为0*00FF0000,代表红色,highBGColor赋值为0*00CCCCCC,代表兰灰色,即背景条。当玩家按住上或下键时,在函数keyPressed(intcode)中的整型变量menuInde*相应的减1或加1,相应的,在paint()函数中会根据menuInde*绘制选项是否被选中。在函数run()中,如果co为真,则不停的repaint(),设置co的意义在于,当进入游戏主画面后,co赋值为false,以终止绘制选项的repaint(),提高游戏速度。当移动选项条到*项,并点击ok时,在mandAction()方法中根据menuInde*的值判断选择了哪个选项,列如中选择“关于〞时,将调用lzhhdm类中的renwuShow()方法以显示〞关于〞界面〔图5-3〕,在renwuShow()方法中,Form类对象a=null,表示清空Form,并重新调用用append()方法在表单上放置StringItem类的实例以显示游戏关于信息,“帮助“界面的显示与〞关于“界面一样,只不过调用的是helpShow()方法。图5-3关于界面图5-3关于界面5.2mybullets类在介绍游戏主类gameScreen类之前,应该先简要说明一下玩家子弹类mybullets类,实际上,mybullets类是应该删除的,其要实现的功能应该放在gameScreen类中,但是由于设计游戏的过程也是一个学习的过程,而在当时,我并没有意识到这一点。Mybullets类继承自Sprite类,以实现玩家子弹的相关功能。首先,创立子弹状态数组privateint[][]bullets,其中,[i][0]代表子弹的*坐标,[i][1]代表子弹的Y坐标,[i][2]代表子弹Y方向速度,[i][3]代表子弹存活状态(由于此类是在早期设计的,而之后子弹存活状态使用了子弹射程作为标志位,所以其并没有起到作用)。类中定义的方法setfirstposition()起到定义玩家子弹发射坐标的作用(此方法在设计时起到的作用是消除每按一次开火玩家子弹位置就重新定位这个BUG,但是,这个BUG完全可以用设置标志位的方法消除)。方法newposition()实现的功能为更新玩家的子弹位置,并且检测玩家子弹与普通敌人的碰撞及记录玩家战果(更新子弹位置的功能可以由使用move()加设置标志位的方法取代;由于设计这个类的时候并没有考虑到添加BOSS等,所以在此检测碰撞,但添加BOOS等功能后,此处的检测完全可以和飞机对飞机等的碰撞检测封装在同一个方法中)。mybullets类在gameScreen中建立了对象数组huokebullet[9],代表玩家所能发射的9发子弹。进入游戏进入游戏y1>-1550pzbzover==0显示敌人玩家被击落了吗?玩家是否还有时机?继续游戏是否过关返回主界面YYNYYN图5-4gameScreen类主要关系流程图N5.3游戏逻辑及gameScreen类是游戏的主类,决定着敌人何时出现,控制着敌人出现的方法,判断敌人及玩家是否被击中等。它运行在独立的线程中,以恒定的频率刷新画面。本程序设置为1/20秒。其主逻辑如图4-4所示。gameScreen类所实现的功能gameScreen类要实现地图的滚动、敌人飞机的相关属性、玩家的相关属性等功能。gameScreen类包括了LayerManager,这样所有静态和动态的图象都不需要手动刷新,只需要在LayerManager中参加所有的需要控制的精灵,在统一由LayerManager刷新即可,因此,在gameScreen中创立LayerManager的对象lm,并在构造函数中实例子化。其他精灵类的对象如敌人飞机、玩家飞机、玩家飞机的子弹、敌人的子弹、BOSS及BOSS所属的子弹都需在gameScreen()类中建立相应的对象,并在构造喊数中实例化,且由lm.appned()方法添加到LayerManager类对象lm中。地图的创立由于手机存储空间的限制,不可能将整地图完整地存储在手机中,为了节约空间,往往提出地图中一样的图片组成一PNG格式的图片,然后象拼图一样拼出地图来,专业的游戏设计者往往自己写一个地图编辑器,以使拼图过程不是则痛苦。创立地图就需要使用TiledLayer。TiledLayer指的是由一块一块类似用瓷砖拼凑起来的画面。地图实际即为TiledLayer的一个对象。先利用TiledLayer的构造函数建立TiledLayer,根据构造函数的参数可以给定Cell数组的大小,并且地图图片切割成等尺寸的画面,并调用setCell()设置具体的图象格容。地图图5-5地图图5-5地图因此,创立一个返回TtiledLayer的方法createBackGround(),以便在gameScreen()的构造函数中调用。在方法中,定义整型数组map1[]以存储Cell的索引值。并使用tiledLayer.setCell(column,row,map1[i])设定TtiledLayer的容,以形成地图。其中i的值由循环for(inti=0;i<map1.length;i++)取得,column由语句column=i%15取得、行由row=(i-column)/15取得。画出地图后,由lm.append()将地图添加到LayerManager类对象lm中。由于地图位于Layer的最低层,即离用户视线最远的层,所以Tiledlayer最后一个被添加到lm中。地图的移动根据游戏的设定,游戏中地图是向下移动的,实现此功能的方法如下:首先,在使用createBackGround()函数创立地图数组时,用(row+1)*16-getHeight()语句对整型变量row2赋值,其中row+1代表地图有多少列,16为地图片的高度,而减去getHeight()是因为要留出一个屏幕的可视区域,由于J2ME规定坐标系中下方向为正,所以使用语句y1=-row2将row2的数值变为负数。其次,render()函数中,使用lm.setViewWindow(0,0,getWidth(),getHeight()+10000)设定可视区域的围,(0,0)表示ViewWindow的起始坐标,(getWidth(),getHeight()+10000)使用lm.paint(g,0,y1)决定ViewWindow从屏幕的哪里画起。在run()函数中的while(conti)中,使用语句y1=y1+1使得每次绘图都使地图下移1个象素。(参考图5-6)图5-6gameScreen类的构造函数gameScreen类的构造函数要将游戏中出现的所有精灵都实例化,实际上,这种方法严重的占用了存,但在当时,我并没有意识到这一点。由于敌人要求不停的出现,但是不可能设置过多的精灵,解决的方法是设定6个Sprite类对象j0、j1、j2及ullet1、ullet1、ullet2,分别代表三架敌机及其配属的子弹。所以在gameScreen类的构造函数分配这6个Sprite类的存储空间,并且使用newSprite(Imageimg,intwidth,intheight)实例化这6个类变量。同关尾cboss与游戏进展中的大飞机其他的Sprite类对象都需要使用一样的方法实例化。同样处于节约存的考虑,sboss与cboss同用3个Sprite类对象bossbullet0,1,2。在构造函数中,定义boolean型变量conti=true。conti的作用在于控制是否进展游戏画面的重绘及其他需要在画面重绘前进展的运算。在构造函数中,将mybullets类里的no和score初始化,现在看来no的初始化没有必要,但是score的初始化是必须的,因为这个变量存储着玩家每次游戏的成绩,如果不在此进展初始化,则玩家重新开场游戏后score并不归0。关于mandAction()方法每个创立mand实例的J2ME的应用程序也必须创立实现mandListener接口的实例。每当用户通过mandAction()方法的方式与命令进展交互的时候,就会通过mandLIstener.所以实现mandListener的类必须实现mandAction()方法。在mandAction()方法中,使用getabel()方法获取命令的标签。如果getLabel()=“暂停〞时,表示玩家点击了暂停键,此时,conti赋值为false,游戏画面的绘制及游戏相关的运算暂停,并且,使用removemand(c)语句将“暂停〞移除,使用addmand(newmand("继续",mand.OK,2));将〞继续“按纽〞添加进来。当玩家点击“继续“时,conti赋值为true,并且,一定要调用start()方法,否则继续功能不可用。必须调用start()方法的原因是:J2ME的线程已stop()方法拿掉,如果想停顿线程的运做,就必须依靠一个旗标(flag),在本程序中,flag就是boolean型变量conti。所以一旦此标识变量被设为false,则while(conti)循环就会完毕,线程也会跟着完毕。当用户按下“继续〞的时候,start()将重新产生一个线程继续执行相关的运算和画面绘制。当玩家通关时或者任务失败时,将显示相应信息,并使用上面的方法将“暂停〞键变为“返回〞键盘,当点击“返回〞键时,将返回主菜单项选择项,调用类lzhhdm里的方法menuscreensecond(),在此方法中,实例化一个MenuScreen类对象,并且使选项“新游戏〞改变为〞重新开场“(图5-7)。完成此项功能的语句子为MenuScreen.mainmenu[0]=〞重新开场〞。中选择〞重新开场“时,使用gamescreen=newgameScreen(this)将使所有变量重新被初始化,如地图的绘制、敌人出现位置的重置、敌人的数量、玩家飞机的当前位置等。使用gamescreen.start()重新开场程序的循环。Sprite类对象的碰撞检测及相关属性游戏进展中,即在while(conti){}中,需要进展玩家飞机、子弹与敌机及敌机子弹与玩家飞机的碰撞检测,即使用函数collidesWith(Sprite,boolean)。由于设计的问题,玩家发射的子弹与普通敌机的碰撞检测被写在了mybullets类中,并且只检测第一发子弹是否与敌人相碰撞,如果碰撞为真时,则使用setVisible(false)函数将敌机隐藏,使用setImage()函数将子弹精灵的三图片置换为爆炸图片(图5-8)。当敌人剩余飞机消失后,即所有的敌人都飞出了屏幕后,使用函数setVisible(true)将敌机重新设置为可见。在按“开火〞键时,使用setImage()函数将huokebullets重新设定为子弹图片(每次击落敌机后屏幕上的爆炸效果有3团,并且按“开火〞后爆炸图片就消失了,实际上setVisavle(false)的应该是子弹数组,而对敌人精灵使用setImage(),这样效果会好一些)。图5-8游戏界面图5-7图5-8游戏界面图5-7敌机与玩家的碰撞检测原理同上,都是使用的collidesWith()函数,遗憾的是,我在写这段代码的时候,并没有考虑设置玩家有4次时机,所以对敌人飞机setVisable(fasle)了,而将玩家的飞机换成了爆炸图片,之后,添加了玩家4次时机这个功能,由于玩家被击落后会重新从屏幕下方进入屏幕,所以爆炸的图片一闪而过,效果不是很好。玩家10次游戏时机的实现方法根据游戏设置,玩家在每关中有共四次时机,当玩家飞机被击中或撞击爆炸后,程序首先检测整型变量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,使其不再执行以上各步。input()input()函数的作用是检测用户的输入。首先使用if(inputno==0)判断用户的输入是否被制止,如为真,则用户输入不被制止。其次,调用getKeyStates()查询按键的状态。当玩家按方向键时,玩家飞机就向不同的方向运行,这需要使用c1.move(int*,inty)函数,当玩家控制飞机向左或右飞时,需要使c1.setFrame()函数改变飞机的图形(参见图5-7,此时飞机右飞)。同时,还需要判断飞机是否飞出屏幕,如,当飞机右飞时,用if(c1.get*()>(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(inti=0;i<=6;i=i+3),即检测3组子弹中每组的第一发,即0,3,6。其次,检测huokebullet[i].no是否等于1,当等于1时使用语句for(intz=i;z<i+3;z++)初始化该组子弹中的3发子弹,而设置子弹位置的函数应该在if(huokebullet[i].no==1)语句外设置,因为当初设计的时候mybullets类里的函数写成了一次设置三组子弹的形式。huokebullet[z].no=huokebullet[z].bulletheight赋予子弹射程。当程序循环运行时no--,当一次发射了三组子弹后,只有*一组子弹消失,即no等于1后才能继续发射子弹。现在看来,玩家发射子弹的设置是完全失败的,降低了效率。render()和renderboss()在方法render()过程中,除了要重绘飞机、地图、子弹外还要在上方绘制关卡信息、战果、玩家飞机数、及无敌状态时的无敌时间、大飞机生命条等。首先使用lm.setViewWindow()和lm.paint()设定可视围ViewWindow和从哪里画起(见5.3.3)其次,使用g.drawString()绘制屏幕上方的关卡信息、战绩、玩家剩余生命标志。drawString()中使用String.valueOf(huokebullet[0].rscore()+huokebullet[3].rscore()+huokebullet[6].rscore())返回玩家成绩score的字符串表示。其中根据playerno的值绘出玩家的飞机标志数(应该有更好方法,但是没有想到)当每次刷新绘图页面时,应使用GameCanvas的flushGraphics()将屏SHAPE幕后台的缓冲区的图像刷新到前台来(flushGraphics()应该写在render(){}的最后)。renderboss()方法重绘的是关尾的精灵cboss、相关信息等,与render()的区别在与于函数lm.paint(g,0,0),起始坐标是不可变的,即,关尾的地图背景是不可变的。实际上,renderboss()是完全不需要的,只要在render()函数中设置相关标志位就可以解决关尾的绘图问题。玩家飞机的生命标志使用drawImage()就可以绘制在屏幕上了。图5-9图5-95.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)语句。图5-10在判断语句if(sbz==2)里,将根据玩家的位置自动飞图5-10行。首先,根据玩家飞机的位置对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),此时飞机变红,在本次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不可见。5.5普通敌人相关属性普通敌人是指游戏中不断出现的兰色飞机。首先在程序中首先定义了aik、aip两个Random()类对象,ai和aipp两个整型变量。程序中使用switch(ai)语句判断下一次的飞机出现情况,为了到达不重复出现的效果,使用语句ai=aik.ne*tInt()%4〔同样应该在构造函数中放置此语句和aipp=aip.ne*tInt()%5,以使每次游戏开场的时候敌人飞机的出现顺序是不固定的〕,以随机出现0,1,2,3四个整数(代表着飞机的四种出现情况〕。情况1:使用aipp=aip.ne*tInt()%5取得随机数aipp,根据下面三条语句设置飞机的出现位置:j0.setPosition(100-aipp*30,planepoup+24);j1.setPosition(100,planepoup); j2.setPosition(100+aipp*30,planepoup-24);;将getHeight()/8赋给整型变量kkk,每次循环kkk-1,当kkk<=1时飞机转向,当aipp>0时,飞机向左下方运行,使用语句setFrame(0)、move(-3,3)达成向左下方运动的效果。当aipp<0时,向右下方运动,实现方法同上。情况2:初始位置设置方法同情况1。当kkk〈0时,飞机掉头向上飞,其中setFrame(3),move(-4,0)。情况3:初始位置设置方法同情况1。当kkk〈0时,飞机只向左转。设置情况3的原因是在更多的随机位置出现敌机。情况4:初始位置设置方法同情况1。但其中的飞机j1具有跟踪能力,其实现方法如下:使用2个if语句if(j1.get*()<c1.get*())、if(j1.get*()>c1.get*())判断J1在c1的左或右侧,并且实时根据判断情况使用setFrame()和move()改变飞机的形态,使用语句if((j1.get*()<c1.get*())&&((j1.get*()+48)>c1.get*())&&(j1.getY()<c1.getY()))判断c1是否在j1的下方,当c1在j1的下方时,发射子弹ullet1。以上四种情况的最后,都将使用函数ne*tInt()产生ai,aipp的值。设置整型数组[4],对应着4种出现情况的标志位。如,执行情况1,首先执行判断语句if([0]==1),在此语句中,首先使用setVisable(true)函数将敌人飞机设置为可见的,并根据上次运行的qipp的值设置敌人飞机的初始位置最后,[0]赋值为2。接着执行判断语句if([0]==2),在此语句中,首先使用move()函数使飞机向下运动,同时kkk减1,当kkk<=0时,飞机转向,此时,根据aipp的正负判断飞机向哪边转向。当飞机飞出屏幕时,[0]赋值为3。需要注意的一点是,当取得ai的数值时,一定要写上这条语句:[ai]=1;因为当4种情况都出现一便的时候,标志位[]里的数值都将变为3,如果不将其重新赋值为1,敌人飞机将只能出现4次。其他3种情况也大致如此。普通敌人是否发射子弹由以下语句if(((j1.get*()<=c1.get*()-18)||((j2.get*()-6)>=c1.get*()))&(z==0)),即c1在j1左侧18象素围或j2左侧6象素围时,j0、j1、j2一起发射子弹,z=1,表示在这组子弹消失前敌人不发射子弹。如果jpb的值为0,则判断语句if(jpb==0)里的move()语句将一直执行下去。之后,还需要对每发子弹于玩家飞机进展碰撞检测,如果碰撞,则将碰撞的这发子弹设置为不可见。5.6白云的实现原理为了游戏界面更加美观,程序中设定了精灵数组cloud[i]来表示白云,由于白云应该在所有飞机的上方,即cloud[i]应该最早被append()到LayerManager中,或者使用insert(cloud[],0)在索引数值0处插入Layer,本程序采用了第一种方法,即在gameScreen类的构造函数中按游戏设置的顺序使用lm.append()参加到LayerManager之中。程序中设定白云数为5。首先设置白云的初始位置,其语句如下:cloud[0].setPosition(25,planepoup-(65));cloud[1].setPosition(80,planepoup-(140));cloud[2].setPosition(112,planepoup-(90));cloud[3].setPosition(175,planepoup-(200));cloud[4].setPosition(223,planepoup-(70));其原理为:将屏幕的*数轴和Y数轴各分成5份,即在*轴的5个围每个围出现一朵白云,Y轴的每个围也只能出现一朵。所以的白云的起始位置在每次游戏开场时是固定的。白云位置设定后,使用move(0,1)使白云移动,由于白云初始位置设定在屏幕的不同区域,故其移动出屏幕的先后顺序是不同的,使用if(cloud[].getY()>planepo)判断白云是否飞出屏幕。如果为真则使用cloud[].setPosition(cloudposition*40,planepoup)设置白云的位置,其中,cloudposition=aicloud.ne*tInt()%5,aicloud为Random()类对象。乘以40表示其在*轴出现的围是多少,cloud[0]、cloud[1]、cloud[2]、cloud[3]、cloud[4]乘以的值分别为40,30、55、15、22。以达成白云的随机出现效果。5.7关尾BOSS及相关属性关尾处飞机在屏幕上方横向移动,而背景地图不动,所以使用renderboss()重绘屏幕,其中,paint(g,0,0)表示屏幕绘制点在坐标轴(0,0)处。BOSS生命进度条由以下语句绘制:g.setColor(255,0,0);g.fillRect(2,2,60,5);//生命进度条背静红g.setColor(255,255,255);g.fillRect(2,2,bosslife,5);//生命进度条前景白其中bosslife记录着BOSS的生命值,其初始值为0,当玩家每击中一次BOSS,其值加5,,即化出白色进度条,当bosslife==60时,表示过关,除玩家飞机与子弹外的其他Sprite均使用setVisable(false)使其不在显示,同样的pzbz要赋予1,以消除玩家飞机还能与敌人碰撞的BUG。如果cboss.get*()<0,则表示其在屏幕左方出界,应改为右飞。同理如果cboss.get*()>(getWidth()-cboss.getWidth()),则表示其在屏幕右方出界,应改为左飞。在飞机横向飞行中,使用以下语句判断飞机是否开火:if(((cboss.get*()<=c1.get*()-10)||(cboss.get*()<=c1.get*()+60))&&(sz==0))当每发一组子弹后,sz=1,则飞机无法开火,知道子弹飞出屏幕,sz才重新设定为0。而((cboss.get*()<=c1.get*()-10)||(cboss.get*()<=c1.get*()+60)表示当玩家飞机处于BOSS的左右各10个象素的围时。BOSS开火。关尾参见图5-10。5.8本章小结第四章中按照相应的步骤描述了所有关键类的具体算法实现,引用了相关函数进展了具体流程的解释,并对原理稍复杂的函数做了详细的分析。对游戏有关的各运行面也做了展示。图5-10关尾图5-10关尾六、详细程序代码与分析Lzhhdm.java负责:mportjava*.microedition.midlet.MIDlet;importjava*.microedition.midlet.MIDletStateChangeE*ception;/**主程序*/importjava*.microedition.lcdui.*;publicclasslzhhdme*tendsMIDletimplementsmandListener{publicDisplaydisplay;privateImagesplashLogo;privatebooleanisSplash=true;publicForma;privateAlertalert;intlength;privateMenuScreenmenuscreen;privategameScreengamescreen;privatemandok,back;privatebyte[]byteInputData;publiclzhhdm() { }protectedvoidstartApp()throwsMIDletStateChangeE*ception{display=Display.getDisplay(this);menuscreen=newMenuScreen(this);if(isSplash) {System.gc();a=null;a=newForm("黑夜行动");ok=newmand("ok",mand.OK,1);a.append(newStringItem(null,"2037年12月12日,一群不明飞行物出现在松田上空,松田空战部队奋起还击,接下来就是进展一场惊心动魄的大战。。。。。。"));a.addmand(ok);a.setmandListener(this);display.setCurrent(a); } }protectedvoidmenuscreenShow() {display.setCurrent(menuscreen); }protectedvoidmenuscreensecond(){menuscreen=newMenuScreen(this);MenuScreen.mainmenu[0]="重新开场";//玩完一遍或牺牲了后在玩一遍菜单第一项改为重新开场display.setCurrent(menuscreen); }protectedvoidpauseApp(){}protectedvoiddestroyApp(booleanarg0)throwsMIDletStateChangeE*ception{ }protectedvoidhelShow() {System.gc();a=null;a=newForm("黑夜行动");back=newmand("返回",mand.BACK,1);a.append(newStringItem(null,"操作方式:上2下8左4右6开火5"));a.append(newStringItem(null,"弹药数:一次最多打三组"));a.addmand(ok);a.setmandListener(this);display.setCurrent(a); }protectedvoidrenwuShow() {System.gc();a=null; a=newForm("黑夜行动");back=newmand("返回",mand.BACK,1);a.append(newStringItem(null,"游戏名称:雷霆战绩之黑夜行动"));a.append(newStringItem(null,"版本号:1.00"));a.append(newStringItem(null,"制09网络1第六组"));a.addmand(ok);a.setmandListener(this);display.setCurrent(a); }protectedvoidgameShow() {try{ System.gc();gamescreen=null;gamescreen=newgameScreen(this);gamescreen.start();display.setCurrent(gamescreen);gamescreen.conti=true; }catch(E*ceptione*p) { System.out.println("dfg"); } }publicvoidmandAction(mandarg0,Displayablearg1){a=null; System.gc();this.menuscreenShow(); } }MenuScreen.java负责:importjava*.microedition.lcdui.*;//MenuScreen类继承CanvasimplementsRunnable,mandListener类publicclassMenuScreene*tendsCanvasimplementsRunnable,mandListener{Fontlowfont=Font.getFont(Font.FACE_MONOSPACE,Font.STYLE_PLAIN,Font.SIZE_MEDIUM);Fonthighfont=Font.getFont(Font.FACE_MONOSPACE,Font.STYLE_BOLD,Font.SIZE_MEDIUM);intlowColor=0*000000FF;//定义变量的颜色为黑色inthighColor=0*00FF0000;//定义变量的颜色为黑色inthighBGColor=0*00CCCCCC;//定义变量的颜色为黑色intwidth;booleanco;intheight;intstartHeight;//定义开场游戏时飞机的高度intspacing=highfont.getHeight()/2;publicstaticString[]mainmenu={"新游戏","帮助","关于"};intmenuInde*; ThreadmenuThread;privatemandok=newmand("ok",mand.OK,1);privatelzhhdmmidlet;//构造方法publicMenuScreen(lzhhdmmidlet) { this.midlet=midlet;width=getWidth();height=getHeight();startHeight=(highfont.getHeight()*mainmenu.length)+((mainmenu.length-1)*spacing);startHeight=(height-startHeight)/2;menuInde*=0; addmand(ok);setmandListener(this); menuThread=newThread(this);menuThread.start();co=true; }publicvoidrun() {while(co) {repaint(); } }//覆盖超类的抽象方法paintpublicvoidpaint(Graphicsg) { g.setColor(0*00FFFFFF);//设置画笔的颜色为黑色 g.fillRect(0,0,width,height);//绘制填充的图形,将屏幕底色填充成白色for(inti=0;i<mainmenu.length;i++) {if(i==menuInde*) { g.setColor(highBGColor); g.fillRect(0,startHeight+(i*highfont.getHeight())+spacing,width,highfont.getHeight()); g.setFont(highfont); g.setColor(highColor);//绘制画笔的颜色为黑色 g.drawString(mainmenu[i],(width-highfont.stringWidth(mainmenu[i]))/2,startHeight+(i*highfont.getHeight())+spacing,20); }else { g.setFont(lowfont); g.setColor(lowColor); g.drawString(mainmenu[i],(width-lowfont.stringWidth(mainmenu[i]))/2,startHeight+(i*highfont.getHeight())+spacing,20); } } }//按键事件处理publicvoidkeyPressed(intcode){//这是返回到上一个界面if(getGameAction(code)==Canvas.UP&&menuInde*-1>=0) {menuInde*--; }//显示下一个界面elseif(getGameAction(code)==Canvas.DOWN&&menuInde*+1<mainmenu.length) {menuInde*++; } }publicvoidmandAction(mandc,Displayabled) {if(c==ok) {switch(menuInde*) {case0:co=false;midlet.gameShow();break;case1:midlet.helShow();break;case2:midlet.renwuShow();break; } } }}Mybullets.java//玩家子弹类//子弹因为飞机的不同而不同importjava*.microedition.lcdui.*;importjava*.microedition.lcdui.game.*;publicclassmybulletse*tendsSprite{publicintscore=0;//战果privateint[][]bullets;//子弹数组//[i][0]子弹*坐标//[i][1]子弹Y坐标//[i][2]子弹Y方向速度//[i][3]子弹存活状态privateintbulletstotal;//数组的长度,即一次可以打出多少炮弹,视飞机型号不同而不同publicintwidth,bulletheight;//屏幕的高和宽publicintno=0;publicmybullets(Imageimg,intpicwidth,intpicheight,intbulletstotal,intwidth,intheight) {super(img,picwidth,picheight);this.bulletstotal=bulletstotal;bullets=newint[bulletstotal][4];this.width=width;this.bulletheight=height/7;//初始子弹射程 }publicvoidinitBullets(inti)//初始化子弹状态数组{bullets[i][3]=1;bullets[i][2]=0;}publicvoidupdata(inti)//根据速度更新子弹下一桢的位置,出界消失{bullets[i][1]+=bullets[i][2];}publicvoidsetfirstposition(int*,inty,intnof,Spritesprite[],Imageimg)//第一科子弹的起始位置{ sprite[nof].setVisible(true);sprite[nof+1].setVisible(true);sprite[nof+2].setVisible(true);//nof第F颗子弹,NOT第NOF+2颗子bullets[nof][0]=*+10;//第一子弹*位置bullets[nof][1]=y-24;//第一子弹〔最上面的〕Y位置,24是飞机左上的点的坐标,所以应减多点,和后面子弹拉开距离sprite[nof].setImage(img,6,6);bullets[nof+1][1]=bullets[nof][1]+10;//第N+1子弹Y位置bullets[nof+1][0]=*+10;//第N+1子弹*位置sprite[nof+1].setImage(img,6,6);bullets[nof+2][1]=bullets[nof+1][1]+10;//第N+2子弹Y位置bullets[nof+2][0]=*+10;//第N+2子弹*位置sprite[nof+2].setImage(img,6,6);}publicvoidnewposition(Spritesprite[],inti,intv,Spritejp0,Spritejp1,Spritejp2,Spriteboss,Imageimg){ bullets[i][2]-=5;//因为子弹是向上走的,所以是减 sprite[i].setPosition(bullets[i][0],bullets[i][1]+bullets[i][2]); sprite[i+1].setPosition(bullets[i][0],bullets[i+1][1]+bullets[i][2]); sprite[i+2].setPosition(bullets[i][0],bullets[i+2][1]+bullets[i][2]);if(sprite[i].collidesWith(jp0,true))//玩家子弹与敌人碰撞检测{ sprite[i].setImage(img,32,32); sprite[i+1].setImage(img,32,32); sprite[i+2].setImage(img,32,32); sprite[i].setFrame(1); sprite[i+1].setFrame(1); sprite[i+2].setFrame(2);bullets[i][3]=0;bullets[i+1][3]=0;bullets[i+2][3]=0; jp0.setVisible(false);no=1; score=score+1; }if(sprite[i].collidesWith(jp1,true)){ sprite[i].setImage(img,32,32); sprite[i+1].setImage(img,32,32); sprite[i+2].setImage(img,32,32); sprite[i].setFrame(1); sprite[i+1].setFrame(1); sprite[i+2].setFrame(2);bullets[i][3]=0;bullets[i+1][3]=0;bullets[i+2][3]=0; jp1.setVisible(false);no=1;score=score+1;}if(sprite[i].collidesWith(jp2,true)){ sprite[i].setImage(img,32,32); sprite[i+1].setImage(img,32,32); sprite[i+2].setImage(img,32,32); sprite[i].setFrame(1); sprite[i+1].setFrame(1); sprite[i+2].setFrame(2);bullets[i][3]=0;bullets[i+1][3]=0;bullets[i+2][3]=0; jp2.setVisible(false);no=1;score=score+1; }}publicbooleanisAlive(inti){if(bullets[i][3]==1)returntrue;elsereturnfalse;}publicvoidsetAlive(inti){for(intz=i;z<i+3;z++) {bullets[i][3]=0; }}publicintrscore(){returnscore;}publicvoidclean(inti,Spritesprite[]){for(intz=i;z<i+3;z++) { sprite[z].setVisible(false);no=1; } }}负责:gameScreen.java//游戏窗口//在这个类里检测碰撞importjava*.microedition.lcdui.*;importjava*.microedition.lcdui.game.*;importjava.util.*;publicclassgameScreene*tendsGameCanvasimplementsRunnable,mandListener{privateFormal;publicLayerManagerlm,lm1;TiledLayerb1;intheight=getHeight();intbosscolor=0;intsbosscolor=0;intsbz=0;//小BOSS相关标志位intsbmove=0;//小BOSS移动标志位置0不动1上2下3左4右intsfire=0;//SBOOS开火intsmovebz=0;//SBOSS移动标志intsbpzbz=0;//SBOOS与玩家子弹碰撞标志intslife=0;//sboss生命intsbo=0;//sboss和飞机碰撞后玩家飞机自动出现的标志位intdrawslife=0;//画SBOSS生命标志位intlr=0;intdrawadd=0;//增加一个飞机画个标志intpzbz=0;//碰状标记,解决中途被挂掉后的碰撞BUGintpzbzover=0;//解决过关后的敌机器仍然出现问题 intplaylife=-1;//玩家延续生命标志位intbosslife=0;//bosslifeintinputno=0;//键盘输入标志位intposition=0;intj1b=0;//CASE3语句里J1具有跟踪能力的子弹intsz=-1;//boss子弹标志intsbsz0=-1;//sboss左子弹标志intsbsz1=-1;//sboss右子弹intsbsz2=-1;//SBOSS上子intsbsz3=-1;//SBOSS下子弹intplanepo;//飞机下方参照坐标intplanepoup;//飞机上方参照坐标intkkk;intgz=0;//上跟踪标志位置intgzks=0;//跟踪开场标志位intjiangli=0;//奖励标志位intjplaneno;intcloundno=0;intright=0;//BOSS移动标志位,初始化向右移动intleft=1;intboss=0;intover=0;intai=0;intplanert=-1;//表示是否三次时机都没了intsupermen=0;//玩家挂后短暂无敌intovercmd=0;intbossover=0;intjpb=-12;//敌人子弹是否运行标志位intaipp=0;intz=0;intcloudposition;Randomaik=newRandom();Randomaip=newRandom();Randomaicloud=newRandom();int[]=newint[5];//敌人飞机,白云出现标志intplayerno=10;//玩家剩余飞机标志,3,2,1三次,0就是挂了privatemybullets[]huokebullet=newmybullets[9];privateSpriteullet0,ullet1,ullet2,bossbullet0,bossbullet1,bossbullet2;privateSpritecloud[]=newSprite[5];//白云彩privateMenuScreenms;privateSpritec1,sboss,cboss,j0,j1,j2,boss1;//2D时为玩家飞机privatelzhhdmmidlet;ints1=0;//无敌时间1ints2=65;//无敌时间2ints3=0;//无敌时间3introw2;introw;intplanecolor=0;//自己的飞机无敌的时候边红intplanecoco=0;//变红控制publicintby1;publicinty1;publicgameScreen(lzhhdmmidlet) {super(true); System.gc();this.midlet=midlet;addmand(newmand("暂停",mand.BACK,1));setmandListener(this);lm=newLayerManager();c1=newSprite(img("/pic/MyPlaneFrames.png"),24,24);//,getWidth(),getHeight()+1000);cboss=newSprite(img("/pic/boss.png"),65,50);//长*宽ullet0=newSprite(img("/pic/bullet.png"),6,6);ullet1=newSprite(img("/pic/bullet.png"),6,6);ullet2=newSprite(img("/pic/bullet.png"),6,6);bossbullet0=newSprite(img("/pic/bullet.png"),6,6);bossbullet1=newSprite(img("/pic/bullet.png"),6,6);bossbullet2=newSprite(img("/pic/bullet.png"),6,6);sboss=newSprite(img("/pic/smallboss.png"),65,50);b1=createBackGround();//创立背景c1.setPosition(getWidth()/2,row2+getHeight()-25);//精灵的起始位置row2+getHeight()-25=1655//精灵是精灵的坐标,而下面的Y1是画屏幕的位置//System.out.println("ffffffffffff");planepoup=row2;//屏幕上方边界planepo=row2+getHeight();//屏幕下方边界j0=newSprite(img("/pic/jplane2.png"),24,22);//宽24,高22j1=newSprite(img("/pic/jplane2.png"),24,22);j2=newSprite(img("/pic/jplane2.png"),24,22);kkk=getHeight()/8;cboss.setVisible(false);//aipp=3;ai=aik.ne*tInt()%4;if(ai<0)ai=-ai;aipp=aip.ne*tInt()%3;if(aipp==0)//动态确定下一组敌机位置的参数 {aipp=aip.ne*tInt()%3; }try {for(inti=0;i<=4;i++) {cloud[i]=newSprite(img("/pic/cloud1.png"),16,16);lm.append(cloud[i]); }}catch(E*ceptione) { System.out.println("cloud"); }lm.append(cboss);lm.append(j0);lm.append(j1);lm.append(j2);lm.append(bossbullet0);lm.append(bossbullet1);lm.append(bossbullet2);lm.append(ullet0);lm.append(ullet1);lm.append(ullet2);lm.append(sboss);[0]=1;[1]=1;[2]=1;[3]=1;try {for(inti=0;i<9;i++) {huokebullet[i]=playerbullet("/pic/bullet.png"); }}catch(E*ceptione){}try {for(inti=0;i<=8;i=i+3) {huokebullet[i].no=1;//okhuokebullet[i].score=0; }}catch(E*ceptione){System.out.println("ffffffffffffff");}for(inti=0;i<9;i++){lm.append(huokebullet[i]); }lm.append(c1);lm.append(b1); }privateImageimg(Stringpic) { Imageimg=null;try { img=Image.createImage(pic); }catch(E*ceptione*p) { System.out.println(e*p); }returnimg; }privatemybulletsplayerbullet(Stringpic)//,intp*,intpy,inttotal,intwidth,intheight) { Imageimg=null;try { img=Image.createImage(pic); }catch(E*ceptione*p) { System.out.println(e*p); }returnnewmybullets(img,6,6,21,getWidth(),getHeight());// }publicTiledLayercreateBackGround() { Imageimg=null;try { img=Image.createImage("/pic/beijing.png"); }catch(E*ceptione*p) { System.out.println("layercreateimage"); }//负责: TiledLayertiledLayer=newTiledLayer(50,200,img,16,16);int[]map1= {3,1,1,3,3,3,1,3,3,3,3,3,3,2,4, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,4, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,4, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,4, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,4, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,4, 1,3,1,1,1,1,1,1,1,1,1,3,3,3,4, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,4, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,4, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,4, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,4, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,4, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,4, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,4, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,4, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,4, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,4, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,4, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,4, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,4, 3,3,3,3,3,3,1,3,3,3,3,3,3,3,4, 3,3,3,3,3,3,3,1,3,3,3,3,3,3,4, 3,3,1,1,1,1,1,1,1,1,1,1,1,1,4, 3,3,1,3,3,3,3,3,3,3,3,3,3,1,4, 3,1,3,3,3,1,3,3,3,1,3,3,1,3,4, 3,3,3,3,1,3,3,3,3,3,1,3,3,3,4, 3,3,3,1,3,3,3,3,3,3,3,1,1,3,4, 3,1,1,3,1,1,1,1,1,1,1,3,1,3,4, 3,3,3,3,3,3,3,1,3,3,3,3,3,3,4, 3,3,3,3,3,3,3,1,

温馨提示

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

评论

0/150

提交评论