前言
飞翔的小鸟 小游戏 可以作为 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> 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() 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 > 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 >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 >1),y,null); } /* 障碍物 的 逻辑 部分 */ private void logic(){ x -= speed; rect.x -=speed; // 障碍物 完全移除了 屏幕 if (x 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 pool =new ArrayList(); // 对象 池中 初始 的对象的个数 public static final int INIT_BARRIER_COUNT = 16; // 最大个数 public static final int MAX_BARRIER_COUNT = 20; static { // 初始化 池中的 对象 for (int i=0;i0) { 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 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 = 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” />
五、感悟
代码可能过长,但是每行代码都有详细的解释哟!!!
学完了java基础,可以自己尝试做个小游戏作为检测哟!!!
希望能给您带来帮助!