课题名称:打飞鸟游戏程序
(本课题为学校Java程序设计课程大作业课题,实现了要求中的全部功能,如有问题欢迎讨论与指正。)
课题要求:
①基本要求:飞鸟(可用位图或简单图形表示)随机出现在游戏窗口中并不断移动飞离窗口,每次一只鸟飞过屏幕后接着下一只飞鸟才出现,可使用鼠标瞄准和射击,飞鸟被击中一发子弹后将落下并有声响。游戏初始设定为有100发子弹和50只飞鸟,子弹全部用完或鸟全部飞走游戏结束,游戏过程中动态显示击落飞鸟数和剩余子弹数以及游戏得分,游戏结束后计算游戏总分。
②提高要求:屏幕上可以同时出现多只飞鸟以不同速度飞过;飞鸟被击中后的下落轨迹受到飞鸟飞行速度影响,满足物理定律;某些飞种类的鸟被击中后变成图标落下,射击图标后可以得到额外的加分或补充子弹;游戏难度会依据游戏者水平发生变化,或设计为有多个关的游戏方式等;游戏画面的精细完善,有最佳的动画以及声音效果。
游戏规则:
每局游戏共有50只飞鸟,10个弹夹(每个弹夹10发子弹,每次更换弹夹需要一秒钟时间),当子弹全部用完或者飞鸟全部飞走时游戏结束。
鼠标瞄准射击,击中不同种类飞鸟可以得到对应的分数,未击中且飞离屏幕扣相应的分数:击中小鸟加10分,未击中且飞离屏幕扣30分;击中鹤加10分,未击中且飞离屏幕扣20分;击中鹰加50分,未击中飞离屏幕扣10分,击中鹰后会有星星在屏幕上随机掉落,击中额外奖励15分,未击中不扣分。该游戏依照难度不同分为简单和困难两个模式——简单难度有两种种类的飞鸟,困难难度有三种种类飞鸟。
软件实现及代码编写(含部分代码):
1、Shootbirds类中定义了背景音乐和各种游戏音效,包含了文件的读取操作。以鸟被击中的音效为例,调用在BirdClass鸟类的构造函数中,当鼠标点击此label时播放鸟类被击中的声音。定义方法如下:
public static voidplayBirdDieSound() { //鸟被击中音效
Clip clip = null;
try {
clip = AudioSystem.getClip();
clip.open(AudioSystem.getAudioInputStream(newFile("sounds/birddie.wav")));
//打开文件,声音文件全部以wav格式储存在sounds文件夹中,因而使用相对路径
clip.start();
} catch (Exception exc) { //读取文件异常处理
exc.printStackTrace();
}
}
2、Shootbirds类中定义了useBullet消耗子弹方法,该方法同时也加入了判断游戏是否结束方法的调用,即在游戏进行过程中玩家每次开枪消耗子弹都会判断一次游戏是否结束,若游戏结束直接显示游戏结束界面。该方法中也对开枪音效方法进行调用、对显示弹夹中剩余子弹和总剩余子弹数目的标签内容进行更新、还加入了鸟类线程的起始方法等。实现方法如下:
public synchronized static void useBullet() { //消耗子弹方法,定义为public为BirdClass以及其他三个类中调用该方法
if (isOver()) //每次使用子弹都判断游戏是否结束
GameOverScreen(); //若判断结束直接调用游戏结束页面,清除所有面板内容显示游戏结束及游戏总得分
else if(countb==0)
return; //这个判断条件是后期调试程序时所加,修复了还未选择难度即没有鸟类出现时就可以开枪点击主面板消耗子弹的bug
else {
synchronized (BulletNum) {
BulletNum--;
playGunShotSound(); //调用定义开枪音效的方法
if (BulletNum > 0) { //每次使用子弹都更新子弹标签的内容
BulletLabel.setText("弹夹子弹数量:" + BulletNum);
BulRestLabel.setText("剩余子弹数量:" + (BulletNum+10*(9-counta)));
}
else{
BulletLabel.setText("Empty!"); //为了在弹夹中子弹打空时再开枪不显示子弹为负数,加入了判断条件,若弹夹子弹数量小于等于0即显示Empty
BulRestLabel.setText("剩余子弹数量:" + (10*(9-counta)));
playNoBulletSound();
new Thread(new Runnable() {
public void run() {
JP.setVisible(true); //子弹小于0时将显示装填子弹的玻璃面板可见
try {
Thread.sleep(1000); //设置换弹夹时间为1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
Reload(); //上子弹的方法
BulletLabel.setText("弹夹子弹数量:" + BulletNum); //上子弹后再更新标签内容
BulRestLabel.setText("剩余子弹数量:" + (BulletNum+10*(9-counta)));
JP.setVisible(false); // 换弹后再玻璃面板不可见
}
}).start(); //每次使用子弹都开始一次鸟类线程
}
}
}
}
private static void Reload() { //上子弹方法,只要将BulletNum重置为10,再进行弹夹计数+1操作即可
if (!isOver()) {
BulletNum = 10;
counta++;
} else
return;
}
3、Shootbirds中定义了判断游戏结束的方法和设置游戏结束界面的方法,内容较为简单,只要判断产生的飞鸟数目是否大于49以及弹夹数目是否大于9即可。而游戏结束界面使用的面板与装填子弹面板相同,都为玻璃面板,在显示之前将面板内容清空再设计即可。实现方法如下:
private static boolean isOver() { //判断游戏结束,定义为私有,只在当前类调用
if (counta > 9 || countb > 49)
return true;
else
return false;
}
private static void GameOverScreen() { //私有方法
JLabel overjl = new JLabel("游戏结束!");
overjl.setBounds(290, 100, 300, 100);
overjl.setFont(new Font("宋体", Font.BOLD, 40));
overjl.setForeground(Color.darkGray);
JLabel finalscore = new JLabel();
finalscore.setText("得分:" + score);
finalscore.setFont(new Font("宋体", Font.BOLD, 40));
finalscore.setForeground(Color.darkGray);
finalscore.setHorizontalAlignment(SwingConstants.CENTER);
JP.removeAll(); //清除玻璃面板内容
groundpanel.removeAll(); //将背景面板上的内容也清空
JP.add(overjl);
JP.add(finalscore);
JP.setVisible(true);
}
4、Shootbirds类中的main方法调用了该类的构造方法,显示出了简单难度和困难难度的选项按钮,点击按钮后调用对应的开始游戏的方法并且再点击后将该按钮移除面板。实现方法如下:
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
public void run() {
Shootbirds frame = new Shootbirds(); //调用构造方法设置面板
frame.setVisible(true); //使面板可见
EasyButton.addActionListener(new //鼠标单击的响应方法
ActionListener()
{
public void actionPerformed(ActionEvent event)
{
EasyButton.setVisible(false); //隐藏并移除按钮
groundpanel.remove(EasyButton);
HardButton.setVisible(false);
groundpanel.remove(HardButton);
frame.startEasy(); //调用简单模式的起始方法
}
});
HardButton.addActionListener(new
ActionListener()
{
public void actionPerformed(ActionEvent event)
{
EasyButton.setVisible(false);
groundpanel.remove(EasyButton);
HardButton.setVisible(false);
groundpanel.remove(HardButton);
frame.startHard(); //调用困难模式的起始方法
}
});
}
});
}
public void startEasy() { //启动简单模式,两种类的飞鸟
new BirdThread().start();
new EagleThread().start();
}
public void startHard() { //启动困难模式,三种类的飞鸟
new BirdThread().start();
new EagleThread().start();
new GooseThread().start();
}
5、BirdClass类(与EagleClass类、GooseClass类、ExtraBonus类相似,以BirdClass类为例进行说明)构造方法中设置了图标,设置了鼠标点击图标的响应即击落飞鸟数+1,调用一次消耗子弹方法,调用加分方法,播放飞鸟被击中的声音,对被击中的飞鸟进行标记再起始下一个线程等。实现方法如下:
public BirdClass() {
super();
ImageIcon icon = new ImageIcon(getClass().getResource("bird.jpg"));
setIcon(icon);
addMouseListener(new MouseAdapter() {
public void mousePressed(final MouseEvent e) {
if (!Shootbirds.JudgeBullet()) //此处判断弹夹中是否还有子弹,若弹夹无子弹,不进行任何操作直接返回
return;
Shootbirds.useBullet(); //调用主类中消耗子弹的方法
if (!beshot) {
Shootbirds.bingoNum++; //bingoNum为击落的飞鸟数
addScore(); //调用加分方法
Shootbirds.playBirdDieSound(); //调用击落飞鸟声音的方法
}
beshot = true; //beshot为被击中标记
}
});
addComponentListener(new ComponentAdapter() {
public void componentResized(final ComponentEvent e) {
thread.start(); //起始线程,调用run()方法
}
});
thread = new Thread(this); //初始化此线程
}
6、BirdClass中的run()方法较为关键,为控制飞鸟移动的方法,包括横移和抛物线掉落,在被击中后,也调用了destroy()方法销毁线程。具体是通过循环来对图标位置进行控制,在循环中调用默认的setLocation()方法移动图标的位置,并且每次移动之后都让循环休眠一小段时间,用此休眠时间即可控制飞鸟的移动速度。实现方法如下: