基于Java实现的潜艇大战游戏
基于Java实现的潜艇大战游戏
一.需求分析
1.1 设计任务
本次游戏课程设计小组成员团队合作的方式,通过游戏总体分析设计,场景画面的绘制,游戏事件的处理,游戏核心算法的分析实现,游戏的碰撞检测,游戏的反复测试,游戏的打包运行等一个完整的游戏设计编码实现的整个过程。进一步提高编写结构清晰,扩展性好,风格良好的应用程序,进一步提高思考解决实际问题的工程能力。
1.2 功能需求
- 玩家进入游戏,退出游戏的功能,没有过关重玩的功能
- 玩家开始游戏,暂停游戏,查看游戏规则
- 玩家键盘A,D(快捷键)鼠标操作实现战舰的移动和投导弹攻击潜艇
- 潜艇左面出,向右移动,每一个时间间隔向上投鱼雷攻击战舰
- 战舰和潜艇被攻击后有相应的爆炸特效和对应的音效
- 玩家的得分统计,进入前十就提示输入姓名,当前最高分的查看
- 玩家通过的关口统计,下一关口难度增加的设置
1.3 用例模型
- Actor主要为玩家和游戏系统
- 玩家 user case 包括 开始游戏,操作战舰,退出游戏,暂停游戏,得分关卡,进入排行榜
- 游戏系统 user case 潜艇出击,爆炸效果,积分榜重置,结束游戏,场景画面
1.4 性能需求
技术可行性
Windows8.1下的开发平台,Windows 8.1的运行平台,使用Java Swing+AWT图形界面制作客户端程序的技术,界面友好,功能齐全,音效动感,操作简单,的一款潜艇大战游戏.
经济可行性
游戏的关卡设计,使游戏的趣味性高,音效设计,都是基于素材网上的免费资源,单机游戏,开发平台在本地,一台装有JAVA的PC即可提供硬件环境,成本极低。
操作可性性
键盘操作,加快捷键和鼠标结合,提供完整游戏规则,查看即可学习,立刻就可以玩起来,操作非常简单。
1.5 系统结构图
1.6 系统流程图
二.游戏功能内容设计
2.1 工程结构图
2.2 游戏界面绘制
2.2.1 进入游戏界面绘制
首先使用Swing组件的JFrame完成窗口的绘制,包括大小,位置,背景图片,按钮的排放,布局采用BorderLayout的边框布局方式.窗口图标采用Toolkit.getDefaultToolkit().getImage(imgUrl)
2.2.2 游戏主界面的绘制
上面的进入游戏的界面点击进入游戏后切换至主界面里面后,完成主界面的颜色背景绘制,主要使用Graphics2D设置相应的颜色代码完成实现。上面的菜单栏采用JMenuBar,JMenu,JMenuItem来完成绘制。至于得分,关数是在指定的位置绘制的。那个游戏的最高分是从游戏玩家排行榜里面读取出来然后绘制的,命数是自定义的3条。在程序里面设置好的。然后就是战舰和导弹的绘制了的.战舰封装一个战舰对象然后结合Image对象加载图片设置位置来实现的.导弹是初始设置五个,通过线程的方式指定的时间内绘制即可。最多为5个,最小为0个.绘制的时候要注意判断当前的导弹数量来绘制的。
2.2.3 游戏对话框的绘制
实现的方法就是继承JDialog对话框+Graphics2D设置宽高和位置就可以设置的,例如帮助对话框的实现。
private static final long serialVersionUID = 1L;
protected JFrame frame1;
private MyPanel panel;
private boolean flag = false;
private boolean isDraw = false;
private boolean isOutDraw = false;
/** 设置对话框为圆角矩形*/
public void setVisiableRigeon(int width,int height)
{
Shape shape = new RoundRectangle2D.Float(20,20,this.getWidth() - 40,this.getHeight()-40,15.0f,15.0f);
Shape shape2 = new Ellipse2D.Double(this.getWidth() - 40, 10,30,30);
Area area = new Area(shape);
Area area2 = new Area(shape2);
area.add(area2);
AWTUtilities.setWindowShape(this,area);
}
2.3 游戏音效制作
JAVA的AWT下面的AudioClip类播放音乐.wav格式的音乐,音乐文件使用的地方有两个:一个是战舰发射导弹命中潜艇,潜艇发射鱼雷命中战舰的,先展示爆炸图片然后在播放背景音效。
2.4 游戏爆炸场景的设计
类如下
关键方法
private MyPanel panel;
private Image image;
/** 爆炸效果显示的时间默认为700毫秒*/
private int liveTime = 700;
/** 位置 x y*/
private int beginX = 0;
private int beginY= 0;
/** 游戏是否正在运行标志*/
private boolean isRunning = false;
/*绘制爆炸效果图片*/
public void drawHitting(Graphics2D g)
{
g.drawImage(this.image, this.beginX, this.beginY, this.panel);
MusicUtil musicUtil = new MusicUtil();
musicUtil.playMissile();
}
/**圆的半径集合**/
private int[] r = {2,3,5,8,12,15,18,20,25,30,33,25,17,15,13,9,5};
/**集合中圆的位置起始标记**/
private int step = 0;
鱼雷命中潜艇是通过一个动态设置圆半径来实现先从小到大在到小的过程结合音效,效果还是不错的。
2.5 碰撞检测分析实现
碰撞的对象有两对的,分别是潜艇和导弹,鱼雷和战舰。通过位置坐标来判断(核心代码如下)当前战舰的位置坐标和窗体的高度来做一个判断后就取其一半,添加到爆炸绘制数组里面的。绘制的时候都设置和相应的位置坐标的。
/** 判断是否击中战舰 **/
public void hitting()
{
if(this.X > (this.ship.getBeginX() - this.weight) && this.X < (this.ship.getBeginX() + this.ship.getWidth()-this.getWeight()))
{
int num = this.panel.getLiveNum();
blast = new Blast(this.ship.getBeginX() + this.ship.getWidth() /2, this.ship.getBeginY() + this.ship.getHeight() / 2);
Thread t = new Thread(blast);
this.panel.getBlastArray().add(blast);
t.start();
num --;
this.panel.setLiveNum(num);
this.panel.loseGmae();
}
}
/** 根据集合中对象的位置参数判断潜艇和战舰是否被击中*/
public void explode()
{
// this.updateScreen();
if(!this.bumbArray.isEmpty() && !this.submarineArray.isEmpty())
{
for(int i = 0; i < this.bumbArray.size(); i ++)
{
if(((Bumb)this.bumbArray.get(i)).flag == false)
{
for(int j = 0; j < this.submarineArray.size(); j ++)
{
if(((Submarine)this.submarineArray.get(j)).flag == false)
{
int by =((Bumb)this.bumbArray.get(i)).getBeginY();
int syStart = ((Submarine)this.submarineArray.get(j)).getY() - ((Bumb)this.bumbArray.get(i)).getHeight();
int syEnd = ((Submarine)this.submarineArray.get(j)).getY() + ((Submarine)this.submarineArray.get(j)).getHeight();
int bx = ((Bumb)this.bumbArray.get(i)).getBeginX();
int sxStart = ((Submarine)this.submarineArray.get(j)).getX() - ((Bumb)this.bumbArray.get(i)).getWidth();
int sxEnd = ((Submarine)this.submarineArray.get(j)).getX() + ((Submarine)this.submarineArray.get(j)).getWeight();
if( by >= syStart && by <= syEnd && bx >= sxStart && bx <= sxEnd)
{
this.bumbArray.get(i).flag = true;
this.submarineArray.get(j).flag = true;
this.hitX = this.bumbArray.get(i).getBeginX() + 10;
this.hitY = this.bumbArray.get(i).getBeginY() + 30;
//this.hitFlag = true;
Hit hit = new Hit(this.hitX,this.hitY,this);
this.hitArray.add(hit);
Thread t = new Thread(hit);
t.start();
this.score += 10;
this.addPass(this.score);
}
}
}
}
}
}
}
2.6 游戏对象绘制
2.6.1 潜艇对象绘制
通过随机产生随机数,0,1,2分别对应不同的潜艇图片,方向是自左向右的(使用0和1代表)。移动的步长是一个1个单位(初始),还有一个是否运行的标记的,后面线程绘制暂停要使用的。使用Timer计时器来每隔一段时间产生潜艇来不断绘制。
// 计时器方法
public TimeManager(WarShip ship, MyPanel panel)
{
this.ship = ship;
this.panel = panel;
}
@Override
public void run()
{
Random r = new Random();
while(this.panel.isRunning())
{
if(this.panel.isStop() == true)
{
synchronized(MyPanel.subLock)
{
try
{
MyPanel.subLock.wait();
}
catch(Exception e)
{
e.printStackTrace();
//this.flag = true;
this.panel.endGame();
}
}
}
Submarine sm = new Submarine(this.ship,this.panel);
this.panel.getSubmarineArray().add(sm);
Thread t = new Thread(sm);
t.start();
try
{
Thread.sleep(this.speed + r.nextInt(this.speed * 3));
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
// 计时器每隔一段时间产生鱼雷对象
TimeManager2 tm2 = new TimeManager2(this,this.panel,this.ship,this.panel.getTorpedoArray());
Thread t = new Thread(tm2);
t.start();
// 绘制方法
public void drawSubmarine(Graphics2D g)
{
g.drawImage(image,this.X, this.Y, panel);
}
向左向右移动就是增加减少当前的坐标X值.然后重新绘制面板即可判断一下X值的范围。
public void moveLeft()
{
this.X -= dx;
this.panel.repaint();
if(this.X < 0)
{
this.flag = true;
}
}
2.6.2 战舰对象绘制
首先是封装一个战舰对象类。
/** 战舰初始位置和高度,宽度*/
private int beginX ;
private int beginY ;
private int width ;
private int height;
/** 初始炸弹数量*/
private int bombNum = 5;
private MyPanel panel;
/** 图片对象*/
private Image image;
就一句话,使用给出初始时刻的位置坐标X,Y,然后就是加载战舰的图片类,X值为相应窗体宽度的一半,高度就是指定一定的高度。同样也有相应的左移和右移的方法实现,指定每次一定的步长为5个单位。
/** 绘制战舰*/
public void drawShip(Graphics2D g)
{ g.drawImage(image,this.beginX ,this.beginY,this.panel);
}
/** 战舰右移动,步长为五*/
public void moveRight()
{
this.beginX +=5;
if((this.beginX + this.width) > this.panel.getWidth())
{
System.out.println("panel:"+this.panel.getWidth());
this.beginX = this.panel.getWidth() - this.width;
}
}
/** 战舰左移动,步长为五*/
public void moveLeft()
{
this.beginX -= 5;
if(this.beginX < 0)
{
this.beginX = 0;
}
}
2.6.3 鱼雷对象绘制
原来和前面相似就不做介绍了的,关键方法如下,移动的距离,鱼雷向上移动。
/** 潜艇对象*/
private WarShip ship;
/** 鱼雷的坐标和宽高*/
private int beginX;
private int beginY;
private int width = 5;
private int height = 15;
public boolean flag = false;
private MyPanel panel;
private Image image;
public Bumb(MyPanel p, WarShip ship)
{
this.beginX= ship.getBeginX()+20;
this.beginY = ship.getBeginY()+20;
this.panel = p;
this.ship = ship;
URL imgUrl=this.getClass().getResource("/images/炸弹.png");
image = Toolkit.getDefaultToolkit().getImage(imgUrl);
image = new ImageIcon(image).getImage();
this.width = image.getWidth(panel);
this.height = image.getHeight(panel);
}
/** 绘制鱼雷*/
public void drawBumb(Graphics2D g)
{
g.drawImage(image, this.beginX, this.beginY, this.panel);
}
/** 鱼雷向下移动*/
public void moveDown()
{
this.beginY += 1;
if(this.beginY > (this.panel.getHeight() - this.height2))
{
flag = true;
}
// this.panel.repaint();
}
// 鱼雷向上移动
public void upMove()
{
this.Y -= this.dy;
if(this.Y <= 150)
{
this.hitting();
this.flag = true;
}
if(this.sm.flag == true)
{
if(this.Y < 150)
{
this.flag = true;
}
}
}
2.6.4 炸弹对象绘制
/** 绘制炸弹*/
public void drawBombNum(Graphics2D g)
{
for(int i = 0;i < this.ship.getBombNum();i ++)
{
g.drawImage(this.bumbImage,this.getWidth()/4 + 25*i + 70, 0,this);
}
}
2.7 战舰装弹实现
/** 装弹保持炸弹数量大于零小于五 并每隔一定时间装弹*/
public void addBomb() {
int bn = this.ship.getBombNum();
if(bn >= 0 && bn < 5)
{
bn ++;
this.ship.setBombNum(bn);
}
if(bn < 0)
{
bn =0;
this.ship.setBombNum(bn);
}
if(bn >= 5)
{
bn = 5;
this.ship.setBombNum(bn);
}
}
2.8 面板绘制
主面板里面绘制的对象非常多连接游戏各个组件的枢纽。
/** 战舰*/
private WarShip ship;
/** 命数默认为3*/
private int liveNum= 3;
/** 通关数默认为0*/
private int pass= 0;
/** 最高分默认为0*/
private int higncore = 0;
/** 玩家得分 默认为0*/
private int score = 0;
/** 战舰装弹速度,默认为3000毫秒*/
private int delay = 3000;
/** 暂停标记*/
private boolean suspendFlag = false;
/** 判断游戏是否正在运行标记*/
private boolean isRunning = false;
/** 潜艇是否被击中标记*/
private boolean hitFlag = false;
/** 击中位置潜艇x坐标*/
private int hitX = 0;
/** 击中位置潜艇y坐标*/
private int hitY = 0;
/** 炸弹图片*/
private Image bumbImage;
/** 击中潜艇效果图片*/
private Image hitImage;
/** 暂停游戏所有线程用到的*/
public static final Object subLock = new Object();
/** 用来不停绘制玩家剩余可用炸弹的计时器*/
private Timer timer;
/** 用来不停重绘面板和判断移动元素位置的计时器*/
private Timer timer3;
/** 用来管理产生潜艇对象的线程*/
private TimeManager tm = null;
/** 炸弹对象集合*/
private ArrayList<Bumb> bumbArray = new ArrayList<Bumb>();
/** 潜水艇对象集合*/
private ArrayList<Submarine> submarineArray = new ArrayList<Submarine>();
/** 鱼雷对象集合*/
private ArrayList<Torpedo> torpedoArray = new ArrayList<Torpedo>();
/** 潜艇爆炸对象集合*/
private ArrayList<Hit> hitArray = new ArrayList<Hit>();
/** 战舰爆炸对象集合*/
private ArrayList<Blast> blastArray = new ArrayList<Blast>();
三.游戏场景创意相关设计
3.1 游戏关卡分数设计
具有挑战创意的分数关卡等你来战,每次多通过一关后,潜艇的速度会变快,就是绘制潜艇的速度会提高的,同时计时器的间隔会减低的。
/** 判断通关数量*/
public void addPass(int score)
{
if(score < 100)
{
this.pass = 0;
}
else if(score >= 100 && score < 200)
{
this.pass = 1;
this.tm.setSpeed(800);
timer.setDelay(2800);
}
else if(score >= 200 && score < 300)
{
this.pass = 2;
this.tm.setSpeed(600);
timer.setDelay(2500);
}
else if(score >= 300 && score < 500)
{
this.pass = 3;
this.tm.setSpeed(500);
timer.setDelay(2300);
}
else if(score >= 500 && score < 800)
{
this.pass = 4;
this.tm.setSpeed(400);
timer.setDelay(2000);
}
else if(score >= 800 && score < 1000)
{
this.pass = 5;
this.tm.setSpeed(300);
timer.setDelay(1800);
}
else if(score >= 1000 && score < 1500)
{
this.pass = 6;
this.tm.setSpeed(200);
timer.setDelay(1500);
}
else if(score >= 1500 && score < 2000)
{
this.pass = 7;
this.tm.setSpeed(100);
timer.setDelay(1200);
}
else if(score >= 2000)
{
this.pass = 8;
this.tm.setSpeed(50);
timer.setDelay(1000);
}
}
还算不错的完成了功能.
3.2 读取游戏最高分
设计通过后判断是否进入前十了的。
/** 得到玩家的最高分*/
public int getHigeScore(InputStream inputStream)
{
BufferedReader bf = null;
int tempNum = 0;
try
{
bf = new BufferedReader(new InputStreamReader(inputStream));
String temp = null;
while((temp = bf.readLine()) != null)
{
String[] info = temp.split(" ");
int num = Integer.valueOf(info[1]);
if(num > tempNum)
{
tempNum = num;
}
}
bf.close();
}
catch(IOException e)
{
e.printStackTrace();
}
finally
{
if(bf != null)
{
try
{
bf.close();
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
return tempNum;
}
游戏界面绘制时要读取出当前的最高分数的,从玩家的排行榜里面读取出来的userInfo/user.txt文件读取。
计算机网络 630 4 2018-06-09
归来 570 3 2018-06-08
时间刺客 480 2 2018-06-07
图像处理 410 2 2018-06-05
深入浅出 390 3 2018-06-04
游戏设计 330 2 2018-06-03
数据挖掘 270 2 2018-06-03
多媒体技术 260 2 2018-06-07
数据结构 250 2 2018-06-10
集合对象 190 1 2018-06-09
下面是一些关键的代码.按照分数的降序来完成的。
{
// 得到用户信息
String name = InputDialog.this.field.getText().trim();
String score = Integer.toString(InputDialog.this.panel.getScore());
String pass = Integer.toString(InputDialog.this.panel.getPass());
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = df.format(new Date());
String args[] = date.split(" ");
String time = args[0];
String user = name + " " + score + " " + pass + " " + time;
//将数据文件中的用户信息取出,去掉最后一名,加入玩家信息,并按降序排序
ArrayList<String> userList = new ArrayList<String>();
BufferedReader br = null;
BufferedWriter bw = null;
游戏结束执行isRecord方法来判断玩家是否进入前十来实现荣誉榜。
/** 判断玩家成绩是否进入前十名,是的话返回true*/
public boolean isRecord()
{
boolean isRecord = false;
InputStream inputStream=MyPanel.class.getClassLoader().getResourceAsStream("userInfo/user.txt");
BufferedReader bf = null;
int num = 0;
String temp = null;
四.游戏相关测试
4.1 功能测试
4.1.1 游戏测试
测试游戏结束中玩家3条命结束后游戏是否按照预期的就结束了的。正确的应该是弹出一个对话框提示重玩还是退出.错误的就是各种报错了的可以在控制台查看的。
这个测试的过程也同时测试了潜艇的正确运行,战舰的键盘事件,投弹爆炸,音效的测试.和一些功能按钮的测试的.
4.1.2 暂停游戏的测试
鼠标左键实现暂停游戏的功能正确的弹出串口的,查看控制台看是否报错的。
4.2 性能测试
4.2.1 测试战舰装弹的性能来实现
正确进入断点处执行,单步调试
4.2.2 测试战舰键盘事件是否向右移动正确
Debug运行单步调试
五.软件的运行的方式
直接运行
基于jar包的运行,我们使用项目构建工具Maven来快速打成jar包。
进入控制台
使用exe4J将jar打成了可执行文件.exe格式的
点击直接可以运行的。