文末有源代码,以及本游戏使用的所有素材,将plane2文件复制在src文件下可以直接运行。
实现效果:结构设计角色设计飞行对象类FlyObject战机类我的飞机MyPlane敌方飞机EnemyPlane子弹类我的子弹MyBullet敌方子弹EnemyBullet道具类Prop加分,加血,升级地图背景类Background玩家类PlayerHP,得分
线程类绘制线程DrawThread移动线程MoveThread生成敌方飞机线程EnemyPlaneThread敌方飞机生成子弹线程EnemyButtleThread检测碰撞线程TestCrashThread
界面类主界面GameUI选择地图界面SelectMapUI
监听器类KListener通过按压键盘改变我方飞机的速度
数据结构我方战机(只有一个)我方飞机子弹集合敌方飞机集合敌方子弹集合道具集合
详细分析Main界面类使用边框布局,给面板分三个区,如图所示
关键代码:
JFramejf=newJFrame("飞机大战");//创建窗体(670,800);(null);(_ON_CLOSE);(newBorderLayout());//布局//创建三个JPanel,左上为按钮,左下为分数显示右为游戏页面JPanelleft=newJPanel();JPanelleftUp=newJPanel();//左上JPanelleftDown=newJPanel();//左下game=newJPanel();//游戏显示区(newDimension(170,800));(newColor(-3355444));(left,);(game,);();(newBorderLayout());(newDimension(0,250));(newColor(-3355444));(leftUp,);(newColor(-6710887));(newDimension(0,550));(leftDown,);绘制背景地图飞行道具类UML图
判断FlyObject对象是否碰撞
publicbooleanjudge_crash(FlyObjectfo){if(x+||y+||+||+){returnfalse;}else{returntrue;}}绘制线程:如何让我们的游戏动起来
秒钟变化25幅,那么人的感觉屏幕上的图像是动的。
绘制时要把所有的飞行物都绘制一遍,所以我们需要在每一个飞行物被创建时,添加到相关的飞行物集合中。(为了方便传值,我们将集合设为静态变量)
我们的绘制线程,选择每30ms绘制一次,注意先画背景,然后再遍历飞行物集合画飞行物。
背景的绘制要想绘制动态的背景,首先我们要先画一张静态的背景图,那么如何绘制一张静态的背景图呢?
获取包中的图片:
StringfileName_0="src\\plane2\\z_img\\img_bg_0.jpg";//相对地址(和绝对地址区分开)BufferedImagebufferedImage;bufferedImage=(newFile(fileName_0));//将文件读出记录在bufferedImage中,记得抛出异常(bufferedImage,0,0,null);//将bufferedImage中的内容画在画笔g对应的地方
我们的地图是一张可以从上往下无缝滚动的图片,就像是这样的地图
接下来,如何画出连续的图片呢?
在绘制函数中,有一个函数可以完美实现我们的需求
img––––––––––(Imageimg,intdx1,intdy1,intdx2,intdy2,intsx1,intsy1,intsx2,intsy2,ImageObserverobserver);
比如说,我们的图片高度为712个像素点,我们在下一时刻,图片向下移动了m个像素点,那么我们就将这张图片的0~712-m部分,绘制到游戏界面的m~712部分,
再将712-m~712部分绘制到游戏界面的0~m部分;
接下来,我们就要确定m的值,这个就很简单了,在绘制线程中,定义一个整数变量m,每次绘制完m++就可以了。(个人建议m+=2比较舒服)
/***@authorliTianLu*@Date2022/5/2123:33*@purpose绘制背景*提醒:这里我写了四种地图的绘制,后面在选择地图时会用到。*/publicclassBackGround{Graphicsg;BufferedImagebufferedImage_1;BufferedImagebufferedImage_2;BufferedImagebufferedImage_3;BufferedImagebufferedImage_4;intw;inth;StringfileName_1="src\\plane2\\z_img\\img_bg_1.jpg";//地图1StringfileName_2="src\\plane2\\z_img\\img_bg_2.jpg";//地图2StringfileName_3="src\\plane2\\z_img\\img_bg_3.jpg";//地图3StringfileName_4="src\\plane2\\z_img\\img_bg_4.jpg";//地图4publicBackGround(Graphicsg)throwsIOException{=g;bufferedImage_1=(newFile(fileName_1));bufferedImage_2=(newFile(fileName_2));bufferedImage_3=(newFile(fileName_3));bufferedImage_4=(newFile(fileName_4));w=bufferedImage_1.getWidth();h=bufferedImage_1.getHeight();}/***i:向下移动了i个像素*num:用来控制绘制哪一个地图*/publicvoiddraw(inti,intnum){switch(num){case1:(bufferedImage_1,0,i,w,i+h,0,0,w,h,null);(bufferedImage_1,0,0,w,i,0,h-i,w,h,null);break;case2:(bufferedImage_2,0,i,w,i+h,0,0,w,h,null);(bufferedImage_2,0,0,w,i,0,h-i,w,h,null);break;case3:(bufferedImage_3,0,i,w,i+h,0,0,w,h,null);(bufferedImage_3,0,0,w,i,0,h-i,w,h,null);break;case4:(bufferedImage_4,0,i,w,i+h,0,0,w,h,null);(bufferedImage_4,0,0,w,i,0,h-i,w,h,null);break;}}publicintgetH(){returnh;}}绘制线程:
(m,);m=m+2;if(m=()){m=0;}我的飞机的绘制使用的飞机素材图片:
//这里仅使用了三张图片来回切换,更多的图片会有更好的效果publicvoiddraw(inti){//此处的i是用来控制显示哪一张图片的intj=i%30;//150ms换一张if(j10){(plane_img,x,y,x+sizeX,y+sizeY,0,0,sizeX,sizeY,null);}elseif(j20){(plane_img,x,y,x+sizeX,y+sizeY,0,sizeY,sizeX,2*sizeY,null);}elseif(j30){(plane_img,x,y,x+sizeX,y+sizeY,288,0,424,112,null);}}敌方飞机,敌方子弹等飞行物的绘制原理与MyPlane相同,后面不在赘述。(为了简化开发流程,飞行物可以不”扇动翅膀“)
移动线程我们已经给每个飞行对象设置了X轴移动速度和Y轴移动速度,所以每次移动的时候,我们只需要遍历所有的飞行对象,
然后逐个移动一个speedX和speedY单位即可。
多久移动一次呢?和绘制线程的间隔时间相同就好了,我们都设为30ms.
当飞行物飞出屏幕时,将飞行物移出集合,减少计算机资源的消耗。
如何控制我的飞机移动?当然是通过键盘的↑↓←→来控制了,我们需要设置一个键盘监听器给game界面,
注意要先使用();获取焦点,键盘监听器才可以使用。
@Override//键盘按压时,设置速度publicvoidkeyPressed(KeyEvente){intc=();if(!=null){switch(c){case37:(-speed);break;case38:(-speed);break;case39:(speed);break;case40:(speed);break;}}}@Override//键盘释放时,速度设为0publicvoidkeyReleased(KeyEvente){intc=();switch(c){case37:case39:(0);break;case38:case40:(0);break;}}敌方飞机线程:如何生成敌方飞机呢?/***@authorliTianLu*@Date2022/5/220:30*@purpose产生敌机的线程*/@Overridepublicvoidrun(){intsleepTime=800;while(true){if(=500){//当分数高于500时,加快敌机产生的频率sleepTime=300;}EnemyPlaneenemyPlane=null;try{enemyPlane=newEnemyPlane();}catch(IOExceptione){();}(enemyPlane);newThread(newEnemyBulletThread(enemyPlane)).start();//启动一个发射子弹线程try{sleep(sleepTime+(300));}catch(InterruptedExceptione){();}}}敌方子弹线程:使每一个敌方飞机开火我们为每一个敌方飞机创建一个生成子弹的线程,要确定子弹产生的具体位置,就要知道敌方飞机的位置,所以我们要传入一个敌方飞机对象给该线程。
publicEnemyBulletThread(EnemyPlaneenemyPlane){=enemyPlane;}@Overridepublicvoidrun(){try{sleep(2000);}catch(InterruptedExceptione){();}while(()){EnemyBulletenemyBullet=null;intenemyBullet_x=()+25;intenemyBullet_y=()+66;try{enemyBullet=newEnemyBullet(enemyBullet_x,enemyBullet_y);}catch(IOExceptione){();}(enemyBullet);try{sleep(2000+(2000));}catch(InterruptedExceptione){();}}}检测碰撞线程:在子弹与敌机碰撞时,移除敌机此时我们会遇到一个问题,就是在遍历时,move移动线程有可能将其中的一个飞行物移出集合,会出现IndexOutOfBoundsException异常
,我们只需要在两个线程使用飞行物集合时,加上synchronized关键字,即可解决。
MoveThread遍历我的子弹集合
synchronized(){if(()!=0){for(inti=0;();i++){(i).setY((i).getY()+(i).getSpeedY());if((i).getY()=-100){(i);continue;}}}}TestCrashThread检测我的子弹与敌方飞机碰撞
synchronized(){for(inti=0;();i++){for(intj=0;();j++){if((i).judge_crash((j))){(j).setAlive(false);//关线程+=5;//分数+5(j);(i);j=-1;}if(i=()){break;}}}}其他功能:显示玩家hp,掉落道具,得分,升级,更换地图显示hp:每次检测到我的飞机与敌方飞机,敌方子弹碰撞,就减分。减到=0时,游戏结束。
得分:子弹打到敌方飞机时,加分,并将当前分数通过绘制线程绘制在屏幕上。
掉落道具:敌机消失的时候,随机掉落一个道具,我的飞机碰到道具时,回血/加分/升级
升级:我的飞机初始为1级,最高为3级,等级改变时,使用switch根据等级改变我的飞机的子弹发射方式。
更换地图:使用一个新的窗体,设置几个单选按钮,选择时通过监听器,改变地图的控制变量,从而改变地图的绘制。