学习于教程:尚硅谷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"
};
}