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,289 +9,275 @@ import com.bird.util.Constant; ...@@ -9,289 +9,275 @@ 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; // 翅膀状态
// 图片资源
private BufferedImage image; // 实时的小鸟图片 // 图片资源
private BufferedImage scoreImg; // 计分牌 private BufferedImage image; // 实时的小鸟图片
private BufferedImage overImg; // 结束标志 private BufferedImage scoreImg; // 计分牌
private BufferedImage againImg; // 继续标志 private BufferedImage overImg; // 结束标志
private BufferedImage againImg; // 继续标志
// 小鸟的状态
private int state; // 小鸟的状态
public static final int STATE_NORMAL = 0; private int state;
public static final int STATE_UP = 1; public static final int STATE_NORMAL = 0;
public static final int STATE_DOWN = 2; public static final int STATE_UP = 1;
public static final int STATE_FALL = 3; public static final int STATE_DOWN = 2;
public static final int STATE_DEAD = 4; 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 final Rectangle birdRect; // 碰撞矩形
public static final int RECT_DESCALE = 2; // 补偿碰撞矩形宽高的参数
private GameTime timing; // 飞行时间
private final GameScore countScore; // 计分器
// 在构造器中对资源初始化
public Bird() { // 在构造器中对资源初始化
timing = GameTime.getInstance(); // 计时器 public Bird() {
countScore = GameScore.getInstance(); // 计分器
// 读取小鸟图片资源
birdImgs = new BufferedImage[STATE_COUNT][IMG_COUNT]; // 读取小鸟图片资源
for (int j = 0; j < STATE_COUNT; j++) { birdImages = new BufferedImage[STATE_COUNT][IMG_COUNT];
for (int i = 0; i < IMG_COUNT; i++) { for (int j = 0; j < STATE_COUNT; j++) {
birdImgs[j][i] = GameUtil.loadBUfferedImage(Constant.BIRDS_IMG_PATH[j][i]); 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; x = Constant.FRAME_WIDTH >> 2;
y = Constant.FRAME_HEIGHT >> 1;
int ImgWidth = birdImgs[state][0].getWidth();
int ImgHeight = birdImgs[state][0].getHeight(); int ImgWidth = birdImages[state][0].getWidth();
int ImgHeight = birdImages[state][0].getHeight();
// 初始化碰撞矩形
int rectX = x - ImgWidth / 2; // 初始化碰撞矩形
int rectY = y - ImgHeight / 2; int rectX = x - ImgWidth / 2;
int rectWidth = ImgWidth; int rectY = y - ImgHeight / 2;
int rectHeight = ImgHeight; birdRect = new Rectangle(rectX + RECT_DESCALE, rectY + RECT_DESCALE * 2, ImgWidth - RECT_DESCALE * 3,
birdRect = new Rectangle(rectX + RECT_DESCALE, rectY + RECT_DESCALE * 2, rectWidth - RECT_DESCALE * 3, ImgHeight - RECT_DESCALE * 4); // 碰撞矩形的坐标与小鸟相同
rectHeight - RECT_DESCALE * 4); // 碰撞矩形的坐标与小鸟相同 }
}
// 绘制方法
// 绘制方法 public void draw(Graphics g) {
public void draw(Graphics g) { fly();
fly(); int state_index = Math.min(state, STATE_FALL); // 图片资源索引
int state_index = state > STATE_FALL ? STATE_FALL : state; // 图片资源索引 // 小鸟中心点计算
// 小鸟中心点计算 int halfImgWidth = birdImages[state_index][0].getWidth() >> 1;
int halfImgWidth = birdImgs[state_index][0].getWidth() >> 1; int halfImgHeight = birdImages[state_index][0].getHeight() >> 1;
int halfImgHeight = birdImgs[state_index][0].getHeight() >> 1; if (speed > 0)
if (speed > 0) image = birdImages[STATE_UP][0];
image = birdImgs[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) { drawScore(g);
} else { }
drawScore(g); // 绘制矩形
} // g.setColor(Color.black);
// 绘制矩形 // g.drawRect((int) birdRect.getX(), (int) birdRect.getY(), (int) birdRect.getWidth(), (int) birdRect.getHeight());
// 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 int SPEED_UP = 32; // 小鸟向上的速度 public static final double T = 0.2; // 小鸟的下落函数执行的时间
public static final double g = 9.8; // 重力加速度
public static final double T = 0.2; // 小鸟的下落函数执行的时间 private double speed = 0; // 小鸟的初速度
private double h; // 小鸟y轴的位移量 private boolean keyFlag = true; // 按键状态,true为已释放,使当按住按键时不会重复调用方法
private double speed = 0; // 小鸟的初速度
public void keyPressed() {
private boolean keyFlag = true; // 按键状态,true为已释放,使当按住按键时不会重复调用方法 keyFlag = false;
}
public void keyPressed() {
keyFlag = false; public void keyReleased() {
} keyFlag = true;
}
public void keyReleased() {
keyFlag = true; public boolean keyIsReleased() {
} return keyFlag;
}
public boolean keyIsReleased() {
return keyFlag; // 小鸟的飞行逻辑
} private void fly() {
// 翅膀状态,实现小鸟振翅飞行
// 小鸟的飞行逻辑 wingState++;
private void fly() { image = birdImages[Math.min(state, STATE_FALL)][wingState / 10 % IMG_COUNT];
// 翅膀状态,实现小鸟振翅飞行
wingState++; switch (state) {
image = birdImgs[state > STATE_FALL ? STATE_FALL : state][wingState / 10 % IMG_COUNT]; case STATE_DOWN:
// 物理公式
switch (state) { speed -= g * T;
case STATE_NORMAL: // 小鸟y轴的位移量
break; double h = speed * T - g * T * T / 2;
y = (int) (y - h);
case STATE_UP: birdRect.y = (int) (birdRect.y - h);
break; // 控制坠落的边界,若y坐标 > 窗口的高度 - 地面的高度 - 小鸟图片的高度则死亡
if (birdRect.y >= Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImages[state][0].getHeight() >> 1)) {
case STATE_DOWN: 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);
speed -= g * T; birdFall();
h = speed * T - g * T * T / 2; }
y = (int) (y - h);
birdRect.y = (int) (birdRect.y - h); break;
// 控制坠落的边界,若y坐标 > 窗口的高度 - 地面的高度 - 小鸟图片的高度则死亡
if (birdRect.y >= Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1)) { case STATE_FALL:
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); speed -= g * T;
birdFall(); h = speed * T - g * T * T / 2;
} y = (int) (y - h);
birdRect.y = (int) (birdRect.y - h);
break;
// 控制坠落的边界,若y坐标 > 窗口的高度 - 地面的高度 - 小鸟图片的高度则死亡
case STATE_FALL: if (birdRect.y >= Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImages[state][0].getHeight() >> 1)) {
// 鸟死亡,自由落体
speed -= g * T; y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImages[state][0].getHeight() >> 1);
h = speed * T - g * T * T / 2; birdRect.y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImages[state][0].getHeight() >> 1);
y = (int) (y - h);
birdRect.y = (int) (birdRect.y - h); GameFrame.setGameState(GameFrame.STATE_OVER); // 改变游戏状态
birdDead();
// 控制坠落的边界,若y坐标 > 窗口的高度 - 地面的高度 - 小鸟图片的高度则死亡 }
if (birdRect.y >= Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1)) { break;
y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1); case STATE_NORMAL:
birdRect.y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1); case STATE_UP:
case STATE_DEAD:
GameFrame.setGameState(GameFrame.STATE_OVER); // 改变游戏状态 break;
birdDead(); }
}
break; // 控制上方边界
if (birdRect.y < -1 * Constant.TOP_PIPE_LENGTHENING / 2) {
case STATE_DEAD: birdRect.y = -1 * Constant.TOP_PIPE_LENGTHENING / 2;
break; y = -1 * Constant.TOP_PIPE_LENGTHENING / 2;
} }
// 控制上方边界 // 控制下方边界
if (birdRect.y < -1 * Constant.TOP_PIPE_LENGTHENING / 2) { if (birdRect.y > Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (image.getHeight() >> 1)) {
birdRect.y = -1 * Constant.TOP_PIPE_LENGTHENING / 2; birdFall();
y = -1 * Constant.TOP_PIPE_LENGTHENING / 2; }
} }
// 控制下方边界 // 小鸟振翅
if (birdRect.y > Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (image.getHeight() >> 1)) { public void birdUp() {
birdFall(); if (keyIsReleased()) { // 如果按键已释放
} if (state == STATE_DEAD || state == STATE_UP || state == STATE_FALL)
} return; // 小鸟死亡或坠落时返回
MusicUtil.playFly(); // 播放音效
// 小鸟振翅 state = STATE_UP;
public void birdUp() { speed = SPEED_UP; // 每次振翅将速度改为上升速度
if (keyIsReleased()) { // 如果按键已释放 wingState = 0; // 重置翅膀状态
if (state == STATE_DEAD || state == STATE_UP || state == STATE_FALL) keyPressed();
return; // 小鸟死亡或坠落时返回 }
MusicUtil.playFly(); // 播放音效 }
state = STATE_UP;
speed = SPEED_UP; // 每次振翅将速度改为上升速度 // 小鸟下降
wingState = 0; // 重置翅膀状态 public void birdDown() {
keyPressed(); if (state == STATE_DEAD || state == STATE_FALL)
} return; // 小鸟死亡或坠落时返回
} state = STATE_DOWN;
}
// 小鸟下降
public void birdDown() { // 小鸟坠落(已死)
if (state == STATE_DEAD || state == STATE_FALL) public void birdFall() {
return; // 小鸟死亡或坠落时返回 state = STATE_FALL;
state = STATE_DOWN; MusicUtil.playCrash(); // 播放音效
} }
// 小鸟坠落(已死) // 小鸟死亡
public void birdFall() { public void birdDead() {
state = STATE_FALL; state = STATE_DEAD;
MusicUtil.playCrash(); // 播放音效 // 加载游戏结束的资源
// 结束计时 if (overImg == null) {
timing.endTiming(); overImg = GameUtil.loadBufferedImage(Constant.OVER_IMG_PATH);
} scoreImg = GameUtil.loadBufferedImage(Constant.SCORE_IMG_PATH);
againImg = GameUtil.loadBufferedImage(Constant.AGAIN_IMG_PATH);
// 小鸟死亡 }
public void birdDead() { countScore.isSaveScore(); // 判断是否保存纪录
state = STATE_DEAD; }
// 加载游戏结束的资源
if (overImg == null) { public boolean isDead() {
overImg = GameUtil.loadBUfferedImage(Constant.OVER_IMG_PATH); return state == STATE_FALL || state == STATE_DEAD;
scoreImg = GameUtil.loadBUfferedImage(Constant.SCORE_IMG_PATH); }
againImg = GameUtil.loadBUfferedImage(Constant.AGAIN_IMG_PATH);
} // 绘制实时分数
} private void drawScore(Graphics g) {
g.setColor(Color.white);
public boolean isDead() { g.setFont(Constant.CURRENT_SCORE_FONT);
return state == STATE_FALL || state == STATE_DEAD; 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);
// 开始计时的方法 }
public void startTiming() {
if (timing.isReadyTiming()) private static final int SCORE_LOCATE = 5; // 位置补偿参数
timing.startTiming();
} private int flash = 0; // 图片闪烁参数
// 绘制实时分数 // 绘制游戏结束的显示
private void drawScore(Graphics g) { private void drawGameOver(Graphics g) {
g.setColor(Color.white); // 绘制结束标志
g.setFont(Constant.TIME_FONT); int x = Constant.FRAME_WIDTH - overImg.getWidth() >> 1;
String str = Long.toString(timing.TimeToScore()); int y = Constant.FRAME_HEIGHT / 4;
int x = Constant.FRAME_WIDTH - GameUtil.getStringWidth(Constant.TIME_FONT, str) >> 1; g.drawImage(overImg, x, y, null);
g.drawString(str, x, Constant.FRAME_HEIGHT / 10);
} // 绘制计分牌
x = Constant.FRAME_WIDTH - scoreImg.getWidth() >> 1;
private static final int SCORE_LOCATE = 5; // 位置补偿参数 y = Constant.FRAME_HEIGHT / 3;
g.drawImage(scoreImg, x, y, null);
private int flash = 0; // 图片闪烁参数
// 绘制本局的分数
// 绘制游戏结束的显示 g.setColor(Color.white);
private void drawGameover(Graphics g) { g.setFont(Constant.SCORE_FONT);
// 绘制结束标志 x = (Constant.FRAME_WIDTH - scoreImg.getWidth() / 2 >> 1) + SCORE_LOCATE;// 位置补偿
int x = Constant.FRAME_WIDTH - overImg.getWidth() >> 1; y += scoreImg.getHeight() >> 1;
int y = Constant.FRAME_HEIGHT / 4; String str = Long.toString(countScore.getScore());
g.drawImage(overImg, x, y, null); x -= GameUtil.getStringWidth(Constant.SCORE_FONT, str) >> 1;
y += GameUtil.getStringHeight(Constant.SCORE_FONT, str);
// 绘制计分牌 g.drawString(str, x, y);
x = Constant.FRAME_WIDTH - scoreImg.getWidth() >> 1;
y = Constant.FRAME_HEIGHT / 3; // 绘制最高分数
g.drawImage(scoreImg, x, y, null); if (countScore.getBestScore() > 0) {
str = Long.toString(countScore.getBestScore());
// 绘制本局的分数 x = (Constant.FRAME_WIDTH + scoreImg.getWidth() / 2 >> 1) - SCORE_LOCATE;// 位置补偿
g.setColor(Color.white); x -= GameUtil.getStringWidth(Constant.SCORE_FONT, str) >> 1;
g.setFont(Constant.SCORE_FONT); g.drawString(str, x, y);
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; final int COUNT = 30; // 闪烁周期
y += GameUtil.getStringHeight(Constant.SCORE_FONT, str); if (flash++ > COUNT)
g.drawString(str, x, y); drawTitle(againImg, g);
if (flash == COUNT * 2) // 重置闪烁参数
// 绘制最高分数 flash = 0;
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; public void reset() {
g.drawString(str, x, y); state = STATE_NORMAL; // 小鸟状态
} y = Constant.FRAME_HEIGHT >> 1; // 小鸟坐标
// 绘制继续游戏 speed = 0; // 小鸟速度
final int COUNT = 30; // 控制闪烁间隔的参数
if (flash++ > COUNT) { int ImgHeight = birdImages[state][0].getHeight();
x = Constant.FRAME_WIDTH - againImg.getWidth() >> 1; birdRect.y = y - ImgHeight / 2 + RECT_DESCALE * 2; // 小鸟碰撞矩形坐标
y = Constant.FRAME_HEIGHT / 5 * 3;
g.drawImage(againImg, x, y, null); countScore.reset(); // 重置计分器
if (flash == COUNT * 2) flash = 0;
flash = 0; }
}
} // 获取小鸟的碰撞矩形
public Rectangle getBirdRect() {
// 重置小鸟 return birdRect;
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
...@@ -3,78 +3,52 @@ package com.bird.main; ...@@ -3,78 +3,52 @@ 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 final BufferedImage img;
private int speed; // 速度
private int x, y; // 坐标 private final int scaleImageWidth;
private final int scaleImageHeight;
private BufferedImage img;
// 构造器
// 云朵图片缩放的比例 1.0~2.0 public Cloud(BufferedImage img, int x, int y) {
private double scale; super();
private int scaleImageWidth; this.img = img;
private int scaleImageHeight; this.x = x;
this.y = y;
// 构造器 this.speed = 2; //云朵的速度
public Cloud(BufferedImage img, int dir, int x, int y) { // 云朵图片缩放的比例 1.0~2.0
super(); double scale = 1 + Math.random(); // Math.random()返回0.0~1.0的随机值
this.img = img; // 缩放云朵图片
this.dir = dir; scaleImageWidth = (int) (scale * img.getWidth());
this.x = x; scaleImageHeight = (int) (scale * img.getWidth());
this.y = y; }
this.speed = 2; //云朵的速度 // 绘制方法
scale = 1 + Math.random(); // Math.random()返回0.0~1.0的随机值 public void draw(Graphics g, Bird bird) {
// 缩放云朵图片 int speed = this.speed;
scaleImageWidth = (int) (scale * img.getWidth()); if (bird.isDead())
scaleImageHeight = (int) (scale * img.getWidth()); speed = 1;
} x -= speed;
g.drawImage(img, x, y, scaleImageWidth, scaleImageHeight, null);
// 绘制方法 }
public void draw(Graphics g, Bird bird) {
int speed = this.speed; /**
* 判断云朵是否飞出屏幕
if(dir == DIR_NONE) //云彩不动 *
speed = 0; * @return 飞出则返回true,否则返回false
*/
x = (dir == DIR_LEFT) ? x - speed : x + speed; // 方向逻辑 public boolean isOutFrame() {
g.drawImage(img, x, y, scaleImageWidth, scaleImageHeight, null); return x < -1 * scaleImageWidth;
} }
/**
* 判断云朵是否飞出屏幕
*
* @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;
}
// 改变方向
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) {
// 绘制背景色 // 绘制背景色
...@@ -58,4 +56,4 @@ public class GameBackground { ...@@ -58,4 +56,4 @@ public class GameBackground {
if (layerX > BackgroundImg.getWidth()) if (layerX > BackgroundImg.getWidth())
layerX = 0; layerX = 0;
} }
} }
\ No newline at end of file
...@@ -9,230 +9,217 @@ import com.bird.util.GameUtil; ...@@ -9,230 +9,217 @@ 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() {
pipes = new ArrayList<>(); pipes = new ArrayList<>();
} }
// 绘制方法 // 绘制方法
public void draw(Graphics g, Bird bird) { public void draw(Graphics g, Bird bird) {
// 遍历水管容器,如果可见则绘制,不可见则归还 // 遍历水管容器,如果可见则绘制,不可见则归还
for (int i = 0; i < pipes.size(); i++) { for (int i = 0; i < pipes.size(); i++) {
Pipe pipe = pipes.get(i); Pipe pipe = pipes.get(i);
if (pipe.isVisible()) { if (pipe.isVisible()) {
pipe.draw(g, bird); pipe.draw(g, bird);
} else { } else {
Pipe remove = pipes.remove(i); Pipe remove = pipes.remove(i);
PipePool.giveBack(remove); PipePool.giveBack(remove);
i--; i--;
} }
} }
// 碰撞检测 // 碰撞检测
isCollideBird(bird); isCollideBird(bird);
pipeBornLogic(bird); pipeBornLogic(bird);
} }
/** /**
* 添加水管的逻辑: 当容器中添加的最后一个元素完全显示到屏幕后,添加下一对; 水管成对地相对地出现,空隙高度为窗口高度的1/6; * 添加水管的逻辑: 当容器中添加的最后一个元素完全显示到屏幕后,添加下一对; 水管成对地相对地出现,空隙高度为窗口高度的1/6;
* 每对水管的间隔距离为屏幕高度的1/4; 水管的高度的取值范围为窗口的[1/8~5/8] * 每对水管的间隔距离为屏幕高度的1/4; 水管的高度的取值范围为窗口的[1/8~5/8]
*/ */
public static final int VERTICAL_INTERVAL = Constant.FRAME_HEIGHT / 5; public static final int VERTICAL_INTERVAL = Constant.FRAME_HEIGHT / 5;
public static final int HORIZONTAL_INTERVAL = Constant.FRAME_HEIGHT >> 2; public static final int HORIZONTAL_INTERVAL = Constant.FRAME_HEIGHT >> 2;
public static final int MIN_HEIGHT = Constant.FRAME_HEIGHT >> 3; public static final int MIN_HEIGHT = Constant.FRAME_HEIGHT >> 3;
public static final int MAX_HEIGHT = ((Constant.FRAME_HEIGHT) >> 3) * 5; public static final int MAX_HEIGHT = ((Constant.FRAME_HEIGHT) >> 3) * 5;
private void pipeBornLogic(Bird bird) { private void pipeBornLogic(Bird bird) {
if (bird.isDead()) { if (bird.isDead()) {
// 鸟死后不再添加水管 // 鸟死后不再添加水管
return; return;
} }
if (pipes.size() == 0) { if (pipes.size() == 0) {
// 若容器为空,则添加一对水管 // 若容器为空,则添加一对水管
int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度 int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度
Pipe top = PipePool.get("Pipe"); Pipe top = PipePool.get("Pipe");
top.setAttribute(Constant.FRAME_WIDTH, -Constant.TOP_PIPE_LENGTHENING, top.setAttribute(Constant.FRAME_WIDTH, -Constant.TOP_PIPE_LENGTHENING,
topHeight + Constant.TOP_PIPE_LENGTHENING, Pipe.TYPE_TOP_NORMAL, true); topHeight + Constant.TOP_PIPE_LENGTHENING, Pipe.TYPE_TOP_NORMAL, true);
Pipe bottom = PipePool.get("Pipe"); Pipe bottom = PipePool.get("Pipe");
bottom.setAttribute(Constant.FRAME_WIDTH, topHeight + VERTICAL_INTERVAL, bottom.setAttribute(Constant.FRAME_WIDTH, topHeight + VERTICAL_INTERVAL,
Constant.FRAME_HEIGHT - topHeight - VERTICAL_INTERVAL, Pipe.TYPE_BOTTOM_NORMAL, true); Constant.FRAME_HEIGHT - topHeight - VERTICAL_INTERVAL, Pipe.TYPE_BOTTOM_NORMAL, true);
pipes.add(top); pipes.add(top);
pipes.add(bottom); pipes.add(bottom);
} 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)// 若窗口中可容纳的水管已满,说明小鸟已飞到第一对水管的位置,开始记分
try { GameScore.getInstance().setScore(bird);
if (GameUtil.isInProbability(2, 5)) { // 40%的概率生成悬浮的普通水管 try {
addHoverPipe(lastPipe); int currentScore = (int) GameScore.getInstance().getScore() + 1; // 获取当前分数
} else { // 移动水管刷新的概率随当前分数递增,当得分大于19后全部刷新移动水管
addNormalPipe(lastPipe); if (GameUtil.isInProbability(currentScore, 20)) {
} if (GameUtil.isInProbability(1, 4)) // 生成移动水管和移动悬浮水管的概率
} catch (Exception e) { addMovingHoverPipe(lastPipe);
e.printStackTrace(); else
} addMovingNormalPipe(lastPipe);
} else { } else {
try { if (GameUtil.isInProbability(1, 2)) // 生成静止普通水管和静止悬浮水管的概率
if (GameUtil.isInProbability(1, 4)) { // 1/4的概率生成普通水管 addNormalPipe(lastPipe);
if(GameUtil.isInProbability(1, 2)) // 生成普通水管和悬浮水管的概率 else
addNormalPipe(lastPipe); addHoverPipe(lastPipe);
else }
addHoverPipe(lastPipe); } catch (Exception e) {
} else { e.printStackTrace();
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"); // 从水管对象池中获取对象
*
* @param lastPipe 传入最后一根水管以获取x坐标 // 设置x, y, height, type属性
*/ top.setAttribute(x, -Constant.TOP_PIPE_LENGTHENING, topHeight + Constant.TOP_PIPE_LENGTHENING,
private void addNormalPipe(Pipe lastPipe) { Pipe.TYPE_TOP_NORMAL, true);
int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度
int x = lastPipe.getX() + HORIZONTAL_INTERVAL; // 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔 Pipe bottom = PipePool.get("Pipe");
bottom.setAttribute(x, topHeight + VERTICAL_INTERVAL, Constant.FRAME_HEIGHT - topHeight - VERTICAL_INTERVAL,
Pipe top = PipePool.get("Pipe"); //从水管对象池中获取对象 Pipe.TYPE_BOTTOM_NORMAL, true);
//设置x, y, height, type属性 pipes.add(top);
top.setAttribute(x, -Constant.TOP_PIPE_LENGTHENING, topHeight + Constant.TOP_PIPE_LENGTHENING, pipes.add(bottom);
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); *
* @param lastPipe 传入最后一根水管以获取x坐标
pipes.add(top); */
pipes.add(bottom); 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]
* @param lastPipe
*/ int type = Pipe.TYPE_HOVER_NORMAL;
private void addHoverPipe(Pipe lastPipe) {
// 生成上部的悬浮水管
// 随机生成水管高度,屏幕高度的[1/4,1/6] Pipe topHover = PipePool.get("Pipe");
int topHoverHeight = GameUtil.getRandomNumber(Constant.FRAME_HEIGHT / 6, Constant.FRAME_HEIGHT / 4); topHover.setAttribute(x, y, topHoverHeight, type, true);
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 bottomHoverHeight = Constant.FRAME_HEIGHT - 2 * y - topHoverHeight - VERTICAL_INTERVAL;
int type = Pipe.TYPE_HOVER_NORMAL; Pipe bottomHover = PipePool.get("Pipe");
bottomHover.setAttribute(x, y + topHoverHeight + VERTICAL_INTERVAL, bottomHoverHeight, type, true);
// 生成上部的悬浮水管
Pipe topHover = PipePool.get("Pipe"); pipes.add(topHover);
topHover.setAttribute(x, y, topHoverHeight, type, true); pipes.add(bottomHover);
// 生成下部的悬浮水管 }
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); * @param lastPipe 传入最后一根水管以获取x坐标
pipes.add(bottomHover); */
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]
* @param lastPipe
*/ int type = Pipe.TYPE_HOVER_HARD;
private void addMovingHoverPipe(Pipe lastPipe) {
// 生成上部的悬浮水管
// 随机生成水管高度,屏幕高度的[1/4,1/6] Pipe topHover = PipePool.get("MovingPipe");
int topHoverHeight = GameUtil.getRandomNumber(Constant.FRAME_HEIGHT / 6, Constant.FRAME_HEIGHT / 4); topHover.setAttribute(x, y, topHoverHeight, type, true);
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 bottomHoverHeight = Constant.FRAME_HEIGHT - 2 * y - topHoverHeight - VERTICAL_INTERVAL;
int type = Pipe.TYPE_HOVER_HARD; Pipe bottomHover = PipePool.get("MovingPipe");
bottomHover.setAttribute(x, y + topHoverHeight + VERTICAL_INTERVAL, bottomHoverHeight, type, true);
// 生成上部的悬浮水管
Pipe topHover = PipePool.get("MovingPipe"); pipes.add(topHover);
topHover.setAttribute(x, y, topHoverHeight, type, true); pipes.add(bottomHover);
// 生成下部的悬浮水管 }
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); * @param lastPipe 传入最后一根水管以获取x坐标
pipes.add(bottomHover); */
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,
* @param lastPipe Pipe.TYPE_TOP_HARD, true);
*/
private void addMovingNormalPipe(Pipe lastPipe) { Pipe bottom = PipePool.get("MovingPipe");
int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度 bottom.setAttribute(x, topHeight + VERTICAL_INTERVAL, Constant.FRAME_HEIGHT - topHeight - VERTICAL_INTERVAL,
int x = lastPipe.getX() + HORIZONTAL_INTERVAL; // 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔 Pipe.TYPE_BOTTOM_HARD, true);
Pipe top = PipePool.get("MovingPipe"); pipes.add(top);
top.setAttribute(x, -Constant.TOP_PIPE_LENGTHENING, topHeight + Constant.TOP_PIPE_LENGTHENING, pipes.add(bottom);
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); *
* @param bird 传入小鸟对象
pipes.add(top); */
pipes.add(bottom); public void isCollideBird(Bird bird) {
} // 若鸟已死则不再判断
if (bird.isDead()) {
/** return;
* 判断元素和小鸟是否发生碰撞,若发生碰撞返回true,否则返回false }
* // 遍历水管容器
* @param bird for (Pipe pipe : pipes) {
* @return // 判断碰撞矩形是否有交集
*/ if (pipe.getPipeRect().intersects(bird.getBirdRect())) {
public boolean isCollideBird(Bird bird) { bird.birdFall(); // 有交集则小鸟坠落
// 若鸟已死则不再判断 return;
if (bird.isDead()) { }
return false; }
} }
// 遍历水管容器
for (int i = 0; i < pipes.size(); i++) { // 重置元素层
Pipe pipe = pipes.get(i); public void reset() {
// 判断碰撞矩形是否有交集 for (Pipe pipe : pipes) {
if (pipe.getPipeRect().intersects(bird.getBirdRect())) { PipePool.giveBack(pipe);
bird.birdFall(); //有交集则小鸟坠落 }
return true; pipes.clear();
} }
} }
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
...@@ -10,89 +10,71 @@ import com.bird.util.GameUtil; ...@@ -10,89 +10,71 @@ 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 final BufferedImage[] cloudImages; // 图片资源
private long time; // 控制云的逻辑运算周期
public static final int CLOUD_INTERVAL = 100; //云朵刷新的逻辑运算的周期
private BufferedImage[] cloudImgs; // 图片资源 public GameForeground() {
private int cloudDir; // 云的方向 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<>(); //云朵的容器 public void draw(Graphics g, Bird bird) {
cloudLogic();
// 读入图片资源 for (Cloud cloud : clouds) {
cloudImgs = new BufferedImage[Constant.CLOUD_IMAGE_COUNT]; cloud.draw(g, bird);
for (int i = 0; i < Constant.CLOUD_IMAGE_COUNT; i++) { }
cloudImgs[i] = GameUtil.loadBUfferedImage(Constant.CLOUDS_IMG_PATH[i]); }
}
// 初始化云朵的属性 // 云朵的控制
cloudDir = Cloud.DIR_LEFT; private void cloudLogic() {
time = System.currentTimeMillis(); // 获取当前时间,用于控制云的逻辑运算周期 // 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) { int x = Constant.FRAME_WIDTH; // 从屏幕左侧开始刷新
cloudLogic(); // y坐标随机在上1/3屏选取
for (int i = 0; i < clouds.size(); i++) { int y = GameUtil.getRandomNumber(Constant.TOP_BAR_HEIGHT, Constant.FRAME_HEIGHT / 3);
clouds.get(i).draw(g, bird);
}
}
// 云朵的控制 //向容器中添加云朵
private void cloudLogic() { Cloud cloud = new Cloud(cloudImages[index], x, y);
// 100ms运算一次 clouds.add(cloud);
if (System.currentTimeMillis() - time > CLOUD_INTERVAL) { }
time = System.currentTimeMillis(); // 重置time } catch (Exception e) {
// 如果屏幕的云朵的数量小于允许的最大数量,根据给定的概率随机添加云朵 e.printStackTrace();
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();
}
} // 添加云朵
// 若云朵飞出屏幕则从容器中移除 // 若云朵飞出屏幕则从容器中移除
for (int i = 0; i < clouds.size(); i++) { for (int i = 0; i < clouds.size(); i++) {
// 遍历容器中的云朵 // 遍历容器中的云朵
Cloud tempCloud = clouds.get(i); Cloud tempCloud = clouds.get(i);
if (tempCloud.isOutFrame()) { if (tempCloud.isOutFrame()) {
clouds.remove(i); clouds.remove(i);
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; ...@@ -18,158 +18,152 @@ import java.awt.image.BufferedImage;
/** /**
* 主窗口类,游戏窗口和绘制的相关内容 * 主窗口类,游戏窗口和绘制的相关内容
*
* @author Kingyu
* *
* @author Kingyu
*/ */
public class GameFrame extends Frame implements Runnable { public class GameFrame extends Frame implements Runnable {
private static final long serialVersionUID = 1L; // 保持版本的兼容性 private static final long serialVersionUID = 1L; // 保持版本的兼容性
private static int gameState; // 游戏状态 private static int gameState; // 游戏状态
public static final int STATE_READY = 0; // 游戏未开始 public static final int STATE_READY = 0; // 游戏未开始
public static final int STATE_START = 1; // 游戏开始 public static final int STATE_START = 1; // 游戏开始
public static final int STATE_OVER = 2; // 游戏结束 public static final int STATE_OVER = 2; // 游戏结束
private GameBackground background; // 游戏背景对象 private GameBackground background; // 游戏背景对象
private GameForeground foreground; // 游戏前景对象 private GameForeground foreground; // 游戏前景对象
private Bird bird; // 小鸟对象 private Bird bird; // 小鸟对象
private GameElementLayer gameElement; // 游戏元素对象 private GameElementLayer gameElement; // 游戏元素对象
private GameReady ready; // 游戏未开始时对象 private GameReady ready; // 游戏未开始时对象
// 在构造器中初始化 // 在构造器中初始化
public GameFrame() { public GameFrame() {
initFrame(); // 初始化游戏窗口 initFrame(); // 初始化游戏窗口
setVisible(true); // 窗口默认为不可见,设置为可见 setVisible(true); // 窗口默认为不可见,设置为可见
initGame(); // 初始化游戏对象 initGame(); // 初始化游戏对象
} }
// 初始化游戏窗口 // 初始化游戏窗口
private void initFrame() { private void initFrame() {
setSize(FRAME_WIDTH, FRAME_HEIGHT); // 设置窗口大小 setSize(FRAME_WIDTH, FRAME_HEIGHT); // 设置窗口大小
setTitle(GAME_TITLE); // 设置窗口标题 setTitle(GAME_TITLE); // 设置窗口标题
setLocation(FRAME_X, FRAME_Y); // 窗口初始位置 setLocation(FRAME_X, FRAME_Y); // 窗口初始位置
setResizable(false); // 设置窗口大小不可变 setResizable(false); // 设置窗口大小不可变
// 添加关闭窗口事件(监听窗口发生的事件,派发给参数对象,参数对象调用对应的方法) // 添加关闭窗口事件(监听窗口发生的事件,派发给参数对象,参数对象调用对应的方法)
addWindowListener(new WindowAdapter() { addWindowListener(new WindowAdapter() {
@Override @Override
public void windowClosing(WindowEvent e) { public void windowClosing(WindowEvent e) {
System.exit(0); // 结束程序 System.exit(0); // 结束程序
} }
}); });
addKeyListener(new BirdKeyListener()); // 添加按键监听 addKeyListener(new BirdKeyListener()); // 添加按键监听
} }
// 用于接收按键事件的对象的内部类 // 用于接收按键事件的对象的内部类
class BirdKeyListener implements KeyListener { class BirdKeyListener implements KeyListener {
// 按键按下,根据游戏当前的状态调用不同的方法 // 按键按下,根据游戏当前的状态调用不同的方法
public void keyPressed(KeyEvent e) { public void keyPressed(KeyEvent e) {
int keycode = e.getKeyChar(); int keycode = e.getKeyChar();
switch (gameState) { switch (gameState) {
case STATE_READY: case STATE_READY:
if (keycode == KeyEvent.VK_SPACE) { if (keycode == KeyEvent.VK_SPACE) {
// 游戏启动界面时按下空格,小鸟振翅一次并开始受重力影响 // 游戏启动界面时按下空格,小鸟振翅一次并开始受重力影响
bird.birdUp(); bird.birdUp();
bird.birdDown(); bird.birdDown();
setGameState(STATE_START); // 游戏状态改变 setGameState(STATE_START); // 游戏状态改变
bird.startTiming(); // 计时器开始计时 }
} break;
break; case STATE_START:
case STATE_START: if (keycode == KeyEvent.VK_SPACE) {
if (keycode == KeyEvent.VK_SPACE) { //游戏过程中按下空格则振翅一次,并持续受重力影响
//游戏过程中按下空格则振翅一次,并持续受重力影响 bird.birdUp();
bird.birdUp(); bird.birdDown();
bird.birdDown(); }
} break;
break; case STATE_OVER:
case STATE_OVER: if (keycode == KeyEvent.VK_SPACE) {
if (keycode == KeyEvent.VK_SPACE) { //游戏结束时按下空格,重新开始游戏
//游戏结束时按下空格,重新开始游戏 resetGame();
resetGame(); }
} break;
break; }
} }
}
// 重新开始游戏
// 重新开始游戏 private void resetGame() {
private void resetGame() { setGameState(STATE_READY);
setGameState(STATE_READY); gameElement.reset();
gameElement.reset(); bird.reset();
bird.reset(); }
}
// 按键松开,更改按键状态标志
// 按键松开,更改按键状态标志 public void keyReleased(KeyEvent e) {
public void keyReleased(KeyEvent e) { int keycode = e.getKeyChar();
int keycode = e.getKeyChar(); if (keycode == KeyEvent.VK_SPACE) {
if (keycode == KeyEvent.VK_SPACE) { bird.keyReleased();
bird.keyReleased(); }
} }
}
public void keyTyped(KeyEvent e) {
public void keyTyped(KeyEvent e) { }
} }
}
// 初始化游戏中的各个对象
// 初始化游戏中的各个对象 private void initGame() {
private void initGame() { background = new GameBackground();
background = new GameBackground(); gameElement = new GameElementLayer();
gameElement = new GameElementLayer(); foreground = new GameForeground();
foreground = new GameForeground(); ready = new GameReady();
ready = new GameReady(); bird = new Bird();
bird = new Bird(); setGameState(STATE_READY);
setGameState(STATE_READY);
// 启动用于刷新窗口的线程
// 启动用于刷新窗口的线程 new Thread(this).start();
new Thread(this).start(); }
}
// 项目中存在两个线程:系统线程,自定义的线程:调用repaint()。
// 项目中存在两个线程:系统线程,自定义的线程:调用repaint()。 // 系统线程:屏幕内容的绘制,窗口事件的监听与处理
// 系统线程:屏幕内容的绘制,窗口事件的监听与处理 // 两个线程会抢夺系统资源,可能会出现一次刷新周期所绘制的内容,并没有在一次刷新周期内完成
// 两个线程会抢夺系统资源,可能会出现一次刷新周期所绘制的内容,并没有在一次刷新周期内完成 // (双缓冲)单独定义一张图片,将需要绘制的内容绘制到这张图片,再一次性地将图片绘制到窗口
// (双缓冲)单独定义一张图片,将需要绘制的内容绘制到这张图片,再一次性地将图片绘制到窗口 private final BufferedImage bufImg = new BufferedImage(FRAME_WIDTH, FRAME_HEIGHT, BufferedImage.TYPE_4BYTE_ABGR);
private 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) {
*/ Graphics bufG = bufImg.getGraphics(); // 获得图片画笔
public void update(Graphics g) { // 使用图片画笔将需要绘制的内容绘制到图片
Graphics bufG = bufImg.getGraphics(); // 获得图片画笔
// 使用图片画笔将需要绘制的内容绘制到图片 background.draw(bufG, bird); // 背景层
foreground.draw(bufG, bird); // 前景层
background.draw(bufG, bird); // 背景层
foreground.draw(bufG, bird); // 前景层 // 鸟
if (gameState == STATE_READY) { // 游戏未开始
if (gameState == STATE_READY) { // 游戏未开始 ready.draw(bufG);
ready.draw(bufG); } else { // 游戏结束
bird.draw(bufG); // 鸟 gameElement.draw(bufG, bird); // 游戏元素层
} else { // 游戏结束 }
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) {
repaint(); // 通过调用repaint(),让JVM调用update() repaint(); // 通过调用repaint(),让JVM调用update()
try { try {
Thread.sleep(GAME_INTERVAL); Thread.sleep(GAME_INTERVAL);
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
} }
// 获取、设置游戏状态的方法 public static void setGameState(int gameState) {
public static int getGameState() { GameFrame.gameState = gameState;
return gameState; }
}
}
public static void setGameState(int gameState) {
GameFrame.gameState = gameState;
}
}
\ No newline at end of file
...@@ -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; if (flash == COUNT * 2) // 重置闪烁参数
y = Constant.FRAME_HEIGHT / 5 * 3;
g.drawImage(noticeImg, x, y, null); // 绘制
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;
} }
public void setScore(Bird bird) {
/** if(!bird.isDead()){
* 游戏用时,毫秒 MusicUtil.playScore(); //每次得分播放音效
* score += 1;
* @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() { public void isSaveScore() {
return getTime() / 1000; long score = getScore();
}
// 计时器是否就绪
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();
if (bestScore < score) if (bestScore < score)
bestScore = score; bestScore = score;
try { try {
...@@ -117,40 +78,79 @@ public class GameTime { ...@@ -117,40 +78,79 @@ public class GameTime {
e.printStackTrace(); 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() { 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;
......
...@@ -9,203 +9,200 @@ import com.bird.util.GameUtil; ...@@ -9,203 +9,200 @@ import com.bird.util.GameUtil;
/** /**
* 水管类 * 水管类
*
* @author Kingyu
* *
* @author Kingyu
*/ */
public class Pipe { public class Pipe {
static BufferedImage[] imgs; // 水管的图片,static保证图片只加载一次 static BufferedImage[] imgs; // 水管的图片,static保证图片只加载一次
static {// 静态代码块,类加载的时候,初始化图片
final int PIPE_IMAGE_COUNT = 3; static {// 静态代码块,类加载的时候,初始化图片
imgs = new BufferedImage[PIPE_IMAGE_COUNT]; final int PIPE_IMAGE_COUNT = 3;
for (int i = 0; i < PIPE_IMAGE_COUNT; i++) { imgs = new BufferedImage[PIPE_IMAGE_COUNT];
imgs[i] = GameUtil.loadBUfferedImage(Constant.PIPE_IMG_PATH[i]); 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_WIDTH = imgs[0].getWidth();
public static final int PIPE_HEAD_WIDTH = imgs[1].getWidth(); public static final int PIPE_HEIGHT = imgs[0].getHeight();
public static final int PIPE_HEAD_HEIGHT = imgs[1].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; // 水管的宽,高 int x, y; // 水管的坐标,相对于元素层
int width, height; // 水管的宽,高
boolean visible; // 水管可见状态,true为可见,false表示可归还至对象池
// 水管的类型 boolean visible; // 水管可见状态,true为可见,false表示可归还至对象池
int type; // 水管的类型
public static final int TYPE_TOP_NORMAL = 0; int type;
public static final int TYPE_TOP_HARD = 1; public static final int TYPE_TOP_NORMAL = 0;
public static final int TYPE_BOTTOM_NORMAL = 2; public static final int TYPE_TOP_HARD = 1;
public static final int TYPE_BOTTOM_HARD = 3; public static final int TYPE_BOTTOM_NORMAL = 2;
public static final int TYPE_HOVER_NORMAL = 4; public static final int TYPE_BOTTOM_HARD = 3;
public static final int TYPE_HOVER_HARD = 5; 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; public static final int SPEED = 1;
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();
pipeRect.width = PIPE_WIDTH; pipeRect.width = PIPE_WIDTH;
} }
/** /**
* 设置水管参数 * 设置水管参数
* *
* @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;
this.y = y; this.y = y;
this.height = height; this.height = height;
this.type = type; this.type = type;
this.visible = visible; this.visible = visible;
setRectangle(this.x, this.y, this.height); setRectangle(this.x, this.y, this.height);
} }
/** /**
* 设置碰撞矩形参数 * 设置碰撞矩形参数
* */
* @param x public void setRectangle(int x, int y, int height) {
* @param y pipeRect.x = x;
* @param height pipeRect.y = y;
*/ pipeRect.height = 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 boolean isVisible() { // 绘制方法
return visible; public void draw(Graphics g, Bird bird) {
} switch (type) {
case TYPE_TOP_NORMAL:
// 绘制方法 drawTopNormal(g);
public void draw(Graphics g, Bird bird) { break;
switch (type) { case TYPE_BOTTOM_NORMAL:
case TYPE_TOP_NORMAL: drawBottomNormal(g);
drawTopNormal(g); break;
break; case TYPE_HOVER_NORMAL:
case TYPE_BOTTOM_NORMAL: drawHoverNormal(g);
drawBottomNormal(g); break;
break; }
case TYPE_HOVER_NORMAL: // //绘制碰撞矩形
drawHoverNormal(g); // g.setColor(Color.black);
break; // g.drawRect((int) pipeRect.getX(), (int) pipeRect.getY(), (int) pipeRect.getWidth(), (int) pipeRect.getHeight());
}
// //绘制碰撞矩形 //鸟死后水管停止移动
// g.setColor(Color.black); if (bird.isDead()) {
// g.drawRect((int) pipeRect.getX(), (int) pipeRect.getY(), (int) pipeRect.getWidth(), (int) pipeRect.getHeight()); return;
}
//鸟死后水管停止移动 pipeLogic();
if (bird.isDead()) { }
return;
} // 绘制从上往下的普通水管
pipeLogic(); private void drawTopNormal(Graphics g) {
} // 拼接的个数
int count = (height - PIPE_HEAD_HEIGHT) / PIPE_HEIGHT + 1; // 取整+1
// 绘制从上往下的普通水管 // 绘制水管的主体
private void drawTopNormal(Graphics g) { for (int i = 0; i < count; i++) {
// 拼接的个数 g.drawImage(imgs[0], x, y + i * PIPE_HEIGHT, null);
int count = (height - PIPE_HEAD_HEIGHT) / PIPE_HEIGHT + 1; // 取整+1 }
// 绘制水管的主体 // 绘制水管的顶部
for (int i = 0; i < count; i++) { g.drawImage(imgs[1], x - ((PIPE_HEAD_WIDTH - width) >> 1),
g.drawImage(imgs[0], x, y + i * PIPE_HEIGHT, null); height - Constant.TOP_PIPE_LENGTHENING - PIPE_HEAD_HEIGHT, null); // 水管头部与水管主体的宽度不同,x坐标需要处理
} }
// 绘制水管的顶部
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;
// 绘制从下往上的普通水管 // 绘制水管的主体
private void drawBottomNormal(Graphics g) { for (int i = 0; i < count; i++) {
// 拼接的个数 g.drawImage(imgs[0], x, Constant.FRAME_HEIGHT - PIPE_HEIGHT - Constant.GROUND_HEIGHT - i * PIPE_HEIGHT,
int count = (height - PIPE_HEAD_HEIGHT - Constant.GROUND_HEIGHT) / PIPE_HEIGHT + 1; null);
// 绘制水管的主体 }
for (int i = 0; i < count; i++) { // 绘制水管的顶部
g.drawImage(imgs[0], x, Constant.FRAME_HEIGHT - PIPE_HEIGHT - Constant.GROUND_HEIGHT - i * PIPE_HEIGHT, g.drawImage(imgs[2], x - ((PIPE_HEAD_WIDTH - width) >> 1), Constant.FRAME_HEIGHT - height, null);
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;
// 绘制悬浮的普通水管 // 绘制水管的上顶部
private void drawHoverNormal(Graphics g) { g.drawImage(imgs[2], x - ((PIPE_HEAD_WIDTH - width) >> 1), y, null);
// 拼接的个数 // 绘制水管的主体
int count = (height - 2 * PIPE_HEAD_HEIGHT) / PIPE_HEIGHT + 1; for (int i = 0; i < count; i++) {
// 绘制水管的上顶部 g.drawImage(imgs[0], x, y + i * PIPE_HEIGHT + PIPE_HEAD_HEIGHT, null);
g.drawImage(imgs[2], x - ((PIPE_HEAD_WIDTH - width) >> 1), y, null); }
// 绘制水管的主体 // 绘制水管的下底部
for (int i = 0; i < count; i++) { int y = this.y + height - PIPE_HEAD_HEIGHT;
g.drawImage(imgs[0], x, y + i * PIPE_HEIGHT + PIPE_HEAD_HEIGHT, null); g.drawImage(imgs[1], x - ((PIPE_HEAD_WIDTH - width) >> 1), y, 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) {// 水管完全离开了窗口
private void pipeLogic() { visible = false;
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;
* @return 若完全出现则返回true,否则返回false }
*/
public boolean isInFrame() { // 获取水管的x坐标
return x + width < Constant.FRAME_WIDTH; public int getX() {
} return x;
}
// 各参数的写入与获取
public void setX(int x) { // 获取水管的碰撞矩形
this.x = x; public Rectangle getPipeRect() {
} return pipeRect;
}
public void setY(int y) {
this.y = y; // 各参数的写入与获取
} // public void setX(int x) {
// this.x = x;
public void setType(int type) { // }
this.type = type; //
} // public void setY(int y) {
// this.y = y;
public void setHeight(int height) { // }
this.height = height; //
} // public void setType(int type) {
// this.type = type;
public void setVisible(boolean visible) { // }
this.visible = visible; //
} // public void setHeight(int height) {
// this.height = height;
public int getX() { // }
return x; //
} // public void setVisible(boolean visible) {
// this.visible = visible;
// 获取水管的碰撞矩形 // }
public Rectangle getPipeRect() {
return pipeRect; }
}
}
\ No newline at end of file
...@@ -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,9 +3,12 @@ package com.bird.util; ...@@ -3,9 +3,12 @@ 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;
/** /**
* 常量类 * 常量类
* *
* @author Kingyu 后续优化可写入数据库或文件中,便于修改 * @author Kingyu 后续优化可写入数据库或文件中,便于修改
*/ */
...@@ -18,15 +21,13 @@ public class Constant { ...@@ -18,15 +21,13 @@ 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",
"resources/img/4.png", "resources/img/5.png", "resources/img/6.png", "resources/img/7.png" }, "resources/img/4.png", "resources/img/5.png", "resources/img/6.png", "resources/img/7.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, 由窗口宽度、水管宽度、水管间距算得
\ No newline at end of file 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;
...@@ -11,76 +11,81 @@ import javax.imageio.ImageIO; ...@@ -11,76 +11,81 @@ import javax.imageio.ImageIO;
/** /**
* 工具类,游戏中用到的工具都在此类 * 工具类,游戏中用到的工具都在此类
*
* @author Kingyu
* *
* @author Kingyu
*/ */
public class GameUtil { 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 图片路径 public static int getStringWidth(Font font, String str) {
* @return AffineTransform affinetransform = new AffineTransform();
*/ FontRenderContext frc = new FontRenderContext(affinetransform, true, true);
public static BufferedImage loadBUfferedImage(String imgPath) { return (int) (font.getStringBounds(str, frc).getWidth());
try { }
return ImageIO.read(new FileInputStream(imgPath));
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/** public static int getStringHeight(Font font, String str) {
* 判断任意概率的概率性事件是否发生 AffineTransform affinetransform = new AffineTransform();
* FontRenderContext frc = new FontRenderContext(affinetransform, true, true);
* @param numerator 分子,不小于0的值 return (int) (font.getStringBounds(str, frc).getHeight());
* @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;
}
/** // 于屏幕x轴中央、y轴3/5处绘制图像
* 返回指定区间的一个随机数 public static void drawTitle(BufferedImage image, Graphics g) {
* int x = Constant.FRAME_WIDTH - image.getWidth() >> 1;
* @param min 区间最小值,包含 int y = Constant.FRAME_HEIGHT / 5 * 3;
* @param max 区间最大值,不包含 g.drawImage(image, x, y, null);
* @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;
}
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; 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;
...@@ -10,51 +9,44 @@ import sun.audio.AudioStream; ...@@ -10,51 +9,44 @@ import sun.audio.AudioStream;
/** /**
* 音乐工具类 * 音乐工具类
* *
* @author Kingyu wav音频:JDK提供的类可直接解码 mp3音频:JDK没有提供支持,需要使用第三方的工具包 * @author Kingyu
* * wav音频:JDK提供的类可直接解码 mp3音频:JDK没有提供支持,需要使用第三方的工具包
*/ */
public class MusicUtil { public class MusicUtil {
private static AudioStream fly; private static AudioStream fly;
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; public static void playFly() {
private static InputStream scoreIn; try {
// create an AudioStream from the InputStream
//wav播放 InputStream flyIn = new FileInputStream("resources/wav/fly.wav");
public static void playFly() { fly = new AudioStream(flyIn);
try { } catch (IOException ignored) {
// create an audiostream from the inputstream }
flyIn = new FileInputStream("resources/wav/fly.wav"); AudioPlayer.player.start(fly);
fly = new AudioStream(flyIn); }
} catch (FileNotFoundException fnfe) {
} catch (IOException ioe) { public static void playCrash() {
} try {
AudioPlayer.player.start(fly); // create an AudioStream from the InputStream
} InputStream crashIn = new FileInputStream("resources/wav/crash.wav");
crash = new AudioStream(crashIn);
public static void playCrash() { } catch (IOException ignored) {
try { }
// create an audiostream from the inputstream AudioPlayer.player.start(crash);
crashIn = new FileInputStream("resources/wav/crash.wav"); }
crash = new AudioStream(crashIn);
} catch (FileNotFoundException fnfe) { public static void playScore() {
} catch (IOException ioe) { try {
} // create an AudioStream from the InputStream
AudioPlayer.player.start(crash); InputStream scoreIn = new FileInputStream("resources/wav/score.wav");
} score = new AudioStream(scoreIn);
} catch (IOException ignored) {
public static void playScore() { }
try { AudioPlayer.player.start(score);
// 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);
}
} }
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