java小游戏 : 飞翔的小鸟 (完整版)

前言

飞翔的小鸟 小游戏 可以作为 java入门阶段的收尾作品 ;
需要掌握 面向对象的使用以及了解 多线程,IO流,异常处理,一些java基础等相关知识。

一 、游戏分析

1. 分析游戏逻辑

(1)先让窗口显示出来,然后绘制 游戏的背景

(2)让小鸟显示在屏幕中,并且可以上下飞

(3)屏幕出现闪烁,解决闪烁问题 : 用双缓冲,就是将内容绘制到 一张图片上,然后再显示出来

(4)让障碍物显示出来,并且让障碍物可以移动起来

(5)碰撞检测

(6)绘制刚开始的页面和死亡后的页面

(7)让 障碍物 动起来

2.代码分析

(1)app 包 :(游戏启动类)
GameApp 类 作为 游戏启动类 。
(2)main 包 : (游戏主要类)
① Bird 类
② Barrier 类
③ BarrierPool 类
④ GameBackGround 类
⑤ GameBarrierLayer 类
⑥ GameFrame 类
⑦ GameReady 类
⑧ GameTime 类
(3)util 包 :(游戏工具类)
① Constant 类
② Gameutil 类
③ MusicUtil 类

二、代码展示 (每个类代码最上面都有该类的解释)

(1) app 包

GameApp 类
import main.GameFrame;
public class GameApp {
    public static void main(String[] args) {
        new GameFrame();
    }
}

(2) main 包

① Bird 类

package main;
import util.Constant;
import util.GameUtil;
import util.MusicUtil;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
public class Bird {
    //    图片的张数
    public static final int IMG_COUNT=8;
    private Image[] imgs;
    // 将 计时 的 方法 引入进来
    private GameTime gameTime;
    // 定义 记分牌 和 结束 时候的 图片
    private BufferedImage over;
    private BufferedImage score;
    //  定义 一下 小鸟 位置 属性,即起始位置
    private static int x;
    private static int y;
    //  定义 小鸟 状态的变量
    private int state = STATE_NORMAL;
    public static final int STATE_NORMAL = 0;
    public static final int STATE_UP = 1;
    public static final int STATE_DOWN = 2;
    public static final int STATE_DIE = 3;
    public static final int STATE_DIED = 4;
    //  定义小鸟在 Y 轴 上的速度
    private int dealtY;
    // 小鸟 向上 飞的 最大 速度
    public static final int MAX_UP_DEALTY=20;
    // 小鸟 向下 飞的 最大 速度
    public static final int MAX_DOWN_DEALTY=15;
    public Rectangle getRect() {
        return rect;
    }
    // 给 小鸟 添加 小框 做碰撞测试
    private Rectangle rect;  // 设置 矩形 特定的 方法
    public static final int RECT_DESC = 2;  // 让小鸟矩形框 在原来的基础上 再减少 2, 让矩形框 再 小一点
    // 对 小鸟 照片进行 初始化
    public Bird(){
        imgs=new Image [IMG_COUNT];
        gameTime=GameTime.getInstance();
        for (int i=0;i<IMG_COUNT;i++){      // 将小鸟 照片 存进这个数组
            imgs[i]= GameUtil.loadBufferedImage(Constant.BIRDS_IMG_PATH[i]);
        }
        // 小鸟 初始位置 坐标,窗口的 中心点
        this.x=Constant.FRAME_WIDTH >> 1 ;
        this.y=Constant.FRAME_HEIGHT >> 1 ;
        // 给 鸟 加上 小框框
        int w = imgs[0].getWidth(null);
        int h = imgs[0].getHeight(null);
        int x=this.x-w/2;
        int y=this.y-h/2;
        rect = new Rectangle(x+RECT_DESC,y+RECT_DESC,w-RECT_DESC*2,h-RECT_DESC*2);
        //               初始时候的   x坐标      y坐标                 宽度             高度
    }
    //  画  小鸟 此时的 状态的
    public void draw(Graphics g){
        flyLogic();
        if (gameTime.getSecondTime() < 10){
            int index=state >STATE_DIE ?STATE_DIE :state;  // 判断 小鸟 现在是 怎样的 状态
            int halfImgW = imgs[index].getWidth(null) >> 1 ;
            int halfImgH = imgs[index].getHeight(null) >> 1 ;
            g.drawImage(imgs[index],x-halfImgW,y-halfImgH,null);
        }else{
            int index=state >STATE_DIE ?STATE_DIE :state;  // 判断 小鸟 现在是 怎样的 状态
            int halfImgW = imgs[index].getWidth(null) >> 1 ;
            int halfImgH = imgs[index].getHeight(null) >> 1 ;
            g.drawImage(imgs[index+4],x-halfImgW,y-halfImgH,null);
        }
        // 做碰撞测试用的,先 把鸟 加上 小框框
//        g.setColor(Color.BLACK);
//        g.drawRect( (int)rect.getX(), (int)rect.getY(), (int)rect.getWidth(), (int)rect.getHeight());
        //  开始 绘制 游戏结束 和 记分牌
        if (state == STATE_DIED){
            drawGameOver(g);
        }else{ // 此时 小鸟 还没有死亡,需要 绘制 计时器
            drawTime(g);
        }
    }
   // 绘制 游戏 结束 时候 的画面 的 方法
    private void drawGameOver(Graphics g){
        // 绘制 over 的 牌子
        int x =Constant.FRAME_WIDTH-over.getWidth() >>1 ;
        int y =Constant.FRAME_HEIGHT/3;
        g.drawImage(over,x,y,null);
        // 绘制 score 的牌子
        x=Constant.FRAME_WIDTH-score.getWidth() >>1;
        y=Constant.FRAME_HEIGHT/2;
        g.drawImage(score,x,y,null);
        // 将 最终 的 分数 绘制 在 记分牌上
        g.setFont(Constant.TIME_FONT);   // 设置字体属性
        g.setColor(Color.WHITE);   // 设置 颜色
        g.drawString(Long.toString(gameTime.getSecondTime()),480,500);  // 这里的 x 和 y 是 分数 最终 的显示 位置
    }
    // 绘制 游戏 上方 的 计时器
    private void drawTime(Graphics g){
        // 显示 小鸟的飞行时间
        g.setFont(Constant.TIME_FONT);
        g.setColor(Color.WHITE);
        g.drawString(Long.toString(gameTime.getSecondTime()),500,88);
    }
     // 按空格 调用 这个方法   fly()和down() 主要作用是  改变小鸟上下飞的速度
    public void fly(){
        // 状态 只 改变一次
        if (state == STATE_UP ||  state == STATE_DIE || state == STATE_DIED) {
            return;
        }
        // 开始 计时
        if(gameTime.isReady()){
//            MusicUtil.playFly(); // 调用 音乐
            gameTime.startTiming();
        }
//        MusicUtil.tiao();
        state=STATE_UP;
        dealtY=0;
    }
    public void down(){
        if (state == STATE_DOWN || state == STATE_DIE || state==STATE_DIED)
           return;
        state=STATE_DOWN;
        dealtY=0;
    }
    //  改变 小鸟 在 y 轴 上的 位移
    private void flyLogic (){
        switch (state){
            case STATE_NORMAL : break;
            case STATE_UP :
                dealtY+=3;
                if (dealtY > MAX_UP_DEALTY){
                    dealtY = MAX_UP_DEALTY;
                }
                y -= dealtY;
                rect.y -= dealtY;
                //  撞上 游戏上侧 则死亡
                if (y < (imgs[state].getHeight(null) >> 1)+ Constant.TOP_BAR_HEIGHT){
                    die();
                }
                break;
            case STATE_DOWN :
                dealtY+=2;
                if (dealtY > MAX_DOWN_DEALTY){
                    dealtY = MAX_DOWN_DEALTY;
                }
                y+=dealtY;
                rect.y += dealtY;
                //  撞上 游戏下侧 则死亡
                if (y > Constant.FRAME_HEIGHT-(imgs[state].getHeight(null) >> 1)){
                    die();
                }
                break;
            case STATE_DIE :  // 死亡 下路 过程
                dealtY++;
                if (dealtY > MAX_DOWN_DEALTY){
                    dealtY = MAX_DOWN_DEALTY;
                }
                y+=dealtY;
                // 最后 静止 在 游戏框 下侧
                if (y > Constant.FRAME_HEIGHT-(imgs[state].getHeight(null) >> 1)){
                    y=Constant.FRAME_HEIGHT-(imgs[state].getHeight(null) >> 1);
                    died();
                }
                break;
            case STATE_DIED :
                break;
        }
    }
    // 重置 小鸟
    public void reset(){
        state=STATE_NORMAL;
        dealtY=0;
        x=Constant.FRAME_WIDTH >> 1 ;
        y=Constant.FRAME_HEIGHT >> 1 ;
        int h=imgs[0].getHeight(null);
        rect.y=this.y-h/2+RECT_DESC;
        gameTime.reset();
    }
   //  小鸟 死亡 的过程
    public void die(){
        state=STATE_DIE;
                  // 结束 计时
        gameTime.endTiming();
        // 加载 游戏结束 时候的 资源
            over=GameUtil.loadBufferedImage(Constant.OVER_IMG_PATH);
            score=GameUtil.loadBufferedImage(Constant.SCORE_IMG_PATH);
    }
     // 小鸟 死亡了
    public void died(){
        state = STATE_DIED;
        GameFrame.setGameState(GameFrame.STATE_OVER);
    }
    // 判断 小鸟 是否 死亡
    public boolean isDied(){
        return state==STATE_DIED;
    }
}

②Barrier 类

/*
        这个类里面   绘制了 四种 类型的 障碍物
                   设置了  三大 类型的 障碍物 绘制的 方法
                   给 悬浮在 中间的 障碍物 添加 逻辑,使其 能 移动
                   设置了 所有 障碍物的 属性
                   给 障碍物 添加了 矩形框
*/
import util.Constant;
import util.GameUtil;
import java.awt.*;
import java.awt.image.BufferedImage;
public class Barrier  {
  private static BufferedImage [] imgs;
  static {   // 静态代码块,最先被执行
      final int COUNT = 3;
      imgs = new BufferedImage[COUNT];
      for (int i=0;i<COUNT;i++){   // 照片赋值
          imgs[i]= GameUtil.loadBufferedImage(Constant.BARRIERS_IMG_PATH[i]);
      }
  }
  // 障碍物 图片的 长宽
  public static final int BARRIER_WIDTH = imgs[0].getWidth();
  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();
  // 添加 障碍物 是否可见 状态 true  代表 可见
  private boolean visible;
  // 给 柱子 添加 小框框
  private Rectangle rect;
  // 这个 x,y 是 障碍物的坐标
  private int x,y;
  // 移动 障碍物 的坐标  在 y 轴一直运动 的坐标
  private int dealtY;
  public static final int MAX_DEALY =66; // 移动障碍物 下降的位移
  private int width,height;  // 一个正常 障碍物的 总长度和总宽度
  // 移动的 障碍物 是否 在向下 移动
  private boolean isDown = true;
  //   障碍物 的移动 速度
  private int speed=7;
  //   障碍物 一共有 四种 情况
  private int type;
  public static final int TYPE_TOP_NORMAL=0;
  //    public static final int TYPE_TOP_HARD=1;
  public static final int TYPE_BOTTOM_NORMAL=2;
  //    public static final int TYPE_BOTTOM_HARD=3;
  public static final int TYPE_HOVER_NORMAL=4;
  public static final int TYPE_HOVER_HARD=5;
  public Barrier () {
      this.width=BARRIER_WIDTH;
      rect = new Rectangle();
      rect.width=this.width;
  }
//  选择 绘画 类型
  public void draw(Graphics g,Bird bird){
      switch (type){
          case TYPE_TOP_NORMAL :
              drawTopNormal(g);
              break;
          case TYPE_BOTTOM_NORMAL :
              drawBottomNormal(g);
              break;
          case TYPE_HOVER_NORMAL :
          case TYPE_HOVER_HARD :
              drawHoverNormal(g);
              break;
      }
      // 绘制 障碍物的 矩形块
//        g.setColor(Color.RED);
//        g.drawRect((int)rect.getX(),(int)rect.getY(),(int)rect.getWidth(),(int)rect.getHeight());
      // 鸟 死亡之后,后面不会在出 障碍物
      if (bird.isDied()){  // 判断鸟是否死亡
          return ;   // 结束 该 方法 ,返回调用出 ,即 此时 不会在 绘制 障碍物了
      }
      logic();
  }
  /*
    绘制 从上到下的 障碍物
   */
  private void drawTopNormal(Graphics g){
      // 绘制 上半部分 拼接 个 个数
      int COUNT = (height-BARRIER_HEAD_HEIGHT)/BARRIER_HEIGHT;
          for (int i = 0; i < COUNT; i++) {
              g.drawImage(imgs[0], x, y + i * BARRIER_HEIGHT + BARRIER_HEAD_HEIGHT, null);
          }
          // 绘制 障碍物 的头
          int y=this.y+height-BARRIER_HEAD_HEIGHT;
          g.drawImage(imgs[2],x-(BARRIER_HEAD_WIDTH-BARRIER_WIDTH),y,null);
  }
  /*
    绘制 从下往上的 障碍物
   */
  private void drawBottomNormal(Graphics g){
      // 绘制 下半部分 拼接 个 个数
      int count = (height-BARRIER_HEAD_HEIGHT)/BARRIER_HEIGHT +1 ;
          for (int i = 0; i < count; i++) {
              g.drawImage(imgs[0], x, y + i * BARRIER_HEIGHT + BARRIER_HEAD_HEIGHT, null);}
          //  绘制 障碍物 的头
          g.drawImage(imgs[1],x-(BARRIER_HEAD_WIDTH-BARRIER_WIDTH>>1),y,null);

  }
  /*
       绘制 在 中间的 障碍物
  */
  private void drawHoverNormal(Graphics g){
     // 先绘制 上面的 头
      g.drawImage(imgs[1],x-(BARRIER_HEAD_WIDTH-BARRIER_WIDTH>>1),y+dealtY,null);
      //  绘制 中间的 部分
      int count = (height - BARRIER_HEAD_HEIGHT*2)/BARRIER_HEIGHT+1;
          for (int i = 0; i < count; i++) {
              g.drawImage(imgs[0], x, y + i * BARRIER_HEIGHT + BARRIER_HEAD_HEIGHT+dealtY, null);
          }
          // 绘制 下面的 头
          int y=this.y+dealtY+height-BARRIER_HEAD_HEIGHT;
          g.drawImage(imgs[2],x-(BARRIER_HEAD_WIDTH-BARRIER_WIDTH>>1),y,null);
  }
  /*
       障碍物 的 逻辑 部分
   */
  private void logic(){
      x -= speed;
      rect.x -=speed;
      // 障碍物 完全移除了 屏幕
      if (x<-BARRIER_WIDTH){
          visible =false;
      }
      // 给 移动的 障碍物 添加 逻辑
      switch (type){
          case TYPE_HOVER_HARD :
              if (isDown){   // 障碍物 下移
                  dealtY++;
                  if(dealtY > MAX_DEALY){
                      isDown=false;
                  }
              }
              else{   //  障碍物  上移
                  dealtY--;
                  if (dealtY == 0){
                      isDown=true;
                  }
              }
              rect.y=this.y+dealtY;
      }
  }
  /*
  判断 障碍物 是否 完全 进入到 游戏 窗口 中
  即 此时柱子的x坐标加上柱子的宽度 如果小于 整个屏幕的宽度,那么 说明 障碍物完全出现在了 游戏窗口中
   */
  public boolean isInFrame(){
      return x+BARRIER_WIDTH < Constant.FRAME_WIDTH;
  }
  public int getX(){ // 此时 该 障碍物的 X 轴的位置
      return x;
  }  // 返回 当前 障碍物的 x 坐标, 以方便控制 两个障碍物 之间的距离
  public boolean isVisible() {
      return visible;
  } // 返回障碍物 是否可见,即是否还在 画面内

  public Rectangle getRect() {
      return rect;
  }
//         设置 障碍物的 属性   (x坐标, y坐标,  高度  ,      类型,     是否可见)
  public void setAttribute(int x,int y,int height,int type,boolean visible) {
      this.x=x;
      this.y=y;
      this.height=height;
      this.type=type;
      this.visible=visible;
      setRectangle(x,y,height);
      dealtY=0;
      isDown=true;
  }
  //  设置 障碍物 矩形框  的 属性
  public void setRectangle(int x,int y,int height) {
      rect.x=x;
      rect.y=y;
      rect.height=height;
  }
}

③ BarrierPool 类

import java.util.ArrayList;
import java.util.List;
/*
*   障碍物的 对象池
*   为了 避免 反复 创建和 销毁 对象
*/
public class BarrierPool {
   static List<Barrier> pool =new ArrayList<Barrier>();
  // 对象 池中 初始 的对象的个数
  public static final int INIT_BARRIER_COUNT = 16;
  // 最大个数
  public static final int MAX_BARRIER_COUNT = 20;

  static {
  	// 初始化 池中的 对象
  	for (int i=0;i<INIT_BARRIER_COUNT;i++) {
  		pool.add(new Barrier());
  	}
  }
  public static Barrier get() {
  	int size = pool.size();
  	if (size>0) {
  		return pool.remove(size-1);
  	}else {
  		// 池塘被 掏空,只能返回 一个新对象
  		return new Barrier();
  	}
  }
  public static void giveBack(Barrier barrier) {
  	if (pool.size()<MAX_BARRIER_COUNT) {	
  		pool.add(barrier);
  	}
  }
}

④ GameBackGround 类

/*
        这个类里面   填充了 游戏页面你的 背景颜色
                   将 最下面 草地的 照片 添加进去
 */
import util.Constant;
import util.GameUtil;
import java.awt.*;
public class GameBackGround {
    private Image bkImg;
    public GameBackGround(){
        bkImg=GameUtil.loadBufferedImage(Constant.BK_IMG_PATH);
    }
    public void draw(Graphics g){
        g.setColor(Constant.BK_COLOR);   // 设置 背景填充 颜色
        g.fillRect(0,0,Constant.FRAME_WIDTH,Constant.FRAME_HEIGHT); //  这个是 完全 填充 背景颜色;
        //  得到 游戏 地面 图片 的 大小
        int imgW=bkImg.getWidth(null);
        int imgH=bkImg.getHeight(null);
        int count=Constant.FRAME_WIDTH/imgW+1;   //  需要重复 叠加的 图片 个数
        for (int i=0;i<count;i++){
            g.drawImage(bkImg,imgW*i,Constant.FRAME_HEIGHT - imgH , null  );
        }
    }
}

⑤ GameBarrierLayer 类

/*
           绘制 障碍物
           将 上下型 和 中间型 的 柱子 添加到  barriers 集合中
           对 障碍物 和 小鸟 进行 碰撞测试
           重新开始游戏,对 障碍物 进行 清空
*/
import util.Constant;
import util.GameUtil;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
public class GameBarrierLayer {
//         障碍物 出现的 规则
   public static final int TYPE1_VER_INTERVAL =Constant.FRAME_HEIGHT>>2;  // 两个 上下 障碍物中间 空 的距离
   public static final int TYPE1_MIN_HEIGHT =Constant.FRAME_HEIGHT>>3;
   public static final int TYPE1_MAX_HEIGHT =(Constant.FRAME_HEIGHT>>3)*5;
   private List<Barrier> barriers;  // 障碍物的集合
   public GameBarrierLayer(){
       barriers = new ArrayList<>();
   }
   public void draw(Graphics g,Bird bird) {
       for (int i=0;i<barriers.size();i++) {
           Barrier barrier=barriers.get(i);
           if (barrier.isVisible()) {  // 障碍物 可见 则 画出来
               barrier.draw(g,bird);
           }
           else { // 不可见 则 移除 容器
               Barrier remove = barriers.remove(i);
               BarrierPool.giveBack(remove);
               i--;
           }
       }
       // 碰撞检测
       collideBird(bird);
       logic(bird);
   }
   // 障碍物 添加 逻辑
   private void logic(Bird bird){
       if (bird.isDied()){
           return;
       }
       if (barriers.size()==0){  //设置 第一个 障碍物
           int height = GameUtil.getRandomNumber(TYPE1_MIN_HEIGHT,TYPE1_MAX_HEIGHT);  // 取 这个区间内的长度为  障碍物的 高度
           int type = Barrier.TYPE_TOP_NORMAL;  // 类型为 上半部分的 障碍物
           Barrier top=BarrierPool.get();   //  从对象池里面获取
           top.setAttribute(Constant.FRAME_WIDTH,0,height,type,true); // 设置 属性
           //             (  起始的x坐标,最右端  ,  y坐标为0 ,高度  , 类型, 是否可见    )
           type=Barrier.TYPE_BOTTOM_NORMAL;  // 类型为 下半部分的  障碍物
           Barrier bottom=BarrierPool.get();  // 从对象池里面获取 并 设置属性
           bottom.setAttribute(Constant.FRAME_WIDTH,height+TYPE1_VER_INTERVAL,Constant.FRAME_HEIGHT-height-TYPE1_VER_INTERVAL,type,true);
          //              (    x坐标也是最右端     ,      y = 上半部分 + 中间的间距  ,    高度 = 屏幕的高度 - 上半部分 - 中间间距 ,                  类型,  可见)
           barriers.add(top);  // 在 集合 里面 添加 上半部分
           barriers.add(bottom);  // 在 集合 里面 添加  下半部分
       }else{  // 设置 后面的 障碍物
           //  判断 最后一个 障碍物 是否完全 进入到 游戏窗口 中
           Barrier last=barriers.get(barriers.size()-1);
           if (last.isInFrame()){  // 此时说明障碍物完全出现在了屏幕中, 然后开始添加下一对
               if (GameTime.getInstance().getSecondTime() < GameTime.HOVER_BARRIER_TIME) {
                   addNormalBarrier(last); // 如果 游戏开始时间 < 规定的悬浮障碍物出现的时间  则 添加上下普通障碍物
               }else{
                   try {
                       if (GameUtil.isInAnyProbability(1,2)){
                           addHoverBarrier(last);  //  1/2 的概率 添加 悬浮 障碍物
                       }else {
                           addNormalBarrier(last); //  1/2 的概率 添加 上下 障碍物
                       }
                   } catch (Exception e) {
                       e.printStackTrace();
                   }
               }
           }
       }
   }
   // 普通的 柱子
   private void addNormalBarrier (Barrier last){
       int x=last.getX()+180;    //  这个地方是 控制 两个 障碍物的间隙的
                                 //  其中 last.getX() 是判断上一个障碍物的 X轴 的位置
       int height = GameUtil.getRandomNumber(TYPE1_MIN_HEIGHT,TYPE1_MAX_HEIGHT);
       int type = Barrier.TYPE_TOP_NORMAL;
       Barrier top=BarrierPool.get();  // 对对象池中取出
       top.setAttribute(x,0,height,type,true);// 设置属性
       type=Barrier.TYPE_BOTTOM_NORMAL;
       Barrier bottom=BarrierPool.get(); // 从对象池中 取出
   bottom.setAttribute(x,height+TYPE1_VER_INTERVAL,Constant.FRAME_HEIGHT-height-TYPE1_VER_INTERVAL,type,true); // 设置属性
       barriers.add(top);
       barriers.add(bottom);
   }
   // 中间悬浮柱子
   private void addHoverBarrier(Barrier last){
       int height = GameUtil.getRandomNumber(Constant.FRAME_HEIGHT/4,Constant.FRAME_WIDTH/3);  // 随机高度
       int type = Barrier.TYPE_HOVER_NORMAL;
       try {  // 1/2 的概率 悬浮柱子能 移动 , 1/2 的概率 悬浮柱子 不能移动
           type = GameUtil.isInAnyProbability(1,2)? Barrier.TYPE_HOVER_NORMAL:Barrier.TYPE_HOVER_HARD;
       } catch (Exception e) {
           e.printStackTrace();
       }
       int x=last.getX()+180;    //  这个地方是 控制 两个 障碍物的间隙的
       int y=GameUtil.getRandomNumber(Constant.FRAME_HEIGHT/3,Constant.FRAME_HEIGHT*3/8);  // y轴 位置 随机
       Barrier hover=BarrierPool.get();
       hover.setAttribute(x,y,height,type,true);
       barriers.add(hover);
   }


/*
 判断 障碍物 和 小鸟 是否 发生了碰撞
*/
   public boolean collideBird(Bird bird){
       for (int i=0;i<barriers.size();i++){
           Barrier barrier=barriers.get(i);
           if (barrier.getRect().intersects(bird.getRect())){  // 这里的 intersects 是 jdk 里面的方法,判断 两者 是否有交集
               bird.die();
               return true;
           }
       }
       return false;
   }

//  重新开始游戏后, 重置障碍物
   public void reset(){
       barriers.clear();  // 这个 clear 方法是 容器自带的功能
   }
}

⑥ GameFrame 类

/*
     初始化  游戏窗口
     增加 按键响应
     重新开始 游戏
     初始化 游戏中 用到的 类
     双缓冲 解决 游戏的 闪屏
     多线程 提高 程序运行 效率
*/
import org.w3c.dom.ls.LSOutput;
import util.Constant;
import util.MusicUtil;
import static util.Constant.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.net.BindException;
public class GameFrame extends Frame implements Runnable{
  // 游戏 刚开始 的界面
  private GameReady ready;
  // 创建 一个 游戏 背景
  private GameBackGround backGround;
  // 创建 小鸟
  private Bird bird;
  // 创建 碰撞物 数据
  private GameBarrierLayer barrierLayer;
  //  游戏进行 状态 有 不用的 状态
  private static int gameState;
  public static final int STATE_READY = 0;
  public static final int STATE_PLAYING = 1;
  public static final int STATE_OVER =2;
  public GameFrame(){
      initFrame();
      initGame();
  }
  // 设置 窗口 属性 (  初始化 窗口 )
  public void initFrame(){
      //设置是否可见
      setVisible(true);
      // 设置 标题
      setTitle("飞翔的小鸟");
      // 设置 大小
      setSize(FRAME_WIDTH,FRAME_HEIGHT);
      // 设置 不可 自己 放大 或 缩小
      setResizable(false);
      // 设置 初始 位置
      setLocation(FRAME_X,FRAME_Y);
      // 初始化 开始界面
      ready=new GameReady();
      // 添加 窗口 关闭事件
      addWindowListener(new WindowAdapter() {
          @Override
          public void windowClosing(WindowEvent e) {
              // 结束程序
              System.exit(0);
          }
      });
         // 添加 按键 事件
      addKeyListener(new MyKeyListener());
  }
  // 按键 感应 即 按空格 和 松开 空格 的方法
  class MyKeyListener implements KeyListener{
      @Override
      public void keyTyped(KeyEvent e) {
      }
      @Override
      public void keyPressed(KeyEvent e) {
        // 捕获 系统 传入的 按键虚拟值
          int keyCode = e.getKeyCode();
          switch (gameState){
              case STATE_READY :
                  if (keyCode == KeyEvent.VK_P){
                      setGameState(STATE_PLAYING);
                  }
                  break;
              case STATE_PLAYING :
                  if (keyCode == KeyEvent.VK_SPACE){
                      bird.fly();
                  }
                  break;
              case STATE_OVER :
                  if (keyCode == KeyEvent.VK_O){
                      resetGame();
                  }
                  break;
          }
      }

      @Override
      public void keyReleased(KeyEvent e) {
//           捕获 系统 传入的 按键虚拟值 (这个是 按键后 松开的状态)
          int keyCode = e.getKeyCode();
          switch (gameState){
              case STATE_READY :
                  break;
              case STATE_PLAYING :
                  if (keyCode == KeyEvent.VK_SPACE){
                      bird.down();
                  }
                  break;
          }
      }

  }

  // 重新开始 游戏
  private void resetGame(){
      setGameState(STATE_READY);
      barrierLayer.reset();
      bird.reset();
  }

  // 对游戏 对象 进行 初始化
  private void initGame(){
      backGround = new GameBackGround();
      barrierLayer=new GameBarrierLayer();
      bird = new Bird();
      // 音乐资源
      MusicUtil.load();

      setGameState(STATE_READY);   // 设置 默认值为 准备状态

      // 启动多线程  , 用于刷新 窗口
      new Thread(this).start();
  }
  /*
     所有 需要 绘制的内容, 都需要 在此 方法 中 绘制
     update 方式 是 jvm 调用的
     该方法 绘制的 所有内容,在调用的时候,都会别 绘制到 Frame  上来。
     update 何时 被 jvm 调用.   当 repaint 方法 被调用时  它 被调用
     g 是 画笔 , 系统 自带的
   */


  /*
      用 双缓冲 解决 屏幕闪烁的问题
      单独定义一张 图片,然后将 需要绘制 的内容,都绘制到 这张图片上来
      然后一次性的 将 图片 绘制 到窗口 中
   */
 // 这是单独定义的一张图片, 然后将需要 绘制的东西 先绘制到 这张图片上面,然后在 绘制到 窗口中
  private BufferedImage bufImg = new BufferedImage(FRAME_WIDTH,FRAME_HEIGHT,BufferedImage.TYPE_4BYTE_ABGR_PRE);


  @Override
  public void update(Graphics g) {
      Graphics bufG = bufImg.getGraphics();
      backGround.draw(bufG);

      if (gameState==STATE_READY){   // 游戏准备阶段
          // 绘制 鸟
          bird.draw(bufG);
          ready.draw(bufG);

      }else{  // 对 游戏 中 阶段 进行一个 绘制
          // 绘制 碰撞物
          barrierLayer.draw(bufG,bird);
          // 绘制 鸟
          bird.draw(bufG);
      }



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

//    @Override   多线程
  public void run() {
      while (true){
          repaint();   // 通过调用 repaint 。 让 jvm  去执行update 方法。进行重新的绘制
          try {
              Thread.sleep(GAME_INTERAVL);  // 刷新率
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      }
  }

 // 设置 游戏进行的 状态的
  public static void setGameState(int gameState) {
       GameFrame.gameState = gameState;
  }
}

⑦ GameReady 类

/*
       绘制 游戏的 开始界面
 */
import util.Constant;
import util.GameUtil;
import java.awt.*;
import java.awt.image.BufferedImage;
public class GameReady {
    private BufferedImage titleImg;
    private BufferedImage noticeImg;
    public GameReady(){
        titleImg= GameUtil.loadBufferedImage(Constant.TITLE_IMG_PATH);
        noticeImg=GameUtil.loadBufferedImage(Constant.NOTICE_IMG_PATH);
    }
    public void draw(Graphics g){
        int x =Constant.FRAME_WIDTH - titleImg.getWidth() >>1 ;  // 开始 牌的 x坐标
        int y =Constant.FRAME_HEIGHT / 3;   // 开始 牌的 y坐标
        g.drawImage(titleImg,x,y,null);   // fly_bird 的 牌子
        x=Constant.FRAME_WIDTH-noticeImg.getWidth() >>1 ;  // 牌子 下面 的东西
        y=Constant.FRAME_HEIGHT /3  <<1;
        g.drawImage(noticeImg,x,y,null);   // press 的 牌子
    }
}

⑧ GameTime 类

/*
       设置 游戏上方 出现的 计时器
 */
public class GameTime {
    private static final GameTime GAME_TIME = new GameTime();
    // 出现 悬浮障碍物的 时间
    public static final int HOVER_BARRIER_TIME=5;
    public static final int MOVING_BARRIER_TIME=12;
    // 游戏时间 状态
    private int  timeState;
    // 还没 开始 计时
    public static final int STATE_READY =0;
    // 开始 计时
    public static final int STATE_RUN = 1;
    // 结束 计时
    public static final int STATE_OVER = 2;
    // 游戏 的 计时 开始和结束
    private long startTime,endTime;
    private GameTime(){
        timeState = STATE_READY;
    }
    // 是否 准备好开始计时
    public boolean isReady(){
        return timeState==STATE_READY;
    }
    // 开始 计时 的 方法
    public void startTiming(){
        startTime=System.currentTimeMillis();// 获取 当前系统时间
        timeState=STATE_RUN;
    }
    // 结束 计时 的 方法
    public void endTiming(){
        endTime=System.currentTimeMillis();  // 获取 当前系统时间
        timeState=STATE_OVER;
    }
    /*
     游戏 用的 毫秒 来计时
     */
    public long getTime(){
        if (timeState==STATE_RUN){
            return System.currentTimeMillis()-startTime;// 游戏 运行时 的时间
        }
        return endTime-startTime;   // 游戏 结束时 的时间
    }
     // 将游戏 用的 毫秒 转化 为秒 来计时
    public long getSecondTime(){
        return getTime()/1000;
    }
    // 这个等于 返回 一个 对象,和 new 一个对象道理差不多
    public static GameTime getInstance(){
        return GAME_TIME;
    }
  // 重新开始后 , 重置时间
    public void reset() {
        timeState=STATE_READY;
        startTime=0;
        endTime=0;
    }
}

(3) util 包

① Constant 类

import java.awt.*;
public class Constant {
    // 窗口 大小
    public static final int FRAME_WIDTH=1000;
    public static final int FRAME_HEIGHT=690;
    //  窗口 初始位置
    public static final int FRAME_X=500;
    public static final int FRAME_Y=200;
    //  游戏 地面 背景 图片
    public static final String BK_IMG_PATH="img/ground.png";
    // 游戏 屏幕 刷新率
    public static final int GAME_INTERAVL=33;
    // 游戏 背景颜色
    public static final Color BK_COLOR=new Color(0x4bc4cf);
    // 小鸟 状态 图片 资源
    public static final String [] BIRDS_IMG_PATH=
        {"img/normal.png","img/up.png","img/down.png","img/die.png","img/bb.png","img/bb1.png","img/bb2.png","img/bb3.png"};
    // 标题栏的 高度
    public static final int TOP_BAR_HEIGHT = 25;
    // 障碍物 的 三种 情况 照片
    public static final String [] BARRIERS_IMG_PATH =
            {"img/barrier.png","img/barrier_up.png","img/barrier_down.png"};
    // 添加 游戏 刚开始和 结束 的照片
    public static final String TITLE_IMG_PATH = "img/title.png";
    public static final String NOTICE_IMG_PATH = "img/start.png";
    public static final String OVER_IMG_PATH = "img/over.png";
    public static final String SCORE_IMG_PATH = "img/score1.jpg";
    // 设置 计时牌 的字体
    public static final Font TIME_FONT = new Font("华文琥珀",Font.BOLD,40);
}

② Gameutil 类

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.IOException;
public class GameUtil {
    /*
             插图 必备 方法
             其中 BufferedImage 为 Image 的 子类 方法
             插入的 照片 需要 引用 这个方法
         */
    public static BufferedImage loadBufferedImage(String imgPath){
        try{
            return ImageIO.read(new FileInputStream(imgPath)) ;
        } catch (IOException e){
            e.printStackTrace();
        }
        return null;
    }
    // 随机出 两个数 之间的数字, 区间 包括左边  不包括右边
    public static int getRandomNumber(int min,int max) {
        return (int)(Math.random()*(max-min)+min);
    }
    /*
            判断是否大于这个 概率。
            左边为 分子,右边为 分母
     */
    public static boolean isInAnyProbability(int numrator , int denominator) throws Exception {
        // 处理的特殊情况
        if (numrator <=0 || denominator <=0){
            throw new Exception("传入了非法的参数!");
        }
        // 一定 发生的 事件
        if (numrator >= denominator){
            return true;
        }
        return getRandomNumber(1,denominator+1) <= numrator;
    }
}

③MusicUtil 类

import java.applet.Applet;
import java.applet.AudioClip;
import java.io.File;
public class MusicUtil {
    private static AudioClip fly;
    private static AudioClip fly1;
    // 装载音乐资源
    public static void load(){
        try{
            fly = Applet.newAudioClip(new File(("music/cf.wav")).toURL());
            fly1 = Applet.newAudioClip(new File(("music/tiao2.wav")).toURL());
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    // wav 音乐的 播放
    public static void playFly(){
        fly.play();
    }
    public static void tiao(){
        fly1.play();
    }
}

三、游戏展示

四 、素材展示

为方便大家找素材 把素材链接分享给大家 :下载链接:https://pan.baidu.com/s/1aMXZY9k-hMtWmv0hXlqeYw?pwd=0620

提取码:0620

五、感悟

代码可能过长,但是每行代码都有详细的解释哟!!!
学完了java基础,可以自己尝试做个小游戏作为检测哟!!!

希望能给您带来帮助!

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2023年12月15日
下一篇 2023年12月15日

相关推荐