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

feat: 优化记分方法

BREAKING CHANGE: 移除了计时器,更改了游戏的记分方法,现在记分更准确了
parent e2cb276d
...@@ -3,6 +3,17 @@ ...@@ -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. 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) ### [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. ...@@ -8,21 +8,26 @@ Flappy Bird for desktop platforms.
## Overview ## 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 ## Package Contents
...@@ -34,6 +39,9 @@ Flappy Bird for desktop platforms. ...@@ -34,6 +39,9 @@ Flappy Bird for desktop platforms.
## Change Log ## Change Log
v1.2.2 - July 12, 2020
* 移除了计时器,优化了游戏的记分方式,现在记分更准确了
v1.2.1 - July 12, 2020 v1.2.1 - July 12, 2020
* 使用AudioClip类的方法播放连续的短音频可能会导致线程冲突使游戏卡顿 * 使用AudioClip类的方法播放连续的短音频可能会导致线程冲突使游戏卡顿
...@@ -55,4 +63,8 @@ v1.0.0 - July 10, 2020 ...@@ -55,4 +63,8 @@ v1.0.0 - July 10, 2020
* sun包在不同操作系统和不同版本的JDK中可能发生变化,因此无法确保工作在所有JAVA平台上 * sun包在不同操作系统和不同版本的JDK中可能发生变化,因此无法确保工作在所有JAVA平台上
## Contact ## Contact
* email: <kingyuluk@mail.dlut.edu.cn> * email: <kingyuluk@hotmail.com>
## License
* 图片与音效资源皆来源于网络,仅供学习交流
...@@ -9,17 +9,19 @@ import com.bird.util.Constant; ...@@ -9,17 +9,19 @@ import com.bird.util.Constant;
import com.bird.util.GameUtil; import com.bird.util.GameUtil;
import com.bird.util.MusicUtil; import com.bird.util.MusicUtil;
import static com.bird.util.GameUtil.drawTitle;
/** /**
* 小鸟类,小鸟的绘制与飞行逻辑都在此类 * 小鸟类,小鸟的绘制与飞行逻辑都在此类
* *
* @author Kingyu * @author Kingyu
*
*/ */
public class Bird { public class Bird {
public static final int IMG_COUNT = 8; // 图片数量 public static final int IMG_COUNT = 8; // 图片数量
public static final int STATE_COUNT = 4; // 状态数 public static final int STATE_COUNT = 4; // 状态数
private BufferedImage[][] birdImgs; // 小鸟的图片数组对象 private final BufferedImage[][] birdImages; // 小鸟的图片数组对象
private int x, y; // 小鸟的坐标 private final int x;
private int y; // 小鸟的坐标
int wingState; // 翅膀状态 int wingState; // 翅膀状态
...@@ -37,20 +39,20 @@ public class Bird { ...@@ -37,20 +39,20 @@ public class Bird {
public static final int STATE_FALL = 3; public static final int STATE_FALL = 3;
public static final int STATE_DEAD = 4; public static final int STATE_DEAD = 4;
private Rectangle birdRect; // 碰撞矩形 private final Rectangle birdRect; // 碰撞矩形
public static final int RECT_DESCALE = 2; // 补偿碰撞矩形宽高的参数 public static final int RECT_DESCALE = 2; // 补偿碰撞矩形宽高的参数
private GameTime timing; // 飞行时间 private final GameScore countScore; // 计分器
// 在构造器中对资源初始化 // 在构造器中对资源初始化
public Bird() { public Bird() {
timing = GameTime.getInstance(); // 计 countScore = GameScore.getInstance(); // 计
// 读取小鸟图片资源 // 读取小鸟图片资源
birdImgs = new BufferedImage[STATE_COUNT][IMG_COUNT]; birdImages = new BufferedImage[STATE_COUNT][IMG_COUNT];
for (int j = 0; j < STATE_COUNT; j++) { for (int j = 0; j < STATE_COUNT; j++) {
for (int i = 0; i < IMG_COUNT; i++) { for (int i = 0; i < IMG_COUNT; i++) {
birdImgs[j][i] = GameUtil.loadBUfferedImage(Constant.BIRDS_IMG_PATH[j][i]); birdImages[j][i] = GameUtil.loadBufferedImage(Constant.BIRDS_IMG_PATH[j][i]);
} }
} }
...@@ -58,33 +60,30 @@ public class Bird { ...@@ -58,33 +60,30 @@ public class Bird {
x = Constant.FRAME_WIDTH >> 2; x = Constant.FRAME_WIDTH >> 2;
y = Constant.FRAME_HEIGHT >> 1; y = Constant.FRAME_HEIGHT >> 1;
int ImgWidth = birdImgs[state][0].getWidth(); int ImgWidth = birdImages[state][0].getWidth();
int ImgHeight = birdImgs[state][0].getHeight(); int ImgHeight = birdImages[state][0].getHeight();
// 初始化碰撞矩形 // 初始化碰撞矩形
int rectX = x - ImgWidth / 2; int rectX = x - ImgWidth / 2;
int rectY = y - ImgHeight / 2; int rectY = y - ImgHeight / 2;
int rectWidth = ImgWidth; birdRect = new Rectangle(rectX + RECT_DESCALE, rectY + RECT_DESCALE * 2, ImgWidth - RECT_DESCALE * 3,
int rectHeight = ImgHeight; ImgHeight - RECT_DESCALE * 4); // 碰撞矩形的坐标与小鸟相同
birdRect = new Rectangle(rectX + RECT_DESCALE, rectY + RECT_DESCALE * 2, rectWidth - RECT_DESCALE * 3,
rectHeight - RECT_DESCALE * 4); // 碰撞矩形的坐标与小鸟相同
} }
// 绘制方法 // 绘制方法
public void draw(Graphics g) { public void draw(Graphics g) {
fly(); fly();
int state_index = state > STATE_FALL ? STATE_FALL : state; // 图片资源索引 int state_index = Math.min(state, STATE_FALL); // 图片资源索引
// 小鸟中心点计算 // 小鸟中心点计算
int halfImgWidth = birdImgs[state_index][0].getWidth() >> 1; int halfImgWidth = birdImages[state_index][0].getWidth() >> 1;
int halfImgHeight = birdImgs[state_index][0].getHeight() >> 1; int halfImgHeight = birdImages[state_index][0].getHeight() >> 1;
if (speed > 0) if (speed > 0)
image = birdImgs[STATE_UP][0]; image = birdImages[STATE_UP][0];
g.drawImage(image, x - halfImgWidth, y - halfImgHeight, null); // x坐标于窗口1/4处,y坐标位窗口中心 g.drawImage(image, x - halfImgWidth, y - halfImgHeight, null); // x坐标于窗口1/4处,y坐标位窗口中心
if (state == STATE_DEAD) { if (state == STATE_DEAD) {
drawGameover(g); drawGameOver(g);
} else if (state == STATE_FALL) { } else if (state != STATE_FALL) {
} else {
drawScore(g); drawScore(g);
} }
// 绘制矩形 // 绘制矩形
...@@ -96,7 +95,6 @@ public class Bird { ...@@ -96,7 +95,6 @@ public class Bird {
public static final double g = 9.8; // 重力加速度 public static final double g = 9.8; // 重力加速度
public static final double T = 0.2; // 小鸟的下落函数执行的时间 public static final double T = 0.2; // 小鸟的下落函数执行的时间
private double h; // 小鸟y轴的位移量
private double speed = 0; // 小鸟的初速度 private double speed = 0; // 小鸟的初速度
private boolean keyFlag = true; // 按键状态,true为已释放,使当按住按键时不会重复调用方法 private boolean keyFlag = true; // 按键状态,true为已释放,使当按住按键时不会重复调用方法
...@@ -117,25 +115,20 @@ public class Bird { ...@@ -117,25 +115,20 @@ public class Bird {
private void fly() { private void fly() {
// 翅膀状态,实现小鸟振翅飞行 // 翅膀状态,实现小鸟振翅飞行
wingState++; wingState++;
image = birdImgs[state > STATE_FALL ? STATE_FALL : state][wingState / 10 % IMG_COUNT]; image = birdImages[Math.min(state, STATE_FALL)][wingState / 10 % IMG_COUNT];
switch (state) { switch (state) {
case STATE_NORMAL:
break;
case STATE_UP:
break;
case STATE_DOWN: case STATE_DOWN:
// 物理公式 // 物理公式
speed -= g * T; speed -= g * T;
h = speed * T - g * T * T / 2; // 小鸟y轴的位移量
double h = speed * T - g * T * T / 2;
y = (int) (y - h); y = (int) (y - h);
birdRect.y = (int) (birdRect.y - h); birdRect.y = (int) (birdRect.y - h);
// 控制坠落的边界,若y坐标 > 窗口的高度 - 地面的高度 - 小鸟图片的高度则死亡 // 控制坠落的边界,若y坐标 > 窗口的高度 - 地面的高度 - 小鸟图片的高度则死亡
if (birdRect.y >= Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1)) { if (birdRect.y >= Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImages[state][0].getHeight() >> 1)) {
y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1); y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImages[state][0].getHeight() >> 1);
birdRect.y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1); birdRect.y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImages[state][0].getHeight() >> 1);
birdFall(); birdFall();
} }
...@@ -149,16 +142,18 @@ public class Bird { ...@@ -149,16 +142,18 @@ public class Bird {
birdRect.y = (int) (birdRect.y - h); birdRect.y = (int) (birdRect.y - h);
// 控制坠落的边界,若y坐标 > 窗口的高度 - 地面的高度 - 小鸟图片的高度则死亡 // 控制坠落的边界,若y坐标 > 窗口的高度 - 地面的高度 - 小鸟图片的高度则死亡
if (birdRect.y >= Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1)) { if (birdRect.y >= Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImages[state][0].getHeight() >> 1)) {
y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1); y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImages[state][0].getHeight() >> 1);
birdRect.y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1); birdRect.y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImages[state][0].getHeight() >> 1);
GameFrame.setGameState(GameFrame.STATE_OVER); // 改变游戏状态 GameFrame.setGameState(GameFrame.STATE_OVER); // 改变游戏状态
birdDead(); birdDead();
} }
break; break;
case STATE_NORMAL:
case STATE_UP:
case STATE_DEAD: case STATE_DEAD:
break; break;
} }
...@@ -199,8 +194,6 @@ public class Bird { ...@@ -199,8 +194,6 @@ public class Bird {
public void birdFall() { public void birdFall() {
state = STATE_FALL; state = STATE_FALL;
MusicUtil.playCrash(); // 播放音效 MusicUtil.playCrash(); // 播放音效
// 结束计时
timing.endTiming();
} }
// 小鸟死亡 // 小鸟死亡
...@@ -208,28 +201,23 @@ public class Bird { ...@@ -208,28 +201,23 @@ public class Bird {
state = STATE_DEAD; state = STATE_DEAD;
// 加载游戏结束的资源 // 加载游戏结束的资源
if (overImg == null) { if (overImg == null) {
overImg = GameUtil.loadBUfferedImage(Constant.OVER_IMG_PATH); overImg = GameUtil.loadBufferedImage(Constant.OVER_IMG_PATH);
scoreImg = GameUtil.loadBUfferedImage(Constant.SCORE_IMG_PATH); scoreImg = GameUtil.loadBufferedImage(Constant.SCORE_IMG_PATH);
againImg = GameUtil.loadBUfferedImage(Constant.AGAIN_IMG_PATH); againImg = GameUtil.loadBufferedImage(Constant.AGAIN_IMG_PATH);
} }
countScore.isSaveScore(); // 判断是否保存纪录
} }
public boolean isDead() { public boolean isDead() {
return state == STATE_FALL || state == STATE_DEAD; return state == STATE_FALL || state == STATE_DEAD;
} }
// 开始计时的方法
public void startTiming() {
if (timing.isReadyTiming())
timing.startTiming();
}
// 绘制实时分数 // 绘制实时分数
private void drawScore(Graphics g) { private void drawScore(Graphics g) {
g.setColor(Color.white); g.setColor(Color.white);
g.setFont(Constant.TIME_FONT); g.setFont(Constant.CURRENT_SCORE_FONT);
String str = Long.toString(timing.TimeToScore()); String str = Long.toString(countScore.getScore());
int x = Constant.FRAME_WIDTH - GameUtil.getStringWidth(Constant.TIME_FONT, str) >> 1; int x = Constant.FRAME_WIDTH - GameUtil.getStringWidth(Constant.CURRENT_SCORE_FONT, str) >> 1;
g.drawString(str, x, Constant.FRAME_HEIGHT / 10); g.drawString(str, x, Constant.FRAME_HEIGHT / 10);
} }
...@@ -238,7 +226,7 @@ public class Bird { ...@@ -238,7 +226,7 @@ public class Bird {
private int flash = 0; // 图片闪烁参数 private int flash = 0; // 图片闪烁参数
// 绘制游戏结束的显示 // 绘制游戏结束的显示
private void drawGameover(Graphics g) { private void drawGameOver(Graphics g) {
// 绘制结束标志 // 绘制结束标志
int x = Constant.FRAME_WIDTH - overImg.getWidth() >> 1; int x = Constant.FRAME_WIDTH - overImg.getWidth() >> 1;
int y = Constant.FRAME_HEIGHT / 4; int y = Constant.FRAME_HEIGHT / 4;
...@@ -254,28 +242,26 @@ public class Bird { ...@@ -254,28 +242,26 @@ public class Bird {
g.setFont(Constant.SCORE_FONT); g.setFont(Constant.SCORE_FONT);
x = (Constant.FRAME_WIDTH - scoreImg.getWidth() / 2 >> 1) + SCORE_LOCATE;// 位置补偿 x = (Constant.FRAME_WIDTH - scoreImg.getWidth() / 2 >> 1) + SCORE_LOCATE;// 位置补偿
y += scoreImg.getHeight() >> 1; y += scoreImg.getHeight() >> 1;
String str = Long.toString(timing.TimeToScore()); String str = Long.toString(countScore.getScore());
x -= GameUtil.getStringWidth(Constant.SCORE_FONT, str) >> 1; x -= GameUtil.getStringWidth(Constant.SCORE_FONT, str) >> 1;
y += GameUtil.getStringHeight(Constant.SCORE_FONT, str); y += GameUtil.getStringHeight(Constant.SCORE_FONT, str);
g.drawString(str, x, y); g.drawString(str, x, y);
// 绘制最高分数 // 绘制最高分数
if (timing.getBestScore() > 0) { if (countScore.getBestScore() > 0) {
str = Long.toString(timing.getBestScore()); str = Long.toString(countScore.getBestScore());
x = (Constant.FRAME_WIDTH + scoreImg.getWidth() / 2 >> 1) - SCORE_LOCATE;// 位置补偿 x = (Constant.FRAME_WIDTH + scoreImg.getWidth() / 2 >> 1) - SCORE_LOCATE;// 位置补偿
x -= GameUtil.getStringWidth(Constant.SCORE_FONT, str) >> 1; x -= GameUtil.getStringWidth(Constant.SCORE_FONT, str) >> 1;
g.drawString(str, x, y); g.drawString(str, x, y);
} }
// 绘制继续游戏
final int COUNT = 30; // 控制闪烁间隔的参数 // 绘制继续游戏,使图像闪烁
if (flash++ > COUNT) { final int COUNT = 30; // 闪烁周期
x = Constant.FRAME_WIDTH - againImg.getWidth() >> 1; if (flash++ > COUNT)
y = Constant.FRAME_HEIGHT / 5 * 3; drawTitle(againImg, g);
g.drawImage(againImg, x, y, null); if (flash == COUNT * 2) // 重置闪烁参数
if (flash == COUNT * 2)
flash = 0; flash = 0;
} }
}
// 重置小鸟 // 重置小鸟
public void reset() { public void reset() {
...@@ -283,10 +269,10 @@ public class Bird { ...@@ -283,10 +269,10 @@ public class Bird {
y = Constant.FRAME_HEIGHT >> 1; // 小鸟坐标 y = Constant.FRAME_HEIGHT >> 1; // 小鸟坐标
speed = 0; // 小鸟速度 speed = 0; // 小鸟速度
int ImgHeight = birdImgs[state][0].getHeight(); int ImgHeight = birdImages[state][0].getHeight();
birdRect.y = y - ImgHeight / 2 + RECT_DESCALE * 2; // 小鸟碰撞矩形坐标 birdRect.y = y - ImgHeight / 2 + RECT_DESCALE * 2; // 小鸟碰撞矩形坐标
timing.reset(); // 计时 countScore.reset(); // 重置计分
flash = 0; flash = 0;
} }
......
...@@ -3,41 +3,31 @@ package com.bird.main; ...@@ -3,41 +3,31 @@ package com.bird.main;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import com.bird.util.Constant;
/** /**
* 云朵类 * 云朵类
* *
* @author Kingyu * @author Kingyu
*
*/ */
public class Cloud { public class Cloud {
private int dir; // 方向 private final int speed; // 速度
public static final int DIR_NONE = 0; private int x; // 坐标
public static final int DIR_LEFT = 1; private final int y;
public static final int DIR_RIGHT = 2;
private int speed; // 速度
private int x, y; // 坐标
private BufferedImage img; private final BufferedImage img;
// 云朵图片缩放的比例 1.0~2.0 private final int scaleImageWidth;
private double scale; private final int scaleImageHeight;
private int scaleImageWidth;
private int scaleImageHeight;
// 构造器 // 构造器
public Cloud(BufferedImage img, int dir, int x, int y) { public Cloud(BufferedImage img, int x, int y) {
super(); super();
this.img = img; this.img = img;
this.dir = dir;
this.x = x; this.x = x;
this.y = y; this.y = y;
this.speed = 2; //云朵的速度 this.speed = 2; //云朵的速度
scale = 1 + Math.random(); // Math.random()返回0.0~1.0的随机值 // 云朵图片缩放的比例 1.0~2.0
double scale = 1 + Math.random(); // Math.random()返回0.0~1.0的随机值
// 缩放云朵图片 // 缩放云朵图片
scaleImageWidth = (int) (scale * img.getWidth()); scaleImageWidth = (int) (scale * img.getWidth());
scaleImageHeight = (int) (scale * img.getWidth()); scaleImageHeight = (int) (scale * img.getWidth());
...@@ -46,11 +36,9 @@ public class Cloud { ...@@ -46,11 +36,9 @@ public class Cloud {
// 绘制方法 // 绘制方法
public void draw(Graphics g, Bird bird) { public void draw(Graphics g, Bird bird) {
int speed = this.speed; int speed = this.speed;
if (bird.isDead())
if(dir == DIR_NONE) //云彩不动 speed = 1;
speed = 0; x -= speed;
x = (dir == DIR_LEFT) ? x - speed : x + speed; // 方向逻辑
g.drawImage(img, x, y, scaleImageWidth, scaleImageHeight, null); g.drawImage(img, x, y, scaleImageWidth, scaleImageHeight, null);
} }
...@@ -60,21 +48,7 @@ public class Cloud { ...@@ -60,21 +48,7 @@ public class Cloud {
* @return 飞出则返回true,否则返回false * @return 飞出则返回true,否则返回false
*/ */
public boolean isOutFrame() { public boolean isOutFrame() {
boolean result = false; return x < -1 * scaleImageWidth;
if (dir == DIR_LEFT) {
if (x < -1 * scaleImageWidth) {
return true;
}
} else if (dir == DIR_RIGHT) {
if (x > Constant.FRAME_WIDTH) {
return true;
}
}
return result;
} }
// 改变方向
public void setDir(int dir) {
this.dir = dir;
}
} }
...@@ -14,9 +14,9 @@ import com.bird.util.GameUtil; ...@@ -14,9 +14,9 @@ import com.bird.util.GameUtil;
*/ */
public class GameBackground { public class GameBackground {
private static BufferedImage BackgroundImg;// 背景图片 private static final BufferedImage BackgroundImg;// 背景图片
private int speed; // 背景层的速度 private final int speed; // 背景层的速度
private int layerX; // 背景层的坐标 private int layerX; // 背景层的坐标
// 在构造器中初始化 // 在构造器中初始化
...@@ -26,11 +26,9 @@ public class GameBackground { ...@@ -26,11 +26,9 @@ public class GameBackground {
} }
static { //读取背景图片 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) { public void draw(Graphics g, Bird bird) {
// 绘制背景色 // 绘制背景色
......
...@@ -11,11 +11,10 @@ import com.bird.util.GameUtil; ...@@ -11,11 +11,10 @@ import com.bird.util.GameUtil;
* 游戏中各种元素层的类 * 游戏中各种元素层的类
* *
* @author Kingyu * @author Kingyu
*
*/ */
public class GameElementLayer { public class GameElementLayer {
private List<Pipe> pipes; // 水管的容器 private final List<Pipe> pipes; // 水管的容器
// 构造器 // 构造器
public GameElementLayer() { public GameElementLayer() {
...@@ -71,35 +70,27 @@ public class GameElementLayer { ...@@ -71,35 +70,27 @@ public class GameElementLayer {
} else { } else {
// 判断最后一对水管是否完全进入游戏窗口,若进入则添加水管 // 判断最后一对水管是否完全进入游戏窗口,若进入则添加水管
Pipe lastPipe = pipes.get(pipes.size() - 1); // 获得容器中最后一个水管 Pipe lastPipe = pipes.get(pipes.size() - 1); // 获得容器中最后一个水管
if (lastPipe.isInFrame()) { // 根据游戏分数难度递增 if (lastPipe.isInFrame()) {
if (GameTime.getInstance().TimeToScore() < Constant.HOVER_MOVING_SCORE) { if (pipes.size() >= Constant.FULL_PIPE - 2)// 若窗口中可容纳的水管已满,说明小鸟已飞到第一对水管的位置,开始记分
GameScore.getInstance().setScore(bird);
try { try {
if (GameUtil.isInProbability(2, 5)) { // 40%的概率生成悬浮的普通水管 int currentScore = (int) GameScore.getInstance().getScore() + 1; // 获取当前分数
addHoverPipe(lastPipe); // 移动水管刷新的概率随当前分数递增,当得分大于19后全部刷新移动水管
} else { if (GameUtil.isInProbability(currentScore, 20)) {
addNormalPipe(lastPipe); if (GameUtil.isInProbability(1, 4)) // 生成移动水管和移动悬浮水管的概率
} addMovingHoverPipe(lastPipe);
} catch (Exception e) { else
e.printStackTrace(); addMovingNormalPipe(lastPipe);
}
} else { } else {
try { if (GameUtil.isInProbability(1, 2)) // 生成静止普通水管和静止悬浮水管的概率
if (GameUtil.isInProbability(1, 4)) { // 1/4的概率生成普通水管
if(GameUtil.isInProbability(1, 2)) // 生成普通水管和悬浮水管的概率
addNormalPipe(lastPipe); addNormalPipe(lastPipe);
else else
addHoverPipe(lastPipe); addHoverPipe(lastPipe);
} else {
if(GameUtil.isInProbability(1, 3)) // 生成移动水管和移动悬浮水管的概率
addMovingHoverPipe(lastPipe);
else
addMovingNormalPipe(lastPipe);
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
}
} }
} }
...@@ -113,9 +104,9 @@ public class GameElementLayer { ...@@ -113,9 +104,9 @@ public class GameElementLayer {
int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度 int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度
int x = lastPipe.getX() + HORIZONTAL_INTERVAL; // 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔 int x = lastPipe.getX() + HORIZONTAL_INTERVAL; // 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
Pipe top = PipePool.get("Pipe"); //从水管对象池中获取对象 Pipe top = PipePool.get("Pipe"); // 从水管对象池中获取对象
//设置x, y, height, type属性 // 设置x, y, height, type属性
top.setAttribute(x, -Constant.TOP_PIPE_LENGTHENING, topHeight + Constant.TOP_PIPE_LENGTHENING, top.setAttribute(x, -Constant.TOP_PIPE_LENGTHENING, topHeight + Constant.TOP_PIPE_LENGTHENING,
Pipe.TYPE_TOP_NORMAL, true); Pipe.TYPE_TOP_NORMAL, true);
...@@ -130,7 +121,7 @@ public class GameElementLayer { ...@@ -130,7 +121,7 @@ public class GameElementLayer {
/** /**
* 添加悬浮水管 * 添加悬浮水管
* *
* @param lastPipe * @param lastPipe 传入最后一根水管以获取x坐标
*/ */
private void addHoverPipe(Pipe lastPipe) { private void addHoverPipe(Pipe lastPipe) {
...@@ -158,7 +149,7 @@ public class GameElementLayer { ...@@ -158,7 +149,7 @@ public class GameElementLayer {
/** /**
* 添加移动的悬浮水管 * 添加移动的悬浮水管
* *
* @param lastPipe * @param lastPipe 传入最后一根水管以获取x坐标
*/ */
private void addMovingHoverPipe(Pipe lastPipe) { private void addMovingHoverPipe(Pipe lastPipe) {
...@@ -186,7 +177,7 @@ public class GameElementLayer { ...@@ -186,7 +177,7 @@ public class GameElementLayer {
/** /**
* 添加移动的普通水管 * 添加移动的普通水管
* *
* @param lastPipe * @param lastPipe 传入最后一根水管以获取x坐标
*/ */
private void addMovingNormalPipe(Pipe lastPipe) { private void addMovingNormalPipe(Pipe lastPipe) {
int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度 int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度
...@@ -205,32 +196,28 @@ public class GameElementLayer { ...@@ -205,32 +196,28 @@ public class GameElementLayer {
} }
/** /**
* 判断元素和小鸟是否发生碰撞,若发生碰撞返回true,否则返回false * 判断元素和小鸟是否发生碰撞
* *
* @param bird * @param bird 传入小鸟对象
* @return
*/ */
public boolean isCollideBird(Bird bird) { public void isCollideBird(Bird bird) {
// 若鸟已死则不再判断 // 若鸟已死则不再判断
if (bird.isDead()) { if (bird.isDead()) {
return false; return;
} }
// 遍历水管容器 // 遍历水管容器
for (int i = 0; i < pipes.size(); i++) { for (Pipe pipe : pipes) {
Pipe pipe = pipes.get(i);
// 判断碰撞矩形是否有交集 // 判断碰撞矩形是否有交集
if (pipe.getPipeRect().intersects(bird.getBirdRect())) { if (pipe.getPipeRect().intersects(bird.getBirdRect())) {
bird.birdFall(); //有交集则小鸟坠落 bird.birdFall(); // 有交集则小鸟坠落
return true; return;
} }
} }
return false;
} }
// 重置元素层 // 重置元素层
public void reset() { public void reset() {
for (int i = 0; i < pipes.size(); i++) { for (Pipe pipe : pipes) {
Pipe pipe = pipes.get(i);
PipePool.giveBack(pipe); PipePool.giveBack(pipe);
} }
pipes.clear(); pipes.clear();
......
...@@ -12,13 +12,11 @@ import com.bird.util.GameUtil; ...@@ -12,13 +12,11 @@ import com.bird.util.GameUtil;
* 前景类, 游戏中的遮挡层 包含多朵云 * 前景类, 游戏中的遮挡层 包含多朵云
* *
* @author Kingyu * @author Kingyu
*
*/ */
public class GameForeground { public class GameForeground {
private List<Cloud> clouds = new ArrayList<>(); // 云朵的容器 private final List<Cloud> clouds; // 云朵的容器
private BufferedImage[] cloudImgs; // 图片资源 private final BufferedImage[] cloudImages; // 图片资源
private int cloudDir; // 云的方向
private long time; // 控制云的逻辑运算周期 private long time; // 控制云的逻辑运算周期
public static final int CLOUD_INTERVAL = 100; //云朵刷新的逻辑运算的周期 public static final int CLOUD_INTERVAL = 100; //云朵刷新的逻辑运算的周期
...@@ -27,21 +25,18 @@ public class GameForeground { ...@@ -27,21 +25,18 @@ public class GameForeground {
clouds = new ArrayList<>(); //云朵的容器 clouds = new ArrayList<>(); //云朵的容器
// 读入图片资源 // 读入图片资源
cloudImgs = new BufferedImage[Constant.CLOUD_IMAGE_COUNT]; cloudImages = new BufferedImage[Constant.CLOUD_IMAGE_COUNT];
for (int i = 0; i < Constant.CLOUD_IMAGE_COUNT; i++) { for (int i = 0; i < Constant.CLOUD_IMAGE_COUNT; i++) {
cloudImgs[i] = GameUtil.loadBUfferedImage(Constant.CLOUDS_IMG_PATH[i]); cloudImages[i] = GameUtil.loadBufferedImage(Constant.CLOUDS_IMG_PATH[i]);
} }
// 初始化云朵的属性
cloudDir = Cloud.DIR_LEFT;
time = System.currentTimeMillis(); // 获取当前时间,用于控制云的逻辑运算周期 time = System.currentTimeMillis(); // 获取当前时间,用于控制云的逻辑运算周期
} }
// 绘制方法 // 绘制方法
public void draw(Graphics g, Bird bird) { public void draw(Graphics g, Bird bird) {
cloudLogic(); cloudLogic();
for (int i = 0; i < clouds.size(); i++) { for (Cloud cloud : clouds) {
clouds.get(i).draw(g, bird); cloud.draw(g, bird);
} }
} }
...@@ -55,18 +50,14 @@ public class GameForeground { ...@@ -55,18 +50,14 @@ public class GameForeground {
try { try {
if (GameUtil.isInProbability(Constant.CLOUD_BORN_PERCENT, 100)) { // 根据给定的概率添加云朵 if (GameUtil.isInProbability(Constant.CLOUD_BORN_PERCENT, 100)) { // 根据给定的概率添加云朵
int index = GameUtil.getRandomNumber(0, Constant.CLOUD_IMAGE_COUNT); // 随机选取云朵图片 int index = GameUtil.getRandomNumber(0, Constant.CLOUD_IMAGE_COUNT); // 随机选取云朵图片
// 云朵刷新的坐标 // 云朵刷新的坐标
int x = Constant.FRAME_WIDTH; // 从屏幕左侧开始刷新 int x = Constant.FRAME_WIDTH; // 从屏幕左侧开始刷新
// 根据当前的方向调整云朵刷新的x坐标(暂时无用)
// if (cloudDir == Cloud.DIR_LEFT) {
// } else {
// x = -1 * cloudImgs[index].getWidth();
// }
// y坐标随机在上1/3屏选取 // y坐标随机在上1/3屏选取
int y = GameUtil.getRandomNumber(Constant.TOP_BAR_HEIGHT, Constant.FRAME_HEIGHT / 3); int y = GameUtil.getRandomNumber(Constant.TOP_BAR_HEIGHT, Constant.FRAME_HEIGHT / 3);
//向容器中添加云朵 //向容器中添加云朵
Cloud cloud = new Cloud(cloudImgs[index], cloudDir, x, y); Cloud cloud = new Cloud(cloudImages[index], x, y);
clouds.add(cloud); clouds.add(cloud);
} }
} catch (Exception e) { } catch (Exception e) {
...@@ -84,15 +75,6 @@ public class GameForeground { ...@@ -84,15 +75,6 @@ public class GameForeground {
} }
} }
/*
* 功能已实现,但不太符合现实,暂时注释掉 // 云朵随机改变方向, 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(); }
*/
} }
} }
} }
...@@ -20,7 +20,6 @@ import java.awt.image.BufferedImage; ...@@ -20,7 +20,6 @@ import java.awt.image.BufferedImage;
* 主窗口类,游戏窗口和绘制的相关内容 * 主窗口类,游戏窗口和绘制的相关内容
* *
* @author Kingyu * @author Kingyu
*
*/ */
public class GameFrame extends Frame implements Runnable { public class GameFrame extends Frame implements Runnable {
...@@ -72,7 +71,6 @@ public class GameFrame extends Frame implements Runnable { ...@@ -72,7 +71,6 @@ public class GameFrame extends Frame implements Runnable {
bird.birdUp(); bird.birdUp();
bird.birdDown(); bird.birdDown();
setGameState(STATE_START); // 游戏状态改变 setGameState(STATE_START); // 游戏状态改变
bird.startTiming(); // 计时器开始计时
} }
break; break;
case STATE_START: case STATE_START:
...@@ -127,12 +125,11 @@ public class GameFrame extends Frame implements Runnable { ...@@ -127,12 +125,11 @@ public class GameFrame extends Frame implements Runnable {
// 系统线程:屏幕内容的绘制,窗口事件的监听与处理 // 系统线程:屏幕内容的绘制,窗口事件的监听与处理
// 两个线程会抢夺系统资源,可能会出现一次刷新周期所绘制的内容,并没有在一次刷新周期内完成 // 两个线程会抢夺系统资源,可能会出现一次刷新周期所绘制的内容,并没有在一次刷新周期内完成
// (双缓冲)单独定义一张图片,将需要绘制的内容绘制到这张图片,再一次性地将图片绘制到窗口 // (双缓冲)单独定义一张图片,将需要绘制的内容绘制到这张图片,再一次性地将图片绘制到窗口
private BufferedImage bufImg = new BufferedImage(FRAME_WIDTH, FRAME_HEIGHT, BufferedImage.TYPE_4BYTE_ABGR); private final BufferedImage bufImg = new BufferedImage(FRAME_WIDTH, FRAME_HEIGHT, BufferedImage.TYPE_4BYTE_ABGR);
/** /**
* 绘制游戏内容 当repaint()方法被调用时,JVM会调用update(),参数g是系统提供的画笔,由系统进行实例化 * 绘制游戏内容 当repaint()方法被调用时,JVM会调用update(),参数g是系统提供的画笔,由系统进行实例化
* 单独启动一个线程,不断地快速调用repaint(),让系统对整个窗口进行重绘 * 单独启动一个线程,不断地快速调用repaint(),让系统对整个窗口进行重绘
*
*/ */
public void update(Graphics g) { public void update(Graphics g) {
Graphics bufG = bufImg.getGraphics(); // 获得图片画笔 Graphics bufG = bufImg.getGraphics(); // 获得图片画笔
...@@ -141,16 +138,18 @@ public class GameFrame extends Frame implements Runnable { ...@@ -141,16 +138,18 @@ public class GameFrame extends Frame implements Runnable {
background.draw(bufG, bird); // 背景层 background.draw(bufG, bird); // 背景层
foreground.draw(bufG, bird); // 前景层 foreground.draw(bufG, bird); // 前景层
// 鸟
if (gameState == STATE_READY) { // 游戏未开始 if (gameState == STATE_READY) { // 游戏未开始
ready.draw(bufG); ready.draw(bufG);
bird.draw(bufG); // 鸟
} else { // 游戏结束 } else { // 游戏结束
gameElement.draw(bufG, bird); // 游戏元素层 gameElement.draw(bufG, bird); // 游戏元素层
bird.draw(bufG); // 鸟
} }
bird.draw(bufG); // 鸟
g.drawImage(bufImg, 0, 0, null); // 一次性将图片绘制到屏幕上 g.drawImage(bufImg, 0, 0, null); // 一次性将图片绘制到屏幕上
} }
// TODO: 不建议在while循环中使用sleep
@SuppressWarnings("InfiniteLoopStatement")
@Override @Override
public void run() { public void run() {
while (true) { while (true) {
...@@ -163,11 +162,6 @@ public class GameFrame extends Frame implements Runnable { ...@@ -163,11 +162,6 @@ public class GameFrame extends Frame implements Runnable {
} }
} }
// 获取、设置游戏状态的方法
public static int getGameState() {
return gameState;
}
public static void setGameState(int gameState) { public static void setGameState(int gameState) {
GameFrame.gameState = gameState; GameFrame.gameState = gameState;
} }
......
...@@ -2,9 +2,9 @@ package com.bird.main; ...@@ -2,9 +2,9 @@ package com.bird.main;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import com.bird.util.Constant; import com.bird.util.Constant;
import com.bird.util.GameUtil; import com.bird.util.GameUtil;
import static com.bird.util.GameUtil.drawTitle;
/** /**
* 游戏启动界面类 * 游戏启动界面类
...@@ -14,15 +14,15 @@ import com.bird.util.GameUtil; ...@@ -14,15 +14,15 @@ import com.bird.util.GameUtil;
*/ */
public class GameReady { public class GameReady {
private BufferedImage titleImg; private final BufferedImage titleImg;
private BufferedImage noticeImg; private final BufferedImage noticeImg;
private int flash; // 图像闪烁参数 private int flash = 0; // 图像闪烁参数
// 构造器中进行初始化,装载图像资源 // 构造器中进行初始化,装载图像资源
public GameReady() { public GameReady() {
titleImg = GameUtil.loadBUfferedImage(Constant.TITLE_IMG_PATH); titleImg = GameUtil.loadBufferedImage(Constant.TITLE_IMG_PATH);
noticeImg = GameUtil.loadBUfferedImage(Constant.NOTICE_IMG_PATH); noticeImg = GameUtil.loadBufferedImage(Constant.NOTICE_IMG_PATH);
} }
public void draw(Graphics g) { public void draw(Graphics g) {
...@@ -33,14 +33,10 @@ public class GameReady { ...@@ -33,14 +33,10 @@ public class GameReady {
// 使notice的图像闪烁 // 使notice的图像闪烁
final int COUNT = 30; // 闪烁周期 final int COUNT = 30; // 闪烁周期
if (flash++ > COUNT) { if (flash++ > COUNT)
// 计算notice图像的x、y坐标 drawTitle(noticeImg, g);
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 * 2) // 重置闪烁参数
flash = 0; flash = 0;
} }
}
} }
...@@ -15,35 +15,27 @@ import com.bird.util.MusicUtil; ...@@ -15,35 +15,27 @@ import com.bird.util.MusicUtil;
* @author Kingyu * @author Kingyu
* *
*/ */
public class GameTime { public class GameScore {
private static final GameTime GAME_TIME = new GameTime(); 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 score = 0; // 分数
private long bestScore; // 最高分数 private long bestScore; // 最高分数
private GameTime() { private GameScore() {
timeState = STATE_READY;
bestScore = -1; bestScore = -1;
try { try {
loadBestTime(); loadBestScore();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
public static GameTime getInstance() { public static GameScore getInstance() {
return GAME_TIME; return GAME_SCORE;
} }
// 装载最高纪录 // 装载最高纪录
private void loadBestTime() throws Exception { private void loadBestScore() throws Exception {
File file = new File(Constant.SCORE_FILE_PATH); File file = new File(Constant.SCORE_FILE_PATH);
if (file.exists()) { if (file.exists()) {
DataInputStream dis = new DataInputStream(new FileInputStream(file)); DataInputStream dis = new DataInputStream(new FileInputStream(file));
...@@ -53,10 +45,10 @@ public class GameTime { ...@@ -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); File file = new File(Constant.SCORE_FILE_PATH);
DataOutputStream dos = new DataOutputStream(new FileOutputStream(file)); DataOutputStream dos = new DataOutputStream(new FileOutputStream(file));
dos.writeLong(time); dos.writeLong(score);
dos.close(); dos.close();
} }
...@@ -64,51 +56,20 @@ public class GameTime { ...@@ -64,51 +56,20 @@ public class GameTime {
return bestScore; return bestScore;
} }
public void setStartTime(long startTime) { public long getScore() {
this.startTime = startTime; return score;
}
public void setOverTime(long endTime) {
this.endTime = endTime;
}
/**
* 游戏用时,毫秒
*
* @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 void setScore(Bird bird) {
// 计时器是否就绪 if(!bird.isDead()){
public boolean isReadyTiming() { MusicUtil.playScore(); //每次得分播放音效
return timeState == STATE_READY; score += 1;
//小鸟没死时记分
} }
// 开始计时
public void startTiming() {
startTime = System.currentTimeMillis();
timeState = STATE_START;
} }
// 结束计时并判断是否保存记录 // 判断是否为最高纪录
public void endTiming() { public void isSaveScore() {
endTime = System.currentTimeMillis(); long score = getScore();
timeState = STATE_OVER;
// 判断本次得分是否为最高分
long score = TimeToScore();
if (bestScore < score) if (bestScore < score)
bestScore = score; bestScore = score;
try { try {
...@@ -118,39 +79,78 @@ public class GameTime { ...@@ -118,39 +79,78 @@ public class GameTime {
} }
} }
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() { public void reset() {
timeState = STATE_READY;
startTime = 0;
endTime = 0;
score = 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 { ...@@ -27,11 +27,11 @@ public class MovingPipe extends Pipe {
/** /**
* 设置水管参数 * 设置水管参数
* *
* @param x * @param x:x坐标
* @param y * @param y:y坐标
* @param height * @param height:水管高度
* @param type * @param type:水管类型
* @param visible * @param visible:水管可见性
*/ */
public void setAttribute(int x, int y, int height, int type, boolean visible) { public void setAttribute(int x, int y, int height, int type, boolean visible) {
this.x = x; this.x = x;
......
...@@ -11,15 +11,15 @@ import com.bird.util.GameUtil; ...@@ -11,15 +11,15 @@ import com.bird.util.GameUtil;
* 水管类 * 水管类
* *
* @author Kingyu * @author Kingyu
*
*/ */
public class Pipe { public class Pipe {
static BufferedImage[] imgs; // 水管的图片,static保证图片只加载一次 static BufferedImage[] imgs; // 水管的图片,static保证图片只加载一次
static {// 静态代码块,类加载的时候,初始化图片 static {// 静态代码块,类加载的时候,初始化图片
final int PIPE_IMAGE_COUNT = 3; final int PIPE_IMAGE_COUNT = 3;
imgs = new BufferedImage[PIPE_IMAGE_COUNT]; imgs = new BufferedImage[PIPE_IMAGE_COUNT];
for (int i = 0; i < PIPE_IMAGE_COUNT; i++) { for (int i = 0; i < PIPE_IMAGE_COUNT; i++) {
imgs[i] = GameUtil.loadBUfferedImage(Constant.PIPE_IMG_PATH[i]); imgs[i] = GameUtil.loadBufferedImage(Constant.PIPE_IMG_PATH[i]);
} }
} }
...@@ -43,15 +43,14 @@ public class Pipe { ...@@ -43,15 +43,14 @@ public class Pipe {
public static final int TYPE_HOVER_HARD = 5; public static final int TYPE_HOVER_HARD = 5;
// 水管的速度 // 水管的速度
public static final int MIN_SPEED = 1; public static final int SPEED = 1;
public static final int MAX_SPEED = 2;
int speed; int speed;
Rectangle pipeRect; // 水管的碰撞矩形 Rectangle pipeRect; // 水管的碰撞矩形
// 构造器 // 构造器
public Pipe() { public Pipe() {
this.speed = MIN_SPEED; this.speed = SPEED;
this.width = PIPE_WIDTH; this.width = PIPE_WIDTH;
pipeRect = new Rectangle(); pipeRect = new Rectangle();
...@@ -61,11 +60,11 @@ public class Pipe { ...@@ -61,11 +60,11 @@ public class Pipe {
/** /**
* 设置水管参数 * 设置水管参数
* *
* @param x * @param x:x坐标
* @param y * @param y:y坐标
* @param height * @param height:水管高度
* @param type * @param type:水管类型
* @param visible * @param visible:水管可见性
*/ */
public void setAttribute(int x, int y, int height, int type, boolean visible) { public void setAttribute(int x, int y, int height, int type, boolean visible) {
this.x = x; this.x = x;
...@@ -78,10 +77,6 @@ public class Pipe { ...@@ -78,10 +77,6 @@ public class Pipe {
/** /**
* 设置碰撞矩形参数 * 设置碰撞矩形参数
*
* @param x
* @param y
* @param height
*/ */
public void setRectangle(int x, int y, int height) { public void setRectangle(int x, int y, int height) {
pipeRect.x = x; pipeRect.x = x;
...@@ -179,27 +174,7 @@ public class Pipe { ...@@ -179,27 +174,7 @@ public class Pipe {
return x + width < Constant.FRAME_WIDTH; return x + width < Constant.FRAME_WIDTH;
} }
// 各参数的写入与获取 // 获取水管的x坐标
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() { public int getX() {
return x; return x;
} }
...@@ -208,4 +183,26 @@ public class Pipe { ...@@ -208,4 +183,26 @@ public class Pipe {
public Rectangle getPipeRect() { public Rectangle getPipeRect() {
return pipeRect; 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; ...@@ -6,25 +6,23 @@ import java.util.List;
import com.bird.util.Constant; import com.bird.util.Constant;
/** /**
* 为了避免反复地创建和销毁对象,使用对象池来提前创建好一些对象,使用时从对象池中获得,使用完归还 * 为了避免反复地创建和销毁对象,使用对象池来提前创建好一些对象,使用时从对象池中获得,使用完归还
* *
* @author Kingyu * @author Kingyu
* *
*/ */
public class PipePool { public class PipePool {
private static List<Pipe> pool = new ArrayList<Pipe>(); // 池中对象的容器 private static final List<Pipe> pool = new ArrayList<>(); // 池中对象的容器
private static List<MovingPipe> movingPool = new ArrayList<MovingPipe>(); // 池中对象的容器 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; // 对象池中对象的最大个数,自行定义 public static final int MAX_PIPE_COUNT = 30; // 对象池中对象的最大个数,自行定义
// 初始化水管容器 // 初始化水管容器,初始化水管的数量的计算方式见常量类中的注释
static { static {
for (int i = 0; i < INIT_PIPE_COUNT; i++) { for (int i = 0; i < Constant.FULL_PIPE; i++) {
pool.add(new Pipe()); 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()); movingPool.add(new MovingPipe());
} }
} }
...@@ -54,8 +52,6 @@ public class PipePool { ...@@ -54,8 +52,6 @@ public class PipePool {
/** /**
* 归还对象给容器 * 归还对象给容器
*
* @param pipe
*/ */
public static void giveBack(Pipe pipe) { public static void giveBack(Pipe pipe) {
//判断类的类型 //判断类的类型
......
...@@ -3,6 +3,9 @@ package com.bird.util; ...@@ -3,6 +3,9 @@ package com.bird.util;
import java.awt.Color; import java.awt.Color;
import java.awt.Font; import java.awt.Font;
import com.bird.main.GameElementLayer;
import com.bird.main.Pipe;
/** /**
* 常量类 * 常量类
* *
...@@ -18,14 +21,12 @@ public class Constant { ...@@ -18,14 +21,12 @@ public class Constant {
public static final String GAME_TITLE = "Flappy Bird written by Kingyu"; 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 int FRAME_Y = 100;
// 图像资源路径 // 图像资源路径
public static final String BG_IMG_PATH = "resources/img/background.png"; // 背景图片 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 = { 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/0.png", "resources/img/1.png", "resources/img/2.png", "resources/img/3.png",
...@@ -72,9 +73,11 @@ public class Constant { ...@@ -72,9 +73,11 @@ public class Constant {
public static final int CLOUD_BORN_PERCENT = 6; // 云朵生成的概率,单位为百分比 public static final int CLOUD_BORN_PERCENT = 6; // 云朵生成的概率,单位为百分比
public static final int CLOUD_IMAGE_COUNT = 2; // 云朵图片的个数 public static final int CLOUD_IMAGE_COUNT = 2; // 云朵图片的个数
public static final int MAX_CLOUD_COUNT = 7; // 云朵的最大数量 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);// 字体 public static final Font SCORE_FONT = new Font("华文琥珀", Font.BOLD, 24);// 字体
// 窗口可容纳的水管数量+2, 由窗口宽度、水管宽度、水管间距算得
public static final int FULL_PIPE = (Constant.FRAME_WIDTH
/ (Pipe.PIPE_HEAD_WIDTH + GameElementLayer.HORIZONTAL_INTERVAL) + 2) * 2;
} }
package com.bird.util; package com.bird.util;
import java.awt.Font; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
...@@ -13,7 +13,6 @@ import javax.imageio.ImageIO; ...@@ -13,7 +13,6 @@ import javax.imageio.ImageIO;
* 工具类,游戏中用到的工具都在此类 * 工具类,游戏中用到的工具都在此类
* *
* @author Kingyu * @author Kingyu
*
*/ */
public class GameUtil { public class GameUtil {
...@@ -24,9 +23,9 @@ public class GameUtil { ...@@ -24,9 +23,9 @@ public class GameUtil {
* 装载图片的方法 * 装载图片的方法
* *
* @param imgPath 图片路径 * @param imgPath 图片路径
* @return * @return 图片资源
*/ */
public static BufferedImage loadBUfferedImage(String imgPath) { public static BufferedImage loadBufferedImage(String imgPath) {
try { try {
return ImageIO.read(new FileInputStream(imgPath)); return ImageIO.read(new FileInputStream(imgPath));
} catch (IOException e) { } catch (IOException e) {
...@@ -42,13 +41,13 @@ public class GameUtil { ...@@ -42,13 +41,13 @@ public class GameUtil {
* @param denominator 分母,不小于0的值 * @param denominator 分母,不小于0的值
* @return 概率性事件发生返回true,否则返回false * @return 概率性事件发生返回true,否则返回false
*/ */
public static boolean isInProbability(int numerator, int denominator)throws Exception{ public static boolean isInProbability(int numerator, int denominator) throws Exception {
// 分子分母不小于0 // 分子分母不小于0
if (numerator <= 0 || denominator <= 0) { if (numerator <= 0 || denominator <= 0) {
throw new Exception("传入了非法的参数"); throw new Exception("传入了非法的参数");
} }
//分子大于分母,一定发生 //分子大于分母,一定发生
if(numerator >= denominator) { if (numerator >= denominator) {
return true; return true;
} }
...@@ -71,16 +70,22 @@ public class GameUtil { ...@@ -71,16 +70,22 @@ public class GameUtil {
*/ */
public static int getStringWidth(Font font, String str) { public static int getStringWidth(Font font, String str) {
AffineTransform affinetransform = new AffineTransform(); AffineTransform affinetransform = new AffineTransform();
FontRenderContext frc = new FontRenderContext(affinetransform,true,true); FontRenderContext frc = new FontRenderContext(affinetransform, true, true);
int textHeight = (int)(font.getStringBounds(str, frc).getWidth()); return (int) (font.getStringBounds(str, frc).getWidth());
return textHeight;
} }
public static int getStringHeight(Font font, String str) { public static int getStringHeight(Font font, String str) {
AffineTransform affinetransform = new AffineTransform(); AffineTransform affinetransform = new AffineTransform();
FontRenderContext frc = new FontRenderContext(affinetransform,true,true); FontRenderContext frc = new FontRenderContext(affinetransform, true, true);
int textHeight = (int)(font.getStringBounds(str, frc).getHeight()); return (int) (font.getStringBounds(str, frc).getHeight());
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);
} }
} }
package com.bird.util; package com.bird.util;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
...@@ -11,8 +10,8 @@ import sun.audio.AudioStream; ...@@ -11,8 +10,8 @@ import sun.audio.AudioStream;
/** /**
* 音乐工具类 * 音乐工具类
* *
* @author Kingyu wav音频:JDK提供的类可直接解码 mp3音频:JDK没有提供支持,需要使用第三方的工具包 * @author Kingyu
* * wav音频:JDK提供的类可直接解码 mp3音频:JDK没有提供支持,需要使用第三方的工具包
*/ */
public class MusicUtil { public class MusicUtil {
...@@ -20,40 +19,33 @@ public class MusicUtil { ...@@ -20,40 +19,33 @@ public class MusicUtil {
private static AudioStream crash; private static AudioStream crash;
private static AudioStream score; private static AudioStream score;
private static InputStream flyIn; // wav播放
private static InputStream crashIn;
private static InputStream scoreIn;
//wav播放
public static void playFly() { public static void playFly() {
try { try {
// create an audiostream from the inputstream // create an AudioStream from the InputStream
flyIn = new FileInputStream("resources/wav/fly.wav"); InputStream flyIn = new FileInputStream("resources/wav/fly.wav");
fly = new AudioStream(flyIn); fly = new AudioStream(flyIn);
} catch (FileNotFoundException fnfe) { } catch (IOException ignored) {
} catch (IOException ioe) {
} }
AudioPlayer.player.start(fly); AudioPlayer.player.start(fly);
} }
public static void playCrash() { public static void playCrash() {
try { try {
// create an audiostream from the inputstream // create an AudioStream from the InputStream
crashIn = new FileInputStream("resources/wav/crash.wav"); InputStream crashIn = new FileInputStream("resources/wav/crash.wav");
crash = new AudioStream(crashIn); crash = new AudioStream(crashIn);
} catch (FileNotFoundException fnfe) { } catch (IOException ignored) {
} catch (IOException ioe) {
} }
AudioPlayer.player.start(crash); AudioPlayer.player.start(crash);
} }
public static void playScore() { public static void playScore() {
try { try {
// create an audiostream from the inputstream // create an AudioStream from the InputStream
scoreIn = new FileInputStream("resources/wav/score.wav"); InputStream scoreIn = new FileInputStream("resources/wav/score.wav");
score = new AudioStream(scoreIn); score = new AudioStream(scoreIn);
} catch (FileNotFoundException fnfe) { } catch (IOException ignored) {
} catch (IOException ioe) {
} }
AudioPlayer.player.start(score); 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