java飞机大战

news2024/12/25 23:45:39

一、 概述

1.1 项目简介

本次Java课程设计是做一个飞机大战的游戏,应用Swing编程,完成一个界面简洁流畅、游戏方式简单,玩起来易于上手的桌面游戏。该飞机大战项目运用的主要技术即是Swing编程中的一些窗口类库、事件监听以及贴图技术。

1.2 实训功能说明

1.2.1 基本功能
(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.2 附加功能
(1) 为游戏界面每个关卡添加了滚动背景图片和背景音乐,并在敌机发送炮弹、战机发射子弹、战机击中敌机、敌机击中战机、战机敌机相撞、敌机战机子弹相撞、战机吃到血包、战机大招、战机升级、战机防护罩、游戏结束时均添加了音效。
(2)为美化游戏界面,采用了一部分全民飞机大战图标,并添加了爆炸动画和升级战机动画特效,背景音乐采用微信飞机大战背景音乐和相关特效音效。
(3)为游戏设置了不同的关卡,每个关卡难度不同,敌机与敌机炸弹的速度随着关卡增大而加快,进入第五关以后敌机从上下方均会随机出现,且随机发射炸弹。
(4)前五关卡敌机从上方飞出,速度一定,战机每打掉一架敌机则增加一分,当战机得分超过该关卡所需分数(和关卡数相关)则可进入Boss模式,打败Boss进入下一关;进入第六关以后,敌机分别从上下两方飞出。随着关卡数增加,敌机数量增加,速度增快,敌机炮弹数量和速度也相应增加,进入Boss所需分数增加,Boss生命值和火力也随着关卡数的增加而增加,游戏难度陡然直升。
(5)游戏界面中显示当前状态下的关卡数、当前命数、当前得分、战机血条、战机魔法条、无敌模式提醒和战机道具提醒,Boss模式下还有Boss血条。
(6)增加了鼠标控制战机位置这一效果,战绩的位置随着鼠标的移动而移动,并且点击鼠标左键可使得战机发射子弹。
(7)进入游戏先进入欢迎界面,欢迎界面中显示游戏使用说明,点击鼠标左键和空格键开始游戏。游戏过程中战机命数使用完、通关均有相应界面进行提醒,用户可选择重新开始游戏或退出游戏。

二、 相关技术

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 enemyList = new ArrayList();
public static List meList = new ArrayList();
public static List bombList = new ArrayList();
public static List ballList = new ArrayList();
public static List explosionList = new ArrayList();
public static List bloodList = new ArrayList();
(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处理爆炸效果

爆炸效果是连续的显示一系列的图片。如果把每一张图片都在要显示的时候进行加载,占用的时间是非常多的,必然后导致程序的可行性下降。List是一个“图象列表”是相同大小图象的集合,每个图象都可由其基于零的索引来参考。可以用来存放爆炸效果的一组图片,通过Timer消息连续循环绘制出List中的多张图片做成的爆炸效果。

三、 总体设计与详细设计

3.1 系统模块划分

该飞机大战游戏程序分为游戏滚动背景绘制模块、各游戏对象绘制模块、游戏对象之间的碰撞模块、爆炸效果产生模块、游戏界面输出玩家得分关卡信息模块、战机道具技能维护模块、消息处理模块、视图生命周期维护模块。
其中在游戏对象绘制模块中,战机是唯一对象,在游戏开始时产生该对象,赋予其固定的生命值,当其与敌机对象、敌机炸弹碰撞时使其生命值减一,直至生命值为零,便删除战机对象。敌机对象与敌机炸弹对象的绘制中采用定时器技术,定时产生。爆炸对象初始化为空,当游戏过程中即时发生碰撞时,在碰撞位置产生爆炸对象,添加到爆炸链表中,并根据爆炸素材图像分八帧进行输出,达到动画特效。

3.2 主要功能模块

3.2.1 系统对象类图

在这里插入图片描述
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:图片加工工具类,实现了透明贴图方法和背景图片拼接。

3.2.3 系统主程序活动图

在这里插入图片描述

3.2.4 系统部分流程图

(1)飞机大战游戏执行流程图

在这里插入图片描述

(2)定时器产生敌机和炸弹流程图

在这里插入图片描述

(3)血包执行流程图
在这里插入图片描述

四、 编码实现

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;
						}
					}
				}
			}
		}

4.6 显示爆炸效果

for (int i = 0; i < explosionList.size(); i++) {
			explosion = explosionList.get(i);
			if (explosion == null)
				continue;
			boolean b = explosion.draw(g, this, isPause);
			if (!b) {
				explosionList.remove(i);
				i--;
			}
		}

4.7 血包功能

游戏三分之一和三分之二进程时刻出现血包
//开启血包
	if (MyPanel.myplane != null && MyPanel.myLife > 0 && !MyPanel.isPause) {
			// 关卡打了三分之一三分之二处出现血包
			if (MyPanel.passScore > (MyPanel.PASS_SCORE + MyPanel.passNum * 5)
					* MyPanel.lifeCount / 3) {
				// 若屏幕中有未吃掉的血包,这次不产生血包
				if (!MyPanel.bloodExist) {
					MyPanel.lifeCount++;
					// 产生血包
					Blood blood = new Blood();
					MyPanel.bloodList.add(blood);
					MyPanel.bloodExist = true;
					bloodTimer = new Timer();
					bloodTimer.schedule(new TimerTask() {

						@Override
						public void run() {
							bloodTimer.cancel();
							bloodTimer = null;
							MyPanel.bloodExist = false;
							// 声明血包位置
							for (int i = 0; i < MyPanel.bloodList.size(); i++) {
								MyPanel.bloodList.remove(i);
								i--;
							}
						}
					}, 10000);
				} else
					MyPanel.lifeCount++;
			}
		}
//血包定时器,10秒后血包消失
	bloodTimer = new Timer();
					bloodTimer.schedule(new TimerTask() {

						@Override
						public void run() {
							bloodTimer.cancel();
							bloodTimer = null;
							MyPanel.bloodExist = false;
							// 声明血包位置
							for (int i = 0; i < MyPanel.bloodList.size(); i++) {
								MyPanel.bloodList.remove(i);
								i--;
							}
						}
					}, 10000);
//显示血包
	if (myplane != null && !isPause) {
			// 检索血包链表,非空时在所在位置显示
			int i = 0;
			while (i < bloodList.size()) {
				blood = bloodList.get(i);
				if (blood == null)
					continue;
				blood.draw(g, this, false);
				i++;
			}// while
		}
//血包碰撞检测
 if (MyPanel.myplane != null && !MyPanel.isPause) {
			// 吃到血包
			// 声明血包位置
			for (int i = 0; i < MyPanel.bloodList.size(); i++) {
				Blood blood = MyPanel.bloodList.get(i);
				// 获得血包矩形
				Rectangle bloodbRect = blood.getRect();
				// 获得战机矩形
				Rectangle planeRect = MyPanel.myplane.getRect();
				// 判断两个矩形区域是否有交接
				if (bloodbRect.intersects(planeRect)) {// 音效
					AudioUtil.play(AudioUtil.AUDIO_BLOOD);
					// 加血效果
					MyPanel.myLife += 5;
					if (MyPanel.myLife > MyPanel.DEFAULT_LIFE)
						MyPanel.myLife = MyPanel.DEFAULT_LIFE;
					// TODO 声音
					// 加血后血包删除
					MyPanel.bloodList.remove(i);
					i--;
					break;
				}// if
			}// for
		}

4.8 通关和死亡消息页面

if (isStop == FLAG_RESTART) {
			Font textFont = new Font("宋体", Font.BOLD, 20);
			g.setFont(textFont);
			// 设置透明背景
			// cdc.SetBkMode(TRANSPARENT);
			g.setColor(Color.red);
			g.drawString("哇,恭喜你已通关!", SpaceWar.WINDOWS_WIDTH / 2 - 100,
					SpaceWar.WINDOWS_HEIGHT / 2 - 30);
			g.drawString("您的得分为:" + myScore, SpaceWar.WINDOWS_WIDTH / 2 - 100,
					SpaceWar.WINDOWS_HEIGHT / 2 - 10);
			g.drawString("COME ON !重新开始?Y/N", SpaceWar.WINDOWS_WIDTH / 2 - 100,
					SpaceWar.WINDOWS_HEIGHT / 2 + 10);
			return;
		} else if (isStop == FLAG_STOP) {
			Font textFont = new Font("宋体", Font.BOLD, 20);
			g.setFont(textFont);
			// 设置透明背景
			// cdc.SetBkMode(TRANSPARENT);
			g.setColor(Color.red);
			// 显示最后结果
			g.drawString("GAME OVER!", SpaceWar.WINDOWS_WIDTH / 2 - 100,
					SpaceWar.WINDOWS_HEIGHT / 2 - 30);
			g.drawString("您的得分为:" + myScore, SpaceWar.WINDOWS_WIDTH / 2 - 100,
					SpaceWar.WINDOWS_HEIGHT / 2 - 10);
			g.drawString("COME ON !重新开始?Y/N", SpaceWar.WINDOWS_WIDTH / 2 - 100,
					SpaceWar.WINDOWS_HEIGHT / 2 + 10);
			return;
		}

4.9 魔法值控制维护

	public class MagicTask extends TimerTask {

	@Override
	public void run() {
		if (MyPanel.myplane != null && !MyPanel.isPause && MyPanel.isStarted) {
			// 防护罩和战机升级没打开,魔法值递增
			if (!MyPanel.isProtect && !MyPanel.isUpdate) {
				MyPanel.magicCount++;
				if (MyPanel.magicCount > 10)
					MyPanel.magicCount = 10;
			}
			// 判断是否打开防护罩
			if (MyPanel.isProtect) {
				// 开启防护罩魔法值递减
				MyPanel.magicCount--;
				if (MyPanel.magicCount <= 0) {
					MyPanel.magicCount = 0;
					MyPanel.isProtect = false;
				}
			}
			// 判断是否升级战机
			if (MyPanel.isUpdate) {
				// 战机升级,魔法值递减
				MyPanel.magicCount--;
				if (MyPanel.magicCount <= 0) {
					MyPanel.magicCount = 0;
					MyPanel.isUpdate = false;
					MyPanel.myplane.isUpdate = MyPanel.isUpdate;
				}
			}
		}
	}
}

4.10 得分到达关卡需求,进入Boss

// 进入Boss
	int pScore = MyPanel.PASS_SCORE + MyPanel.passNum * 5;
		// TODO调试条件
		// if (MyPanel.myplane != null && MyPanel.passScore >= 3 &&
		// !MyPanel.isPause&&!MyPanel.isBoss)
		if (MyPanel.myplane != null && MyPanel.passScore >= pScore
				&& !MyPanel.isPause && !MyPanel.isBoss) {
			// 进入Boss
			MyPanel.isBoss = true;
			MyPanel.boss = new Boss(1);
			MyPanel.boss.setSpeed(Boss.BOSS_SPEED + MyPanel.passNum - 1);
			MyPanel.boss.life = Boss.BOSS_LIFE + MyPanel.passNum * 50;// Boss总血量
			MyPanel.bossBlood = Boss.BOSS_LIFE + MyPanel.passNum * 50;// 当前Boss血量
			// Boss出场,暂停游戏
			MyPanel.bossLoaded = false;

			// 重新设置Boss的子弹产生频率,增强Boss子弹发射频率
			MyPanel.enemyTimer.cancel();
			MyPanel.enemyTimer = null;
			MyPanel.enemyTimer = new Timer();
			MyPanel.enemyTimer.schedule(new EnemyTask(MyPanel.enemyList), 0,
					2000 - MyPanel.passNum * 120);
		}
//显示Boss
	if (myplane != null && boss != null && !isPause && isBoss) {
			boolean status = boss.draw(g, this, passNum, isPause);
			if (status)
				bossLoaded = true;
		}

4.11 检测标记位isPass,判断打赢Boss,进入下一关

if (MyPanel.isPass) {
			MyPanel.isPass = false;
			if (MyPanel.passNum == 10)// 10关
			{
				// 重新初始化数据
				MyPanel.killTimer();
				MyPanel.myplane = new MyPlane(false);
				MyPanel.isPause = true;

				MyPanel.isStop = MyPanel.FLAG_RESTART;
				// 清屏
			}// if
			else {
				MyPanel.killTimer();
				MyPanel.isPause = true;
				// 保存所需数据
				int tScore = MyPanel.myScore + MyPanel.passScore;
				int tPassNum = MyPanel.passNum + 1;
				boolean tTest = MyPanel.test;
				int magic = MyPanel.magicCount;
				// 重新开始游戏
				MyPanel.Restart();
				MyPanel.myplane = new MyPlane(false);
				MyPanel.myScore = tScore;
				MyPanel.passNum = tPassNum;
				MyPanel.magicCount = magic;
				MyPanel.test = tTest;
			}// else
		}// if

五、 课程设计中遇到的主要问题及解决方法

5.1滚动背景实现问题

要实现滚动背景,需要在背景图上取出客户区矩形大小的区域,并按照Timer进行递进,并且要在背景图边界处衔接好返回背景图开头位置,实现一张背景图的循环反复,达到滚动背景图的目的,刚开始由于不懂得如何实现开头结尾处的衔接问题,导致滚动背景图实现难度大,后来经由网上查阅的资料得知要把背景图加载两份,第二份背景图开头衔接在第一份结尾处,让客户区矩形在该两份背景图上进行滑动,当滑动到第二份背景图时,再把第一份接到第二份结尾处,从而实现循环反复滚动背景。其中实现的难点在于控制好客户区矩形坐标和背景图上要显示的图片块位于图片上的坐标的对应关系。

5.2 背景音乐循环播放和游戏音效同时输出问题

由于平时接触Swing太少,对Swing操作多媒体文件颇为陌生,对于Java的音频播放也是颇为陌生,通过网上查找了很多相关资料,才把AudioClip对音频的播放功能实现,而且刚开始使用,没注意到音频文件播放完的释放问题,导致程序运行一阶段之后出现了内存溢出而崩溃的情况。

5.3多帧位图素材的动画特效实现问题

飞机大战中的爆炸显示是通过一张带有八个帧的位图进行连续输出实现爆炸特效,刚开始只是简单的在一个while循环中循环八次,结果不尽如人意,后来联想到帧和时间的对应关系,在每一次TimerTask调用时输出一帧,并在爆炸对象中用progress标记位记录播放位置,等到八个帧播放结束时,返回播放结束标记位,在TimerTask中检测并删除播放完成的该爆炸对象。

5.4 游戏结束和游戏重新开始的游戏资源维护问题

该游戏由于实现了多个额外功能,且都是带有全局意义的,因此放置了较多标记位来标记每个状态,通过这些标记位来控制游戏进程中的各个状态,因此在游戏结束和重新开始游戏时,各个标记位的正确重置将会决定重新开始游戏之后的游戏状态。还由于这些操作可能会在TimerTask类调用过程中进行中断,因此程序运行中途的中断时的游戏参数的维护对程序的正确执行至关重要。

六、程序截图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

七、 课程设计体会

由于对Swing接触不多,因此在开发过程中走了很多弯路,但是也是这次的实训,让我了解了Swing的事件处理机制,了解了如何在屏幕中绘制需要向用户展示的图形信息,学会了使用基本的操作对屏幕中绘制的图形进行控制,也了解了Java的Timer定时器的机制,学习了Sun封装好的Swing类库,学会遇到问题到JDK文档中查询Api,并对Eclipse的使用和编程也进行了提高。
在开发过程中遇到了大量的异常,通过此次开发,也学会了遇到错误如何慢慢通过IDE的错误信息进行错误查找和代码排错更正,通过添加断点调试程序一步步监视程序运行的具体过程,从而找到程序运行出错的原因。
本次的课程设计的飞机大战代码是基于前一次的MFC飞机大战进行移植的,因此,在模块的分布和设计方面大多维持原来C++的布局,因此,本次项目开发的源码层次接口没有严格的按照Java的规范,这是这个项目的不足之处,如果时间允许,应该着手对其中的代码布局进行相应的修改,以符合Java的风格,增强代码的可读性。

八、交流与联系

q:969060742 报告、完整源码、程序资源

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1059515.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微信小程序WebSocket实现stream流式聊天对话功能

要在微信小程序实现聊天对话功能&#xff0c;回话是流式应答&#xff0c;这里使用了WebSocket技术。WebSocket大家应该都很熟悉&#xff0c;使用wx.connectSocket就可以了。这里可能需要注意下的是流式应答&#xff0c;后端如何发送&#xff0c;前端如何接收。直接上代码&#…

【1】c++设计模式——>UML类图的画法

UML介绍 UML:unified modeling language 统一建模语言 面向对象设计主要就是使用UML类图&#xff0c;类图用于描述系统中所包含的类以及他们之间的相互关系&#xff0c;帮助人们简化对系统的理解&#xff0c;他是系统分析和设计阶段的重要产物&#xff0c;也是系统编码和测试的…

小程序 用户反馈 与 客服对话 使用说明

在开发小程序时&#xff0c;通过翻阅官方文档&#xff0c;会发现 button 的 open-type 属性有很多值可以选。因此&#xff0c;我们就可以实现相应的按钮功能。 微信开发文档-表单组件-buttonhttps://developers.weixin.qq.com/miniprogram/dev/component/button.html contact…

嵌入式学习笔记(44)S5PV210的SD卡启动实战

8.5.1任务&#xff1a;大于16KB的bin文件使用SD卡启动 (1)总体思路&#xff1a;将我们的代码分为2部分&#xff0c;第一部分BL1小于等于16KB&#xff0c;第二部分为任意大小&#xff0c;iROM代码执行完成后从SD卡启动会自动读取BL1到iRAM中执行&#xff1b;BL1执行时负责初始化…

ChatGPT启蒙之旅:弟弟妹妹的关键概念入门

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

腾讯云服务器哪个配置比较值得?

腾讯云服务器哪款配置比较好值得买&#xff1f;轻量应用服务器性价比值得买&#xff0c;轻量2核2G3M带宽95元一年、2核4G5M带宽218元一年、2核2G4M带宽三年540元一年、4核8G12M配置446元一年、8核16G18M带宽1668元15个月、16核32G28M轻量服务器3468元15个月。腾讯活动入口&…

tiny模式基本原理整合

【Tiny模式】的基本构成 M【首头在首位】 U【/】 V【HTTP/】 Host H【真实ip】 XH \r回车 \n换行 \t制表 \ 空格 一个基本的模式构成 [method] [uri] [version]\r\nHost: [host]\r\n[method] [uri] [version]\r\nHost: [host]\r\n 检测顺序 http M H XH 有些地区 XH H M 我这边…

lenovo联想台式机 拯救者 刃7000-28ICBR(90KX)原装出厂Windows10系统镜像

LENOVO联想拯救者(90KX)原厂WIN10系统 下载链接&#xff1a;https://pan.baidu.com/s/1beocPJSmnFbY4Y_ZQM2djA?pwd4d1n 系统自带所有驱动、出厂主题壁纸LOGO、Office办公软件、联想电脑管家等预装程序 所需要工具&#xff1a;16G或以上的U盘 文件格式&#xff1a;ISO 文件大…

华为云云耀云服务器L实例评测|云耀云服务器L实例部署ZFile在线网盘服务

华为云云耀云服务器L实例评测&#xff5c;云耀云服务器L实例部署ZFile在线网盘服务 一、云耀云服务器L实例介绍1.1 云耀云服务器L实例简介1.2 云耀云服务器L实例特点 二、ZFile介绍2.1 ZFile简介2.2 ZFile特点 三、本次实践介绍3.1 本次实践简介3.2 本次环境规划 四、购买华为云…

《幸福之路》罗素(读书笔记)

目录 作者简介 作者的感悟 经典摘录 一、不幸福的成因 1、一部分要归咎于社会制度 2、一部分则得归咎于个人心理——当然&#xff0c;你可以说个人心理是社会制度的产物。 二、欠缺某些想要的东西&#xff0c;是快乐的必要条件 三、无聊与刺激 四、现代人的精神疲劳 五…

word已排序好的参考文献,插入新的参考文献,序号更新

原排序好的文献序号。 现在在3号后面插入一个新文献。4&#xff0c;5号应该成为5&#xff0c;6 这时在3号后面&#xff0c;回车&#xff0c;就会自动的增长。如下图&#xff1a; 但是如果手滑&#xff0c;把[4]删除了如何排序&#xff1f;&#xff1f; 如下图&#xff1a; …

基于Java的医院药品管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

聊天记录一句一句出现的视频制作,制作抖音聊天记录视频教程

聊天记录情感中视频制作工具是一款专注于将聊天记录转化为抖音视频的工具。它可以将平淡的聊天截图转化为生动有趣的视频&#xff0c;让你的回忆变得更加具有观赏性和情感共鸣。 首先&#xff0c;聊天记录一句一句出现的视频制作功能是该工具的一大特点。通过这个功能&#xf…

应力分析概要

1.概述 应力分析是一种用于结构件寿命分析的方法&#xff0c;最早的应用场景可能是路桥&#xff0c;因为西方提前我们两百年以上进入大工业时代。许多人工建筑的寿命是有限的&#xff0c;这类实际需求催生出寿命预测和诊断。结构件的失效&#xff0c;最根本的因素是因为应力的…

基于Matlab求解高教社杯全国大学生数学建模竞赛(CUMCM2004A题)-奥运会临时超市网点设计(附上源码+数据)

文章目录 题目思路源码数据下载 题目 2008年北京奥运会的建设工作已经进入全面设计和实施阶段。奥运会期间&#xff0c;在比赛主场馆的周边地区需要建设由小型商亭构建的临时商业网点&#xff0c;称为迷你超市&#xff08;Mini Supermarket, 以下记做MS&#xff09;网&#xf…

mybatis项目启动报错:reader entry: ���� = v

问题再现 解决方案一 由于指定的VFS没有找&#xff0c;mybatis启用了默认的DefaultVFS&#xff0c;然后由于DefaultVFS的内部逻辑&#xff0c;从而导致了reader entry乱码。 去掉mybatis配置文件中关于别名的配置&#xff0c;然后在mapper.xml文件中使用完整的类名。 待删除的…

排序---P1781 宇宙总统

思路&#xff1a; 当我们要对这些超大数进行比较排序时&#xff0c;如果我们用int或long基本数据类型时&#xff0c;会超出能承载的范围&#xff0c;因此我们选择用引用数据类型&#xff1a;BigDecimal或BigInteger。 区别在于基本数据类型直接比较大小&#xff0c;而是调用这…

平面图—简单应用

平面图&#xff1a;若一个图&#x1d43a;能画在平面&#x1d446;上&#xff0c;且使&#x1d43a;的边仅在端点处相交&#xff0c;则称图&#x1d43a;为可嵌入平面&#x1d446;&#xff0c;&#x1d43a;称为可平面图&#xff0c;简称为平面图。 欧拉公式&#xff1a;设有…

机器学习笔记 - 深入研究spaCy库及其使用技巧

一、简述 spaCy 是一个用于 Python 中高级自然语言处理的开源库。它专为生产用途而设计,这意味着它不仅功能强大,而且快速高效。spaCy 在学术界和工业界广泛用于各种 NLP 任务,例如标记化、词性标注、命名实体识别等。 安装,这里使用阿里的源。 pip install spacy…

Arcgis小技巧【14】——拓扑(Topology)的方方面面

在ArcGIS中&#xff0c;拓扑是定义点要素、线要素以及面要素共享重叠几何的方式的排列布置。 简单来说&#xff0c;我们可以将拓扑理解各类要素的地理空间关系&#xff0c;如重叠、相交、相连等。 拓扑的主要功能就是用于保证数据质量&#xff0c;当然它还有其它很多功能&…