线程项目——坦克大战总结
一.预计游戏实现功能
1)两方坦克,电脑控制敌方坦克,由线程控制。玩家控制自己的坦克,通过添加键盘监听器实现。
2)坦克发射子弹,并能判断敌我,判断输赢。通过建立子弹线程实现。
3)坦克之间不能互撞。
3)添加背景,有普通强(普通子弹课打破),白色墙(普通子弹无法打破),水(子弹可通过,坦克不能),草(子弹,坦克均能通过)。
4)实现子弹,坦克碰到不同障碍物的不同执行方法。
5)优化界面,添加音乐,判断输赢,实现重玩,升级等其他功能。
二.碰到的技术问题
其实,整个过程我们碰到了无数大大小小,奇奇怪怪的问题,以下只罗列出几个关键性问题供大家参考。
1)两方坦克,两方子弹图片,走法均不同,如何设计既能减少代码,又能实现该功能。 虽说玩家自己的坦克不是线程,但和其他坦克只是图片不同,创建一个坦克类,写一个设置名字的方法,在创建坦克对象时,区别敌我。此类中,写坦克行走,转向,碰到其他坦克和障碍物的方法。再单独写一个坦克线程类,利用人机算法,在run方法中,操作坦克运动。
大致如下:
坦克类
public class Tank {
//构造器,传入初始坦克的位置
public Tank(Graphics g, int x, int y) {}
//设置不同方向坦克的图片对象
public void setImage() {}
// 坦克前进的方法
public int forward() {}
//坦克攻击的方法
public void attack() {}
//得到坦克运动时方向的方法
public String getDirection() {}
//判断坦克是否和其他会坦克相撞
public int isSeparate() {}
//设置坦克名字的方法
public void setName(String name) {}
}
坦克线程类
public class TankRunnable implements Runnable{
//构造器,传入坦克对象
public TankRunnable(Tank tank, Graphics g) {}
//重写run方法
public void run() {}
}
而子弹类则直接写成一个线程类,重写run方法时直接调用发射的方法。此类中,也有诸如根据坦克名字设置图片,判断敌我的方法。
大致如下:
子弹线程类
public class Bullet extends Thread{
//构造器,传入此时要发射坦克的坐标和坦克对象
public Bullet(Graphics g, int x, int y, String direction, String name,
Tank tank) {}
//发射子弹的方法
public void shot() {}
//判断是否撞到墙
private void isHitWall() {}
//设置子弹图片的方法
public void setImage() {}
//判断是否打到坦克
public void isHitTank() {}
//重写run方法
public void run() { shot();}
}
此外,判断子弹打中坦克方法中,要把子弹和对方坦克一一进行判断,此时,可以用集合分别存储这些坦克对象,写在一个借口里,让子弹类实现接口即可。
2)墙的画法
由于墙被子弹打到后,要爆炸消失,所有墙肯定不能用一张背景图代替,必须一块一块的画在画布上。在这个问题上,我们先用坐标循环画出墙,并把每个墙的对象都存入一个集合里,而此时却出现了很大的一个问题,子弹线程中,有判断子弹是否打到墙的方法,我们是让子弹与画面上所有墙进行比较判断,这无疑加大了子弹的压力。画面上一次会出现多个子弹,而每颗子弹没走一步就要执行这么多次,主程序就出现了最讨厌的问题——卡!
最后,我们决定像五子棋那样,用一个二维数组存储这些墙,子弹判断时只需要根据坐标判断与之相邻的一块砖即可,大大减少代码的执行。
3)边界
这个问题,我们暂时还没有解决。
由于在坦克类中,我们写了一个碰到边界即反向的方法,而此方法却不适合玩家自己的坦克,进而出现了玩家到边界自动转向,图片却没有转换过来,且还能继续走,甚至出边界。
4)卡
这本属于项目优化,我们写的子弹线程中太多方法,子弹每走一步,要判断执行很多代码,必然导致运行速度慢,甚至卡,死机等问题。
对此,我们先是用数组存储墙缓解这个问题,也进一步限制子弹颗数,即一颗子弹要么出边界,要么打到墙或坦克,才能发射另一颗子弹。
5)人机算法
若是智能的算法,电脑的坦克应该追随玩家的坦克,并在合适时机发射子弹,像五子棋一样,既要有防守,也要有进攻。而我们写的算法很简单,坦克其实在无序的行走,这个将慢慢完善。
三.几个重点技术实现方法
1)坦克运动方向和图片的匹配
public int forward() {
// 坦克初始位置
g.drawImage(image, x, y, High, Wide, null);
// 设置一个黑色矩形
g.setColor(Color.BLACK);
try {
// 如果是自己的坦克,线程不休眠,阻止键盘延迟
if (name.equals("Mytank")) {
sleep = 0;
}
Thread.sleep(sleep);// 线程休眠
g.fillRect(x, y, High, Wide);// 覆盖原先坦克的图片
// 判断方向,改变相应坐标值
if (direction.equals("Right")) {
x += speed;
setDirection("Right");
}
if (direction.equals("Left")) {
x -= speed;
setDirection("Left");
}
if (direction.equals("Up")) {
y -= speed;
setDirection("Up");
}
if (direction.equals("Down")) {
y += speed;
setDirection("Down");
}
// 画出新位置,走一步
g.drawImage(image, x, y, High, Wide, null);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return 0;
}
2)坦克,子弹行走的动态效果
上述代码已有介绍,即根据起始坐标先画出图,用黑色矩形框盖住原先图片,再画出新位置的图。这和画布重绘其实是一样的。
当然也可以在重绘方法中实现。
此外,重要的是要利用双缓冲清楚闪屏的情况。
3)判断子弹或坦克是否碰到障碍物
这是一个技术难点,即使得到两者坐标,又如何判断。
此刻,我们利用Rectangle中的intersect(Rectangle R)方法判断两者是否相撞。
即,根据两者坐标创建两个矩形对象,用上述方法即可。
4)声效
File file = new File("music\\fire.wav");
URL rul;
try {
rul = file.toURL();
AudioClip ac = Applet.newAudioClip(rul);
ac.play();
} catch (MalformedURLException e) {
e.printStackTrace();
}
四.自我体会
这是第一个我们做的比较复杂的项目,加之我们选的题目复杂程度也高,中间碰到了很多问题,刚开始还比较积极,但后来些许有些烦躁。好在我们都坚持下来了,这里,我最想说的是以下几点;
1)团队合作很重要
刚开始,想要一个人独立完成一个项目的确有点困难,而团队合作不仅在节省了时间,也保证了项目的质量性。三个人有三种想法,只要交流的好,面对问题,总能找出比较好的解决办法。当然,在找错时也很有帮助。
2)做项目要有全局思维
一个完整的项目前前后后的联系是很紧密的,在初始做时,要对整个游戏,每个步骤有宏观的把握,即要想到后面的步骤。不然,很容易出现前面一直解决问题,最后发现思路完全错,最大的致命性问题无法解决。
3)遇到问题要积极及时与他人交流