Java_GUI小游戏--FlappyBird 橡树鸟

本文讲解的是一款来自独立游戏开发者Dong Nguyen所开发的游戏FlappyBird,也叫橡树鸟

(文章有点长,整理完自己也学到蛮多的,文章最后面有项目代码哦)

 四个类代码:

package Gary;

import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.*;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.*;

//step1:初始世界,包含背景图片,开始图片
public class World extends JPanel{
    //step1:背景图片
    BufferedImage background;
    //step1:开始图片
    BufferedImage startImg;
    //step2:增加了一个ground
    Ground ground;
    
    //step3:增加两根柱子
    Column column1;
    Column column2;
    //step3:增加标志位,标志游戏是否开始
    boolean start = false;
    //step6:添加一个游戏结束的标志位
    boolean gameover = false;
    //step5:增加一只小鸟
    Bird bird;
    //游戏得分
    int score=0;
    
    //step1:构造方法中,来对图片进行加载
    public World() throws IOException {
        background = ImageIO.read(getClass().getResource("bg.png"));
        startImg = ImageIO.read(getClass().getResource("start.png"));
        //step2:初始化这个ground
        ground= new Ground();
        //step3:初始化两根柱子
        column1 = new Column(320+100);        
        column2 = new Column(320+180+100);
        //step5:初始化这只小鸟
        bird = new Bird(140,255);  //参数表示小鸟出现的位置
        start = false;
        gameover = false;
        
    }
    
    //step1:重写父类的paint方法,重绘图形
    public void paint(Graphics g) {
        //step1:绘制背景图片
        g.drawImage(background, 0, 0, null);

    

    
        //step6:绘制游戏借宿的图片
        if(gameover == true) {
            try
            {        
                score = 0;
                //step2:初始化这个ground
                ground= new Ground();
                //step3:初始化两根柱子
                column1 = new Column(320+100);        
                column2 = new Column(320+180+100);
                //step5:初始化这只小鸟
                bird = new Bird(140,255);  //参数表示小鸟出现的位置
                start = false;
                gameover = false;
                BufferedImage end = ImageIO.read(getClass().getResource("gameover.png"));
                g.drawImage(end, 0, 0, null);
                return;
            } catch (IOException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        if(start == true) {
            //step3:绘制柱子
            column1.paint(g);
            column2.paint(g);
            //step5:绘制小鸟
            bird.paint(g);
        }
        
        //游戏得分
        g.setFont(new Font(Font.MONOSPACED,Font.BOLD,30));
        g.setColor(Color.WHITE);
        g.drawString("score="+score, 30, 50);
        //step3:判断游戏没有开始的时候绘制开始图片
        if(start ==false) {
            g.drawImage(startImg, 0, 0, null);
        }
        
        //step2:使用ground绘制可移动的地面图片
        ground.paint(g);
    }
    
    
    //step2:开始动作
    public void action() throws InterruptedException {
        //step3:给world主面板注册到鼠标点击的事件监听器上
        addMouseListener(new MouseListener() {

            @Override
            public void mouseClicked(MouseEvent e)
            {
                // TODO Auto-generated method stub
                
            }

            @Override
            public void mouseEntered(MouseEvent e)
            {
                // TODO Auto-generated method stub
                
            }

            @Override
            public void mouseExited(MouseEvent e)
            {
                // TODO Auto-generated method stub
                
            }

            @Override
            public void mousePressed(MouseEvent e)
            {
                //step6:如果游戏结束,从新进行初始化
                if(gameover == true) {
                    try
                    {
                        //step2:初始化这个ground
                        ground= new Ground();
                        //step3:初始化两根柱子
                        column1 = new Column(320+100);        
                        column2 = new Column(320+180+100);
                        //step5:初始化这只小鸟
                        bird = new Bird(140,255);  //参数表示小鸟出现的位置
                        start = false;
                        gameover = false;
                    } catch (IOException e1)
                    {
                        // TODO Auto-generated catch block
                        e1.printStackTrace();
                    }
                }
                //step3:鼠标点击一下,则游戏开始
                start = true;
                //step5:每次点击鼠标,小鸟的当前速度重新变为向上的20
                bird.flappy();
            }

            @Override
            public void mouseReleased(MouseEvent e)
            {
                // TODO Auto-generated method stub
                
            }
            
        });
        
        while(true) {
            ground.step();//可移动的图片的x--
            if(start == true) {    //step4:游戏开始之后,绘制柱子
                column1.step();
                column2.step();
                //step5:小鸟扇翅膀
                bird.step();
                //step6:碰撞检测
                if(bird.hit(column1, column2,ground)==true) {
                    start = false;
                    gameover = true;
                }
                if(bird.pass(column1, column2)) {
                    score++;
                }
            }
            repaint();//重绘
            Thread.sleep(1000/60);//每隔1/60秒重绘一次
        }
    }
    public static void main(String[] args) throws IOException, InterruptedException {
        //创建了一个窗口
        JFrame frame = new JFrame("游戏");
        //创建一个面板
        World world = new World();
        //给窗口设置基本的属性(320*480)
        frame.setSize(320,480);
        //绘制窗口不能调整大小
        frame.setResizable(false);
        //给窗口设置默认的关闭动作
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //设置窗口出现时显示在屏幕的正中
        frame.setLocationRelativeTo(null);
        //把World面板添加到窗口上
        frame.add(world);
        //让窗口frame可见
        frame.setVisible(true);
        //step2:执行开始动作
        world.action();
    }
}
World.class
package Gary;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;

//step5:设置小鸟的图片以及位置信息,以及小鸟移动、绘制方法的类
public class Bird{
    int x;
    int y;
    //step5:小鸟的初始图片
    BufferedImage bird;
    //step5:装载3张不同姿态的小鸟的图片
    BufferedImage[] birds;
    //step5:数组的下标
    int index = 0;
    //step5:重力加速度
    int g;
    //step5:间隔时间
    double t;
    //step5:初始速度 像素/s
    double v0;
    //step5:当前时刻的速度
    double vt;
    //step5:运动的距离
    double s;
    
    //step5:鸟的飞行角度
    double angle;
    //step6:小鸟的大小
    double size = 26;
    
    public Bird(int x,int y) throws IOException {
        this.x = x;
        this.y = y;
        birds = new BufferedImage[3];
        for(int i = 0;i < 3; i++) {
            birds[i] = ImageIO.read(getClass().getResource(i+".png"));
        }
        bird = birds[0];
        
        //step5:初始化物理参数
        g = 4;                   //g取重力加速度就会显得很不自然
        t = 0.25;
        v0 = 20;
        vt=v0;
    }
    
    //step5:绘制小鸟的方法
    public void paint(Graphics g) {
    //    g.drawImage(bird, x, y, null);
        //旋转坐标系
        Graphics2D g2d = (Graphics2D)g;
        g2d.rotate(angle,x,y);
        
        //以x和y为中心,来绘制图片
        int x1 = x - bird.getWidth()/2;
        int y1 = y - bird.getWidth()/2;
        g.drawImage(bird, x1, y1, null);
        
        //坐标系旋转回来
        g2d.rotate(-angle, x, y);
    }
    
    //step5:主管了小鸟的动作、扇翅膀、向上飞、向下自由落体
    public void step() {
        //*******************抛物运动************************
        double vt1 = vt;
        //计算垂直上抛运行,经过时间t以后的速度
        double v = vt1 - g * t;
        //v作为下一次计算位移时的初始速度
        vt = v;
        //运行距离
        s = vt1 * t - 0.5 * g * t * t;
        y = y - (int)s;
        
        //计算小鸟的仰角
        angle = -Math.atan(s/15);   //因为是往上飞的所以取一个负值   60像素/s * 0.25秒 =15 像素
        
        
        //*******************扇翅膀动作**************************
        index++;    //0,1,2,3,4,5,6,7,8...
        bird = birds[index/8%3];  //除以8是为了让小鸟放慢8倍的速度
    }
    
    //step5:每次点击鼠标的时候,小鸟重新获得初速度,向上飞
    //      直到下一次点击前,都一直做自由落体运动
    public void flappy() {
        vt = v0;   //v0=20
    }
    
    //检测小鸟是否与地面、天花板、柱子发生了碰撞
    //True表示发生了碰撞,False表示没有发生碰撞
    public boolean hit(Column col1,Column col2,Ground ground) {
        //判断小鸟是否与天花板发生了碰撞
        if(y-size/2<=0)
            return true;
        //判断小鸟是否与地面发生了碰撞
        if(y-size/2>ground.y)        //y表示的是地面ground的坐标,不是背景图片的纵坐标
            return true;
        //判断鸟是否与地面发生了碰撞
        if(hitColumn(col1)||hitColumn(col2)) {
            return true;
        }
        
        return false;
    }
    
    //是否碰撞柱子
    public boolean hitColumn(Column col) {
        if(x>col.x - col.width/2-size/2&&x<col.x+col.width/2+size/2) {
            if(y>col.y-col.gap /2+size/2&&y<col.y+col.gap/2-size/2) {
                return false;                //这种情况就通过
            }else {
                return true;
            }
        }
        return false;
    }
    
    public boolean pass(Column col1,Column col2) {
        return col1.x==x||col2.x==x;
    }
}
Birld.class
package Gary;

import java.awt.Graphics;
import java.awt.image.*;
import java.io.IOException;

import javax.imageio.ImageIO;

public class Ground{
    //step2:要显示的地面图片
    BufferedImage ground;
    //step2:图片的横坐标
    int x;
    //step2:图片的纵坐标
    int y;
    //step6:柱子上下空间的高度
    int gap = 109;
    
    public Ground() throws IOException {
        //step2:加载图片
        ground = ImageIO.read(getClass().getResource("ground.png"));
        //step2:设置纵坐标
        y = 400;
        
    }
    
    //step2:减小x 让图片向左移动 当x<某个数时,复位
    public void step() {
        x--;
        //(360-497(图片的长度))
        if(x<-137) {
            x=0;
        }
    }
    
    public void paint(Graphics g) {
        g.drawImage(ground, x, y, null);
    }
}
Ground.class
package Gary;

import java.awt.*;
import java.awt.image.*;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;

//Step3:柱子的类
public class Column{
    //Step3:加载柱子图片的缓存
    BufferedImage column;
    
    //step4:上下柱子之间空当的中间点的坐标
    int x;
    int y;
    //Step3:柱子图片的宽度
    int width;
    //Step3:柱子图片的高度
    int height;
    
    //step4:用来创建随机数的对象
    Random random;
    //step6:柱子上下之间的高度
    int gap = 109;
    //Step3:x1当使用本类创建柱子对象时,需确定柱子出现在哪儿(x)
    public  Column(int x1) throws IOException {
        x = x1;
        //初始化柱子的图片
        column = ImageIO.read(getClass().getResource("column.png"));
        //获得柱子图片的高度和宽度
        width = column.getWidth();
        height = column.getHeight();
        //step4:初始化random随机数对象
        random = new Random();
        y = 140 + random.nextInt(140);
    }
    
    //Step3:绘制柱子图片到World上
    public void paint(Graphics g) {
        g.drawImage(column,x-width/2,y-height/2,null);
    }
    
    //step4:用来改变柱子的横坐标,并且添加限制条件,不能一直向左移动
    public void step() {
        x--;
        if(x<-width) {    //重新摆放柱子的位置
            x = 320;
            y = 140 +random.nextInt(140);
        }
    }
}
Colunm.class

在《FlappyBird》这款游戏中,玩家只需要用一根手指来操控,点击触摸屏幕,小鸟就会往上飞,不断的点击就会不断的往高处飞。放松手指,则会快速下降。所以玩家要控制小鸟一直向前飞行,然后注意躲避途中高低不平的管子。[1]

1、在游戏开始后,点击屏幕,要记住是有间歇的点击屏幕,不要让小鸟掉下来。

2、尽量保持平和的心情,点的时候不要下手太重,尽量注视着小鸟。

3、游戏的得分是,小鸟安全穿过一个柱子且不撞上就是1分。当然撞上就直接挂掉,只有一条命。

游戏制作分六个步骤

1.创建游戏的初始世界

2.让下方的地面向左移动

3.添加点击事件,游戏开始,柱子出现

4.让柱子动起来

5.添加小鸟

6.碰撞检测

(每2个游戏步骤都有一个jar包)

小知识 public abstract boolean drawImage(img,x,y,osberver)

参数:
img - 要绘制的指定图像。如果 img 为 null,则此方法不执行任何操作。
x - x 坐标。
y - y 坐标。
observer - 转换了更多图像时要通知的对象。
返回:
如果图像像素仍在更改,则返回 false;否则返回 true。

下面我们一起来学习这款游戏的制作吧

一、创建游戏的初始世界

1.创建World类,包含背景图片、开始图片,在构造方法中初始化。

//初始世界,包含背景图片,开始图片
public class World extends JPanel{
    //背景图片
    BufferedImage background;
    //开始图片
    BufferedImage startImg;
    
    //构造方法中,来对图片进行加载
    public World() throws IOException {
        background = ImageIO.read(getClass().getResource("bg.png"));
        startImg = ImageIO.read(getClass().getResource("start.png"));
    }
    
}

2.重写paint方法,用来重新绘制图形

//重写父类的paint方法,重绘图形
    public void paint(Graphics g) {
        //绘制背景图片
        g.drawImage(background, 0, 0, null);
        //绘制开始图片
        g.drawImage(startImg, 0, 0, null);
    }
(注意"开始图片"和"背景图片"的顺序不要弄反了,图片为PNG格式,把"开始图片"先绘制到面板上,运行程序时"开始图片"会被"背景图片"覆盖)
 
 
3.main方法中创建了一个窗口,容纳并且展现World类
 
public static void main(String[] args) throws IOException {
        //创建了一个窗口
        JFrame frame = new JFrame("游戏");
        //创建一个面板
        World world = new World();
        //给窗口设置基本的属性(320*480)
        frame.setSize(320,480);
        //绘制窗口不能调整大小
        frame.setResizable(false);
        //给窗口设置默认的关闭动作
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //设置窗口出现时显示在屏幕的正中
        frame.setLocationRelativeTo(null);
        //把World面板添加到窗口上
        frame.add(world);
        //让窗口frame可见
        frame.setVisible(true);
    }
(窗口属性是由背景图片大小格式来确定的,背景图片为320*480)
 
 
运行程序:
 

(上面的三个步骤都放在同一个类里面,就不过多粘贴代码啦O(∩_∩)O)

二、让下方的地面向左移动

1.创建Ground类,包含滚动图片,x滚动的横坐标,y=400

public class Ground{
    //step2:要显示的地面图片
    BufferedImage ground;
    //step2:图片的横坐标
    int x;
    //step2:图片的纵坐标
    int y;
    
    public Ground() throws IOException {
        //step2:加载图片
        ground = ImageIO.read(getClass().getResource("ground.png"));
        //step2:设置纵坐标
        y = 400;
        
    }
    
}

 2.  用Ground.paint(Graphics g)来绘制滚动条,用Ground.step用来改变Ground图片的x坐标

public void step() {
        x--;
    }
    
    public void paint(Graphics g) {
        g.drawImage(ground, x, y, null);
    }

3.World中增减一个action,循环调用ground.step(x--),repaint()-->ground.paint(g);


最后在主函数里面开始执行这个动作

这时候运行程序可以看到下方Ground图片一直向左移动,不一会儿就漏出了背景(仔细放大可以看得出来的)

这是因为我们没有让Ground图片进行循环复位,导致图片只能一直向左边进行移动,下面我们需要再Ground这个类中改写循环调用ground.step(x--)

step方法中x不能一直--;可以算出当小于-137时,x=0

//step2:减小x 让图片向左移动 当x<某个数时,复位
    public void step() {
        x--;
        //(360-497(图片的长度))
        if(x<-137) {
            x=0;
        }
    }

地面视觉上看起来不断向后运动是在World类中action写了一个while循环不断复位,实际上都只有一张图片

while(true) {
            ground.step();//可移动的图片的x--
            repaint();//重绘
            Thread.sleep(1000/60);//每隔1/60秒重绘一次
        }

这时候运行程序就可以看到地面不断的想后移动,视觉上产生鸟不断的向前飞行

三.添加点击事件,游戏开始,柱子出现

1.构建了一个Column类,包含柱子图片,x,y横纵坐标 

x,y放置的地方在柱子左上角,一开始的(0,0)点的坐标表示柱子左上角的坐标

//Step3:柱子的类
public class Column{
    //Step3:加载柱子图片的缓存
    BufferedImage column;
    //Step3:柱子图片的横纵坐标
    int x;
    int y;
    //Step3:柱子图片的宽度
    int width;
    //Step3:柱子图片的高度
    int height;
    
    //Step3:x1当使用本类创建柱子对象时,需确定柱子出现在哪儿(x)
    public  Column(int x1) throws IOException {
        x = x1;
        //初始化柱子的图片
        column = ImageIO.read(getClass().getResource("column.png"));
        //获得柱子图片的高度和宽度
        width = column.getWidth();
        height = column.getHeight();
        y = -110;//-(height-480)/2
    }
    
    //Step3:绘制柱子图片到World上
    public void paint(Graphics g) {
        g.drawImage(column,x,y,null);
    }
}

y = -110(让柱子中间的空间出现在正中,用柱子(图片的高度-面板的高度)/2 就是柱子中心点放置的位置)

2.World类中增加三个成员变量column1,column2,start,并在构造方法中初始化两根柱子(两根柱子表示玩游戏同一个界面最多出现的柱子条数,三根就有点难度了,这里就用两根来表示)

3.注册鼠标点击事件的监听器

 World.action()方法中注册鼠标点击事件的监听器,start=true;
 world.paint()方法中public void mousePressed(MouseEvent e)添加判断条件,start=false绘制开始图片,start=true时绘制柱子

//step2:开始动作
    public void action() throws InterruptedException {
        //step3:给world主面板注册到鼠标点击的事件监听器上
        addMouseListener(new MouseListener() {

            @Override
            public void mouseClicked(MouseEvent e)
            {
                // TODO Auto-generated method stub
                
            }

            @Override
            public void mouseEntered(MouseEvent e)
            {
                // TODO Auto-generated method stub
                
            }

            @Override
            public void mouseExited(MouseEvent e)
            {
                // TODO Auto-generated method stub
                
            }

            @Override
            public void mousePressed(MouseEvent e)
            {
                //step3:鼠标点击一下,则游戏开始
                start = true;
            }

            @Override
            public void mouseReleased(MouseEvent e)
            {
                // TODO Auto-generated method stub
                
            }
            
        });
        
        while(true) {
            ground.step();//可移动的图片的x--
            repaint();//重绘
            Thread.sleep(1000/60);//每隔1/60秒重绘一次
        }
    }

    

当点击游戏背景的时候,游戏开始,绘制柱子和地面,所以前面绘制顺序也要修改

//step1:重写父类的paint方法,重绘图形
    public void paint(Graphics g) {
        //step1:绘制背景图片
        g.drawImage(background, 0, 0, null);

        //step3:判断游戏没有开始的时候绘制开始图片
        if(start ==false) {
            g.drawImage(startImg, 0, 0, null);
        }
        if(start == true) {
            //step3:绘制柱子
            column1.paint(g);
            column2.paint(g);
        }
        
        //step2:使用ground绘制可移动的地面图片
        ground.paint(g);
    }
    

程序一开始绘制背景图片->判断游戏是否开始→(开始)绘制柱子->绘制可移动的地面 

                                                                    ↓→(没开始)绘制开始图片

    (注意一定要先绘制柱子,再绘制地面图片,顺序颠倒会出现下面这种情况)

   

这时候就应该让柱子和地面绘制出现的顺序调换一下,让fround图片去覆盖柱子的图片

这时候第三步就完成了,点击游戏背景图片开始游戏,地面动起来,柱子出现,接下来就到了第四步

四.让柱子动起来

1让柱子类似地面不断向左移动

在Column类中paint方法下面增加一个step来不断改变柱子横坐标

//step4:用来改变柱子的横坐标,并且添加限制条件,不能一直向左移动
    public void step() {
        x--;
        if(x<-width) {    //width表示的是柱子图片的宽度
            x = 320;
        }
    }

在World类中action方法里面改写while(true)也就是地面不断向左移动

while(true) {
            ground.step();//可移动的图片的x--
            if(start == true) {    //step4:游戏开始之后
                column1.step();
                column2.step();
            }
            repaint();//重绘
            Thread.sleep(1000/60);//每隔1/60秒重绘一次
        }


现在就能看到柱子和地面一样的动起来了,可是这里也发现了一个问题:柱子间距太小了,这也是因为先前我们在初始化这根柱子的时候设置它们之间的间距太小了

  

修改下column2的横坐标调整大一些

间距大小可以自己来调哦

想看柱子是怎么加入进来的可以在main函数里面设置一下界面是可以调整大小的frame.setResizable(false)参数改为true

原来柱子都是不断的在游戏右边加入进来的O(∩_∩)O

接下来就要做一些调整

(游戏界面320*480       柱子要考虑到进入游戏的时候柱子不直接出现在界面上,而是从右边慢慢移动进来,所以还要修改柱子初始化的参数)

运行程序可以看到柱子是慢慢进入游戏界面的

为了让柱子中间能不固定和方便下面小鸟的碰撞检测,我们可以调整一下绘制时候柱子的默认坐标,从柱子左上角改到上下柱子之间空当的中间,(0,0)点的坐标就表示柱子中间的点

从柱子的中心点开始绘制g.drawImage(column,x-width/2,y-height/2,null)

为了让柱子随机出现,这时候我们还需要在Column中定义一个随机数变量,并进行初始化

因为柱子是上下随机动的,所以36行y也要改为  y = 140 + random.nextInt(140);

运行程序可以看到柱子现在中心点的坐标不会一直在一条直线上

可以看到柱子已经能上下随机移动,测试的时候还会发现一个问题,柱子上下移动会一直重复,一上一下而且像一个伪随机数

原因是Column类step()方法不断的重新摆放了柱子的位置,这时我们就需要对这里进行修改

public void step() {
        x--;
        if(x<-width) {    //重新摆放柱子的位置
            x = 320;
        
        }
    }

添加一句y = 140 +random.nextInt(140)来进行对柱子纵坐标修改,这里也可以不用140,数值越大两根柱子的偏差也容易越大,游戏难度就会提升,越小就很难看出柱子上下移动的间距

运行程序就发现现在柱子上下随机出现了

到这里运行程序就可以实现柱子动起来的效果了

五.添加小鸟

1.构建了一个Bird类

public class Bird{
    int x;
    int y;
    
    BufferedImage bird;
    
    public Bird(int x,int y) throws IOException {
        this.x = x;
        this.y = y;
        bird = ImageIO.read(getClass().getResource("0.png"));
    }
    
    //step5:绘制小鸟的方法
    public void paint(Graphics g) {
        g.drawImage(bird, x, y, null);
    }
}

在World类中定义一只小鸟bird,构造方法中初始化bird(x,y),小鸟的出现用paint()方法来进行绘制,小鸟是在游戏开始的时候才出现的,所以绘制放在start==true中

public void paint(Graphics g) {
        //step1:绘制背景图片
        g.drawImage(background, 0, 0, null);

        //step3:判断游戏没有开始的时候绘制开始图片
        if(start ==false) {
            g.drawImage(startImg, 0, 0, null);
        }
        if(start == true) {
            //step3:绘制柱子
            column1.paint(g);
            column2.paint(g);
            //step5:绘制小鸟
            bird.paint(g);
        }
        
        //step2:使用ground绘制可移动的地面图片
        ground.paint(g);
    }


运行程序,这时候可以看到游戏界面中就有一直小鸟了,这里的小鸟是不向前飞行的,只是地面和柱子不的向后移动,产生了小鸟不断向前飞行的视觉

这里还没有进行碰撞检测,碰撞检测是在第六步的时候进行的,下面就来对小鸟扇翅膀动作实现,图片0代表小鸟进入游戏时的初始化动作,图片1、2、3不断刷新来实现小鸟扇翅膀动作

2.添加主管了小鸟动作的step方法

在Bird类中添加一个数组,来进行对图片1、2、3的存放,开始游戏小鸟初始化图片为birds[0]

step方法更新小鸟的图片

//step5:设置小鸟的图片以及位置信息,以及小鸟移动、绘制方法的类
public class Bird{
    int x;
    int y;
    //step5:小鸟的初始图片
    BufferedImage bird;
    //step5:装载3张不同姿态的小鸟的图片
    BufferedImage[] birds;
    //step5:数组的下标
    int index = 0;
    
    public Bird(int x,int y) throws IOException {
        this.x = x;
        this.y = y;
        birds = new BufferedImage[3];
        for(int i = 0;i < 3; i++) {
            birds[i] = ImageIO.read(getClass().getResource(i+".png"));
        }
        bird = birds[0];
    }
    
    //step5:绘制小鸟的方法
    public void paint(Graphics g) {
        g.drawImage(bird, x, y, null);
    }
    
    //step5:主管了小鸟的动作
    public void step() {
        index++;    //0,1,2,3,4,5,6,7,8...
        bird = birds[index/8%3];  //除以8是为了让小鸟放慢8倍的速度
    }
}

下面到World类中while(true)不断绘制里面游戏开始的时候不断绘制小鸟扇翅膀的动作

World.action中,start==true时,bird.step()

这时候小鸟扇翅膀的动作就基本实现啦

运行程序

3.鼠标不点击,实现小鸟做自由落体运行

小鸟自由落体下坠的时候不是匀速而是越来越快的,所以下面这里就用到物理加速度

重力加速度为10会使得小鸟下坠的不自然,这里去4

先在Bird中定义小鸟实现、初始化向上向下飞行的物理变量

//step5:设置小鸟的图片以及位置信息,以及小鸟移动、绘制方法的类
public class Bird{
 int x;
 int y;
 //step5:小鸟的初始图片
 BufferedImage bird;
 //step5:装载3张不同姿态的小鸟的图片
 BufferedImage[] birds;
 //step5:数组的下标
 int index = 0;
 //step5:重力加速度
 int g;
 //step5:间隔时间
 double t;
 //step5:初始速度 像素/s
 double v0;
 //step5:当前时刻的速度
 double vt;
 //step5:运动的距离
 double s;
 
 public Bird(int x,int y) throws IOException {
  this.x = x;
  this.y = y;
  birds = new BufferedImage[3];
  for(int i = 0;i < 3; i++) {
   birds[i] = ImageIO.read(getClass().getResource(i+".png"));
  }
  bird = birds[0];
  
  //step5:初始化物理参数
  g = 4;                   //g取重力加速度就会显得很不自然
  t = 0.25;
  v0 = 20;
 }
 
 //step5:绘制小鸟的方法
 public void paint(Graphics g) {
  g.drawImage(bird, x, y, null);
 }
 
 //step5:主管了小鸟的动作、扇翅膀、向上飞、向下自由落体
 public void step() {
  //*******************抛物运动************************
  double vt1 = vt;
  //计算垂直上抛运行,经过时间t以后的速度
  double v = vt1 - g * t;
  //v作为下一次计算位移时的初始速度
  vt = v;
  //运行距离
  s = vt1 * t - 0.5 * g * t * t;
  y = y - (int)s;
  //*******************扇翅膀动作**************************
  index++; //0,1,2,3,4,5,6,7,8...
  bird = birds[index/8%3];  //除以8是为了让小鸟放慢8倍的速度
 }
}

运行程序可以看到实现了小鸟自由落体下坠

4.点击鼠标,实现小鸟向上飞

实现小鸟向上飞行只需要在Bird类中添加一个flappy()方法,每次点击鼠标的时候,小鸟重新获得初速度,向上飞直到下一次点击前,都一直做自由落体运动

//step5:主管了小鸟的动作、扇翅膀、向上飞、向下自由落体
    public void step() {
        //*******************抛物运动************************
        double vt1 = vt;
        //计算垂直上抛运行,经过时间t以后的速度
        double v = vt1 - g * t;
        //v作为下一次计算位移时的初始速度
        vt = v;
        //运行距离
        s = vt1 * t - 0.5 * g * t * t;
        y = y - (int)s;
        //*******************扇翅膀动作**************************
        index++;    //0,1,2,3,4,5,6,7,8...
        bird = birds[index/8%3];  //除以8是为了让小鸟放慢8倍的速度
    }
    
    //step5:每次点击鼠标的时候,小鸟重新获得初速度,向上飞
    //      直到下一次点击前,都一直做自由落体运动
    public void flappy() {
        vt = v0;   //v0=20
    }
 

接下里在public void mousePressed(MouseEvent e)方法中调用这个flappy方法,让鼠标每点击一次游戏界面的时候,小鸟就获得一个初速度向上飞

5.实现当小鸟向上飞、向下飞的动作

在Birld类中增加一个变量angle来计算鸟飞行的角度

计算小鸟的仰角
  angle = -Math.atan(s/15);   //因为是往上飞的所以取一个负值   60像素/s * 0.25秒 =15 像素

重写Birld类中的paint绘制方法

rotate来对图片进行旋转,小鸟向上飞的时候逆时针旋转,负值,小鸟向下飞的时候顺时针旋转,正值

//step5:绘制小鸟的方法
    public void paint(Graphics g) {
    //    g.drawImage(bird, x, y, null);
        //旋转坐标系
        Graphics2D g2d = (Graphics2D)g;
        g2d.rotate(angle,x,y);
        
        //以x和y为中心,来绘制图片
        int x1 = x - bird.getWidth()/2;
        int y1 = y - bird.getWidth()/2;
        g.drawImage(bird, x1, y1, null);
        
        //坐标系旋转回来
        g2d.rotate(-angle, x, y);
    }


这时候在运行程序,就能看到小鸟向上飞和向下飞时不同的动作

package Gary;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;

//step5:设置小鸟的图片以及位置信息,以及小鸟移动、绘制方法的类
public class Bird{
    int x;
    int y;
    //step5:小鸟的初始图片
    BufferedImage bird;
    //step5:装载3张不同姿态的小鸟的图片
    BufferedImage[] birds;
    //step5:数组的下标
    int index = 0;
    //step5:重力加速度
    int g;
    //step5:间隔时间
    double t;
    //step5:初始速度 像素/s
    double v0;
    //step5:当前时刻的速度
    double vt;
    //step5:运动的距离
    double s;
    
    //鸟的飞行角度
    double angle;
    
    public Bird(int x,int y) throws IOException {
        this.x = x;
        this.y = y;
        birds = new BufferedImage[3];
        for(int i = 0;i < 3; i++) {
            birds[i] = ImageIO.read(getClass().getResource(i+".png"));
        }
        bird = birds[0];
        
        //step5:初始化物理参数
        g = 4;                   //g取重力加速度就会显得很不自然
        t = 0.25;
        v0 = 20;
        vt=v0;
    }
    
    //step5:绘制小鸟的方法
    public void paint(Graphics g) {
    //    g.drawImage(bird, x, y, null);
        //旋转坐标系
        Graphics2D g2d = (Graphics2D)g;
        g2d.rotate(angle,x,y);
        
        //以x和y为中心,来绘制图片
        int x1 = x - bird.getWidth()/2;
        int y1 = y - bird.getWidth()/2;
        g.drawImage(bird, x1, y1, null);
        
        //坐标系旋转回来
        g2d.rotate(-angle, x, y);
    }
    
    //step5:主管了小鸟的动作、扇翅膀、向上飞、向下自由落体
    public void step() {
        //*******************抛物运动************************
        double vt1 = vt;
        //计算垂直上抛运行,经过时间t以后的速度
        double v = vt1 - g * t;
        //v作为下一次计算位移时的初始速度
        vt = v;
        //运行距离
        s = vt1 * t - 0.5 * g * t * t;
        y = y - (int)s;
        
        //计算小鸟的仰角
        angle = -Math.atan(s/15);   //因为是往上飞的所以取一个负值   60像素/s * 0.25秒 =15 像素
        
        
        //*******************扇翅膀动作**************************
        index++;    //0,1,2,3,4,5,6,7,8...
        bird = birds[index/8%3];  //除以8是为了让小鸟放慢8倍的速度
    }
    
    //step5:每次点击鼠标的时候,小鸟重新获得初速度,向上飞
    //      直到下一次点击前,都一直做自由落体运动
    public void flappy() {
        vt = v0;   //v0=20
    }
}
Birld.class

接着就到了最后一步对游戏来进行碰撞检测

六.碰撞检测

进行碰撞检测一共会有三种情况

      (1)当小鸟碰到天空的时候,游戏结束

      (2)当小鸟碰到地面的时候,游戏结束

      (3)当小鸟碰到柱子的时候,游戏结束

对这三种情况的碰撞放在Bird类中hit()碰撞的方法

1.当小鸟碰到天空的时候

public boolean hit(Column col1,Column col2,Ground ground) {
        //判断小鸟是否与天花板发生了碰撞
        if(y-size/2<=0)
            return true;
        return false;
    }

2.当小鸟碰到地面的时候

public boolean hit(Column col1,Column col2,Ground ground) {
        //判断小鸟是否与天花板发生了碰撞
        if(y-size/2<=0)
            return true;
        //判断小鸟是否与地面发生了碰撞
        if(y-size/2>ground.y)        //y表示的是地面ground的坐标,不是背景图片的纵坐标
            return true;
        return false;
    }

3.当小鸟碰到柱子的时候

定义一个鸟碰撞柱子的方法hitColumn

//是否碰撞柱子
    public boolean hitColumn(Column col) {
        if(x>col.x - col.width/2-size/2&&x<col.x+col.width/2+size/2) {
            if(y>col.y-col.gap /2+size/2&&y<col.y+col.gap/2-size/2) {
                return false;                //这种情况就通过
            }else {
                return true;
            }
        }
        return false;
    }

第一个判断条件表示鸟没有和柱子发生任何关系(鸟的x轴和柱子的x轴不重合)

第二个判断条件表示鸟能通过柱子中间的缝隙

public boolean hit(Column col1,Column col2,Ground ground) {
        //判断小鸟是否与天花板发生了碰撞
        if(y-size/2<=0)
            return true;
        //判断小鸟是否与地面发生了碰撞
        if(y-size/2>ground.y)        //y表示的是地面ground的坐标,不是背景图片的纵坐标
            return true;
        //判断鸟是否与地面发生了碰撞
        if(hitColumn(col1)||hitColumn(col2)) {
            return true;
        }
        
        return false;
    }


到这里就实现了游戏的碰撞检测,后面就是对游戏进行的一些优化

添加得分:每当鸟通过一个柱子的时候,游戏得分

判断小鸟是否通过

public boolean pass(Column col1,Column col2) {
        return col1.x==x||col2.x==x;
    }

通过柱子可以得分

if(bird.hit(column1, column2,ground)==true) {
                    start = false;
                    gameover = true;
                }
                if(bird.pass(column1, column2)) {
                    score++;
                }


设置游戏得分标签

//游戏得分
        g.setFont(new Font(Font.MONOSPACED,Font.BOLD,30));
        g.setColor(Color.WHITE);
        g.drawString("score="+score, 30, 50);


在游戏结束时if(gameover == true)需要把得分清0

这个游戏到这里基本就完成啦
嗯下面就给出所有类的代码:

package Gary;

import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.*;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.*;

//step1:初始世界,包含背景图片,开始图片
public class World extends JPanel{
    //step1:背景图片
    BufferedImage background;
    //step1:开始图片
    BufferedImage startImg;
    //step2:增加了一个ground
    Ground ground;
    
    //step3:增加两根柱子
    Column column1;
    Column column2;
    //step3:增加标志位,标志游戏是否开始
    boolean start = false;
    //step6:添加一个游戏结束的标志位
    boolean gameover = false;
    //step5:增加一只小鸟
    Bird bird;
    //游戏得分
    int score=0;
    
    //step1:构造方法中,来对图片进行加载
    public World() throws IOException {
        background = ImageIO.read(getClass().getResource("bg.png"));
        startImg = ImageIO.read(getClass().getResource("start.png"));
        //step2:初始化这个ground
        ground= new Ground();
        //step3:初始化两根柱子
        column1 = new Column(320+100);        
        column2 = new Column(320+180+100);
        //step5:初始化这只小鸟
        bird = new Bird(140,255);  //参数表示小鸟出现的位置
        start = false;
        gameover = false;
        
    }
    
    //step1:重写父类的paint方法,重绘图形
    public void paint(Graphics g) {
        //step1:绘制背景图片
        g.drawImage(background, 0, 0, null);

    

    
        //step6:绘制游戏借宿的图片
        if(gameover == true) {
            try
            {        
                score = 0;
                //step2:初始化这个ground
                ground= new Ground();
                //step3:初始化两根柱子
                column1 = new Column(320+100);        
                column2 = new Column(320+180+100);
                //step5:初始化这只小鸟
                bird = new Bird(140,255);  //参数表示小鸟出现的位置
                start = false;
                gameover = false;
                BufferedImage end = ImageIO.read(getClass().getResource("gameover.png"));
                g.drawImage(end, 0, 0, null);
                return;
            } catch (IOException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        if(start == true) {
            //step3:绘制柱子
            column1.paint(g);
            column2.paint(g);
            //step5:绘制小鸟
            bird.paint(g);
        }
        
        //游戏得分
        g.setFont(new Font(Font.MONOSPACED,Font.BOLD,30));
        g.setColor(Color.WHITE);
        g.drawString("score="+score, 30, 50);
        //step3:判断游戏没有开始的时候绘制开始图片
        if(start ==false) {
            g.drawImage(startImg, 0, 0, null);
        }
        
        //step2:使用ground绘制可移动的地面图片
        ground.paint(g);
    }
    
    
    //step2:开始动作
    public void action() throws InterruptedException {
        //step3:给world主面板注册到鼠标点击的事件监听器上
        addMouseListener(new MouseListener() {

            @Override
            public void mouseClicked(MouseEvent e)
            {
                // TODO Auto-generated method stub
                
            }

            @Override
            public void mouseEntered(MouseEvent e)
            {
                // TODO Auto-generated method stub
                
            }

            @Override
            public void mouseExited(MouseEvent e)
            {
                // TODO Auto-generated method stub
                
            }

            @Override
            public void mousePressed(MouseEvent e)
            {
                //step6:如果游戏结束,从新进行初始化
                if(gameover == true) {
                    try
                    {
                        //step2:初始化这个ground
                        ground= new Ground();
                        //step3:初始化两根柱子
                        column1 = new Column(320+100);        
                        column2 = new Column(320+180+100);
                        //step5:初始化这只小鸟
                        bird = new Bird(140,255);  //参数表示小鸟出现的位置
                        start = false;
                        gameover = false;
                    } catch (IOException e1)
                    {
                        // TODO Auto-generated catch block
                        e1.printStackTrace();
                    }
                }
                //step3:鼠标点击一下,则游戏开始
                start = true;
                //step5:每次点击鼠标,小鸟的当前速度重新变为向上的20
                bird.flappy();
            }

            @Override
            public void mouseReleased(MouseEvent e)
            {
                // TODO Auto-generated method stub
                
            }
            
        });
        
        while(true) {
            ground.step();//可移动的图片的x--
            if(start == true) {    //step4:游戏开始之后,绘制柱子
                column1.step();
                column2.step();
                //step5:小鸟扇翅膀
                bird.step();
                //step6:碰撞检测
                if(bird.hit(column1, column2,ground)==true) {
                    start = false;
                    gameover = true;
                }
                if(bird.pass(column1, column2)) {
                    score++;
                }
            }
            repaint();//重绘
            Thread.sleep(1000/60);//每隔1/60秒重绘一次
        }
    }
    public static void main(String[] args) throws IOException, InterruptedException {
        //创建了一个窗口
        JFrame frame = new JFrame("游戏");
        //创建一个面板
        World world = new World();
        //给窗口设置基本的属性(320*480)
        frame.setSize(320,480);
        //绘制窗口不能调整大小
        frame.setResizable(false);
        //给窗口设置默认的关闭动作
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //设置窗口出现时显示在屏幕的正中
        frame.setLocationRelativeTo(null);
        //把World面板添加到窗口上
        frame.add(world);
        //让窗口frame可见
        frame.setVisible(true);
        //step2:执行开始动作
        world.action();
    }
}
World.class
package Gary;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;

//step5:设置小鸟的图片以及位置信息,以及小鸟移动、绘制方法的类
public class Bird{
    int x;
    int y;
    //step5:小鸟的初始图片
    BufferedImage bird;
    //step5:装载3张不同姿态的小鸟的图片
    BufferedImage[] birds;
    //step5:数组的下标
    int index = 0;
    //step5:重力加速度
    int g;
    //step5:间隔时间
    double t;
    //step5:初始速度 像素/s
    double v0;
    //step5:当前时刻的速度
    double vt;
    //step5:运动的距离
    double s;
    
    //step5:鸟的飞行角度
    double angle;
    //step6:小鸟的大小
    double size = 26;
    
    public Bird(int x,int y) throws IOException {
        this.x = x;
        this.y = y;
        birds = new BufferedImage[3];
        for(int i = 0;i < 3; i++) {
            birds[i] = ImageIO.read(getClass().getResource(i+".png"));
        }
        bird = birds[0];
        
        //step5:初始化物理参数
        g = 4;                   //g取重力加速度就会显得很不自然
        t = 0.25;
        v0 = 20;
        vt=v0;
    }
    
    //step5:绘制小鸟的方法
    public void paint(Graphics g) {
    //    g.drawImage(bird, x, y, null);
        //旋转坐标系
        Graphics2D g2d = (Graphics2D)g;
        g2d.rotate(angle,x,y);
        
        //以x和y为中心,来绘制图片
        int x1 = x - bird.getWidth()/2;
        int y1 = y - bird.getWidth()/2;
        g.drawImage(bird, x1, y1, null);
        
        //坐标系旋转回来
        g2d.rotate(-angle, x, y);
    }
    
    //step5:主管了小鸟的动作、扇翅膀、向上飞、向下自由落体
    public void step() {
        //*******************抛物运动************************
        double vt1 = vt;
        //计算垂直上抛运行,经过时间t以后的速度
        double v = vt1 - g * t;
        //v作为下一次计算位移时的初始速度
        vt = v;
        //运行距离
        s = vt1 * t - 0.5 * g * t * t;
        y = y - (int)s;
        
        //计算小鸟的仰角
        angle = -Math.atan(s/15);   //因为是往上飞的所以取一个负值   60像素/s * 0.25秒 =15 像素
        
        
        //*******************扇翅膀动作**************************
        index++;    //0,1,2,3,4,5,6,7,8...
        bird = birds[index/8%3];  //除以8是为了让小鸟放慢8倍的速度
    }
    
    //step5:每次点击鼠标的时候,小鸟重新获得初速度,向上飞
    //      直到下一次点击前,都一直做自由落体运动
    public void flappy() {
        vt = v0;   //v0=20
    }
    
    //检测小鸟是否与地面、天花板、柱子发生了碰撞
    //True表示发生了碰撞,False表示没有发生碰撞
    public boolean hit(Column col1,Column col2,Ground ground) {
        //判断小鸟是否与天花板发生了碰撞
        if(y-size/2<=0)
            return true;
        //判断小鸟是否与地面发生了碰撞
        if(y-size/2>ground.y)        //y表示的是地面ground的坐标,不是背景图片的纵坐标
            return true;
        //判断鸟是否与地面发生了碰撞
        if(hitColumn(col1)||hitColumn(col2)) {
            return true;
        }
        
        return false;
    }
    
    //是否碰撞柱子
    public boolean hitColumn(Column col) {
        if(x>col.x - col.width/2-size/2&&x<col.x+col.width/2+size/2) {
            if(y>col.y-col.gap /2+size/2&&y<col.y+col.gap/2-size/2) {
                return false;                //这种情况就通过
            }else {
                return true;
            }
        }
        return false;
    }
    
    public boolean pass(Column col1,Column col2) {
        return col1.x==x||col2.x==x;
    }
}
Birld.class
package Gary;

import java.awt.Graphics;
import java.awt.image.*;
import java.io.IOException;

import javax.imageio.ImageIO;

public class Ground{
    //step2:要显示的地面图片
    BufferedImage ground;
    //step2:图片的横坐标
    int x;
    //step2:图片的纵坐标
    int y;
    //step6:柱子上下空间的高度
    int gap = 109;
    
    public Ground() throws IOException {
        //step2:加载图片
        ground = ImageIO.read(getClass().getResource("ground.png"));
        //step2:设置纵坐标
        y = 400;
        
    }
    
    //step2:减小x 让图片向左移动 当x<某个数时,复位
    public void step() {
        x--;
        //(360-497(图片的长度))
        if(x<-137) {
            x=0;
        }
    }
    
    public void paint(Graphics g) {
        g.drawImage(ground, x, y, null);
    }
}
Ground.class
package Gary;

import java.awt.*;
import java.awt.image.*;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;

//Step3:柱子的类
public class Column{
    //Step3:加载柱子图片的缓存
    BufferedImage column;
    
    //step4:上下柱子之间空当的中间点的坐标
    int x;
    int y;
    //Step3:柱子图片的宽度
    int width;
    //Step3:柱子图片的高度
    int height;
    
    //step4:用来创建随机数的对象
    Random random;
    //step6:柱子上下之间的高度
    int gap = 109;
    //Step3:x1当使用本类创建柱子对象时,需确定柱子出现在哪儿(x)
    public  Column(int x1) throws IOException {
        x = x1;
        //初始化柱子的图片
        column = ImageIO.read(getClass().getResource("column.png"));
        //获得柱子图片的高度和宽度
        width = column.getWidth();
        height = column.getHeight();
        //step4:初始化random随机数对象
        random = new Random();
        y = 140 + random.nextInt(140);
    }
    
    //Step3:绘制柱子图片到World上
    public void paint(Graphics g) {
        g.drawImage(column,x-width/2,y-height/2,null);
    }
    
    //step4:用来改变柱子的横坐标,并且添加限制条件,不能一直向左移动
    public void step() {
        x--;
        if(x<-width) {    //重新摆放柱子的位置
            x = 320;
            y = 140 +random.nextInt(140);
        }
    }
}
Column.class

当然这个游戏还有很多需要完善的,添加游戏音效,游戏排行榜,联机等等,分享的文章就当一起学习啦O(∩_∩)O

猜你喜欢

转载自www.cnblogs.com/1138720556Gary/p/8961904.html