天天看點

《Java小遊戲實作》:坦克大戰(續2)《Java小遊戲實作》:坦克大戰(續2)

《Java小遊戲實作》:坦克大戰(續2)

相關博文:

《Java小遊戲實作》:坦克大戰http://blog.csdn.net/u010412719/article/details/51712663

《Java小遊戲實作》:坦克大戰(續一):http://blog.csdn.net/u010412719/article/details/51723570

博文《Java小遊戲實作》:坦克大戰(續1)中已經實作到了坦克可以發射一顆子彈了。這篇博文在此基礎上繼續實作更多的功能。

完成功能:坦克發射多顆子彈

有了坦克發射一顆子彈的基礎,發射多顆子彈就相當簡單的,隻需要在TankClien類中加入一個容器來存放多枚子彈即可。由于容器的容量也是有限的,我們不能一直往裡面裝。是以,在子彈類中,我們為子彈對象引入了一個live屬性,用來判斷這個子彈是否還存活。判斷子彈是否還存活是根據子彈的位置是否出界。

下面隻貼出完成這個功能相關代碼

TankClient.java

private List<Missile> missiles = new ArrayList<Missile> ();

    public List<Missile> getMissiles() {
        return missiles;
    }
    @Override
    public void paint(Graphics g) {
        //直接調用坦克類的draw方法
        tk.draw(g); 
        //畫子彈
        for(int i=;i<missiles.size();i++){
            Missile ms = missiles.get(i);
            //判斷子彈是否還存活在,如果不是存活的,則移除
            if(!ms.isLive()){
                missiles.remove(ms);
            }
            else{
                ms.draw(g);
            }

        }
    }
           

Missile.java類

在move方法中根據子彈所在的位置判斷子彈是否還存活。

public class Missile {

        private void move() {
            if(dir==Direction.L){//L,LU,U,RU,R,RD,D,LD,STOP
                x -= XSPEED;
            }
            else if(dir==Direction.LU){
                x -= XSPEED;
                y -= YSPEED;
            }
            else if(dir==Direction.U){
                y -= YSPEED;
            }
            else if(dir==Direction.RU){
                x += XSPEED;
                y -= YSPEED;
            }
            else if(dir==Direction.R){
                x += XSPEED;
            }
            else if(dir==Direction.RD){
                x += XSPEED;
                y += YSPEED;
            }
            else if(dir==Direction.D){
                y += YSPEED;
            }
            else if(dir==Direction.LD){
                x -= XSPEED;
                y += YSPEED;
            }

            //根據子彈所在的位置x,y來判斷子彈是否還存活在
            if(x<||x>TankClient.GAME_WIDTH||y<||y>TankClient.GAME_HEIGHT){
                live = false;
            }
        }
        public boolean isLive() {   
            return live;
        }
    }
           

Tank.java檔案内容如下:

在坦克類中,在keyPressed方法中,每按一次Ctrl鍵,就發射一顆子彈。

public class Tank {

        //記錄鍵盤的按鍵情況
        public void keyPressed(KeyEvent e){
            int key=e.getKeyCode();
            //System.out.println(key);
            switch(key){
            case :
                tc.getMissiles().add(fire());
                break;
            case KeyEvent.VK_LEFT:
                b_L=true;
                break;
            case KeyEvent.VK_UP:
                b_U=true;
                break;
            case KeyEvent.VK_RIGHT:
                b_R=true;
                break;
            case KeyEvent.VK_DOWN:
                b_D=true;
                break;
            }
            //根據上面的按鍵情況,确定坦克即将要運作的方向
            moveDirection();
        }

    }
           

完整代碼下載下傳連結:http://download.csdn.net/detail/u010412719/9555292

完成功能:解決坦克越界問題

坦克越界問題比較簡單,隻需要判斷坦克所在的位置是否越界,如果越界,則将其位置設定為邊界位置即可。

實作代碼如下:

/*
         * 函數功能:處理坦克越界問題
         * */
        private void dealTankBorder() {
            if(x<){
                x = ;
            } 
            else if(x > TankClient.GAME_WIDTH-this.WIDTH){
                x = TankClient.GAME_WIDTH-this.WIDTH ;
            } 
            if(y<){
                 y = ;
            }
            else if(y>TankClient.GAME_WIDTH - this.HEIGHT){
                 y = TankClient.GAME_WIDTH - this.HEIGHT;
            }
        }
           

上面函數在move()方法最後進行調用即可。

完成功能:加入敵人的坦克

前面實作的所用功能是自己一個坦克在界面上面運動呀、發子彈呀等等。

下面我們完成在界面上加入一個敵人坦克。

實作敵人坦克有兩種方式

1、第一種是再建立一個EnemyTank類,

2、第二種是在原來的Tank類中加入一個屬性來表示此坦克的類型。

由于我們有一個坦克類了,為友善起見,這裡采用第二種方式:在原來的Tank類中加入一個屬性來表示此坦克的類型。

Tank.java中新增加的内容主要有

//添加一個屬性,表示此坦克是好還是壞
        private boolean good;

    //更改下構造方法

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

        public Tank(int x, int y,boolean good, TankClient tc) {
            this(x,y,good);
            this.tc = tc;
        }
    //根據坦克的類型給出不同的顔色
        public void draw(Graphics g){

            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();//根據鍵盤按鍵的結果改變坦克所在的位置
        }
           

Missile類沒有任何變動。

總管家TankClient.java中需要添加的内容有:

1、new 出兩個坦克對象

private Tank tk=new Tank(,,true,this);

private Tank enemy = new Tank(,,false,this);
           

2、在paint方法中畫出兩個坦克

@Override
    public void paint(Graphics g) {
        //直接調用坦克類的draw方法
        tk.draw(g); 

        enemy.draw(g);
        //畫子彈
        for(int i=;i<missiles.size();i++){
            Missile ms = missiles.get(i);
            //判斷子彈是否還存活在,如果不是存活的,則移除
            if(!ms.isLive()){
                missiles.remove(ms);
            }
            else{
                ms.draw(g);
            }

        }
    }
           
《Java小遊戲實作》:坦克大戰(續2)《Java小遊戲實作》:坦克大戰(續2)

以上就實作了添加敵對坦克這一功能,但是還沒有對其添加随機運動。

完成的功能:子彈打死敵人坦克

經過上面的實作,我們有了一個不會動且不會發子彈的傻傻的敵對坦克,但是我們也不能打死它,下面我們就來實作打死坦克。哈哈,是不是稍微變得有趣一點了。

分析:

1、由于是子彈打死敵人坦克,根據面向對象的思想,在子彈類中,加入一個hitTank方法。

public boolean hitTank(Tank t){

}
           

2、那麼hitTank這個方法應該如何來判斷是否子彈打中了該坦克呢??這就涉及到一個碰撞的問題。由于我們的子彈和坦克都是矩形,是以碰撞問題就得到了很好的簡化,隻是判斷兩個矩形是否有重疊的部分。如果有,則就判斷發生了碰撞,子彈就打中了坦克。

是以,在Missile類和Tank類中,,均添加一個getRect()方法,傳回子彈和坦克所在的矩形區域對象。

public Rectangle getRect(){
        return new Rectangle(x, y, WIDTH, HEIGHT);
}
           

3、當子彈打中了坦克,則子彈和坦克就應該都消失。是以,在坦克中需要引入一個布爾變量來判斷坦克是否存活

public boolean hitTank(Tank t){
        //首先判斷此坦克是否是存活的,如果是死的,就不打了
        if(!t.isLive()){
            return false;
        }
        if(this.getRect().intersects(t.getRect())){//判斷是否有碰撞
            //碰撞之後,子彈和該坦克就應該都死了
            this.live = false;//子彈死了
            t.setLive(false);//坦克死了
            return true;
        }
        else{
            return false;
        }
    }
           

無論是子彈消失,在前面的版本中有處理,但是本版本上的坦克消失,目前的處理方法就是不繪畫出來,即在draw方法中,首先判斷一下,看此對象是否存活,如果存活,則繪畫出來。

代碼如下:

public void draw(Graphics g){
            if(!live){      //判斷坦克是否存活,如果死了,則不繪畫出來,直接傳回     
                return ;
            }
            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();//根據鍵盤按鍵的結果改變坦克所在的位置
        }
           

最後,貼下Tank類、Missile類、TankClient類的完整代碼:

Tank類

public class Tank {
        //坦克所在的位置坐标
        private int x;
        private int y;

        //坦克的高度和寬度
        private static final int WIDTH = ;
        private static final int HEIGHT = ;

        //定義兩個常量,表示運動的速度
        private static final int XSPEED = ;
        private static final int YSPEED = ;

        //定義四個布爾類型變量來記錄按鍵的情況,預設狀态下為false,表示沒有鍵按下
        private boolean b_L,b_U,b_R,b_D;

        //添加一個屬性,表示此坦克是好還是壞
        private boolean good;

        public boolean isGood() {
            return good;
        }

        //用來辨別此坦克對象是否存活
        private boolean live =true;

        public boolean isLive() {
            return live;
        }

        public void setLive(boolean live) {
            this.live = live;
        }
        //定義一個枚舉類型來表示運作的方向  
        public enum Direction{
            L,LU,U,RU,R,RD,D,LD,STOP
        }
        //定義一個變量來表示坦克要運作的方向,初始狀态為STOP
        private Direction dir = Direction.STOP;

        //炮筒方向
        private Direction ptDir = Direction.D;

        private TankClient tc;

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

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

        public void draw(Graphics g){
            if(!live){      //判斷坦克是否存活,如果死了,則不繪畫出來,直接傳回     
                return ;
            }
            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();//根據鍵盤按鍵的結果改變坦克所在的位置
        }

        private void drawGunBarrel(Graphics g) {
            int centerX = this.x + this.WIDTH/;
            int centerY = this.y + this.HEIGHT/;

            if(ptDir==Direction.L){//L,LU,U,RU,R,RD,D,LD,STOP
                g.drawLine(centerX, centerY, x, y + HEIGHT/);
            }
            else if(ptDir==Direction.LU){
                g.drawLine(centerX, centerY, x, y );
            }
            else if(ptDir==Direction.U){
                g.drawLine(centerX, centerY, x+ WIDTH/, y );
            }
            else if(ptDir==Direction.RU){
                g.drawLine(centerX, centerY, x + WIDTH, y );
            }
            else if(ptDir==Direction.R){
                g.drawLine(centerX, centerY, x+ WIDTH, y + HEIGHT/);
            }
            else if(ptDir==Direction.RD){
                g.drawLine(centerX, centerY, x+ WIDTH, y + HEIGHT);
            }
            else if(ptDir==Direction.D){
                g.drawLine(centerX, centerY, x+ WIDTH/, y + HEIGHT);
            }
            else if(ptDir==Direction.LD){
                g.drawLine(centerX, centerY, x, y + HEIGHT);
            }

        }

        //記錄鍵盤的按鍵情況
        public void keyPressed(KeyEvent e){
            int key=e.getKeyCode();
            //System.out.println(key);
            switch(key){
//          case 17://避免因Ctrl一直按下,一直發射子彈,是以将這一功能放入keyReleased方法中了
//              tc.getMissiles().add(fire());
//              break;
            case KeyEvent.VK_LEFT:
                b_L=true;
                break;
            case KeyEvent.VK_UP:
                b_U=true;
                break;
            case KeyEvent.VK_RIGHT:
                b_R=true;
                break;
            case KeyEvent.VK_DOWN:
                b_D=true;
                break;
            }
            //根據上面的按鍵情況,确定坦克即将要運作的方向
            moveDirection();
        }

        //鍵盤按鍵松下時,也要進行記錄
        public void keyReleased(KeyEvent e) {
            int key=e.getKeyCode();
            switch(key){
            case :
                tc.getMissiles().add(fire());
                break;
            case KeyEvent.VK_LEFT:
                b_L=false;
                break;
            case KeyEvent.VK_UP:
                b_U=false;
                break;
            case KeyEvent.VK_RIGHT:
                b_R=false;
                break;
            case KeyEvent.VK_DOWN:
                b_D=false;
                break;
            }
        }

        //根據鍵盤的按鍵情況來确定坦克的運作方向
        private void moveDirection() {//L,LU,U,RU,R,RD,D,LD,STOP
            if(b_L&&!b_U&&!b_R&&!b_D){
                dir = Direction.L;
            }
            else if(b_L&&b_U&&!b_R&&!b_D){
                dir = Direction.LU;
            }
            else if(!b_L&&b_U&&!b_R&&!b_D){
                dir = Direction.U;
            }
            else if(!b_L&&b_U&&b_R&&!b_D){
                dir = Direction.RU;
            }
            else if(!b_L&&!b_U&&b_R&&!b_D){
                dir = Direction.R;
            }
            else if(!b_L&&!b_U&&b_R&&b_D){
                dir = Direction.RD;
            }
            else if(!b_L&&!b_U&&!b_R&&b_D){
                dir = Direction.D;
            }
            else if(b_L&&!b_U&&!b_R&&b_D){
                dir = Direction.LD;
            }
            else{//其它所有情況,都是不動
                dir = Direction.STOP;
            }
            //将坦克方向指派給炮筒方向
            if(dir!=Direction.STOP){
                ptDir = dir;
            }

        }

        //上面有運作方向,但是還缺少具體的運作細節,例如:假設是按下了右鍵,則應該橫坐标x+=XSPEED;
        private void move(){
            if(dir==Direction.L){//L,LU,U,RU,R,RD,D,LD,STOP
                x -= XSPEED;
            }
            else if(dir==Direction.LU){
                x -= XSPEED;
                y -= YSPEED;
            }
            else if(dir==Direction.U){
                y -= YSPEED;
            }
            else if(dir==Direction.RU){
                x += XSPEED;
                y -= YSPEED;
            }
            else if(dir==Direction.R){
                x += XSPEED;
            }
            else if(dir==Direction.RD){
                x += XSPEED;
                y += YSPEED;
            }
            else if(dir==Direction.D){
                y += YSPEED;
            }
            else if(dir==Direction.LD){
                x -= XSPEED;
                y += YSPEED;
            }
            else if(dir==Direction.STOP){
                //... nothing
            }

            //處理坦克越界問題
            dealTankBorder();       
        }
        /*
         * 函數功能:處理坦克越界問題
         * */
        private void dealTankBorder() {
            if(x<){
                x = ;
            } 
            else if(x > TankClient.GAME_WIDTH-this.WIDTH){
                x = TankClient.GAME_WIDTH-this.WIDTH ;
            } 
            if(y<){
                 y = ;
            }
            else if(y>TankClient.GAME_WIDTH - this.HEIGHT){
                 y = TankClient.GAME_WIDTH - this.HEIGHT;
            }
        }

        public Missile fire(){
            //計算子彈的位置,并利用炮筒的方向來new一個子彈對象
            int x = this.x +(this.WIDTH)/ - (Missile.WIDTH)/;
            int y = this.y + (this.HEIGHT)/ -(Missile.HEIGHT)/;
            Missile ms = new Missile(x,y,this.ptDir);
            return ms;
        }
        /*
         * 函數功能:得到坦克所在位置的矩形框
         * */
        public Rectangle getRect(){
            return new Rectangle(x, y, WIDTH, HEIGHT);
        }

    }
           

Missile類

代碼如下

public class Missile {

        //定義兩個常量,表示運動的速度
        private static final int XSPEED = ;
        private static final int YSPEED = ;

        //子彈所在的位置
        private int x;
        private int y;

        //坦克的高度和寬度
        public static final int WIDTH = ;
        public static final int HEIGHT = ;

        //子彈的運作方向
        private Direction dir;

        private boolean live = true;

        public Missile(int x, int y, Direction dir) {
            this.x = x;
            this.y = y;
            this.dir = dir;
        }
        public void draw(Graphics g){
            //如果該子彈不是存活的,則不進行繪圖
            if(!live){
                return ;
            }
            Color c = g.getColor();
            g.setColor(Color.YELLOW);
            g.fillOval(x, y, WIDTH, HEIGHT);
            g.setColor(c);
            move();
        }

        private void move() {
            if(dir==Direction.L){//L,LU,U,RU,R,RD,D,LD,STOP
                x -= XSPEED;
            }
            else if(dir==Direction.LU){
                x -= XSPEED;
                y -= YSPEED;
            }
            else if(dir==Direction.U){
                y -= YSPEED;
            }
            else if(dir==Direction.RU){
                x += XSPEED;
                y -= YSPEED;
            }
            else if(dir==Direction.R){
                x += XSPEED;
            }
            else if(dir==Direction.RD){
                x += XSPEED;
                y += YSPEED;
            }
            else if(dir==Direction.D){
                y += YSPEED;
            }
            else if(dir==Direction.LD){
                x -= XSPEED;
                y += YSPEED;
            }

            //根據子彈所在的位置x,y來判斷子彈是否還存活在
            if(x<||x>TankClient.GAME_WIDTH||y<||y>TankClient.GAME_HEIGHT){
                live = false;
            }
        }
        public boolean isLive() {   
            return live;
        }

        public Rectangle getRect(){
            return new Rectangle(x, y, WIDTH, HEIGHT);
        }

        public boolean hitTank(Tank t){
            //首先判斷此坦克是否是存活的,如果是死的,就不打了
            if(!t.isLive()){
                return false;
            }
            if(this.getRect().intersects(t.getRect())){//判斷是否有碰撞
                //碰撞之後,子彈和該坦克就應該都死了
                this.live = false;//子彈死了
                t.setLive(false);//坦克死了
                return true;
            }
            else{
                return false;
            }
        }
           

TankClient類代碼如下:

/*
     * 此版本添加了子彈打死敵對坦克
     * */
    public class TankClient extends Frame{

        public final static int GAME_WIDTH=;
        public final static int GAME_HEIGHT=;


        private Tank tk=new Tank(,,true,this);

        private Tank enemy = new Tank(,,false,this);

        private List<Missile> missiles = new ArrayList<Missile> ();

        public List<Missile> getMissiles() {
            return missiles;
        }

        private Image offScreenImage = null;

        public static void main(String[] args) {
            new TankClient().launchFrame();
        }

        @Override
        public void update(Graphics g) {
            if (offScreenImage == null) {
                offScreenImage = this.createImage(GAME_WIDTH, GAME_HEIGHT);
            }
            Graphics goffScreen = offScreenImage.getGraphics();// 重新定義一個畫虛拟桌布的畫筆//
            Color c = goffScreen.getColor();
            goffScreen.setColor(Color.darkGray);
            goffScreen.fillRect(, , GAME_WIDTH, GAME_HEIGHT);
            goffScreen.setColor(c);
            paint(goffScreen);
            g.drawImage(offScreenImage, , , null);
        }

        @Override
        public void paint(Graphics g) {
            //直接調用坦克類的draw方法
            tk.draw(g);         
            enemy.draw(g);
            //畫子彈
            for(int i=;i<missiles.size();i++){
                Missile ms = missiles.get(i);           
                //判斷子彈是否還存活在,如果不是存活的,則移除
                if(!ms.isLive()){
                    missiles.remove(ms);
                }
                else{
                    ms.hitTank(enemy);
                    ms.draw(g);
                }

            }
        }

        public void launchFrame(){

            this.setTitle("坦克大戰");
            this.setLocation(, );
            this.setSize(GAME_WIDTH, GAME_HEIGHT);
            this.setBackground(Color.GRAY);
            //為關閉視窗添加響應
            this.addWindowListener(new WindowAdapter(){

                @Override
                public void windowClosing(WindowEvent e) {
                    System.exit();
                }

            });
            //設定是否允許使用者改變視窗的大小,這裡限制下,不允許
            this.setResizable(false);
            this.setVisible(true);

            new Thread(new MyRepaint()).start();
            this.addKeyListener(new KeyMonitor());

        }

        private class MyRepaint implements Runnable{

            @Override
            public void run() {
                while(true){
                    //每50ms重畫一次
                    repaint();
                    try {
                        Thread.sleep();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        }

        private class KeyMonitor extends KeyAdapter{

            @Override
            public void keyPressed(KeyEvent e) {
                tk.keyPressed(e);
            }

            @Override
            public void keyReleased(KeyEvent e) {
                tk.keyReleased(e);
            }   

        }

    }
           

以上就完成了坦克發射子彈可以擊打敵對坦克的功能。

未完,剩餘功能見下篇博文