韩顺平坦克大战项目0.3(发射子弹,多线程应用)

1.子弹发射原理分析

1.首先想到,子弹应该是一个单独的线程,因为发射后就和坦克的移动无关了,自己会一直朝前走。

2.子弹程序内部:(1)子弹要朝前走,所以他应该是一个循环一直改变位置的值,应该有发射方向,初始位置,发射速度这三个基本参数需要初始化。(2)子弹应该沿着初始方向一直匀速前进,直到出界才销毁,所以应该定义一个子弹位置x,y不断变化的循环。

3.子弹是某个坦克的子弹,所以他可以在坦克的类中使用并创建,包装在坦克类的方法里,调用这个方法即可发射一枚子弹(线程生成)。

4.很明显发射子弹需要按键触发,所以在画布的监听器里加一个触发器,如果按了某个键就调用某个坦克的发射子弹方法。

5.现在子弹其实已经发射了,只需要把子弹画出来就行了,由于子弹一直在动,所以我们要不停的重绘,才有动态效果,画布是一个单独的类,想要不断重绘就要把它也线程化,在run里写repaint,然后用while(true)包裹住。
(这里要注意,对于监听器来说,会自动监视passkey方法,对于new 画布来说,会自动调用paint方法,对于Runnable这个线程接口来说,会自动调用run方法,这三者不是谁包括谁的关系,而是并列的,都会自动调用。而且,重写方法不能再方法里,所以不可能包互相包括)

6.关于画子弹,就在paint里面直接画圆就行,坐标传的是坦克对象里的子弹对象的x和y,因为x,y会自动更新,然后画图线程会不断重绘,就会出来动态效果了。
(这里有个注意点的点,就是我们调坦克对象肯定没问题, 但是调坦克对象里的子弹对象,存在两个问:
1).作用域问题,要把子弹这个对象先在属性那定义出来,也就是

    Shot shot = null;

然后再再方法里new他

public void shoot(){
        shot = new Shot(x, y, direct);
            shot.start();
    }

这样做的目的是能够直接调用,不然作用域不够找不到shot这个子弹对象

2).由于一开始shou是null,所以画图的时候要先判断是否以及存在了shot对象,不然直接用的话肯定会报错(因为我们要用对象里面的x,y)

2.升级->连发

上述方法可以实现发射一枚子弹,但是如果再按一次,子弹会消失,重新发射。

我一开始以为原因是每次生成子弹的时候都是重新覆盖了,所以原来的子弹对象消失了,这其实是个很愚蠢且外行的误解。对象生成了他就存在那里不会覆盖,对象名就像指针一样,重新给他赋了对象他只不过指向新的对象了而已,原来的对象只不过失去了名字。更何况他作为一个线程,肯定还在自己运行的呀,举例:

for (int i = 0; i<2 ; i++) {
         cc cc = new cc();
         cc.start();
     }

像这个循环(与本题问题极为相似),他会生成两个线程,互不相干,也就是两个对象。

这么愚蠢的问题不应该画这么大的篇幅解释,不过作为编程成长路上的问题,遇到这种问题也应该认真对待。

所以回归正题
想要实现连发,首先先明白目前这种现象的原因是什么(现象是每次只能发一枚子弹,发第二枚,第一枚就会消失),很明显是因为再绘图那里,始终在画新的子弹,有了新的线程,新的对象,就会画新的,因为新的才叫shot…
绘画的语句如下:

 g.fillOval(hero.shot.x, hero.shot.y, 10, 10);

解决方法:
因为每次按下j就会创建新的子弹对象,所以在按键那里把新的子弹对象加到集合里就好,然后绘图的时候就不断循环遍历集合即可(注意集合要用Vector,线程安全),代码如下:

if(e.getKeyCode() == KeyEvent.VK_J){
    hero.shoot();	//这里shoot方法里会创建Shot的对象,赋给shot这个对象名字
    zidan.add(hero.shot);		//把新的shot加进去
}
if(!(zidan.isEmpty())){
   for (Shot s : zidan) {
      g.fillOval(s.x, s.y, 10, 10);	//这里zidan集合记得用泛型,不然还要向下转型
      }
}

下面附上完整代码:
DrawCircle类:

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Map;
import java.util.Vector;

public class DrawCircle extends JFrame {	//这是画板(框)(展示器)
    private MyPanel mp = null;
    public static void main(String[] args) {
        new DrawCircle();
    }
    public DrawCircle(){
        mp = new MyPanel();	//这是画布
        Thread thread = new Thread(mp);
        thread.start();
        this.add(mp);           //调的本对象的父类JFrame的方法     //在框架里加入画布
        this.addKeyListener(mp);
        this.setSize(1000,750);             //设置框的大小
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
    }
}

class MyPanel extends JPanel implements KeyListener,Runnable {          //画布,在这里画东西

    Hero hero = null;
    Vector<Enemy> enemies = new Vector<>(); //用一个集合存储enemy,用Vector的原因是可以多线程
    Vector<Shot> zidan = new Vector<>(); //用一个集合存储enemy,用Vector的原因是可以多线程
    int enemySize = 4;
    public MyPanel(){
        hero = new Hero(10,20,0);
        for (int i = 0; i<enemySize; i++){
            enemies.add(new Enemy(100*i,0,0));
        }
    }

    @Override
    public void paint(Graphics g) {  //g是画笔     //这个paint函数是自动调用的,只要new了MyPanel这个类就会调,相当于画画的工作台
        super.paint(g);     //要先调用父类初始化
        g.fillRect(0,0,1000,750);       //画一个和框架大小一样的和黑色背景
        drawTank(hero.getX(),hero.getY(),g,hero.getDirect(),0);//调用画坦克的方法
        if(!(zidan.isEmpty())){
            for (Shot s : zidan) {
                g.fillOval(s.x, s.y, 10, 10);
            }
        }

        for(Enemy e : enemies){
            drawTank(e.getX(),e.getY(),g,e.getDirect(),1);//调用画坦克的方法

        }
    }

    /*
    x,y是坦克的坐标
    g是画笔
    direct是坦克面朝的方向
    type是坦克的类型
    */
    public void drawTank(int x, int y , Graphics g, int direct, int type){

        //绘制不同类型的坦克
        switch (type){
            case 0: //我们的坦克
                g.setColor(Color.cyan);     //把g这个画笔设置成某种颜色
                break;
            case 1: //敌人的坦克
                g.setColor(Color.yellow);
                break;
        }

        //绘制不同方向的坦克
        switch (direct){
            case 0: //上
                g.fill3DRect(x,y,10,60,false);//画左轮子
                g.fill3DRect(x+30,y,10,60,false);//画右轮子
                g.fill3DRect(x+10,y+10,20,40,false);//画中间的
                g.fillOval(x+10,y+20,20,20);//画圆
                g.drawLine(x+20,y+30,x+20,y);//画炮
                break;
            case 1: //右
                g.fill3DRect(x,y,60,10,false);//画左轮子
                g.fill3DRect(x,y+30,60,10,false);//画右轮子
                g.fill3DRect(x+10,y+10,40,20,false);//画中间的
                g.fillOval(x+20,y+10,20,20);//画圆
                g.drawLine(x+30,y+20,x+60,y+20);//画炮
                break;
            case 2://下
                g.fill3DRect(x,y,10,60,false);//画左轮子
                g.fill3DRect(x+30,y,10,60,false);//画右轮子
                g.fill3DRect(x+10,y+10,20,40,false);//画中间的
                g.fillOval(x+10,y+20,20,20);//画圆
                g.drawLine(x+20,y+30,x+20,y+60);//画炮
                break;
            case 3: //左
                g.fill3DRect(x,y,60,10,false);//画左轮子
                g.fill3DRect(x,y+30,60,10,false);//画右轮子
                g.fill3DRect(x+10,y+10,40,20,false);//画中间的
                g.fillOval(x+20,y+10,20,20);//画圆
                g.drawLine(x+30,y+20,x,y+20);//画炮
                break;
            default:
                System.out.println("输错了");
                break;
        }

    }

    @Override
    public void keyTyped(KeyEvent e) {

    }

    @Override
    public void keyPressed(KeyEvent e) {
        switch (e.getKeyCode()){
            case KeyEvent.VK_DOWN:
                hero.setDirect(2);
                hero.setY(hero.getY()+3);
                break;
            case KeyEvent.VK_UP:
                hero.setDirect(0);
                hero.setY(hero.getY()-3);
                break;
            case KeyEvent.VK_LEFT:
                hero.setDirect(3);
                hero.setX(hero.getX()-3);
                break;
            case KeyEvent.VK_RIGHT:
                hero.setDirect(1);
                hero.setX(hero.getX()+3);
                break;
        }
        if(e.getKeyCode() == KeyEvent.VK_J){
            hero.shoot();
            zidan.add(hero.shot);
        }
        this.repaint();         //一直在监视键盘,有了就重绘
    }

    @Override
    public void keyReleased(KeyEvent e) {

    }

    @Override
    public void run() {

        while (true) this.repaint();

    }
}

Tank类:

import java.util.Vector;

public class Tank {
    private int x;
    private int y;
    private int direct;
    Shot shot = null;
    Vector<Shot> zidan = new Vector<>();

    public Tank(int x, int y, int direct) {
        this.x = x;
        this.y = y;
        this.direct = direct;
    }


    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getDirect() {
        return direct;
    }

    public void setDirect(int direct) {
        this.direct = direct;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public void shoot(){
        shot = new Shot(x, y, direct);
            shot.start();
    }
}

Hero类

public class Hero extends Tank{
    public Hero(int x, int y,int direct) {
        super(x, y, direct);
    }

}

Shot类

public class Shot extends Thread{
    int x,y;    //子弹当前的坐标
    int direct  = 0; //坦克当前的方向
    int speed  = 3; //子弹的飞行速度
    boolean isLive = true;

    public Shot(int x, int y, int direct) {
        this.x = x;
        this.y = y;
        this.direct = direct;
    }

    @Override
    public void run() {
        super.run();
        switch (direct){
            case 0:
                x = x+20-5;
                break;
            case 1:
                x = x+60;
                y = y+20-5;
                break;
            case 2:
                x = x+20-5;
                y = y+60;
                break;
            case 3:
                y = y+20-5;
                break;
        }
        while (x>0&& x<1000&&y>0&&y<750){
            switch (direct){
                case 0:
                    y-=3;
                    break;
                case 1:
                    x+=3;
                    break;
                case 2:
                    y+=3;
                    break;
                case 3:
                    x-=3;
                    break;
            }
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        isLive = false;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值