Java多线程游戏-雷霆战机
先来张效果图(结尾附上源码地址,欢迎交流)
一、总述
飞机大战大家童年的时候都玩过,前两天学了线程方面的知识后,决定写一个关于多线程雷霆战机的游戏(单机)。这里用到了许多有关线程,图片处理,音乐等的技术我刚接触Java半年,做这个游戏磕磕绊绊。花费了很多时间,最后还是有一些瑕疵。素材是从另一位博主那下载的。
素材下载地址:
地址
二、相关技术
- 了解什么是线程,进程,多线程,并发线程
- 了解线程寿命以及线程的控制
- 了解线程安全问题,处理游戏中出现的线程安全问题
- 了解Java中的list类以及它的底层实现
- 音乐文件的读取与播放
- 了解键盘监听
- 缓冲区(解决屏幕闪烁问题)
三、功能介绍及讲解
(一)功能简介
1.加载页面
2.菜单页面
3.弹窗提示
4.多重关卡(四关)
5.多种飞机(八种)
6.音效
7.地图动态滚动,爆炸动图
8.分数达到一定程度飞机升级
9.计时
10.暂停\继续游戏(线程控制)
11.开始新游戏(清空原有数据与加载新数据)
12.道具栏与道具使用(刷新道具栏,使用道具,四种道具)
13.关卡boss
(二)功能讲解
1.加载页面的制作
关键点:进度条的使用
加载界面,无外乎就是个界面嘛,重要的可能就是那个加载条了吧。这个加载条你可以自己做,也可以用Java中的加载条,这里我用的是Java中自带的加载条。
JProgressBar bar = new JProgressBar(1, 100);
bar.setStringPainted(true); //描绘文字
bar.setString("加载飞机和子弹中,请稍候,精彩马上开始"); //设置显示文字
bar.setBackground(Color.ORANGE); //设置背景色
bar.setBounds(100, 800, 1000, 100);//设置大小和位置
bar.setValue(20); //设置进度条值
2.菜单页面的制作
就是提供一些功能按键以及弹窗实现各功能的有序进行。
关键点:弹窗、按钮监听、焦点的获取
弹窗,无外乎也是个界面嘛,要么自己做一个窗口,要么使用Java中自带的弹窗类
JDialog cs=new JDialog();
弹窗的各项操作与窗体JFrame类是一致的。
这里要注意的就是因为后面要实现键盘的监听,所以焦点必须要注意他的位置。并注意获取焦点。
3.多重关卡
这个没啥好说的,我就只是找了四张地图而已。
4.多种飞机
这个比较麻烦,在设计类的时候,可以将各种飞机不同的属性用私有数据来进行初始化。在创建飞机对象的时候将各项数据传进去即可。
下面是我的飞机类代码;
英雄机
public class MyHeroPlane {
private int Blood;
private int speed;
private ImageIcon image;
public MyHeroPlane(int blood, int speed, ImageIcon image) {
super();
Blood = blood;
this.speed = speed;
this.image = image;
}
public int getBlood() {
return Blood;
}
public int getSpeed() {
return speed;
}
public ImageIcon getImage() {
return image;
}
public void setBlood(int blood) {
Blood = blood;
}
}
敌机类
public class EnemyPlane extends Thread{
private int blood;
private int x,y;
private ImageIcon image;
private int speed;
private boolean islive;
private int sore;
private boolean suspend=false;
public EnemyPlane(int blood, int x, int y, ImageIcon image,int speed,int sore) {
super();
this.blood = blood;
this.x = x;
this.y = y;
this.image = image;
this.speed=speed;
this.islive=true;
this.sore=sore;
}
public void run()
{
while(blood>=0&&islive)
{
while(suspend)
{
//暂停功能
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
y+=speed;
}
}
public int getBlood() {
return blood;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public ImageIcon getImage() {
return image;
}
public int getSpeed() {
return speed;
}
public boolean isIslive() {
return islive;
}
public void setIslive(boolean islive) {
this.islive = islive;
}
public void setBlood(int blood) {
this.blood = blood;
}
public int getSore() {
return sore;
}
public boolean isSuspend() {
return suspend;
}
public void setSuspend(boolean suspend) {
this.suspend = suspend;
}
}
子弹类(我方,敌方子弹)
我方子弹类
public class MyHeroBullets extends Thread{
private boolean islive=true;
private int x;
private int y;
private int atk;
private ImageIcon image;
private int speed;
private boolean suspend=false;
public MyHeroBullets(int x, int y, int atk, ImageIcon image, int speed) {
super();
this.x = x;
this.y = y;
this.atk = atk;
this.image = image;
this.speed = speed;
}
public void run() {
while(islive)
{
while(suspend)
{
//暂停功能
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
y-=speed;
}
}
public boolean isIslive() {
return islive;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getAtk() {
return atk;
}
public ImageIcon getImage() {
return image;
}
public int getSpeed() {
return speed;
}
public void setIslive(boolean islive) {
this.islive = islive;
}
public boolean isSuspend() {
return suspend;
}
public void setSuspend(boolean suspend) {
this.suspend = suspend;
}
}
敌方子弹类
public class EnemyBullets extends Thread{
private int atk;
private ImageIcon image;
private int speed;
private boolean islive=true;
private int x,y;
private boolean suspend=false;
public EnemyBullets(int atk, ImageIcon image, int speed, int x, int y) {
super();
this.atk = atk;
this.image = image;
this.speed = speed;
this.x = x;
this.y = y;
}
public void run() {
while(islive)
{
while(suspend)
{
//暂停功能
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
y+=speed;
}
}
public boolean isIslive() {
return islive;
}
public void setIslive(boolean islive) {
this.islive = islive;
}
public int getAtk() {
return atk;
}
public ImageIcon getImage() {
return image;
}
public int getSpeed() {
return speed;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public boolean isSuspend() {
return suspend;
}
public void setSuspend(boolean suspend) {
this.suspend = suspend;
}
}
这里我没有将子弹与飞机封装在一起,这个不太方便,各位可以采用封装在一起的方法来实现。
5.音效
话不多说,读取文件后播放,直接上代码。
public class GetMusic extends Thread{
private String path;
private boolean flag=true;
private boolean whileplay;
private double value ;
public GetMusic(String path,boolean whileplay,double value) {
super();
this.path = path;
this.whileplay=whileplay;
this.value=value;
}
public void run()
{
if(whileplay)
{
while(flag)
{
playMusic();
}
}
else
playMusic();
}
public void playMusic() {// 背景音乐播放
try {
AudioInputStream ais = AudioSystem.getAudioInputStream(new File("music\\"+path));
AudioFormat aif = ais.getFormat();
final SourceDataLine sdl;
DataLine.Info info = new DataLine.Info(SourceDataLine.class, aif);
sdl = (SourceDataLine) AudioSystem.getLine(info);
sdl.open(aif);
sdl.start();
FloatControl fc = (FloatControl) sdl.getControl(FloatControl.Type.MASTER_GAIN);
// value可以用来设置音量,从0-2.0
double value = 2;
float dB = (float) (Math.log(value == 0.0 ? 0.0001 : value) / Math.log(10.0) * 20.0);
fc.setValue(dB);
int nByte = 0;
int writeByte = 0;
final int SIZE = 1024 * 64;
byte[] buffer = new byte[SIZE];
while (nByte != -1) {
if(flag) {
sdl.write(buffer, 0, nByte);
nByte = ais.read(buffer, 0, SIZE);
}else {
nByte = ais.read(buffer, 0, 0);
}
}
sdl.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
6.飞机升级
这个挺好实现的,你在产生子弹的线程中多产生两路子弹就可以了。当然如果你有什么更好的想法,欢迎交流。
效果图:(达到4000分升级)
7.暂停\重新开始
我在这里用的是在线程run方法中加入死循环,进入暂停时,所有线程进入死循环。当然这样也有缺点,就是对电脑资源占用较高。也可以采用对线程进行休眠与唤醒来实现,这种方法在我的程序中较为麻烦,就没采用。
8.开始新游戏
这个你可以用重新创建对象来实现,但是如果你之前创建对象使用的是static,那么,你就需要去手动清空所有数据。很不巧,我就是部分对象创建的是static,所以会稍微麻烦一些。
9.道具的使用
为了增加游戏的可玩性,你可以增加一些道具,例如,加血道具,护盾道具,僚机,强化子弹。。。。。
这个你可以自己设计。你可以通过我的源码看懂怎么设计的就不赘述了。上两张效果图。
护盾
僚机
强化子弹
10.关卡boss
这个和设计敌机一样,只是吧,你可把boss血量调高一些,发射子弹的方式调一下。
(三)关键算法
碰撞判断
先看一张图
碰撞无外乎就是上面四种情况,所以,我们可以有很多种方法去实现它,你可以通过判断两张图片中心点之间的距离,你也可以通过遍历我方飞机的四个点,是否有一个点在敌方飞机或子弹的范围内即可。
四、我这个项目明显的缺点
- 所有我方子弹,敌方飞机,敌方子弹都是一个新线程,游戏进行中同时运行了成百上千的线程。造成电脑卡顿,资源浪费。解决方法,移动的线程不必每次创建子弹和飞机时都启动一个线程,用一个移动线程就能控制。
- 图片因为是四四方方的一张图片,有一部分留白,所以在写碰撞函数时无法兼顾所有图片,存在一定视觉上的误差(明明打中了却穿过去了,或者明明看上去没打中,但实际上却打中了)
- 游戏难度设置不合理(啊哈哈哈哈,因为我没有考虑玩家感受,在打boss的时候我试了一下,基本很难过关)。
- 游戏程序稳定性不好,遇到部分不容易出现的bug 会卡死。
- 存在一定闪烁问题(我加了缓冲功能,但是因为程序占用了太多资源,执行起来还是有一定的闪烁)。
最后的最后,附上我源代码的链接:
源码