桌面弹球(java开发)分析及源码_第1页
桌面弹球(java开发)分析及源码_第2页
桌面弹球(java开发)分析及源码_第3页
桌面弹球(java开发)分析及源码_第4页
桌面弹球(java开发)分析及源码_第5页
已阅读5页,还剩40页未读 继续免费阅读

下载本文档

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

文档简介

桌面弹球(java开发)分析及源码·PAGE222·Eclipse从入门到精通桌面弹球1桌面弹球概述桌面弹球是游戏中常见的游戏,从以前的掌上游戏机到如今的手机游戏,都是一个十分经典的游戏。玩家控制一个可以左右移动的挡板去改变运动中小球的移动方向,目的是用小球消除游戏屏幕中的所有障碍物到达下一关,在障碍物被消除的过程中,可能会产生一些能改变挡板或者小球状态的物品,例如:挡板变长、变短,小球威力加强等等。本章主要介绍如何实现一个简单的弹球游戏,让读者了解“动画”的实现原理。在本章中,将介绍与使用Java的绘图功能,使用到JPanel的paint(Graphicsg)方法去绘图,绘图主要是依靠这个方法中的Graphics类型的参数,将使用Java中的Timer去重复绘图,产生动画效果,桌面弹球游戏的效果如图4.1所示。图1.1桌面弹球1.1动画原理简单地来说,动画是利用人的视觉暂留的生理特性,实现出来的一种假象,只要每隔一段时间(这个时间少于人的视频暂留时间)就重新绘制一幅状态改变的图片,就能造成这种“动”的假象。我们在程序中不断的进行绘画(使用repaint方法),对程序来讲,只需要在短时间内进行多次的绘画,并且每次绘画都需要改变绘画的相关值,就可以达到“动画”的效果。1.2小球反弹的方向在本章实现的过程中,我们会设置小球于对称的图3.1桌面弹球类图笔者在这里提供了本章的类图,是为了让读者可以更清晰的了解本章程序的结构,但在实现开发的过程中,我们可以根据实际情况,加入或者改变各个类的关系或者程序的结构,但最终都是为降低程序的耦合、提高内聚、编写出优秀的代码。3.1基类BallComponentBallComponent,做为Brick(砖块)类、Magic(道具)类、Stick(挡板)类、Ball(小球)类的父类,定义了这些子类共有的属性与方法,属性有:x坐标,初始值为-1;y坐标,初始值为-1;图片image,初始值为null;速度speed,初始值为5。根据不同的需要,提供以下三个构造方法:BallComponent(Stringpath),path是图片的路径,用图片的路径来构造一个BallComponent,在此构造方法中,将根据路径去读取图片,再设置对象中image属性。BallComponent(intpanelWidth,intpanelHeight,Stringpath),以panelWidth,panelHeight,与path去构造一个Component。BallComponent(Stringpath,intx,inty),以x坐标,y坐标和path去构造一个BallComponet。除去这些构造方法,此类提供了这些属性的setter与getter方法,用于获取对象的坐标与图片,或者改变对象的坐标位置与图片属性。如果我们在编码的过程中发现有一些共同的属性或者方法,我们可以将这些放到这个基类中。创建BallComponent的时候,我们可以将这个类变成抽象类,即使它没有任何的抽象方法,这样做的目的是,在我们的桌面弹球游戏中,该类并不是具体存在的某一个对象,而是我们将一些公用的属性或者方法存放到该类中,因此它在游戏中并不代表某个具体的对象。将该类创建为抽象类,我们就可以提供(如果需要的话)一些抽象方法让子类去实现,并且可以在父类中调用这些抽象方法。3.2砖块类(Brick)此类是BallComponet的一个子类,提供一个Brick(Stringpath,inttype,intx,inty)构造器,其中pah、x与y参数用于调用父类的构造器,type是代表砖块的类型:1代表此砖块里面有LongMagic类型的道具;2代表此砖块里面有ShortMagic类型的道具;其它代表此砖块里面没有道具。另外,本类增加了magic与disable属性,magic代表此砖块中所包含的道具,初始值为null,disable是用来标志Brick的状态,如果diable为true,则表明此砖块已经不可用,不会再显示。并提供这两个属性相关的以下方法:voidsetMagic(Magicmagic),设置道具。MagicgetMagic(),获取道具。booleanisDisable(),用来判断此类是否有效。voidsetDisable(booleandisable),停用或者启用此类,disable的值为true或者false。确定了一个砖块由一个Brick对象来表示后,在界面中,我们可以提供一个Brick的二维数组,来表示界面中所有的砖块,实现原理与控制台五子棋中的棋盘一样,但是在本章中,二维数组的每一个元素并不是字符串,而是具体的某个Brick对象,在以后的章节中,当遇到需要在界面中绘画某些图片的时候,我们都可以建立一个二维数组,将相应的对象放置到该数组中,当界面进行绘画的时候,就可以将这个二维数组“画”出来。3.3道具类及其子类(Magic)Magic类是一个道具类,在游戏中表现包含在砖块中,是BallComponet的一个抽象子类,此类提供一个Magic(Stringpath,intx,inty)构造器去调用父类的构造器,并提供一个抽象的方法magicDo(Stickstick),此抽象方法是实现道具的效果功能,用于给其子类实现,现在实现的子类的LongMagic类和ShortMagic类,两个子类的magicDo方法中分别实现使挡板变长与变短的功能。abstractvoidmagicDo(Stickstick),道具的功能,给其子类实现。在本例中,挡板是可以变长或者变短的,而使挡板变长或者变短的方式是通过道具来实现,因此可以将道具抽象成变长的道具或者变短的道具,而它们都需要做同一件是,就是改变挡板的展现形式。为了程序的可扩展性,我们在这里将一个道具变为一个抽象类(Magic),当我们需要有其他形式的道具的时候,就可以为该类添加子类,并提供不同的实现。当然,这里只提供一个Stick的参数可能并不够,如果以后游戏中出现另外一种道具,会改变球的速度(变快或者变慢),那么我们就需要为该抽象类提供更多的参数。3.4挡板类(Stick)同样,Stick类也是BallComponet的子类,用来代表游戏中的挡板,由于挡板只有左右移动的,所以,此类中只定义了挡板x方向的移动速度SPEED,还有定义挡板的初始长度preWidth,并提供此方法的setter与getter方法,如下:voidsetPreWidth(intpreWidth),设置初始长度。intgetPreWidth(),获取初始长度。由于该类继承于BallComponet类,因此只需要提供一个构造器即可。在本例中,挡板是可以变长或者变短的,并且在建立道具抽象类的时候,已经定义了一个magicDo的方法,该方法的参数就是一个挡板对象,所以挡板类必须包括长度的属性,这样,在实现道具类的时候,就可以通过改变挡板类的长度来实现本例中所需要实现的长短挡板功能。在Stick类中并不需要关心挡板的图片、位置与大小,这些属性已经在BallComponet中体现。3.5小球类(Ball)Ball类也是BallComponet的子类,由于小球在游戏面板中运动的时候除了横竖方向,还有各种角度的斜方向,所以我们把小球的速度分解成横向速度与竖向速度(speedX与speedY),游戏未开始前,小球是处于静止状态,所以用一个started属性来标志小球是否已经开始运动。游戏结束后,小球也是处于静止状态,但不能再移动,同样,用一个stop属性来标志小球是否能再移动。除了定义这些属性,还为这些属性提供相应的setter与getter

方法,如下:setSpeedX(intspeed),设置小球的横向速度。setSpeedY(intspeed),设置小球的竖向速度。booleanisStarted(),小球是否已经在运动。voidsetStarted(booleanb),把小球状态设置为运动或者静止。intgetSpeedX(),获取小球的横向速度。intgetSpeedY(),获取小球的竖向速度。在本例中,小球对象只保存一些相关的属性,例如横向速度与纵向速度(图片、位置与大小在父类中体现),如果需要改变小球的速度,可以调用相关的setter方法来进行,但是我们需要知道由哪些对象来改变小球的相关属性,我们在前面的章节中提到,提供一个业务类进负责处理游戏的相关逻辑,因此,业务类就需要维护一个小球的对象,来控制小球的运动或者其他行为。在这里,小球对象可以单纯的看作一个简单的对象,并不负责处理任何的行为,这可以看作我们一般所说的贫血模式,对象并不负责处理任何的业务逻辑。如果需要将该小球对象编写成为充血模式,可以为小球对象提供一些与之相关的行为,例如小球会运动,我们可以为Ball类加入一个run的方法,表示球的运动,例如小球会停止运动(在游戏结束或者开始时),我们就可以为Ball类添加一个stopRun的方法,总之,如果需要做到充血模式,可以将所有与小球相关的方法加入到Ball中。3.6业务处理类(BallService)BallService处理了这个游戏中的大部分业务功能,包括开始游戏、小球移动、道具移动、挡板移动、测试小球与挡板是否有碰撞或者挡板和其它元素有碰撞、设置挡板的长度、判断用户是否通关、初始化砖块的排列与道具、画图等功能。这些功能的实现都有对应的方法,如下:voidrun(),小球进行运动。voidsetStickPos(KeyEventke),改变挡板的坐标位置。setBallPos(),改变小球的坐标位置。booleanisHitBrick(Brickbrick),测试小球与砖块是否有碰撞,参数brikc是指砖块。isHitStick(BallComponentimage),测试某元素与挡板是否有碰撞。voidsetMagicPos(),改变道具的坐标位置。voidsetStickWidth(Magicmagic),根据道具(magic)的类型去设置改变挡板的长度。booleanisWon(),判断玩家是否已经过关。Brick[][]createBrickArr(Stringpath,intxSize,intySize),创建砖块,返回一个Brick类型的数组,参数path是指砖块的图片,xSize与ySize是数组的长度。voiddraw(Graphicsg),画图,方法中是使用Graphics对象g去画图。当游戏开始时,程序中需要不停的调用run方法,让小球进行运动,当然,小球进行运动的前提是Ball的isStarted方法返回true,即游戏已经开始,run方法的主要功能就是调小球的位置。我们需要在游戏中通过上、下、左、右的键来控制挡板的位置,因此就需要提供一个setStickPos的方法来改变挡板的位置。在本章的程序中,BallService处理所有的相关逻辑,例如判断小球在运动的过程中是否越界、游戏是否胜利等。在例中BallService处理了大部分的游戏逻辑,当然,我们也可将这些逻辑放到相关的类中(即前面提到的充血模式),例如道具的下落、挡板的移动等。3.7主界面类(BallFrame)BallFrame是创建一个JFrame主界面,设置主界面的标题、长与宽、画板等属性,并且为增加键盘事件监听器以及创立一个Timer每隔一小段时间去刷新画板,主要有初始化界面与或者画板两个方法,如下:voidinitialize()throwsIOException,此方法抛出IO异常,初始化界面。BallPanelgetBallPanel(),获取一个BallPanel类型的JPanel去充当画板,BallPanel是这个类中的一个内部类。我们使用了BallService类来处理大部分的游戏逻辑,主界面类中几乎不包括任何的逻辑处理,该类维护一个BallService的对象,得到界面中相关对象的信息后,可以调用BallService中的方法进行处理,并根据返回的信息来改变界面。例如小球的运动,我们可以调用BallService的run方法,再调用BallSerivce的draw方法将小球的图片“画”到界面中。到此,本章中所有的对象都已经创建并确定了它们的行为,在建立道具类(Magic)的时候,我们将一个道具抽象为一个Magic对象,该类可以有多个实现,在使用Magic对象的时候,我们可以利用面向对象的多态特性,使用Magic的magicDo方法来进行“道具的使用”,在这个过程中,我们并不需要去关心道具具体的实现。在创建游戏各个对象的过程中,我们将处理逻辑的方法放置到一个业务类中,从一定程度上讲,减少了代码之间的耦合,并遵循了单一职责的原则。4主界面实现在这个桌面弹球游戏中,游戏中的所有元素都是用Graphics对象画出来的,所以,我们的主界面应该是一个只设置了窗口标题还有颜色等基本属性的JFrame,在这个JFrame中,我们只需要提供一个JPanel对象即可,因为游戏的界面并没有多复杂的布局与界面交互。当我们实现游戏的一些相关逻辑的时候(球的运动、道具的下落等),我们可以调用JPanel的repaint方法将JPanel进行重绘。4.1初始化界面(initialize()方法)首先,设置JFrame窗口的标题、背景颜色与是否可以改变大小,然后获取JPanel对象,最后把JPanel画板加到JFrame中,见以下代码。代码清单:code\ball\src\org\crazyit\ball\BallFrame.javapublicvoidinitialize()throwsIOException{ //设置窗口的标题 this.setTitle("弹球"); //设置为不可改变大小 this.setResizable(false); //设置背景为黑色 this.setBackground(Color.BLACK); //获取画板 ballPanel=getBallPanel(); //把画板加到JFrame this.add(ballPanel);}看加粗的一行代码ballPanel=getBallPanel()是调用本类中的getBallPanel()方法去获取一个BallPanle对象,BallPanel是本类的一个内部类,并且继承JPanel,见以下代码。代码清单:code\ball\src\org\crazyit\ball\BallFrame.java//定义一个JPanel内部类来完成画图功能publicclassBallPanelextendsJPanel{ /** *重写voidpaint(Graphicsg)方法 * *@paramgGraphics *@returnvoid */ publicvoidpaint(Graphicsg){ //可以调用BallService的draw方法进行绘制 }}而获取这个BallPanel实现是在BallPanelgetBallPanel方法中,此类保证这个Panel是单态的,每次只有一个BallPanle对象,见以下代码。代码清单:code\ball\src\org\crazyit\ball\BallFrame.javapublicBallPanelgetBallPanel(){ if(ballPanel==null){ //新建一个画板 ballPanel=newBallPanel(); //设置画板的大小 ballPanel.setPreferredSize( newDimension(BALLPANEL_WIDTH,BALLPANEL_HEIGHT)); } returnballPanel;}在这里需要注意的是,我们需要在BallFrame中维护一个BallPanel的对象,然后通过getBallPanel的方法来获得BallPanel的实例,由于BallPanel并不需要每次去创建,所以我们可以将BallPane变成单态的。在众多的设计模式中,有一种叫做单态模式。如果遇到一些对象并不需要多次创建或者创建这些对象将会严重消耗系统资源,那么我们可以考虑将该对象写成单态的。4.2单态模式简介单态模式也可以叫单例模式,该模式保证一个类有且仅有一个实例,并为外界提供一个访问,让外界可以通过这个访问点来访问该类的唯一实例。在我们平时开发的过程中,会遇到一些不需要多次创建的对象,例如JDBC的Connection对象,那么我们就可以利用单态模式来创建这些对象。例如单态模式,系统可以不必多次创建该对象的实例,外界使用的时候可以使用同一个实例,因此在一定程序上减低了系统在创建对象时的开销。为一个类实现单态模式,需要为该类提供一个私有的构造器,再提供一个可以获取该类实现的方法(为外界提供唯一的访问点),私有构造器是为了不让外界去使用new关键字来创建该类的实现,如果外键可以使用new关键字来创建该类的实例,那么就意味着该类将不会是单态,有可能外界多次通过new关键字来创建,这就无法保证该对象的实例的唯一性。4.3运行效果编写了BallFrame的初始化代码后,我们可以运行具体查看相关的游戏效果。编写创建BallFrame的代码: BallFrameballFrame=newBallFrame(); ballFrame.pack(); ballFrame.setVisible(true); ballFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);当前程序的效果如图4.1所示。图4.1初始化游戏时的界面注:我们当前并没有对BallService中的draw方法作任何的实现,我们实现了BallService的draw方法后,就可以将BallPanel中的paint方法加入BallService.draw。4.4监听器与Timerjavax.swing.Timer可以设定每隔一个时间周期就重复执行某个task,类似于Window系统的计划任务或者Linux系统的crobtab,并用start()方法去启用Timer。在这个弹球游戏中,我们只有键盘操作,所以只监听键盘的操作,用一个KeyListener去监听键盘的动作,请看以下代码。代码清单:code\ball\src\org\crazyit\ball\BallFrame.java//定义每0.1秒执行一次监听器ActionListenertask=newActionListener(){ publicvoidactionPerformed(ActionEvente){ //开始改变位置 service.run(); //刷新画板 ballPanel.repaint(); }};//如果timer不为空,调用timer的restart方法if(timer!=null){ //重新开始timer timer.restart();}else{ //新建一个timer timer=newTimer(100,task); //开始timer timer.start();}//增加事件监听器KeyListener[]klarr=this.getKeyListeners();if(klarr.length==0){ //定义键盘监听适配器 KeyListenerkeyAdapter=newKeyAdapter(){ publicvoidkeyPressed(KeyEventke){ //改变挡板的坐标 service.setStickPos(ke); } }; this.addKeyListener(keyAdapter);}首先,建立一个ActionListener对象做为Timer的task,这个task主要是处理游戏中各个组件位置的改变以及reapint画板,这个task每100毫秒执行一次,即每隔一百毫秒小球(或者其他组件)会执行一次运动。如果此类的属性timer为空,就以ActionListern对象为参数去创建一个每100毫秒执行一次的Timer,并用调用start()方法启动Timer,如果timer不为空,直接调用restart()方法启动timer。在这里我们需要明白的是,第一次进行游戏时,timer为null,就需要进行创建,当进行第二次游戏的时候,timer非空,由于游戏停止(胜利或者失败),因此需要调用restart方法重新启动。由于我们是在BallService控制游戏的,也就意味着进行第二次游戏的时候,就需要再次调用BallFrame的initialize方法初始化游戏。接下来再增加事件监听器,先使用JFrame的keyKeyListeners()方法获取本窗口的KeyLister数组,如果这个数组的长度为空,说明本窗口并没有添加到任何KeyListener,所以就创建一个KeyAdapter(为JFrame创建一个键盘监听器)并重写KeyAdapter类的voidkeyPressed(KeyEventke)方法,这个方法用来监听键盘的按键是否有按下,如果有的话,就需要调用BallService的setStickPos方法。当我们去实现setStickPos方法的时候,就需要设置小球为运动状态,启动弹球游戏就意味着小球开始进行运动。当我们在游戏中按下左右键的时候,同时需要移动挡板,启动游戏后,我们并不需要关心小球的移动,仅仅设置小球的运动状态,换言之,setStickPos方法只是处理挡板的移动,小球的运动让BallService的run处理(run方法100毫秒执行一次)。5挡板、小球、砖块、道具在这个设计中,挡板、小球、砖块与砖块中所包含的道具都有一个共同的父类BallComponet,可以使用父类的setX与setY方法设置坐标,也可以使用getX与getY方法获取坐标,还可以使用getImage方法获取图片,并且父类根据不同的情况提供了几个不同的构造器,5.1挡板(Stick类)此类提供一个以画板的宽、高和挡板的图片路径为参数的构造器,见以下代码。代码清单:code\ball\src\org\crazyit\ball\Stick.javapublicStick(intpanelWidth,intpanelHeight,Stringpath)throwsIOException{ //调用父构造器 super(panelWidth,panelHeight,path);//设置y坐标 this.setY(panelHeight-super.getImage().getHeight(null)); //设置原本的长度 this.preWidth=super.getImage().getWidth(null);}首先调用父类的BallComponent(intx,inty,Stringpath)构造器,把此对象的x坐标设置到画板中间的位置,并且使用javax.imageio.ImageIO的read方法去读取磁盘中的图片文件。接下来把y坐标设置到画板的底部,再根据读取出来的图片的宽度去设置Stick对象的初始长度属性。在从磁盘读取图片的过程是一个IO操作,所以会抛出IOException,见以下代码。代码清单:code\ball\src\org\crazyit\ball\BallComponent.javapublicBallComponent(intpanelWidth,intpanelHeight,Stringpath)throwsIOException{ super(); //读取图片 this.image=ImageIO.read(newFile(path)); //设置x坐标 this.x=(int)((panelWidth-image.getWidth(null))/2);}由于挡板的长度可能会改变,所以Stick类有的个int类型的preWidth属性,代表挡板的长度,并定义一个finalint类型的SPEED属性,代表挡板的移动速度,每次移动,x坐标都会向左或者向右移动SPEED个坐标位置,需要为preWidth属性提供setter与getter方法。实现了挡板类后,我们可以实现BallService的draw方法,先将挡板“画”到BallPanel中,并在BallPanel中调用BallService的draw方法,以下是BallService的draw方法的部分实现。代码清单:code\ball\src\org\crazyit\ball\BallService.java //如果赢了 if(isWon()){ //绘制赢的图片 g.drawImage(won.getImage(),won.getX(),won.getY(),width, height-10,null); }elseif(ball.isStop()){ //绘制游戏结束图像 g.drawImage(gameOver.getImage(),gameOver.getX(),gameOver.getY(), width,height-10,null); }else{ //清除原来的图像 g.clearRect(0,0,width,height); //绘制挡板图像 g.drawImage(stick.getImage(),stick.getX(),stick.getY(),stick .getPreWidth(),stick.getImage().getHeight(null),null); }到此,我们可以运行程序查看创建挡板后的效果,具体的效果如图5.1所示。图5.1创建挡板5.2小球(Ball类)此类提供一个以画板的宽、高、挡板高度与小球的图片路径为参数的构造器,见以下代码。代码清单:code\ball\src\org\crazyit\ball\Ball.javapublicBall(intpanelWidth,intpanelHeight,intoffset,Stringpath) throwsIOException{ //调用父构造器 super(panelWidth,panelHeight,path); //设置y坐标 this.setY(panelHeight-super.getImage().getHeight(null)-offset);}首先调用父类的BallComponent(intx,inty,Stringpath)构造器,把此对象的x坐标设置到画板中间的位置,并且使用javax.imageio.ImageIO的read方法去读取磁盘中的图片文件。接下来把y坐标设置到板位上面的位置。在这里,小球对象有两种状态,一种是小球是否开始运动,这种状态下,如果小球没有开始运动,代表准备开始游戏,反则代表游戏已经开始,没游戏没结束之前,小球就一直运动;一个是小球是否结束运动,如果小球结束运动,代表游戏已经结束,小球不能再运动,挡板也不再受玩家的控制,反则代表正在游戏中。我们在Ball中提供一个started的属性来标识这两种状态。那么当游戏开始时,就可以直接设置Ball的started属性为true。我们把小球的速度方向分为横与竖两个方向,所以这里用int类型的speedX与speedY两个属性去代表小球的横向方向与竖向方向,并增加相应的setter与getter方法。为Ball对象添加了相关的属性后,我们可以在BallService的draw方法中,将一个小球“画”到BallPanel中。具体的效果如图5.2所示。g.drawImage(ball.getImage(),ball.getX(),ball.getY(),null);图5.2画小球图片5.3道具(Magic及其子类)Magic类是一个抽像类,它是BallComponet的子类,又是LongMagic与ShortMagic的父类,此类只有一个抽象方法magicDo,用来完成道具的功能,提供一个使用图片路径与x、y坐标为参数的构造器供其子类继承,见以下代码。代码清单:code\ball\src\org\crazyit\ball\Magic.javapublicMagic(Stringpath,intx,inty)throwsIOException{ super(path,x,y);}publicabstractvoidmagicDo(Stickstick);这个构造器只调用父类BallComponent的构造器,去设置道具的表现图片与初始坐标。加粗的一行代码是用来完成道具功能的抽像方法,这里只有定义,没有实现,让其子类去实现。Magic类有两个子类:LongMagic与ShortMagic,这两个道具的功能是使游戏中的挡板变长和变短,功能都在magicDo的实现方法中实现,首先看LongMagic类实现的magicDo方法。代码清单:code\ball\src\org\crazyit\ball\LongMagic.javapublicvoidmagicDo(Stickstick){ doubleimageWidth=stick.getImage().getWidth(null); //如果挡板没有变长过 if(stick.getPreWidth()<=imageWidth) { //将挡板的长度改为双倍 stick.setPreWidth((int)(stick.getPreWidth()*2)); }}首先获取挡板图片的长度,再拿这个长度和挡板现在的长度比较,如果挡板的长度小于或者等于图片的长度,说明挡板的长度没有增加过,所以就调用Stick的setPreWidth方法把挡板的长度设置为又倍,下面再看ShortMagic实现的magicDo方法。代码清单:code\ball\src\org\crazyit\ball\ShortMagic.javapublicvoidmagicDo(Stickstick){ doubleimageWidth=stick.getImage().getWidth(null); //如果挡板没有变短过 if(stick.getPreWidth()>=imageWidth){ //将挡板的宽度改为一半 stick.setPreWidth((int)(stick.getPreWidth()*0.5)); }}这里的流程和LongMagic中实现的方法相似,首先获取挡板图片的长度,如果现在的长度大于或者等于图片的长度,说明挡板的长度没有减少过,就调用Stick的setPreWidth方法把挡板的长度设置为一半。5.4砖块(Brick类)Brick类是BallComponet的一个子类,用一个boolean类型的属性disalbe去标志对象是否有效果,还包含一个Magic类型的属性magic,在构造器中初始化这个属性,见以下代码中。代码清单:code\ball\src\org\crazyit\ball\Brick.javapublicBrick(Stringpath,inttype,intx,inty)throwsIOException{ super(path); if(type==Brick.MAGIC_LONG_TYPE){ this.magic=newLongMagic("img/long.gif",x,y); }elseif(type==Brick.MAGIC_SHORT_TYPE){ this.magic=newShortMagic("img/short.gif",x,y); } if(this.magic!=null){ this.magic.setX(x); this.magic.setY(y); }}在这个构造器的参数中,除了读取图片文件的path参数和对象坐标的x与y参数,还有一个int类型的参数type,构造器主要是根据这个参数的值去决定此对象包含的Magic,如是type等于Brick.MAGIC_LONG_TYPE,magic就是一个LongMaigc对象,如果type等于Brick.MAGIC_SHORT_TYPE,magic就是一个ShortMagic对象,如果magic不是空值,就设置magic的x与y坐标。当然,同样需要为magic与disalbe属性增加相应的setter和getter方法。6BallService类实现BallService被定义成一个专门处理此游戏逻辑功能的类,包含处理小球的移动、处理挡板的移动、初始化砖块与道具、判断玩家的输赢,判断游戏中的图片元素是否有碰撞,把图片绘制到画板等功能。由于BallService负责处理几乎全部的游戏逻辑,那么该类中就需要维护界面所有的组件:小球对象、挡板对象、砖块的二维数组等。BallService中所有的方法都是对这些对象进行处理,修改它们的相关属性或者执行相关的行为。6.1创建与设置砖块在本游戏的设计中,为了简单起见,没有加入游戏关卡的概念,没有去设置每一关的砖块与道具等东西的分布,所以,游戏开始的时候,我们会用一个方法名为createBrickArr的方法去随机产生砖块与道具,先看以下代码。代码清单:code\ball\src\org\crazyit\ball\BallService.javapublicBrick[][]createBrickArr(Stringpath,intxSize,intySize)throwsIOException{ //创建一个Brick[][] Brick[][]bricks=newBrick[xSize][ySize]; intx=0; inty=0; intrandom=0; intimageSize=28; booleanisDisable=false; //迭代初始化数组 for(inti=0;i<xSize;i++){ for(intj=0;j<ySize;j++){ //创建一个新的砖块 random=(int)(Math.random()*3); x=i*imageSize; y=j*imageSize; //一定机率没有砖块 isDisable=Math.random()>0.8?true:false; if(isDisable){ random=0; } Brickbrick=newBrick(path,random,x,y); brick.setDisable(isDisable); //设置x坐标 brick.setX(x); //设置y坐标 brick.setY(y); bricks[i][j]=brick; } } returnbricks;}这个方法的返回类型是Brick[][],也就是说是一个Brick类型的二维数组,bricks[i][j]代表砖块在第i行第j列。有三个参数:String类型的图片文件路径path,还有代表返回数组大小的xSize与ySize,这两个参数是int类型。首先,以xSize与ySize去创建一个Brick[][]类型的变量,接下来遍历这个数组,在遍历的过程中,每次先创建一个砖块,然后随机设置砖块对象的disable属性,disable属性为true的砖块将不会被显示,创建砖块的过程中,也是随机创建砖块所包含的道具,然后再设置这个砖块的x与y坐标,最后把新创建的砖块对象赋给bricks[i][j]。遍历完这个数组后,便把bricks返回。这样就完成创建游戏中所有砖块与道具的过程。创建了砖块的二维数组后,我们就需要将这个二维数组“画”到BallPanel中,为BallService的draw加入相关的实现即可。代码清单:code\ball\src\org\crazyit\ball\BallService.java //迭代绘制砖块图像 for(inti=0;i<bricks.length;i++){ for(intj=0;j<bricks[i].length;j++){ BallComponentmagic=bricks[i][j].getMagic(); //如果这个砖块图像对像是有效的 if(!bricks[i][j].isDisable()){ //里面的数字1为砖块图像间的间隙 g.drawImage(bricks[i][j].getImage(),bricks[i][j] .getX(),bricks[i][j].getY(),bricks[i][j] .getImage().getWidth(null)-1,bricks[i][j] .getImage().getHeight(null)-1,null); }elseif(magic!=null&&magic.getY()<height){ g.drawImage(magic.getImage(),magic.getX(),magic .getY(),null); } } }同样地,使用嵌套循环将砖块的二维数组“画”到BallPanel中,在绘画该二维数组的时候,要判断砖块是否有效。需要注意的是,必须是游戏中时才进行绘画,当游戏结束(胜利、失败)或者小球停止运动的时候,我们并不需要绘画此二维数组。绘画砖块的具体效果如图6.1所示。图6.1创建砖块6.2设置挡板的位置(移动挡板)挡板的移动主要是依靠监听玩家的键盘操作,然后做出相应的反应,去改变挡板的坐标位置,所以需要以一个KeyEvent对象做为这个方法的参数,在方法内可以通过这个对象的getKeyCode()方法去获取玩家所按下的键盘按键,先看以下代码。代码清单:code\ball\src\org\crazyit\ball\BallService.javapublicvoidsetStickPos(KeyEventke){ //把弹球的运动状态设为true ball.setStarted(true); //如果是左方向键 if(ke.getKeyCode()==KeyEvent.VK_LEFT){ if(stick.getX()-stick.SPEED>0){ //x坐标向左移动 stick.setX(stick.getX()-stick.SPEED); } } //如果是右方向键 if(ke.getKeyCode()==KeyEvent.VK_RIGHT){ if(stick.getX()+stick.SPEED<width -stick.getPreWidth()){ //x坐标向右移动 stick.setX(stick.getX()+stick.SPEED); //ballFrame.getBallGame().reStart(ballFrame); } } //如果是F2键 if(ke.getKeyCode()==KeyEvent.VK_F2) { //初始化ballFrame try{ ballFrame.initialize(); }catch(IOExceptione){ System.out.println(e.getMessage()); } }}如果玩家按下的是左键,也就是ke.getKeyCode()等于KeyEvent.VK_LEFT,就先检查挡板是否已经在游戏面板的最左边,如果不是,就把挡板的位置向左移动Stick.SPEED个位置(SPEED代表挡板的移动速度),否则不做任何操作。如果玩家按下的是右键,处理方式与左键类似,只不过是方向相反。如果玩家按下的是F2键(这里定义F2键是重新开始游戏),就调用BallFrame对象的initialize方法是重新初始化界面。实现挡板的移动较为简单,只需要设置挡板对象的坐标并判断是否越界面即可。6.3小球与砖块碰撞图6.2小球与砖块碰撞在游戏中,如果运行的小球碰到砖块,就要把砖块消掉,所以我们需要判断小球与砖块是否有碰撞,假设小球圆心的坐标是(x1,y1),砖块中间的坐标是(x2,y2),砖块的一半边长是n,小球的半径是r,那么,如果(x1,y1)与(x2,y2)的距离小于n+r,砖块与小球就处于碰撞的状态,见图4.9与以下代码。代码清单:code\ball\src\org\crazyit\ball\BallService.javapublicbooleanisHitBrick(Brickbrick){ if(brick.isDisable()){ returnfalse; } //ball的圆心x坐标 doubleballX=ball.getX() +ball.getImage().getWidth(null)/2; //ball的圆心y坐标 doubleballY=ball.getY() +ball.getImage().getHeight(null)/2; //brick的中心x坐标 doublebrickX=brick.getX() +brick.getImage().getWidth(null)/2; //brick的中心y坐标 doublebrickY=brick.getY() +brick.getImage().getHeight(null)/2; //两个坐标点的距离 doubledistance=Math.sqrt( Math.pow(ballX-brickX,2) +Math.pow(ballY-brickY,2)); //如果两个图形重叠,返回true; if(distance<(ball.getImage().getWidth(null) +brick.getImage().getWidth(null))/2){ //使brick无效 brick.setDisable(true); returntrue; } returnfalse;}粗体代码部分就是以(x1,y1),(x2,y2)两个点的距离与n、r的和比较,如果这个距离小于和,就调用Brick对象的setDisable方法把Brick对象设置为无效,并返回true。我们将砖块的二维数组“画”到BallPanel中的时候(遍历二维数组),得到每一个砖块对象,都需判断该对象的disable属性,如果该属性为true,则表示这块砖块仍然处在原来的位置,如果该属性为false,则表示这块砖块已经被小球碰撞,并出“跌落”了相应的道具,在draw的时候,就需要将道具的图片画到界面中(BallPanel),小球碰撞砖块的效果如图6.3所示。图6.3小球与砖块碰撞如图6.3所示,当小球与砖块发生碰撞的时候,砖块就会变成道具,并且该道具会进行下落。道具的移动、道具与挡板的碰撞我们将在下面的章节中描述。6.4小球、道具与挡板碰撞我们需要实现BallService的isHitStick方法,该方法判断小球、道具与挡板是否发生了碰撞,只要它们发生了碰撞,该方法就需要返回true。isHitStick方法只要判断是否发生了碰撞,至于发生碰撞后所需要处理的事情,并不由该方法进行处理。在这里,由于挡板是长方形的,而且挡板的y坐标是不变的,所以可以不使用上节判断小球与砖块碰撞的方法。假设挡板的坐标是指这个长方形的左上角,用(x1,y1)表示,挡板的长度为n,那么,只要小球或者道具的x坐标处于x1与x1+n之间(也就是处于挡板的范围内),y坐标大于y1,那么就可以判断它们在碰撞,见以下代码。代码清单:code\ball\src\org\crazyit\ball\BallService.javapublicbooleanisHitStick(BallComponentbc){ //获取图片对象 ImagetempImage=bc.getImage(); //如果与挡板有碰撞 if(bc.getX()+tempImage.getWidth(null)>stick.getX() &&bc.getX()<stick.getX()+stick.getPreWidth() &&bc.getY()+tempImage.getHeight(null)>stick.getY()){ returntrue; } returnfalse;}这个方法中的参数bc代表的是小球或者道具,加粗代码部分是判断它们是否有碰撞,bc.getX()+tempImage.getWidth(null)>stick.getX()&&bc.getX()<stick.getX()+stick.getPreWidth()是确认bc的x坐标是不是处于挡板的范围内,bc.getY()+tempImage.getHeight(null)>stick.getY()是确认bc的y坐标是否大于挡板的y坐标。6.5道具的移动当小球与砖块发生碰撞后,砖块将会变成道具(如图4.10所示)。前面的章节中讲到这个游戏有两个道具,LongMagic与ShortMagic,作用分别是使挡板就长或者变短,而道具是保存在Brick对象中,所以我们需要遍历bricks数组中的所有Brick对象,如果Brick对象的状态是disable为true(也就是说砖块被小球消掉),而且这个Brick对象中有Magic对象不为null,并且Magic对象的y坐标小于小于画板的高度height(这里意思是说这个道具还在画板的范围之内),那么,便以Magic对象的速度每次增加magic.getSpeed()个y坐标值,达到道具向下移动的效果,见如下代码。代码清单:code\ball\src\org\crazyit\ball\BallService.javapublicvoidsetMagicPos(){ for(inti=0;i<bricks.length;i++){ for(intj=0;j<bricks[i].length;j++){ //获取magic Magicmagic=bricks[i][j].getMagic(); if(magic!=null){ //如果这个brick的状态是无效的 if(bricks[i][j].isDisable()&&magic.getY()<height){ //设置magic的y坐标向下增加 magic.setY(magic.getY()+magic.getSpeed()); //设置挡板的宽度 setStickWidth(magic); } } } }}以上的代码实现了setMagicPos方法,该方法每执行一次,都会改变道具的位置,因此,我们可以在BallService的run方法调用setMagicPos方法(run方法每100毫秒执行一次),如果砖块被消除的话,界面中就会出现下落的道具,具体的效果如图6.4所示。图6.4道具的下落6.6改变挡板的长度(道具的作用)在6.4中实现了判断小球与挡板、砖块是否发生碰撞的方法,因此在这里改变挡板长度,实现起来将会十分简单,只要判断道具与挡板是否有碰撞(调用isHitStick方法),如果挡板与“掉下来”的道具发生碰撞,便调用Magic对象的magicDo方法,magicDo方法会将挡板的长度,见以下代码。代码清单:code\ball\src\org\crazyit\ball\BallService.javapublicvoidsetSti

温馨提示

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

评论

0/150

提交评论