1.1 项目简介
本次Java课程设计是做一个飞机大战的游戏,应用Swing编程,完成一个界面简洁流畅、游戏方式简单,玩起来易于上手的桌面游戏。该飞机大战项目运用的主要技术即是Swing编程中的一些窗口类库、事件监听以及贴图技术。
1.2 实训功能说明
(1)通过键盘,方向键和ASWD键可控制战机的位置,空格键和鼠标左键发射子弹。
(2)界面中敌机出现的位置,以及敌机和Boss炸弹的发射均为随机的,敌机与敌机炸弹、Boss炸弹均具有一定的速度,且随着关卡难度的增大,数量和速度均随着关卡数增加而增加。
(3)对于随机产生的敌机和敌机炸弹,若超过矩形区域,则释放该对象。
(4)添加碰撞效果,包括战机子弹打中敌机爆炸、敌机炸弹打中战机爆炸、战机与敌机相撞爆炸、战机子弹与敌机炸弹相撞爆炸、战机子弹打中Boss、战机与Boss碰撞以及战机吃到血包七种碰撞效果。且碰撞发生后子弹、炸弹、血包均消失,战机生命值减一,敌机和Boss生命值减少当前战机炮弹威力的生命值,若敌机或Boss生命值归零,则删除敌机或Boss。
(5)血包:随着关卡游戏进程的进行,会出现一定量的血包供战机补给生命值,血包会在客户区矩形框内运动,10秒后消失;若战机在10秒内吃到血包,则会增加5点生命值知道生命值上限。
(6)每关中战机有三条命,每条命10点生命值,生命使用完后,会进入GameOver界面显示得分数,并提供重新开始游戏和退出功能。
(7)游戏提供10个关卡,每个关卡需要打死相应关卡的敌机数量才能进入Boss模式,打败Boss之后将会进入下一关。10关通关后,显示通关界面,并提供重新开始游戏和退出游戏的功能选项。
(8)暂停功能:游戏进行过程中按下Z键可进入暂停模式,再按Z则返回游戏。
(9)无敌模式:游戏进行过程中按下Y键可进入无敌模式,再按Y则返回正常游戏。该模式下战机生命值不会减少,可供测试使用。
(10)魔法值:游戏进行过程中,战机魔法值会随着时间递增到上限10,魔法值供战机道具功能的使用,过一个关卡魔法值不清零。
(11)战机大招:当战机魔法值为10满状态时,按下X键消耗所有魔法值可发动大招,对屏幕中的敌机进行清屏,Boss扣50点血量。
(12)防护罩:当魔法值不为0时,按下C键可打开防护罩道具,该状态下战机处于无敌状态,不会损失生命值,但魔法值会随着防护罩开启慢慢降低。
(13)战机升级功能:战机子弹单个威力为1,在魔法值不为0时,按下V键开启升级战机模式,战机图标变为动画,子弹威力变成两倍。(若同时开启防护罩和战机升级,则魔法值递减速度翻倍)。
(1) 为游戏界面每个关卡添加了滚动背景图片和背景音乐,并在敌机发送炮弹、战机发射子弹、战机击中敌机、敌机击中战机、战机敌机相撞、敌机战机子弹相撞、战机吃到血包、战机大招、战机升级、战机防护罩、游戏结束时均添加了音效。
(2)为美化游戏界面,采用了一部分全民飞机大战图标,并添加了爆炸动画和升级战机动画特效,背景音乐采用微信飞机大战背景音乐和相关特效音效。
(3)为游戏设置了不同的关卡,每个关卡难度不同,敌机与敌机炸弹的速度随着关卡增大而加快,进入第五关以后敌机从上下方均会随机出现,且随机发射炸弹。
(4)前五关卡敌机从上方飞出,速度一定,战机每打掉一架敌机则增加一分,当战机得分超过该关卡所需分数(和关卡数相关)则可进入Boss模式,打败Boss进入下一关;进入第六关以后,敌机分别从上下两方飞出。随着关卡数增加,敌机数量增加,速度增快,敌机炮弹数量和速度也相应增加,进入Boss所需分数增加,Boss生命值和火力也随着关卡数的增加而增加,游戏难度陡然直升。
(5)游戏界面中显示当前状态下的关卡数、当前命数、当前得分、战机血条、战机魔法条、无敌模式提醒和战机道具提醒,Boss模式下还有Boss血条。
(6)增加了鼠标控制战机位置这一效果,战绩的位置随着鼠标的移动而移动,并且点击鼠标左键可使得战机发射子弹。
(7)进入游戏先进入欢迎界面,欢迎界面中显示游戏使用说明,点击鼠标左键和空格键开始游戏。游戏过程中战机命数使用完、通关均有相应界面进行提醒,用户可选择重新开始游戏或退出游戏。
2. 相关技术
2.1 Timer定时器技术
本次项目采用了Java的Timer定时器和TimerTask任务,Timer周期性地在每经过一个指定的时间间隔后就通知TimerTask一次,让TimerTask按照Timer设定的周期循环地执行任务。本程序中使用多个定时器,分别控制不同的功能,分别是屏幕刷新Timer,敌机产生Timer,魔法值变化Timer,血包生命周期Timer。
2.2 透明贴图实现技术
绘制透明位图的关键就是创建一个“掩码”位图(mask bitmap),这个“掩码”位图是一个单色位图,它是位图中图像的一个单色剪影。
整个绘制过程需要使用到ImageUtil类的createImageByMaskColorEx静态方法,把传入的原图BufferedImage中的指定颜色的背景去掉,返回去掉背景的BufferedImage对象,传送到窗口进行展示。
2.3 游戏对象列表
Java类库中提供了丰富的List接口的实现方法,本项目采用ArrayList存放游戏运行过程中的游戏对象。
(1)滚动背景模块
private static BufferedImage titleImage;// 欢迎界面图像列表
public static Scene scene; //游戏背景对象
(2)各游戏对象
public static MyPlane myplane = null;
Enemy enemy = null;
public static Boss boss = null;
Bomb bomb = null;
Ball ball = null;
Explosion explosion = null;
Blood blood = null;
(3)存储游戏对象的对象列表
public static List<Enemy> enemyList = new ArrayList<Enemy>();
public static List<MyPlane> meList = new ArrayList<MyPlane>();
public static List<Bomb> bombList = new ArrayList<Bomb>();
public static List<Ball> ballList = new ArrayList<Ball>();
public static List<Explosion> explosionList = new ArrayList<Explosion>();
public static List<Blood> bloodList = new ArrayList<Blood>();
(4)游戏运行相关参数:
int speed;// 战机的速度,方向键控制
public static int myLife;// 为战机设置生命值
public static int lifeNum;// 战机命条数
public static int myScore;// 战机的得分
public static int passScore;// 当前关卡得分数
public static int lifeCount;// 血包产生控制参数
public static boolean bloodExist;// 标记屏幕中是否存在血包
public static int magicCount;// 魔法值,控制能否发大招
public static int bossBlood;// Boss血量
public static int passNum;// 记录当前关卡
(5)游戏运行相关标志位
public static boolean isPass;// 是否通关的标志
public static boolean isPause;// 是否暂停
public static boolean isBoss;// 标记是否进入Boss
public static boolean bossLoaded;// 标记Boss出场完成
public static boolean isProtect;// 标记是否开启防护罩
public static boolean isUpdate;// 标记战机是否升级
public static boolean test;// 无敌模式参数
public static int isStop;// 标记游戏停止
public static boolean isStarted;// 标记欢迎界面是否加载完成
2.4获取矩形区域
使用 Rectangle的intersects方法来判断两个源矩形是否有重合的部分。如果有重合,返回true,没有从何返回false。
2.5 List<BufferedImage>处理爆炸效果
爆炸效果是连续的显示一系列的图片。如果把每一张图片都在要显示的时候进行加载,占用的时间是非常多的,必然后导致程序的可行性下降。List<BufferedImage>是一个“图象列表”是相同大小图象的集合,每个图象都可由其基于零的索引来参考。可以用来存放爆炸效果的一组图片,通过Timer消息连续循环绘制出List<BufferedImage>中的多张图片做成的爆炸效果。
3. 总体设计与详细设计
3.1 系统模块划分
该飞机大战游戏程序分为游戏滚动背景绘制模块、各游戏对象绘制模块、游戏对象之间的碰撞模块、爆炸效果产生模块、游戏界面输出玩家得分关卡信息模块、战机道具技能维护模块、消息处理模块、视图生命周期维护模块。
其中在游戏对象绘制模块中,战机是唯一对象,在游戏开始时产生该对象,赋予其固定的生命值,当其与敌机对象、敌机炸弹碰撞时使其生命值减一,直至生命值为零,便删除战机对象。敌机对象与敌机炸弹对象的绘制中采用定时器技术,定时产生。爆炸对象初始化为空,当游戏过程中即时发生碰撞时,在碰撞位置产生爆炸对象,添加到爆炸链表中,并根据爆炸素材图像分八帧进行输出,达到动画特效。
3.2 主要功能模块
GameObject是各个游戏对象的抽象父类,继承自Object类,其他的类:战机类、敌机类、爆炸类、子弹类、炸弹类、血包类、文字类都继承了此类,Boss类继承敌机类。
每个游戏对象类中既继承了来自父类GameObject的属性,又有自己的特有属性和方法。
GameObject类介绍:
protected Point point;//该游戏对象在窗口中的坐标位置。
public boolean draw(Graphics g, JPanel panel, boolean pause);//在窗口中绘制该游戏对象图标。
public Rectangle getRect();//获取该游戏对象图标在窗口中的矩形框,进行碰撞检测时使用。
public static boolean loadImage(BufferedImage image, String source);//加载该游戏对象对应的图像到内存,方便之后的显示调用。
继承GameObject的其他游戏对象类也都重载了相关方法,以便于各个游戏对象在程序的调用过程中调用方式的一致性。
3.2.2 项目包和类层次结构图
MyPanel:JPanel的继承类,是飞机大战窗口的总显示面板,其中包含了游戏运行过程中的大量全局参数和全局标记位。
SpaceWar:程序的入口,窗口对象启动的位置,并在此对相关事件进行了监听。
Ball:敌机炮弹类,继承GameObject,有加载图片,获取矩形框,获取对象位置,绘制回想等游戏对象通用操作方法。
Blood:血包类,继承GameObject,有加载图片,获取矩形框,获取对象位置,绘制回想等游戏对象通用操作方法。
Bomb:战机炮弹类,继承GameObject,有加载图片,获取矩形框,获取对象位置,绘制回想等游戏对象通用操作方法。
Boss:Boss类,继承GameObject,有加载图片,获取矩形框,获取对象位置,绘制回想等游戏对象通用操作方法。
Enemy:敌机类,继承GameObject,有加载图片,获取矩形框,获取对象位置,绘制回想等游戏对象通用操作方法。
Explosion:爆炸效果类,继承GameObject,有加载图片,获取矩形框,获取对象位置,绘制图片等游戏对象通用操作方法。
GameObject:游戏对象基类,有加载图片,获取矩形框,获取对象位置,绘制图片的游戏对象统一的方法,统一游戏对象的操作方式。
MyPlane:战机类,继承GameObject,有加载图片,获取矩形框,获取对象位置,绘制图片等游戏对象通用操作方法。
Scene:场景类,实现了背景滚动,并根据关卡显示不同的背景图片。
EnemyTask:实现了TimerTask接口,随着Timer计时器的调用而随时间产生敌机对象和敌机炮弹对象,实现自动产生敌机的效果。
MagicTask:实现了TimerTask接口,随着Timer计时器的调用而随时间改变魔法值。
RefreshTask:实现了TimerTask接口,随着Timer计时器的调用而随时间刷新窗口界面。
AudioUtil:音频操作工具类,提供播放背景音乐和操作音效。
ImageUtil:图片加工工具类,实现了透明贴图方法和背景图片拼接。
(1)飞机大战游戏执行流程图
(2)定时器产生敌机和炸弹流程图
(3)血包执行流程图
4. 编码实现
4.1 滚动背景
在滚动背景的初始化方法和释放方法添加背景音乐播放和释放
//场景类
public class Scene {
private int beginY;// 背景的Y坐标
private List<BufferedImage> images;
public Scene() {
this.images = new ArrayList<BufferedImage>();
}
// 初始化场景
public boolean initScene() {
// 加载开始图片
BufferedImage buffer;
try {
buffer = ImageUtil.copyImage(ImageIO.read(new File(
"images/start.bmp")));
this.images.add(buffer);
// 如果加载失败, 返回false
for (int i = 1; i <= 6; i++) {
buffer = ImageUtil.copyImage(ImageIO.read(new File(
"images/background" + i + ".bmp")));
this.images.add(buffer);
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
// 背景起始坐标为0
this.beginY = 0;
// 播放背景音乐
AudioUtil.playBackground();
return true;
}
// 绘制场景
public void stickScene(Graphics graphics, int index, ImageObserver observer) {
if (index == -1)
index = 0;
else
index = index % 6 + 1;
BufferedImage image = images.get(index);
// 窗口滑在图片中间
if (beginY >= 0
&& beginY + SpaceWar.WINDOWS_HEIGHT <= image.getHeight()) {
BufferedImage buffer = image.getSubimage(0, beginY,
image.getWidth(), SpaceWar.WINDOWS_HEIGHT);
graphics.drawImage(buffer, 0, 0, SpaceWar.WINDOWS_WIDTH,
SpaceWar.WINDOWS_HEIGHT, observer);
} else if (beginY < 0) {
// 超出图片上界
BufferedImage imageUp = image.getSubimage(0, image.getHeight()
+ beginY, image.getWidth(), -beginY);
graphics.drawImage(imageUp, 0, 0, SpaceWar.WINDOWS_WIDTH, -beginY,
observer);
graphics.drawImage(image, 0, -beginY, SpaceWar.WINDOWS_WIDTH,
SpaceWar.WINDOWS_HEIGHT, observer);
if (-beginY > SpaceWar.WINDOWS_HEIGHT) {
beginY = image.getHeight() + beginY;
}
}
}
// 移动背景
public void moveBg() {
// 移动背景
beginY -= 1;
}
// 释放内存资源
public void releaseScene() {
for (int i = 0; i < 7; i++)
if (images.get(i) != null)
images.get(i).flush();
// 关闭背景音乐
AudioUtil.stopBackground();
}
public int getBeginY() {
return beginY;
}
public void setBeginY(int beginY) {
this.beginY = beginY;
}
}
//在MyPanel中刷新滚动
// 滚动背景
scene.stickScene(g, -1, this);
scene.moveBg();
4.2 显示战机
if (myplane != null) {
myplane.draw(g, this, isPause, isProtect);
}
4.3 随机产生敌机和敌机炮弹、Boss炮弹
//随机添加敌机,敌机随机发射炸弹,此时敌机速度与数量和关卡有关
// 根据关卡数产生敌机
if (MyPanel.passNum <= 5) {
// 前五关只有一个方向的敌机
Enemy enemy = new Enemy(Enemy.ENEMY_SPEED, 1);// 设置敌机的方向,从上方飞出
enemyList.add(enemy);// 随机产生敌机
if (new Random().nextInt(2) == 0) {// 控制敌机炮弹发出频率
Ball ball = new Ball(
enemy.getPoint().x + Enemy.ENEMY_WIDTH / 2,
enemy.getPoint().y + Enemy.ENEMY_HEIGHT,
enemy.getDirection());
ball.setBallSpeed(enemy.getSpeed()+2);
MyPanel.ballList.add(ball);
// 音效
AudioUtil.play(AudioUtil.AUDIO_BALL);
}
} else if (MyPanel.passNum > 5) {// 第五关之后,两个方向的敌机
Enemy enemy1 = new Enemy(Enemy.ENEMY_SPEED, 1);// 设置敌机的方向,从上方飞出
enemy1.setSpeed(Enemy.ENEMY_SPEED
+ (new Random().nextInt(2) + MyPanel.passNum - 1));
enemyList.add(enemy1);
Enemy enemy2 = new Enemy(Enemy.ENEMY_SPEED, -1);// 设置敌机的方向,从下方飞出
enemy2.setSpeed(Enemy.ENEMY_SPEED
+ (new Random().nextInt(2) + MyPanel.passNum - 1));
enemyList.add(enemy2);
int rand = new Random().nextInt(3);
if (rand == 0) {// 控制敌机炮弹发出频率
Ball ball = new Ball(enemy1.getPoint().x + Enemy.ENEMY_WIDTH
/ 2, enemy1.getPoint().y + Enemy.ENEMY_HEIGHT,
enemy1.getDirection());
ball.setBallSpeed(enemy1.getSpeed()+2);
MyPanel.ballList.add(ball);
// 音效
AudioUtil.play(AudioUtil.AUDIO_BALL);
}
if (rand == 1) {// 控制敌机炮弹发出频率
Ball ball = new Ball(enemy2.getPoint().x + Enemy.ENEMY_WIDTH
/ 2, enemy2.getPoint().y, enemy2.getDirection());
ball.setBallSpeed(enemy2.getSpeed()+2);
MyPanel.ballList.add(ball);
// 音效
AudioUtil.play(AudioUtil.AUDIO_BALL);
}
}
if (MyPanel.isBoss) {
// Boss发射子弹
// 敌机炸弹产生定时器触发
// 设置定时器产生敌机炸弹
Ball ball1 = new Ball(MyPanel.boss.getPoint().x + Boss.BOSS_WIDTH
/ 2, MyPanel.boss.getPoint().y + Boss.BOSS_HEIGHT, 1);
ball1.setBallSpeed(Ball.BALL_SPEED + (MyPanel.passNum - 1) * 2);
MyPanel.ballList.add(ball1);
Ball ball2 = new Ball(MyPanel.boss.getPoint().x + 5,
MyPanel.boss.getPoint().y + Boss.BOSS_HEIGHT, 1);
ball2.setBallSpeed(Ball.BALL_SPEED + (MyPanel.passNum - 1) * 2);
MyPanel.ballList.add(ball2);
Ball ball3 = new Ball(MyPanel.boss.getPoint().x + Boss.BOSS_WIDTH
- 5, MyPanel.boss.getPoint().y + Boss.BOSS_HEIGHT, 1);
ball3.setBallSpeed(Ball.BALL_SPEED + (MyPanel.passNum - 1) * 2);
MyPanel.ballList.add(ball3);
Ball ball4 = new Ball(MyPanel.boss.getPoint().x + Boss.BOSS_WIDTH
/ 2 + 85, MyPanel.boss.getPoint().y + Boss.BOSS_HEIGHT, 1);
ball4.setBallSpeed(Ball.BALL_SPEED + (MyPanel.passNum - 1) * 2);
MyPanel.ballList.add(ball4);
Ball ball5 = new Ball(MyPanel.boss.getPoint().x + Boss.BOSS_WIDTH
/ 2 - 85, MyPanel.boss.getPoint().y + Boss.BOSS_HEIGHT, 1);
ball5.setBallSpeed(Ball.BALL_SPEED + (MyPanel.passNum - 1) * 2);
MyPanel.ballList.add(ball5);
// 音效
AudioUtil.play(AudioUtil.AUDIO_BALL);
}
4.4 显示战机发射子弹
for (int i = 0; i < bombList.size(); i++) {
bomb = bombList.get(i);
if (bomb == null)
continue;
bomb.setCurrentIndex(i);
bomb.isUpdate = isUpdate;
if (!bomb.draw(g, this, isPause))
i--;
}
4.5 碰撞检测,以战机子弹集中敌机为例
if (MyPanel.myplane != null && !MyPanel.isPause) {
// 子弹打中敌机
boolean flag = false;
for (int i = 0; i < MyPanel.bombList.size(); i++) {
Bomb bomb = MyPanel.bombList.get(i);
if (bomb == null)
continue;
Rectangle bombRectangle = bomb.getRect();
for (int j = 0; j < MyPanel.enemyList.size(); j++) {
Enemy enemy = MyPanel.enemyList.get(j);
if (enemy == null)
continue;
Rectangle enemyRectangle = enemy.getRect();
if (enemyRectangle.intersects(bombRectangle)) {
Explosion explosion = new Explosion(
(bomb.getPoint().x + Bomb.BOMB_WIDTH / 2 - Explosion.EXPLOSION_WIDTH / 2),
(bomb.getPoint().y + Bomb.BOMB_HEIGHT / 2 - Explosion.EXPLOSION_WIDTH / 2));
MyPanel.explosionList.add(explosion);
// 音效
AudioUtil.play(AudioUtil.AUDIO_EXPLOSION);
// 爆炸后删除子弹
MyPanel.bombList.remove(i);
i--;
// 敌机生命值减少
enemy.life -= MyPanel.isUpdate ? 2 : 1;
if (enemy.life <= 0) {
// 增加得分
MyPanel.passScore++;
// 删除敌机
MyPanel.enemyList.remove(j);
j--;
}
// 炮弹已删除,直接跳出本循环
flag = true;
break;
}
}
if (flag)
continue;
if (MyPanel.isBoss && bomb != null) {
// 获得战机子弹的矩形区域
Rectangle bombRect = bomb.getRect();
// 获得Boss的矩形区域
Rectangle bossRect = MyPanel.boss.getRect();
// 判断两个矩形区域是否有交接
if (bombRect.intersects(bossRect)) {
// 将爆炸对象添加到爆炸链表中
Explosion explosion = new Explosion(
(bomb.getPoint().x + Bomb.BOMB_WIDTH / 2 - Explosion.EXPLOSION_WIDTH / 2),
(bomb.getPoint().y + Bomb.BOMB_HEIGHT / 2 - Explosion.EXPLOSION_WIDTH / 2));
MyPanel.explosionList.add(explosion);
// 音效
AudioUtil.play(AudioUtil.AUDIO_EXPLOSION);
// 爆炸后删除子弹
MyPanel.bombList.remove(i);
i--;
bomb = null;
// 是Boss,不删除敌机,只扣血
MyPanel.bossBlood -= MyPanel.isUpdate ? 2 : 1;
if (MyPanel.bossBlood <= 0) {
Explosion explosion1 = new Explosion(
MyPanel.boss.getPoint().x,
MyPanel.boss.getPoint().y);
MyPanel.explosionList.add(explosion1);
Explosion explosion2 = new Explosion(
(MyPanel.boss.getPoint().x + Boss.BOSS_WIDTH),
(MyPanel.boss.getPoint().y + Boss.BOSS_HEIGHT));
MyPanel.explosionList.add(explosion2);
Explosion explosion3 = new Explosion(
(MyPanel.boss.getPoint().x + Boss.BOSS_WIDTH),
(MyPanel.boss.getPoint().y));
MyPanel.explosionList.add(explosion3);
Explosion explosion4 = new Explosion(
(MyPanel.boss.getPoint().x),
(MyPanel.boss.getPoint().y + Boss.BOSS_HEIGHT));
MyPanel.explosionList.add(explosion4);
Explosion explosion5 = new Explosion(
(MyPanel.boss.getPoint().x
+ Boss.BOSS_WIDTH / 2 - Explosion.EXPLOSION_WIDTH / 2),
(MyPanel.boss.getPoint().y
+ Boss.BOSS_HEIGHT / 2 - Explosion.EXPLOSION_WIDTH / 2));
explosion5.setBossDie(true);// 标记最后一个炸弹,炸完之后跳入下一关
MyPanel.explosionList.add(explosion5);
MyPanel.boss = null;
// 过关的标志变量
// isPause = TRUE;
// CMyPlane* temp = myplane;
// myplane = new CMyPlane(FALSE);
MyPanel.myplane = null;
MyPanel.isPass = true;
MyPanel.isBoss = false;
}
}
}
}
}
5.程序截图
6.完整代码
q:969060742