乐趣区

关于java:老Java程序员花一天时间写了个飞机大战

代码实现
创立窗口
首先创立一个游戏窗体类 GameFrame,继承至 JFrame,用来显示在屏幕上(window 的对象),每个游戏都有一个窗口,设置好窗口题目、尺寸、布局等就能够。

/*
 * 游戏窗体类
 */
public class GameFrame extends JFrame {public GameFrame() {setTitle("飞机大战");// 设置题目
        setSize(526, 685);// 设定尺寸
        setLayout(new BorderLayout());
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 点击敞开按钮是关闭程序
        setLocationRelativeTo(null);   // 设置居中
        setResizable(false); // 不容许批改界面大小
    }
}

创立面板容器 GamePanel 继承至 JPanel

package main;

import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
/*
 * 画布类
 */
public class GamePanel extends JPanel{
    GamePanel gamePanel=this;
    private JFrame mainFrame=null;
    // 结构外面初始化相干参数
    public GamePanel(JFrame frame){this.setLayout(null);
        mainFrame = frame;
        
        mainFrame.setVisible(true);
    }
    
    @Override
    public void paint(Graphics g) {}}

再创立一个 Main 类,来启动这个窗口,用来启动。

package main;
public class Main {
    // 主类
    public static void main(String[] args) {GameFrame frame = new GameFrame();
        GamePanel panel = new GamePanel(frame);
        frame.add(panel);
        frame.setVisible(true);// 设定显示
    }
}

右键执行这个 Main 类,窗口建进去了

创立菜单及菜单选项
创立菜单

private void  initMenu(){
    // 创立菜单及菜单选项
    jmb = new JMenuBar();
    JMenu jm1 = new JMenu("游戏");
    jm1.setFont(new Font("微软雅黑", Font.BOLD, 15));// 设置菜单显示的字体
    JMenu jm2 = new JMenu("帮忙");
    jm2.setFont(new Font("微软雅黑", Font.BOLD, 15));// 设置菜单显示的字体
    
    JMenuItem jmi1 = new JMenuItem("开始新游戏");
    JMenuItem jmi2 = new JMenuItem("退出");
    jmi1.setFont(new Font("微软雅黑", Font.BOLD, 15));
    jmi2.setFont(new Font("微软雅黑", Font.BOLD, 15));
    
    JMenuItem jmi3 = new JMenuItem("操作阐明");
    jmi3.setFont(new Font("微软雅黑", Font.BOLD, 15));
    JMenuItem jmi4 = new JMenuItem("胜利条件");
    jmi4.setFont(new Font("微软雅黑", Font.BOLD, 15));
    
    jm1.add(jmi1);
    jm1.add(jmi2);
    
    jm2.add(jmi3);
    jm2.add(jmi4);
    
    jmb.add(jm1);
    jmb.add(jm2);
    mainFrame.setJMenuBar(jmb);// 菜单 Bar 放到 JFrame 上
    jmi1.addActionListener(this);
    jmi1.setActionCommand("Restart");
    jmi2.addActionListener(this);
    jmi2.setActionCommand("Exit");
    
    jmi3.addActionListener(this);
    jmi3.setActionCommand("help");
    jmi4.addActionListener(this);
    jmi4.setActionCommand("win");
}

实现 ActionListener 并重写办法 actionPerformed

actionPerformed 办法的实现

@Override
public void actionPerformed(ActionEvent e) {String command = e.getActionCommand();
    UIManager.put("OptionPane.buttonFont", new FontUIResource(new Font("宋体", Font.ITALIC, 18)));
    UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("宋体", Font.ITALIC, 18)));
    if ("Exit".equals(command)) {Object[] options = {"确定", "勾销"};
        int response = JOptionPane.showOptionDialog(this, "您确认要退出吗", "",
                JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,
                options, options[0]);
        if (response == 0) {System.exit(0);
        } 
    }else if("Restart".equals(command)){if(startFlag){Object[] options = {"确定", "勾销"};
            int response = JOptionPane.showOptionDialog(this, "游戏中,您确认要从新开始吗", "",
                    JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,
                    options, options[0]);
            if (response == 0) {
                // 须要先完结游戏
                realGameEnd(1);
                restart();} 
        }else{restart();
        }
    }else if("help".equals(command)){
        JOptionPane.showMessageDialog(null, "游戏开始后,要先动鼠标到飞机处,触发挪动成果,而后飞机就会追随鼠标挪动!",
                "提醒!", JOptionPane.INFORMATION_MESSAGE);
    }else if("win".equals(command)){
        JOptionPane.showMessageDialog(null, "得分 1000,获得胜利!",
                "提醒!", JOptionPane.INFORMATION_MESSAGE);
    }
}


创立背景
在 GamePanel 类中重写 paint 办法,绘制背景图即可

// 绘图办法
@Override
public void paint(Graphics g) {gameHeight = this.getHeight();
    gameWidth = this.getWidth();
    // 绘制背景
    g.drawImage((BufferedImage)imageMap.get("bg"), 0, -150, null);
}


开启主线程
主线程,用来重绘页面,重绘全副交给主线程,主线程调用 repaint 办法就行,要产生动画就要靠这个 repaint。

// 刷新线程,用来从新绘制页面
private class RefreshThread implements Runnable {
    @Override
    public void run() {while (startFlag) {repaint();
            try {Thread.sleep(50);
            } catch (InterruptedException e) {e.printStackTrace();
            }
        }
    }
}

在 GamePanel 的结构外面启动这个主线程

有了这个主线程刷新,待会咱们更新飞机的地位,飞机就会挪动,不须要另外的代码去调用 repaint 办法了(这是我的做法,仅供参考)。

创立我方飞机
创立 MyPlane 类,属性有坐标 x、y,宽高、图片、是否存活、是否能够挪动等;办法次要有绘制、挪动、射击等。

public class MyPlane {

    private int x = 0;
    private int y = 0;
    private int width = 0;
    private int height = 0;
    private BufferedImage image = null;
    private GamePanel panel=null;
    private HashMap imageMap=null;
    private boolean alive=true;
    private boolean canMove=false;
    private int key=1;
    private HashMap boomImageMap=null;
    private boolean hitFlag=false;// 正在碰撞
    
    public MyPlane(int x,int y,int width,int height,GamePanel panel) {
        this.x=x;
        this.y=y;
        this.width=width;
        this.height=height;
        this.panel=panel;
        this.imageMap=panel.imageMap;
        this.image=(BufferedImage)imageMap.get("myplane1");
        this.boomImageMap=panel.mypalneBoomImageMap;
        
    }
    // 绘制
    public void draw(Graphics g) {g.drawImage(image, x, y, width,height, null);
    }
}

创立(这里只是创立好了飞机对象,须要绘制)

// 创立本人飞机
private void initMyPlane() {myPlane = new MyPlane(200, 530, 132, 86, this);
}

在 paint 办法中绘制

// 绘图办法
@Override
public void paint(Graphics g) {gameHeight = this.getHeight();
    gameWidth = this.getWidth();
    // 绘制背景
    g.drawImage((BufferedImage)imageMap.get("bg"), 0, -150, null);
    
    // 绘制飞机
    if(myPlane!=null){myPlane.draw(g);
    }
}


鼠标事件监听
退出监听是为了让飞机追随鼠标挪动,我这里定的规定是第一次鼠标必须挪动到飞机上,而后飞机才会追随。

代码外面用一个属性 canMove 来管制,默认是 false,只有鼠标第一次移入到飞机上时,这个属性设置为 true,而后就能够追随鼠标挪动了。

// 鼠标事件的创立
private void createMouseListener() {MouseAdapter mouseAdapter = new MouseAdapter() {
        @Override
        public void mouseMoved(MouseEvent e) {int x = e.getX();
            int y = e.getY();
            if(myPlane==null) return ;
            // 飞机第一次是不容许挪动的, 只有飞机的 canMove 为 true 才去追随
            if(myPlane.isCanMove()){myPlane.move(x,y);
                return;
            }
            // 判断鼠标的移入,如果挪动到飞机上则 canMove 设置为 true
            if(myPlane.isPoint(x,y)){myPlane.setCanMove(true);
            }
        }
    };
    addMouseMotionListener(mouseAdapter);
    addMouseListener(mouseAdapter);
}

来实现一下 MyPlane 的 move 办法,这里解决了边界,保障飞机不出界,同时保障鼠标在飞机的两头地位

// 飞机追随鼠标挪动
public void move(int x,int y) {
    // 判断范畴,当横向挪动在窗口范畴内
    if(x-width/2>=0 && x<=panel.getWidth()-width/2){this.x=x-width/2;}
    // 判断范畴,当纵向挪动在窗口范畴内
    if(y-height/2>=0 && y<=panel.getHeight()-height/2){this.y=y-height/2;}
}


创立子弹类
属性也就是坐标、宽高这些,给子弹退出挪动办法

// 挪动
void move(){new Thread(new Runnable() {
        @Override
        public void run() {while(alive){
                y-=speed;
                if(y<=0){clear();
                }
                
                try {Thread.sleep(50);
                } catch (InterruptedException e) {e.printStackTrace();
                }
            }
        }
    }).start();}

飞机类退出射击办法,200 毫秒创立一发子弹

// 射击
void shoot() {new Thread(new Runnable() {
        @Override
        public void run() {while(alive){
                // 创立子弹
                createBullet();
                try {Thread.sleep(200);
                } catch (InterruptedException e) {e.printStackTrace();
                }
            }
        }

        private void createBullet() {Bullet bullet = new Bullet(x+width/2-10, y, 20, 30, panel);
            panel.bulletList.add(bullet);
            new MusicPlayer("/music/shoot.wav").play();}
    }).start();}

别忘记在 paint 办法外面绘制子弹进去

// 绘制子弹
Bullet bullet=null;
for (int i = 0; i < bulletList.size(); i++) {bullet = (Bullet)bulletList.get(i);
    bullet.draw(g);
}


创立敌机
创立抽象类 Plane

package main;

import java.awt.Graphics;

public abstract class Plane {public abstract void move();
    public abstract void draw(Graphics g);
    public abstract void boom();
    public abstract void clear();
    
    abstract int getX();
    abstract int getY();
    abstract int getWidth();
    abstract int getHeight();}

创立敌机子类

有个非凡一点的中央: 因为有 4 种敌机,这里随机 1、2、3、4 这 4 个数字作为属性 index,而后依据这个 index 来展示不同的飞机图片,当然也能够通过这个 index 来设置敌机不同的挪动速度,然而我为了偷懒,就全副一样的挪动速度,嘿嘿。
挪动就是开启线程让 y 坐标减少,没什么好讲的,这里加一个飞机碰撞,就是当敌机跟我方飞机如何判断碰撞的问题。

撞机剖析(敌机与我机的撞机)



从下面几个图可看出什么?因为图片是方形的,他们的 4 个顶点肯定至多有一个在对方的范畴内。再看一下从右边撞击的图:



从上图看到也是这样,其余两个方向的也是一样的情理,为了稳点我还加了一种状况:

1. 判断敌机的 4 个点是否在飞机范畴内,如果有则示意碰撞了。
2. 如果 1 不成立,则反过来,判断我机的 4 个点是否在敌机的范畴内,如果是示意碰撞了。

// 判断飞机与子弹是否碰撞
private boolean isPoint(MyPlane plane) {
    /*
     * 
     * 两种状况
     * 1. 须要判断敌机的 4 个点是否在飞机范畴内,如果有则示意碰撞了
     * 2. 如果步骤 1 不成立,则反过来,判断我机的 4 个点是否在敌机的范畴内,如果是标记碰撞了
    */
    
    // 形式 1
    
    // 左上角
    int x1 = x;
    int y1 = y;
    // 右上角
    int x2 = x+width;
    int y2 = y;
    // 右下角
    int x3 = x+width;
    int y3 = y+height;
    // 左下角
    int x4 = x;
    int y4 = y+height;
    // 只有有一个点在范畴内,则判断为碰撞
    if(comparePointMyPlane(x1,y1,plane)|| comparePointMyPlane(x2,y2,plane)||comparePointMyPlane(x3,y3,plane)||comparePointMyPlane(x4,y4,plane) ){return true;}
    
    // 形式 1 没成立则用形式 2 判断
    
    // 形式 2
    x1 = plane.getX();
    y1 = plane.getY();
    // 右上角
    x2 = plane.getX()+plane.getWidth();
    y2 = plane.getY();
    // 右下角
    x3 = plane.getX()+plane.getWidth();
    y3 =plane.getY()+plane.getHeight();
    // 左下角
    x4 = plane.getX();
    y4 = plane.getY()+plane.getHeight();
    if(comparePoint(x1,y1)|| comparePoint(x2,y2)||comparePoint(x3,y3)||comparePoint(x4,y4) ){return true;}
    return false;
}
// 用敌机的坐标来判断
private boolean comparePointMyPlane(int x,int y,MyPlane plane){
    // 大于左上角,小于右下角的坐标则必定在范畴内
    if(x>plane.getX() && y >plane.getY()
        && x<plane.getX()+plane.getWidth() && y <plane.getY()+plane.getHeight()    ){return  true;}
    return false;
}
// 用我机的坐标来判断
private boolean comparePoint(int x,int y){
    // 大于左上角,小于右下角的坐标则必定在范畴内
    if(x>this.x && y >this.y
        && x<this.x+this.width && y <this.y+this.height){return  true;}
    return false;
}

测试一下成果

遗记说击中敌机的了(原理跟方才差不多,代码间接放了)

// 判断击中敌机
protected void hitEnemy() {
    EnemyPlane enemyPlane=null;
    List enemys = panel.enemyList;
    for (int i = 0; i < enemys.size(); i++) {
        try {enemyPlane = (EnemyPlane)enemys.get(i);
        } catch (Exception e) { }
        if(enemyPlane==null) continue;
        if(this.isPoint(enemyPlane)){panel.curCount+=enemyPlane.getCount();
            // 删除以后子弹
            clear();
            
            // 飞机爆炸
            enemyPlane.boom();
            
            if(panel.curCount>=panel.totalCount){panel.myPlane.setCanMove(false);
                panel.gameWin();}
        }
    }
}

// 判断飞机与子弹是否碰撞
private boolean isPoint(EnemyPlane plane) {
    // 因为子弹比飞机小,所以只须要判断子弹的 4 个点是否在飞机范畴内,如果有则示意碰撞了
    // 左上角
    int x1 = x;
    int y1 = y;
    // 右上角
    int x2 = x+width;
    int y2 = y;
    // 右下角
    int x3 = x+width;
    int y3 = y+height;
    // 左下角
    int x4 = x;
    int y4 = y+height;
    // 只有有一个点在范畴内,则判断为碰撞
    if(comparePoint(x1,y1,plane)|| comparePoint(x2,y2,plane)||comparePoint(x3,y3,plane)||comparePoint(x4,y4,plane) ){return true;}
    return false;
}

private boolean comparePoint(int x,int y,EnemyPlane plane){
    // 大于左上角,小于右下角的坐标则必定在范畴内
    if(x>plane.getX() && y >plane.getY()
        && x<plane.getX()+plane.getWidth() && y <plane.getY()+plane.getHeight()    ){return  true;}
    return false;
}

最初加上计分的、胜利、失败等提醒就实现了!到这里一款飞机游戏就制作实现了

退出移动版