Java零基础开发坦克大战(上)

作为一名java初学者能够独立开发出小游戏也是很有感觉的。

尤其是感受到面向对象的方法,特此总结一下,以备后用。

(最后会附整体代码,及详细注释)说明一下,此小游戏只是熟悉java基础只是而已,谈不上项目...

截止此时已经实现的功能是:画出了一辆坦克,可以八个方向灵活移动,按CTRL建可以发射炮弹 。

《第一阶段》做出界面:练习GUI基础知识

该阶段代码

1import java.awt.*;

2import java.awt.event.*;

3

4public class TankClient extends Frame {

5   

6    public void lauchFrame() {

7        this.setLocation(400, 300);//设定初始位置

8        this.setSize(800, 600);//设定大小

9        this.setTitle("TankWar");//设定标题

10        this.addWindowListener(new WindowAdapter() {

11            public void windowClosing(WindowEvent e) {

12                System.exit(0);

13            }

14        });//匿名类实现窗口关闭

15        this.setResizable(false);//使窗口固定

16        setVisible(true);//将窗口显示出来

17    }

18

19    public static void main(String[] args) {

20        TankClient tc = new TankClient();

21        tc.lauchFrame();//启动窗口

22    }

23

24}

《第二阶段》画出一辆坦克,并实现四个方向的移动:此阶段写完后实现了,坦克根据键盘控制进行四个方向的移动,但是运动不灵活。

      (1)重写paint方法,画出代表坦克的实心圆。paint()方法在每次窗口被激活会自动调用,repaint()方法也会自动调用paint()方法。

此部代码    

1public void paint(Graphics g) {//g相当于一个画笔

2        Color c = g.getColor();//拿到当前颜色,以便恢复

3        g.setColor(Color.RED);

4        g.fillOval(x, y, 30, 30);//画一个圆

5        g.setColor(c);//恢复为初始颜色

6    }

   (2)让坦克运动起来。其实初想感觉很简单,按一下方向键,改变一下x,y的值,如x+=5,y+=5,然后再重画一下,不就可以实现坦克的移动了吗?但是如果按一次键,才重新画一下的话,动画会很不均匀。解决方法是加一个线程,这个线程只做一件事情,就是每隔35ms调用repaint()方法重新刷新一下屏幕。x,y的值如果没改,则坦克没动,如果按下键盘,那么x,y值立刻变为x',y'。在改变的<35ms的时间内,坦克必定会运动。而且用线程控制屏幕刷新,也可方便之后子弹运动的实现。

     新写的线程类PaintThread,事件处理类KeyMonitor,都设定为内部类,原因:可以方便的访问包装类的方法不方便公开的,只为包装类服务的类应当定义为内部类。

     其中的双缓冲处理机制,将所有东西画在虚拟图片上,一次性显示出来,其它不多做解释了,毕竟此案例目的是熟悉java基础知识 !

按键事件处理代码:

//TankClient类中的内部类

private class KeyMonitor extends KeyAdapter {

        public void keyPressed(KeyEvent e) {

            int key = e.getKeyCode();

            switch(key) {

            case KeyEvent.VK_LEFT :

                x -= 5;

                break;

            case KeyEvent.VK_UP :

                y -= 5;

                break;

            case KeyEvent.VK_RIGHT :

                x += 5;

                break;

            case KeyEvent.VK_DOWN :

                y += 5;

                break;

            }

        }

    }

/*********************************************/

//勿忘加上监听器!!!

public void lauchFrame() {

        。。。。。。                                。。。。。。

        this.addKeyListener(new KeyMonitor());

       

        setVisible(true);

       

        new Thread(new PaintThread()).start();

    }

线程类代码

每隔50ms刷新一次,勿忘在launchFrame()函数中启动线程,见上段代码。

1private class PaintThread implements Runnable {

2

3        public void run() {

4            while(true) {

5                repaint();

6                try {

7                    Thread.sleep(50);

8                } catch (InterruptedException e) {

9                    e.printStackTrace();

10                }

11            }

12        }

13    }

此阶段整体代码

/****************************** TankClient.java ***********************************/

import java.awt.*;

import java.awt.event.*;

public class TankClient extends Frame {

    //常量定义

    public static final int GAME_WIDTH = 800;

    public static final int GAME_HEIGHT = 600;

    //成员变量

    int x = 50, y = 50;

   

    public void paint(Graphics g) {

        Color c = g.getColor();

        g.setColor(Color.RED);

        g.fillOval(x, y, 30, 30);

        g.setColor(c);

    }

    //通过双缓冲取消屏闪问题,从写update()方法

    Image offScreenImage = null;

    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.GREEN);

        gOffScreen.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);

        gOffScreen.setColor(c);

        paint(gOffScreen);

        g.drawImage(offScreenImage, 0, 0, null);

    }

    public void lauchFrame() {

        this.setLocation(400, 300);

        this.setSize(GAME_WIDTH, GAME_HEIGHT);

        this.setTitle("TankWar");

        this.addWindowListener(new WindowAdapter() {

            public void windowClosing(WindowEvent e) {

                System.exit(0);

            }

        });

        this.setResizable(false);

        this.setBackground(Color.GREEN);

        //加监听器

        this.addKeyListener(new KeyMonitor());

       

        setVisible(true);

        //启动线程

        new Thread(new PaintThread()).start();

    }

    public static void main(String[] args) {

        TankClient tc = new TankClient();

        tc.lauchFrame();

    }

    //线程控制刷新,每隔50ms刷新一下屏幕

    private class PaintThread implements Runnable {

        public void run() {

            while(true) {

                repaint();

                try {

                    Thread.sleep(50);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

        }

    }

    //键盘事件处理类,继承KeyAdapter类比实现KeyListener接口要方便些

    private class KeyMonitor extends KeyAdapter {

       

        public void keyPressed(KeyEvent e) {

            int key = e.getKeyCode();

            switch(key) {

            case KeyEvent.VK_LEFT :

                x -= 5;

                break;

            case KeyEvent.VK_UP :

                y -= 5;

                break;

            case KeyEvent.VK_RIGHT :

                x += 5;

                break;

            case KeyEvent.VK_DOWN :

                y += 5;

                break;

            }

        }

       

    }

}

《第三阶段》

到关键时刻了,体会面向对象的思想,创建tank类,关于tank的事情,让tank自己去做,TankClient类只作为一个大管家,实现各个类,对象的信息沟通和管理。


Tank类代码:

其中该类中,实现了坦克运动的流畅走向,并不像过去,按一下键盘,改变一下x,y的坐标,而是,按一下键盘,仅改变方向,move()方法通过方向再改变

x,y的坐标。此机制也实现了,同时按下右和上可以向东北方向运动。

±添加记录按键状态的布尔量

±添加代表方向的量(使用枚举)

±根据按键状态确定Tank方向

±根据方向进行下一步的移动(move)

具体看代码备注中标注的四步<1>.键盘事件处理____void keyPressed(KeyEvent e) void keyReleased(KeyEvent e)

                                      <2>.根据键盘事件中拿到的方向记录信息,判定dir的值____void locateDirection()

                                        <3>根据判定出的dir值来改变x,y坐标____void move()

                                      <4>实现动画public void draw(Graphics g) 

/***********************************Tank.java*******************************/

import java.awt.*;

import java.awt.event.*;

public class Tank {

    //常量声明

    public static final int WIDTH = 30,HEIGHT = 30;   

    public static final int XSPEED = 10,YSPEED = 10;

   

    //成员变量

    TankClient tc;

    private int x, y;

    private boolean bL=false, bU=false, bR=false, bD = false;

    private Direction dir = Direction.STOP;//坦克方向,决定坦克走向

    //定义枚举类型,成员变量 dir 的类型,而且在switch语句中表达更加形象

    enum Direction {L, LU, U, RU, R, RD, D, LD, STOP};

   

   

    //构造方法一(基本)

    public Tank(int x, int y) {

        this.x = x;

        this.y = y;

    }

    //保证坦克流畅运动的四步,实际上就没让小车停

    //键盘的控制只是改变方向,把stop也理解为一种方向

   

    //<1>.键盘事件处理

    //键盘按下,则bool类型的方向记录信息设为true

    public void keyPressed(KeyEvent e) {

        int key = e.getKeyCode();

        switch(key) {

        case KeyEvent.VK_LEFT :

            bL = true;

            break;

        case KeyEvent.VK_UP :

            bU = true;

            break;

        case KeyEvent.VK_RIGHT :

            bR = true;

            break;

        case KeyEvent.VK_DOWN :

            bD = true;

            break;

        }

        locateDirection();

    }

    //键盘松开时,bool类型的方向记录信息立刻设为false

    public void keyReleased(KeyEvent e) {

        int key = e.getKeyCode();

        switch(key) {

        case KeyEvent.VK_LEFT :

            bL = false;

            break;

        case KeyEvent.VK_UP :

            bU = false;

            break;

        case KeyEvent.VK_RIGHT :

            bR = false;

            break;

        case KeyEvent.VK_DOWN :

            bD = false;

            break;

        }

        locateDirection();       

    }

    //<2>.根据键盘事件中拿到的方向记录信息,判定dir的值

    void locateDirection() {

        if(bL && !bU && !bR && !bD) dir = ptdir = Direction.L;

        else if(bL && bU && !bR && !bD) dir = ptdir  = Direction.LU;

        else if(!bL && bU && !bR && !bD) dir = ptdir  = Direction.U;

        else if(!bL && bU && bR && !bD) dir = ptdir  = Direction.RU;

        else if(!bL && !bU && bR && bD) dir = ptdir  = Direction.RD;

        else if(!bL && !bU && !bR && bD) dir = ptdir  = Direction.D;

        else if(bL && !bU && !bR && bD) dir = ptdir  = Direction.LD;

        else if(!bL && !bU && bR && !bD) dir = ptdir  = Direction.R;

        else if(!bL && !bU && !bR && !bD) dir = Direction.STOP;

    }

    //<3>根据判定出的dir值来改变x,y坐标

    void move() {

        switch(dir) {

        case L:

            x -= XSPEED;

            break;

        case LU:

            x -= XSPEED;

            y -= YSPEED;

            break;

        case U:

            y -= YSPEED;

            break;

        case RU:

            x += XSPEED;

            y -= YSPEED;

            break;

        case R:

            x += XSPEED;

            break;

        case RD:

            x += XSPEED;

            y += YSPEED;

            break;

        case D:

            y += YSPEED;

            break;

        case LD:

            x -= XSPEED;

            y += YSPEED;

            break;

        case STOP:

            break;

        }

        //防止坦克开出去:

        if(x<7)x=7;

        if(y<30)y=30;

        if(x>TankClient.GAME_WIDTH-Tank.WIDTH-7)x=TankClient.GAME_WIDTH-Tank.WIDTH-7;

        if(y>TankClient.GAME_HEIGHT-Tank.HEIGHT-10)y=TankClient.GAME_HEIGHT-Tank.HEIGHT-10;

       

    }

    //<4>实现动画

    public void draw(Graphics g) {

        Color c = g.getColor();

        g.setColor(Color.RED);

        g.fillOval(x, y, WIDTH, HEIGHT);

        g.setColor(c);

        //每刷一次屏(35ms)move一次,所以方向中要有stop方向

        move();

    }

}

TankClient 类得重构,大管家的身份渐渐明显 !

import java.awt.*;

import java.util.List;

import java.util.ArrayList;

import java.util.*;

import java.awt.event.*;

public class TankClient extends Frame {

    //常量声明

    public static final int WIDTH = 10,HEIGHT = 10;

    public static final int GAME_WIDTH=800,GAME_HEIGHT =600;

    /****************************************************************************/ 

    //对象建立

    Tank myTank = new Tank(50,50);

   

    //启动框架frame

    public void launchFrame(){

        this.setSize(GAME_WIDTH,GAME_HEIGHT);

        this.setLocation(100,100);

        this.setBackground(Color.BLACK);

        this.setVisible(true);

        new Thread(new PaintThread()).start();//启动线程

        this.addKeyListener(new KeyMonitor());//添加事件监听器

        this.addWindowListener(new WindowAdapter(){//窗口关闭方法

                    public void windowClosing(WindowEvent e){

                        System.exit(0);

            }

        });

    }

    /****************************************************************************/

    //把画笔g传递给对象自己去画!

    public void paint(Graphics g){

        Color c = g.getColor();

        g.setColor(Color.WHITE);

        g.setColor(c);

        myTank.draw(g);//把主画笔交个坦克自己去画

      }

    /****************************************************************************/

    //双缓冲机制,取消屏闪

    Image offScreenImage = null;

    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.BLACK);

        gOffScreen.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);

        gOffScreen.setColor(c);

        paint(gOffScreen);

        g.drawImage(offScreenImage, 0, 0, null);

    }

   

    //主函数,构造整体框架frame,并启动launchFrame函数

    public static void main(String[] args) {

        TankClient client = new TankClient();

        client.launchFrame();

    }

   

    //线程控制,每35ms刷新一下屏幕,即调用repaint()方法

    //repaint自动调用paint()方法,即画出此时的x,y,即可实现动画效果

    private class PaintThread implements Runnable{

        public void run() {

            while(true){

                repaint();

                try {

                    Thread.sleep(30);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

           

        }   

    }

    /******************************************************************/

    //键盘事件的处理,将事件e传递给tank类,让其自己处理自己的事情

    private class KeyMonitor extends KeyAdapter{

        @Override

        public void keyReleased(KeyEvent e) {

            myTank.keyReleased(e);//把事件对象e传递给tank自己去处理!!!

        }

        public void keyPressed(KeyEvent e){

            myTank.keyPressed(e);

        }

    }

    /*******************************************************************/   

}

第四阶段

有一句话叫做三人行必有我师,其实做为一个开发者,有一个学习的氛围跟一个交流圈子特别重要这是一个我的java交流学习群549505940

不管你是小白还是大牛欢迎入驻,正在求职的也可以加入

,大家一起交流学习,话糙理不糙,互相学习,共同进步,一起加油吧。

你可能感兴趣的:(Java零基础开发坦克大战(上))