Commit 2968bb8e authored by kingyu's avatar kingyu Committed by kingyuluk
Browse files

feat: 优化记分方法

BREAKING CHANGE: 移除了计时器,更改了游戏的记分方法,现在记分更准确了
parent e2cb276d
......@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [1.2.2](https://github.com/kingyuluk/FlappyBird/compare/v1.2.1...v1.2.2) (2020-07-12)
### ⚠ BREAKING CHANGES
* 移除了计时器,更改了游戏的记分方法,现在记分更准确了
### Features
* 优化记分方法 ([9aa9275](https://github.com/kingyuluk/FlappyBird/commit/9aa927537dc9180942c4a982ea46edd840d7c769))
### [1.2.1](https://github.com/kingyuluk/FlappyBird/compare/v1.2.0...v1.2.1) (2020-07-12)
......
File added
......@@ -8,21 +8,26 @@ Flappy Bird for desktop platforms.
## Overview
本项目为Flappy bird的桌面平台版,具备原版的所有功能,且相较于原版优化了游戏难度并加入移动型水管,让游戏更具可玩性。
### 游戏玩法
游戏只需空格键即可操作,每局游戏随机刷新所有元素,小鸟受到重力作用会不断下坠,敲击空格键小鸟就会振翅向上飞,游戏过程中需要玩家控制小鸟不断飞行,并注意躲避随机生成的水管,每飞过一对水管就会得分,飞行过程中如果撞到水管或掉落在地则游戏结束。
本项目为Flappy bird的桌面平台版,具备原版的所有功能,且相较于原版优化了游戏难度并加入移动型水管,增加可玩性。
## How to play
直接运行FlappyBird.jar即可开始游戏。
游戏使用空格键操作。
每局游戏随机刷新所有元素,小鸟受到重力作用会不断下坠,敲击空格键使小鸟振翅向上飞,游戏过程中需要玩家控制小鸟不断飞行,并注意躲避随机生成的水管,每飞过一对水管就会得分,飞行过程中如果撞到水管或掉落在地则游戏结束。
## 游戏界面
### 游戏启动
![image](https://github.com/kingyuluk/flappy-bird/blob/master/examples/start.png)
![image](https://github.com/kingyuluk/FlappyBird/blob/master/resources/readme_img/start.png)
### 运行示例
![image](https://github.com/kingyuluk/flappy-bird/blob/master/examples/play.gif)
![image](https://github.com/kingyuluk/FlappyBird/blob/master/resources/readme_img/play.gif)
### 游戏结束
![image](https://github.com/kingyuluk/flappy-bird/blob/master/examples/over.png)
![image](https://github.com/kingyuluk/FlappyBird/blob/master/resources/readme_img/over.png)
## Package Contents
......@@ -34,6 +39,9 @@ Flappy Bird for desktop platforms.
## Change Log
v1.2.2 - July 12, 2020
* 移除了计时器,优化了游戏的记分方式,现在记分更准确了
v1.2.1 - July 12, 2020
* 使用AudioClip类的方法播放连续的短音频可能会导致线程冲突使游戏卡顿
......@@ -55,4 +63,8 @@ v1.0.0 - July 10, 2020
* sun包在不同操作系统和不同版本的JDK中可能发生变化,因此无法确保工作在所有JAVA平台上
## Contact
* email: <kingyuluk@mail.dlut.edu.cn>
* email: <kingyuluk@hotmail.com>
## License
* 图片与音效资源皆来源于网络,仅供学习交流
......@@ -9,289 +9,275 @@ import com.bird.util.Constant;
import com.bird.util.GameUtil;
import com.bird.util.MusicUtil;
import static com.bird.util.GameUtil.drawTitle;
/**
* 小鸟类,小鸟的绘制与飞行逻辑都在此类
*
* @author Kingyu
*
* @author Kingyu
*/
public class Bird {
public static final int IMG_COUNT = 8; // 图片数量
public static final int STATE_COUNT = 4; // 状态数
private BufferedImage[][] birdImgs; // 小鸟的图片数组对象
private int x, y; // 小鸟的坐标
int wingState; // 翅膀状态
// 图片资源
private BufferedImage image; // 实时的小鸟图片
private BufferedImage scoreImg; // 计分牌
private BufferedImage overImg; // 结束标志
private BufferedImage againImg; // 继续标志
// 小鸟的状态
private int state;
public static final int STATE_NORMAL = 0;
public static final int STATE_UP = 1;
public static final int STATE_DOWN = 2;
public static final int STATE_FALL = 3;
public static final int STATE_DEAD = 4;
private Rectangle birdRect; // 碰撞矩形
public static final int RECT_DESCALE = 2; // 补偿碰撞矩形宽高的参数
private GameTime timing; // 飞行时间
// 在构造器中对资源初始化
public Bird() {
timing = GameTime.getInstance(); // 计时器
// 读取小鸟图片资源
birdImgs = new BufferedImage[STATE_COUNT][IMG_COUNT];
for (int j = 0; j < STATE_COUNT; j++) {
for (int i = 0; i < IMG_COUNT; i++) {
birdImgs[j][i] = GameUtil.loadBUfferedImage(Constant.BIRDS_IMG_PATH[j][i]);
}
}
// 初始化小鸟的坐标
x = Constant.FRAME_WIDTH >> 2;
y = Constant.FRAME_HEIGHT >> 1;
int ImgWidth = birdImgs[state][0].getWidth();
int ImgHeight = birdImgs[state][0].getHeight();
// 初始化碰撞矩形
int rectX = x - ImgWidth / 2;
int rectY = y - ImgHeight / 2;
int rectWidth = ImgWidth;
int rectHeight = ImgHeight;
birdRect = new Rectangle(rectX + RECT_DESCALE, rectY + RECT_DESCALE * 2, rectWidth - RECT_DESCALE * 3,
rectHeight - RECT_DESCALE * 4); // 碰撞矩形的坐标与小鸟相同
}
// 绘制方法
public void draw(Graphics g) {
fly();
int state_index = state > STATE_FALL ? STATE_FALL : state; // 图片资源索引
// 小鸟中心点计算
int halfImgWidth = birdImgs[state_index][0].getWidth() >> 1;
int halfImgHeight = birdImgs[state_index][0].getHeight() >> 1;
if (speed > 0)
image = birdImgs[STATE_UP][0];
g.drawImage(image, x - halfImgWidth, y - halfImgHeight, null); // x坐标于窗口1/4处,y坐标位窗口中心
if (state == STATE_DEAD) {
drawGameover(g);
} else if (state == STATE_FALL) {
} else {
drawScore(g);
}
// 绘制矩形
// g.setColor(Color.black);
// g.drawRect((int) birdRect.getX(), (int) birdRect.getY(), (int) birdRect.getWidth(), (int) birdRect.getHeight());
}
public static final int SPEED_UP = 32; // 小鸟向上的速度
public static final double g = 9.8; // 重力加速度
public static final double T = 0.2; // 小鸟的下落函数执行的时间
private double h; // 小鸟y轴的位移量
private double speed = 0; // 小鸟的初速度
private boolean keyFlag = true; // 按键状态,true为已释放,使当按住按键时不会重复调用方法
public void keyPressed() {
keyFlag = false;
}
public void keyReleased() {
keyFlag = true;
}
public boolean keyIsReleased() {
return keyFlag;
}
// 小鸟的飞行逻辑
private void fly() {
// 翅膀状态,实现小鸟振翅飞行
wingState++;
image = birdImgs[state > STATE_FALL ? STATE_FALL : state][wingState / 10 % IMG_COUNT];
switch (state) {
case STATE_NORMAL:
break;
case STATE_UP:
break;
case STATE_DOWN:
// 物理公式
speed -= g * T;
h = speed * T - g * T * T / 2;
y = (int) (y - h);
birdRect.y = (int) (birdRect.y - h);
// 控制坠落的边界,若y坐标 > 窗口的高度 - 地面的高度 - 小鸟图片的高度则死亡
if (birdRect.y >= Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1)) {
y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1);
birdRect.y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1);
birdFall();
}
break;
case STATE_FALL:
// 鸟死亡,自由落体
speed -= g * T;
h = speed * T - g * T * T / 2;
y = (int) (y - h);
birdRect.y = (int) (birdRect.y - h);
// 控制坠落的边界,若y坐标 > 窗口的高度 - 地面的高度 - 小鸟图片的高度则死亡
if (birdRect.y >= Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1)) {
y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1);
birdRect.y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1);
GameFrame.setGameState(GameFrame.STATE_OVER); // 改变游戏状态
birdDead();
}
break;
case STATE_DEAD:
break;
}
// 控制上方边界
if (birdRect.y < -1 * Constant.TOP_PIPE_LENGTHENING / 2) {
birdRect.y = -1 * Constant.TOP_PIPE_LENGTHENING / 2;
y = -1 * Constant.TOP_PIPE_LENGTHENING / 2;
}
// 控制下方边界
if (birdRect.y > Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (image.getHeight() >> 1)) {
birdFall();
}
}
// 小鸟振翅
public void birdUp() {
if (keyIsReleased()) { // 如果按键已释放
if (state == STATE_DEAD || state == STATE_UP || state == STATE_FALL)
return; // 小鸟死亡或坠落时返回
MusicUtil.playFly(); // 播放音效
state = STATE_UP;
speed = SPEED_UP; // 每次振翅将速度改为上升速度
wingState = 0; // 重置翅膀状态
keyPressed();
}
}
// 小鸟下降
public void birdDown() {
if (state == STATE_DEAD || state == STATE_FALL)
return; // 小鸟死亡或坠落时返回
state = STATE_DOWN;
}
// 小鸟坠落(已死)
public void birdFall() {
state = STATE_FALL;
MusicUtil.playCrash(); // 播放音效
// 结束计时
timing.endTiming();
}
// 小鸟死亡
public void birdDead() {
state = STATE_DEAD;
// 加载游戏结束的资源
if (overImg == null) {
overImg = GameUtil.loadBUfferedImage(Constant.OVER_IMG_PATH);
scoreImg = GameUtil.loadBUfferedImage(Constant.SCORE_IMG_PATH);
againImg = GameUtil.loadBUfferedImage(Constant.AGAIN_IMG_PATH);
}
}
public boolean isDead() {
return state == STATE_FALL || state == STATE_DEAD;
}
// 开始计时的方法
public void startTiming() {
if (timing.isReadyTiming())
timing.startTiming();
}
// 绘制实时分数
private void drawScore(Graphics g) {
g.setColor(Color.white);
g.setFont(Constant.TIME_FONT);
String str = Long.toString(timing.TimeToScore());
int x = Constant.FRAME_WIDTH - GameUtil.getStringWidth(Constant.TIME_FONT, str) >> 1;
g.drawString(str, x, Constant.FRAME_HEIGHT / 10);
}
private static final int SCORE_LOCATE = 5; // 位置补偿参数
private int flash = 0; // 图片闪烁参数
// 绘制游戏结束的显示
private void drawGameover(Graphics g) {
// 绘制结束标志
int x = Constant.FRAME_WIDTH - overImg.getWidth() >> 1;
int y = Constant.FRAME_HEIGHT / 4;
g.drawImage(overImg, x, y, null);
// 绘制计分牌
x = Constant.FRAME_WIDTH - scoreImg.getWidth() >> 1;
y = Constant.FRAME_HEIGHT / 3;
g.drawImage(scoreImg, x, y, null);
// 绘制本局的分数
g.setColor(Color.white);
g.setFont(Constant.SCORE_FONT);
x = (Constant.FRAME_WIDTH - scoreImg.getWidth() / 2 >> 1) + SCORE_LOCATE;// 位置补偿
y += scoreImg.getHeight() >> 1;
String str = Long.toString(timing.TimeToScore());
x -= GameUtil.getStringWidth(Constant.SCORE_FONT, str) >> 1;
y += GameUtil.getStringHeight(Constant.SCORE_FONT, str);
g.drawString(str, x, y);
// 绘制最高分数
if (timing.getBestScore() > 0) {
str = Long.toString(timing.getBestScore());
x = (Constant.FRAME_WIDTH + scoreImg.getWidth() / 2 >> 1) - SCORE_LOCATE;// 位置补偿
x -= GameUtil.getStringWidth(Constant.SCORE_FONT, str) >> 1;
g.drawString(str, x, y);
}
// 绘制继续游戏
final int COUNT = 30; // 控制闪烁间隔的参数
if (flash++ > COUNT) {
x = Constant.FRAME_WIDTH - againImg.getWidth() >> 1;
y = Constant.FRAME_HEIGHT / 5 * 3;
g.drawImage(againImg, x, y, null);
if (flash == COUNT * 2)
flash = 0;
}
}
// 重置小鸟
public void reset() {
state = STATE_NORMAL; // 小鸟状态
y = Constant.FRAME_HEIGHT >> 1; // 小鸟坐标
speed = 0; // 小鸟速度
int ImgHeight = birdImgs[state][0].getHeight();
birdRect.y = y - ImgHeight / 2 + RECT_DESCALE * 2; // 小鸟碰撞矩形坐标
timing.reset(); // 计时器
flash = 0;
}
// 获取小鸟的碰撞矩形
public Rectangle getBirdRect() {
return birdRect;
}
}
\ No newline at end of file
public static final int IMG_COUNT = 8; // 图片数量
public static final int STATE_COUNT = 4; // 状态数
private final BufferedImage[][] birdImages; // 小鸟的图片数组对象
private final int x;
private int y; // 小鸟的坐标
int wingState; // 翅膀状态
// 图片资源
private BufferedImage image; // 实时的小鸟图片
private BufferedImage scoreImg; // 计分牌
private BufferedImage overImg; // 结束标志
private BufferedImage againImg; // 继续标志
// 小鸟的状态
private int state;
public static final int STATE_NORMAL = 0;
public static final int STATE_UP = 1;
public static final int STATE_DOWN = 2;
public static final int STATE_FALL = 3;
public static final int STATE_DEAD = 4;
private final Rectangle birdRect; // 碰撞矩形
public static final int RECT_DESCALE = 2; // 补偿碰撞矩形宽高的参数
private final GameScore countScore; // 计分器
// 在构造器中对资源初始化
public Bird() {
countScore = GameScore.getInstance(); // 计分器
// 读取小鸟图片资源
birdImages = new BufferedImage[STATE_COUNT][IMG_COUNT];
for (int j = 0; j < STATE_COUNT; j++) {
for (int i = 0; i < IMG_COUNT; i++) {
birdImages[j][i] = GameUtil.loadBufferedImage(Constant.BIRDS_IMG_PATH[j][i]);
}
}
// 初始化小鸟的坐标
x = Constant.FRAME_WIDTH >> 2;
y = Constant.FRAME_HEIGHT >> 1;
int ImgWidth = birdImages[state][0].getWidth();
int ImgHeight = birdImages[state][0].getHeight();
// 初始化碰撞矩形
int rectX = x - ImgWidth / 2;
int rectY = y - ImgHeight / 2;
birdRect = new Rectangle(rectX + RECT_DESCALE, rectY + RECT_DESCALE * 2, ImgWidth - RECT_DESCALE * 3,
ImgHeight - RECT_DESCALE * 4); // 碰撞矩形的坐标与小鸟相同
}
// 绘制方法
public void draw(Graphics g) {
fly();
int state_index = Math.min(state, STATE_FALL); // 图片资源索引
// 小鸟中心点计算
int halfImgWidth = birdImages[state_index][0].getWidth() >> 1;
int halfImgHeight = birdImages[state_index][0].getHeight() >> 1;
if (speed > 0)
image = birdImages[STATE_UP][0];
g.drawImage(image, x - halfImgWidth, y - halfImgHeight, null); // x坐标于窗口1/4处,y坐标位窗口中心
if (state == STATE_DEAD) {
drawGameOver(g);
} else if (state != STATE_FALL) {
drawScore(g);
}
// 绘制矩形
// g.setColor(Color.black);
// g.drawRect((int) birdRect.getX(), (int) birdRect.getY(), (int) birdRect.getWidth(), (int) birdRect.getHeight());
}
public static final int SPEED_UP = 32; // 小鸟向上的速度
public static final double g = 9.8; // 重力加速度
public static final double T = 0.2; // 小鸟的下落函数执行的时间
private double speed = 0; // 小鸟的初速度
private boolean keyFlag = true; // 按键状态,true为已释放,使当按住按键时不会重复调用方法
public void keyPressed() {
keyFlag = false;
}
public void keyReleased() {
keyFlag = true;
}
public boolean keyIsReleased() {
return keyFlag;
}
// 小鸟的飞行逻辑
private void fly() {
// 翅膀状态,实现小鸟振翅飞行
wingState++;
image = birdImages[Math.min(state, STATE_FALL)][wingState / 10 % IMG_COUNT];
switch (state) {
case STATE_DOWN:
// 物理公式
speed -= g * T;
// 小鸟y轴的位移量
double h = speed * T - g * T * T / 2;
y = (int) (y - h);
birdRect.y = (int) (birdRect.y - h);
// 控制坠落的边界,若y坐标 > 窗口的高度 - 地面的高度 - 小鸟图片的高度则死亡
if (birdRect.y >= Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImages[state][0].getHeight() >> 1)) {
y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImages[state][0].getHeight() >> 1);
birdRect.y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImages[state][0].getHeight() >> 1);
birdFall();
}
break;
case STATE_FALL:
// 鸟死亡,自由落体
speed -= g * T;
h = speed * T - g * T * T / 2;
y = (int) (y - h);
birdRect.y = (int) (birdRect.y - h);
// 控制坠落的边界,若y坐标 > 窗口的高度 - 地面的高度 - 小鸟图片的高度则死亡
if (birdRect.y >= Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImages[state][0].getHeight() >> 1)) {
y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImages[state][0].getHeight() >> 1);
birdRect.y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImages[state][0].getHeight() >> 1);
GameFrame.setGameState(GameFrame.STATE_OVER); // 改变游戏状态
birdDead();
}
break;
case STATE_NORMAL:
case STATE_UP:
case STATE_DEAD:
break;
}
// 控制上方边界
if (birdRect.y < -1 * Constant.TOP_PIPE_LENGTHENING / 2) {
birdRect.y = -1 * Constant.TOP_PIPE_LENGTHENING / 2;
y = -1 * Constant.TOP_PIPE_LENGTHENING / 2;
}
// 控制下方边界
if (birdRect.y > Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (image.getHeight() >> 1)) {
birdFall();
}
}
// 小鸟振翅
public void birdUp() {
if (keyIsReleased()) { // 如果按键已释放
if (state == STATE_DEAD || state == STATE_UP || state == STATE_FALL)
return; // 小鸟死亡或坠落时返回
MusicUtil.playFly(); // 播放音效
state = STATE_UP;
speed = SPEED_UP; // 每次振翅将速度改为上升速度
wingState = 0; // 重置翅膀状态
keyPressed();
}
}
// 小鸟下降
public void birdDown() {
if (state == STATE_DEAD || state == STATE_FALL)
return; // 小鸟死亡或坠落时返回
state = STATE_DOWN;
}
// 小鸟坠落(已死)
public void birdFall() {
state = STATE_FALL;
MusicUtil.playCrash(); // 播放音效
}
// 小鸟死亡
public void birdDead() {
state = STATE_DEAD;
// 加载游戏结束的资源
if (overImg == null) {
overImg = GameUtil.loadBufferedImage(Constant.OVER_IMG_PATH);
scoreImg = GameUtil.loadBufferedImage(Constant.SCORE_IMG_PATH);
againImg = GameUtil.loadBufferedImage(Constant.AGAIN_IMG_PATH);
}
countScore.isSaveScore(); // 判断是否保存纪录
}
public boolean isDead() {
return state == STATE_FALL || state == STATE_DEAD;
}
// 绘制实时分数
private void drawScore(Graphics g) {
g.setColor(Color.white);
g.setFont(Constant.CURRENT_SCORE_FONT);
String str = Long.toString(countScore.getScore());
int x = Constant.FRAME_WIDTH - GameUtil.getStringWidth(Constant.CURRENT_SCORE_FONT, str) >> 1;
g.drawString(str, x, Constant.FRAME_HEIGHT / 10);
}
private static final int SCORE_LOCATE = 5; // 位置补偿参数
private int flash = 0; // 图片闪烁参数
// 绘制游戏结束的显示
private void drawGameOver(Graphics g) {
// 绘制结束标志
int x = Constant.FRAME_WIDTH - overImg.getWidth() >> 1;
int y = Constant.FRAME_HEIGHT / 4;
g.drawImage(overImg, x, y, null);
// 绘制计分牌
x = Constant.FRAME_WIDTH - scoreImg.getWidth() >> 1;
y = Constant.FRAME_HEIGHT / 3;
g.drawImage(scoreImg, x, y, null);
// 绘制本局的分数
g.setColor(Color.white);
g.setFont(Constant.SCORE_FONT);
x = (Constant.FRAME_WIDTH - scoreImg.getWidth() / 2 >> 1) + SCORE_LOCATE;// 位置补偿
y += scoreImg.getHeight() >> 1;
String str = Long.toString(countScore.getScore());
x -= GameUtil.getStringWidth(Constant.SCORE_FONT, str) >> 1;
y += GameUtil.getStringHeight(Constant.SCORE_FONT, str);
g.drawString(str, x, y);
// 绘制最高分数
if (countScore.getBestScore() > 0) {
str = Long.toString(countScore.getBestScore());
x = (Constant.FRAME_WIDTH + scoreImg.getWidth() / 2 >> 1) - SCORE_LOCATE;// 位置补偿
x -= GameUtil.getStringWidth(Constant.SCORE_FONT, str) >> 1;
g.drawString(str, x, y);
}
// 绘制继续游戏,使图像闪烁
final int COUNT = 30; // 闪烁周期
if (flash++ > COUNT)
drawTitle(againImg, g);
if (flash == COUNT * 2) // 重置闪烁参数
flash = 0;
}
// 重置小鸟
public void reset() {
state = STATE_NORMAL; // 小鸟状态
y = Constant.FRAME_HEIGHT >> 1; // 小鸟坐标
speed = 0; // 小鸟速度
int ImgHeight = birdImages[state][0].getHeight();
birdRect.y = y - ImgHeight / 2 + RECT_DESCALE * 2; // 小鸟碰撞矩形坐标
countScore.reset(); // 重置计分器
flash = 0;
}
// 获取小鸟的碰撞矩形
public Rectangle getBirdRect() {
return birdRect;
}
}
......@@ -3,78 +3,52 @@ package com.bird.main;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import com.bird.util.Constant;
/**
* 云朵类
*
* @author Kingyu
*
* @author Kingyu
*/
public class Cloud {
private int dir; // 方向
public static final int DIR_NONE = 0;
public static final int DIR_LEFT = 1;
public static final int DIR_RIGHT = 2;
private int speed; // 速度
private int x, y; // 坐标
private BufferedImage img;
// 云朵图片缩放的比例 1.0~2.0
private double scale;
private int scaleImageWidth;
private int scaleImageHeight;
// 构造器
public Cloud(BufferedImage img, int dir, int x, int y) {
super();
this.img = img;
this.dir = dir;
this.x = x;
this.y = y;
this.speed = 2; //云朵的速度
scale = 1 + Math.random(); // Math.random()返回0.0~1.0的随机值
// 缩放云朵图片
scaleImageWidth = (int) (scale * img.getWidth());
scaleImageHeight = (int) (scale * img.getWidth());
}
// 绘制方法
public void draw(Graphics g, Bird bird) {
int speed = this.speed;
if(dir == DIR_NONE) //云彩不动
speed = 0;
x = (dir == DIR_LEFT) ? x - speed : x + speed; // 方向逻辑
g.drawImage(img, x, y, scaleImageWidth, scaleImageHeight, null);
}
/**
* 判断云朵是否飞出屏幕
*
* @return 飞出则返回true,否则返回false
*/
public boolean isOutFrame() {
boolean result = false;
if (dir == DIR_LEFT) {
if (x < -1 * scaleImageWidth) {
return true;
}
} else if (dir == DIR_RIGHT) {
if (x > Constant.FRAME_WIDTH) {
return true;
}
}
return result;
}
private final int speed; // 速度
private int x; // 坐标
private final int y;
private final BufferedImage img;
private final int scaleImageWidth;
private final int scaleImageHeight;
// 构造器
public Cloud(BufferedImage img, int x, int y) {
super();
this.img = img;
this.x = x;
this.y = y;
this.speed = 2; //云朵的速度
// 云朵图片缩放的比例 1.0~2.0
double scale = 1 + Math.random(); // Math.random()返回0.0~1.0的随机值
// 缩放云朵图片
scaleImageWidth = (int) (scale * img.getWidth());
scaleImageHeight = (int) (scale * img.getWidth());
}
// 绘制方法
public void draw(Graphics g, Bird bird) {
int speed = this.speed;
if (bird.isDead())
speed = 1;
x -= speed;
g.drawImage(img, x, y, scaleImageWidth, scaleImageHeight, null);
}
/**
* 判断云朵是否飞出屏幕
*
* @return 飞出则返回true,否则返回false
*/
public boolean isOutFrame() {
return x < -1 * scaleImageWidth;
}
// 改变方向
public void setDir(int dir) {
this.dir = dir;
}
}
......@@ -14,9 +14,9 @@ import com.bird.util.GameUtil;
*/
public class GameBackground {
private static BufferedImage BackgroundImg;// 背景图片
private static final BufferedImage BackgroundImg;// 背景图片
private int speed; // 背景层的速度
private final int speed; // 背景层的速度
private int layerX; // 背景层的坐标
// 在构造器中初始化
......@@ -26,11 +26,9 @@ public class GameBackground {
}
static { //读取背景图片
BackgroundImg = GameUtil.loadBUfferedImage(Constant.BG_IMG_PATH);
BackgroundImg = GameUtil.loadBufferedImage(Constant.BG_IMG_PATH);
}
public static final int BG_IMAGE_HEIGHT = BackgroundImg.getHeight(); //图片的高度
// 绘制方法
public void draw(Graphics g, Bird bird) {
// 绘制背景色
......@@ -58,4 +56,4 @@ public class GameBackground {
if (layerX > BackgroundImg.getWidth())
layerX = 0;
}
}
\ No newline at end of file
}
......@@ -9,230 +9,217 @@ import com.bird.util.GameUtil;
/**
* 游戏中各种元素层的类
*
* @author Kingyu
*
* @author Kingyu
*/
public class GameElementLayer {
private List<Pipe> pipes; // 水管的容器
// 构造器
public GameElementLayer() {
pipes = new ArrayList<>();
}
// 绘制方法
public void draw(Graphics g, Bird bird) {
// 遍历水管容器,如果可见则绘制,不可见则归还
for (int i = 0; i < pipes.size(); i++) {
Pipe pipe = pipes.get(i);
if (pipe.isVisible()) {
pipe.draw(g, bird);
} else {
Pipe remove = pipes.remove(i);
PipePool.giveBack(remove);
i--;
}
}
// 碰撞检测
isCollideBird(bird);
pipeBornLogic(bird);
}
/**
* 添加水管的逻辑: 当容器中添加的最后一个元素完全显示到屏幕后,添加下一对; 水管成对地相对地出现,空隙高度为窗口高度的1/6;
* 每对水管的间隔距离为屏幕高度的1/4; 水管的高度的取值范围为窗口的[1/8~5/8]
*/
public static final int VERTICAL_INTERVAL = Constant.FRAME_HEIGHT / 5;
public static final int HORIZONTAL_INTERVAL = Constant.FRAME_HEIGHT >> 2;
public static final int MIN_HEIGHT = Constant.FRAME_HEIGHT >> 3;
public static final int MAX_HEIGHT = ((Constant.FRAME_HEIGHT) >> 3) * 5;
private void pipeBornLogic(Bird bird) {
if (bird.isDead()) {
// 鸟死后不再添加水管
return;
}
if (pipes.size() == 0) {
// 若容器为空,则添加一对水管
int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度
Pipe top = PipePool.get("Pipe");
top.setAttribute(Constant.FRAME_WIDTH, -Constant.TOP_PIPE_LENGTHENING,
topHeight + Constant.TOP_PIPE_LENGTHENING, Pipe.TYPE_TOP_NORMAL, true);
Pipe bottom = PipePool.get("Pipe");
bottom.setAttribute(Constant.FRAME_WIDTH, topHeight + VERTICAL_INTERVAL,
Constant.FRAME_HEIGHT - topHeight - VERTICAL_INTERVAL, Pipe.TYPE_BOTTOM_NORMAL, true);
pipes.add(top);
pipes.add(bottom);
} else {
// 判断最后一对水管是否完全进入游戏窗口,若进入则添加水管
Pipe lastPipe = pipes.get(pipes.size() - 1); // 获得容器中最后一个水管
if (lastPipe.isInFrame()) { // 根据游戏分数难度递增
if (GameTime.getInstance().TimeToScore() < Constant.HOVER_MOVING_SCORE) {
try {
if (GameUtil.isInProbability(2, 5)) { // 40%的概率生成悬浮的普通水管
addHoverPipe(lastPipe);
} else {
addNormalPipe(lastPipe);
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
try {
if (GameUtil.isInProbability(1, 4)) { // 1/4的概率生成普通水管
if(GameUtil.isInProbability(1, 2)) // 生成普通水管和悬浮水管的概率
addNormalPipe(lastPipe);
else
addHoverPipe(lastPipe);
} else {
if(GameUtil.isInProbability(1, 3)) // 生成移动水管和移动悬浮水管的概率
addMovingHoverPipe(lastPipe);
else
addMovingNormalPipe(lastPipe);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
/**
* 添加普通水管
*
* @param lastPipe 传入最后一根水管以获取x坐标
*/
private void addNormalPipe(Pipe lastPipe) {
int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度
int x = lastPipe.getX() + HORIZONTAL_INTERVAL; // 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
Pipe top = PipePool.get("Pipe"); //从水管对象池中获取对象
//设置x, y, height, type属性
top.setAttribute(x, -Constant.TOP_PIPE_LENGTHENING, topHeight + Constant.TOP_PIPE_LENGTHENING,
Pipe.TYPE_TOP_NORMAL, true);
Pipe bottom = PipePool.get("Pipe");
bottom.setAttribute(x, topHeight + VERTICAL_INTERVAL, Constant.FRAME_HEIGHT - topHeight - VERTICAL_INTERVAL,
Pipe.TYPE_BOTTOM_NORMAL, true);
pipes.add(top);
pipes.add(bottom);
}
/**
* 添加悬浮水管
*
* @param lastPipe
*/
private void addHoverPipe(Pipe lastPipe) {
// 随机生成水管高度,屏幕高度的[1/4,1/6]
int topHoverHeight = GameUtil.getRandomNumber(Constant.FRAME_HEIGHT / 6, Constant.FRAME_HEIGHT / 4);
int x = lastPipe.getX() + HORIZONTAL_INTERVAL; // 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
int y = GameUtil.getRandomNumber(Constant.FRAME_HEIGHT / 12, Constant.FRAME_HEIGHT / 6); // 随机水管的y坐标,窗口的[1/6,1/12]
int type = Pipe.TYPE_HOVER_NORMAL;
// 生成上部的悬浮水管
Pipe topHover = PipePool.get("Pipe");
topHover.setAttribute(x, y, topHoverHeight, type, true);
// 生成下部的悬浮水管
int bottomHoverHeight = Constant.FRAME_HEIGHT - 2 * y - topHoverHeight - VERTICAL_INTERVAL;
Pipe bottomHover = PipePool.get("Pipe");
bottomHover.setAttribute(x, y + topHoverHeight + VERTICAL_INTERVAL, bottomHoverHeight, type, true);
pipes.add(topHover);
pipes.add(bottomHover);
}
/**
* 添加移动的悬浮水管
*
* @param lastPipe
*/
private void addMovingHoverPipe(Pipe lastPipe) {
// 随机生成水管高度,屏幕高度的[1/4,1/6]
int topHoverHeight = GameUtil.getRandomNumber(Constant.FRAME_HEIGHT / 6, Constant.FRAME_HEIGHT / 4);
int x = lastPipe.getX() + HORIZONTAL_INTERVAL; // 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
int y = GameUtil.getRandomNumber(Constant.FRAME_HEIGHT / 12, Constant.FRAME_HEIGHT / 6); // 随机水管的y坐标,窗口的[1/6,1/12]
int type = Pipe.TYPE_HOVER_HARD;
// 生成上部的悬浮水管
Pipe topHover = PipePool.get("MovingPipe");
topHover.setAttribute(x, y, topHoverHeight, type, true);
// 生成下部的悬浮水管
int bottomHoverHeight = Constant.FRAME_HEIGHT - 2 * y - topHoverHeight - VERTICAL_INTERVAL;
Pipe bottomHover = PipePool.get("MovingPipe");
bottomHover.setAttribute(x, y + topHoverHeight + VERTICAL_INTERVAL, bottomHoverHeight, type, true);
pipes.add(topHover);
pipes.add(bottomHover);
}
/**
* 添加移动的普通水管
*
* @param lastPipe
*/
private void addMovingNormalPipe(Pipe lastPipe) {
int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度
int x = lastPipe.getX() + HORIZONTAL_INTERVAL; // 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
Pipe top = PipePool.get("MovingPipe");
top.setAttribute(x, -Constant.TOP_PIPE_LENGTHENING, topHeight + Constant.TOP_PIPE_LENGTHENING,
Pipe.TYPE_TOP_HARD, true);
Pipe bottom = PipePool.get("MovingPipe");
bottom.setAttribute(x, topHeight + VERTICAL_INTERVAL, Constant.FRAME_HEIGHT - topHeight - VERTICAL_INTERVAL,
Pipe.TYPE_BOTTOM_HARD, true);
pipes.add(top);
pipes.add(bottom);
}
/**
* 判断元素和小鸟是否发生碰撞,若发生碰撞返回true,否则返回false
*
* @param bird
* @return
*/
public boolean isCollideBird(Bird bird) {
// 若鸟已死则不再判断
if (bird.isDead()) {
return false;
}
// 遍历水管容器
for (int i = 0; i < pipes.size(); i++) {
Pipe pipe = pipes.get(i);
// 判断碰撞矩形是否有交集
if (pipe.getPipeRect().intersects(bird.getBirdRect())) {
bird.birdFall(); //有交集则小鸟坠落
return true;
}
}
return false;
}
// 重置元素层
public void reset() {
for (int i = 0; i < pipes.size(); i++) {
Pipe pipe = pipes.get(i);
PipePool.giveBack(pipe);
}
pipes.clear();
}
}
\ No newline at end of file
private final List<Pipe> pipes; // 水管的容器
// 构造器
public GameElementLayer() {
pipes = new ArrayList<>();
}
// 绘制方法
public void draw(Graphics g, Bird bird) {
// 遍历水管容器,如果可见则绘制,不可见则归还
for (int i = 0; i < pipes.size(); i++) {
Pipe pipe = pipes.get(i);
if (pipe.isVisible()) {
pipe.draw(g, bird);
} else {
Pipe remove = pipes.remove(i);
PipePool.giveBack(remove);
i--;
}
}
// 碰撞检测
isCollideBird(bird);
pipeBornLogic(bird);
}
/**
* 添加水管的逻辑: 当容器中添加的最后一个元素完全显示到屏幕后,添加下一对; 水管成对地相对地出现,空隙高度为窗口高度的1/6;
* 每对水管的间隔距离为屏幕高度的1/4; 水管的高度的取值范围为窗口的[1/8~5/8]
*/
public static final int VERTICAL_INTERVAL = Constant.FRAME_HEIGHT / 5;
public static final int HORIZONTAL_INTERVAL = Constant.FRAME_HEIGHT >> 2;
public static final int MIN_HEIGHT = Constant.FRAME_HEIGHT >> 3;
public static final int MAX_HEIGHT = ((Constant.FRAME_HEIGHT) >> 3) * 5;
private void pipeBornLogic(Bird bird) {
if (bird.isDead()) {
// 鸟死后不再添加水管
return;
}
if (pipes.size() == 0) {
// 若容器为空,则添加一对水管
int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度
Pipe top = PipePool.get("Pipe");
top.setAttribute(Constant.FRAME_WIDTH, -Constant.TOP_PIPE_LENGTHENING,
topHeight + Constant.TOP_PIPE_LENGTHENING, Pipe.TYPE_TOP_NORMAL, true);
Pipe bottom = PipePool.get("Pipe");
bottom.setAttribute(Constant.FRAME_WIDTH, topHeight + VERTICAL_INTERVAL,
Constant.FRAME_HEIGHT - topHeight - VERTICAL_INTERVAL, Pipe.TYPE_BOTTOM_NORMAL, true);
pipes.add(top);
pipes.add(bottom);
} else {
// 判断最后一对水管是否完全进入游戏窗口,若进入则添加水管
Pipe lastPipe = pipes.get(pipes.size() - 1); // 获得容器中最后一个水管
if (lastPipe.isInFrame()) {
if (pipes.size() >= Constant.FULL_PIPE - 2)// 若窗口中可容纳的水管已满,说明小鸟已飞到第一对水管的位置,开始记分
GameScore.getInstance().setScore(bird);
try {
int currentScore = (int) GameScore.getInstance().getScore() + 1; // 获取当前分数
// 移动水管刷新的概率随当前分数递增,当得分大于19后全部刷新移动水管
if (GameUtil.isInProbability(currentScore, 20)) {
if (GameUtil.isInProbability(1, 4)) // 生成移动水管和移动悬浮水管的概率
addMovingHoverPipe(lastPipe);
else
addMovingNormalPipe(lastPipe);
} else {
if (GameUtil.isInProbability(1, 2)) // 生成静止普通水管和静止悬浮水管的概率
addNormalPipe(lastPipe);
else
addHoverPipe(lastPipe);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/**
* 添加普通水管
*
* @param lastPipe 传入最后一根水管以获取x坐标
*/
private void addNormalPipe(Pipe lastPipe) {
int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度
int x = lastPipe.getX() + HORIZONTAL_INTERVAL; // 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
Pipe top = PipePool.get("Pipe"); // 从水管对象池中获取对象
// 设置x, y, height, type属性
top.setAttribute(x, -Constant.TOP_PIPE_LENGTHENING, topHeight + Constant.TOP_PIPE_LENGTHENING,
Pipe.TYPE_TOP_NORMAL, true);
Pipe bottom = PipePool.get("Pipe");
bottom.setAttribute(x, topHeight + VERTICAL_INTERVAL, Constant.FRAME_HEIGHT - topHeight - VERTICAL_INTERVAL,
Pipe.TYPE_BOTTOM_NORMAL, true);
pipes.add(top);
pipes.add(bottom);
}
/**
* 添加悬浮水管
*
* @param lastPipe 传入最后一根水管以获取x坐标
*/
private void addHoverPipe(Pipe lastPipe) {
// 随机生成水管高度,屏幕高度的[1/4,1/6]
int topHoverHeight = GameUtil.getRandomNumber(Constant.FRAME_HEIGHT / 6, Constant.FRAME_HEIGHT / 4);
int x = lastPipe.getX() + HORIZONTAL_INTERVAL; // 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
int y = GameUtil.getRandomNumber(Constant.FRAME_HEIGHT / 12, Constant.FRAME_HEIGHT / 6); // 随机水管的y坐标,窗口的[1/6,1/12]
int type = Pipe.TYPE_HOVER_NORMAL;
// 生成上部的悬浮水管
Pipe topHover = PipePool.get("Pipe");
topHover.setAttribute(x, y, topHoverHeight, type, true);
// 生成下部的悬浮水管
int bottomHoverHeight = Constant.FRAME_HEIGHT - 2 * y - topHoverHeight - VERTICAL_INTERVAL;
Pipe bottomHover = PipePool.get("Pipe");
bottomHover.setAttribute(x, y + topHoverHeight + VERTICAL_INTERVAL, bottomHoverHeight, type, true);
pipes.add(topHover);
pipes.add(bottomHover);
}
/**
* 添加移动的悬浮水管
*
* @param lastPipe 传入最后一根水管以获取x坐标
*/
private void addMovingHoverPipe(Pipe lastPipe) {
// 随机生成水管高度,屏幕高度的[1/4,1/6]
int topHoverHeight = GameUtil.getRandomNumber(Constant.FRAME_HEIGHT / 6, Constant.FRAME_HEIGHT / 4);
int x = lastPipe.getX() + HORIZONTAL_INTERVAL; // 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
int y = GameUtil.getRandomNumber(Constant.FRAME_HEIGHT / 12, Constant.FRAME_HEIGHT / 6); // 随机水管的y坐标,窗口的[1/6,1/12]
int type = Pipe.TYPE_HOVER_HARD;
// 生成上部的悬浮水管
Pipe topHover = PipePool.get("MovingPipe");
topHover.setAttribute(x, y, topHoverHeight, type, true);
// 生成下部的悬浮水管
int bottomHoverHeight = Constant.FRAME_HEIGHT - 2 * y - topHoverHeight - VERTICAL_INTERVAL;
Pipe bottomHover = PipePool.get("MovingPipe");
bottomHover.setAttribute(x, y + topHoverHeight + VERTICAL_INTERVAL, bottomHoverHeight, type, true);
pipes.add(topHover);
pipes.add(bottomHover);
}
/**
* 添加移动的普通水管
*
* @param lastPipe 传入最后一根水管以获取x坐标
*/
private void addMovingNormalPipe(Pipe lastPipe) {
int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度
int x = lastPipe.getX() + HORIZONTAL_INTERVAL; // 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
Pipe top = PipePool.get("MovingPipe");
top.setAttribute(x, -Constant.TOP_PIPE_LENGTHENING, topHeight + Constant.TOP_PIPE_LENGTHENING,
Pipe.TYPE_TOP_HARD, true);
Pipe bottom = PipePool.get("MovingPipe");
bottom.setAttribute(x, topHeight + VERTICAL_INTERVAL, Constant.FRAME_HEIGHT - topHeight - VERTICAL_INTERVAL,
Pipe.TYPE_BOTTOM_HARD, true);
pipes.add(top);
pipes.add(bottom);
}
/**
* 判断元素和小鸟是否发生碰撞
*
* @param bird 传入小鸟对象
*/
public void isCollideBird(Bird bird) {
// 若鸟已死则不再判断
if (bird.isDead()) {
return;
}
// 遍历水管容器
for (Pipe pipe : pipes) {
// 判断碰撞矩形是否有交集
if (pipe.getPipeRect().intersects(bird.getBirdRect())) {
bird.birdFall(); // 有交集则小鸟坠落
return;
}
}
}
// 重置元素层
public void reset() {
for (Pipe pipe : pipes) {
PipePool.giveBack(pipe);
}
pipes.clear();
}
}
......@@ -10,89 +10,71 @@ import com.bird.util.GameUtil;
/**
* 前景类, 游戏中的遮挡层 包含多朵云
*
* @author Kingyu
*
* @author Kingyu
*/
public class GameForeground {
private List<Cloud> clouds = new ArrayList<>(); // 云朵的容器
private final List<Cloud> clouds; // 云朵的容器
private final BufferedImage[] cloudImages; // 图片资源
private long time; // 控制云的逻辑运算周期
public static final int CLOUD_INTERVAL = 100; //云朵刷新的逻辑运算的周期
private BufferedImage[] cloudImgs; // 图片资源
private int cloudDir; // 云的方向
public GameForeground() {
clouds = new ArrayList<>(); //云朵的容器
private long time; // 控制云的逻辑运算周期
public static final int CLOUD_INTERVAL = 100; //云朵刷新的逻辑运算的周期
// 读入图片资源
cloudImages = new BufferedImage[Constant.CLOUD_IMAGE_COUNT];
for (int i = 0; i < Constant.CLOUD_IMAGE_COUNT; i++) {
cloudImages[i] = GameUtil.loadBufferedImage(Constant.CLOUDS_IMG_PATH[i]);
}
time = System.currentTimeMillis(); // 获取当前时间,用于控制云的逻辑运算周期
}
public GameForeground() {
clouds = new ArrayList<>(); //云朵的容器
// 读入图片资源
cloudImgs = new BufferedImage[Constant.CLOUD_IMAGE_COUNT];
for (int i = 0; i < Constant.CLOUD_IMAGE_COUNT; i++) {
cloudImgs[i] = GameUtil.loadBUfferedImage(Constant.CLOUDS_IMG_PATH[i]);
}
// 绘制方法
public void draw(Graphics g, Bird bird) {
cloudLogic();
for (Cloud cloud : clouds) {
cloud.draw(g, bird);
}
}
// 初始化云朵的属性
cloudDir = Cloud.DIR_LEFT;
time = System.currentTimeMillis(); // 获取当前时间,用于控制云的逻辑运算周期
}
// 云朵的控制
private void cloudLogic() {
// 100ms运算一次
if (System.currentTimeMillis() - time > CLOUD_INTERVAL) {
time = System.currentTimeMillis(); // 重置time
// 如果屏幕的云朵的数量小于允许的最大数量,根据给定的概率随机添加云朵
if (clouds.size() < Constant.MAX_CLOUD_COUNT) {
try {
if (GameUtil.isInProbability(Constant.CLOUD_BORN_PERCENT, 100)) { // 根据给定的概率添加云朵
int index = GameUtil.getRandomNumber(0, Constant.CLOUD_IMAGE_COUNT); // 随机选取云朵图片
// 绘制方法
public void draw(Graphics g, Bird bird) {
cloudLogic();
for (int i = 0; i < clouds.size(); i++) {
clouds.get(i).draw(g, bird);
}
}
// 云朵刷新的坐标
int x = Constant.FRAME_WIDTH; // 从屏幕左侧开始刷新
// y坐标随机在上1/3屏选取
int y = GameUtil.getRandomNumber(Constant.TOP_BAR_HEIGHT, Constant.FRAME_HEIGHT / 3);
// 云朵的控制
private void cloudLogic() {
// 100ms运算一次
if (System.currentTimeMillis() - time > CLOUD_INTERVAL) {
time = System.currentTimeMillis(); // 重置time
// 如果屏幕的云朵的数量小于允许的最大数量,根据给定的概率随机添加云朵
if (clouds.size() < Constant.MAX_CLOUD_COUNT) {
try {
if (GameUtil.isInProbability(Constant.CLOUD_BORN_PERCENT, 100)) { // 根据给定的概率添加云朵
int index = GameUtil.getRandomNumber(0, Constant.CLOUD_IMAGE_COUNT); // 随机选取云朵图片
// 云朵刷新的坐标
int x = Constant.FRAME_WIDTH; // 从屏幕左侧开始刷新
// 根据当前的方向调整云朵刷新的x坐标(暂时无用)
// if (cloudDir == Cloud.DIR_LEFT) {
// } else {
// x = -1 * cloudImgs[index].getWidth();
// }
// y坐标随机在上1/3屏选取
int y = GameUtil.getRandomNumber(Constant.TOP_BAR_HEIGHT, Constant.FRAME_HEIGHT / 3);
//向容器中添加云朵
Cloud cloud = new Cloud(cloudImgs[index], cloudDir, x, y);
clouds.add(cloud);
}
} catch (Exception e) {
e.printStackTrace();
}
} // 添加云朵
//向容器中添加云朵
Cloud cloud = new Cloud(cloudImages[index], x, y);
clouds.add(cloud);
}
} catch (Exception e) {
e.printStackTrace();
}
} // 添加云朵
// 若云朵飞出屏幕则从容器中移除
for (int i = 0; i < clouds.size(); i++) {
// 遍历容器中的云朵
Cloud tempCloud = clouds.get(i);
if (tempCloud.isOutFrame()) {
clouds.remove(i);
i--;
}
}
// 若云朵飞出屏幕则从容器中移除
for (int i = 0; i < clouds.size(); i++) {
// 遍历容器中的云朵
Cloud tempCloud = clouds.get(i);
if (tempCloud.isOutFrame()) {
clouds.remove(i);
i--;
}
}
/*
* 功能已实现,但不太符合现实,暂时注释掉 // 云朵随机改变方向, try { if (GameUtil.isInProbability(1,
* Constant.CLOUD_DIRCHANGE)) { // 新的云彩方向 int newDir =
* GameUtil.getRandomNumber(Cloud.DIR_LEFT, Cloud.DIR_RIGHT + 1); if (newDir !=
* cloudDir) { // 风向改变,调整云彩状态 cloudDir = newDir; for (int i = 0; i <
* clouds.size(); i++) { // 遍历容器中的云朵 Cloud tempCloud = clouds.get(i);
* tempCloud.setDir(newDir); } } System.out.println(cloudDir); } } catch
* (Exception e) { e.printStackTrace(); }
*/
}
}
}
\ No newline at end of file
}
}
}
......@@ -18,158 +18,152 @@ import java.awt.image.BufferedImage;
/**
* 主窗口类,游戏窗口和绘制的相关内容
*
* @author Kingyu
*
* @author Kingyu
*/
public class GameFrame extends Frame implements Runnable {
private static final long serialVersionUID = 1L; // 保持版本的兼容性
private static int gameState; // 游戏状态
public static final int STATE_READY = 0; // 游戏未开始
public static final int STATE_START = 1; // 游戏开始
public static final int STATE_OVER = 2; // 游戏结束
private GameBackground background; // 游戏背景对象
private GameForeground foreground; // 游戏前景对象
private Bird bird; // 小鸟对象
private GameElementLayer gameElement; // 游戏元素对象
private GameReady ready; // 游戏未开始时对象
// 在构造器中初始化
public GameFrame() {
initFrame(); // 初始化游戏窗口
setVisible(true); // 窗口默认为不可见,设置为可见
initGame(); // 初始化游戏对象
}
// 初始化游戏窗口
private void initFrame() {
setSize(FRAME_WIDTH, FRAME_HEIGHT); // 设置窗口大小
setTitle(GAME_TITLE); // 设置窗口标题
setLocation(FRAME_X, FRAME_Y); // 窗口初始位置
setResizable(false); // 设置窗口大小不可变
// 添加关闭窗口事件(监听窗口发生的事件,派发给参数对象,参数对象调用对应的方法)
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0); // 结束程序
}
});
addKeyListener(new BirdKeyListener()); // 添加按键监听
}
// 用于接收按键事件的对象的内部类
class BirdKeyListener implements KeyListener {
// 按键按下,根据游戏当前的状态调用不同的方法
public void keyPressed(KeyEvent e) {
int keycode = e.getKeyChar();
switch (gameState) {
case STATE_READY:
if (keycode == KeyEvent.VK_SPACE) {
// 游戏启动界面时按下空格,小鸟振翅一次并开始受重力影响
bird.birdUp();
bird.birdDown();
setGameState(STATE_START); // 游戏状态改变
bird.startTiming(); // 计时器开始计时
}
break;
case STATE_START:
if (keycode == KeyEvent.VK_SPACE) {
//游戏过程中按下空格则振翅一次,并持续受重力影响
bird.birdUp();
bird.birdDown();
}
break;
case STATE_OVER:
if (keycode == KeyEvent.VK_SPACE) {
//游戏结束时按下空格,重新开始游戏
resetGame();
}
break;
}
}
// 重新开始游戏
private void resetGame() {
setGameState(STATE_READY);
gameElement.reset();
bird.reset();
}
// 按键松开,更改按键状态标志
public void keyReleased(KeyEvent e) {
int keycode = e.getKeyChar();
if (keycode == KeyEvent.VK_SPACE) {
bird.keyReleased();
}
}
public void keyTyped(KeyEvent e) {
}
}
// 初始化游戏中的各个对象
private void initGame() {
background = new GameBackground();
gameElement = new GameElementLayer();
foreground = new GameForeground();
ready = new GameReady();
bird = new Bird();
setGameState(STATE_READY);
// 启动用于刷新窗口的线程
new Thread(this).start();
}
// 项目中存在两个线程:系统线程,自定义的线程:调用repaint()。
// 系统线程:屏幕内容的绘制,窗口事件的监听与处理
// 两个线程会抢夺系统资源,可能会出现一次刷新周期所绘制的内容,并没有在一次刷新周期内完成
// (双缓冲)单独定义一张图片,将需要绘制的内容绘制到这张图片,再一次性地将图片绘制到窗口
private BufferedImage bufImg = new BufferedImage(FRAME_WIDTH, FRAME_HEIGHT, BufferedImage.TYPE_4BYTE_ABGR);
/**
* 绘制游戏内容 当repaint()方法被调用时,JVM会调用update(),参数g是系统提供的画笔,由系统进行实例化
* 单独启动一个线程,不断地快速调用repaint(),让系统对整个窗口进行重绘
*
*/
public void update(Graphics g) {
Graphics bufG = bufImg.getGraphics(); // 获得图片画笔
// 使用图片画笔将需要绘制的内容绘制到图片
background.draw(bufG, bird); // 背景层
foreground.draw(bufG, bird); // 前景层
if (gameState == STATE_READY) { // 游戏未开始
ready.draw(bufG);
bird.draw(bufG); // 鸟
} else { // 游戏结束
gameElement.draw(bufG, bird); // 游戏元素层
bird.draw(bufG); // 鸟
}
g.drawImage(bufImg, 0, 0, null); // 一次性将图片绘制到屏幕上
}
@Override
public void run() {
while (true) {
repaint(); // 通过调用repaint(),让JVM调用update()
try {
Thread.sleep(GAME_INTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 获取、设置游戏状态的方法
public static int getGameState() {
return gameState;
}
public static void setGameState(int gameState) {
GameFrame.gameState = gameState;
}
}
\ No newline at end of file
private static final long serialVersionUID = 1L; // 保持版本的兼容性
private static int gameState; // 游戏状态
public static final int STATE_READY = 0; // 游戏未开始
public static final int STATE_START = 1; // 游戏开始
public static final int STATE_OVER = 2; // 游戏结束
private GameBackground background; // 游戏背景对象
private GameForeground foreground; // 游戏前景对象
private Bird bird; // 小鸟对象
private GameElementLayer gameElement; // 游戏元素对象
private GameReady ready; // 游戏未开始时对象
// 在构造器中初始化
public GameFrame() {
initFrame(); // 初始化游戏窗口
setVisible(true); // 窗口默认为不可见,设置为可见
initGame(); // 初始化游戏对象
}
// 初始化游戏窗口
private void initFrame() {
setSize(FRAME_WIDTH, FRAME_HEIGHT); // 设置窗口大小
setTitle(GAME_TITLE); // 设置窗口标题
setLocation(FRAME_X, FRAME_Y); // 窗口初始位置
setResizable(false); // 设置窗口大小不可变
// 添加关闭窗口事件(监听窗口发生的事件,派发给参数对象,参数对象调用对应的方法)
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0); // 结束程序
}
});
addKeyListener(new BirdKeyListener()); // 添加按键监听
}
// 用于接收按键事件的对象的内部类
class BirdKeyListener implements KeyListener {
// 按键按下,根据游戏当前的状态调用不同的方法
public void keyPressed(KeyEvent e) {
int keycode = e.getKeyChar();
switch (gameState) {
case STATE_READY:
if (keycode == KeyEvent.VK_SPACE) {
// 游戏启动界面时按下空格,小鸟振翅一次并开始受重力影响
bird.birdUp();
bird.birdDown();
setGameState(STATE_START); // 游戏状态改变
}
break;
case STATE_START:
if (keycode == KeyEvent.VK_SPACE) {
//游戏过程中按下空格则振翅一次,并持续受重力影响
bird.birdUp();
bird.birdDown();
}
break;
case STATE_OVER:
if (keycode == KeyEvent.VK_SPACE) {
//游戏结束时按下空格,重新开始游戏
resetGame();
}
break;
}
}
// 重新开始游戏
private void resetGame() {
setGameState(STATE_READY);
gameElement.reset();
bird.reset();
}
// 按键松开,更改按键状态标志
public void keyReleased(KeyEvent e) {
int keycode = e.getKeyChar();
if (keycode == KeyEvent.VK_SPACE) {
bird.keyReleased();
}
}
public void keyTyped(KeyEvent e) {
}
}
// 初始化游戏中的各个对象
private void initGame() {
background = new GameBackground();
gameElement = new GameElementLayer();
foreground = new GameForeground();
ready = new GameReady();
bird = new Bird();
setGameState(STATE_READY);
// 启动用于刷新窗口的线程
new Thread(this).start();
}
// 项目中存在两个线程:系统线程,自定义的线程:调用repaint()。
// 系统线程:屏幕内容的绘制,窗口事件的监听与处理
// 两个线程会抢夺系统资源,可能会出现一次刷新周期所绘制的内容,并没有在一次刷新周期内完成
// (双缓冲)单独定义一张图片,将需要绘制的内容绘制到这张图片,再一次性地将图片绘制到窗口
private final BufferedImage bufImg = new BufferedImage(FRAME_WIDTH, FRAME_HEIGHT, BufferedImage.TYPE_4BYTE_ABGR);
/**
* 绘制游戏内容 当repaint()方法被调用时,JVM会调用update(),参数g是系统提供的画笔,由系统进行实例化
* 单独启动一个线程,不断地快速调用repaint(),让系统对整个窗口进行重绘
*/
public void update(Graphics g) {
Graphics bufG = bufImg.getGraphics(); // 获得图片画笔
// 使用图片画笔将需要绘制的内容绘制到图片
background.draw(bufG, bird); // 背景层
foreground.draw(bufG, bird); // 前景层
// 鸟
if (gameState == STATE_READY) { // 游戏未开始
ready.draw(bufG);
} else { // 游戏结束
gameElement.draw(bufG, bird); // 游戏元素层
}
bird.draw(bufG); // 鸟
g.drawImage(bufImg, 0, 0, null); // 一次性将图片绘制到屏幕上
}
// TODO: 不建议在while循环中使用sleep
@SuppressWarnings("InfiniteLoopStatement")
@Override
public void run() {
while (true) {
repaint(); // 通过调用repaint(),让JVM调用update()
try {
Thread.sleep(GAME_INTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void setGameState(int gameState) {
GameFrame.gameState = gameState;
}
}
......@@ -2,9 +2,9 @@ package com.bird.main;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import com.bird.util.Constant;
import com.bird.util.GameUtil;
import static com.bird.util.GameUtil.drawTitle;
/**
* 游戏启动界面类
......@@ -14,15 +14,15 @@ import com.bird.util.GameUtil;
*/
public class GameReady {
private BufferedImage titleImg;
private BufferedImage noticeImg;
private final BufferedImage titleImg;
private final BufferedImage noticeImg;
private int flash; // 图像闪烁参数
private int flash = 0; // 图像闪烁参数
// 构造器中进行初始化,装载图像资源
public GameReady() {
titleImg = GameUtil.loadBUfferedImage(Constant.TITLE_IMG_PATH);
noticeImg = GameUtil.loadBUfferedImage(Constant.NOTICE_IMG_PATH);
titleImg = GameUtil.loadBufferedImage(Constant.TITLE_IMG_PATH);
noticeImg = GameUtil.loadBufferedImage(Constant.NOTICE_IMG_PATH);
}
public void draw(Graphics g) {
......@@ -33,14 +33,10 @@ public class GameReady {
// 使notice的图像闪烁
final int COUNT = 30; // 闪烁周期
if (flash++ > COUNT) {
// 计算notice图像的x、y坐标
x = Constant.FRAME_WIDTH - noticeImg.getWidth() >> 1;
y = Constant.FRAME_HEIGHT / 5 * 3;
g.drawImage(noticeImg, x, y, null); // 绘制
if (flash == COUNT * 2) // 重置闪烁参数
if (flash++ > COUNT)
drawTitle(noticeImg, g);
if (flash == COUNT * 2) // 重置闪烁参数
flash = 0;
}
}
}
......@@ -15,35 +15,27 @@ import com.bird.util.MusicUtil;
* @author Kingyu
*
*/
public class GameTime {
private static final GameTime GAME_TIME = new GameTime();
public class GameScore {
private static final GameScore GAME_SCORE = new GameScore();
private int timeState; // 计时器的状态
public static final int STATE_READY = 0; // 计时就绪
public static final int STATE_START = 1; // 计时开始
public static final int STATE_OVER = 2; // 计时结束
private long startTime = 0; // 开始时间 ms
private long endTime = 0; // 开始时间 ms
private long score = 0; // 分数
private long bestScore; // 最高分数
private GameTime() {
timeState = STATE_READY;
private GameScore() {
bestScore = -1;
try {
loadBestTime();
loadBestScore();
} catch (Exception e) {
e.printStackTrace();
}
}
public static GameTime getInstance() {
return GAME_TIME;
public static GameScore getInstance() {
return GAME_SCORE;
}
// 装载最高纪录
private void loadBestTime() throws Exception {
private void loadBestScore() throws Exception {
File file = new File(Constant.SCORE_FILE_PATH);
if (file.exists()) {
DataInputStream dis = new DataInputStream(new FileInputStream(file));
......@@ -53,10 +45,10 @@ public class GameTime {
}
// 保存最高纪录
public void saveBestScore(long time) throws Exception {
public void saveBestScore(long score) throws Exception {
File file = new File(Constant.SCORE_FILE_PATH);
DataOutputStream dos = new DataOutputStream(new FileOutputStream(file));
dos.writeLong(time);
dos.writeLong(score);
dos.close();
}
......@@ -64,51 +56,20 @@ public class GameTime {
return bestScore;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
}
public void setOverTime(long endTime) {
this.endTime = endTime;
public long getScore() {
return score;
}
/**
* 游戏用时,毫秒
*
* @return
*/
public long getTime() {
if (timeState == STATE_READY) {
return startTime;
} else if (timeState == STATE_START) {
return (System.currentTimeMillis() - startTime);
} else {
return (endTime - startTime);
public void setScore(Bird bird) {
if(!bird.isDead()){
MusicUtil.playScore(); //每次得分播放音效
score += 1;
//小鸟没死时记分
}
}
//游戏时间转换为秒
public long getTimeInSeconds() {
return getTime() / 1000;
}
// 计时器是否就绪
public boolean isReadyTiming() {
return timeState == STATE_READY;
}
// 开始计时
public void startTiming() {
startTime = System.currentTimeMillis();
timeState = STATE_START;
}
// 结束计时并判断是否保存记录
public void endTiming() {
endTime = System.currentTimeMillis();
timeState = STATE_OVER;
// 判断本次得分是否为最高分
long score = TimeToScore();
// 判断是否为最高纪录
public void isSaveScore() {
long score = getScore();
if (bestScore < score)
bestScore = score;
try {
......@@ -117,40 +78,79 @@ public class GameTime {
e.printStackTrace();
}
}
private static final int FIRST_SCORE_TIME = 6600; // 从游戏开始到通过第一根水管的所需时间
private static final int PER_SCORE_TIME = 2900; // 通过后续每一根水管的间隔的所需时间
//将游戏时间转换为通过水管的数量
public long TimeToScore() {
long time = getTime();
long temp = score;
if (time >= FIRST_SCORE_TIME && time < FIRST_SCORE_TIME + PER_SCORE_TIME) {
score = 1; //time大于FIRST_SCORE_TIME且未到第二对水管
} else if (time >= FIRST_SCORE_TIME + PER_SCORE_TIME) {
score = (int) (time - FIRST_SCORE_TIME) / PER_SCORE_TIME + 1;
}
if (score - temp > 0) {
MusicUtil.playScore(); //每次得分播放音效
}
return score;
}
/**
* 是否正在计时
*
* @return
*/
public boolean isTiming() {
return timeState == STATE_START;
}
//重置计时器
//重置计分器
public void reset() {
timeState = STATE_READY;
startTime = 0;
endTime = 0;
score = 0;
}
// private int timeState; // 计时器的状态
// public static final int STATE_READY = 0; // 计时就绪
// public static final int STATE_START = 1; // 计时开始
// public static final int STATE_OVER = 2; // 计时结束
// 以下为游戏计时的相关方法,因改进了记分方式目前无用
// /**
// * 游戏用时,毫秒
// *
// * @return
// */
// public long getTime() {
// if (timeState == STATE_READY) {
// return startTime;
// } else if (timeState == STATE_START) {
// return (System.currentTimeMillis() - startTime);
// } else {
// return (endTime - startTime);
// }
// }
//
// //游戏时间转换为秒
// public long getTimeInSeconds() {
// return getTime() / 1000;
// }
//
// // 计时器是否就绪
// public boolean isReadyTiming() {
// return timeState == STATE_READY;
// }
//
// // 开始计时
// public void startTiming() {
// startTime = System.currentTimeMillis();
// timeState = STATE_START;
// }
//
// // 结束计时
// public void endTiming() {
// endTime = System.currentTimeMillis();
// timeState = STATE_OVER;
// }
//
// private static final int FIRST_SCORE_TIME = 6600; // 从游戏开始到通过第一根水管的所需时间
// private static final int PER_SCORE_TIME = 2900; // 通过后续每一根水管的间隔的所需时间
//
// //将游戏时间转换为通过水管的数量
// public long TimeToScore() {
// long time = getTime();
// long temp = score;
// if (time >= FIRST_SCORE_TIME && time < FIRST_SCORE_TIME + PER_SCORE_TIME) {
// score = 1; //time大于FIRST_SCORE_TIME且未到第二对水管
// } else if (time >= FIRST_SCORE_TIME + PER_SCORE_TIME) {
// score = (int) (time - FIRST_SCORE_TIME) / PER_SCORE_TIME + 1;
// }
// if (score - temp > 0) {
// MusicUtil.playScore(); //每次得分播放音效
// }
// return score;
// }
//
// /**
// * 是否正在计时
// *
// * @return
// */
// public boolean isTiming() {
// return timeState == STATE_START;
// }
}
......@@ -27,11 +27,11 @@ public class MovingPipe extends Pipe {
/**
* 设置水管参数
*
* @param x
* @param y
* @param height
* @param type
* @param visible
* @param x:x坐标
* @param y:y坐标
* @param height:水管高度
* @param type:水管类型
* @param visible:水管可见性
*/
public void setAttribute(int x, int y, int height, int type, boolean visible) {
this.x = x;
......
......@@ -9,203 +9,200 @@ import com.bird.util.GameUtil;
/**
* 水管类
*
* @author Kingyu
*
* @author Kingyu
*/
public class Pipe {
static BufferedImage[] imgs; // 水管的图片,static保证图片只加载一次
static {// 静态代码块,类加载的时候,初始化图片
final int PIPE_IMAGE_COUNT = 3;
imgs = new BufferedImage[PIPE_IMAGE_COUNT];
for (int i = 0; i < PIPE_IMAGE_COUNT; i++) {
imgs[i] = GameUtil.loadBUfferedImage(Constant.PIPE_IMG_PATH[i]);
}
}
// 所有水管的宽高
public static final int PIPE_WIDTH = imgs[0].getWidth();
public static final int PIPE_HEIGHT = imgs[0].getHeight();
public static final int PIPE_HEAD_WIDTH = imgs[1].getWidth();
public static final int PIPE_HEAD_HEIGHT = imgs[1].getHeight();
int x, y; // 水管的坐标,相对于元素层
int width, height; // 水管的宽,高
boolean visible; // 水管可见状态,true为可见,false表示可归还至对象池
// 水管的类型
int type;
public static final int TYPE_TOP_NORMAL = 0;
public static final int TYPE_TOP_HARD = 1;
public static final int TYPE_BOTTOM_NORMAL = 2;
public static final int TYPE_BOTTOM_HARD = 3;
public static final int TYPE_HOVER_NORMAL = 4;
public static final int TYPE_HOVER_HARD = 5;
// 水管的速度
public static final int MIN_SPEED = 1;
public static final int MAX_SPEED = 2;
int speed;
Rectangle pipeRect; // 水管的碰撞矩形
// 构造器
public Pipe() {
this.speed = MIN_SPEED;
this.width = PIPE_WIDTH;
pipeRect = new Rectangle();
pipeRect.width = PIPE_WIDTH;
}
/**
* 设置水管参数
*
* @param x
* @param y
* @param height
* @param type
* @param visible
*/
public void setAttribute(int x, int y, int height, int type, boolean visible) {
this.x = x;
this.y = y;
this.height = height;
this.type = type;
this.visible = visible;
setRectangle(this.x, this.y, this.height);
}
/**
* 设置碰撞矩形参数
*
* @param x
* @param y
* @param height
*/
public void setRectangle(int x, int y, int height) {
pipeRect.x = x;
pipeRect.y = y;
pipeRect.height = height;
}
// 判断水管是否位于窗口
public boolean isVisible() {
return visible;
}
// 绘制方法
public void draw(Graphics g, Bird bird) {
switch (type) {
case TYPE_TOP_NORMAL:
drawTopNormal(g);
break;
case TYPE_BOTTOM_NORMAL:
drawBottomNormal(g);
break;
case TYPE_HOVER_NORMAL:
drawHoverNormal(g);
break;
}
// //绘制碰撞矩形
// g.setColor(Color.black);
// g.drawRect((int) pipeRect.getX(), (int) pipeRect.getY(), (int) pipeRect.getWidth(), (int) pipeRect.getHeight());
//鸟死后水管停止移动
if (bird.isDead()) {
return;
}
pipeLogic();
}
// 绘制从上往下的普通水管
private void drawTopNormal(Graphics g) {
// 拼接的个数
int count = (height - PIPE_HEAD_HEIGHT) / PIPE_HEIGHT + 1; // 取整+1
// 绘制水管的主体
for (int i = 0; i < count; i++) {
g.drawImage(imgs[0], x, y + i * PIPE_HEIGHT, null);
}
// 绘制水管的顶部
g.drawImage(imgs[1], x - ((PIPE_HEAD_WIDTH - width) >> 1),
height - Constant.TOP_PIPE_LENGTHENING - PIPE_HEAD_HEIGHT, null); // 水管头部与水管主体的宽度不同,x坐标需要处理
}
// 绘制从下往上的普通水管
private void drawBottomNormal(Graphics g) {
// 拼接的个数
int count = (height - PIPE_HEAD_HEIGHT - Constant.GROUND_HEIGHT) / PIPE_HEIGHT + 1;
// 绘制水管的主体
for (int i = 0; i < count; i++) {
g.drawImage(imgs[0], x, Constant.FRAME_HEIGHT - PIPE_HEIGHT - Constant.GROUND_HEIGHT - i * PIPE_HEIGHT,
null);
}
// 绘制水管的顶部
g.drawImage(imgs[2], x - ((PIPE_HEAD_WIDTH - width) >> 1), Constant.FRAME_HEIGHT - height, null);
}
// 绘制悬浮的普通水管
private void drawHoverNormal(Graphics g) {
// 拼接的个数
int count = (height - 2 * PIPE_HEAD_HEIGHT) / PIPE_HEIGHT + 1;
// 绘制水管的上顶部
g.drawImage(imgs[2], x - ((PIPE_HEAD_WIDTH - width) >> 1), y, null);
// 绘制水管的主体
for (int i = 0; i < count; i++) {
g.drawImage(imgs[0], x, y + i * PIPE_HEIGHT + PIPE_HEAD_HEIGHT, null);
}
// 绘制水管的下底部
int y = this.y + height - PIPE_HEAD_HEIGHT;
g.drawImage(imgs[1], x - ((PIPE_HEAD_WIDTH - width) >> 1), y, null);
}
/**
* 普通水管的运动逻辑
*/
private void pipeLogic() {
x -= speed;
pipeRect.x -= speed;
if (x < -1 * PIPE_HEAD_WIDTH) {// 水管完全离开了窗口
visible = false;
}
}
/**
* 判断当前水管是否完全出现在窗口中
*
* @return 若完全出现则返回true,否则返回false
*/
public boolean isInFrame() {
return x + width < Constant.FRAME_WIDTH;
}
// 各参数的写入与获取
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setType(int type) {
this.type = type;
}
public void setHeight(int height) {
this.height = height;
}
public void setVisible(boolean visible) {
this.visible = visible;
}
public int getX() {
return x;
}
// 获取水管的碰撞矩形
public Rectangle getPipeRect() {
return pipeRect;
}
}
\ No newline at end of file
static BufferedImage[] imgs; // 水管的图片,static保证图片只加载一次
static {// 静态代码块,类加载的时候,初始化图片
final int PIPE_IMAGE_COUNT = 3;
imgs = new BufferedImage[PIPE_IMAGE_COUNT];
for (int i = 0; i < PIPE_IMAGE_COUNT; i++) {
imgs[i] = GameUtil.loadBufferedImage(Constant.PIPE_IMG_PATH[i]);
}
}
// 所有水管的宽高
public static final int PIPE_WIDTH = imgs[0].getWidth();
public static final int PIPE_HEIGHT = imgs[0].getHeight();
public static final int PIPE_HEAD_WIDTH = imgs[1].getWidth();
public static final int PIPE_HEAD_HEIGHT = imgs[1].getHeight();
int x, y; // 水管的坐标,相对于元素层
int width, height; // 水管的宽,高
boolean visible; // 水管可见状态,true为可见,false表示可归还至对象池
// 水管的类型
int type;
public static final int TYPE_TOP_NORMAL = 0;
public static final int TYPE_TOP_HARD = 1;
public static final int TYPE_BOTTOM_NORMAL = 2;
public static final int TYPE_BOTTOM_HARD = 3;
public static final int TYPE_HOVER_NORMAL = 4;
public static final int TYPE_HOVER_HARD = 5;
// 水管的速度
public static final int SPEED = 1;
int speed;
Rectangle pipeRect; // 水管的碰撞矩形
// 构造器
public Pipe() {
this.speed = SPEED;
this.width = PIPE_WIDTH;
pipeRect = new Rectangle();
pipeRect.width = PIPE_WIDTH;
}
/**
* 设置水管参数
*
* @param x:x坐标
* @param y:y坐标
* @param height:水管高度
* @param type:水管类型
* @param visible:水管可见性
*/
public void setAttribute(int x, int y, int height, int type, boolean visible) {
this.x = x;
this.y = y;
this.height = height;
this.type = type;
this.visible = visible;
setRectangle(this.x, this.y, this.height);
}
/**
* 设置碰撞矩形参数
*/
public void setRectangle(int x, int y, int height) {
pipeRect.x = x;
pipeRect.y = y;
pipeRect.height = height;
}
// 判断水管是否位于窗口
public boolean isVisible() {
return visible;
}
// 绘制方法
public void draw(Graphics g, Bird bird) {
switch (type) {
case TYPE_TOP_NORMAL:
drawTopNormal(g);
break;
case TYPE_BOTTOM_NORMAL:
drawBottomNormal(g);
break;
case TYPE_HOVER_NORMAL:
drawHoverNormal(g);
break;
}
// //绘制碰撞矩形
// g.setColor(Color.black);
// g.drawRect((int) pipeRect.getX(), (int) pipeRect.getY(), (int) pipeRect.getWidth(), (int) pipeRect.getHeight());
//鸟死后水管停止移动
if (bird.isDead()) {
return;
}
pipeLogic();
}
// 绘制从上往下的普通水管
private void drawTopNormal(Graphics g) {
// 拼接的个数
int count = (height - PIPE_HEAD_HEIGHT) / PIPE_HEIGHT + 1; // 取整+1
// 绘制水管的主体
for (int i = 0; i < count; i++) {
g.drawImage(imgs[0], x, y + i * PIPE_HEIGHT, null);
}
// 绘制水管的顶部
g.drawImage(imgs[1], x - ((PIPE_HEAD_WIDTH - width) >> 1),
height - Constant.TOP_PIPE_LENGTHENING - PIPE_HEAD_HEIGHT, null); // 水管头部与水管主体的宽度不同,x坐标需要处理
}
// 绘制从下往上的普通水管
private void drawBottomNormal(Graphics g) {
// 拼接的个数
int count = (height - PIPE_HEAD_HEIGHT - Constant.GROUND_HEIGHT) / PIPE_HEIGHT + 1;
// 绘制水管的主体
for (int i = 0; i < count; i++) {
g.drawImage(imgs[0], x, Constant.FRAME_HEIGHT - PIPE_HEIGHT - Constant.GROUND_HEIGHT - i * PIPE_HEIGHT,
null);
}
// 绘制水管的顶部
g.drawImage(imgs[2], x - ((PIPE_HEAD_WIDTH - width) >> 1), Constant.FRAME_HEIGHT - height, null);
}
// 绘制悬浮的普通水管
private void drawHoverNormal(Graphics g) {
// 拼接的个数
int count = (height - 2 * PIPE_HEAD_HEIGHT) / PIPE_HEIGHT + 1;
// 绘制水管的上顶部
g.drawImage(imgs[2], x - ((PIPE_HEAD_WIDTH - width) >> 1), y, null);
// 绘制水管的主体
for (int i = 0; i < count; i++) {
g.drawImage(imgs[0], x, y + i * PIPE_HEIGHT + PIPE_HEAD_HEIGHT, null);
}
// 绘制水管的下底部
int y = this.y + height - PIPE_HEAD_HEIGHT;
g.drawImage(imgs[1], x - ((PIPE_HEAD_WIDTH - width) >> 1), y, null);
}
/**
* 普通水管的运动逻辑
*/
private void pipeLogic() {
x -= speed;
pipeRect.x -= speed;
if (x < -1 * PIPE_HEAD_WIDTH) {// 水管完全离开了窗口
visible = false;
}
}
/**
* 判断当前水管是否完全出现在窗口中
*
* @return 若完全出现则返回true,否则返回false
*/
public boolean isInFrame() {
return x + width < Constant.FRAME_WIDTH;
}
// 获取水管的x坐标
public int getX() {
return x;
}
// 获取水管的碰撞矩形
public Rectangle getPipeRect() {
return pipeRect;
}
// 各参数的写入与获取
// public void setX(int x) {
// this.x = x;
// }
//
// public void setY(int y) {
// this.y = y;
// }
//
// public void setType(int type) {
// this.type = type;
// }
//
// public void setHeight(int height) {
// this.height = height;
// }
//
// public void setVisible(boolean visible) {
// this.visible = visible;
// }
}
......@@ -6,25 +6,23 @@ import java.util.List;
import com.bird.util.Constant;
/**
* 为了避免反复地创建和销毁对象,使用对象池来提前创建好一些对象,使用时从对象池中获得,使用完归还
* 为了避免反复地创建和销毁对象,使用对象池来提前创建好一些对象,使用时从对象池中获得,使用完归还
*
* @author Kingyu
*
*/
public class PipePool {
private static List<Pipe> pool = new ArrayList<Pipe>(); // 池中对象的容器
private static List<MovingPipe> movingPool = new ArrayList<MovingPipe>(); // 池中对象的容器
private static final List<Pipe> pool = new ArrayList<>(); // 池中对象的容器
private static final List<MovingPipe> movingPool = new ArrayList<>(); // 池中对象的容器
public static final int INIT_PIPE_COUNT = (Constant.FRAME_WIDTH
/ (Pipe.PIPE_HEAD_WIDTH + GameElementLayer.HORIZONTAL_INTERVAL) + 2) * 2; // 根据窗口宽度算得对象池中对象的初始个数
public static final int MAX_PIPE_COUNT = 30; // 对象池中对象的最大个数,自行定义
// 初始化水管容器
// 初始化水管容器,初始化水管的数量的计算方式见常量类中的注释
static {
for (int i = 0; i < INIT_PIPE_COUNT; i++) {
for (int i = 0; i < Constant.FULL_PIPE; i++) {
pool.add(new Pipe());
}
for (int i = 0; i < INIT_PIPE_COUNT; i++) {
for (int i = 0; i < Constant.FULL_PIPE; i++) {
movingPool.add(new MovingPipe());
}
}
......@@ -54,8 +52,6 @@ public class PipePool {
/**
* 归还对象给容器
*
* @param pipe
*/
public static void giveBack(Pipe pipe) {
//判断类的类型
......
......@@ -3,9 +3,12 @@ package com.bird.util;
import java.awt.Color;
import java.awt.Font;
import com.bird.main.GameElementLayer;
import com.bird.main.Pipe;
/**
* 常量类
*
*
* @author Kingyu 后续优化可写入数据库或文件中,便于修改
*/
......@@ -18,15 +21,13 @@ public class Constant {
public static final String GAME_TITLE = "Flappy Bird written by Kingyu";
// 窗口位置
public static final int FRAME_X = 1200;
public static final int FRAME_X = 600;
public static final int FRAME_Y = 100;
// 图像资源路径
public static final String BG_IMG_PATH = "resources/img/background.png"; // 背景图片
public static final int HOVER_MOVING_SCORE = 4; //出现移动管道的分数
// 小鸟图片
// 小鸟图片
public static final String[][] BIRDS_IMG_PATH = {
{ "resources/img/0.png", "resources/img/1.png", "resources/img/2.png", "resources/img/3.png",
"resources/img/4.png", "resources/img/5.png", "resources/img/6.png", "resources/img/7.png" },
......@@ -72,9 +73,11 @@ public class Constant {
public static final int CLOUD_BORN_PERCENT = 6; // 云朵生成的概率,单位为百分比
public static final int CLOUD_IMAGE_COUNT = 2; // 云朵图片的个数
public static final int MAX_CLOUD_COUNT = 7; // 云朵的最大数量
public static final int CLOUD_DIRCHANGE = 50; // 云朵随机改变方向的概率,越大表示概率越小
public static final Font TIME_FONT = new Font("华文琥珀", Font.BOLD, 32);// 字体
public static final Font CURRENT_SCORE_FONT = new Font("华文琥珀", Font.BOLD, 32);// 字体
public static final Font SCORE_FONT = new Font("华文琥珀", Font.BOLD, 24);// 字体
}
\ No newline at end of file
// 窗口可容纳的水管数量+2, 由窗口宽度、水管宽度、水管间距算得
public static final int FULL_PIPE = (Constant.FRAME_WIDTH
/ (Pipe.PIPE_HEAD_WIDTH + GameElementLayer.HORIZONTAL_INTERVAL) + 2) * 2;
}
package com.bird.util;
import java.awt.Font;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.IOException;
......@@ -11,76 +11,81 @@ import javax.imageio.ImageIO;
/**
* 工具类,游戏中用到的工具都在此类
*
* @author Kingyu
*
* @author Kingyu
*/
public class GameUtil {
private GameUtil() {
} // 私有化,防止其他类实例化此类
private GameUtil() {
} // 私有化,防止其他类实例化此类
/**
* 装载图片的方法
*
* @param imgPath 图片路径
* @return 图片资源
*/
public static BufferedImage loadBufferedImage(String imgPath) {
try {
return ImageIO.read(new FileInputStream(imgPath));
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 判断任意概率的概率性事件是否发生
*
* @param numerator 分子,不小于0的值
* @param denominator 分母,不小于0的值
* @return 概率性事件发生返回true,否则返回false
*/
public static boolean isInProbability(int numerator, int denominator) throws Exception {
// 分子分母不小于0
if (numerator <= 0 || denominator <= 0) {
throw new Exception("传入了非法的参数");
}
//分子大于分母,一定发生
if (numerator >= denominator) {
return true;
}
return getRandomNumber(1, denominator + 1) <= numerator;
}
/**
* 返回指定区间的一个随机数
*
* @param min 区间最小值,包含
* @param max 区间最大值,不包含
* @return 该区间的随机数
*/
public static int getRandomNumber(int min, int max) {
return (int) (Math.random() * (max - min) + min);
}
/**
* 装载图片的方法
*
* @param imgPath 图片路径
* @return
*/
public static BufferedImage loadBUfferedImage(String imgPath) {
try {
return ImageIO.read(new FileInputStream(imgPath));
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 获得指定字符串在指定字体的宽高
*/
public static int getStringWidth(Font font, String str) {
AffineTransform affinetransform = new AffineTransform();
FontRenderContext frc = new FontRenderContext(affinetransform, true, true);
return (int) (font.getStringBounds(str, frc).getWidth());
}
/**
* 判断任意概率的概率性事件是否发生
*
* @param numerator 分子,不小于0的值
* @param denominator 分母,不小于0的值
* @return 概率性事件发生返回true,否则返回false
*/
public static boolean isInProbability(int numerator, int denominator)throws Exception{
// 分子分母不小于0
if (numerator <= 0 || denominator <= 0) {
throw new Exception("传入了非法的参数");
}
//分子大于分母,一定发生
if(numerator >= denominator) {
return true;
}
public static int getStringHeight(Font font, String str) {
AffineTransform affinetransform = new AffineTransform();
FontRenderContext frc = new FontRenderContext(affinetransform, true, true);
return (int) (font.getStringBounds(str, frc).getHeight());
}
return getRandomNumber(1, denominator + 1) <= numerator;
}
/**
* 返回指定区间的一个随机数
*
* @param min 区间最小值,包含
* @param max 区间最大值,不包含
* @return 该区间的随机数
*/
public static int getRandomNumber(int min, int max) {
return (int) (Math.random() * (max - min) + min);
}
/**
* 获得指定字符串在指定字体的宽高
*/
public static int getStringWidth(Font font, String str) {
AffineTransform affinetransform = new AffineTransform();
FontRenderContext frc = new FontRenderContext(affinetransform,true,true);
int textHeight = (int)(font.getStringBounds(str, frc).getWidth());
return textHeight;
}
// 于屏幕x轴中央、y轴3/5处绘制图像
public static void drawTitle(BufferedImage image, Graphics g) {
int x = Constant.FRAME_WIDTH - image.getWidth() >> 1;
int y = Constant.FRAME_HEIGHT / 5 * 3;
g.drawImage(image, x, y, null);
}
public static int getStringHeight(Font font, String str) {
AffineTransform affinetransform = new AffineTransform();
FontRenderContext frc = new FontRenderContext(affinetransform,true,true);
int textHeight = (int)(font.getStringBounds(str, frc).getHeight());
return textHeight;
}
}
package com.bird.util;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
......@@ -10,51 +9,44 @@ import sun.audio.AudioStream;
/**
* 音乐工具类
*
* @author Kingyu wav音频:JDK提供的类可直接解码 mp3音频:JDK没有提供支持,需要使用第三方的工具包
*
*
* @author Kingyu
* wav音频:JDK提供的类可直接解码 mp3音频:JDK没有提供支持,需要使用第三方的工具包
*/
public class MusicUtil {
private static AudioStream fly;
private static AudioStream crash;
private static AudioStream score;
private static InputStream flyIn;
private static InputStream crashIn;
private static InputStream scoreIn;
//wav播放
public static void playFly() {
try {
// create an audiostream from the inputstream
flyIn = new FileInputStream("resources/wav/fly.wav");
fly = new AudioStream(flyIn);
} catch (FileNotFoundException fnfe) {
} catch (IOException ioe) {
}
AudioPlayer.player.start(fly);
}
public static void playCrash() {
try {
// create an audiostream from the inputstream
crashIn = new FileInputStream("resources/wav/crash.wav");
crash = new AudioStream(crashIn);
} catch (FileNotFoundException fnfe) {
} catch (IOException ioe) {
}
AudioPlayer.player.start(crash);
}
public static void playScore() {
try {
// create an audiostream from the inputstream
scoreIn = new FileInputStream("resources/wav/score.wav");
score = new AudioStream(scoreIn);
} catch (FileNotFoundException fnfe) {
} catch (IOException ioe) {
}
AudioPlayer.player.start(score);
}
private static AudioStream fly;
private static AudioStream crash;
private static AudioStream score;
// wav播放
public static void playFly() {
try {
// create an AudioStream from the InputStream
InputStream flyIn = new FileInputStream("resources/wav/fly.wav");
fly = new AudioStream(flyIn);
} catch (IOException ignored) {
}
AudioPlayer.player.start(fly);
}
public static void playCrash() {
try {
// create an AudioStream from the InputStream
InputStream crashIn = new FileInputStream("resources/wav/crash.wav");
crash = new AudioStream(crashIn);
} catch (IOException ignored) {
}
AudioPlayer.player.start(crash);
}
public static void playScore() {
try {
// create an AudioStream from the InputStream
InputStream scoreIn = new FileInputStream("resources/wav/score.wav");
score = new AudioStream(scoreIn);
} catch (IOException ignored) {
}
AudioPlayer.player.start(score);
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment