Java实现基础坦克⼤战(含源码)
1. 成果演⽰
2. 实现了哪些功能?
1. 按键WDSA是⼰⽅坦克⽅向键,J键负责发射⼦弹。⼰⽅⼀次最多可以发射5颗⼦弹。⼦弹⾛到边界或者击中敌⽅坦克时,⽣命周期结
束。
2. 当我⽅⼦弹击中敌⽅或者敌⼈⼦弹打中我⽅时,会有爆炸效果
3. 敌⼈坦克随机移动,随机发射⼦弹。坦克与坦克的位置不能重合,且不能移动到边界之外
4. 我⽅击杀敌⼈数量动态更新在界⾯右上⾓
5. 游戏关闭后默认存档,保存了关闭前敌⽅坦克的位置、⽅向以及我⽅击杀的坦克数。选择继续游戏的话,会还原场景,不过当时发射
的⼦弹会被清屏
6. 关闭游戏时,若我⽅坦克的阵亡情况不会被存档,也就是说当游戏再次运⾏时,我⽅坦克会恢复⽣命。
7. 开启游戏时会播放事先准备好的⾳乐
3. 需要哪些基础?
线程、向上/下转型、⼦类⽗类、事件监听、I/O流
4. 设计思路
设计思路配合源码⾷⽤!!
①绘制坦克
绘图基础
Java的图形化设计以JFrame类为框架【可以理解为画板】,JPanel类为⾯板【理解为画纸】。
X-Y坐标轴以左上⾓为原点,右边是X轴正向;Y轴下⽅是正向
JPanel上的画图由paint(Graphics g)⽅法实现,其中**g可以理解为画笔**。Graphics是⼀个接⼝,在JPanel中实现了初始化,我们重写的时候收到的实际是⼀个上转型对象,所以可以直接调⽤⽅法使⽤。【简单来说就是JDK已经帮忙把活⼲好了,直接拿来⽤就⾏】
以下情况会⾃动调⽤paint()⽅法
1. 窗⼝最⼩化再最⼤化
2. 窗⼝⼤⼩发⽣变化
3. 调⽤repaint函数【刷新组件外观⽅法】被调⽤
使⽤画笔进⾏画图时经常需要标明坐标位置以及图像的⾼宽,⾼宽是在坐标位置基础上计算的,坐标位置如箭头所⽰
坦克绘制
不难发现,这辆坦克是由⼀个长⽅形1号+长⽅形2号+长⽅形1号构成的主体
炮台部分是⼀个圆形,炮筒部分则是⼀条直线
各位根据⾃⼰的审美喜好,进⾏绘画拼接即可。我个坦克的整体宽度是40像素,长度是60像素
//提供⼏个画图⽅法,其中g是画笔(paint⽅法中传⼊的参数),具体使⽤参考JDK⽂档
g.fill3DRect  //画具有3D效果的矩形
g.fillOval //画椭圆(长轴与短轴相同时就是圆)
g.drawLine //画直线
g.fillRect //⽤画笔颜⾊填充矩形,可以⽤来画⾯板
经过这些步骤,相信各位已经可以完成坦克以及⾯板的绘制了~
②我⽅坦克动起来
其实原理很简单,每个坦克都有⾃⼰的坐标【因此将坦克封装成⼀个对象,拥有⾃⼰属性】。⽽我们对⼰⽅坦克进⾏键盘事件监听,当按下⽅向键的时,就改变⾃⼰的坐标,接着马上调⽤repaint()⽅法。这个⽅法的作⽤的调⽤paint()⽅法,即重新绘图。⼜因为我们的坐标改变了,所以画出来的就是⼀幅新的图⽚。
同时我们知道,⽅向不同时,构成坦克的各图形坐标位置会有所变化(⼤⼩不变)。因此绘图时还要根据⽅向进⾏调节。建议将画根据⽅向画坦克封装成⼀个drawTank⽅法,后边只需要传⼊坐标和⽅向即可完成绘制
CPU的运⾏速度很快,当⼀直按着⽅向键的时,就会绘制⼀张张不同的新图⽚,连起来看就像动起来了⼀样。
③我⽅发射⼦弹
⼦弹的本质是什么?
根据常识,⼦弹发射出去后移动与原坦克没关系了,看作是独⽴个体,因此要单独封装成⼀个类,享有⾃⼰的属性,同时⼦弹的移动也体现在坐标的变化上。
因此想要让⼦弹有规律的动起来,⼀个想法是让⼦弹成为⼀个线程。按下发射键时,⼦弹线程就启动,并且有规律的改变⾃⼰的坐标。当⼦弹坐标与敌⽅坦克重合或者⾛到边界时,⽣命周期结束。
⼦弹的移动要满⾜什么规律?
当我⽅坦克向上,发射的⼦弹⾃然也向上……因此⼦弹坐标的改变需要根据发射时坦克的⽅向进⾏确定,所以像前边举的例⼦,⼦弹向上⾛,只需要改变Y轴即可
如何控制最多发射5颗⼦弹?
⼦弹虽然⾃⼰是⼀个个体,但是实际上还是和原来的坦克有关系的【不然就不知道是谁发射的⼦弹】,因此我们可以⽤⼀个Vector来装当前发射出去的⼦弹,每发射⼀颗⼦弹就将其加⼊Vector,当数量达到5时不允许再发射;当⼦弹⽣命周期结束时就将其从Vector 移除
④我⽅⼦弹击中敌⼈坦克
此时敌⽅坦克应该消失,我们发出的⼦弹也应该消失
所有敌⼈坦克可以放在⼀个Vector中,每次调⽤paint()⽅法时,遍历敌⼈坦克集合,接着在这个过程中再遍历我⽅发射出的⼦弹,将这些⼦弹的坐标与当前遍历到的坦克坐标进⾏对⽐,如果⼦弹进⼊了坦克范围就代表击中了坦克。我们将被击中的坦克移出坦克集合同时将这颗⼦弹移出⼰⽅⼦弹集合
⑤我⽅⼦弹击中敌⽅产⽣爆炸效果
爆炸效果如何做?
其实就是提前准备好⼏张图⽚,当⼦弹击中坦克时,在击中的位置绘制这⼏张图⽚,从⽽达到爆炸的效果
每个爆炸有⾃⼰的⽣命值,如⽣命值为3绘制第⼀张图⽚;⽣命值为2绘制第⼆张图⽚……从⽽达到动态效果
由于爆炸的效果与⼦弹移动、坦克移动等都是互不⼲扰的,且其是⼀个动态变化过程,所以爆炸也要单独封装成⼀个类,享有⾃⼰的属性
//得到图⽚对象
Image image1 = DefaultToolkit().getImage(Resource("/相对路径"));
g.drawImage(image1, X, Y,宽,⾼,this);//画图⽅法
爆炸实现具体思路
爆炸可以多处⼀起发⽣,因此我们也将爆炸对象Bomb存放到Vector中。每次绘图的时候对这个Vector进⾏遍历,得到其中的Bomb 对象根据不同⽣命值进⾏爆炸效果绘制。当其⽣命值为0时将其移除,移除后⾃然不会再绘制。
⑥敌⽅坦克⾃动移动
所谓⾃动移动,其实就是定时改个⽅向和坐标,接着再对页⾯进⾏刷新即可。由此我们不难想到将敌⼈坦克设置为线程,隔⼀段时间就随机设置⼀个⽅向以及这个⽅向上正向坐标。
但是重绘怎么要怎么触发呢?毕竟只有在JPanel类以及⼦类才会有这个⽅法。
解决办法是将整个⾯板也设置为⼀个线程,隔⼀段时间就进⾏重绘
⑦敌⽅坦克⾃动发射⼦弹
隔⼀段时间就得到⼀个随机数,如果随机数⼤于某个值就允许其产⽣⼀个⼦弹对象【这是为了控制⼦弹发射速率】,⼦弹线程启动,并且将⼦弹放⼊⾃⼰的⼦弹集合中
⑧如何防⽌敌我坦克重叠?
我的作法是在setX以及setY时对新位置进⾏⼀个判断,遍历当前⾯板上存在的所有坦克,如果我的新位置没有进⼊其他坦克的范围,才被认为是合法的
原理⽐较简单,但是实现起来需要⼀些耐⼼,具体可以参考韩⽼师给的图↑
⑨如何存档
我利⽤的⽅法是将需要记录的数据通通放到⼀个Recorder类中【⽐如当前敌⼈坦克集合、我击杀的敌⽅坦克数量】,当关闭窗⼝事件发⽣时,就将这个类写⼊⽂件中。
当选择继续游戏时,输⼊流会从⽂件中读取这个类,并将其中的信息进⾏对应赋值。
⑩如何添加背景⾳乐?
利⽤JDK⾃带的⾳乐播放⼯具进⾏播放即可,⼤致也是I/O流原理,参考以下代码
AutoMusic.java
package TankGame1;
import*;
import File;
import IOException;
public class AutoMusic extends Thread {
String path;
public AutoMusic(String path){
this.path = path;
}
@Override
public void run(){
File file =new File(path);
AudioInputStream audioInputStream =null;
try{
audioInputStream = AudioInputStream(file);
}catch(Exception e){
e.printStackTrace();
return;
}
AudioFormat format = Format();
SourceDataLine auline =null;
DataLine.Info info =new DataLine.Info(SourceDataLine.class, format);
try{
auline =(SourceDataLine) Line(info);
auline.open(format);
}catch(Exception e){
e.printStackTrace();
}
auline.start();
int nBytesRead =0;
//这是缓冲
byte[] abData =new byte[512];
try{
while(nBytesRead !=-1){
nBytesRead = ad(abData,0, abData.length);
if(nBytesRead >=0){
auline.write(abData,0, nBytesRead);
}
}
}catch(IOException e){
e.printStackTrace();
}finally{
auline.drain();
auline.close();
简单的java游戏代码}
}
}
在⾯板构造函数处将此线程启动即可播放事先准备好的⾳乐
5. 源码
1.MyFrame.java(主函数所在)
package TankGame1;
import*;
import FontUIResource;
import*;
import WindowAdapter;
import WindowEvent;
import WindowEvent;
import FileOutputStream;
import IOException;
import ObjectOutputStream;
import Vector;
/**
* ⾯板启动
*/
public class MyFrame extends JFrame {
private MyPanel myPanel;
private ObjectOutputStream oos;
public static void main(String[] args)throws Exception {
new MyFrame();
}
public MyFrame()throws Exception {
int i=modelChoose();
if(i==-1){
}
myPanel =new MyPanel(i);
new Thread(myPanel).start();
this.setSize(1300,820);
this.add(myPanel);
this.addKeyListener(myPanel);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
/
/监听关闭窗⼝事件
this.addWindowListener(new WindowAdapter(){
@Override
public void windowClosing(WindowEvent e){
try{
Vector<EnemyTank> tanks = EnemyTanks();
for(int i =0; i < tanks.size(); i++){
<(i).getShots().clear();
}
//对象输出流
oos =new ObjectOutputStream(new FileOutputStream("src\\"));
/
/将记录对象Recorder写到⽂件
oos.Recorder());
}catch(Exception e1){
e1.printStackTrace();
}finally{
if(oos !=null){
try{
oos.close();
}catch(IOException ioException){
ioException.printStackTrace();
}
}
}
}
});
}
public int modelChoose(){
// 设置按钮字体⼤⼩
UIManager.put("OptionPane.buttonFont",new FontUIResource(new Font("楷体", Font.BOLD,25)));
// 设置⽂本字体⼤⼩
UIManager.put("ssageFont",new FontUIResource(new Font("楷体", Font.BOLD,30)));        Object[] options ={"新游戏","继续上局游戏"};
int i = JOptionPane.showOptionDialog(null,"请选择开局⽅式?","选择", JOptionPane.YES_NO_OPTION,                JOptionPane.QUESTION_MESSAGE,null, options,null);
return i;
}