疯狂java实战演义第4章 桌面弹球.docx
- 文档编号:6619450
- 上传时间:2023-01-08
- 格式:DOCX
- 页数:30
- 大小:211.85KB
疯狂java实战演义第4章 桌面弹球.docx
《疯狂java实战演义第4章 桌面弹球.docx》由会员分享,可在线阅读,更多相关《疯狂java实战演义第4章 桌面弹球.docx(30页珍藏版)》请在冰豆网上搜索。
疯狂java实战演义第4章桌面弹球
第4章桌面弹球
4.1桌面弹球概述
桌面弹球是游戏中常见的游戏,从以前的掌上游戏机到如今的手机游戏,都是一个十分经典的游戏。
玩家控制一个可以左右移动的挡板去改变运动中小球的移动方向,目的是用小球消除游戏屏幕中的所有障碍物到达下一关,在障碍物被消除的过程中,可能会产生一些能改变挡板或者小球状态的物品,例如:
挡板变长、变短,小球威力加强等等。
本章主要介绍如何实现一个简单的弹球游戏,让读者了解“动画”的实现原理。
在本章中,将介绍与使用Java的绘图功能,使用到JPanel的paint(Graphicsg)方法去绘图,绘图主要是依靠这个方法中的Graphics类型的参数,将使用Java中的Timer去重复绘图,产生动画效果,桌面弹球游戏的效果如图4.1所示。
图4.1桌面弹球
4.1.1动画原理
简单地来说,动画是利用人的视觉暂留的生理特性,实现出来的一种假象,只要每隔一段时间(这个时间少于人的视频暂留时间)就重新绘制一幅状态改变的图片,就能造成这种“动”的假象。
我们在程序中不断的进行绘画(使用repaint方法),对程序来讲,只需要在短时间内进行多次的绘画,并且每次绘画都需要改变绘画的相关值,就可以达到“动画”的效果。
4.1.2小球反弹的方向
在本章实现的过程中,我们会设置小球于对称的方式,并出现少许偏移的方式反弹,如图4.2所示。
让小球反弹出现少许编移是为了让游戏增加点不确定性,增加游戏的趣味性。
我们需要在编写游戏前确定这些小细节,这样在开发的过程中,我们就可以按照这些小细节去逐步实现我们的程序。
图4.2小球的反弹
4.2流程描述
玩家使用左右方向键开始游戏与控制挡板,在未消除完所有的障碍物或者挡板没有档住向下移动的小球之前,会一直处于游戏状态,在这个状态中,小球会一直处于直线运动或者改变方向,当小球消除掉障碍物的时候,有机率产生一些物品,产生的物品会直线向下移动,用挡板接住物品后,物品的特殊效果会生效。
如果消除了所有的障碍物,就判断玩家为赢,如果挡板没有接住向下移动的小球,就判断玩家为输。
具体的游戏流程如图4.3所示。
图4.3游戏流程
游戏中并不涉及复杂的流程,只需要处理游戏的输赢即可,因此在实现的过程中,关键是如何确定游戏输赢的标准(挡栏没有挡住小球)。
4.3创建游戏对象
在这个游戏中,有挡板,小球,砖块(障碍物),道具等物品,这些物品都有共同的特性,有属于自己的x与y坐标属性,有图片属性,有速度属性,所以,在这时在,设计一个基类BallComponent包含这些属性与相关的方法,让其子类继承。
继承此类的子类有Stick类(用于定义挡板的行为于属性),Ball类(控制小球的移动与其它动作),Brick类(砖块类),Magic类(道具抽像类,此类中有一个用于使道具功能实现的抽象方法,供其子类实现)。
道具类的子类有LongMagic与ShortMagic,作用是使Stick的长度变长或者变短。
在平时的开发中,如果发现多个对象之间有一些共同的特性或者行为,并且觉得可以使用这些特性或者行为构成一个对象,那么可以建立一个新的对象作为这些对象的父类。
如果该父类中某些方法并不需要由父类实现,我们可以将父类做成抽象类,并将这些方法变成抽象的。
确定了我们游戏中的所涉及的对象后,我们还需要一个BallFrame类去创建一个画板,用于绘制图片,此类还完成界面的初始化,监听用户的键盘,而与游戏相关的业务逻辑(判断输赢或者球的运动),我们放到BallService类中去处理,本章类的关系如图4.4所示。
图4.4桌面弹球类图
笔者在这里提供了本章的类图,是为了让读者可以更清晰的了解本章程序的结构,但在实现开发的过程中,我们可以根据实际情况,加入或者改变各个类的关系或者程序的结构,但最终都是为降低程序的耦合、提高内聚、编写出优秀的代码。
4.3.1基类BallComponent
BallComponent,做为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的时候,我们可以将这个类变成抽象类,即使它没有任何的抽象方法,这样做的目的是,在我们的桌面弹球游戏中,该类并不是具体存在的某一个对象,而是我们将一些公用的属性或者方法存放到该类中,因此它在游戏中并不代表某个具体的对象。
将该类创建为抽象类,我们就可以提供(如果需要的话)一些抽象方法让子类去实现,并且可以在父类中调用这些抽象方法。
4.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对象,在以后的章节中,当遇到需要在界面中绘画某些图片的时候,我们都可以建立一个二维数组,将相应的对象放置到该数组中,当界面进行绘画的时候,就可以将这个二维数组“画”出来。
4.3.3道具类及其子类(Magic)
Magic类是一个道具类,在游戏中表现包含在砖块中,是BallComponet的一个抽象子类,此类提供一个Magic(Stringpath,intx,inty)构造器去调用父类的构造器,并提供一个抽象的方法magicDo(Stickstick),此抽象方法是实现道具的效果功能,用于给其子类实现,现在实现的子类的LongMagic类和ShortMagic类,两个子类的magicDo方法中分别实现使挡板变长与变短的功能。
❑abstractvoidmagicDo(Stickstick),道具的功能,给其子类实现。
在本例中,挡板是可以变长或者变短的,而使挡板变长或者变短的方式是通过道具来实现,因此可以将道具抽象成变长的道具或者变短的道具,而它们都需要做同一件是,就是改变挡板的展现形式。
为了程序的可扩展性,我们在这里将一个道具变为一个抽象类(Magic),当我们需要有其他形式的道具的时候,就可以为该类添加子类,并提供不同的实现。
当然,这里只提供一个Stick的参数可能并不够,如果以后游戏中出现另外一种道具,会改变球的速度(变快或者变慢),那么我们就需要为该抽象类提供更多的参数。
4.3.4挡板类(Stick)
同样,Stick类也是BallComponet的子类,用来代表游戏中的挡板,由于挡板只有左右移动的,所以,此类中只定义了挡板x方向的移动速度SPEED,还有定义挡板的初始长度preWidth,并提供此方法的setter与getter方法,如下:
❑voidsetPreWidth(intpreWidth),设置初始长度。
❑intgetPreWidth(),获取初始长度。
由于该类继承于BallComponet类,因此只需要提供一个构造器即可。
在本例中,挡板是可以变长或者变短的,并且在建立道具抽象类的时候,已经定义了一个magicDo的方法,该方法的参数就是一个挡板对象,所以挡板类必须包括长度的属性,这样,在实现道具类的时候,就可以通过改变挡板类的长度来实现本例中所需要实现的长短挡板功能。
在Stick类中并不需要关心挡板的图片、位置与大小,这些属性已经在BallComponet中体现。
4.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中。
4.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处理了大部分的游戏逻辑,当然,我们也可将这些逻辑放到相关的类中(即前面提到的充血模式),例如道具的下落、挡板的移动等。
4.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.4主界面实现
在这个桌面弹球游戏中,游戏中的所有元素都是用Graphics对象画出来的,所以,我们的主界面应该是一个只设置了窗口标题还有颜色等基本属性的JFrame,在这个JFrame中,我们只需要提供一个JPanel对象即可,因为游戏的界面并没有多复杂的布局与界面交互。
当我们实现游戏的一些相关逻辑的时候(球的运动、道具的下落等),我们可以调用JPanel的repaint方法将JPanel进行重绘。
4.4.1初始化界面(initialize()方法)
首先,设置JFrame窗口的标题、背景颜色与是否可以改变大小,然后获取JPanel对象,最后把JPanel画板加到JFrame中,见以下代码。
代码清单:
code\ball\src\org\crazyit\ball\BallFrame.java
publicvoidinitialize()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.java
publicBallPanelgetBallPanel(){
if(ballPanel==null){
//新建一个画板
ballPanel=newBallPanel();
//设置画板的大小
ballPanel.setPreferredSize(
newDimension(BALLPANEL_WIDTH,BALLPANEL_HEIGHT));
}
returnballPanel;
}
在这里需要注意的是,我们需要在BallFrame中维护一个BallPanel的对象,然后通过getBallPanel的方法来获得BallPanel的实例,由于BallPanel并不需要每次去创建,所以我们可以将BallPane变成单态的。
在众多的设计模式中,有一种叫做单态模式。
如果遇到一些对象并不需要多次创建或者创建这些对象将会严重消耗系统资源,那么我们可以考虑将该对象写成单态的。
4.4.2单态模式简介
单态模式也可以叫单例模式,该模式保证一个类有且仅有一个实例,并为外界提供一个访问,让外界可以通过这个访问点来访问该类的唯一实例。
在我们平时开发的过程中,会遇到一些不需要多次创建的对象,例如JDBC的Connection对象,那么我们就可以利用单态模式来创建这些对象。
例如单态模式,系统可以不必多次创建该对象的实例,外界使用的时候可以使用同一个实例,因此在一定程序上减低了系统在创建对象时的开销。
为一个类实现单态模式,需要为该类提供一个私有的构造器,再提供一个可以获取该类实现的方法(为外界提供唯一的访问点),私有构造器是为了不让外界去使用new关键字来创建该类的实现,如果外键可以使用new关键字来创建该类的实例,那么就意味着该类将不会是单态,有可能外界多次通过new关键字来创建,这就无法保证该对象的实例的唯一性。
4.4.3运行效果
编写了BallFrame的初始化代码后,我们可以运行具体查看相关的游戏效果。
编写创建BallFrame的代码:
BallFrameballFrame=newBallFrame();
ballFrame.pack();
ballFrame.setVisible(true);
ballFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
当前程序的效果如图4.5所示。
图4.5初始化游戏时的界面
注:
我们当前并没有对BallService中的draw方法作任何的实现,我们实现了BallService的draw方法后,就可以将BallPanel中的paint方法加入BallService.draw。
4.4.4监听器与Timer
javax.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非空,由于游戏停止
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 疯狂java实战演义第4章 桌面弹球 疯狂 java 实战 演义 桌面 弹球