桌面弹球游戏制作总结
1 创建游戏对象
本人在做这个游戏时,没有完全按照书上的做法来做,相反的是我根据以前做过的坦克大战游戏思路对类的具体实现及其设计做了很大的改动并纠正书上明显的错误之处,有幸的是游戏可以运行,不免小小激动了一下,但当挡板吃掉道具时还有一部分小问题,就是当吃了道具以后变长,当在吃另一个变长的道具是就没有反应,问题是由于在处理道具的时候增加了一条if判断语句,但是本人改了半天仍然没有的到完好的实现,所以就到此作罢。当然,除了书上所说
用时间控制器来控制画板的重画之外,在这里我个人又用了多线程的方法,让线程睡眠100ms绘画一次,同样实现了游戏的功能
在这个游戏中,有挡板(Stick),小球(Ball),砖块(Brick)(障碍物),道具(Magic)等物品,把这些物品都抽象成具体的类,类名见括号里的红体字,这些物品都有自己的属性,但是它们也都有公共属性:有属于自己的位置(横坐标x和纵坐标y),有图片image属性,还有运动速度speed属性。所以可以设计一个基类BallComponent包含些共有的属性和相关的方法,让其子类继承。其中道具类Magic的作用就是让挡板变长或者变短。它是一个抽象类,该类中有一个用于道具功能实现的抽象方法,供其子类LongMagic(让道具变长的道具类)和ShortMagic(让道具变短的道具类)实现
。同时游戏中还有一个画板(BallFrame),用于绘制图片,此类还负责完成界面的初始化,监听用户的键盘,而游戏的相关的业务逻辑,比如判断输赢,或者弹球的运动,挡板的运动等,都放在BallService中去处理。最后提供一个单独的类(Main),里面只有main方法,这是个人的习惯问题,当然也可以把它放在BallFrame中去
注意,在平时的开发中,如果发现多个对象之间一些共有的特性或者行为,并且觉得可以使用这些特性或者行为构造一个对象,那么可以考虑建立一个新的对象作为这些对象的父类
2 编辑各个类
BallComponent(父类)
前面说了,这个类是Stick, Ball, Brick 和Magic的父类,拥有自己的位置坐标x,y,还有图片image,速度speed属性,所以该类的代码到目前为止暂且如下(后面还有具体的添加代码的操作)
public class BallComponent {
//imagePath为图片文件路径
public BallComponent(String imagePath,int x,int y) throws IOException{
// 读取图片
this.image = ImageIO.read(new File(imagePath));
// 设置x y 坐标
this.x = x;
this.y = y;
}
//得到图片
public Image getImage() {
return image;
}
// 得到横坐标
public int getX() {
return x;
}
// 设置横坐标
public void setX(int x) {
this.x = x;
}
// 得到纵坐标
public int getY() {
return y;
}
// 设置纵坐标
public void setY(int y) {
this.y = y;
}
// 获得运动的速度
public int getSpeed() {
return speed;
}
private Image image = null;
private int speed = 5;
private int x = -1;
private int y = -1;
}
弹球类(Ball)
Ball是BallComponent的子类,由于小球在运动的时候除了横竖方向上的运动,还有各个角度的斜方向,所以在此把小球的速度分解成横向速度SpeedX和竖向速度SpeedY。也就是说Ball类提供了SpeedX和SpeedY属性,此外游戏开始时,小球处于静止状态(也可以说是死亡状态),所以用一个布尔变量started来表示小球是否开始运动,初始值为false(当然在做游戏时,我沿用的坦克大战里面的思路定义的是isLife,来判断是否活着)。游戏结束后,小球也是处于静止状态,但是不能移动,同样再定义一个布尔变量stop属性来标识小球能否在移动。当然仍然为这些属性定义了相应的getter,setter方法
同时为该类提供一个构造器,所以其代码如下:
public class BallComponent {
// public BallComponent(int panelWidth,int panelHeight,String imagePath) throws IOException {
// this.image = ImageIO.read(new File(imagePath));//读取图片
// //设置x坐标,在这里是把图片设置到画板中间的位置
// this.x =(int)( panelWidth - image.getWidth(null))/2;
// }
// //用图片来构造一个BallComponent
// public BallComponent(String imagePath) throws IOException{
读取图片
// image = ImageIO.read(new File(imagePath));
// }
public BallComponent(String imagePath,int x,int y) throws IOException{
// 读取图片
this.image = ImageIO.read(new File(imagePath));
// 设置x y 坐标
this.x = x;
this.y = y;
}
//得到图片
public Image getImage() {
return image;
}
// 得到横坐标
public int getX() {
return x;
}
// 设置横坐标
public void setX(int x) {
this.x = x;
}
// 得到纵坐标
public int getY() {
return y;
}
// 设置纵坐标
public void setY(int y) {
this.y = y;
}
// 获得运动的速度
public int getSpeed() {
return speed;
}
/**
* 获得包围图片的矩形
* @return Rectangle 矩形
*/
public Rectangle getRect() {
return new Rectangle(this.getX(),this.getY(),this.getImage().getWidth(null),this.getImage().getHeight(null));
}
private Image image = null;
private int speed = 5;
private int x = -1;
private int y = -1;
}
道具类Magic
道具类Magic是一个抽象类,它是BallComponent的子类,又是LongMagic和ShortMagic的父类,此类之提供一个方法magicDo.道具是来出来挡板Stick的,所以magicDo方法里传一个Stick 类型的参数用来完成道具的功能,使得挡板在吃了道具之后变长或者变短,变长变短的处理当然首先需要获得挡板的原有宽度preWidth,在进行处理,这也是在Stick里提供getPreWidth的原因之一。在此游戏中,道具图片随机地隐藏在砖块中,当弹球和砖块撞击时砖块消失,如果该砖块有道具,那么就在画板中画出来并且随即向下运动。
这个类提供了一个使用图片路径和x y坐标的参数的构造器使其子类继承,代码如下
public abstract class Magic extends BallComponent{
/**
* 提供子类调用的构造器
* @param imagePath图片路径
* @param x 横坐标
* @param y纵坐标
* @throws IOException
*/
public Magic(String imagePath, int x,int y) throws IOException{
super(imagePath,x,y);
}
/**
* 道具的功能
* @param stick
* Stick
* @return void
*/
public abstract void magicDo(Stick stick);
}
LongMagic 代码
主要是使得挡板的宽度变成原来的二倍
public class LongMagic extends Magic{
/**
* 让挡板变长的道具
* @param imagePath图片路径
* @param x 横坐标
* @param y 纵坐标
* @throws IOException
*/
public LongMagic(String imagePath, int x, int y) throws IOException{
super(imagePath, x,y);
}
@Override
public void magicDo(Stick stick) {
double sticklength = stick.getImage().getWidth(null);
//如果挡板没变长过
if(stick.getPreWidth()
=stickImageLength)stick.setPreWidth((int)(stick.getPreWidth()*0.5));
}
}
砖块类 Brick
Brick类是BallComponent的一个子类,在游戏中当小球和挡板碰撞时砖块就被消灭不在画板上显示,变成无效的,所以在本类中用一个布尔变量disable属性来标志对象是否有效的效果,其中还包含一个Magic类型的属性magic,
其代码如下
public class Brick extends BallComponent{
public Brick(String imagePath,int magicType, int x, int y) throws IOException{
super(imagePath,x,y);
if(magicType==MAGIC_LONG_TYPE){
//长道具
magic = new LongMagic("img/long.gif",x,y);
}else if(magicType == MAGIC_SHORT_TYPE){
//短道具
magic = new ShortMagic("img/short.gif",x,y);
}
}
/**
* 判断砖块是否有生命
* @return booelean 是否有生命
*/
public boolean isDisable() {
return disable;
}
/**
* 设置砖块的生命状态
* @param isLive
*/
public void setDisable(boolean disable) {
this.disable = disable;
}
/**
* 获得道具
* @return Magic
*/
public Magic getMagic() {
return magic;
}
/**
* 设置砖块要用到的道具
* @param magic
*/
public void setMagic(Magic magic) {
this.magic = magic;
}
private boolean disable = false;
private static final int MAGIC_LONG_TYPE = 1;
private static final int MAGIC_SHORT_TYPE = 2;
private Magic magic = null;
}
挡板类Stick
此类提供了一个以画板的宽,高,和挡板的图片路径为参数的构造器,首先调用父类的构造器,初始化位置并用javax.ImageIO.的read方法读取磁盘中的图片,然后把y坐标设置到画板的底部和中部。
由于挡板在游戏中吃了道具(Magic)会改变,所以该类有个int类型的preWidth属性,代表挡板的长度,并提供了gettet和setter方法,并定义一个final int 类型的SPEED的属性,代表挡板移动的速度
public class Stick extends BallComponent {
public Stick(int panelWidth,int panelHeight,String imagePath) throws IOException{
super(imagePath,panelWidth,panelHeight);
//把挡板设置到画板中央
this.setX((int)( panelWidth - this.getImage().getWidth(null))/2);
//把挡板设置到画板底部
this.setY(panelHeight - super.getImage().getHeight(null));//设置y坐标
// 获得挡板原本的宽度,也就是挡板图片的宽度,把挡板的宽度设置为第一次调用图片的宽度
this.preWidth = super.getImage().getWidth(null);
}
// 得到挡板的宽度
public int getPreWidth() {
return preWidth;
}
//设置挡板的宽度
public void setPreWidth(int preWidth) {
this.preWidth = preWidth;
}
public static final int SPEED = 20;//设置挡板的运动速度
private int preWidth = 0;//挡板的宽度
}
到此位置,游戏需要用到的角色(对象类)都已经全部设计完成,现在该是处理游戏功能(逻辑业务)的时候了,包括游戏的开始,游戏的结束,处理小球的运动,挡板的移动,初始化砖块和道具,判断游戏中的图片元素是否有碰撞以及把图片绘制到画板等功能,在这里单独设计一个类BallService来完成这个工作。
由于该类需要维护界面中所有的组件:小球对象,挡板对像。砖块的二维数组等下面重点将将这个工作。
根据面向对象的思维方式,既然是处理小球和挡板还有砖块的功能,该类中显然有Ball Stick 和Brick属性。这些属性在该类的构造器里进行初始化
代码如下
// 提供一个挡板
private Stick stick = null;
// 提供一个弹球
private Ball ball = null;
// 定义一个砖块数组
private Brick[][] brick = null;
private int panelWidth ;//游戏界面的宽度
private int panelHeight;//游戏界面的高度
注意:确定一个砖块对象以后,在界面中由于要画出许多的砖块,在这里提供一个Brick的二维数组,来表示界面中所有的砖块,二维数组的所有元素都是每个Birck对象,在画障碍物砖块的时候就可以遍历这个二维数组来进行绘制。
游戏中主要实现的是是否发生撞击,包括弹球和砖块的撞击,弹球和挡板的撞击,挡板和道具的撞击,为了判断是否发生撞击。在这里分别提供isHitBrick,isHitStick,和stickEatMagic三个布尔类型的方法,仔细分析一下,“弹球”撞击“砖块”,当然该方法得提供一个砖块的对象作为参数,不然撞谁去呢,所以同理isHitStick也要提供一个挡板对象作为参数,stiickEatMagic要提供一个Magic作为参数;
到这里先停一下。按照以前我做的坦克大战的游戏的思路,发生撞击时是两个图片所包围的矩形进行碰撞,所以仿照坦克大战的游戏的方法,需要”获得”所有这些对象的图片所包围的矩形Rectangle, 就需要为Ball Stick 和Brick Magic 提供一Rectangle类型的getRect()方法,很明显,因为都得发生碰撞,所以可以看出每个类里都需要这个方法,因此可以把这个方法放在这些类的父类BallComponent里面去,所以在这里在BallComponent类里添加了下面代码
/**
* 获得包围图片的矩形
* @return Rectangle 矩形
*/
public Rectangle getRect() {
return new Rectangle(this.getX(),this.getY(),this.getImage().getWidth(null),this.getImage().getHeight(null));
}
当然需要注意的是,如果球和砖块撞击,那么砖块就变得无效了,所以在处理砖块和弹球转机的同时需要判断和设置一下砖块的disable属性。因此判断撞击的方法可以写成如下所示
/**
* 判断是否和砖块相撞,既然是和砖块相撞,必须提供砖块,所以参数里面传了一个Brick
* @param brick 判断要撞的砖块,这里判断是否相撞,我采取了坦克大战里面的方法
* @return 如果撞击了,返回true 否则返回false
*/
public boolean isHitBrick(Brick brick){
// 如果砖块无效,由前面知道界面中就不会画出砖块来了,所以肯定撞不住,所以返回false
if(brick.isDisable()){
return false;
}
// 判断小球是否开始运动
if(ball.isStarted()&&ball.getRect().intersects(brick.getRect())){
// 设置砖块的生命状态为false,也就是砖块死了
brick.setDisable(true);//如果撞击了就设置砖块为无效状态
return true;
}
return false;
}
/**
* 判断是否和格挡板相撞,同理得参数提供一个Stick
* @param stick Stick挡板
* @return 如果撞击了返回true否则返回false
*/
public boolean isHitStick(Stick stick){
if(ball.isStarted()&&ball.getRect().intersects(stick.getRect())){
return true;
}
return false;
}
/**
* 判断挡板是否吃了道具,同理需提供一个道具Magic
* @param magic 道具
* @return 如果吃了返回true 否则返回false
*/
public boolean stickEatMagic(Magic magic){
if(stick.getRect().intersects(magic.getRect())){
return true;
}
return false;
}
注:查一下api intersects(Retangle r)方法就是判断当前的矩形和另一个矩形是否相交,如果相交说明发生了撞击
下面来设置挡板的左右移动的位置setStickPos。这个位置的处理是用键盘上的左右键来处理的。在本类中提供了一个KeyEvent ke作为此方法的参数用来监听键盘的动作。当然当挡板运动的时候还得把小球的运动状态设置为true
Ball.setStarted(true);
同时在这里如果按下F2时需要重新开始,重新开始的处理很简单,只要重新绘制一下画板Frame即可,所以我们要为该类在提供一个BallFrame,并且在该类的构造器里初始化,然后就可以调用该类的init()方法得到开始时的画面
所以此时完成的该类构造器的操作
public BallService(int panelWidth, int panelHeight, BallFrame frame) throws IOException {
super();
this.panelWidth = panelWidth;
this.panelHeight = panelHeight;
this.frame = frame;
// 初始化挡板
stick = new Stick(panelWidth,panelHeight,"img/stick.jpg");
// 初始化弹球
ball = new Ball(panelWidth,panelHeight,stick.getImage().getHeight(null),"img/ball.gif");
// 初始化砖块数组
brick = this.creatBricks("img/brick.gif", 11, 6);
// 游戏结束图片
gameOver = new BallComponent("img/over.gif",0,0);
// 赢图片
won = new BallComponent("img/win.gif",0,0);
}
/**
* 控制挡板的移动方向
* @param ke KeyEvent
* @return void
* @throws IOException
*/
public void setStickPos(KeyEvent ke) throws IOException{
// 把弹球的运动状态设为true
ball.setStarted(true);
// 如果是按的左键,
if(ke.getKeyCode() == KeyEvent.VK_LEFT){
// 判断是否到达游戏界面的最左边,当二者之差大于零时说明还没有到达最左边,如果按下left建,仍然向左运动
if(stick.getX() - Stick.SPEED >=0)
// 挡板向左运动
stick.setX(stick.getX()-Stick.SPEED);
// 如果按的是右键
}
if(ke.getKeyCode()==KeyEvent.VK_RIGHT){
// 判断挡板是否到达游戏界面的最右边,同理如果if语句里面成立,说明还没有到达最右边,可以继续向右运动
if(stick.getX()+ Stick.SPEED
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?