java基础游戏项目《笨鸟先飞》


头像
原创
发布时间: 2026-06-28 21:30:00 | 阅读数 0收藏数 0评论数 0
封面
本文基于 Java Swing 从零实现经典小游戏《笨鸟先飞》,完整讲解游戏窗口搭建、图片资源加载、游戏循环、键盘事件监听、小鸟重力与飞行算法、障碍物生成、碰撞检测、计分系统以及 GAME OVER 界面的实现过程,带你通过一个完整项目掌握 Java GUI 编程与面向对象设计。

准备工作:

材料:

图片在附件中自取

ZIP
img.zip
56.00KB
1

创建项目

如图所示创建一个项目 要资源和代码我放在最后一步了大家自取

2

绘制窗体

首先我们创建一个 constants包 在里面新建一个BirdConstants 常量类 内容如下 这个是我们所有的相关常量方便我们进行一个管理


package com.bird.constants;

/**
* @author gjq
* @version 1.0
* @description: 常量
* @date 2026/6/27 15:11
*/
public class BirdConstants {

/**
* 窗体标题
*/
public static final String FRAME_TITLE = "笨鸟先飞";

/**
* 窗体宽度
*/
public static final Integer FRAME_WIDTH = 600;

/**
* 窗体高度
*/
public static final Integer FRAME_HEIGHT = 500;


}


然后创建一个 GameFrame继承JFrame 用于进行一个窗口的绘制 代码如下


package com.bird.main;

import static com.bird.constants.BirdConstants.*;

import javax.swing.*;
import java.awt.*;

/**
* @author gjq
* @version 1.0
* @description: 游戏的主窗口
* @date 2026/6/27 15:08
*/
public class GameFrame extends JFrame {
public GameFrame() {
// 初始化窗口参数
initFrame();
// 显示窗口
setVisible(true);
}

// 初始化窗体参数
private void initFrame() {
// 设置窗体标题
setTitle(FRAME_TITLE);
// 设置窗体大小
setSize(FRAME_WIDTH,FRAME_HEIGHT);
// 窗体居中显示
setLocationRelativeTo(null);
// 设置窗体大小不能改变
setResizable(false);
// 设置窗体关闭按钮的作用,是正常退出
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

}
}


然后程序入口处创建窗口类即可

/**
* @author gjq
* @version 1.0
* @description: 游戏的入口
* @date 2026/6/27 15:17
*/
public class GameApplication {
public static void main(String[] args) {
GameFrame gameFrame = new GameFrame();
}
}




3

绘制背景

在main 下面创建一个 GameBackground 这个类用于针对背景


首先在常量类中添加背景颜色和背景图片两个属性

/**
* 背景图片路径
*/
public static final String BACKGROUND_IMG = "img/background.png";

/**
* 背景颜色
*/
public static final Color BACKGROUND_COLOR = new Color(0x4B4CF);


然后在utils包下面创建一个GameUtils 用于加载图片 如图2

/**
* @author gjq
* @version 1.0
* @description: 游戏相关工具类
* @date 2026/6/27 15:33
*/
public class GameUtils {

/**
* 加载 resource 目录下的图片
* @param path 相对于 resources 根目录的路径,例如 "img/bk.png"
* @return BufferedImage 对象,失败返回 null
*/
public static BufferedImage loadBufferedImage(String path) {
// 使用类加载器获取资源流,路径绝对不要以 "/" 开头
try (InputStream is = GameUtils.class.getClassLoader().getResourceAsStream(path)) {
if (is == null) {
System.err.println("【错误】找不到资源文件:" + path);
return null;
}
return ImageIO.read(is);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

}



然后在main下面 创建一个类 GameBackground 这段代码首先是把背景全部填充上颜色 然后再添加背景图片


package com.bird.main;

import com.bird.utils.GameUtils;

import java.awt.*;
import java.awt.image.BufferedImage;

import static com.bird.constants.BirdConstants.*;

/**
* @author gjq
* @version 1.0
* @description: 背景
* @date 2026/6/27 16:49
*/
public class GameBackground {

private BufferedImage image;

public GameBackground() {
image = GameUtils.loadBufferedImage(BACKGROUND_IMG);
}

public void draw(Graphics g) {
// 设置背景颜色
g.setColor(BACKGROUND_COLOR);
g.fillRect(0,0,FRAME_WIDTH,FRAME_HEIGHT);
g.setColor(Color.black);

// 绘制图片
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();

int count = FRAME_WIDTH / imageWidth + 1;

for (int i = 0; i <= count; i++) {
g.drawImage(image,
i * imageWidth,
FRAME_HEIGHT - imageHeight,
null);
}
}
}


然后创建一个绘画类 GamePanel 把刚刚的背景图片绘画到这个类中去

/**
* @author gjq
* @version 1.0
* @description: 游戏绘制
* @date 2026/6/27 16:17
*/
public class GamePanel extends JPanel {

private GameBackground background;


public GamePanel() {
background = new GameBackground();
}

@Override
protected void paintComponent(Graphics g) {
background.draw(g);
}
}


然后再把这个绘画添加到窗口中 如图5

add(new GamePanel());


效果如图6


4

绘制小鸟

效果看媒体3

原理就是我们绘画一个小鸟 有三张图片分别是翅膀飞翔 落下 平行 根据这三张图片实现一个飞行的ui 然后定时调用小鸟的移动方法传输一个下落参数过去 然后我们点击空格的时候出发事件监听再调用移动方法传输上升参数过去


首先我们要创建一个enum 对应鸟的飞行状态以及三张图片 分别是平飞 向上飞 向下飞

package com.bird.domain;

/**
* @author gjq
* @version 1.0
* @description: 飞翔状态enum
* @date 2026/6/27 16:35
*/
public enum FlyDirectionEnum {
// 平飞
PARALLEL(0),
// 向上
UP(1),
// 向下
DOWN(2);

private int code;

FlyDirectionEnum(int code) {
this.code = code;
}

public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}
}


然后我们在常量里添加 小鸟的三张图片


/**
* 小鸟图片资源
*/
public static final String[] BIRD_IMAGES = {"img/bird.png", "img/upBird.png", "img/downBird.png"};



然后 创建一个 GameBird 这个里面是小鸟的相关方法

package com.bird.main;

import com.bird.domain.FlyDirectionEnum;
import com.bird.utils.GameUtils;

import java.awt.*;
import java.awt.image.BufferedImage;

import static com.bird.constants.BirdConstants.*;

/**
* 小鸟对象
*
* @author gjq
*/
public class GameBird {

// 小鸟三种状态图片
private final BufferedImage[] birdImages;

// 飞行方向
private FlyDirectionEnum direction =
FlyDirectionEnum.PARALLEL;

// 小鸟坐标
private int x = 200, y = 200;

// 当前Y方向移动速度
private int speedY = 0;

// 每一帧增加的下落速度
private static final int FALL_SPEED = 1;

// 按下空格后的上升速度
private static final int FLY_SPEED = -10;

// 最大下落速度
private static final int MAX_SPEED = 12;

/**
* 构造方法
* 初始化小鸟图片资源
*/
public GameBird() {
birdImages = new BufferedImage[BIRD_IMAGES.length];

for (int i = 0; i < BIRD_IMAGES.length; i++) {
birdImages[i] =
GameUtils.loadBufferedImage(BIRD_IMAGES[i]);
}
}

/**
* 绘制小鸟
*/
public void draw(Graphics g) {
g.drawImage(
birdImages[direction.getCode()],
x,
y,
null
);
}

/**
* 每一帧更新小鸟状态
*/
public void move() {

// 下落速度逐渐增加
speedY += FALL_SPEED;

// 限制最大下落速度
if (speedY > MAX_SPEED) {
speedY = MAX_SPEED;
}

// 更新小鸟位置
y += speedY;

// 根据速度切换图片状态
if (speedY < -2) {
direction = FlyDirectionEnum.UP;
} else if (speedY > 2) {
direction = FlyDirectionEnum.DOWN;
} else {
direction = FlyDirectionEnum.PARALLEL;
}

// 防止飞出顶部
if (y < 0) {
y = 0;
speedY = 0;
}

// 防止掉出底部
int maxY =
FRAME_HEIGHT - birdImages[0].getHeight();

if (y > maxY) {
y = maxY;
speedY = 0;
}
}

/**
* 小鸟向上飞
*/
public void fly() {
speedY = FLY_SPEED;
direction = FlyDirectionEnum.UP;
}


}


GamePanel l里面需要添加一个定时器 让小鸟进行下落 如何我们点击空格的时候再向上

package com.bird.main;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import static com.bird.constants.BirdConstants.*;

public class GamePanel extends JPanel {

private final GameBackground background;

private final GameBird gameBird;


public GamePanel() {

setPreferredSize(
new Dimension(
FRAME_WIDTH,
FRAME_HEIGHT
)
);

background = new GameBackground();
gameBird = new GameBird();
setFocusable(true);

addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode()
== KeyEvent.VK_SPACE) {
gameBird.fly();
}
}
});

SwingUtilities.invokeLater(
this::requestFocusInWindow
);

new RunThread().start();
}

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);

// 背景
background.draw(g);
// 小鸟
gameBird.draw(g);
}

class RunThread extends Thread {

@Override
public void run() {

while (true) {

// 更新小鸟
gameBird.move();

// 重绘
repaint();

try {
Thread.sleep(30);
} catch (InterruptedException e) {
break;
}
}
}
}

}


5

绘制云彩

首先在 常量类中添加 云彩图片 这是一个数组到时候我们随机生成

/**
* 云彩图片资源
*/
public static final String[] CLOUD_IMAGES = {"img/cloud0.png", "img/cloud1.png"};


然后我们创建一个类名为cloud 在里面写上以下代码 这个相当于是我们在整个页面上方随机的位置绘制随机的图片 然后添加一个移动的 方法 再用我们之前定时代码去进行移动实现一个参照物的效果 这样看似就是小鸟移动了


package com.bird.main;

import com.bird.utils.GameUtils;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;

import static com.bird.constants.BirdConstants.*;

/**
* 云彩对象
*/
public class Cloud {

private static final Random RANDOM = new Random();

/**
* 云彩图片
*/
private BufferedImage cloudImage;

/**
* 云彩位置
*/
private int cloudX, cloudY;

/**
* 云彩移动速度
*/
private int speed = 2;

/**
* 初始化时指定X坐标
*/
public Cloud(int x) {
init();
this.cloudX = x;
}

/**
* 初始化云彩
*/
private void init() {

// 随机云彩图片
int index = RANDOM.nextInt(CLOUD_IMAGES.length);
cloudImage = GameUtils.loadBufferedImage(CLOUD_IMAGES[index]);
// 随机高度
cloudY = RANDOM.nextInt(150);
}

/**
* 绘制云彩
*/
public void draw(Graphics g) {
g.drawImage(
cloudImage,
cloudX,
cloudY,
null
);
}

/**
* 移动云彩
*/
public void move() {

// 向左移动
cloudX -= speed;

// 飞出屏幕后重新生成
if (cloudX < -cloudImage.getWidth()) {
init();
cloudX = FRAME_WIDTH + RANDOM.nextInt(100);
}
}
}


再创一个前景类 GameFrontGround 调用我们的云朵 代码如下、


package com.bird.main;

import java.awt.*;
import java.util.ArrayList;
import java.util.List;

/**
* 前景对象
*/
public class GameFrontGround {

/**
* 云彩集合
*/
private final List<Cloud> clouds =
new ArrayList<>();

public GameFrontGround() {

// 初始化5朵云
for (int i = 0; i < 5; i++) {
// 均匀分布
clouds.add(new Cloud(i * 150)
);
}
}

/**
* 绘制云彩
*/
public void draw(Graphics g) {
for (Cloud cloud : clouds) {
cloud.draw(g);
}
}

/**
* 更新云彩
*/
public void move() {
for (Cloud cloud : clouds) {
cloud.move();
}
}
}


绘制类代码如下

package com.bird.main;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import static com.bird.constants.BirdConstants.*;

public class GamePanel extends JPanel {

private final GameBackground background;
private final GameFrontGround frontGround;
private final GameBird gameBird;

public GamePanel() {

setPreferredSize(
new Dimension(FRAME_WIDTH, FRAME_HEIGHT)
);

background = new GameBackground();
frontGround = new GameFrontGround();
gameBird = new GameBird();
setFocusable(true);

// 空格控制
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {

if (e.getKeyCode() == KeyEvent.VK_SPACE) {

// 死亡后禁止操作(可选)
if (gameBird.isAlive()) {
gameBird.fly();
}
}
}
});

SwingUtilities.invokeLater(this::requestFocusInWindow);

new RunThread().start();
}

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);

// 绘制背景
background.draw(g);

// 绘制云彩
frontGround.draw(g);

// 绘制小鸟
gameBird.draw(g);

}

// 循环检测
class RunThread extends Thread {

@Override
public void run() {

while (true) {

// 更新小鸟位置
gameBird.move();
// 更新云彩位置
frontGround.move();

// 重绘
repaint();

try {
Thread.sleep(30);
} catch (InterruptedException e) {
break;
}
}
}
}
}


最后效果看最后一张图

6

障碍物类

常量类里添加

/**
* 障碍物顶层图片
*/
public static final String BARRIER_UP = "img/barrier_up.png";

/**
* 障碍物中层图片
*/
public static final String BARRIER_MIDDLE = "img/barrier.png";

/**
* 障碍物底层图片
*/
public static final String BARRIER_DOWN = "img/barrier_down.png";



创建一个 Barrier 类 这个跟云彩的原理是一样的 只不过有三层图片需要进行一个拼接操作 然后管口会粗一点所以需要-3px


package com.bird.main;

import com.bird.utils.GameUtils;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;

import static com.bird.constants.BirdConstants.*;

/**
* 管道障碍物对象
*
* 由上管道和下管道组成,中间留有固定间隙供小鸟通过。
*
* @author gjq
*/
public class Barrier {

/**
* 管道移动速度(每帧向左移动的像素)
*/
private static final int SPEED = 3;

/**
* 上下管道之间的间隙高度
*/
private static final int GAP = 150;

/**
* 随机数对象
*/
private static final Random RANDOM = new Random();

/**
* 管道当前X坐标
*/
private int x;

/**
* 缺口起始Y坐标
* 即上管道结束的位置
*/
private int gapY;

/**
* 当前管道是否已经计过分
*/
private boolean passed = false;

/**
* 上管道图片(管帽)
*/
private final BufferedImage upImg;

/**
* 管道中间身体图片
*/
private final BufferedImage middleImg;

/**
* 下管道图片(管帽)
*/
private final BufferedImage downImg;

/**
* 管帽与管身的水平偏移量
*/
private final int headOffset;

/**
* 创建管道对象
*
* @param x 初始X坐标
*/
public Barrier(int x) {
this.x = x;

// 注意:资源图片名称与实际方向相反
upImg = GameUtils.loadBufferedImage(BARRIER_DOWN);
middleImg = GameUtils.loadBufferedImage(BARRIER_MIDDLE);
downImg = GameUtils.loadBufferedImage(BARRIER_UP);

// 自动计算管帽与管身的偏移量
headOffset =
(upImg.getWidth()
- middleImg.getWidth()) / 2;

randomGap();
}

/**
* 随机生成上下管道之间的缺口位置
*/
private void randomGap() {
gapY = 80 + RANDOM.nextInt(180);

// 管道重新生成后允许再次计分
passed = false;
}

/**
* 更新管道位置
*/
public void move() {

// 管道向左移动
x -= SPEED;

// 飞出屏幕后重新生成
if (x < -getWidth()) {
x = FRAME_WIDTH + 250;
randomGap();
}
}

/**
* 绘制管道
*
* @param g 画笔对象
*/
public void draw(Graphics g) {

int bodyWidth = middleImg.getWidth();

/*
* ================= 上管道 =================
*/

// 上管身高度
int topBodyHeight =
gapY - upImg.getHeight();

// 绘制上管身
g.drawImage(
middleImg,
x,
0,
bodyWidth,
topBodyHeight,
null
);

// 绘制上管帽
g.drawImage(
upImg,
x - headOffset,
topBodyHeight,
null
);

/*
* ================= 下管道 =================
*/

// 下管帽开始位置
int bottomY = gapY + GAP;

// 绘制下管帽
g.drawImage(
downImg,
x - headOffset,
bottomY,
null
);

// 下管身开始位置
int bottomBodyY =
bottomY + downImg.getHeight();

// 下管身高度
int bottomBodyHeight =
FRAME_HEIGHT - bottomBodyY;

// 绘制下管身
g.drawImage(
middleImg,
x,
bottomBodyY,
bodyWidth,
bottomBodyHeight,
null
);
}

/**
* 获取管道宽度
*
* @return 管道宽度
*/
public int getWidth() {
return upImg.getWidth();
}

/**
* 获取管道X坐标
*
* @return 管道X坐标
*/
public int getX() {
return x;
}

/**
* 判断当前管道是否已经计分
*
* @return true:已经计分;false:未计分
*/
public boolean isPassed() {
return passed;
}

/**
* 设置当前管道是否已经计分
*
* @param passed 是否已经计分
*/
public void setPassed(boolean passed) {
this.passed = passed;
}

/**
* 获取上管道碰撞区域
*
* @return 上管道矩形区域
*/
public Rectangle getTopRect() {
return new Rectangle(
x,
0,
middleImg.getWidth(),
gapY
);
}

/**
* 获取下管道碰撞区域
*
* @return 下管道矩形区域
*/
public Rectangle getBottomRect() {
return new Rectangle(
x,
gapY + GAP,
middleImg.getWidth(),
FRAME_HEIGHT - gapY - GAP
);
}
}



然后再创建一个BarrierManager 这个类似于云彩的前景类也是对于管道进行批量管理 的


package com.bird.main;

import java.awt.*;
import java.util.ArrayList;
import java.util.List;

import static com.bird.constants.BirdConstants.*;

public class BarrierManager {

/**
* 管道集合
*/
private final List<Barrier> barriers =
new ArrayList<>();

/**
* 初始化管道
* 默认生成3组管道,并让它们均匀分布在屏幕右侧。
*/
public BarrierManager() {

for (int i = 0; i < 3; i++) {
barriers.add(
new Barrier(
FRAME_WIDTH + i * 250
)
);
}
}

/**
* 更新所有管道的位置
*/
public void move() {
for (Barrier barrier : barriers) {
barrier.move();
}
}

/**
* 绘制所有管道
*
* @param g 画笔对象
*/
public void draw(Graphics g) {
for (Barrier barrier : barriers) {
barrier.draw(g);
}
}

/**
* 获取所有管道对象
*
* @return 管道集合
*/
public List<Barrier> getBarriers() {
return barriers;
}
}


绘制类代码如下

package com.bird.main;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import static com.bird.constants.BirdConstants.*;

public class GamePanel extends JPanel {

private final GameBackground background;
private final GameFrontGround frontGround;
private final GameBird gameBird;
private final BarrierManager barrierManager;


public GamePanel() {

setPreferredSize(
new Dimension(FRAME_WIDTH, FRAME_HEIGHT)
);

background = new GameBackground();
frontGround = new GameFrontGround();
gameBird = new GameBird();
barrierManager = new BarrierManager();
setFocusable(true);

// 空格控制
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {

if (e.getKeyCode() == KeyEvent.VK_SPACE) {

// 死亡后禁止操作(可选)
if (gameBird.isAlive()) {
gameBird.fly();
}
}
}
});

SwingUtilities.invokeLater(this::requestFocusInWindow);

new RunThread().start();
}

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);

// 绘制背景
background.draw(g);

// 绘制云彩
frontGround.draw(g);

// 绘制管道障碍物
barrierManager.draw(g);

// 绘制小鸟
gameBird.draw(g);

}

// 循环检测
class RunThread extends Thread {

@Override
public void run() {

while (true) {

// 更新小鸟位置
gameBird.move();
// 更新云彩位置
frontGround.move();
// 更新管道位置
barrierManager.move();

// 重绘
repaint();

try {
Thread.sleep(30);
} catch (InterruptedException e) {
break;
}
}
}
}
}


7

碰撞与分数

碰撞需要我们获取小鸟的位置和管道的位置

修改小鸟代码如下

package com.bird.main;

import com.bird.domain.FlyDirectionEnum;
import com.bird.utils.GameUtils;

import java.awt.*;
import java.awt.image.BufferedImage;

import static com.bird.constants.BirdConstants.*;

public class GameBird {

// 小鸟三种状态图片
private final BufferedImage[] birdImages;

// 飞行方向

private FlyDirectionEnum direction = FlyDirectionEnum.PARALLEL;

// 小鸟坐标
private int x = 200, y = 200;

// 当前Y方向移动速度
private int speedY = 0;

// 每一帧增加的下落速度
private static final int FALL_SPEED = 1;

// 按下空格后的上升速度
private static final int FLY_SPEED = -10;

// 最大下落速度
private static final int MAX_SPEED = 12;


// 是否存活
private boolean alive = true;
// 分数
private int score = 0;

/**
* 构造方法
* 初始化小鸟图片资源
*/
public GameBird() {
birdImages = new BufferedImage[BIRD_IMAGES.length];

for (int i = 0; i < BIRD_IMAGES.length; i++) {
birdImages[i] =
GameUtils.loadBufferedImage(BIRD_IMAGES[i]);
}
}

/**
* 绘制小鸟
*/
public void draw(Graphics g) {
g.drawImage(
birdImages[direction.getCode()],
x,
y,
null
);
}

/**
* 每一帧更新小鸟状态
*/
public void move() {

// 下落速度逐渐增加
speedY += FALL_SPEED;

// 限制最大下落速度
if (speedY > MAX_SPEED) {
speedY = MAX_SPEED;
}

// 更新小鸟位置
y += speedY;

// 根据速度切换图片状态
if (speedY < -2) {
direction = FlyDirectionEnum.UP;
} else if (speedY > 2) {
direction = FlyDirectionEnum.DOWN;
} else {
direction = FlyDirectionEnum.PARALLEL;
}

// 防止飞出顶部
if (y < 0) {
y = 0;
speedY = 0;
}

// 防止掉出底部
int maxY =
FRAME_HEIGHT - birdImages[0].getHeight();

if (y > maxY) {
y = maxY;
speedY = 0;
}
}

/**
* 小鸟向上飞
*/
public void fly() {
speedY = FLY_SPEED;
direction = FlyDirectionEnum.UP;
}


// 获取位置
public Rectangle getRect() {
return new Rectangle(
x,
y,
birdImages[0].getWidth(),
birdImages[0].getHeight()
);
}

public void die() {
alive = false;
}

public boolean isAlive() {
return alive;
}

public void addScore(int v) {
score += v;
}

public int getScore() {
return score;
}

public int getX() {
return x;
}
}



然后添加一个碰撞类BarrierCollisionSystem


package com.bird.main;

import java.awt.*;
import java.util.List;

public class BarrierCollisionSystem {

public void update(GameBird bird, List<Barrier> barriers) {

if (!bird.isAlive()) return;

Rectangle birdRect = bird.getRect();

for (Barrier b : barriers) {

// 碰撞就把小鸟状态变为死亡
if (birdRect.intersects(b.getTopRect())
|| birdRect.intersects(b.getBottomRect())) {
bird.die();
return;
}

// 否则就加分
if (!b.isPassed()
&& b.getX() + b.getWidth() < bird.getX()) {

bird.addScore(1);
b.setPassed(true);
}
}
}
}



绘制类代码如下


package com.bird.main;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import static com.bird.constants.BirdConstants.*;

public class GamePanel extends JPanel {

private final GameBackground background;
private final GameFrontGround frontGround;
private final GameBird gameBird;
private final BarrierManager barrierManager;

private final BarrierCollisionSystem collisionSystem;


public GamePanel() {

setPreferredSize(
new Dimension(FRAME_WIDTH, FRAME_HEIGHT)
);

background = new GameBackground();
frontGround = new GameFrontGround();
gameBird = new GameBird();
barrierManager = new BarrierManager();
collisionSystem = new BarrierCollisionSystem();
setFocusable(true);

// 空格控制
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {

if (e.getKeyCode() == KeyEvent.VK_SPACE) {

// 死亡后禁止操作(可选)
if (gameBird.isAlive()) {
gameBird.fly();
}
}
}
});

SwingUtilities.invokeLater(this::requestFocusInWindow);

new RunThread().start();
}

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);

// 绘制背景
background.draw(g);

// 绘制云彩
frontGround.draw(g);

// 绘制管道障碍物
barrierManager.draw(g);

// 绘制小鸟
gameBird.draw(g);

// 计算分数
g.setColor(Color.BLACK);
g.setFont(new Font("Arial", Font.BOLD, 20));
g.drawString("分数: " + gameBird.getScore(), 20, 30);

// 判断是否死亡
if (!gameBird.isAlive()) {

String text = "游戏结束";
// 字体样式
g.setColor(Color.RED);
g.setFont(new Font("Arial", Font.BOLD, 40));

FontMetrics fm = g.getFontMetrics();

int textWidth = fm.stringWidth(text);
int textHeight = fm.getAscent();

int x = (getWidth() - textWidth) / 2;
int y = (getHeight() - textHeight) / 2 + textHeight;

g.drawString(text, x, y);
}
}

// 循环检测
class RunThread extends Thread {

@Override
public void run() {

while (true) {

// 更新小鸟位置
gameBird.move();
// 更新云彩位置
frontGround.move();
// 更新管道位置
barrierManager.move();

// 碰撞检测
collisionSystem.update(
gameBird,
barrierManager.getBarriers()
);

// 重绘
repaint();

try {
Thread.sleep(30);
} catch (InterruptedException e) {
break;
}
}
}
}
}


8

效果演示&完整代码

演示效果看视频 完整代码在附件

ZIP
fly-bird.zip
578.92KB
阅读记录0
点赞0
收藏0
禁止 本文未经作者允许授权,禁止转载
猜你喜欢
评论/提问(已发布 0 条)
头像
评论 评论
收藏 收藏
分享 分享
pdf下载 下载
pdf下载 举报