《Java小游戏实现》:坦克大战(续三)

Posted HelloWorld_EE

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Java小游戏实现》:坦克大战(续三)相关的知识,希望对你有一定的参考价值。

《Java小游戏实现》:坦克大战(续三)

相关博文:

《Java小游戏实现》:坦克大战http://blog.csdn.net/u010412719/article/details/51712663

《Java小游戏实现》:坦克大战(续一):http://blog.csdn.net/u010412719/article/details/51723570

《Java小游戏实现》:坦克大战(续二):http://blog.csdn.net/u010412719/article/details/51729655

博文《Java小游戏实现》:坦克大战(续二)中已经实现到了坦克可以发射子弹了并可以击中敌方的坦克了。这篇博文在此基础上继续实现更多的功能。

完成功能:添加爆炸效果

在上一个版本中,我们击中敌方的坦克,敌方的坦克只是消失了,没有产生类似于我们熟悉的爆炸的效果。下面我们添加这一爆炸的效果。

在一些具有爆炸效果的游戏中,我们所看到的爆炸效果就是类似于上面一些图片的顺序显示。这里我们就来下面画圆这种方式模拟这一功能。

首先,我们创建一个爆炸类Explode.

爆炸类中,会有如下属性和方法

1、位置信息x,y;

2、标识是否存活的属性

3、产生爆炸的图片数组(这里用直径画图来来代替)

4、draw方法

代码如下:

    public class Explode {
        //爆炸所在的位置
        private int x;
        private int y;

        //爆炸图片的直径
        private int[] diameter={6,20,40,60,20,7};

        //标识爆炸对象是否存活的属性
        private boolean live =true;

        public boolean isLive() {
            return live;
        }

        //标识爆炸显示到第几步了
        private int step = 0;

        private TankClient tc;

        public Explode(int x ,int y , TankClient tc){
            this.x = x;
            this.y = y;
            this.tc = tc;
        }

        /*
         * 让爆炸显示出来
         * */
        public void draw(Graphics g){
            if(!live) return;
            //判断显示到第几步了,如果全部显示完了,则此对象已死,返回
            if(step>=diameter.length){
                live = false;
                step = 0;
                return;
            }
            Color c = g.getColor();
            g.setColor(Color.YELLOW);
            g.fillOval(x, y, diameter[step], diameter[step]);
            g.setColor(c);
            step++;
        }


    }

在我们写好这个爆炸类之后,我们可以在TankClient类中测试下,测试需要添加的代码如下:

    private Explode explode = new Explode(200,200,this);
    @Override
    public void paint(Graphics g) {
        //炸弹对象
        explode.draw(g);
    }

测试发现,确实看到了类似于爆炸的效果。

完成功能:当子弹击中坦克时添加爆炸效果

当子弹击中坦克时添加爆炸效果;从这句话可以看出,我们在击中一个坦克时,需要添加一个爆炸对象。因此按照下面两步来完成这一功能。

1、爆炸应该存在于集合类中。

与子弹类似,在TankClient类中加入集合
将集合中的爆炸逐一画出(如果死去就去除)

在TankClient类中添加代码如下:

    /*
     * 为每个被击中的坦克添加一个爆炸对象
     * */
    private List<Explode> explodes = new ArrayList<Explode>(); 

    @Override
    public void paint(Graphics g) {
        //炸弹对象
        for(int i=0;i<explodes.size();i++){
            Explode e = explodes.get(i);
            if(!e.isLive()){
                explodes.remove(e);
            }
            else{
                e.draw(g);
            }

        }

    }

2、子弹击中一个坦克后应产生爆炸对象,添加到TankClient的爆炸集合中。

在Missile类中hitTank方法中添加相关代码

Missile类中hitTank方法的代码如下:

    public boolean hitTank(Tank t){
        //先判断该坦克是否还是存活,如果已经死了,子弹就不打他了
        if(!t.isLive()){
            return false;
        }
        if(this.getRect().intersects(t.getRect())){//判断是否有碰撞
            //碰撞之后,子弹和该坦克就应该都死了
            this.live = false;//子弹死了
            t.setLive(false);//坦克死了
            Explode e = new Explode(x,y,tc);
            tc.getExplodes().add(e);
            return true;
        }
        else{
            return false;
        }
    }

以上就实现了当一颗子弹击中坦克时会产生爆炸效果。

完成的功能:添加多辆敌方的坦克

上一个版本中有一个我方的坦克,有一个不会动且不会发子弹的敌方的坦克,我们一打就死了,没意思,因此,我们可以通过按键A我们来在界面中添加多辆敌方坦克,也可以随机产生坦克。

在我们实际的游戏中,坦克一般是随机产生,当我们消灭掉后又会产生一批,就是这样一个过程。

实现过程如下:

1、在TankClient.java中声明一个List enemyTanks对象,用来存放一定数量的敌方坦克对象。

2、写如下一个函数,用来随机产生一定数量的随机位置的坦克。

    /*
     * 函数功能:产生敌方坦克
     * */
    public void produceTank(){

        int totalNum =r.nextInt(4)+3 ;

        for(int i=0;i<totalNum;i++){
            //位置也随机
            int x = (r.nextInt(10)+1)*40;
            int y = (r.nextInt(10)+1)*30;
            Tank enemy = new Tank(x,y,false,this);
            enemyTanks.add(enemy);
        }
    }

3、在TankClient.java中的draw函数中画出来即可。

    @Override
    public void paint(Graphics g) {

        //直接调用坦克类的draw方法
        tk.draw(g); 

        /*
         * 将敌方坦克也画出来,如果没有了敌方坦克,则产生一定数量的地方坦克
         * */
        if(enemyTanks.size()==0){
            this.produceTank();
        }
        for(int i=0;i<enemyTanks.size();i++){
            Tank enemy = enemyTanks.get(i);
            if(!enemy.isLive()){
                enemyTanks.remove(enemy);
            }
            else{
                enemy.draw(g);
            }

        }

        //炸弹对象
        for(int i=0;i<explodes.size();i++){
            Explode e = explodes.get(i);
            if(!e.isLive()){
                explodes.remove(e);
            }
            else{
                e.draw(g);
            }

        }
        //画子弹
        for(int i=0;i<missiles.size();i++){
            Missile ms = missiles.get(i);           
            //判断子弹是否还存活在,如果不是存活的,则移除
            if(!ms.isLive()){
                missiles.remove(ms);
            }
            else{
                ms.hitTanks(enemyTanks);
                ms.draw(g);
            }

        }
    }

4、由于此时敌方的坦克有多个,因此在Missile中,添加了一个hitTanks(List tanks)方法,用来打一系列的坦克。在TankClient里面每发子弹都打tanks。

    public boolean hitTank(Tank t){
        //先判断该坦克是否还是存活,如果已经死了,子弹就不打他了
        if(!t.isLive()){
            return false;
        }
        if(this.getRect().intersects(t.getRect())){//判断是否有碰撞
            //碰撞之后,子弹和该坦克就应该都死了
            this.live = false;//子弹死了
            t.setLive(false);//坦克死了
            Explode e = new Explode(x,y,tc);
            tc.getExplodes().add(e);
            return true;
        }
        else{
            return false;
        }
    }
    /*
     * 一颗子弹打List中的坦克
     * */
    public boolean hitTanks(List<Tank> tanks){
        for(int i=0;i<tanks.size();i++){
            if(hitTank(tanks.get(i))){
                return true;
            }
        }
        return false;
    }

完成功能:让敌方的坦克智能化一点(动起来)

上一个版本只能产生一定随机数量的坦克,但是不能动,这个版本就让其随机动起来。

改动如下:

1、在Tank类中,添加一个如下的构造函数

        public Tank(int x, int y,boolean good,Direction dir, TankClient tc) {
            this(x,y,good);
            this.dir = dir;
            this.tc = tc;
        }

2、然后在TankClient类中的produceTank方法中使用上面的构造函数来new 每一个敌方坦克对象

    /*
     * 函数功能:产生敌方坦克
     * */
    public void produceTank(){

        int totalNum =r.nextInt(4)+3 ;

        for(int i=0;i<totalNum;i++){
            //位置也随机
            int x = (r.nextInt(10)+1)*40;
            int y = (r.nextInt(10)+1)*30;

            Direction[] dirs =Direction.values();
            int rn = r.nextInt(dirs.length);
            Direction dir = dirs[rn];
            Tank enemy = new Tank(x,y,false,dir,this);
            enemyTanks.add(enemy);
        }
    }

通过1、2两步敌方坦克就可以动起来了,但是这还不够好,因为他会一直朝着他刚初始化的方向一直运动下去。

为了解决这个问题。在Tank类中的draw方法中,我们设置一个运动次数step,当一个敌方坦克运动次数达到step之后,我们就随机给他换一个方向运动。

基于上面的思想,Tank类中的draw方法的代码如下:

        //坦克没走step步,随机换一个方向
        private Random r = new Random();
        private int step = r.nextInt(7)+3;


        public void draw(Graphics g){
            if(!live){      //判断坦克是否存活,如果死了,则不绘画出来,直接返回     
                return ;
            }
            if(!this.good){
                if(step==0){
                    Direction[] dirs =Direction.values();
                    int rn = r.nextInt(dirs.length);
                    this.dir = dirs[rn];
                    step = r.nextInt(7)+3;
                }
            }

            Color c = g.getColor();
            if(good){
                g.setColor(Color.RED);
            }
            else{
                g.setColor(Color.BLUE);
            }

            g.fillOval(x, y, WIDTH, HEIGHT);
            g.setColor(c);
            //画一个炮筒
            drawGunBarrel(g);

            move();//根据键盘按键的结果改变坦克所在的位置

            step--;
        }

以上就实现了多个敌方坦克的随机运动。

完成功能:敌方坦克发射子弹

这个版本就为敌方坦克添加发射子弹的功能。

既然我方坦克也要发射子弹,敌方坦克也要发射子弹,子弹与子弹之间就要有所区分。因此,为子弹添加一个good属性来标识子弹的好与坏。

具体步骤如下:

1、在Missile类中添加good属性,并添加一个如下的构造方法

    private boolean good =true;

    public Missile(int x,int y,Direction dir,boolean good,TankClient tc){
        this(x,y,dir);
        this.good = good;
        this.tc = tc;

    }

2、在Tank类中产生子弹的fire()方法中,使用上面的构造函数来进行构造子弹Missile ms = new Missile(x,y,this.ptDir,this.good,this.tc);this.good指的是坦克的好坏,就坦克是好的,子弹对象就是好的,否则子弹对象就是坏的,即根据坦克的好坏来产生子弹的好坏对象

Tank类中fire方法的具体代码如下:

        public Missile fire(){
            //计算子弹的位置,并利用炮筒的方向来new一个子弹对象
            int x = this.x +(this.WIDTH)/2 - (Missile.WIDTH)/2;
            int y = this.y + (this.HEIGHT)/2 -(Missile.HEIGHT)/2;
            //根据坦克的类型(good)来new与之对应的子弹类型
            Missile ms = new Missile(x,y,this.ptDir,this.good,this.tc);
            return ms;
        }

3、更改以上两点,则在Tank类中draw方法中为坏坦克添加发射子弹这一功能。

        public void draw(Graphics g){
            if(!live){      //判断坦克是否存活,如果死了,则不绘画出来,直接返回     
                return ;
            }
            //为坏坦克添加随机的方向
            if(!this.good){
                if(step==0){
                    Direction[] dirs =Direction.values();
                    int rn = r.nextInt(dirs.length);
                    this.dir = dirs[rn];
                    step = r.nextInt(7)+3;
                }
            }
            //根据坦克的好坏来设置不同的颜色
            Color c = g.getColor();
            if(good){
                g.setColor(Color.RED);
            }
            else{
                g.setColor(Color.BLUE);
            }

            g.fillOval(x, y, WIDTH, HEIGHT);
            g.setColor(c);
            //画一个炮筒
            drawGunBarrel(g);

            move();//根据键盘按键的结果改变坦克所在的位置

            step--;

            //敌方子弹开火
            if(!this.good&&r.nextInt(40)>38){
                this.tc.getMissiles().add(fire());
            }

        }

4、此时,经过上面的三步之后,我方和敌方都能够发射子弹了,但是敌方能够打死敌方的坦克。因此,还需要对Missile的hitTank(Tank t)方法进行修正。

即在碰撞检测条件中添加这一条:this.good!=t.isGood(),即只有子弹和坦克不是同一类型的,才能打死对方。

具体完整代码如下:

    public boolean hitTank(Tank t){
        //先判断该坦克是否还是存活,如果已经死了,子弹就不打他了
        if(!t.isLive()){
            return false;
        }
        if(this.live&&this.good!=t.isGood()&&this.getRect().intersects(t.getRect())){//判断是否有碰撞
            //碰撞之后,子弹和该坦克就应该都死了
            this.live = false;//子弹死了
            t.setLive(false);//坦克死了
            Explode e = new Explode(x,y,tc);
            tc.getExplodes().add(e);
            return true;
        }
        else{
            return false;
        }
    }

5、在Missile的draw方法中,根据子弹的好坏给出不同的颜色。这里采用好子弹采用红色,坏子弹采用蓝色进行区分。

    public void draw(Graphics g){
        //如果该子弹不是存活的,则不进行绘图
        if(!live){
            return ;
        }
        Color c = g.getColor();
        //根据子弹的好坏来设置不同的颜色
        if(this.good){
            g.setColor(Color.RED);
        }
        else{
            g.setColor(Color.BLUE);
        }

        g.fillOval(x, y, WIDTH, HEIGHT);
        g.setColor(c);
        move();
    }

6、现在差不多就算完成了,但是,我们还需要在TankClient类中的draw方法中做一些改善,例如:1)将我方的坦克置于被敌方子弹攻击的范围内,2)我方坦克被打死之后,应该如何处理。

本项目中,处理的方法为,在我方坦克死亡之后,提示“Game Over,按键A可以复活!!!”字样,并按下键盘A产生一个新的我方坦克。

    @Override
    public void paint(Graphics g) {

        /*
         * 画出我们自己的坦克,首先判断自己的坦克是否是活的,如果是,则画出来
         * 否则,则提示 Game  Over ,并休眠100000毫秒
         * */
        if(tk.isLive()){
            tk.draw(g); 
        }
        else{
            g.drawString("Game Over,按键A可以复活!!!",GAME_WIDTH/2 , GAME_HEIGHT/2);
        }   

        /*
         * 将敌方坦克也画出来,如果没有了敌方坦克,则产生一定数量的地方坦克
         * */
        if(enemyTanks.size()==0){
            this.produceTank();
        }
        for(int i=0;i<enemyTanks.size();i++){
            Tank enemy = enemyTanks.get(i);
            if(!enemy.isLive()){
                enemyTanks.remove(enemy);
            }
            else{
                enemy.draw(g);
            }

        }

        //炸弹对象
        for(int i=0;i<explodes.size();i++){
            Explode e = explodes.get(i);
            if(!e.isLive()){
                explodes.remove(e);
            }
            else{
                e.draw(g);
            }

        }

        //画子弹
        for(int i=0;i<missiles.size();i++){
            Missile ms = missiles.get(i);           
            //判断子弹是否还存活在,如果不是存活的,则移除
            if(!ms.isLive()){
                missiles.remove(ms);
            }
            else{
                ms.hitTanks(enemyTanks);
                ms.draw(g);
                //将自己的坦克也加入到子弹可以攻击的范围
                ms.hitTank(tk);
            }

        }
    }

在Tank类中的keyReleased方法中添加键盘A的事件。具体代码如下:

        //键盘按键松下时,也要进行记录
        public void keyReleased(KeyEvent e) {
            int key=e.getKeyCode();
            switch(key){
            case KeyEvent.VK_A:
                produceMainTank();
                break;
            //.....一些其它的case
            }
        }

        private void produceMainTank() {
            Tank t=this.tc.getTk();
            if(!t.isLive()){
                int x = r.nextInt(100)+200;
                int y = r.nextInt(150)+300;
                Tank newTank =new Tank(x,y,true,Direction.STOP,this.tc);
                this.tc.setTk(newTank);
            }           
        }

以上就基本上实现了坦克大战的敌我双方的游戏了。但是,还有一些需要我们完善的功能,例如,加入一些墙等等。在后面,将会慢慢实现。也会在博文中进行记录。

完整代码可以从这里获取:https://github.com/wojiushimogui/Tank

https://github.com/wojiushimogui/Tank_2.3 下的Tank2.7是最终版本。

参考资料

1、关于坦克相关博客均参考于马士兵老师的相关视频完成,特此说明。

以上是关于《Java小游戏实现》:坦克大战(续三)的主要内容,如果未能解决你的问题,请参考以下文章

《Java小游戏实现》:坦克大战

《Java小游戏实现》:坦克大战(续一)

java实现经典坦克大战及源代码下载

java学习之坦克大战游戏

基于java的坦克大战游戏

基于JavaSwing坦克大战游戏的设计和实现