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

来源:互联网 发布:爱的算法免费下载 编辑:程序博客网 时间:2024/05/13 17:33

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

上篇博文(http://blog.csdn.net/u010412719/article/details/51712663)只介绍到了能够控制一个坦克在4个方向上移动就结束了,今天就在此基础上继续完成一个一个的小功能。

完成的功能:根据键盘的按键控制一个坦克在8个方向上移动

要完成这个功能,我们要做一下几个事情

1、记录下键盘的按键情况,即要重写键盘按下和抬起的监听事件

采用4个boolean变量来记录,按下为true,抬起为false

具体实现的代码如下:

    //定义四个布尔类型变量来记录按键的情况,默认状态下为false,表示没有键按下        private boolean b_L,b_U,b_R,b_D;    //记录键盘的按键情况        public void keyMonitor(KeyEvent e){            int key=e.getKeyCode();            switch(key){            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 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;            }        }

2、根据1中所记录的按键情况来确定坦克的运行方向

8个运行方向采用一个枚举类型来保存。即        private enum Direction{            L,LU,U,RU,R,RD,D,LD,STOP        }    //例如:如果键盘的左键和下键被按下,则运行方向dir=Direction.LD.    //定义一个变量来表示坦克要运行的方向,初始状态为STOP        private Direction dir = Direction.STOP;        //根据键盘的按键情况来确定坦克的运行方向        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;            }        }

3、根据2中的运动方向来确定具体的运行快慢等情况,例如,如果是像左下方向运动,则坦克目前所在的位置(x,y)—->(x-XSPEED,y+YSPEED);其中XSPEED、YSPEED为x、y轴的运行速度。

        //上面有运行方向,但是还缺少具体的运行细节,例如:假设是按下了右键,则应该横坐标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            }        }

上面三步完整代码代码如下:

    public class Tank {        //坦克所在的位置坐标        private int x;        private int y;        //定义两个常量,表示运动的速度        private static final int XSPEED = 5;        private static final int YSPEED = 5;        //定义四个布尔类型变量来记录按键的情况,默认状态下为false,表示没有键按下        private boolean b_L,b_U,b_R,b_D;        //定义一个枚举类型来表示运行的方向          private enum Direction{            L,LU,U,RU,R,RD,D,LD,STOP        }        //定义一个变量来表示坦克要运行的方向,初始状态为STOP        private Direction dir = Direction.STOP;        public Tank(int x, int y) {            super();            this.x = x;            this.y = y;        }        public int getX() {            return x;        }        public void setX(int x) {            this.x = x;        }        public int getY() {            return y;        }        public void setY(int y) {            this.y = y;        }        public void draw(Graphics g){            Color c = g.getColor();            g.setColor(Color.RED);            g.fillOval(x, y, 30, 30);            g.setColor(c);            move();//根据键盘按键的结果改变坦克所在的位置        }        //记录键盘的按键情况        public void keyMonitor(KeyEvent e){            int key=e.getKeyCode();            switch(key){            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 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;            }        }        //上面有运行方向,但是还缺少具体的运行细节,例如:假设是按下了右键,则应该横坐标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            }        }    }

而在TarkClient.java文件中主要修改的地方重写了一个按键抬起的方法。

    private class KeyMonitor extends KeyAdapter{        @Override        public void keyPressed(KeyEvent e) {            tk.keyMonitor(e);        }        @Override        public void keyReleased(KeyEvent e) {            tk.keyReleased(e);        }       }

以上就完成了控制坦克在8个方向上的运行。

完成功能:坦克可以打出子弹

既然是坦克大战,必须要可以打出子弹,是吧,否则,多没意思呀。

首先,我们建立一个子弹类,子弹类中也应该有位置和方向属性,以及draw方法和move方法。

代码如下:

    public class Missile {        //定义两个常量,表示运动的速度        private static final int XSPEED = 20;        private static final int YSPEED = 20;        //子弹所在的位置        private int x;        private int y;        //子弹的运行方向        private Direction dir;        public Missile(int x, int y, Direction dir) {            this.x = x;            this.y = y;            this.dir = dir;        }        public void draw(Graphics g){            Color c = g.getColor();            g.setColor(Color.YELLOW);            g.fillOval(x, y, 5, 5);            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;            }        }    }

然后,我们来测试下,在界面中画出一颗不受控制的子弹出来,

在TankClient类中,有一个Missile对象,然后在paint中调用Missile的draw画出来即可。

    public class TankClient extends Frame{        //....无关代码没有显示        private Missile ms =new Missile(50,50,Direction.D);//一个子弹对象        @Override        public void paint(Graphics g) {            //直接调用坦克至圣的draw方法            tk.draw(g);             if(ms!=null){                ms.draw(g);            }               }        //....无关代码没有显示    }

现在我们来实现子弹是按下Ctrl键由坦克发射出来。既然是坦克发出一颗子弹,那么在坦克类中就应该有一个发子弹的方法,取名为fire(),返回值为一个子弹对象。子弹对象有坦克当前的位置以及方向来进行初始化。

        public Missile fire(){            Missile ms = new Missile(x,y,dir);            return ms;        }

我们要按下Ctrl键时,发射一颗子弹。

因此,在坦克类中的keyPressed方法中,添加键值是ctrl的情况。由于fire()返回的Missile对象需要用来初始化TankClient类中的Missile引用。因此,在Tank类中,需要持有一个TankClient的引用,借此访问TankClient中的Missile引用。

        private TankClient tc;        public Tank(int x, int y) {            this.x = x;            this.y = y;        }        public Tank(int x, int y, TankClient tc) {            this(x,y);            this.tc = tc;        }    //记录键盘的按键情况        public void keyPressed(KeyEvent e){            int key=e.getKeyCode();            //System.out.println(key);            switch(key){            case 17:  //按下Ctrl键时的处理情况,tc是一个TankClient对象,里面有一个子弹的引用                tc.setMs(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();        }

而TankClient.java类中的代码如下:

    public class TankClient extends Frame{        private final static int GAME_WIDTH=600;        private final static int GAME_HEIGHT=600;        private Tank tk=new Tank(50,50,this);//将this穿进去初始化TankClient        private Missile ms ;//持有一个Missile的引用        public Missile getMs() {            return ms;        }        public void setMs(Missile ms) {            this.ms = ms;        }        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(0, 0, GAME_WIDTH, GAME_HEIGHT);            goffScreen.setColor(c);            paint(goffScreen);            g.drawImage(offScreenImage, 0, 0, null);        }        @Override        public void paint(Graphics g) {            //直接调用坦克至圣的draw方法            tk.draw(g);             if(ms!=null){                ms.draw(g);            }               }        public void launchFrame(){            this.setTitle("坦克大战");            this.setLocation(300, 400);            this.setSize(GAME_WIDTH, GAME_HEIGHT);            this.setBackground(Color.GRAY);            //为关闭窗口添加响应            this.addWindowListener(new WindowAdapter(){                @Override                public void windowClosing(WindowEvent e) {                    System.exit(0);                }            });            //设置是否允许用户改变窗口的大小,这里限制下,不允许            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(50);                    } 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);            }           }    }

以上就实现了一个坦克按下Ctrl键就发射一颗子弹。

虽然上面坦克能够发送一颗子弹了,但是坦克发射一颗子弹还存在有两个小问题

1、发射位置不是在坦克的中心。

2、当坦克处于暂定状态时,不能发送子弹。

先解决第一个问题:使的子弹发送的位置在坦克的中心。

下图画出了如何来根据坦克的位置(X,Y)、高度宽度以及子弹的高度和宽度来求得子弹应该所在的位置。

有了求和子弹位置的表达式,在代码实现中就只需要修正一下Tank类中的fire()方法即可。

        public Missile fire(){            int x = this.x +(this.WIDTH)/2 - (Missile.WIDTH)/2;            int y = this.y + (this.HEIGHT)/2 -(Missile.HEIGHT)/2;            Missile ms = new Missile(x,y,dir);            return ms;        }

先解决第二个问题:当坦克处于暂定状态时,不能发送子弹。

为解决这个问题,引入了炮筒方向,当坦克运动时炮筒方向与坦克方向一致,而当坦克停止时,则炮筒就保持坦克停止前最后的运动方向。而子弹的发射方向为炮筒的指向。

实现代码如下:

    //引入一个代表炮筒方向的属性        private Direction ptDir = Direction.D;    //并根据炮筒的方向在坦克中画出炮筒        public void draw(Graphics g){            Color c = g.getColor();            g.setColor(Color.RED);            g.fillOval(x, y, 30, 30);            g.setColor(c);            //画一个炮筒            drawGunBarrel(g);            move();//根据键盘按键的结果改变坦克所在的位置        }        private void drawGunBarrel(Graphics g) {            int centerX = this.x + this.WIDTH/2;            int centerY = this.y + this.HEIGHT/2;            if(ptDir==Direction.L){//L,LU,U,RU,R,RD,D,LD,STOP                g.drawLine(centerX, centerY, x, y + HEIGHT/2);            }            else if(dir==Direction.LU){                g.drawLine(centerX, centerY, x, y );            }            else if(dir==Direction.U){                g.drawLine(centerX, centerY, x+ WIDTH/2, y );            }            else if(dir==Direction.RU){                g.drawLine(centerX, centerY, x + WIDTH, y );            }            else if(dir==Direction.R){                g.drawLine(centerX, centerY, x+ WIDTH, y + HEIGHT/2);            }            else if(dir==Direction.RD){                g.drawLine(centerX, centerY, x+ WIDTH, y + HEIGHT);            }            else if(dir==Direction.D){                g.drawLine(centerX, centerY, x+ WIDTH/2, y + HEIGHT);            }            else if(dir==Direction.LD){                g.drawLine(centerX, centerY, x, y + HEIGHT);            }        }    //最后根据炮筒的方向来发射子弹        public Missile fire(){            //计算子弹的位置,并利用炮筒的方向来new一个子弹对象            int x = this.x +(this.WIDTH)/2 - (Missile.WIDTH)/2;            int y = this.y + (this.HEIGHT)/2 -(Missile.HEIGHT)/2;            Missile ms = new Missile(x,y,this.ptDir);            return ms;        }

以上就解决了当坦克停止时不能发射子弹的问题。

Tank.java完整代码如下:

public class Tank {        //坦克所在的位置坐标        private int x;        private int y;        //坦克的高度和宽度        private static final int WIDTH = 30;        private static final int HEIGHT = 30;        //定义两个常量,表示运动的速度        private static final int XSPEED = 5;        private static final int YSPEED = 5;        //定义四个布尔类型变量来记录按键的情况,默认状态下为false,表示没有键按下        private boolean b_L,b_U,b_R,b_D;        //定义一个枚举类型来表示运行的方向          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) {            this.x = x;            this.y = y;        }        public Tank(int x, int y, TankClient tc) {            this(x,y);            this.tc = tc;        }        public void draw(Graphics g){            Color c = g.getColor();            g.setColor(Color.RED);            g.fillOval(x, y, 30, 30);            g.setColor(c);            //画一个炮筒            drawGunBarrel(g);            move();//根据键盘按键的结果改变坦克所在的位置        }        private void drawGunBarrel(Graphics g) {            int centerX = this.x + this.WIDTH/2;            int centerY = this.y + this.HEIGHT/2;            if(ptDir==Direction.L){//L,LU,U,RU,R,RD,D,LD,STOP                g.drawLine(centerX, centerY, x, y + HEIGHT/2);            }            else if(dir==Direction.LU){                g.drawLine(centerX, centerY, x, y );            }            else if(dir==Direction.U){                g.drawLine(centerX, centerY, x+ WIDTH/2, y );            }            else if(dir==Direction.RU){                g.drawLine(centerX, centerY, x + WIDTH, y );            }            else if(dir==Direction.R){                g.drawLine(centerX, centerY, x+ WIDTH, y + HEIGHT/2);            }            else if(dir==Direction.RD){                g.drawLine(centerX, centerY, x+ WIDTH, y + HEIGHT);            }            else if(dir==Direction.D){                g.drawLine(centerX, centerY, x+ WIDTH/2, y + HEIGHT);            }            else if(dir==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:                tc.setMs(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 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            }        }        public Missile fire(){            //计算子弹的位置,并利用炮筒的方向来new一个子弹对象            int x = this.x +(this.WIDTH)/2 - (Missile.WIDTH)/2;            int y = this.y + (this.HEIGHT)/2 -(Missile.HEIGHT)/2;            Missile ms = new Missile(x,y,this.ptDir);            return ms;        }    }

未完,余下的功能见下篇博文

1 0
原创粉丝点击