在坦克大战游戏(1.1版)基础上添加如下功能:当玩家按一下j键,就发射颗子弹。
个人思路,考虑创建bullet类,因为将来我们的坦克可以射出乱七八糟的子弹。定义大小,颜色,方向,速度,改变子弹位置应当在Bullet类内完成;
考虑在Hero内写shotBullet方法,该方法能调用生成新的子弹;
考虑在画板上使用监听者,当我们使用J键的时候就调用创建一个新的子弹对象,画子弹代码在MyPanel上完成;
考虑将画板变成线程,不停的repaint画板。
可能发射一堆子弹,子弹最好是做多线程避免堵塞;
1.创建一个Bullet类
1)该类应当有x,y,direct方向,speed速度,isLive是否存活,因为子弹会被发射,定义一个Shot内部类,当我们子弹被创建的时候意味着子弹要被射出去,所以构造器内创建一个shot对象;
2)在shot内部类内我们定义子弹的移动方向和路径,当direct为不同时 x y变化;
3)当我们的子弹碰到边界或者敌人时,该子弹对象应当被置为不用的空间,所以加上if判断是否消亡
4)子弹对象理应是多线程的对象,所以Shot类实现Runable接口,但其实Shot类可能有点多余了,因为一个Bullet好像也能实现该接口,也能定义乱七八糟的类型啥的,再看看,如果需要后面再进行更改
public class Bullet {
private int x;
private int y;
private int direct = 0;//子弹的方向
private double speed;
private boolean isLive = true;
Shot shot = null;
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getDirect() {
return direct;
}
public double getSpeed() {
return speed;
}
public boolean isLive() {
return isLive;
}
public Bullet(int x, int y, double speed, int direct) {
this.x = x;
this.y = y;
this.speed = speed;
shot = new Shot();
this.direct = direct;
}
public class Shot implements Runnable {
public Shot() {
}
@Override
public void run() {
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
switch (direct){
case 0:
y -= speed;
break;
case 1:
x += speed;
break;
case 2:
y += speed;
break;
case 3:
x -= speed;
break;
}
System.out.println("子弹坐标" + x + "子弹坐标" + y);
//如果子弹移动到面板边界时,应该销毁
if(!(x >= 0 && x <1600 && y>= 0 && y <= 900)){
isLive = false;
break;
}
}
}
}
}
2.给Hero类加入子弹功能
1)按正常来说,我们的Hero调用某个方法时,就应当创建一个子弹对象,所以定义shotBullet类,射出子弹,当执行hero.shotBullet时,在该方法内创建我们的bullet对象,根据我们的坦克位置,传入子弹的初始位置
2)因为子弹需要多个同时进行操作,所以实现Runnable来完成多线程,因为是Hero类创建的对象,所以在Hero内开启线程
public Bullet shotBullet(){
Bullet bullet = null;
switch (getDirect()){
case 0:
bullet = new Bullet(this.getX() + 18,this.getY() - 10,50,getDirect());
break;
case 1:
bullet = new Bullet(this.getX() + 60,this.getY() +18,50,getDirect());
break;
case 2:
bullet = new Bullet(this.getX() + 18,this.getY() +60,50,getDirect());
break;
case 3:
bullet = new Bullet(this.getX() - 10,this.getY()+18,50,getDirect());
break;
}
Bullet.Shot shot = bullet.new Shot();
Thread thread = new Thread(shot);
thread.start();
return bullet;
}
3.监听键盘输入事件调用hero.shotBullet
1)这个没啥好说的,MyPanel实现监听接口,当按下的键为J的时候执行方法
2)我们的方法返回的是Bullet对象,如果只在MyPanel内定义一个Bullet bullet,会导致每一次激活该监听都会让bullet指向该方法返回的对象,会导致我们的这个MyPanel类每次都只有一个子弹对象,这显然是不合理的,所以我们要创建一个线程安全的列表存入我们的子弹类
public void keyPressed(KeyEvent e) {
//如果按下的是J就调用hero的shotBullet方法
if(e.getKeyCode() == KeyEvent.VK_J){
bullets.add(hero.shotBullet());
}
this.repaint();
}
4.创建绘画方法
1)在该类内创建一个方法drawBullet,该方法可以绘出一个子弹
2)填充为圆形,颜色为青色
public void drawBullet(Graphics g,Bullet bullet) {
g.setColor(Color.CYAN);
g.fillOval(bullet.getX(), bullet.getY(), 5, 5);
}
5.调用绘画方法
1)我们已经创建了绘画方法,应当在paint内调用该方法
2)调用方法的时候我们已经接受了该子弹对象存入了列表内,然后我们再在paint内取出该对象,把他画出来
3)这时候问题又来了,我们的列表存入了该bullet对象,如果我们一直发射,会出现内存溢出的情况,为了避免该情况,我们加一个存活判断(并不是真的不存活了,如果我们的列表一直执行该对象,会让他一直被调用,这种情况回收机制不会回收该内存空间),如果islive为false,我们就丢到另外一个列表里,然后bullets.removeAll(unliveBullets)即可。这种方式当然不是最好的,看看有没有更好的解决方法再进行改进;
4)进行判断,当我们的bullet取出来是个null或者已经死掉了,我们就不画他
5)该列表是有序的,所以我们可能会有很多不必要的东西占用该列表的空间大小,想想怎么解决
Vector<Bullet> bullets = new Vector<>();
public void paint(Graphics g) {
//有个问题 这里的bullet只会接受一个对象,所以想想怎么保证绘出多个bullet
//可以用ArrayList接受,但是怎么销毁为空的对象呢?
Vector<Bullet> unliveBullets = new Vector<>();
bullets.removeAll(unliveBullets);
for (int i = 0; i < bullets.size(); i++) {
Bullet bullet = bullets.get(i);
if(!bullet.isLive()){
unliveBullets.add(bullet);
}
if(bullet != null && bullet.isLive()){
drawBullet(g,bullet);
}
}
unliveBullets.clear();
}
6.画板设置为多线程
也可以在paint里调用repaint方法,即下图
应该没区别,我们在上面已经处理过消亡的子弹了,这样调用每次有子弹存活就会repaint一次,每按一次也会repaint一次,没差别应该,有的话可能是线程的安全问题吧,太多地方调用repaint了,所以干脆直接让画板进入多线程得了
public class MyPanel extends JPanel implements KeyListener,Runnable{
@Override
public void run() {
while (true){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.repaint();
}
}
}
7.结束,完成hero坦克发射子弹的功能
最后记得在主程序内调用画板线程
效果图,可以再调调,但大体就是这样了