JAVA项目:flappy_bird基于JAVASE和AWT组件

发布于:2022-11-09 ⋅ 阅读:(7) ⋅ 点赞:(0) ⋅ 评论:(0)

学习于教程:尚硅谷JAVASE项目:【FlappyBird游戏项目】手把手用Java教你做游戏_FlappyBird飞翔的小鸟_哔哩哔哩_bilibili

按自己想法修改了程序

项目结构

开发流程

游戏窗口的绘制:

在main包下创建一个GameFrame类:

public class GameFrame extends Frame  {

    //构造方法中初始化一些参数
    public GameFrame() {  //初始化参数

        setVisible(true); //窗口可见

        setSize(FRAME_WIDTH, FRAME_HEIGHT);  //窗口大小 参数分别为宽高

        setTitle(FRAME_TITLE); //窗口标题

        setLocation(FRAME_X, FRAME_Y);  //窗口初始位置

        setResizable(false); //窗口大小不可改变

        //窗口的关闭事件,创建窗口监听事件
        addWindowListener(new WindowAdapter() {   //接收窗口事件的抽象适配器类,此类中的方法为空,此类存在的目的是方便创建侦听器对象
            @Override
            public void windowClosing(WindowEvent e) {  //窗口正处在关闭过程中时调用
                System.exit(0); //结束程序
            }
        });
    }
}
GameFrame类继承于Frame类,Frame是一种控件,可作为父窗体加载其他AWT控件
窗口监听事件参数为匿名内部类,当窗口关闭时,同步退出程序

在APP包下建立游戏入口:

public class GameAPP {  //游戏启动
    public static void main(String[] args) {
        new GameFrame();
    }
}

游戏背景添加

在until包下建立读入图片的类

public class GameUtil {
    //装载图片
    public static BufferedImage loadBufferedImage(String imgPath){  //读入图片文件
        try {
            return ImageIO.read(new FileInputStream(imgPath));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

BufferedImage继承自抽象类Image,BufferedImage是其实现类,是一个带缓冲区图像类,主要作用是将一幅图片加载到内存中,提供获得绘图对象和图片的一些功能,里面有获取图片宽度高度等一些方法。

JAVA进行图片I/O可以用到ImageIO工具里的方法,可用里面的read静态方法读取图片,源码为:

绘制背景(地面以及天空)

新建一个GameBackGround类

public class GameBackGround {
    //背景需要的资源图片
    private BufferedImage bkimg;  //将一副图片加载到内存中的类

    //构造器初始化资源
    public GameBackGround(){
        bkimg= GameUtil.loadBufferedImage(BK_IMG_PATH);
    }

    public void draw(Graphics g){ //绘制图片  Graphics类可以理解为画笔,为我们提供了各种绘制图形的方法

        //填充背景色
        g.setColor(BK_COLOR); 
                //左上角坐标      //长宽
        g.fillRect(0,0,FRAME_WIDTH,FRAME_HEIGHT);  //在画布上填充矩形
        g.setColor(Color.black);  //矩形颜色

        //得到图片的宽高
        int height=bkimg.getHeight();
        int weight=bkimg.getWidth();
        //所需要的图片的张数(循环的次数)
        int count=FRAME_WIDTH/weight+1;
        for(int i=0;i<count;i++){
                      //图片      横坐标位置     纵坐标位置(底面)       回调(通知进度)
            g.drawImage(bkimg,weight*i,FRAME_HEIGHT-height,null);
        }
    }
}

Graphics类可以理解为画笔,为我们提供了各种绘制图形

天空直接用蓝色背景表示

g.fillRect()用于持续给游戏添加背景颜色(天空颜色)

地面绘制逻辑是使用for循环一张一张图片画出来,从边框地面-地面图片高度开始绘制

绘制小鸟

建立Bird类



public class Bird {
    //小鸟矩形对象
    private Rectangle rect;  //Rectangle类表示矩形

    //存放小鸟图片
    private BufferedImage[] images;

    public static final int BIRD_IMG_COUNT=3;

    //鸟的状态  0 1 2分别是平着飞,往上飞,往下飞
    private int state;
    public static final int STATE_NORMAL=0;
    public static final int STATE_UP=1;
    public static final int STATE_DOWN=2;

    //小鸟的位置
    private int x=300,y=300;


    // 初始化资源
    public Bird(){
        images=new BufferedImage[BIRD_IMG_COUNT];
        for(int i=0;i<BIRD_IMG_COUNT;i++){
            images[i]=GameUtil.loadBufferedImage(BIRD_IMG[i]);
        }

        int w=images[0].getWidth();
        int h=images[0].getHeight();
        rect=new Rectangle(w,h);
    }

    //绘制小鸟
   

}

小鸟有三种状态所以建立一个三个元素的图片数组,储存小鸟三种状态的图片

 rect=new Rectangle(w,h)是套上后续用于判断小鸟与障碍物相撞的矩形

实现小鸟上下移动

更新Bird类:

public class Bird {
       //小鸟的生命
    public boolean life=true;

    //存放小鸟图片
    private BufferedImage[] images;
    public static final int BIRD_IMG_COUNT=3;
    //鸟的状态  0 1 2分别是平着飞,往上飞,往下飞
    private int state;
    public static final int STATE_NORMAL=0;
    public static final int STATE_UP=1;
    public static final int STATE_DOWN=2;

    //小鸟的位置
    private int x=300,y=300;

    //小鸟的移动方向 上下
    private boolean up=false,down=false;

    //小鸟移动速度
    private int speed=5;

    //小鸟的加速度
    private double acceleration;

     // 初始化资源
    public Bird(){
        images=new BufferedImage[BIRD_IMG_COUNT];
        for(int i=0;i<BIRD_IMG_COUNT;i++){
            images[i]=GameUtil.loadBufferedImage(BIRD_IMG[i]);
        }

        int w=images[0].getWidth();
        int h=images[0].getHeight();
        rect=new Rectangle(w,h);
    }

    //绘制小鸟
    public void draw(Graphics g){
        flyLogic();

        g.drawImage(images[state],x,y,null );

        //绘制小鸟的矩形          图片高度       矩形宽度
     //   g.drawRect(x,y,(int)rect.getWidth(),(int)rect.getHeight());

        rect.x=this.x;
        rect.y=this.y;
    }

    //控制小鸟移动方向
    public void flyLogic(){
        if (up){
            y-=speed;
            if (y<20){
                y=20;
            }
        }
        if (!up){
            y+=speed;
            if (y>475){
                y=475;
            }
        }

    }

//控制小鸟移动方向

    public void flyLogic(){

        if(up){
            acceleration-= Bird_ACC;
            y+=acceleration;
            if(acceleration<-Bird_SPEED){  //最高速度
                acceleration=-Bird_SPEED;
            }

           // y-=speed;  //往上飞
            if(y<20){  //防止飞出地图外
                y=20;
                acceleration=0;
            }
        }
        if(!up){
            acceleration+=Bird_ACC;
            y+=acceleration;
            if(acceleration>Bird_SPEED){  //最高速度
                acceleration=Bird_SPEED;
            }

            //y+=speed;  //落体

        }
    }

    public void fly(int fly){
        switch (fly){
            case 1:
                state=1;
                up=true;
                break;
            case 5:
                state=2;
                up=false;
                break;
        }
    }

}

小鸟飞行速度为匀加速运动,速到到最大不再加速

在frame类加上监听事件、实现画面更新另一个线程


public class GameFrame extends Frame {   //创建窗口 Frame是一个GUI中的基类

    //实例化GamebackGround类
    private GameBackGround gameBackGround;

    //实例化Bird类
    private Bird bird;

    //实例化前景类
    private GameFrontGround gameFrontGround;


    public GameFrame(){  //初始化参数

        setVisible(true); //窗口可见

        setSize(FRAME_WIDTH,FRAME_HEIGHT);  //窗口大小 参数分别为宽高

        setTitle(FRAME_TITLE); //窗口标题

        setLocation(FRAME_X,FRAME_Y);  //窗口初始位置

        setResizable(false); //窗口大小不可改变

        //窗口的关闭事件,创建窗口监听事件
        addWindowListener(new WindowAdapter() {   //接收窗口事件的抽象适配器类,此类中的方法为空,此类存在的目的是方便创建侦听器对象
            @Override
            public void windowClosing(WindowEvent e) {  //窗口正处在关闭过程中时调用
                System.exit(0); //结束程序
            }
        });

        initGamg();

        new run().start();

        //添加按键监听
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {  //按下
                add(e);
            }

            @Override
            public void keyReleased(KeyEvent e) {  //抬键
                minu(e);
            }
        });

        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) { add_mouse(e)  ;}

            @Override
            public void mouseReleased(MouseEvent e) { minu_mouse(e); }
        });

    }

    public void initGamg(){  //游戏中对象的初始化
        gameBackGround=new GameBackGround();
        bird=new Bird();
  
    }

    class run extends Thread{  //刷新图片
        @Override
        public void run() {
            while(true) {
                repaint();  //具有刷新页面效果的方法,若不调用repaint方法图形发生变化后不会立刻显示
                try {
                    Thread.sleep(33); //33毫秒刷一次
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

     /**
     * 所有的我们需要绘制的内容都在此方法中进行调用绘制
     */
    @Override
    public void update(Graphics g) {
        gameBackGround.draw(g);
        bird.draw(g);
    }

    //按键
    public void add(KeyEvent e){  //KeyEvent类负责捕获键盘事件
        switch (e.getKeyCode()){  //返回按下键盘对应的码
            case KeyEvent.VK_UP:
                bird.fly(1);
                break;
            case KeyEvent.VK_SPACE:  //死亡重开
                if(bird.life==false){
                    restart();
                }
        }
    }

    //抬键
    public void minu(KeyEvent e){
        switch (e.getKeyCode()){
            case KeyEvent.VK_UP:
                bird.fly(5);
                break;
        }
    }


    public void add_mouse(MouseEvent e){
        switch (e.getButton()){
            case MouseEvent.BUTTON1:
                bird.fly(1);
                break;
        }
    }

    public void minu_mouse(MouseEvent e){
        switch(e.getButton()){
            case MouseEvent.BUTTON1:
                bird.fly(5);
                break;
        }
    }


}

事件监听可以让我们与程序互动,添加了键盘监听和鼠标监听,当用户按鼠标左键或者上方向键可以让小鸟飞翔

 repaint()方法是一个具有刷新页面效果的方法,若不调用repaint方法图形发生变化后不会立刻显示
 repaint调用update()清除当前显示并再调用paint()方法
 如果不调用repaint()就不能实现每一次的刷新显示,不能立刻显示就无法马上进行下一步的绘画

此时会有严重的屏幕闪所问题

解决屏幕闪烁问题

修改GameFrame类的update方法以及再该类中添加一个图片流

添加一张大图片,将图片一次性的绘制到这张大图片中,就可以解决只有一个通道要加载多张图片出现的闪烁问题

private BufferedImage buffimg=new BufferedImage(FRAME_WIDTH,FRAME_HEIGHT,BufferedImage.TYPE_4BYTE_ABGR);

 public void update(Graphics g){ //更新画面
        if(bird.life) { //小鸟活着
            //得到图片的画笔
            Graphics graphics = buffimg.getGraphics();
            //画图
            gameBackGround.draw(graphics);
            bird.draw(graphics);
           //一次性将图片绘制到屏幕中
            g.drawImage(buffimg, 0, 0, null);

 云彩的添加与移动,生成与销毁

添加Cloud类

public class Cloud {

    private BufferedImage img; //云彩图片

    private int speed; //云彩的速度

    private int x,y; //云彩的位置

    public Cloud(){};

    public Cloud(BufferedImage img, int speed, int x, int y) {
        this.img = img;
        this.speed = speed;
        this.x = x;
        this.y = y;
    }

    public void draw(Graphics g){
        x-=speed;
        g.drawImage(img,x,y,null);
    }

    //用于判断云彩是否废除屏幕以外

    public boolean isOutFrame(){
        if(x<-100){
            return true;
        }
        return false;
    }

添加GameFrontGround类

public class GameFrontGround {

    private static final int CLOUD_COUNT=2;//云彩个数

    private List<Cloud> clouds;//存放云彩的容器

    public static final int CLOUD_SPEED=2;  //云彩的速度

    private BufferedImage[] img;  //得到图片资源

    private Random random; //产生随机数

    //构造器初始化数据
    public GameFrontGround(){
        clouds =new ArrayList<>();
        img=new BufferedImage[CLOUD_COUNT];

        //容器中添加云彩的图片
        for(int i=0;i<CLOUD_COUNT;i++){
            img[i]= GameUtil.loadBufferedImage("img/cloud"+i%2+".png");  //有两种云
        }
        random=new Random();
    }

    //绘制云彩
    public void draw(Graphics g){
        logic();

        for (int i = 0; i < clouds.size(); i++) {
            clouds.get(i).draw(g);
        }

    }

    //用于云彩的个数控制
    private void logic(){
        if((int)(500*Math.random())<5){
           Cloud cloud= new Cloud(img[random.nextInt(CLOUD_COUNT)],CLOUD_SPEED, Constant.FRAME_WIDTH,random.nextInt(200)+50);
            clouds.add(cloud);

            for (int i = 0; i < clouds.size(); i++) {
               Cloud cloud1= clouds.get(i);
               if(cloud1.isOutFrame()){
                   clouds.remove(i);
                   i--;
                   System.out.println("云被移除"+cloud1);
               }

            }
        }
    }

}

 把所有云储存到ArrayList集合中,有两种云,图片命名好之后,可通过参生随机数与路径命组合随机创建云

云离开屏幕后会从集合中移除,设计随机数的判定控制生成云的频率,0.03秒刷新一次,每次有1%产生云,平均3秒一个云

修改GameFrame类的两个方法,为程序加上生成云的功能

  public void initGamg(){
        gameBackGround = new GameBackGround();
        bird = new Bird();
        gameFrontGround = new GameFrontGround();

    }

    public void update(Graphics g) {
        //得到图片的画笔
        Graphics graphics = buffimg.getGraphics();

        gameBackGround.draw(graphics);
        bird.draw(graphics);
        gameFrontGround.draw(graphics);

        //一次性的将图片绘制到屏幕中
        g.drawImage(buffimg,0,0,null);

    }

障碍物浏览

  

绘制普通障碍物(上下一组固定的障碍物)


public class Barrier {


    //障碍物需要的三个图片
    private static BufferedImage[] imgs;


    static {  //初始化参数
        final int COUNT=3;
        //加载的时候将三个图片初始化 分别是 1:中间 2:头 3:尾
        imgs=new BufferedImage[COUNT];
        for(int i=0;i<COUNT;i++){
            imgs[i]= GameUtil.loadBufferedImage(Constant.BARRIER_IMG_PATH[i]);
        }
    }

    //位置
    private int x,y;
    //宽度高度
    private int width,height;
    //障碍物的类型
    private int type;
    public static final int TYPE_TOP_NORMAL=0;
    public static final int TYPE_BOTTOM_NORMAL=2;
    public static final int TYPE_HOVER_NORMAL=4;
    public static final int TYPE_MOBILE=6;

    //获得障碍物的宽度和高度
    public static final int BARRIER_WIDTH=imgs[0].getWidth();  //BufferedImage类里面的获取图片宽度
    public static final int BARRIER_HEIGHT=imgs[0].getHeight();
    public static final int BARRIER_HEAD_WIDTH=imgs[1].getWidth();
    public static final int BARRIER_HEAD_HEIGHT=imgs[1].getHeight();

    public Barrier(){

    }

    public Barrier(int x, int y, int height, int type) {
        this.x = x;
        this.y = y;
        this.width = BARRIER_WIDTH;  //宽度固定
        this.height = height;
        this.type = type;
    }

    //根据不同的类型绘制障碍物
    public void draw(Graphics g){
        switch (type){
            case TYPE_TOP_NORMAL :
                drawTopNormal(g);
                break;
            case TYPE_BOTTOM_NORMAL:
                drawNomalTOP(g);
                break;
            
        }

    }

    //绘制上面的的障碍物
    private void drawTopNormal(Graphics g){
        //求出所需中间障碍物的块数
        int count=(height-BARRIER_HEAD_HEIGHT)/BARRIER_HEIGHT+1;
        //for循环绘制中间障碍物
        for (int i = 0; i < count; i++) {
            g.drawImage(imgs[0],x,y+i*BARRIER_HEIGHT ,null);
        }

        //绘制头
        int y=height-BARRIER_HEAD_HEIGHT;
                                      //让头部与中间显现居中效果
        g.drawImage(imgs[2], x-(BARRIER_HEAD_WIDTH-BARRIER_WIDTH)/2,y,null);
    
        }

    }


    //绘制下面的障碍物
    private void drawNomalTOP(Graphics g){
        //求出所需要障碍物的块数
        int count=height/BARRIER_HEIGHT+1;
        //for循环绘制障碍物
        for(int i=0;i<count;i++){
            g.drawImage(imgs[0],x,Constant.FRAME_HEIGHT-i*BARRIER_HEIGHT,null );
        }
        //绘制头
        int y=Constant.FRAME_HEIGHT-height;
        g.drawImage(imgs[1],x-(BARRIER_HEAD_HEIGHT-BARRIER_HEIGHT)/2,y,null);
 

    }

   

障碍物由三张图片组成(头、尾、中间),头尾各绘制一次,使用for循环绘制多次中间部分

上面障碍物和下面障碍物绘制逻辑不同,下面障碍物的纵坐标是窗口底边坐标减去高度

创建GameBarrierLayer类

public class GameBarrierLayer {

    private List<Barrier> barriers;

    public GameBarrierLayer(){
        barriers = new ArrayList<>();
    }

    //绘制障碍物
    public void draw(Graphics g){
        Barrier barrier = new Barrier(200, 0, 200, 0);
        barriers.add(barrier);
        barriers.get(0).draw(g);
        Barrier barrier1 = new Barrier(300, 0, 300, 2);
        barriers.add(barrier1);
        barriers.get(1).draw(g);
    }

}

生成第一组障碍物

 障碍物自动生成

在Barrier类中添加一个方法用于控制障碍物生成频率

     static final int frequency=170;
      //判断什么时候控制下一组障碍物  可用于调整障碍物出现的频率
    public  boolean isInFrame(){  //可控制障碍物生成频率
        return Constant.FRAME_WIDTH-x>frequency;
    }

更新BarrierLayer类

public class GameBarrierLayer {

    private Random random = new Random();

    private List<Barrier> barriers;

    public GameBarrierLayer(){
        barriers = new ArrayList<>();
    }

    //绘制连续生成障碍物
    public void draw(Graphics g){
        for (int i = 0; i < barriers.size(); i++) {
            Barrier barrier = barriers.get(i);
            barrier.draw(g);
        }
        logic();

    }

    public void logic(){
        if (barriers.size() == 0) {
            ran();
            Barrier top = new Barrier(600,0,numberTop,0);
            barriers.add(top);
            Barrier down = new Barrier(600,500-numberDown,numberDown,2);
            barriers.add(down);
        }else {
            //判断最后一个障碍物是否完全进入屏幕内
            Barrier last = barriers.get(barriers.size() - 1);
            if (last.isInFrame()) {  //条件成立,继续绘制下一组障碍物
                ran();
                Barrier top = new Barrier(600,0,numberTop,0);
                barriers.add(top);
                Barrier down = new Barrier(600,500-numberDown,numberDown,2);
                barriers.add(down);
            }
        }
    }



//产生随即高度
    public void ran(){  //设置障碍物随机生成参数 可调难度 bound为长度,后面的系数为最小长度
        numberTop=random.nextInt(600)+100;
        numberDown=random.nextInt(600)+100;
        number=random.nextInt(500);

        //如果管道重合,则重新随机
        if(numberTop+numberDown>640){  //中间间隙 700为重合
            ran();
        }

       if(numberTop+numberDown<400){  //中间最大间隙
            ran();
        }

    }



}

使用随机数产生障碍物的高度,上下高度和判断是否重合或者间隙太大,若不合法则递归重新绘制,直到合法为止

障碍物在向左移动,小鸟不动,则看起来小鸟在移动

障碍物对象池:

对象池作用:可以集中管理池中对象,减少频繁创建和销毁长期使用的对象,从而提升复用性,以节约资源的消耗,可以有效避免频繁为对象分配内存和释放堆中内存,进而减轻jvm垃圾收集器的负担,避免内存抖动。

创建Barrierpool类



public class Barrierpool {
    //用于管理池中所有对象
    private static List<Barrier> pool = new ArrayList<>();
    //池中初始化对象个数
    public static final int initCount=16;
    //对象池中最大个数
    public static final int maxCount=20;

    static { //初始化池中对象
        for(int i=0;i<initCount;i++){
            pool.add(new Barrier());
        }
    }

    //从池中获取一个对象
    public static Barrier getPool(){
        int size=pool.size();
        //如果池中有对象才可以拿
        if(size>0){
            //移除并返回对象
            System.out.println("拿走一个");
            return pool.remove((size-1));
        }else{
            //池中没有对象
            System.out.println("新的对象");
            return new Barrier();
        }
    }

    //将对象归回给容器中
    public static void setPool(Barrier barrier){
        if(pool.size()<maxCount){
            pool.add(barrier);
            System.out.println("容器归还");

        }
    }
}

池中有对象时,生成障碍物直接从池里拿,没有就返回一个创建的对象,当障碍物出了窗口外,则归还给对象池,如此循环可以节省资源

更新Barrier的两个方法,判断是否出了地图外用以归还对象

  private void drawNomalTOP(Graphics g){
        //求出所需要障碍物的块数
        int count=height/BARRIER_HEIGHT+1;
        //for循环绘制障碍物
        for(int i=0;i<count;i++){
            g.drawImage(imgs[0],x,Constant.FRAME_HEIGHT-i*BARRIER_HEIGHT,null );
        }
        //绘制头
        int y=Constant.FRAME_HEIGHT-height;
        g.drawImage(imgs[1],x-(BARRIER_HEAD_HEIGHT-BARRIER_HEIGHT)/2,y,null);
        x-=speed;

        if(x<-50) { //出了屏幕外
            visible=false;
        }

    }

    //绘制悬浮障碍物
    private void drawHoverNormal(Graphics g) {
        //求出所需要的障碍物的块数
        int count = (height-BARRIER_HEAD_HEIGHT)/BARRIER_HEIGHT;
        //绘制上头
        g.drawImage(imgs[1],x,y,null);
        //for循环绘制障碍物
        for (int i = 0; i < count; i++) {
            g.drawImage(imgs[0], x, y+BARRIER_HEAD_HEIGHT+i*BARRIER_HEIGHT, null);
        }

        rect(g);

        //绘制下头
        int y11 = y+height-BARRIER_HEAD_HEIGHT;
        g.drawImage(imgs[2],x,y11,null);
        x -= speed;
        if (x < -50) {
            visible = false;
        }
    }

在BarrierLayer类添加方法从池中取出障碍物对象


    /**
     * 用于从池中获取对象,并吧参数封装成barrier 存入barriers数组中
     */
    public void insert(int x,int y ,int num,int type){
        Barrier top = Barrierpool.getPool();
        top.setX(x);
        top.setY(y);
        top.setHeight(num);
        top.setType(type);
        top.setVisible(true);
        barriers.add(top);
    }


判断小鸟与障碍物发生碰撞以及处理

给小鸟和障碍物套上矩形判定框,若两个矩形有交集就判定发生碰撞

更新Bird类的几个方法 给小鸟套上矩形

 public Bird() {
    private Rectangle rect;
    public boolean life = true;

        int w;
        for(w = 0; w < 3; ++w) {
            this.images[w] = GameUtil.loadBufferedImage(Constant.BIRD_IMG[w]);
        }

        w = this.images[0].getWidth();
        int h = this.images[0].getHeight();
        this.rect = new Rectangle(w, h); //添加矩形
    }

  public void draw(Graphics g) {
        this.flyLogic();
        g.drawImage(this.images[this.state], this.x, this.y, (ImageObserver)null);
        g.drawRect(this.x, this.y, (int)this.rect.getWidth(), this.rect.height);  //绘制矩形 用于调试
        this.rect.x = this.x;
        this.rect.y = this.y;
    }

public Rectangle getRect() {
        return this.rect;
    }
}

 更新Barrier方法,给障碍物套上矩形判定框

public class Barrier {
    private Rectangle rect;

    public Barrier() {
        this.rect = new Rectangle();
        }

    private void drawTopMormal(Graphics g) {
        int count = (this.height - BARRIRE_HEAD_HEIGHT) / BARRIRE_HEIGHT + 1;

        int y;
        for(y = 0; y < count; ++y) {
            g.drawImage(imgs[0], this.x, this.y + y * BARRIRE_HEIGHT, (ImageObserver)null);
        }

        y = this.height - BARRIRE_HEAD_HEIGHT;
        g.drawImage(imgs[2], this.x - (BARRIRE_HEAD_WIDTH - BARRIRE_WIDTH) / 2, y,         
        (ImageObserver)null);
        this.x -= this.speed;
        if (this.x < 50) {
            this.visible = false;
        }

        this.rect(g);
    }
    private void drawNomalTop(Graphics g) {
        int count = this.height / BARRIRE_HEIGHT + 1;

        int y;
        for(y = 0; y < count; ++y) {
            g.drawImage(imgs[0], this.x, 500 - y * BARRIRE_HEIGHT, (ImageObserver)null);
        }

        y = 500 - this.height;
        g.drawImage(imgs[1], this.x - (BARRIRE_HEAD_WIDTH - BARRIRE_WIDTH) / 2, y, (ImageObserver)null);
        this.x -= this.speed;
        if (this.x < -50) {
            this.visible = false;
        }

        this.rect(g);
    }

    public void rect(Graphics g) {
        int x1 = this.x;
        int y1 = this.y;
        int w1 = imgs[0].getWidth();
        g.setColor(Color.blue);
        g.drawRect(x1, y1, w1, this.height);
        this.setRecyangle(x1, y1, w1, this.height);
    }

    public void setRecyangle(int x, int y, int width, int height) {
        this.rect.x = x;
        this.rect.y = y;
        this.rect.width = width;
        this.rect.height = height;
    }
}

在BarrierLayer类中添加判断相撞的方法

    public boolean collideBird(Bird bird){
        for (int i = 0; i < barriers.size(); i++) {
            Barrier barrier=barriers.get(i);

            //判断矩形是否相交(小鸟与障碍物是否碰撞)
            if(barrier.getRect().intersects(bird.getRect())){
                System.out.println("撞上了");
                bird.life=false;  //可设置无敌
            }
        }
        return false;
    }

用循环让小鸟与每个障碍物都能判定,碰上了就死亡重开

更新GameFrame类里的方法,使小鸟碰撞后输出文字以及暂停画面

    public void update(Graphics g){ //更新画面
        if(bird.life) { //小鸟活着
            //得到图片的画笔
            Graphics graphics = buffimg.getGraphics();
            //画图
            gameBackGround.draw(graphics);
            bird.draw(graphics);
            gameFrontGround.draw(graphics);
            gameBarrierLayer.draw(graphics, bird);

            //一次性将图片绘制到屏幕中
            g.drawImage(buffimg, 0, 0, null);
        }else{  //游戏结束
            String over="寄";
            g.setColor(Color.red);
            g.setFont(new Font("微软雅黑",1,300));
            g.drawString(over,FRAME_HEIGHT/2,FRAME_HEIGHT/2+100);


        }

    }

更新bird类,早flyLogic方法添加,判断小鸟是否飞到地下摔死



            if(y>FRAME_HEIGHT-10){  //防止下沉
                life=false;
            }
    

添加计时器

添加GameTime类

public class GameTime {

    //开始
    private long beginTime;
    //结束
    private long endTime;
    //时间差
    private long differ;

    public GameTime(){};

    public void begin(){
        beginTime=System.currentTimeMillis();
    }
    public long differ(){
        endTime=System.currentTimeMillis();
        return differ=(endTime-beginTime)/1000;
    }
}

时间差用秒表示,并实时更新

更新BarrierLayer类的logic方法,实时把生存时间实时输出到屏幕上

public void logic(Graphics g){
        if(barriers.size()==0){  //第一个障碍物
            ran();
            gameTime.begin(); //游戏开始时就计时

           /* Barrier top=new Barrier(Constant.FRAME_WIDTH,0,numberTop,0);
            barriers.add(top);
            Barrier down=new Barrier(Constant.FRAME_WIDTH,Constant.FRAME_HEIGHT-numberDown,numberDown,2);
            barriers.add(down);*/

            insert(Constant.FRAME_WIDTH,0,numberTop,0);
            insert(Constant.FRAME_WIDTH,Constant.FRAME_HEIGHT-numberDown,numberDown,2);

        }else{

            long differ= gameTime.differ();
            g.setColor(Color.white);
            g.setFont(new Font("微软雅黑",1,25));
            g.drawString("你已经被折磨了: "+differ+" 秒惹",45,70);

            
            //判断最后一个障碍物是否完全进入屏幕内
            Barrier last = barriers.get(barriers.size()-1);

            if(last.isInFrame()){  //条件成立,继续绘制下一组障碍物

                 ran();
                System.out.println("number:"+number);
                 if(number<50){  //十分之一概率是悬浮的障碍物
                                                        //high
                      insert(Constant.FRAME_WIDTH,200,300,4);
                 }else if(number>450){//十分之一摆动障碍物
                      insert(Constant.FRAME_WIDTH,150,200,6);
                 }
                 else{
                     insert(Constant.FRAME_WIDTH,0,numberTop,0);
                     insert(Constant.FRAME_WIDTH,Constant.FRAME_HEIGHT-numberDown,numberDown,2);
                 }

                /* Barrier top=new Barrier(Constant.FRAME_WIDTH,0,numberTop,0);
                 barriers.add(top);
                 Barrier down=new Barrier(Constant.FRAME_WIDTH,Constant.FRAME_HEIGHT-numberDown,numberDown,2);
                 barriers.add(down);*/


             }
        }
    }

 

 添加两种特殊障碍物

更新bairrer类,添加或更新方法

          //根据不同的类型绘制障碍物
    public void draw(Graphics g){
        switch (type){
            case TYPE_TOP_NORMAL :
                drawTopNormal(g);
                break;
            case TYPE_BOTTOM_NORMAL:
                drawNomalTOP(g);
                break;
            case TYPE_HOVER_NORMAL:
                drawHoverNormal(g);
                break;
            case TYPE_MOBILE:
                drawMobile(g);
                break;
        }

    }

  //根据不同的类型绘制障碍物
    public void draw(Graphics g){
        switch (type){
            case TYPE_TOP_NORMAL :
                drawTopNormal(g);
                break;
            case TYPE_BOTTOM_NORMAL:
                drawNomalTOP(g);
                break;
            case TYPE_HOVER_NORMAL:
                drawHoverNormal(g);
                break;
            case TYPE_MOBILE:
                drawMobile(g);
                break;
        }

    }


//绘制悬浮障碍物
    private void drawHoverNormal(Graphics g) {
        //求出所需要的障碍物的块数
        int count = (height-BARRIER_HEAD_HEIGHT)/BARRIER_HEIGHT;
        //绘制上头
        g.drawImage(imgs[1],x,y,null);
        //for循环绘制障碍物
        for (int i = 0; i < count; i++) {
            g.drawImage(imgs[0], x, y+BARRIER_HEAD_HEIGHT+i*BARRIER_HEIGHT, null);
        }

        rect(g);

        //绘制下头
        int y11 = y+height-BARRIER_HEAD_HEIGHT;
        g.drawImage(imgs[2],x,y11,null);
        x -= speed;
        if (x < -50) {
            visible = false;
        }
    }

    private void drawMobile(Graphics g) {
        //求出所需要的障碍物的块数
        int count = (height-BARRIER_HEAD_HEIGHT)/BARRIER_HEIGHT;
        //绘制上头
        g.drawImage(imgs[1],x,y,null);
        //for循环绘制障碍物
        for (int i = 0; i < count; i++) {
            g.drawImage(imgs[0], x, y+BARRIER_HEAD_HEIGHT+i*BARRIER_HEIGHT, null);
        }

        rect(g);

        //绘制下头
        int y11 = y+height-BARRIER_HEAD_HEIGHT;
        g.drawImage(imgs[2],x,y11,null);
        x -= speed;
        if (x < -50) {
            visible = false;
        }

        if(mob){  //往下
            y+=5;
            if(y>=Constant.FRAME_HEIGHT-300){
                mob=false;
            }
        }else if(!mob){ //往上
            y-=5;
            if(y<=100){
                mob=true;
            }
        }
    }

更新BarrierLayer的logic方法里的绘制障碍物逻辑

 if(last.isInFrame()){  //条件成立,继续绘制下一组障碍物

                 ran();
                System.out.println("number:"+number);
                 if(number<50){  //十分之一概率是悬浮的障碍物
                                                        //high
                      insert(Constant.FRAME_WIDTH,200,300,4);
                 }else if(number>450){//十分之一摆动障碍物
                      insert(Constant.FRAME_WIDTH,150,200,6);
                 }
                 else{
                     insert(Constant.FRAME_WIDTH,0,numberTop,0);
                     insert(Constant.FRAME_WIDTH,Constant.FRAME_HEIGHT-numberDown,numberDown,2);
                 }

                /* Barrier top=new Barrier(Constant.FRAME_WIDTH,0,numberTop,0);
                 barriers.add(top);
                 Barrier down=new Barrier(Constant.FRAME_WIDTH,Constant.FRAME_HEIGHT-numberDown,numberDown,2);
                 barriers.add(down);*/


             }

十分之一生成两个特殊障碍物的其中一个

死亡后重开功能:

在BarrierLayer类中添加重开后清空障碍物对象池的功能

public void restart(){
        barriers.clear();
    }

在GameFrame类更新键盘监听,死后按空格可以重开

    public void add(KeyEvent e){  //KeyEvent类负责捕获键盘事件
        switch (e.getKeyCode()){  //返回按下键盘对应的码
            case KeyEvent.VK_UP:
                bird.fly(1);
                break;
            case KeyEvent.VK_SPACE:  //死亡重开
                if(bird.life==false){
                    restart();
                }
        }
    }

在Bird类中添加重开复活方法

public void restartDraw(){
        life=true;
        x=300;
        y=300;
    }

在GameFrame类添加重开方法

   public void restart(){
        gameBarrierLayer.restart();
        bird.restartDraw();
    }

保存最高记录

在项目目录下建立一个文本文件,用于记录数据

在BarrierLayer类中添加文本IO操作

 //该类主要用于文件和目录的创建、文件的查找和文件的删除

    File file=new File("record.txt");

    //用于得到文件中的数据
    public int getTxt(){
        //BufferedReader类从字符输入流中读取文本并缓冲字符,以便有效地读取字符,数组和行
        BufferedReader in = null;
        try {
            in = new BufferedReader(new FileReader(file));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        int read = 0;

        // 字符串转化int      读取文本一行
        try {
            read=Integer.parseInt(in.readLine());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return read;
    }

    //用于储存数据
    public void setTxt(String str){
        //该类按字符向流中写入数据
        FileWriter fileWriter= null;
        try {
            fileWriter = new FileWriter(file);
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            fileWriter.write(str); //写入字符串,覆盖数据
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            fileWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

 文本内容只有一行一个数据,写入时覆盖该数据以更新

在BarrierLayer类中logic方法部分内容

  if(barriers.size()==0){  //第一个障碍物
            ran();
            gameTime.begin(); //游戏开始时就计时

           /* Barrier top=new Barrier(Constant.FRAME_WIDTH,0,numberTop,0);
            barriers.add(top);
            Barrier down=new Barrier(Constant.FRAME_WIDTH,Constant.FRAME_HEIGHT-numberDown,numberDown,2);
            barriers.add(down);*/

            insert(Constant.FRAME_WIDTH,0,numberTop,0);
            insert(Constant.FRAME_WIDTH,Constant.FRAME_HEIGHT-numberDown,numberDown,2);

        }else{

            long differ= gameTime.differ();
            g.setColor(Color.white);
            g.setFont(new Font("微软雅黑",1,25));
            g.drawString("你已经被折磨了: "+differ+" 秒惹",45,70);

            //显示最高成绩
            txt=getTxt();

            //System.out.println(txt);
            if(differ <txt){  //当前的记录小于等于最高纪录
                g.drawString("你最牛逼的记录是: "+txt+"秒",700,70);
            }else{  //更新最高纪录
                setTxt(String.valueOf(differ));
                g.drawString("你创造了最吊记录: "+getTxt()+"秒",700,70);
            }

实时读取文本数据,若创造了最高纪录,换不同的文字,并实时更新最高记录

until包下的常数Constant类(每个人根据自己需求内容都不同)

public class Constant {
    //窗口的大小
    public static final int FRAME_WIDTH=1000;
    public static final int FRAME_HEIGHT=700;

    public static final int Barrier_SPEED=3;

    public static final int Bird_ACC=1;

    public static final int Bird_SPEED=10;

    //窗口的标题
    public static final String FRAME_TITLE="*********";

    //窗口初始化位置
    public static final int FRAME_X=200;
    public static final int FRAME_Y=200;

    public static final String BK_IMG_PATH="img/bird_bk.png";    //图片路径

    public static final Color BK_COLOR=new Color(0x4B4CF);  //封装背景颜色

   public static final String[] BIRD_IMG={"img/bird_normal.png","img/bird_up.png","img/bird_down.png"};
//    public static final String[] BIRD_IMG={"img/1.jpg","img/1.jpg","img/1.jpg"};

    public static final String[] BARRIER_IMG_PATH={  //障碍物图片
            "img/barrier.png","img/barrier_up.png","img/barrier_down.png"
    };

}