java Flappy Bird小游戏二次开发

引言

Flappy Bird 是一款广为人知的经典小游戏,以其简单的操作方式和高难度挑战吸引了全球数百万玩家。游戏的核心机制非常直接——玩家通过点击屏幕使小鸟飞翔,避免碰撞到上下移动的管道,同时尽可能地飞行得更远。这种看似简单的游戏设计隐藏了深层的挑战性和上瘾性,让人不禁一试再试。

尽管 Flappy Bird 的玩法简单,但其背后的代码实现和游戏设计思想却值得深究。对于游戏开发初学者和爱好者来说,复现 Flappy Bird 不仅是一种技术上的练习,也是理解游戏设计精髓的一种方式。本文将探讨如何在 Java 中实现 Flappy Bird 的基本功能,并进一步对游戏进行改进,增加新的特性来提升游戏的趣味性和挑战性。

原始代码概览

点击查看代码
package org.wf.game.flappybird;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class FalappyBirdGame {
    public static void main(String[] args) {
        // 定义画框
        JFrame jf = new JFrame("bird_game");
        jf.setSize(432, 674);
        jf.setAlwaysOnTop(false);
        jf.setLocationRelativeTo(null);
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.setResizable(false);
        Sky sky = new Sky();
        jf.add(sky);
        // 显示画框
        jf.setVisible(true);
        sky.action();
    }
}

// 天空类
class Sky extends JPanel {
    private static final long serialVersionUID = 1L;
    BufferedImage bgBufferImage; // 背景图片
    Ground ground = new Ground(); // 地面
    Column column = new Column(350); // 钢管
    Column column2 = new Column(600); // 钢管
    static Bird bird = new Bird(); // 小鸟
    int score = 0; // 游戏得分
    BufferedImage startBufferImage; // 开始准备界面
    boolean isStrat; // 是否开始游戏
    BufferedImage overBufferImage; // 游戏结束界面
    boolean isOver; // 游戏是否结束

    public Sky() {
        super();
        // 读取图片
        File bgImage = new File("images/bg.png");
        File starImage = new File("images/start.png");
        File overImage = new File("images/gameover.png");
        try {
            bgBufferImage = ImageIO.read(bgImage);
            startBufferImage = ImageIO.read(starImage);
            overBufferImage = ImageIO.read(overImage);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 绘制界面方法
    @Override
    public void paint(Graphics graphics) {
        // 画背景
        graphics.drawImage(bgBufferImage, 0, 0, null);
        // 获取新的画笔对象
        Graphics2D gg = (Graphics2D) graphics;
        gg.rotate(-bird.ratation, bird.bird_x, bird.bird_y);
        // 画小鸟
        graphics.drawImage(bird.biBufferImage, bird.bird_x - bird.bird_width
                / 2, bird.bird_y - bird.bird_height / 2, null);
        gg.rotate(bird.ratation, bird.bird_x, bird.bird_y);
        // 画钢管
        graphics.drawImage(column.coBufferImage, column.column_x - column.width
                / 2, column.column_y - column.height / 2, null);
        graphics.drawImage(column2.coBufferImage, column2.column_x
                        - column2.width / 2, column2.column_y - column2.height / 2,
                null);
        // 画地面
        graphics.drawImage(ground.grBufferImage, ground.ground_x,
                ground.ground_y, null);
        // 画文字
        graphics.setColor(Color.BLUE);
        graphics.setFont(new Font("楷体", Font.ITALIC, 30));
        graphics.drawString("分数:" + score, 100, 600);
        // 画开始准备图片
        if (!isStrat && !isOver) {
            graphics.drawImage(startBufferImage, 0, 0, null);
        }
        // 画结束界面
        if (isOver) {
            graphics.drawImage(overBufferImage, 0, 0, null);
        }
    }

    // 游戏启动逻辑
    public void action() {
        // 添加鼠标监听器
        MouseAdapter adapter = new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                // System.out.println("点击了鼠标");
                /*
                 * 若游戏结束重新开始游戏,游戏恢复初始状态
                 * 若未结束:鸟飞起来
                 */
                if (isOver) {
                    bird = new Bird();
                    ground = new Ground();
                    column = new Column(350);
                    column2 = new Column(600);
                    score = 0;
                    isOver = false;
                    isStrat = false;
                } else {
                    bird.refly();
                    isStrat = true;
                }
            }
        };
        this.addMouseListener(adapter);
        // 添加键盘监听器
        KeyAdapter keyAdapter = new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                char charA = e.getKeyChar();
                if (charA == 'w') {
                    if (bird.bird_y > 20) {
                        bird.bird_y -= 20;
                    }
                } else if (charA == 's') {
                    if (bird.bird_y < 465) {
                        bird.bird_y += 20;
                    }
                } else if (charA == 'a') {
                    if (bird.bird_x > 20) {
                        bird.bird_x -= 20;
                    }
                } else if (charA == 'd') {
                    if (bird.bird_x < 395) {
                        bird.bird_x += 20;
                    }
                } else if (charA == ' ') {
                    /*
                     * 若游戏结束重新开始游戏,游戏恢复初始状态
                     * 若未结束:鸟飞起来
                     */
                    if (isOver) {
                        bird = new Bird();
                        ground = new Ground();
                        column = new Column(350);
                        column2 = new Column(600);
                        score = 0;
                        isOver = false;
                        isStrat = false;
                    } else {
                        bird.refly();
                        isStrat = true;
                    }
                }
                super.keyPressed(e);
            }
        };
        this.addKeyListener(keyAdapter);
        this.requestFocus();

        while (true) {
            // 判断游戏是否开始
            if (isStrat && !isOver) {
                ground.move();
                column.move();
                column2.move();
                bird.change();
                bird.move_go();
            }
            // 判断撞击障碍
            if (bird.bird_x - bird.bird_width / 2 == column.column_x
                    + column.width / 2
                    || bird.bird_x - bird.bird_width / 2 == column2.column_x
                    + column2.width / 2) {
                score++;
            }
            if (bird.hit(ground) || bird.hit(column) || bird.hit(column2)) {
                isStrat = false;
                isOver = true;
            }
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            repaint();
        }
    }
}

// 地面类
class Ground {
    int ground_x, ground_y; // 地面的坐标
    BufferedImage grBufferImage; // 地面图片

    public Ground() {
        super();
        File grImage = new File("images/ground.png");
        try {
            grBufferImage = ImageIO.read(grImage);
        } catch (IOException e) {
            e.printStackTrace();
        }
        ground_y = 500;
    }

    // 地面动画方法
    public void move() {
        ground_x--;
        if (ground_x < -110) {
            ground_x = 0;
        }
    }
}

// 钢管类
class Column {
    int column_x, column_y; // 钢管的中心坐标
    int width, height; // 宽度高度
    int gap = 140; // 钢管的空隙
    Random random = new Random();
    ; // 随机坐标
    BufferedImage coBufferImage; // 钢管图片

    public Column(int x) {
        super();
        File coImage = new File("images/column.png");
        try {
            coBufferImage = ImageIO.read(coImage);
        } catch (IOException e) {
            e.printStackTrace();
        }
        column_x = x;
        column_y = random.nextInt(180) + 150;
        width = coBufferImage.getWidth();
        height = coBufferImage.getHeight();
    }

    // 钢管动画方法
    public void move() {
        column_x--;
        if (column_x < -width / 2) {
            column_y = random.nextInt(180) + 150;
            column_x = 432 + width / 2;
        }
    }
}

// 鸟类
class Bird {
    int bird_x = 60, bird_y = 300; // 鸟的中心点坐标
    int bird_width, bird_height; // 鸟的宽度,高度
    double speed = 20; // 速度
    double g = 4; // 加速度
    double s; // 运动距离
    double t = 0.3; // 运动时间
    BufferedImage biBufferImage; // 鸟图片
    BufferedImage[] images = new BufferedImage[8];
    int bird_icon = 0;

    public Bird() {
        super();
        for (int i = 0; i < images.length; i++) {
            File biImage = new File("images/" + i + ".png");
            try {
                images[i] = ImageIO.read(biImage);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        biBufferImage = images[0];
        bird_width = biBufferImage.getWidth();
        bird_height = biBufferImage.getHeight();
    }

    // 小鸟展翅动画方法
    int index = 0;

    public void change() {
        index++;
        biBufferImage = images[index / 3 % 8];
    }

    // 小鸟移动的方法
    double ratation; // 倾斜角度

    public void move_go() {
        double v0 = speed;
        s = v0 * t - 0.5 * g * t * t;
        double vt = v0 - g * t;
        speed = vt;
        bird_y = bird_y - (int) s;
        ratation = s / 16;
        if (bird_y <= bird_height / 2) {
            bird_y = bird_height / 2;
        }
    }

    // 重新飞翔
    public void refly() {
        speed = 20;
    }

    // 撞击地面
    public boolean hit(Ground ground) {
        return bird_y + bird_height / 2 >= ground.ground_y;
    }

    // 撞击钢管
    public boolean hit(Column column) {
        int left_x = column.column_x - column.width / 2 - bird_width / 2;
        int right_x = column.column_x + column.width / 2 + bird_width / 2;
        int top_y = column.column_y - column.gap / 2 + bird_height / 2 - 5;
        int down_y = column.column_y + column.gap / 2 - bird_height / 2 + 5;
        if (bird_x > left_x && bird_x < right_x) {
            if (bird_y > top_y && bird_y < down_y) {
                return false;
            } else {
                return true;
            }
        } else {
            return false;
        }
    }
}

主要组成部分

Flappy Bird 游戏主要由以下几个核心组件构成:

  • 游戏窗口(JFrame):作为游戏的主体框架,负责展示游戏内容并处理基本的窗口事件。

  • 游戏画面(Sky 类):继承自 JPanel,负责游戏背景、小鸟、钢管和地面的绘制,以及游戏状态的管理。

  • 小鸟(Bird 类):游戏的主角,通过玩家的点击或按键操作控制其上升和下落,模拟飞行行为。

  • 钢管(Column 类):作为游戏中的障碍物,以固定间隔出现,玩家需要操控小鸟穿越钢管之间的空隙。

  • 地面(Ground 类):在游戏画面的底部滚动,形成小鸟飞行的视觉参考。

关键功能

  • 游戏循环:通过 Sky 类中的 action 方法实现,负责游戏状态的更新、绘制和重绘,以及游戏逻辑的循环执行。

  • 事件监听:游戏监听鼠标和键盘事件,允许玩家通过点击或按键来控制小鸟的飞行。

  • 碰撞检测:游戏实时检测小鸟与钢管或地面的碰撞,一旦发生碰撞,游戏结束。

  • 得分机制:玩家每成功穿越一对钢管,得分增加。分数显示在游戏窗口的一角。

原作者对于游戏的基本功能已经实现,因此我将给游戏添加更多功能作为目标进行程序的改进

改进目标

我的改进目标主要集中在两个方面:

  1. 随着分数的提高,逐渐增加游戏速度,以提高游戏的挑战性。
  2. 让钢管的空隙大小随机变化,增加游戏的不可预测性,让玩家每次玩游戏时都有不同的体验。

加快游戏速度

首先,在 Sky 类中定义一个表示游戏速度的变量,此变量将用于控制地面、钢管以及小鸟的移动速度。

class Sky extends JPanel {
    // 其他代码保持不变

    private double speedMultiplier = 1.0; // 游戏速度倍数,初始为 1.0

    // 其他方法保持不变
}

并且我们需要添加一个方法来根据当前分数调整 speedMultiplier。

public void updateGameSpeed() {
    // 根据分数调整游戏速度,例如每增加10分,速度提升10%
    speedMultiplier = 1.0 + score / 10 * 0.1;
}

接下来,我们需要修改 Ground 和 Column 类的 move 方法,使它们的移动速度能够根据 Sky 类中的 speedMultiplier 变量进行调整。

// Ground 类
public void move(double speedMultiplier) {
    ground_x -= speedMultiplier;
    if (ground_x < -110) {
        ground_x = 0;
    }
}

// Column 类
public void move(double speedMultiplier) {
    column_x -= speedMultiplier;
    if (column_x < -width / 2) {
        column_y = random.nextInt(180) + 150;
        column_x = 432 + width / 2;
    }
}

然后,在 Sky 类中,当你调用这些 move 方法时,传递 speedMultiplier:

// Sky 类中的 action 方法或类似的更新逻辑部分
ground.move(speedMultiplier);
column.move(speedMultiplier);
column2.move(speedMultiplier);

这样,随着玩家分数的增加,游戏的难度也会逐渐提升,使游戏更具挑战性和趣味性。

随机的间隙大小

在 move 方法中更新 gap 的值,以便每次钢管重新出现时都有一个新的随机空隙:

public void move() {
    column_x--;
    if (column_x < -width / 2) {
        column_y = random.nextInt(180) + 150;
        column_x = 432 + width / 2;
        // 每次钢管重新出现时更新空隙大小
        this.gap = random.nextInt(51) + 100; // 例如,空隙大小介于 100 到 150 之间
    }
}

结语

在本次探索和改进 Flappy Bird 游戏的旅程中,我们深入了解了游戏的基础代码结构,实现了两项关键的改进:随着玩家分数的增加逐步加快游戏速度,以及让钢管的空隙大小随机变化。这些改进不仅增加了游戏的挑战性,也为玩家带来了新鲜感和更多的乐趣。

通过这次经验,我们看到了即使对于简单的游戏,通过细微的调整也能大大增强游戏体验。这不仅考验了我们对游戏机制的理解,也锻炼了我们的编程技能,特别是在处理游戏逻辑和用户交互方面。

posted @ 2024-03-02 22:31  toner1ko  阅读(81)  评论(0编辑  收藏  举报